Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a31f554a | ||
|
|
d14ec6aea5 | ||
|
|
917cedf893 | ||
|
|
193dd71600 | ||
|
|
6c293a3d7f | ||
|
|
e847ddf011 | ||
|
|
7e8b448985 | ||
|
|
d1f3a87c08 | ||
|
|
9f8145e590 | ||
|
|
791efc171a | ||
|
|
144312a525 | ||
|
|
92684112ed | ||
|
|
ef114ee6cb | ||
|
|
667f209742 | ||
|
|
4ad0747c78 | ||
|
|
c5cf66402c | ||
|
|
05417049eb | ||
|
|
c7b0c46d9f | ||
|
|
df578c91ad | ||
|
|
70008c47c9 | ||
|
|
938affef32 | ||
|
|
a3c33000ee | ||
|
|
1e54b7b294 | ||
|
|
cc0dbcf3f4 | ||
|
|
c5a2cadccc | ||
|
|
9aa43416b6 | ||
|
|
8364029db8 | ||
|
|
d842d21be0 | ||
|
|
3514fd2433 | ||
|
|
6778ff27ea | ||
|
|
f32315d699 | ||
|
|
8b754b24b6 | ||
|
|
b1bee9ff38 | ||
|
|
569be2d402 | ||
|
|
78a73eac53 | ||
|
|
533cb99c33 | ||
|
|
79726940dc | ||
|
|
27c7891169 | ||
|
|
7a3a793a12 | ||
|
|
8088469eca | ||
|
|
3dcb082015 | ||
|
|
bece023028 | ||
|
|
9c4df66925 | ||
|
|
2b43ceb6c6 | ||
|
|
c143adba91 | ||
|
|
142fdc8d86 | ||
|
|
67778dcd3d |
@@ -575,6 +575,8 @@ PCM_LIBS = \
|
|||||||
|
|
||||||
if ENABLE_DSD
|
if ENABLE_DSD
|
||||||
libpcm_a_SOURCES += \
|
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/PcmDsd.cxx src/pcm/PcmDsd.hxx \
|
||||||
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
|
src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
|
||||||
endif
|
endif
|
||||||
|
|||||||
17
NEWS
17
NEWS
@@ -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)
|
ver 0.20.1 (2017/01/09)
|
||||||
* input
|
* input
|
||||||
- curl: fix crash bug
|
- curl: fix crash bug
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
AC_PREREQ(2.60)
|
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_MAJOR=0
|
||||||
VERSION_MINOR=20
|
VERSION_MINOR=20
|
||||||
|
|||||||
42
doc/user.xml
42
doc/user.xml
@@ -2206,6 +2206,48 @@ run</programlisting>
|
|||||||
Decodes various codecs using
|
Decodes various codecs using
|
||||||
<application>FFmpeg</application>.
|
<application>FFmpeg</application>.
|
||||||
</para>
|
</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>
|
||||||
|
|
||||||
<section id="flac_decoder">
|
<section id="flac_decoder">
|
||||||
|
|||||||
@@ -146,7 +146,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool IsSame(const DetachedSong &other) const {
|
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
|
gcc_pure gcc_nonnull_all
|
||||||
|
|||||||
@@ -317,6 +317,9 @@ DecoderBridge::CommandFinished()
|
|||||||
|
|
||||||
dc.pipe->Clear(*dc.buffer);
|
dc.pipe->Clear(*dc.buffer);
|
||||||
|
|
||||||
|
if (convert != nullptr)
|
||||||
|
convert->Reset();
|
||||||
|
|
||||||
timestamp = dc.seek_time.ToDoubleS();
|
timestamp = dc.seek_time.ToDoubleS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ extern "C" {
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Muxer options to be passed to avformat_open_input().
|
||||||
|
*/
|
||||||
|
static AVDictionary *avformat_options = nullptr;
|
||||||
|
|
||||||
static AVFormatContext *
|
static AVFormatContext *
|
||||||
FfmpegOpenInput(AVIOContext *pb,
|
FfmpegOpenInput(AVIOContext *pb,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
@@ -67,7 +72,11 @@ FfmpegOpenInput(AVIOContext *pb,
|
|||||||
|
|
||||||
context->pb = 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)
|
if (err < 0)
|
||||||
throw MakeFfmpegError(err, "avformat_open_input() failed");
|
throw MakeFfmpegError(err, "avformat_open_input() failed");
|
||||||
|
|
||||||
@@ -75,12 +84,30 @@ FfmpegOpenInput(AVIOContext *pb,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ffmpeg_init(gcc_unused const ConfigBlock &block)
|
ffmpeg_init(const ConfigBlock &block)
|
||||||
{
|
{
|
||||||
FfmpegInit();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ffmpeg_finish()
|
||||||
|
{
|
||||||
|
av_dict_free(&avformat_options);
|
||||||
|
}
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 25, 0) /* FFmpeg 3.1 */
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
@@ -967,7 +994,7 @@ static const char *const ffmpeg_mime_types[] = {
|
|||||||
const struct DecoderPlugin ffmpeg_decoder_plugin = {
|
const struct DecoderPlugin ffmpeg_decoder_plugin = {
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
ffmpeg_init,
|
ffmpeg_init,
|
||||||
nullptr,
|
ffmpeg_finish,
|
||||||
ffmpeg_decode,
|
ffmpeg_decode,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ public:
|
|||||||
return out_audio_format;
|
return out_audio_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the filter's state, e.g. drop/flush buffers.
|
||||||
|
*/
|
||||||
|
virtual void Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters a block of PCM data.
|
* Filters a block of PCM data.
|
||||||
*
|
*
|
||||||
@@ -60,7 +66,7 @@ public:
|
|||||||
* @param src the input buffer
|
* @param src the input buffer
|
||||||
* @return the destination buffer on success (will be
|
* @return the destination buffer on success (will be
|
||||||
* invalidated by deleting this object or the next FilterPCM()
|
* invalidated by deleting this object or the next FilterPCM()
|
||||||
* call)
|
* or Reset() call)
|
||||||
*/
|
*/
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
|
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ public:
|
|||||||
:Filter(_filter->GetOutAudioFormat()),
|
:Filter(_filter->GetOutAudioFormat()),
|
||||||
filter(std::move(_filter)), convert(std::move(_convert)) {}
|
filter(std::move(_filter)), convert(std::move(_convert)) {}
|
||||||
|
|
||||||
|
void Reset() override {
|
||||||
|
filter->Reset();
|
||||||
|
|
||||||
|
if (convert)
|
||||||
|
convert->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* virtual methods from class Filter */
|
/* virtual methods from class Filter */
|
||||||
|
void Reset() override;
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,6 +131,13 @@ PreparedChainFilter::Open(AudioFormat &in_audio_format)
|
|||||||
return chain.release();
|
return chain.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ChainFilter::Reset()
|
||||||
|
{
|
||||||
|
for (auto &child : children)
|
||||||
|
child.filter->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
ChainFilter::FilterPCM(ConstBuffer<void> src)
|
ChainFilter::FilterPCM(ConstBuffer<void> src)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ public:
|
|||||||
|
|
||||||
void Set(const AudioFormat &_out_audio_format);
|
void Set(const AudioFormat &_out_audio_format);
|
||||||
|
|
||||||
|
void Reset() override {
|
||||||
|
state.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "AlsaInputPlugin.hxx"
|
#include "AlsaInputPlugin.hxx"
|
||||||
#include "../InputPlugin.hxx"
|
#include "../InputPlugin.hxx"
|
||||||
#include "../AsyncInputStream.hxx"
|
#include "../AsyncInputStream.hxx"
|
||||||
|
#include "event/Call.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/StringCompare.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_MAX_BUFFERED = default_rate * default_channels * 2;
|
||||||
static constexpr size_t ALSA_RESUME_AT = ALSA_MAX_BUFFERED / 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
|
class AlsaInputStream final
|
||||||
: public AsyncInputStream,
|
: public AsyncInputStream,
|
||||||
MultiSocketMonitor, DeferredMonitor {
|
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;
|
ReusableArray<pollfd> pfd_buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AlsaInputStream(EventLoop &loop,
|
AlsaInputStream(EventLoop &loop,
|
||||||
const char *_uri, Mutex &_mutex, Cond &_cond,
|
const char *_uri, Mutex &_mutex, Cond &_cond,
|
||||||
|
const char *_device,
|
||||||
snd_pcm_t *_handle, int _frame_size)
|
snd_pcm_t *_handle, int _frame_size)
|
||||||
:AsyncInputStream(_uri, _mutex, _cond,
|
:AsyncInputStream(_uri, _mutex, _cond,
|
||||||
ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
|
ALSA_MAX_BUFFERED, ALSA_RESUME_AT),
|
||||||
MultiSocketMonitor(loop),
|
MultiSocketMonitor(loop),
|
||||||
DeferredMonitor(loop),
|
DeferredMonitor(loop),
|
||||||
|
device(_device),
|
||||||
capture_handle(_handle),
|
capture_handle(_handle),
|
||||||
frame_size(_frame_size)
|
frame_size(_frame_size)
|
||||||
{
|
{
|
||||||
@@ -99,6 +99,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~AlsaInputStream() {
|
~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);
|
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;
|
int frame_size = snd_pcm_format_width(format) / 8 * channels;
|
||||||
return new AlsaInputStream(io_thread_get(),
|
return new AlsaInputStream(io_thread_get(),
|
||||||
uri, mutex, cond,
|
uri, mutex, cond,
|
||||||
handle, frame_size);
|
device, handle, frame_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::steady_clock::duration
|
std::chrono::steady_clock::duration
|
||||||
@@ -205,6 +213,9 @@ AlsaInputStream::DispatchSockets()
|
|||||||
snd_pcm_sframes_t n_frames;
|
snd_pcm_sframes_t n_frames;
|
||||||
while ((n_frames = snd_pcm_readi(capture_handle,
|
while ((n_frames = snd_pcm_readi(capture_handle,
|
||||||
w.data, w_frames)) < 0) {
|
w.data, w_frames)) < 0) {
|
||||||
|
if (n_frames == -EAGAIN)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Recover(n_frames) < 0) {
|
if (Recover(n_frames) < 0) {
|
||||||
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
|
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
|
||||||
cond.broadcast();
|
cond.broadcast();
|
||||||
@@ -221,20 +232,51 @@ AlsaInputStream::Recover(int err)
|
|||||||
{
|
{
|
||||||
switch(err) {
|
switch(err) {
|
||||||
case -EPIPE:
|
case -EPIPE:
|
||||||
LogDebug(alsa_input_domain, "Buffer Overrun");
|
FormatDebug(alsa_input_domain,
|
||||||
// drop through
|
"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)
|
#if GCC_CHECK_VERSION(7,0)
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
#endif
|
#endif
|
||||||
|
case SND_PCM_STATE_OPEN:
|
||||||
case -ESTRPIPE:
|
case SND_PCM_STATE_SETUP:
|
||||||
case -EINTR:
|
case SND_PCM_STATE_XRUN:
|
||||||
err = snd_pcm_recover(capture_handle, err, 1);
|
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;
|
break;
|
||||||
default:
|
|
||||||
// something broken somewhere, give up
|
|
||||||
err = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,23 +315,63 @@ ConfigureCapture(snd_pcm_t *capture_handle,
|
|||||||
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
throw FormatRuntimeError("Cannot set sample rate (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
|
|
||||||
/* period needs to be big enough so that poll() doesn't fire too often,
|
snd_pcm_uframes_t buffer_size_min, buffer_size_max;
|
||||||
* but small enough that buffer overruns don't occur if Read() is not
|
snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
|
||||||
* invoked often enough.
|
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
|
||||||
* the calculation here is empirical; however all measurements were
|
unsigned buffer_time_min, buffer_time_max;
|
||||||
* done using 44100:16:2. When we extend this plugin to support
|
snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
|
||||||
* other audio formats then this may need to be revisited */
|
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
|
||||||
snd_pcm_uframes_t period = read_buffer_size * 2;
|
FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
|
||||||
int direction = -1;
|
(unsigned)buffer_size_min, (unsigned)buffer_size_max,
|
||||||
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
|
buffer_time_min, buffer_time_max);
|
||||||
&period, &direction)) < 0)
|
|
||||||
throw FormatRuntimeError("Cannot set period size (%s)",
|
snd_pcm_uframes_t period_size_min, period_size_max;
|
||||||
snd_strerror(err));
|
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)
|
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
|
||||||
throw FormatRuntimeError("Cannot set parameters (%s)",
|
throw FormatRuntimeError("Cannot set parameters (%s)",
|
||||||
snd_strerror(err));
|
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_t *sw_params;
|
||||||
|
|
||||||
snd_pcm_sw_params_malloc(&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);
|
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)
|
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
|
||||||
throw FormatRuntimeError("unable to install sw params (%s)",
|
throw FormatRuntimeError("unable to install sw params (%s)",
|
||||||
snd_strerror(err));
|
snd_strerror(err));
|
||||||
@@ -316,7 +393,8 @@ AlsaInputStream::OpenDevice(const char *device,
|
|||||||
snd_pcm_t *capture_handle;
|
snd_pcm_t *capture_handle;
|
||||||
int err;
|
int err;
|
||||||
if ((err = snd_pcm_open(&capture_handle, device,
|
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)",
|
throw FormatRuntimeError("Failed to open device: %s (%s)",
|
||||||
device, snd_strerror(err));
|
device, snd_strerror(err));
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,21 @@
|
|||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks for an asynchronous libnfs operation.
|
||||||
|
*
|
||||||
|
* Note that no callback is invoked for cancelled operations.
|
||||||
|
*/
|
||||||
class NfsCallback {
|
class NfsCallback {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* The operation completed successfully.
|
||||||
|
*/
|
||||||
virtual void OnNfsCallback(unsigned status, void *data) = 0;
|
virtual void OnNfsCallback(unsigned status, void *data) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error has occurred.
|
||||||
|
*/
|
||||||
virtual void OnNfsError(std::exception_ptr &&e) = 0;
|
virtual void OnNfsError(std::exception_ptr &&e) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,14 @@
|
|||||||
struct nfsfh;
|
struct nfsfh;
|
||||||
class NfsConnection;
|
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 {
|
class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor {
|
||||||
enum class State {
|
enum class State {
|
||||||
INITIAL,
|
INITIAL,
|
||||||
@@ -63,14 +71,30 @@ public:
|
|||||||
void DeferClose();
|
void DeferClose();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Open the file. This method is thread-safe.
|
||||||
|
*
|
||||||
* Throws std::runtime_error on error.
|
* Throws std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
void Open(const char *uri);
|
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.
|
* Throws std::runtime_error on error.
|
||||||
*/
|
*/
|
||||||
void Read(uint64_t offset, size_t size);
|
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();
|
void CancelRead();
|
||||||
|
|
||||||
bool IsIdle() const {
|
bool IsIdle() const {
|
||||||
@@ -78,8 +102,27 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
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;
|
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;
|
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;
|
virtual void OnNfsFileError(std::exception_ptr &&e) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -111,8 +111,7 @@ AudioOutput::Open()
|
|||||||
f = source.Open(request.audio_format, *request.pipe,
|
f = source.Open(request.audio_format, *request.pipe,
|
||||||
prepared_replay_gain_filter,
|
prepared_replay_gain_filter,
|
||||||
prepared_other_replay_gain_filter,
|
prepared_other_replay_gain_filter,
|
||||||
prepared_filter)
|
prepared_filter);
|
||||||
.WithMask(config_audio_format);
|
|
||||||
|
|
||||||
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
|
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
|
||||||
software_mixer_set_filter(*mixer, volume_filter.Get());
|
software_mixer_set_filter(*mixer, volume_filter.Get());
|
||||||
@@ -121,14 +120,16 @@ AudioOutput::Open()
|
|||||||
name, plugin.name));
|
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
|
/* if the filter's output format changes, the output
|
||||||
must be reopened as well */
|
must be reopened as well */
|
||||||
CloseOutput(true);
|
CloseOutput(true);
|
||||||
open = false;
|
open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_audio_format = f;
|
filter_audio_format = cf;
|
||||||
|
|
||||||
if (!open) {
|
if (!open) {
|
||||||
try {
|
try {
|
||||||
@@ -139,6 +140,27 @@ AudioOutput::Open()
|
|||||||
}
|
}
|
||||||
|
|
||||||
open = true;
|
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));
|
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 {
|
try {
|
||||||
convert_filter_set(convert_filter.Get(), out_audio_format);
|
convert_filter_set(convert_filter.Get(), out_audio_format);
|
||||||
} catch (const std::runtime_error &e) {
|
} 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]",
|
std::throw_with_nested(FormatRuntimeError("Failed to convert for \"%s\" [%s]",
|
||||||
name, plugin.name));
|
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
|
void
|
||||||
|
|||||||
@@ -70,6 +70,22 @@ AudioOutputSource::Close()
|
|||||||
CloseFilter();
|
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
|
void
|
||||||
AudioOutputSource::OpenFilter(AudioFormat audio_format,
|
AudioOutputSource::OpenFilter(AudioFormat audio_format,
|
||||||
PreparedFilter *prepared_replay_gain_filter,
|
PreparedFilter *prepared_replay_gain_filter,
|
||||||
|
|||||||
@@ -139,11 +139,7 @@ public:
|
|||||||
PreparedFilter *prepared_filter);
|
PreparedFilter *prepared_filter);
|
||||||
|
|
||||||
void Close();
|
void Close();
|
||||||
|
void Cancel();
|
||||||
void Cancel() {
|
|
||||||
current_chunk = nullptr;
|
|
||||||
pipe.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that ReadTag() or PeekData() return any input.
|
* Ensure that ReadTag() or PeekData() return any input.
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ struct AlsaOutput {
|
|||||||
void Open(AudioFormat &audio_format);
|
void Open(AudioFormat &audio_format);
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
|
size_t PlayRaw(ConstBuffer<void> data);
|
||||||
size_t Play(const void *chunk, size_t size);
|
size_t Play(const void *chunk, size_t size);
|
||||||
void Drain();
|
void Drain();
|
||||||
void Cancel();
|
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
|
* 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
|
static int
|
||||||
AlsaTryFormatDsd(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
|
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);
|
int err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
|
||||||
|
|
||||||
#if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32)
|
#if defined(ENABLE_DSD) && defined(HAVE_ALSA_DSD_U32)
|
||||||
if (err == 0)
|
if (err == 0) {
|
||||||
|
params.dsd_u16 = false;
|
||||||
params.dsd_u32 = false;
|
params.dsd_u32 = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
|
if (err == -EINVAL && fmt == SND_PCM_FORMAT_DSD_U8) {
|
||||||
/* attempt to switch to DSD_U32 */
|
/* 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);
|
err = AlsaTryFormatOrByteSwap(pcm, hwparams, fmt, params);
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
params.dsd_u32 = true;
|
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
|
#endif
|
||||||
|
|
||||||
@@ -469,7 +486,6 @@ static void
|
|||||||
AlsaSetup(AlsaOutput *ad, AudioFormat &audio_format,
|
AlsaSetup(AlsaOutput *ad, AudioFormat &audio_format,
|
||||||
PcmExport::Params ¶ms)
|
PcmExport::Params ¶ms)
|
||||||
{
|
{
|
||||||
unsigned int sample_rate = audio_format.sample_rate;
|
|
||||||
unsigned int channels = audio_format.channels;
|
unsigned int channels = audio_format.channels;
|
||||||
int err;
|
int err;
|
||||||
unsigned retry = MPD_ALSA_RETRY_NR;
|
unsigned retry = MPD_ALSA_RETRY_NR;
|
||||||
@@ -513,18 +529,23 @@ configure_hw:
|
|||||||
|
|
||||||
audio_format.channels = (int8_t)channels;
|
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,
|
err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
|
||||||
&sample_rate, nullptr);
|
&output_sample_rate, nullptr);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
throw FormatRuntimeError("Failed to configure sample rate %u Hz: %s",
|
||||||
audio_format.sample_rate,
|
requested_sample_rate,
|
||||||
snd_strerror(-err));
|
snd_strerror(-err));
|
||||||
|
|
||||||
if (sample_rate == 0)
|
if (output_sample_rate == 0)
|
||||||
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
|
throw FormatRuntimeError("Failed to configure sample rate %u Hz",
|
||||||
audio_format.sample_rate);
|
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_uframes_t buffer_size_min, buffer_size_max;
|
||||||
snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min);
|
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;
|
AudioFormat dop_format = audio_format;
|
||||||
dop_format.format = SampleFormat::S24_P32;
|
dop_format.format = SampleFormat::S24_P32;
|
||||||
dop_format.sample_rate /= 2;
|
|
||||||
|
|
||||||
const AudioFormat check = dop_format;
|
const AudioFormat check = dop_format;
|
||||||
|
|
||||||
@@ -694,11 +714,12 @@ AlsaOutput::SetupOrDop(AudioFormat &audio_format, PcmExport::Params ¶ms)
|
|||||||
std::exception_ptr dop_error;
|
std::exception_ptr dop_error;
|
||||||
if (dop && audio_format.format == SampleFormat::DSD) {
|
if (dop && audio_format.format == SampleFormat::DSD) {
|
||||||
try {
|
try {
|
||||||
SetupDop(audio_format, params);
|
|
||||||
params.dop = true;
|
params.dop = true;
|
||||||
|
SetupDop(audio_format, params);
|
||||||
return;
|
return;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
dop_error = std::current_exception();
|
dop_error = std::current_exception();
|
||||||
|
params.dop = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,6 +763,11 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
|||||||
GetDevice()));
|
GetDevice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_DSD
|
||||||
|
if (params.dop)
|
||||||
|
FormatDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
|
||||||
|
#endif
|
||||||
|
|
||||||
pcm_export->Open(audio_format.format,
|
pcm_export->Open(audio_format.format,
|
||||||
audio_format.channels,
|
audio_format.channels,
|
||||||
params);
|
params);
|
||||||
@@ -777,6 +803,7 @@ AlsaOutput::Recover(int err)
|
|||||||
#if GCC_CHECK_VERSION(7,0)
|
#if GCC_CHECK_VERSION(7,0)
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
#endif
|
#endif
|
||||||
|
case SND_PCM_STATE_OPEN:
|
||||||
case SND_PCM_STATE_SETUP:
|
case SND_PCM_STATE_SETUP:
|
||||||
case SND_PCM_STATE_XRUN:
|
case SND_PCM_STATE_XRUN:
|
||||||
period_position = 0;
|
period_position = 0;
|
||||||
@@ -785,12 +812,11 @@ AlsaOutput::Recover(int err)
|
|||||||
case SND_PCM_STATE_DISCONNECTED:
|
case SND_PCM_STATE_DISCONNECTED:
|
||||||
break;
|
break;
|
||||||
/* this is no error, so just keep running */
|
/* this is no error, so just keep running */
|
||||||
|
case SND_PCM_STATE_PREPARED:
|
||||||
case SND_PCM_STATE_RUNNING:
|
case SND_PCM_STATE_RUNNING:
|
||||||
|
case SND_PCM_STATE_DRAINING:
|
||||||
err = 0;
|
err = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
/* unknown state, do nothing */
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
@@ -822,6 +848,8 @@ AlsaOutput::Cancel()
|
|||||||
must_prepare = true;
|
must_prepare = true;
|
||||||
|
|
||||||
snd_pcm_drop(pcm);
|
snd_pcm_drop(pcm);
|
||||||
|
|
||||||
|
pcm_export->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
@@ -831,6 +859,36 @@ AlsaOutput::Close()
|
|||||||
delete[] silence;
|
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
|
inline size_t
|
||||||
AlsaOutput::Play(const void *chunk, size_t size)
|
AlsaOutput::Play(const void *chunk, size_t size)
|
||||||
{
|
{
|
||||||
@@ -856,29 +914,8 @@ AlsaOutput::Play(const void *chunk, size_t size)
|
|||||||
been played */
|
been played */
|
||||||
return size;
|
return size;
|
||||||
|
|
||||||
chunk = e.data;
|
const size_t bytes_written = PlayRaw(e);
|
||||||
size = e.size;
|
return pcm_export->CalcSourceSize(bytes_written);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef AudioOutputWrapper<AlsaOutput> Wrapper;
|
typedef AudioOutputWrapper<AlsaOutput> Wrapper;
|
||||||
|
|||||||
@@ -659,6 +659,10 @@ OssOutput::Cancel()
|
|||||||
ioctl(fd, SNDCTL_DSP_RESET, 0);
|
ioctl(fd, SNDCTL_DSP_RESET, 0);
|
||||||
DoClose();
|
DoClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef AFMT_S24_PACKED
|
||||||
|
pcm_export->Reset();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline size_t
|
inline size_t
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ RecorderOutput::ReopenFormat(AllocatedPath &&new_path)
|
|||||||
assert(path.IsNull());
|
assert(path.IsNull());
|
||||||
assert(file == nullptr);
|
assert(file == nullptr);
|
||||||
|
|
||||||
FileOutputStream *new_file = new FileOutputStream(path);
|
FileOutputStream *new_file = new FileOutputStream(new_path);
|
||||||
|
|
||||||
AudioFormat new_audio_format = effective_audio_format;
|
AudioFormat new_audio_format = effective_audio_format;
|
||||||
|
|
||||||
|
|||||||
62
src/pcm/Dsd16.cxx
Normal file
62
src/pcm/Dsd16.cxx
Normal 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
36
src/pcm/Dsd16.hxx
Normal 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
64
src/pcm/Dsd32.cxx
Normal 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
36
src/pcm/Dsd32.hxx
Normal 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
|
||||||
@@ -65,6 +65,12 @@ GluePcmResampler::Close()
|
|||||||
resampler->Close();
|
resampler->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GluePcmResampler::Reset()
|
||||||
|
{
|
||||||
|
resampler->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
GluePcmResampler::Resample(ConstBuffer<void> src)
|
GluePcmResampler::Resample(ConstBuffer<void> src)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ public:
|
|||||||
return output_sample_format;
|
return output_sample_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see PcmResampler::Reset()
|
||||||
|
*/
|
||||||
|
void Reset();
|
||||||
|
|
||||||
ConstBuffer<void> Resample(ConstBuffer<void> src);
|
ConstBuffer<void> Resample(ConstBuffer<void> src);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ LibsampleratePcmResampler::Close()
|
|||||||
state = src_delete(state);
|
state = src_delete(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LibsampleratePcmResampler::Reset()
|
||||||
|
{
|
||||||
|
src_reset(state);
|
||||||
|
}
|
||||||
|
|
||||||
inline ConstBuffer<float>
|
inline ConstBuffer<float>
|
||||||
LibsampleratePcmResampler::Resample2(ConstBuffer<float> src)
|
LibsampleratePcmResampler::Resample2(ConstBuffer<float> src)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class LibsampleratePcmResampler final : public PcmResampler {
|
|||||||
public:
|
public:
|
||||||
AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
|
AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
|
||||||
void Close() override;
|
void Close() override;
|
||||||
|
void Reset() override;
|
||||||
ConstBuffer<void> Resample(ConstBuffer<void> src) override;
|
ConstBuffer<void> Resample(ConstBuffer<void> src) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the buffer, and guarantee a minimum size. This buffer becomes
|
* 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
|
* This function will never return nullptr, even if size is
|
||||||
* zero, because the PCM library uses the nullptr return value
|
* zero, because the PCM library uses the nullptr return value
|
||||||
|
|||||||
@@ -117,6 +117,17 @@ PcmConvert::Close()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PcmConvert::Reset()
|
||||||
|
{
|
||||||
|
if (enable_resampler)
|
||||||
|
resampler.Reset();
|
||||||
|
|
||||||
|
#ifdef ENABLE_DSD
|
||||||
|
dsd.Reset();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
PcmConvert::Convert(ConstBuffer<void> buffer)
|
PcmConvert::Convert(ConstBuffer<void> buffer)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the filter's state, e.g. drop/flush buffers.
|
||||||
|
*/
|
||||||
|
void Reset();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts PCM data between two audio formats.
|
* Converts PCM data between two audio formats.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
|
|||||||
ConstBuffer<uint8_t> _src)
|
ConstBuffer<uint8_t> _src)
|
||||||
{
|
{
|
||||||
assert(audio_valid_channel_count(channels));
|
assert(audio_valid_channel_count(channels));
|
||||||
assert(!_src.IsNull());
|
|
||||||
assert(_src.size > 0);
|
|
||||||
assert(_src.size % channels == 0);
|
assert(_src.size % channels == 0);
|
||||||
|
|
||||||
const unsigned num_src_samples = _src.size;
|
const unsigned num_src_samples = _src.size;
|
||||||
|
|||||||
@@ -71,41 +71,3 @@ PcmDsd::ToFloat(unsigned channels, ConstBuffer<uint8_t> src)
|
|||||||
|
|
||||||
return { dest, num_samples };
|
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};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,10 +48,4 @@ public:
|
|||||||
ConstBuffer<uint8_t> src);
|
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
|
#endif
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
|
#include "Dsd16.hxx"
|
||||||
|
#include "Dsd32.hxx"
|
||||||
#include "PcmDsd.hxx"
|
#include "PcmDsd.hxx"
|
||||||
#include "PcmDop.hxx"
|
#include "PcmDop.hxx"
|
||||||
#endif
|
#endif
|
||||||
@@ -41,9 +43,15 @@ PcmExport::Open(SampleFormat sample_format, unsigned _channels,
|
|||||||
: SampleFormat::UNDEFINED;
|
: SampleFormat::UNDEFINED;
|
||||||
|
|
||||||
#ifdef ENABLE_DSD
|
#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));
|
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;
|
dsd_u32 = params.dsd_u32 && sample_format == SampleFormat::DSD;
|
||||||
if (dsd_u32)
|
if (dsd_u32)
|
||||||
/* after the conversion to DSD_U32, the DSD samples
|
/* 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
|
/* after the conversion to DoP, the DSD
|
||||||
samples are stuffed inside fake 24 bit samples */
|
samples are stuffed inside fake 24 bit samples */
|
||||||
sample_format = SampleFormat::S24_P32;
|
sample_format = SampleFormat::S24_P32;
|
||||||
#else
|
|
||||||
(void)_channels;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
shift8 = params.shift8 && sample_format == SampleFormat::S24_P32;
|
shift8 = params.shift8 && sample_format == SampleFormat::S24_P32;
|
||||||
@@ -84,6 +90,9 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
|
|||||||
return audio_format.channels * 3;
|
return audio_format.channels * 3;
|
||||||
|
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
|
if (dsd_u16)
|
||||||
|
return channels * 2;
|
||||||
|
|
||||||
if (dsd_u32)
|
if (dsd_u32)
|
||||||
return channels * 4;
|
return channels * 4;
|
||||||
|
|
||||||
@@ -98,6 +107,46 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
|
|||||||
return audio_format.GetFrameSize();
|
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>
|
ConstBuffer<void>
|
||||||
PcmExport::Export(ConstBuffer<void> data)
|
PcmExport::Export(ConstBuffer<void> data)
|
||||||
{
|
{
|
||||||
@@ -106,6 +155,11 @@ PcmExport::Export(ConstBuffer<void> data)
|
|||||||
alsa_channel_order, channels);
|
alsa_channel_order, channels);
|
||||||
|
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
|
if (dsd_u16)
|
||||||
|
data = Dsd8To16(dop_buffer, channels,
|
||||||
|
ConstBuffer<uint8_t>::FromVoid(data))
|
||||||
|
.ToVoid();
|
||||||
|
|
||||||
if (dsd_u32)
|
if (dsd_u32)
|
||||||
data = Dsd8To32(dop_buffer, channels,
|
data = Dsd8To32(dop_buffer, channels,
|
||||||
ConstBuffer<uint8_t>::FromVoid(data))
|
ConstBuffer<uint8_t>::FromVoid(data))
|
||||||
|
|||||||
@@ -31,18 +31,7 @@ template<typename T> struct ConstBuffer;
|
|||||||
* outside of MPD. It has a few more options to tweak the binary
|
* outside of MPD. It has a few more options to tweak the binary
|
||||||
* representation which are not supported by the pcm_convert library.
|
* representation which are not supported by the pcm_convert library.
|
||||||
*/
|
*/
|
||||||
struct PcmExport {
|
class 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This buffer is used to reorder channels.
|
* This buffer is used to reorder channels.
|
||||||
*
|
*
|
||||||
@@ -90,6 +79,11 @@ struct PcmExport {
|
|||||||
SampleFormat alsa_channel_order;
|
SampleFormat alsa_channel_order;
|
||||||
|
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
|
/**
|
||||||
|
* Convert DSD (U8) to DSD_U16?
|
||||||
|
*/
|
||||||
|
bool dsd_u16;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert DSD (U8) to DSD_U32?
|
* Convert DSD (U8) to DSD_U32?
|
||||||
*/
|
*/
|
||||||
@@ -121,6 +115,34 @@ struct PcmExport {
|
|||||||
*/
|
*/
|
||||||
uint8_t reverse_endian;
|
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.
|
* Open the object.
|
||||||
*
|
*
|
||||||
@@ -134,6 +156,12 @@ struct PcmExport {
|
|||||||
void Open(SampleFormat sample_format, unsigned channels,
|
void Open(SampleFormat sample_format, unsigned channels,
|
||||||
Params params);
|
Params params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the filter's state, e.g. drop/flush buffers.
|
||||||
|
*/
|
||||||
|
void Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the size of one output frame.
|
* Calculate the size of one output frame.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the filter's state, e.g. drop/flush buffers.
|
||||||
|
*/
|
||||||
|
virtual void Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resamples a block of PCM data.
|
* Resamples a block of PCM data.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -30,10 +30,12 @@
|
|||||||
#ifndef REUSABLE_ARRAY_HXX
|
#ifndef REUSABLE_ARRAY_HXX
|
||||||
#define REUSABLE_ARRAY_HXX
|
#define REUSABLE_ARRAY_HXX
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager for a temporary array which grows as needed. This attempts
|
* Manager for a temporary array which grows as needed. This attempts
|
||||||
* to reduce the number of consecutive heap allocations and
|
* to reduce the number of consecutive heap allocations and
|
||||||
@@ -44,19 +46,30 @@
|
|||||||
*/
|
*/
|
||||||
template<typename T, size_t M=1>
|
template<typename T, size_t M=1>
|
||||||
class ReusableArray {
|
class ReusableArray {
|
||||||
T *buffer;
|
T *buffer = nullptr;
|
||||||
size_t capacity;
|
size_t capacity = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ReusableArray():buffer(nullptr), capacity(0) {}
|
ReusableArray() = default;
|
||||||
|
|
||||||
ReusableArray(const ReusableArray &other) = delete;
|
ReusableArray(ReusableArray &&src)
|
||||||
ReusableArray &operator=(const ReusableArray &other) = delete;
|
: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() {
|
~ReusableArray() {
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t GetCapacity() const {
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free resources allocated by this object. This invalidates
|
* Free resources allocated by this object. This invalidates
|
||||||
* the buffer returned by Get().
|
* the buffer returned by Get().
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
#ifndef MPD_TEST_PCM_ALL_HXX
|
#ifndef MPD_TEST_PCM_ALL_HXX
|
||||||
#define MPD_TEST_PCM_ALL_HXX
|
#define MPD_TEST_PCM_ALL_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
#include <cppunit/TestFixture.h>
|
#include <cppunit/TestFixture.h>
|
||||||
#include <cppunit/extensions/HelperMacros.h>
|
#include <cppunit/extensions/HelperMacros.h>
|
||||||
|
|
||||||
@@ -126,6 +128,7 @@ class PcmExportTest : public CppUnit::TestFixture {
|
|||||||
CPPUNIT_TEST(TestPack24);
|
CPPUNIT_TEST(TestPack24);
|
||||||
CPPUNIT_TEST(TestReverseEndian);
|
CPPUNIT_TEST(TestReverseEndian);
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
|
CPPUNIT_TEST(TestDsdU16);
|
||||||
CPPUNIT_TEST(TestDsdU32);
|
CPPUNIT_TEST(TestDsdU32);
|
||||||
CPPUNIT_TEST(TestDop);
|
CPPUNIT_TEST(TestDop);
|
||||||
#endif
|
#endif
|
||||||
@@ -137,6 +140,7 @@ public:
|
|||||||
void TestPack24();
|
void TestPack24();
|
||||||
void TestReverseEndian();
|
void TestReverseEndian();
|
||||||
#ifdef ENABLE_DSD
|
#ifdef ENABLE_DSD
|
||||||
|
void TestDsdU16();
|
||||||
void TestDsdU32();
|
void TestDsdU32();
|
||||||
void TestDop();
|
void TestDop();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "test_pcm_all.hxx"
|
#include "test_pcm_all.hxx"
|
||||||
#include "test_pcm_util.hxx"
|
#include "test_pcm_util.hxx"
|
||||||
#include "pcm/PcmDither.cxx"
|
#include "pcm/PcmDither.cxx"
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ PcmExportTest::TestShift8()
|
|||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.shift8 = true;
|
params.shift8 = true;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
|
||||||
|
|
||||||
PcmExport e;
|
PcmExport e;
|
||||||
e.Open(SampleFormat::S24_P32, 2, params);
|
e.Open(SampleFormat::S24_P32, 2, params);
|
||||||
|
|
||||||
@@ -71,6 +74,9 @@ PcmExportTest::TestPack24()
|
|||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.pack24 = true;
|
params.pack24 = true;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
|
||||||
|
|
||||||
PcmExport e;
|
PcmExport e;
|
||||||
e.Open(SampleFormat::S24_P32, 2, params);
|
e.Open(SampleFormat::S24_P32, 2, params);
|
||||||
|
|
||||||
@@ -97,6 +103,9 @@ PcmExportTest::TestReverseEndian()
|
|||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.reverse_endian = true;
|
params.reverse_endian = true;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
|
||||||
|
|
||||||
PcmExport e;
|
PcmExport e;
|
||||||
e.Open(SampleFormat::S8, 2, params);
|
e.Open(SampleFormat::S8, 2, params);
|
||||||
|
|
||||||
@@ -117,6 +126,37 @@ PcmExportTest::TestReverseEndian()
|
|||||||
|
|
||||||
#ifdef ENABLE_DSD
|
#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
|
void
|
||||||
PcmExportTest::TestDsdU32()
|
PcmExportTest::TestDsdU32()
|
||||||
{
|
{
|
||||||
@@ -128,15 +168,18 @@ PcmExportTest::TestDsdU32()
|
|||||||
};
|
};
|
||||||
|
|
||||||
static constexpr uint32_t expected[] = {
|
static constexpr uint32_t expected[] = {
|
||||||
0xcd894501,
|
0x014589cd,
|
||||||
0xefab6723,
|
0x2367abef,
|
||||||
0x77553311,
|
0x11335577,
|
||||||
0x88664422,
|
0x22446688,
|
||||||
};
|
};
|
||||||
|
|
||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.dsd_u32 = true;
|
params.dsd_u32 = true;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(705600u), 176400u);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(176400u), 705600u);
|
||||||
|
|
||||||
PcmExport e;
|
PcmExport e;
|
||||||
e.Open(SampleFormat::DSD, 2, params);
|
e.Open(SampleFormat::DSD, 2, params);
|
||||||
|
|
||||||
@@ -163,6 +206,9 @@ PcmExportTest::TestDop()
|
|||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.dop = true;
|
params.dop = true;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(705600u), 352800u);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(352800u), 705600u);
|
||||||
|
|
||||||
PcmExport e;
|
PcmExport e;
|
||||||
e.Open(SampleFormat::DSD, 2, params);
|
e.Open(SampleFormat::DSD, 2, params);
|
||||||
|
|
||||||
@@ -192,6 +238,9 @@ TestAlsaChannelOrder51()
|
|||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.alsa_channel_order = true;
|
params.alsa_channel_order = true;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
|
||||||
|
|
||||||
PcmExport e;
|
PcmExport e;
|
||||||
e.Open(F, 6, params);
|
e.Open(F, 6, params);
|
||||||
|
|
||||||
@@ -219,6 +268,9 @@ TestAlsaChannelOrder71()
|
|||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.alsa_channel_order = true;
|
params.alsa_channel_order = true;
|
||||||
|
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcOutputSampleRate(42u), 42u);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(params.CalcInputSampleRate(42u), 42u);
|
||||||
|
|
||||||
PcmExport e;
|
PcmExport e;
|
||||||
e.Open(F, 8, params);
|
e.Open(F, 8, params);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "test_pcm_all.hxx"
|
#include "test_pcm_all.hxx"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "test_pcm_all.hxx"
|
#include "test_pcm_all.hxx"
|
||||||
#include "test_pcm_util.hxx"
|
#include "test_pcm_util.hxx"
|
||||||
#include "pcm/PcmPack.hxx"
|
#include "pcm/PcmPack.hxx"
|
||||||
|
|||||||
Reference in New Issue
Block a user