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