Compare commits

..

47 Commits

Author SHA1 Message Date
Max Kellermann
96a31f554a release v0.20.2 2017-01-15 01:28:00 +01:00
Max Kellermann
d14ec6aea5 output/Thread: reconfigure ConvertFilter for its new input AudioFormat
If the input AudioFormat changes but the out_audio_format doesn't
change (e.g. because there is a fixed "format" setting in this
"audio_output" section), the ConvertFilter needs to be reconfigured.
This didn't happen, resulting in awful static noise after changing
songs.
2017-01-15 01:24:17 +01:00
Max Kellermann
917cedf893 output/Thread: move AudioFormat logging code around 2017-01-15 01:23:49 +01:00
Max Kellermann
193dd71600 output/Thread: remember the original filter audio format in local variable 2017-01-15 01:21:14 +01:00
Max Kellermann
6c293a3d7f lib/nfs: add more API documentation 2017-01-15 00:58:49 +01:00
Max Kellermann
e847ddf011 DetachedSong: compare start_time and end_time in IsSame()
This method is used by DecoderControl::IsCurrentSong(), which is used
by the player thread to check whether the current decoder instance can
be reused to seek.  When switching to another song in the same CUE
sheet, previously DetachedSong::IsSame() returned true, and thus the
old decoder instance was used for the new song, not considering the
new end_time.  This led to the old decoder quickly quitting.
2017-01-15 00:54:25 +01:00
Max Kellermann
7e8b448985 input/alsa: set period_size=buffer_size/4
This way, we have four periods instead of the default of two.  With
only two periods, we don't get woken up often enough, and we
frequently encounter buffer overruns.  With four periods, we have more
time to breathe, and the buffer overruns magically disappear.
2017-01-14 21:50:28 +01:00
Max Kellermann
d1f3a87c08 input/alsa: remove the start_threshold setting
This setting is mostly useless for capture devices.  There's no point
in configuring it.
2017-01-14 21:47:37 +01:00
Max Kellermann
9f8145e590 input/alsa: dump buffer/period sizes 2017-01-14 21:09:57 +01:00
Steven O'Brien
791efc171a input/alsa: enable non-blocking mode 2017-01-14 20:59:57 +01:00
Steven O'Brien
144312a525 input/alsa: handle EAGAIN 2017-01-14 20:59:23 +01:00
Max Kellermann
92684112ed input/alsa: call snd_pcm_start() after snd_pcm_prepare()
This is necessary because we'll never get woken up again by
epoll_wait() after a buffer overrun recovery, unless we start the PCM
explicitly before returning to the I/O loop.
2017-01-14 20:58:30 +01:00
Max Kellermann
ef114ee6cb input/alsa: improve logging in Recover()
Copy yet more code from the ALSA output plugin.
2017-01-14 20:52:41 +01:00
Max Kellermann
667f209742 input/alsa: check snd_pcm_state() in Recover()
Copy some good code from the ALSA output plugin.
2017-01-14 20:51:51 +01:00
Max Kellermann
4ad0747c78 output/alsa: explicitly mention all snd_pcm_state() enums
I want a compiler warning when a new state needs to be considered
here.
2017-01-14 20:49:15 +01:00
Max Kellermann
c5cf66402c input/alsa: make two attributes "const" 2017-01-13 20:26:36 +01:00
Max Kellermann
05417049eb input/alsa: clear sockets from within IOThread
Fixes assertion failure in implicit destructor.
2017-01-13 20:17:16 +01:00
Max Kellermann
c7b0c46d9f output/recorder: fix typo in variable name
Fixes the dreaded error "Failed to create : No such file or
directory".

 https://bugs.musicpd.org/view.php?id=4625
2017-01-12 21:36:32 +01:00
Max Kellermann
df578c91ad output/alsa: log DoP mode 2017-01-11 22:50:40 +01:00
Max Kellermann
70008c47c9 output/alsa: support DSD_U16 2017-01-11 22:47:21 +01:00
Max Kellermann
938affef32 pcm/export: support DSD_U16 2017-01-11 22:47:12 +01:00
Max Kellermann
a3c33000ee pcm/Dsd32: include cleanup 2017-01-11 22:47:12 +01:00
Max Kellermann
1e54b7b294 test/test_pcm: fix the DSD_U32 byte order
The unit test was wrong as well.  D'oh!
2017-01-11 22:39:23 +01:00
Max Kellermann
cc0dbcf3f4 pcm/Dsd32: fix the byte order
The byte order of DSD_U32 was wrong from the start.  The oldest bits
must be in the MSB, not in the LSB, according to
snd_pcm_format_descriptions in alsa-lib.
2017-01-11 22:25:54 +01:00
Max Kellermann
c5a2cadccc pcm/Export: convert to class, make members private 2017-01-11 21:48:43 +01:00
Max Kellermann
9aa43416b6 pcm/dop: remove unnecessary assertions 2017-01-11 21:48:43 +01:00
Max Kellermann
8364029db8 output/alsa: move code to PlayRaw() 2017-01-11 21:38:05 +01:00
Max Kellermann
d842d21be0 util/ReusableArray: add method GetCapacity() 2017-01-11 20:37:12 +01:00
Max Kellermann
3514fd2433 util/ReusableArray: add move constructor/operator 2017-01-11 20:37:12 +01:00
Max Kellermann
6778ff27ea util/ReusableArray: use C++11 initializers 2017-01-11 20:33:01 +01:00
Max Kellermann
f32315d699 pcm/Export: remove obsolete gcc warning suppression 2017-01-11 20:31:48 +01:00
Max Kellermann
8b754b24b6 pcm/Buffer: update API documentation 2017-01-11 20:24:32 +01:00
Max Kellermann
b1bee9ff38 test/test_pcm: enable the DSD unit tests
These were disabled by accident.
2017-01-11 20:06:10 +01:00
Max Kellermann
569be2d402 test/test_pcm_export: fix TestDop() sample rate results 2017-01-11 20:06:10 +01:00
Max Kellermann
78a73eac53 pcm/Export: add (dummy) method Cancel()
We'll have some code for it soon.
2017-01-11 15:41:28 +01:00
Max Kellermann
533cb99c33 output/Source: reset all filters in Cancel() 2017-01-11 15:39:18 +01:00
Max Kellermann
79726940dc output/Source: un-inline Cancel() 2017-01-11 15:39:00 +01:00
Max Kellermann
27c7891169 filter/Internal: add method Reset() 2017-01-11 15:34:25 +01:00
Max Kellermann
7a3a793a12 decoder/Bridge: call PcmConvert::Reset() after seeking 2017-01-11 15:32:57 +01:00
Max Kellermann
8088469eca pcm/Convert: add method Reset() 2017-01-11 15:30:30 +01:00
Max Kellermann
3dcb082015 pcm/Resampler: add method Reset()
Hook for src_reset(), not yet used.
2017-01-11 15:26:48 +01:00
Max Kellermann
bece023028 pcm/PcmDsd: move Dsd8To32() to Dsd32.cxx 2017-01-11 15:22:43 +01:00
Max Kellermann
9c4df66925 pcm/Export: halve the sample rate for DoP
Move this sample rate fixup from the ALSA output plugin to PcmExport,
where it belongs.
2017-01-11 10:33:23 +01:00
Max Kellermann
2b43ceb6c6 pcm/Export: DSD_U32 quarters the sample rate
DSD_U32 packs four bytes instead of one large "sample", thus the
sample rate is one quarter of the input sample rate.  This fixes a
rather critical DSD_U32 playback problem.
2017-01-11 10:14:41 +01:00
Max Kellermann
c143adba91 pcm/Export: add CalcOutputSampleRate(), CalcInputSampleRate()
Prepare for DSD sample rate fixups.
2017-01-10 23:48:26 +01:00
Max Kellermann
142fdc8d86 decoder/flac: add options "probesize" and "analyzeduration"
https://bugs.musicpd.org/view.php?id=3876
2017-01-10 23:05:04 +01:00
Max Kellermann
67778dcd3d configure.ac: prepare for 0.20.2 2017-01-10 23:01:42 +01:00
43 changed files with 842 additions and 175 deletions

View File

@@ -575,6 +575,8 @@ PCM_LIBS = \
if ENABLE_DSD
libpcm_a_SOURCES += \
src/pcm/Dsd16.cxx src/pcm/Dsd16.hxx \
src/pcm/Dsd32.cxx src/pcm/Dsd32.hxx \
src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
endif

17
NEWS
View File

@@ -1,3 +1,20 @@
ver 0.20.2 (2017/01/15)
* input
- alsa: fix crash bug
- alsa: fix buffer overruns
* decoder
- flac: add options "probesize" and "analyzeduration"
* resampler
- libsamplerate: reset state after seeking
* output
- fix static noise after changing to a different audio format
- alsa: fix the DSD_U32 sample rate
- alsa: fix the DSD_U32 byte order
- alsa: support DSD_U16
- recorder: fix error "Failed to create : No such file or directory"
* playlist
- cue: fix skipping songs
ver 0.20.1 (2017/01/09)
* input
- curl: fix crash bug

View File

@@ -1,6 +1,6 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.20.1, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.20.2, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=20

View File

@@ -2206,6 +2206,48 @@ run</programlisting>
Decodes various codecs using
<application>FFmpeg</application>.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>analyzeduration</varname>
<parameter>VALUE</parameter>
</entry>
<entry>
Sets the FFmpeg muxer option
<varname>analyzeduration</varname>, which specifies
how many microseconds are analyzed to probe the
input. The <ulink
url="https://ffmpeg.org/ffmpeg-formats.html">FFmpeg
formats documentation</ulink> has more information.
</entry>
</row>
<row>
<entry>
<varname>probesize</varname>
<parameter>VALUE</parameter>
</entry>
<entry>
Sets the FFmpeg muxer option
<varname>probesize</varname>, which specifies
probing size in bytes, i.e. the size of the data to
analyze to get stream information. The <ulink
url="https://ffmpeg.org/ffmpeg-formats.html">FFmpeg
formats documentation</ulink> has more information.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section id="flac_decoder">

View File

@@ -146,7 +146,9 @@ public:
*/
gcc_pure
bool IsSame(const DetachedSong &other) const {
return uri == other.uri;
return uri == other.uri &&
start_time == other.start_time &&
end_time == other.end_time;
}
gcc_pure gcc_nonnull_all

View File

@@ -317,6 +317,9 @@ DecoderBridge::CommandFinished()
dc.pipe->Clear(*dc.buffer);
if (convert != nullptr)
convert->Reset();
timestamp = dc.seek_time.ToDoubleS();
}

View File

@@ -56,6 +56,11 @@ extern "C" {
#include <assert.h>
#include <string.h>
/**
* Muxer options to be passed to avformat_open_input().
*/
static AVDictionary *avformat_options = nullptr;
static AVFormatContext *
FfmpegOpenInput(AVIOContext *pb,
const char *filename,
@@ -67,7 +72,11 @@ FfmpegOpenInput(AVIOContext *pb,
context->pb = pb;
int err = avformat_open_input(&context, filename, fmt, nullptr);
AVDictionary *options = nullptr;
AtScopeExit(&options) { av_dict_free(&options); };
av_dict_copy(&options, avformat_options, 0);
int err = avformat_open_input(&context, filename, fmt, &options);
if (err < 0)
throw MakeFfmpegError(err, "avformat_open_input() failed");
@@ -75,12 +84,30 @@ FfmpegOpenInput(AVIOContext *pb,
}
static bool
ffmpeg_init(gcc_unused const ConfigBlock &block)
ffmpeg_init(const ConfigBlock &block)
{
FfmpegInit();
static constexpr const char *option_names[] = {
"probesize",
"analyzeduration",
};
for (const char *name : option_names) {
const char *value = block.GetBlockValue(name);
if (value != nullptr)
av_dict_set(&avformat_options, name, value, 0);
}
return true;
}
static void
ffmpeg_finish()
{
av_dict_free(&avformat_options);
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
gcc_pure
@@ -967,7 +994,7 @@ static const char *const ffmpeg_mime_types[] = {
const struct DecoderPlugin ffmpeg_decoder_plugin = {
"ffmpeg",
ffmpeg_init,
nullptr,
ffmpeg_finish,
ffmpeg_decode,
nullptr,
nullptr,

View File

@@ -52,6 +52,12 @@ public:
return out_audio_format;
}
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
virtual void Reset() {
}
/**
* Filters a block of PCM data.
*
@@ -60,7 +66,7 @@ public:
* @param src the input buffer
* @return the destination buffer on success (will be
* invalidated by deleting this object or the next FilterPCM()
* call)
* or Reset() call)
*/
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
};

View File

@@ -48,6 +48,13 @@ public:
:Filter(_filter->GetOutAudioFormat()),
filter(std::move(_filter)), convert(std::move(_convert)) {}
void Reset() override {
filter->Reset();
if (convert)
convert->Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
};

View File

@@ -61,6 +61,7 @@ public:
}
/* virtual methods from class Filter */
void Reset() override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
};
@@ -130,6 +131,13 @@ PreparedChainFilter::Open(AudioFormat &in_audio_format)
return chain.release();
}
void
ChainFilter::Reset()
{
for (auto &child : children)
child.filter->Reset();
}
ConstBuffer<void>
ChainFilter::FilterPCM(ConstBuffer<void> src)
{

View File

@@ -52,6 +52,10 @@ public:
void Set(const AudioFormat &_out_audio_format);
void Reset() override {
state.Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
};

View File

@@ -28,6 +28,7 @@
#include "AlsaInputPlugin.hxx"
#include "../InputPlugin.hxx"
#include "../AsyncInputStream.hxx"
#include "event/Call.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx"
@@ -56,31 +57,30 @@ static constexpr unsigned int default_rate = 44100; // cd quality
static constexpr size_t ALSA_MAX_BUFFERED = default_rate * default_channels * 2;
static constexpr size_t ALSA_RESUME_AT = ALSA_MAX_BUFFERED / 2;
/**
* This value should be the same as the read buffer size defined in
* PcmDecoderPlugin.cxx:pcm_stream_decode().
* We use it to calculate how many audio frames to buffer in the alsa driver
* before reading from the device. snd_pcm_readi() blocks until that many
* frames are ready.
*/
static constexpr size_t read_buffer_size = 4096;
class AlsaInputStream final
: public AsyncInputStream,
MultiSocketMonitor, DeferredMonitor {
snd_pcm_t *capture_handle;
size_t frame_size;
/**
* The configured name of the ALSA device.
*/
const std::string device;
snd_pcm_t *const capture_handle;
const size_t frame_size;
ReusableArray<pollfd> pfd_buffer;
public:
AlsaInputStream(EventLoop &loop,
const char *_uri, Mutex &_mutex, Cond &_cond,
const char *_device,
snd_pcm_t *_handle, int _frame_size)
:AsyncInputStream(_uri, _mutex, _cond,
ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
MultiSocketMonitor(loop),
DeferredMonitor(loop),
device(_device),
capture_handle(_handle),
frame_size(_frame_size)
{
@@ -99,6 +99,14 @@ public:
}
~AlsaInputStream() {
/* ClearSocketList must be called from within the
IOThread; if we don't do it manually here, the
~MultiSocketMonitor() will do it in the current
thread */
BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
ClearSocketList();
});
snd_pcm_close(capture_handle);
}
@@ -162,7 +170,7 @@ AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond)
int frame_size = snd_pcm_format_width(format) / 8 * channels;
return new AlsaInputStream(io_thread_get(),
uri, mutex, cond,
handle, frame_size);
device, handle, frame_size);
}
std::chrono::steady_clock::duration
@@ -205,6 +213,9 @@ AlsaInputStream::DispatchSockets()
snd_pcm_sframes_t n_frames;
while ((n_frames = snd_pcm_readi(capture_handle,
w.data, w_frames)) < 0) {
if (n_frames == -EAGAIN)
return;
if (Recover(n_frames) < 0) {
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
cond.broadcast();
@@ -221,20 +232,51 @@ AlsaInputStream::Recover(int err)
{
switch(err) {
case -EPIPE:
LogDebug(alsa_input_domain, "Buffer Overrun");
// drop through
FormatDebug(alsa_input_domain,
"Overrun on ALSA capture device \"%s\"",
device.c_str());
break;
case -ESTRPIPE:
FormatDebug(alsa_input_domain,
"ALSA capture device \"%s\" was suspended",
device.c_str());
break;
}
switch (snd_pcm_state(capture_handle)) {
case SND_PCM_STATE_PAUSED:
err = snd_pcm_pause(capture_handle, /* disable */ 0);
break;
case SND_PCM_STATE_SUSPENDED:
err = snd_pcm_resume(capture_handle);
if (err == -EAGAIN)
return 0;
/* fall-through to snd_pcm_prepare: */
#if GCC_CHECK_VERSION(7,0)
[[fallthrough]];
#endif
case -ESTRPIPE:
case -EINTR:
err = snd_pcm_recover(capture_handle, err, 1);
case SND_PCM_STATE_OPEN:
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
err = snd_pcm_prepare(capture_handle);
if (err == 0)
err = snd_pcm_start(capture_handle);
break;
case SND_PCM_STATE_DISCONNECTED:
break;
case SND_PCM_STATE_PREPARED:
case SND_PCM_STATE_RUNNING:
case SND_PCM_STATE_DRAINING:
/* this is no error, so just keep running */
err = 0;
break;
default:
// something broken somewhere, give up
err = -1;
}
return err;
}
@@ -273,23 +315,63 @@ ConfigureCapture(snd_pcm_t *capture_handle,
throw FormatRuntimeError("Cannot set sample rate (%s)",
snd_strerror(err));
/* period needs to be big enough so that poll() doesn't fire too often,
* but small enough that buffer overruns don't occur if Read() is not
* invoked often enough.
* the calculation here is empirical; however all measurements were
* done using 44100:16:2. When we extend this plugin to support
* other audio formats then this may need to be revisited */
snd_pcm_uframes_t period = read_buffer_size * 2;
int direction = -1;
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
&period, &direction)) < 0)
throw FormatRuntimeError("Cannot set period size (%s)",
snd_strerror(err));
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
unsigned buffer_time_min, buffer_time_max;
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
buffer_time_min, buffer_time_max);
snd_pcm_uframes_t period_size_min, period_size_max;
snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, 0);
snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, 0);
unsigned period_time_min, period_time_max;
snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, 0);
snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, 0);
FormatDebug(alsa_input_domain, "period: size=%u..%u time=%u..%u",
(unsigned)period_size_min, (unsigned)period_size_max,
period_time_min, period_time_max);
/* choose the maximum possible buffer_size ... */
snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params,
buffer_size_max);
/* ... and calculate the period_size to have four periods in
one buffer; this way, we get woken up often enough to avoid
buffer overruns, but not too often */
snd_pcm_uframes_t buffer_size;
if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
snd_pcm_uframes_t period_size = buffer_size / 4;
int direction = -1;
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
&period_size, &direction)) < 0)
throw FormatRuntimeError("Cannot set period size (%s)",
snd_strerror(err));
}
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
throw FormatRuntimeError("Cannot set parameters (%s)",
snd_strerror(err));
snd_pcm_uframes_t alsa_buffer_size;
err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa_buffer_size);
if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
snd_strerror(-err));
snd_pcm_uframes_t alsa_period_size;
err = snd_pcm_hw_params_get_period_size(hw_params, &alsa_period_size,
nullptr);
if (err < 0)
throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
snd_strerror(-err));
FormatDebug(alsa_input_domain, "buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_malloc(&sw_params);
@@ -299,11 +381,6 @@ ConfigureCapture(snd_pcm_t *capture_handle,
snd_pcm_sw_params_free(sw_params);
};
if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
period)) < 0)
throw FormatRuntimeError("unable to set start threshold (%s)",
snd_strerror(err));
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
throw FormatRuntimeError("unable to install sw params (%s)",
snd_strerror(err));
@@ -316,7 +393,8 @@ AlsaInputStream::OpenDevice(const char *device,
snd_pcm_t *capture_handle;
int err;
if ((err = snd_pcm_open(&capture_handle, device,
SND_PCM_STREAM_CAPTURE, 0)) < 0)
SND_PCM_STREAM_CAPTURE,
SND_PCM_NONBLOCK)) < 0)
throw FormatRuntimeError("Failed to open device: %s (%s)",
device, snd_strerror(err));

View File

@@ -24,9 +24,21 @@
#include <exception>
/**
* Callbacks for an asynchronous libnfs operation.
*
* Note that no callback is invoked for cancelled operations.
*/
class NfsCallback {
public:
/**
* The operation completed successfully.
*/
virtual void OnNfsCallback(unsigned status, void *data) = 0;
/**
* An error has occurred.
*/
virtual void OnNfsError(std::exception_ptr &&e) = 0;
};

View File

@@ -35,6 +35,14 @@
struct nfsfh;
class NfsConnection;
/**
* A helper class which helps with reading from a file. It obtains a
* connection lease (#NfsLease), opens the given file, "stats" the
* file, and finally allos you to read its contents.
*
* To get started, derive your class from it and implement the pure
* virtual methods, construct an instance, and call Open().
*/
class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor {
enum class State {
INITIAL,
@@ -63,14 +71,30 @@ public:
void DeferClose();
/**
* Open the file. This method is thread-safe.
*
* Throws std::runtime_error on error.
*/
void Open(const char *uri);
/**
* Attempt to read from the file. This may only be done after
* OnNfsFileOpen() has been called. Only one read operation
* may be performed at a time.
*
* This method is not thread-safe and must be called from
* within the I/O thread.
*
* Throws std::runtime_error on error.
*/
void Read(uint64_t offset, size_t size);
/**
* Cancel the most recent Read() call.
*
* This method is not thread-safe and must be called from
* within the I/O thread.
*/
void CancelRead();
bool IsIdle() const {
@@ -78,8 +102,27 @@ public:
}
protected:
/**
* The file has been opened successfully. It is a regular
* file, and its size is known. It is ready to be read from
* using Read().
*
* This method will be called from within the I/O thread.
*/
virtual void OnNfsFileOpen(uint64_t size) = 0;
/**
* A Read() has completed successfully.
*
* This method will be called from within the I/O thread.
*/
virtual void OnNfsFileRead(const void *data, size_t size) = 0;
/**
* An error has occurred, which can be either while waiting
* for OnNfsFileOpen(), or while waiting for OnNfsFileRead(),
* or if disconnected while idle.
*/
virtual void OnNfsFileError(std::exception_ptr &&e) = 0;
private:

View File

@@ -111,8 +111,7 @@ AudioOutput::Open()
f = source.Open(request.audio_format, *request.pipe,
prepared_replay_gain_filter,
prepared_other_replay_gain_filter,
prepared_filter)
.WithMask(config_audio_format);
prepared_filter);
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
software_mixer_set_filter(*mixer, volume_filter.Get());
@@ -121,14 +120,16 @@ AudioOutput::Open()
name, plugin.name));
}
if (open && f != filter_audio_format) {
const auto cf = f.WithMask(config_audio_format);
if (open && cf != filter_audio_format) {
/* if the filter's output format changes, the output
must be reopened as well */
CloseOutput(true);
open = false;
}
filter_audio_format = f;
filter_audio_format = cf;
if (!open) {
try {
@@ -139,6 +140,27 @@ AudioOutput::Open()
}
open = true;
} else if (f != out_audio_format) {
/* reconfigure the final ConvertFilter for its new
input AudioFormat */
try {
convert_filter_set(convert_filter.Get(),
out_audio_format);
} catch (const std::runtime_error &e) {
Close(false);
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
name, plugin.name));
}
}
if (f != source.GetInputAudioFormat() || f != out_audio_format) {
struct audio_format_string afs1, afs2, afs3;
FormatDebug(output_domain, "converting in=%s -> f=%s -> out=%s",
audio_format_to_string(source.GetInputAudioFormat(),
&afs1),
audio_format_to_string(f, &afs2),
audio_format_to_string(out_audio_format, &afs3));
}
}
@@ -154,6 +176,12 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
name, plugin.name));
}
struct audio_format_string af_string;
FormatDebug(output_domain,
"opened plugin=%s name=\"%s\" audio_format=%s",
plugin.name, name,
audio_format_to_string(out_audio_format, &af_string));
try {
convert_filter_set(convert_filter.Get(), out_audio_format);
} catch (const std::runtime_error &e) {
@@ -177,17 +205,6 @@ AudioOutput::OpenOutputAndConvert(AudioFormat desired_audio_format)
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
name, plugin.name));
}
struct audio_format_string af_string;
FormatDebug(output_domain,
"opened plugin=%s name=\"%s\" audio_format=%s",
plugin.name, name,
audio_format_to_string(out_audio_format, &af_string));
if (source.GetInputAudioFormat() != out_audio_format)
FormatDebug(output_domain, "converting from %s",
audio_format_to_string(source.GetInputAudioFormat(),
&af_string));
}
void

View File

@@ -70,6 +70,22 @@ AudioOutputSource::Close()
CloseFilter();
}
void
AudioOutputSource::Cancel()
{
current_chunk = nullptr;
pipe.Cancel();
if (replay_gain_filter_instance != nullptr)
replay_gain_filter_instance->Reset();
if (other_replay_gain_filter_instance != nullptr)
other_replay_gain_filter_instance->Reset();
if (filter_instance != nullptr)
filter_instance->Reset();
}
void
AudioOutputSource::OpenFilter(AudioFormat audio_format,
PreparedFilter *prepared_replay_gain_filter,

View File

@@ -139,11 +139,7 @@ public:
PreparedFilter *prepared_filter);
void Close();
void Cancel() {
current_chunk = nullptr;
pipe.Cancel();
}
void Cancel();
/**
* Ensure that ReadTag() or PeekData() return any input.

View File

@@ -141,6 +141,7 @@ struct AlsaOutput {
void Open(AudioFormat &audio_format);
void Close();
size_t PlayRaw(ConstBuffer<void> data);
size_t Play(const void *chunk, size_t size);
void Drain();
void Cancel();
@@ -384,7 +385,7 @@ AlsaTryFormatOrByteSwap(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
/**
* Attempts to configure the specified sample format. On DSD_U8
* failure, attempt to switch to DSD_U32.
* failure, attempt to switch to DSD_U32 or DSD_U16.
*/
static int
AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
@@ -393,8 +394,10 @@ AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
int err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
#if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32)
if (err == 0)
if (err == 0) {
params.dsd_u16 = false;
params.dsd_u32 = false;
}
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
/* attempt to switch to DSD_U32 */
@@ -404,6 +407,20 @@ AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
if (err == 0)
params.dsd_u32 = true;
else
fmt = SND_PCM_FORMAT_DSD_U8;
}
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
/* attempt to switch to DSD_U16 */
fmt = IsLittleEndian()
? SND_PCM_FORMAT_DSD_U16_LE
: SND_PCM_FORMAT_DSD_U16_BE;
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
if (err == 0)
params.dsd_u16 = true;
else
fmt = SND_PCM_FORMAT_DSD_U8;
}
#endif
@@ -469,7 +486,6 @@ static void
AlsaSetup(AlsaOutput *ad, AudioFormat &audio_format,
PcmExport::Params &params)
{
unsigned int sample_rate = audio_format.sample_rate;
unsigned int channels = audio_format.channels;
int err;
unsigned retry = MPD_ALSA_RETRY_NR;
@@ -513,18 +529,23 @@ configure_hw:
audio_format.channels = (int8_t)channels;
const unsigned requested_sample_rate =
params.CalcOutputSampleRate(audio_format.sample_rate);
unsigned output_sample_rate = requested_sample_rate;
err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
&sample_rate, nullptr);
&output_sample_rate, nullptr);
if (err < 0)
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
audio_format.sample_rate,
requested_sample_rate,
snd_strerror(-err));
if (sample_rate == 0)
if (output_sample_rate == 0)
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
audio_format.sample_rate);
audio_format.sample_rate = sample_rate;
if (output_sample_rate != requested_sample_rate)
audio_format.sample_rate = params.CalcInputSampleRate(output_sample_rate);
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
@@ -662,7 +683,6 @@ AlsaOutput::SetupDop(const AudioFormat audio_format,
AudioFormat dop_format = audio_format;
dop_format.format = SampleFormat::S24_P32;
dop_format.sample_rate /= 2;
const AudioFormat check = dop_format;
@@ -694,11 +714,12 @@ AlsaOutput::SetupOrDop(AudioFormat &audio_format, PcmExport::Params &params)
std::exception_ptr dop_error;
if (dop && audio_format.format == SampleFormat::DSD) {
try {
SetupDop(audio_format, params);
params.dop = true;
SetupDop(audio_format, params);
return;
} catch (...) {
dop_error = std::current_exception();
params.dop = false;
}
}
@@ -742,6 +763,11 @@ AlsaOutput::Open(AudioFormat &audio_format)
GetDevice()));
}
#ifdef ENABLE_DSD
if (params.dop)
FormatDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
#endif
pcm_export->Open(audio_format.format,
audio_format.channels,
params);
@@ -777,6 +803,7 @@ AlsaOutput::Recover(int err)
#if GCC_CHECK_VERSION(7,0)
[[fallthrough]];
#endif
case SND_PCM_STATE_OPEN:
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
period_position = 0;
@@ -785,12 +812,11 @@ AlsaOutput::Recover(int err)
case SND_PCM_STATE_DISCONNECTED:
break;
/* this is no error, so just keep running */
case SND_PCM_STATE_PREPARED:
case SND_PCM_STATE_RUNNING:
case SND_PCM_STATE_DRAINING:
err = 0;
break;
default:
/* unknown state, do nothing */
break;
}
return err;
@@ -822,6 +848,8 @@ AlsaOutput::Cancel()
must_prepare = true;
snd_pcm_drop(pcm);
pcm_export->Reset();
}
inline void
@@ -831,6 +859,36 @@ AlsaOutput::Close()
delete[] silence;
}
inline size_t
AlsaOutput::PlayRaw(ConstBuffer<void> data)
{
if (data.IsEmpty())
return 0;
assert(data.size % out_frame_size == 0);
const size_t n_frames = data.size / out_frame_size;
assert(n_frames > 0);
while (true) {
const auto frames_written = snd_pcm_writei(pcm, data.data,
n_frames);
if (frames_written > 0) {
period_position = (period_position + frames_written)
% period_frames;
return frames_written * out_frame_size;
}
if (frames_written < 0 && frames_written != -EAGAIN &&
frames_written != -EINTR &&
Recover(frames_written) < 0)
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
snd_strerror(-frames_written));
}
}
inline size_t
AlsaOutput::Play(const void *chunk, size_t size)
{
@@ -856,29 +914,8 @@ AlsaOutput::Play(const void *chunk, size_t size)
been played */
return size;
chunk = e.data;
size = e.size;
assert(size % out_frame_size == 0);
size /= out_frame_size;
assert(size > 0);
while (true) {
snd_pcm_sframes_t ret = snd_pcm_writei(pcm, chunk, size);
if (ret > 0) {
period_position = (period_position + ret)
% period_frames;
size_t bytes_written = ret * out_frame_size;
return pcm_export->CalcSourceSize(bytes_written);
}
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
Recover(ret) < 0)
throw FormatRuntimeError("snd_pcm_writei() failed: %s",
snd_strerror(-ret));
}
const size_t bytes_written = PlayRaw(e);
return pcm_export->CalcSourceSize(bytes_written);
}
typedef AudioOutputWrapper<AlsaOutput> Wrapper;

View File

@@ -659,6 +659,10 @@ OssOutput::Cancel()
ioctl(fd, SNDCTL_DSP_RESET, 0);
DoClose();
}
#ifdef AFMT_S24_PACKED
pcm_export->Reset();
#endif
}
inline size_t

View File

@@ -274,7 +274,7 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
assert(path.IsNull());
assert(file == nullptr);
FileOutputStream *new_file = new FileOutputStream(path);
FileOutputStream *new_file = new FileOutputStream(new_path);
AudioFormat new_audio_format = effective_audio_format;

62
src/pcm/Dsd16.cxx Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Dsd16.hxx"
#include "PcmBuffer.hxx"
#include "util/ConstBuffer.hxx"
/**
* Construct a 16 bit integer from two bytes.
*/
static constexpr inline uint16_t
Construct16(uint8_t a, uint8_t b)
{
/* "a" is the oldest byte, which must be in the most
significant byte */
return uint16_t(b) | (uint16_t(a) << 8);
}
static constexpr inline uint16_t
Dsd8To16Sample(const uint8_t *src, unsigned channels)
{
return Construct16(src[0], src[channels]);
}
ConstBuffer<uint16_t>
Dsd8To16(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
{
const size_t in_frames = _src.size / channels;
const size_t out_frames = in_frames / 2;
const size_t out_samples = out_frames * channels;
const uint8_t *src = _src.data;
uint16_t *const dest0 = buffer.GetT<uint16_t>(out_samples);
uint16_t *dest = dest0;
for (size_t i = 0; i < out_frames; ++i) {
for (size_t c = 0; c < channels; ++c)
*dest++ = Dsd8To16Sample(src++, channels);
src += channels;
}
return {dest0, out_samples};
}

36
src/pcm/Dsd16.hxx Normal file
View File

@@ -0,0 +1,36 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_PCM_DSD_16_HXX
#define MPD_PCM_DSD_16_HXX
#include "check.h"
#include <stdint.h>
template<typename T> struct ConstBuffer;
class PcmBuffer;
/**
* Convert DSD_U8 to DSD_U16 (native endian, oldest bits in MSB).
*/
ConstBuffer<uint16_t>
Dsd8To16(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> src);
#endif

64
src/pcm/Dsd32.cxx Normal file
View File

@@ -0,0 +1,64 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "Dsd32.hxx"
#include "PcmBuffer.hxx"
#include "util/ConstBuffer.hxx"
/**
* Construct a 32 bit integer from four bytes.
*/
static constexpr inline uint32_t
Construct32(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
/* "a" is the oldest byte, which must be in the most
significant byte */
return uint32_t(d) | (uint32_t(c) << 8) |
(uint32_t(b) << 16) | (uint32_t(a) << 24);
}
static constexpr inline uint32_t
Dsd8To32Sample(const uint8_t *src, unsigned channels)
{
return Construct32(src[0], src[channels],
src[2 * channels], src[3 * channels]);
}
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
{
const size_t in_frames = _src.size / channels;
const size_t out_frames = in_frames / 4;
const size_t out_samples = out_frames * channels;
const uint8_t *src = _src.data;
uint32_t *const dest0 = buffer.GetT<uint32_t>(out_samples);
uint32_t *dest = dest0;
for (size_t i = 0; i < out_frames; ++i) {
for (size_t c = 0; c < channels; ++c)
*dest++ = Dsd8To32Sample(src++, channels);
src += 3 * channels;
}
return {dest0, out_samples};
}

36
src/pcm/Dsd32.hxx Normal file
View File

@@ -0,0 +1,36 @@
/*
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_PCM_DSD_32_HXX
#define MPD_PCM_DSD_32_HXX
#include "check.h"
#include <stdint.h>
template<typename T> struct ConstBuffer;
class PcmBuffer;
/**
* Convert DSD_U8 to DSD_U32 (native endian, oldest bits in MSB).
*/
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> src);
#endif

View File

@@ -65,6 +65,12 @@ GluePcmResampler::Close()
resampler->Close();
}
void
GluePcmResampler::Reset()
{
resampler->Reset();
}
ConstBuffer<void>
GluePcmResampler::Resample(ConstBuffer<void> src)
{

View File

@@ -55,6 +55,11 @@ public:
return output_sample_format;
}
/**
* @see PcmResampler::Reset()
*/
void Reset();
ConstBuffer<void> Resample(ConstBuffer<void> src);
};

View File

@@ -114,6 +114,12 @@ LibsampleratePcmResampler::Close()
state = src_delete(state);
}
void
LibsampleratePcmResampler::Reset()
{
src_reset(state);
}
inline ConstBuffer<float>
LibsampleratePcmResampler::Resample2(ConstBuffer<float> src)
{

View File

@@ -44,6 +44,7 @@ class LibsampleratePcmResampler final : public PcmResampler {
public:
AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
void Close() override;
void Reset() override;
ConstBuffer<void> Resample(ConstBuffer<void> src) override;
private:

View File

@@ -40,7 +40,7 @@ public:
/**
* Get the buffer, and guarantee a minimum size. This buffer becomes
* invalid with the next pcm_buffer_get() call.
* invalid with the next Get() call.
*
* This function will never return nullptr, even if size is
* zero, because the PCM library uses the nullptr return value

View File

@@ -117,6 +117,17 @@ PcmConvert::Close()
#endif
}
void
PcmConvert::Reset()
{
if (enable_resampler)
resampler.Reset();
#ifdef ENABLE_DSD
dsd.Reset();
#endif
}
ConstBuffer<void>
PcmConvert::Convert(ConstBuffer<void> buffer)
{

View File

@@ -67,6 +67,11 @@ public:
*/
void Close();
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
void Reset();
/**
* Converts PCM data between two audio formats.
*

View File

@@ -44,8 +44,6 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
ConstBuffer<uint8_t> _src)
{
assert(audio_valid_channel_count(channels));
assert(!_src.IsNull());
assert(_src.size > 0);
assert(_src.size % channels == 0);
const unsigned num_src_samples = _src.size;

View File

@@ -71,41 +71,3 @@ PcmDsd::ToFloat(unsigned channels, ConstBuffer<uint8_t> src)
return { dest, num_samples };
}
/**
* Construct a 32 bit integer from four bytes.
*/
static constexpr inline uint32_t
Construct32(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
return uint32_t(a) | (uint32_t(b) << 8) |
(uint32_t(c) << 16) | (uint32_t(d) << 24);
}
static constexpr inline uint32_t
Dsd8To32Sample(const uint8_t *src, unsigned channels)
{
return Construct32(src[0], src[channels],
src[2 * channels], src[3 * channels]);
}
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> _src)
{
const size_t in_frames = _src.size / channels;
const size_t out_frames = in_frames / 4;
const size_t out_samples = out_frames * channels;
const uint8_t *src = _src.data;
uint32_t *const dest0 = buffer.GetT<uint32_t>(out_samples);
uint32_t *dest = dest0;
for (size_t i = 0; i < out_frames; ++i) {
for (size_t c = 0; c < channels; ++c)
*dest++ = Dsd8To32Sample(src++, channels);
src += 3 * channels;
}
return {dest0, out_samples};
}

View File

@@ -48,10 +48,4 @@ public:
ConstBuffer<uint8_t> src);
};
/**
* Convert DSD_U8 to DSD_U32 (native endian).
*/
ConstBuffer<uint32_t>
Dsd8To32(PcmBuffer &buffer, unsigned channels, ConstBuffer<uint8_t> src);
#endif

View File

@@ -25,6 +25,8 @@
#include "util/ConstBuffer.hxx"
#ifdef ENABLE_DSD
#include "Dsd16.hxx"
#include "Dsd32.hxx"
#include "PcmDsd.hxx"
#include "PcmDop.hxx"
#endif
@@ -41,9 +43,15 @@ PcmExport::Open(SampleFormat sample_format, unsigned _channels,
: SampleFormat::UNDEFINED;
#ifdef ENABLE_DSD
assert(!params.dsd_u32 || !params.dop);
assert((params.dsd_u16 + params.dsd_u32 + params.dop) <= 1);
assert(!params.dop || audio_valid_channel_count(_channels));
dsd_u16 = params.dsd_u16 && sample_format == SampleFormat::DSD;
if (dsd_u16)
/* after the conversion to DSD_U16, the DSD samples
are stuffed inside fake 16 bit samples */
sample_format = SampleFormat::S16;
dsd_u32 = params.dsd_u32 && sample_format == SampleFormat::DSD;
if (dsd_u32)
/* after the conversion to DSD_U32, the DSD samples
@@ -55,8 +63,6 @@ PcmExport::Open(SampleFormat sample_format, unsigned _channels,
/* after the conversion to DoP, the DSD
samples are stuffed inside fake 24 bit samples */
sample_format = SampleFormat::S24_P32;
#else
(void)_channels;
#endif
shift8 = params.shift8 && sample_format == SampleFormat::S24_P32;
@@ -84,6 +90,9 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
return audio_format.channels * 3;
#ifdef ENABLE_DSD
if (dsd_u16)
return channels * 2;
if (dsd_u32)
return channels * 4;
@@ -98,6 +107,46 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
return audio_format.GetFrameSize();
}
unsigned
PcmExport::Params::CalcOutputSampleRate(unsigned sample_rate) const
{
#ifdef ENABLE_DSD
if (dsd_u16)
/* DSD_U16 combines two 8-bit "samples" in one 16-bit
"sample" */
sample_rate /= 2;
if (dsd_u32)
/* DSD_U32 combines four 8-bit "samples" in one 32-bit
"sample" */
sample_rate /= 4;
if (dop)
/* DoP packs two 8-bit "samples" in one 24-bit
"sample" */
sample_rate /= 2;
#endif
return sample_rate;
}
unsigned
PcmExport::Params::CalcInputSampleRate(unsigned sample_rate) const
{
#ifdef ENABLE_DSD
if (dsd_u16)
sample_rate *= 2;
if (dsd_u32)
sample_rate *= 4;
if (dop)
sample_rate *= 2;
#endif
return sample_rate;
}
ConstBuffer<void>
PcmExport::Export(ConstBuffer<void> data)
{
@@ -106,6 +155,11 @@ PcmExport::Export(ConstBuffer<void> data)
alsa_channel_order, channels);
#ifdef ENABLE_DSD
if (dsd_u16)
data = Dsd8To16(dop_buffer, channels,
ConstBuffer<uint8_t>::FromVoid(data))
.ToVoid();
if (dsd_u32)
data = Dsd8To32(dop_buffer, channels,
ConstBuffer<uint8_t>::FromVoid(data))

View File

@@ -31,18 +31,7 @@ template<typename T> struct ConstBuffer;
* outside of MPD. It has a few more options to tweak the binary
* representation which are not supported by the pcm_convert library.
*/
struct PcmExport {
struct Params {
bool alsa_channel_order = false;
#ifdef ENABLE_DSD
bool dsd_u32 = false;
bool dop = false;
#endif
bool shift8 = false;
bool pack24 = false;
bool reverse_endian = false;
};
class PcmExport {
/**
* This buffer is used to reorder channels.
*
@@ -90,6 +79,11 @@ struct PcmExport {
SampleFormat alsa_channel_order;
#ifdef ENABLE_DSD
/**
* Convert DSD (U8) to DSD_U16?
*/
bool dsd_u16;
/**
* Convert DSD (U8) to DSD_U32?
*/
@@ -121,6 +115,34 @@ struct PcmExport {
*/
uint8_t reverse_endian;
public:
struct Params {
bool alsa_channel_order = false;
#ifdef ENABLE_DSD
bool dsd_u16 = false;
bool dsd_u32 = false;
bool dop = false;
#endif
bool shift8 = false;
bool pack24 = false;
bool reverse_endian = false;
/**
* Calculate the output sample rate, given a specific input
* sample rate. Usually, both are the same; however, with
* DSD_U32, four input bytes (= 4 * 8 bits) are combined to
* one output word (32 bits), dividing the sample rate by 4.
*/
gcc_pure
unsigned CalcOutputSampleRate(unsigned input_sample_rate) const;
/**
* The inverse of CalcOutputSampleRate().
*/
gcc_pure
unsigned CalcInputSampleRate(unsigned output_sample_rate) const;
};
/**
* Open the object.
*
@@ -134,6 +156,12 @@ struct PcmExport {
void Open(SampleFormat sample_format, unsigned channels,
Params params);
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
void Reset() {
}
/**
* Calculate the size of one output frame.
*/

View File

@@ -54,6 +54,12 @@ public:
*/
virtual void Close() = 0;
/**
* Reset the filter's state, e.g. drop/flush buffers.
*/
virtual void Reset() {
}
/**
* Resamples a block of PCM data.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013 Max Kellermann <max@duempel.org>
* Copyright (C) 2013-2017 Max Kellermann <max@duempel.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -30,10 +30,12 @@
#ifndef REUSABLE_ARRAY_HXX
#define REUSABLE_ARRAY_HXX
#include <stddef.h>
#include "Compiler.h"
#include <utility>
#include <stddef.h>
/**
* Manager for a temporary array which grows as needed. This attempts
* to reduce the number of consecutive heap allocations and
@@ -44,19 +46,30 @@
*/
template<typename T, size_t M=1>
class ReusableArray {
T *buffer;
size_t capacity;
T *buffer = nullptr;
size_t capacity = 0;
public:
ReusableArray():buffer(nullptr), capacity(0) {}
ReusableArray() = default;
ReusableArray(const ReusableArray &other) = delete;
ReusableArray &operator=(const ReusableArray &other) = delete;
ReusableArray(ReusableArray &&src)
:buffer(std::exchange(src.buffer, nullptr)),
capacity(std::exchange(src.capacity, 0)) {}
ReusableArray &operator=(const ReusableArray &&src) {
std::swap(buffer, src.buffer);
std::swap(capacity, src.capacity);
return *this;
}
~ReusableArray() {
delete[] buffer;
}
size_t GetCapacity() const {
return capacity;
}
/**
* Free resources allocated by this object. This invalidates
* the buffer returned by Get().

View File

@@ -20,6 +20,8 @@
#ifndef MPD_TEST_PCM_ALL_HXX
#define MPD_TEST_PCM_ALL_HXX
#include "check.h"
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
@@ -126,6 +128,7 @@ class PcmExportTest : public CppUnit::TestFixture {
CPPUNIT_TEST(TestPack24);
CPPUNIT_TEST(TestReverseEndian);
#ifdef ENABLE_DSD
CPPUNIT_TEST(TestDsdU16);
CPPUNIT_TEST(TestDsdU32);
CPPUNIT_TEST(TestDop);
#endif
@@ -137,6 +140,7 @@ public:
void TestPack24();
void TestReverseEndian();
#ifdef ENABLE_DSD
void TestDsdU16();
void TestDsdU32();
void TestDop();
#endif

View File

@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "test_pcm_all.hxx"
#include "test_pcm_util.hxx"
#include "pcm/PcmDither.cxx"

View File

@@ -35,6 +35,9 @@ PcmExportTest::TestShift8()
PcmExport::Params params;
params.shift8 = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
PcmExport e;
e.Open(SampleFormat::S24_P32, 2, params);
@@ -71,6 +74,9 @@ PcmExportTest::TestPack24()
PcmExport::Params params;
params.pack24 = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
PcmExport e;
e.Open(SampleFormat::S24_P32, 2, params);
@@ -97,6 +103,9 @@ PcmExportTest::TestReverseEndian()
PcmExport::Params params;
params.reverse_endian = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
PcmExport e;
e.Open(SampleFormat::S8, 2, params);
@@ -117,6 +126,37 @@ PcmExportTest::TestReverseEndian()
#ifdef ENABLE_DSD
void
PcmExportTest::TestDsdU16()
{
static constexpr uint8_t src[] = {
0x01, 0x23, 0x45, 0x67,
0x89, 0xab, 0xcd, 0xef,
0x11, 0x22, 0x33, 0x44,
0x55, 0x66, 0x77, 0x88,
};
static constexpr uint16_t expected[] = {
0x0145, 0x2367,
0x89cd, 0xabef,
0x1133, 0x2244,
0x5577, 0x6688,
};
PcmExport::Params params;
params.dsd_u16 = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(705600u), 352800u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(352800u), 705600u);
PcmExport e;
e.Open(SampleFormat::DSD, 2, params);
auto dest = e.Export({src, sizeof(src)});
CPPUNIT_ASSERT_EQUAL(sizeof(expected), dest.size);
CPPUNIT_ASSERT(memcmp(dest.data, expected, dest.size) == 0);
}
void
PcmExportTest::TestDsdU32()
{
@@ -128,15 +168,18 @@ PcmExportTest::TestDsdU32()
};
static constexpr uint32_t expected[] = {
0xcd894501,
0xefab6723,
0x77553311,
0x88664422,
0x014589cd,
0x2367abef,
0x11335577,
0x22446688,
};
PcmExport::Params params;
params.dsd_u32 = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(705600u), 176400u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(176400u), 705600u);
PcmExport e;
e.Open(SampleFormat::DSD, 2, params);
@@ -163,6 +206,9 @@ PcmExportTest::TestDop()
PcmExport::Params params;
params.dop = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(705600u), 352800u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(352800u), 705600u);
PcmExport e;
e.Open(SampleFormat::DSD, 2, params);
@@ -192,6 +238,9 @@ TestAlsaChannelOrder51()
PcmExport::Params params;
params.alsa_channel_order = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
PcmExport e;
e.Open(F, 6, params);
@@ -219,6 +268,9 @@ TestAlsaChannelOrder71()
PcmExport::Params params;
params.alsa_channel_order = true;
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
PcmExport e;
e.Open(F, 8, params);

View File

@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "test_pcm_all.hxx"
#include "Compiler.h"

View File

@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "test_pcm_all.hxx"
#include "test_pcm_util.hxx"
#include "pcm/PcmPack.hxx"