1 /*
2 * Copyright 2003-2021 The Music Player Daemon Project
3 * http://www.musicpd.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "config.h"
21
22 #include "PcmDecoderPlugin.hxx"
23 #include "../DecoderAPI.hxx"
24 #include "pcm/CheckAudioFormat.hxx"
25 #include "pcm/Pack.hxx"
26 #include "input/InputStream.hxx"
27 #include "util/ByteOrder.hxx"
28 #include "util/Domain.hxx"
29 #include "util/ByteReverse.hxx"
30 #include "util/StaticFifoBuffer.hxx"
31 #include "util/NumberParser.hxx"
32 #include "util/MimeType.hxx"
33 #include "Log.hxx"
34
35 #ifdef ENABLE_ALSA
36 #include "pcm/AudioParser.hxx"
37 #endif
38
39 #include <exception>
40
41 #include <string.h>
42
43 static constexpr Domain pcm_decoder_domain("pcm_decoder");
44
45 template<typename B>
46 static bool
FillBuffer(DecoderClient & client,InputStream & is,B & buffer)47 FillBuffer(DecoderClient &client, InputStream &is, B &buffer)
48 {
49 buffer.Shift();
50 auto w = buffer.Write();
51 if (w.empty())
52 return true;
53
54 size_t nbytes = decoder_read(client, is, w.data, w.size);
55 if (nbytes == 0 && is.LockIsEOF())
56 return false;
57
58 buffer.Append(nbytes);
59 return true;
60 }
61
62 static void
pcm_stream_decode(DecoderClient & client,InputStream & is)63 pcm_stream_decode(DecoderClient &client, InputStream &is)
64 {
65 AudioFormat audio_format = {
66 44100,
67 SampleFormat::S16,
68 2,
69 };
70
71 const char *const mime = is.GetMimeType();
72
73 const bool l16 = mime != nullptr &&
74 GetMimeTypeBase(mime) == "audio/L16";
75 const bool l24 = mime != nullptr &&
76 GetMimeTypeBase(mime) == "audio/L24";
77 const bool is_float = mime != nullptr &&
78 GetMimeTypeBase(mime) == "audio/x-mpd-float";
79 if (l16 || l24 || is_float) {
80 audio_format.sample_rate = 0;
81 audio_format.channels = 1;
82 }
83
84 if (l24)
85 audio_format.format = SampleFormat::S24_P32;
86
87 const bool reverse_endian = (l16 && IsLittleEndian()) ||
88 (mime != nullptr &&
89 strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0);
90
91 if (is_float)
92 audio_format.format = SampleFormat::FLOAT;
93
94 if (mime != nullptr) {
95 const auto mime_parameters = ParseMimeTypeParameters(mime);
96
97 /* MIME type parameters according to RFC 2586 */
98 auto i = mime_parameters.find("rate");
99 if (i != mime_parameters.end()) {
100 const char *s = i->second.c_str();
101 char *endptr;
102 unsigned value = ParseUnsigned(s, &endptr);
103 if (endptr == s || *endptr != 0) {
104 FmtWarning(pcm_decoder_domain,
105 "Failed to parse sample rate: {}",
106 s);
107 return;
108 }
109
110 try {
111 CheckSampleRate(value);
112 } catch (...) {
113 LogError(std::current_exception());
114 return;
115 }
116
117 audio_format.sample_rate = value;
118 }
119
120 i = mime_parameters.find("channels");
121 if (i != mime_parameters.end()) {
122 const char *s = i->second.c_str();
123 char *endptr;
124 unsigned value = ParseUnsigned(s, &endptr);
125 if (endptr == s || *endptr != 0) {
126 FmtWarning(pcm_decoder_domain,
127 "Failed to parse sample rate: {}",
128 s);
129 return;
130 }
131
132 try {
133 CheckChannelCount(value);
134 } catch (...) {
135 LogError(std::current_exception());
136 return;
137 }
138
139 audio_format.channels = value;
140 }
141
142 #ifdef ENABLE_ALSA
143 if (GetMimeTypeBase(mime) == "audio/x-mpd-alsa-pcm") {
144 i = mime_parameters.find("format");
145 if (i != mime_parameters.end()) {
146 const char *s = i->second.c_str();
147 audio_format = ParseAudioFormat(s, false);
148 if (!audio_format.IsFullyDefined()) {
149 FmtWarning(pcm_decoder_domain,
150 "Invalid audio format specification: {}",
151 mime);
152 return;
153 }
154 }
155 }
156 #endif
157 }
158
159 if (audio_format.sample_rate == 0) {
160 FmtWarning(pcm_decoder_domain,
161 "Missing 'rate' parameter: {}",
162 mime);
163 return;
164 }
165
166 const auto out_frame_size = audio_format.GetFrameSize();
167 const auto in_frame_size = out_frame_size;
168
169 const auto total_time = is.KnownSize()
170 ? SignedSongTime::FromScale<uint64_t>(is.GetSize() / in_frame_size,
171 audio_format.sample_rate)
172 : SignedSongTime::Negative();
173
174 client.Ready(audio_format, is.IsSeekable(), total_time);
175
176 StaticFifoBuffer<uint8_t, 4096> buffer;
177
178 /* a buffer for pcm_unpack_24be() large enough to hold the
179 results for a full source buffer */
180 int32_t unpack_buffer[buffer.GetCapacity() / 3];
181
182 DecoderCommand cmd;
183 do {
184 if (!FillBuffer(client, is, buffer))
185 break;
186
187 auto r = buffer.Read();
188 /* round down to the nearest frame size, because we
189 must not pass partial frames to
190 DecoderClient::SubmitData() */
191 r.size -= r.size % in_frame_size;
192 buffer.Consume(r.size);
193
194 if (reverse_endian)
195 /* make sure we deliver samples in host byte order */
196 reverse_bytes_16((uint16_t *)r.data,
197 (uint16_t *)r.data,
198 (uint16_t *)(r.data + r.size));
199 else if (l24) {
200 /* convert big-endian packed 24 bit
201 (audio/L24) to native-endian 24 bit (in 32
202 bit integers) */
203 pcm_unpack_24be(unpack_buffer, r.begin(), r.end());
204 r.data = (uint8_t *)&unpack_buffer[0];
205 r.size = (r.size / 3) * 4;
206 }
207
208 cmd = !r.empty()
209 ? client.SubmitData(is, r.data, r.size, 0)
210 : client.GetCommand();
211 if (cmd == DecoderCommand::SEEK) {
212 uint64_t frame = client.GetSeekFrame();
213 offset_type offset = frame * in_frame_size;
214
215 try {
216 is.LockSeek(offset);
217 buffer.Clear();
218 client.CommandFinished();
219 } catch (...) {
220 LogError(std::current_exception());
221 client.SeekError();
222 }
223
224 cmd = DecoderCommand::NONE;
225 }
226 } while (cmd == DecoderCommand::NONE);
227 }
228
229 static const char *const pcm_mime_types[] = {
230 /* RFC 2586 */
231 "audio/L16",
232
233 /* RFC 3190 */
234 "audio/L24",
235
236 /* MPD-specific: float32 native-endian */
237 "audio/x-mpd-float",
238
239 /* for streams obtained by the cdio_paranoia input plugin */
240 "audio/x-mpd-cdda-pcm",
241
242 /* same as above, but with reverse byte order */
243 "audio/x-mpd-cdda-pcm-reverse",
244
245 #ifdef ENABLE_ALSA
246 /* for streams obtained by the alsa input plugin */
247 "audio/x-mpd-alsa-pcm",
248 #endif
249
250 nullptr
251 };
252
253 constexpr DecoderPlugin pcm_decoder_plugin =
254 DecoderPlugin("pcm", pcm_stream_decode, nullptr)
255 .WithMimeTypes(pcm_mime_types);
256