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 /*
21  * ALSA code based on an example by Paul Davis released under GPL here:
22  * http://equalarea.com/paul/alsa-audio.html
23  * and one by Matthias Nagorni, also GPL, here:
24  * http://alsamodular.sourceforge.net/alsa_programming_howto.html
25  */
26 
27 #include "AlsaInputPlugin.hxx"
28 #include "lib/alsa/NonBlock.hxx"
29 #include "lib/alsa/Error.hxx"
30 #include "lib/alsa/Format.hxx"
31 #include "../AsyncInputStream.hxx"
32 #include "event/Call.hxx"
33 #include "config/Block.hxx"
34 #include "util/Domain.hxx"
35 #include "util/ASCII.hxx"
36 #include "util/DivideString.hxx"
37 #include "pcm/AudioParser.hxx"
38 #include "pcm/AudioFormat.hxx"
39 #include "Log.hxx"
40 #include "event/MultiSocketMonitor.hxx"
41 #include "event/InjectEvent.hxx"
42 
43 #include <alsa/asoundlib.h>
44 
45 #include <cassert>
46 
47 #include <string.h>
48 
49 static constexpr Domain alsa_input_domain("alsa");
50 
51 static constexpr auto ALSA_URI_PREFIX = "alsa://";
52 
53 static constexpr auto BUILTIN_DEFAULT_DEVICE = "default";
54 static constexpr auto BUILTIN_DEFAULT_FORMAT = "48000:16:2";
55 
56 static constexpr auto DEFAULT_BUFFER_TIME = std::chrono::milliseconds(1000);
57 static constexpr auto DEFAULT_RESUME_TIME = DEFAULT_BUFFER_TIME / 2;
58 
59 
60 static struct {
61 	EventLoop *event_loop;
62 	const char *default_device;
63 	const char *default_format;
64 	int mode;
65 } global_config;
66 
67 
68 class AlsaInputStream final
69 	: public AsyncInputStream,
70 	  MultiSocketMonitor {
71 
72 	/**
73 	 * The configured name of the ALSA device.
74 	 */
75 	const std::string device;
76 
77 	snd_pcm_t *capture_handle;
78 	const size_t frame_size;
79 
80 	AlsaNonBlockPcm non_block;
81 
82 	InjectEvent defer_invalidate_sockets;
83 
84 public:
85 
86 	class SourceSpec;
87 
88 	AlsaInputStream(EventLoop &_loop,
89 			Mutex &_mutex,
90 			const SourceSpec &spec);
91 
~AlsaInputStream()92 	~AlsaInputStream() override {
93 		BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
94 				MultiSocketMonitor::Reset();
95 				defer_invalidate_sockets.Cancel();
96 			});
97 
98 		snd_pcm_close(capture_handle);
99 	}
100 
101 	AlsaInputStream(const AlsaInputStream &) = delete;
102 	AlsaInputStream &operator=(const AlsaInputStream &) = delete;
103 
104 	static InputStreamPtr Create(EventLoop &event_loop, const char *uri,
105 				     Mutex &mutex);
106 
107 protected:
108 	/* virtual methods from AsyncInputStream */
DoResume()109 	void DoResume() override {
110 		snd_pcm_resume(capture_handle);
111 
112 		InvalidateSockets();
113 	}
114 
DoSeek(offset_type new_offset)115 	void DoSeek([[maybe_unused]] offset_type new_offset) override {
116 		/* unreachable because seekable==false */
117 		SeekDone();
118 	}
119 
120 private:
121 	void OpenDevice(const SourceSpec &spec);
122 	void ConfigureCapture(AudioFormat audio_format);
123 
Pause()124 	void Pause() {
125 		AsyncInputStream::Pause();
126 		InvalidateSockets();
127 	}
128 
129 	int Recover(int err);
130 
131 	/* virtual methods from class MultiSocketMonitor */
132 	Event::Duration PrepareSockets() noexcept override;
133 	void DispatchSockets() noexcept override;
134 };
135 
136 
137 class AlsaInputStream::SourceSpec {
138 	const char *uri;
139 	const char *device_name;
140 	const char *format_string;
141 	AudioFormat audio_format;
142 	DivideString components;
143 
144 public:
SourceSpec(const char * _uri)145 	explicit SourceSpec(const char *_uri)
146 		: uri(_uri)
147 		, components(uri, '?')
148 	{
149 		if (components.IsDefined()) {
150 			device_name = StringAfterPrefixCaseASCII(components.GetFirst(),
151 			                                                  ALSA_URI_PREFIX);
152 			format_string = StringAfterPrefixCaseASCII(components.GetSecond(),
153 			                                                        "format=");
154 		}
155 		else {
156 			device_name = StringAfterPrefixCaseASCII(uri, ALSA_URI_PREFIX);
157 			format_string = global_config.default_format;
158 		}
159 		if (IsValidScheme()) {
160 			if (*device_name == 0)
161 				device_name = global_config.default_device;
162 			if (format_string != nullptr)
163 				audio_format = ParseAudioFormat(format_string, false);
164 		}
165 	}
IsValidScheme() const166 	[[nodiscard]] bool IsValidScheme() const noexcept {
167 		return device_name != nullptr;
168 	}
IsValid() const169 	[[nodiscard]] bool IsValid() const noexcept {
170 		return (device_name != nullptr) && (format_string != nullptr);
171 	}
GetURI() const172 	[[nodiscard]] const char *GetURI() const noexcept {
173 		return uri;
174 	}
GetDeviceName() const175 	[[nodiscard]] const char *GetDeviceName() const noexcept {
176 		return device_name;
177 	}
GetFormatString() const178 	[[nodiscard]] const char *GetFormatString() const noexcept {
179 		return format_string;
180 	}
GetAudioFormat() const181 	[[nodiscard]] AudioFormat GetAudioFormat() const noexcept {
182 		return audio_format;
183 	}
184 };
185 
AlsaInputStream(EventLoop & _loop,Mutex & _mutex,const SourceSpec & spec)186 AlsaInputStream::AlsaInputStream(EventLoop &_loop,
187 		Mutex &_mutex,
188 		const SourceSpec &spec)
189 	:AsyncInputStream(_loop, spec.GetURI(), _mutex,
190 		 spec.GetAudioFormat().TimeToSize(DEFAULT_BUFFER_TIME),
191 		 spec.GetAudioFormat().TimeToSize(DEFAULT_RESUME_TIME)),
192 	 MultiSocketMonitor(_loop),
193 	 device(spec.GetDeviceName()),
194 	 frame_size(spec.GetAudioFormat().GetFrameSize()),
195 	 defer_invalidate_sockets(_loop,
196 				  BIND_THIS_METHOD(InvalidateSockets))
197 {
198 	OpenDevice(spec);
199 
200 	std::string mimestr = "audio/x-mpd-alsa-pcm;format=";
201 	mimestr += spec.GetFormatString();
202 	SetMimeType(mimestr.c_str());
203 
204 	InputStream::SetReady();
205 
206 	snd_pcm_start(capture_handle);
207 
208 	defer_invalidate_sockets.Schedule();
209 }
210 
211 inline InputStreamPtr
Create(EventLoop & event_loop,const char * uri,Mutex & mutex)212 AlsaInputStream::Create(EventLoop &event_loop, const char *uri,
213 			Mutex &mutex)
214 {
215 	assert(uri != nullptr);
216 
217 	AlsaInputStream::SourceSpec spec(uri);
218 	if (!spec.IsValidScheme())
219 		return nullptr;
220 
221 	return std::make_unique<AlsaInputStream>(event_loop, mutex, spec);
222 }
223 
224 Event::Duration
PrepareSockets()225 AlsaInputStream::PrepareSockets() noexcept
226 {
227 	if (IsPaused()) {
228 		ClearSocketList();
229 		return Event::Duration(-1);
230 	}
231 
232 	return non_block.PrepareSockets(*this, capture_handle);
233 }
234 
235 void
DispatchSockets()236 AlsaInputStream::DispatchSockets() noexcept
237 {
238 	non_block.DispatchSockets(*this, capture_handle);
239 
240 	const std::scoped_lock<Mutex> protect(mutex);
241 
242 	auto w = PrepareWriteBuffer();
243 	const snd_pcm_uframes_t w_frames = w.size / frame_size;
244 	if (w_frames == 0) {
245 		/* buffer is full */
246 		Pause();
247 		return;
248 	}
249 
250 	snd_pcm_sframes_t n_frames;
251 	while ((n_frames = snd_pcm_readi(capture_handle,
252 					 w.data, w_frames)) < 0) {
253 		if (n_frames == -EAGAIN)
254 			return;
255 
256 		if (Recover(n_frames) < 0) {
257 			postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
258 			InvokeOnAvailable();
259 			return;
260 		}
261 	}
262 
263 	size_t nbytes = n_frames * frame_size;
264 	CommitWriteBuffer(nbytes);
265 }
266 
267 inline int
Recover(int err)268 AlsaInputStream::Recover(int err)
269 {
270 	switch(err) {
271 	case -EPIPE:
272 		FmtDebug(alsa_input_domain,
273 			 "Overrun on ALSA capture device \"{}\"",
274 			 device);
275 		break;
276 
277 	case -ESTRPIPE:
278 		FmtDebug(alsa_input_domain,
279 			 "ALSA capture device \"{}\" was suspended",
280 			 device);
281 		break;
282 	}
283 
284 	switch (snd_pcm_state(capture_handle)) {
285 	case SND_PCM_STATE_PAUSED:
286 		err = snd_pcm_pause(capture_handle, /* disable */ 0);
287 		break;
288 
289 	case SND_PCM_STATE_SUSPENDED:
290 		err = snd_pcm_resume(capture_handle);
291 		if (err == -EAGAIN)
292 			return 0;
293 		/* fall-through to snd_pcm_prepare: */
294 #if CLANG_OR_GCC_VERSION(7,0)
295 		[[fallthrough]];
296 #endif
297 	case SND_PCM_STATE_OPEN:
298 	case SND_PCM_STATE_SETUP:
299 	case SND_PCM_STATE_XRUN:
300 		err = snd_pcm_prepare(capture_handle);
301 		if (err == 0)
302 			err = snd_pcm_start(capture_handle);
303 		break;
304 
305 	case SND_PCM_STATE_DISCONNECTED:
306 		break;
307 
308 	case SND_PCM_STATE_PREPARED:
309 	case SND_PCM_STATE_RUNNING:
310 	case SND_PCM_STATE_DRAINING:
311 		/* this is no error, so just keep running */
312 		err = 0;
313 		break;
314 
315 	default:
316 		/* this default case is just here to work around
317 		   -Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
318 		   1.1.6) */
319 		break;
320 	}
321 
322 	return err;
323 }
324 
325 void
ConfigureCapture(AudioFormat audio_format)326 AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
327 {
328 	int err;
329 
330 	snd_pcm_hw_params_t *hw_params;
331 	snd_pcm_hw_params_alloca(&hw_params);
332 
333 	if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
334 		throw Alsa::MakeError(err, "snd_pcm_hw_params_any() failed");
335 
336 	if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
337 	                                       SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
338 		throw Alsa::MakeError(err, "snd_pcm_hw_params_set_access() failed");
339 
340 	if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params,
341 	                               	ToAlsaPcmFormat(audio_format.format))) < 0)
342 		throw Alsa::MakeError(err, "Cannot set sample format");
343 
344 	if ((err = snd_pcm_hw_params_set_channels(capture_handle,
345 	                                    hw_params, audio_format.channels)) < 0)
346 		throw Alsa::MakeError(err, "Cannot set channels");
347 
348 	if ((err = snd_pcm_hw_params_set_rate(capture_handle,
349 	                              hw_params, audio_format.sample_rate, 0)) < 0)
350 		throw Alsa::MakeError(err, "Cannot set sample rate");
351 
352 	snd_pcm_uframes_t buffer_size_min, buffer_size_max;
353 	snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
354 	snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
355 	unsigned buffer_time_min, buffer_time_max;
356 	snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, nullptr);
357 	snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, nullptr);
358 	FmtDebug(alsa_input_domain, "buffer: size={}..{} time={}..{}",
359 		 buffer_size_min, buffer_size_max,
360 		 buffer_time_min, buffer_time_max);
361 
362 	snd_pcm_uframes_t period_size_min, period_size_max;
363 	snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, nullptr);
364 	snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, nullptr);
365 	unsigned period_time_min, period_time_max;
366 	snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, nullptr);
367 	snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, nullptr);
368 	FmtDebug(alsa_input_domain, "period: size={}..{} time={}..{}",
369 		 period_size_min, period_size_max,
370 		 period_time_min, period_time_max);
371 
372 	/* choose the maximum possible buffer_size ... */
373 	snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params,
374 					  buffer_size_max);
375 
376 	/* ... and calculate the period_size to have four periods in
377 	   one buffer; this way, we get woken up often enough to avoid
378 	   buffer overruns, but not too often */
379 	snd_pcm_uframes_t buffer_size;
380 	if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
381 		snd_pcm_uframes_t period_size = buffer_size / 4;
382 		int direction = -1;
383 		if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
384 		                             hw_params, &period_size, &direction)) < 0)
385 			throw Alsa::MakeError(err, "Cannot set period size");
386 	}
387 
388 	if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
389 		throw Alsa::MakeError(err, "snd_pcm_hw_params() failed");
390 
391 	snd_pcm_uframes_t alsa_buffer_size;
392 	err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa_buffer_size);
393 	if (err < 0)
394 		throw Alsa::MakeError(err, "snd_pcm_hw_params_get_buffer_size() failed");
395 
396 	snd_pcm_uframes_t alsa_period_size;
397 	err = snd_pcm_hw_params_get_period_size(hw_params, &alsa_period_size,
398 						nullptr);
399 	if (err < 0)
400 		throw Alsa::MakeError(err, "snd_pcm_hw_params_get_period_size() failed");
401 
402 	FmtDebug(alsa_input_domain, "buffer_size={} period_size={}",
403 		 alsa_buffer_size, alsa_period_size);
404 
405 	snd_pcm_sw_params_t *sw_params;
406 	snd_pcm_sw_params_alloca(&sw_params);
407 
408 	snd_pcm_sw_params_current(capture_handle, sw_params);
409 
410 	if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
411 		throw Alsa::MakeError(err, "snd_pcm_sw_params() failed");
412 }
413 
414 inline void
OpenDevice(const SourceSpec & spec)415 AlsaInputStream::OpenDevice(const SourceSpec &spec)
416 {
417 	int err;
418 
419 	if ((err = snd_pcm_open(&capture_handle, spec.GetDeviceName(),
420 				SND_PCM_STREAM_CAPTURE,
421 				SND_PCM_NONBLOCK | global_config.mode)) < 0)
422 		throw Alsa::MakeError(err,
423 				      fmt::format("Failed to open device {}",
424 						  spec.GetDeviceName()).c_str());
425 
426 	try {
427 		ConfigureCapture(spec.GetAudioFormat());
428 	} catch (...) {
429 		snd_pcm_close(capture_handle);
430 		throw;
431 	}
432 
433 	snd_pcm_prepare(capture_handle);
434 }
435 
436 /*#########################  Plugin Functions  ##############################*/
437 
438 
439 static void
alsa_input_init(EventLoop & event_loop,const ConfigBlock & block)440 alsa_input_init(EventLoop &event_loop, const ConfigBlock &block)
441 {
442 	global_config.event_loop = &event_loop;
443 	global_config.default_device = block.GetBlockValue("default_device", BUILTIN_DEFAULT_DEVICE);
444 	global_config.default_format = block.GetBlockValue("default_format", BUILTIN_DEFAULT_FORMAT);
445 	global_config.mode = 0;
446 
447 #ifdef SND_PCM_NO_AUTO_RESAMPLE
448 	if (!block.GetBlockValue("auto_resample", true))
449 		global_config.mode |= SND_PCM_NO_AUTO_RESAMPLE;
450 #endif
451 
452 #ifdef SND_PCM_NO_AUTO_CHANNELS
453 	if (!block.GetBlockValue("auto_channels", true))
454 		global_config.mode |= SND_PCM_NO_AUTO_CHANNELS;
455 #endif
456 
457 #ifdef SND_PCM_NO_AUTO_FORMAT
458 	if (!block.GetBlockValue("auto_format", true))
459 		global_config.mode |= SND_PCM_NO_AUTO_FORMAT;
460 #endif
461 }
462 
463 static InputStreamPtr
alsa_input_open(const char * uri,Mutex & mutex)464 alsa_input_open(const char *uri, Mutex &mutex)
465 {
466 	return AlsaInputStream::Create(*global_config.event_loop, uri,
467 				       mutex);
468 }
469 
470 static constexpr const char *alsa_prefixes[] = {
471 	ALSA_URI_PREFIX,
472 	nullptr
473 };
474 
475 const struct InputPlugin input_plugin_alsa = {
476 	"alsa",
477 	alsa_prefixes,
478 	alsa_input_init,
479 	nullptr,
480 	alsa_input_open,
481 	nullptr
482 };
483