diff --git a/NEWS b/NEWS index 99e0e5db6..132005978 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,7 @@ ver 0.20 (not yet released) - gme: add option "accuracy" - mad: reduce memory usage while scanning tags - mpcdec: read the bit rate + - pcm: support audio/L16 (RFC 2586) * playlist - cue: don't skip pregap - embcue: fix last track diff --git a/doc/user.xml b/doc/user.xml index 4314dced9..c941be86b 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -1942,7 +1942,9 @@ buffer_size: 16384 <varname>pcm</varname> - Read raw PCM samples. + Read raw PCM samples. It understands the "audio/L16" MIME + type with parameters "rate" and "channels" according to RFC + 2586. diff --git a/src/decoder/plugins/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx index 8e5c770f0..3fbfc30d0 100644 --- a/src/decoder/plugins/PcmDecoderPlugin.cxx +++ b/src/decoder/plugins/PcmDecoderPlugin.cxx @@ -20,9 +20,12 @@ #include "config.h" #include "PcmDecoderPlugin.hxx" #include "../DecoderAPI.hxx" +#include "CheckAudioFormat.hxx" #include "input/InputStream.hxx" #include "util/Error.hxx" #include "util/ByteReverse.hxx" +#include "util/NumberParser.hxx" +#include "util/MimeType.hxx" #include "Log.hxx" #include @@ -30,7 +33,7 @@ static void pcm_stream_decode(Decoder &decoder, InputStream &is) { - static constexpr AudioFormat audio_format = { + AudioFormat audio_format = { 44100, SampleFormat::S16, 2, @@ -40,6 +43,66 @@ pcm_stream_decode(Decoder &decoder, InputStream &is) const bool reverse_endian = mime != nullptr && strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0; + const bool l16 = mime != nullptr && + GetMimeTypeBase(mime) == "audio/L16"; + if (l16) { + audio_format.sample_rate = 0; + audio_format.channels = 1; + } + + { + const auto mime_parameters = ParseMimeTypeParameters(mime); + Error error; + + /* MIME type parameters according to RFC 2586 */ + auto i = mime_parameters.find("rate"); + if (i != mime_parameters.end()) { + const char *s = i->second.c_str(); + char *endptr; + unsigned value = ParseUnsigned(s, &endptr); + if (endptr == s || *endptr != 0) { + FormatWarning(audio_format_domain, + "Failed to parse sample rate: %s", + s); + return; + } + + if (!audio_check_sample_rate(value, error)) { + LogError(error); + return; + } + + audio_format.sample_rate = value; + } + + i = mime_parameters.find("channels"); + if (i != mime_parameters.end()) { + const char *s = i->second.c_str(); + char *endptr; + unsigned value = ParseUnsigned(s, &endptr); + if (endptr == s || *endptr != 0) { + FormatWarning(audio_format_domain, + "Failed to parse sample rate: %s", + s); + return; + } + + if (!audio_check_channel_count(value, error)) { + LogError(error); + return; + } + + audio_format.channels = value; + } + } + + if (audio_format.sample_rate == 0) { + FormatWarning(audio_format_domain, + "Missing 'rate' parameter: %s", + mime); + return; + } + const auto frame_size = audio_format.GetFrameSize(); const auto total_time = is.KnownSize() @@ -88,6 +151,9 @@ pcm_stream_decode(Decoder &decoder, InputStream &is) } static const char *const pcm_mime_types[] = { + /* RFC 2586 */ + "audio/L16", + /* for streams obtained by the cdio_paranoia input plugin */ "audio/x-mpd-cdda-pcm",