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
pcm
- 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",