AlsaInputPlugin.cxx 13.4 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * ALSA code based on an example by Paul Davis released under GPL here:
 * http://equalarea.com/paul/alsa-audio.html
 * and one by Matthias Nagorni, also GPL, here:
 * http://alsamodular.sourceforge.net/alsa_programming_howto.html
 */

#include "AlsaInputPlugin.hxx"
28
#include "lib/alsa/NonBlock.hxx"
29
#include "lib/alsa/Format.hxx"
Max Kellermann's avatar
Max Kellermann committed
30
#include "../InputPlugin.hxx"
31
#include "../AsyncInputStream.hxx"
32
#include "event/Call.hxx"
33
#include "thread/Cond.hxx"
34
#include "config/Block.hxx"
35
#include "util/Domain.hxx"
36
#include "util/RuntimeError.hxx"
37
#include "util/StringCompare.hxx"
38
#include "util/ASCII.hxx"
39 40 41
#include "util/DivideString.hxx"
#include "AudioParser.hxx"
#include "AudioFormat.hxx"
42
#include "Log.hxx"
43
#include "event/MultiSocketMonitor.hxx"
44
#include "event/DeferEvent.hxx"
45 46 47

#include <alsa/asoundlib.h>

48 49 50
#include <assert.h>
#include <string.h>

51 52
static constexpr Domain alsa_input_domain("alsa");

53
static constexpr auto ALSA_URI_PREFIX = "alsa://";
54

55 56
static constexpr auto BUILTIN_DEFAULT_DEVICE = "default";
static constexpr auto BUILTIN_DEFAULT_FORMAT = "48000:16:2";
57

58 59
static constexpr auto DEFAULT_BUFFER_TIME = std::chrono::milliseconds(1000);
static constexpr auto DEFAULT_RESUME_TIME = DEFAULT_BUFFER_TIME / 2;
60

61 62 63 64 65 66 67 68 69

static struct {
	EventLoop *event_loop;
	const char *default_device;
	const char *default_format;
	int mode;
} global_config;


70
class AlsaInputStream final
71
	: public AsyncInputStream,
72
	  MultiSocketMonitor {
73 74 75 76 77 78

	/**
	 * The configured name of the ALSA device.
	 */
	const std::string device;

79
	snd_pcm_t *capture_handle;
80
	const size_t frame_size;
81

82
	AlsaNonBlockPcm non_block;
83

84 85
	DeferEvent defer_invalidate_sockets;

86 87
public:

88
	class SourceSpec;
89

90 91 92
	AlsaInputStream(EventLoop &_loop,
			Mutex &_mutex,
			const SourceSpec &spec);
93 94

	~AlsaInputStream() {
95
		BlockingCall(MultiSocketMonitor::GetEventLoop(), [this](){
96
				MultiSocketMonitor::Reset();
97
				defer_invalidate_sockets.Cancel();
98 99
			});

100 101
		snd_pcm_close(capture_handle);
	}
102

103
	static InputStreamPtr Create(EventLoop &event_loop, const char *uri,
104
				     Mutex &mutex);
105

106 107 108 109
protected:
	/* virtual methods from AsyncInputStream */
	virtual void DoResume() override {
		snd_pcm_resume(capture_handle);
110

111
		InvalidateSockets();
112 113
	}

114 115 116
	virtual void DoSeek(gcc_unused offset_type new_offset) override {
		/* unreachable because seekable==false */
		SeekDone();
117 118 119
	}

private:
120 121
	void OpenDevice(const SourceSpec &spec);
	void ConfigureCapture(AudioFormat audio_format);
122

123 124 125 126 127
	void Pause() {
		AsyncInputStream::Pause();
		InvalidateSockets();
	}

128 129
	int Recover(int err);

130 131 132
	/* virtual methods from class MultiSocketMonitor */
	std::chrono::steady_clock::duration PrepareSockets() noexcept override;
	void DispatchSockets() noexcept override;
133 134
};

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

class AlsaInputStream::SourceSpec {
	const char *uri;
	const char *device_name;
	const char *format_string;
	AudioFormat audio_format;
	DivideString components;

public:
	SourceSpec(const char *_uri)
		: uri(_uri)
		, components(uri, '?')
	{
		if (components.IsDefined()) {
			device_name = StringAfterPrefixCaseASCII(components.GetFirst(),
			                                                  ALSA_URI_PREFIX);
			format_string = StringAfterPrefixCaseASCII(components.GetSecond(),
			                                                        "format=");
		}
		else {
			device_name = StringAfterPrefixCaseASCII(uri, ALSA_URI_PREFIX);
156
			format_string = global_config.default_format;
157 158 159
		}
		if (IsValidScheme()) {
			if (*device_name == 0)
160
				device_name = global_config.default_device;
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
			if (format_string != nullptr)
				audio_format = ParseAudioFormat(format_string, false);
		}
	}
	bool IsValidScheme() const noexcept {
		return device_name != nullptr;
	}
	bool IsValid() const noexcept {
		return (device_name != nullptr) && (format_string != nullptr);
	}
	const char *GetURI() const noexcept {
		return uri;
	}
	const char *GetDeviceName() const noexcept {
		return device_name;
	}
	const char *GetFormatString() const noexcept {
		return format_string;
	}
	AudioFormat GetAudioFormat() const noexcept {
		return audio_format;
	}
};

AlsaInputStream::AlsaInputStream(EventLoop &_loop,
		Mutex &_mutex,
		const SourceSpec &spec)
	:AsyncInputStream(_loop, spec.GetURI(), _mutex,
		 spec.GetAudioFormat().TimeToSize(DEFAULT_BUFFER_TIME),
		 spec.GetAudioFormat().TimeToSize(DEFAULT_RESUME_TIME)),
	 MultiSocketMonitor(_loop),
	 device(spec.GetDeviceName()),
	 frame_size(spec.GetAudioFormat().GetFrameSize()),
	 defer_invalidate_sockets(_loop,
				  BIND_THIS_METHOD(InvalidateSockets))
{
	OpenDevice(spec);

	std::string mimestr = "audio/x-mpd-alsa-pcm;format=";
	mimestr += spec.GetFormatString();
	SetMimeType(mimestr.c_str());

	InputStream::SetReady();

	snd_pcm_start(capture_handle);

	defer_invalidate_sockets.Schedule();
}

210
inline InputStreamPtr
211
AlsaInputStream::Create(EventLoop &event_loop, const char *uri,
212
			Mutex &mutex)
213
{
214
	assert(uri != nullptr);
215

216 217 218
	AlsaInputStream::SourceSpec spec(uri);
	if (!spec.IsValidScheme())
		return nullptr;
219

220
	return std::make_unique<AlsaInputStream>(event_loop, mutex, spec);
221 222
}

223
std::chrono::steady_clock::duration
224
AlsaInputStream::PrepareSockets() noexcept
225
{
226
	if (IsPaused()) {
227
		ClearSocketList();
228
		return std::chrono::steady_clock::duration(-1);
229 230
	}

231
	return non_block.PrepareSockets(*this, capture_handle);
232 233 234
}

void
235
AlsaInputStream::DispatchSockets() noexcept
236
{
237 238
	non_block.DispatchSockets(*this, capture_handle);

239
	const std::lock_guard<Mutex> protect(mutex);
240 241 242 243 244 245 246 247 248 249 250 251

	auto w = PrepareWriteBuffer();
	const snd_pcm_uframes_t w_frames = w.size / frame_size;
	if (w_frames == 0) {
		/* buffer is full */
		Pause();
		return;
	}

	snd_pcm_sframes_t n_frames;
	while ((n_frames = snd_pcm_readi(capture_handle,
					 w.data, w_frames)) < 0) {
252 253 254
		if (n_frames == -EAGAIN)
			return;

255
		if (Recover(n_frames) < 0) {
256
			postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
257
			InvokeOnAvailable();
258 259 260 261 262 263
			return;
		}
	}

	size_t nbytes = n_frames * frame_size;
	CommitWriteBuffer(nbytes);
264 265 266 267 268 269 270
}

inline int
AlsaInputStream::Recover(int err)
{
	switch(err) {
	case -EPIPE:
271 272 273 274 275 276 277 278 279
		FormatDebug(alsa_input_domain,
			    "Overrun on ALSA capture device \"%s\"",
			    device.c_str());
		break;

	case -ESTRPIPE:
		FormatDebug(alsa_input_domain,
			    "ALSA capture device \"%s\" was suspended",
			    device.c_str());
280 281 282 283 284 285 286 287 288 289 290 291 292
		break;
	}

	switch (snd_pcm_state(capture_handle)) {
	case SND_PCM_STATE_PAUSED:
		err = snd_pcm_pause(capture_handle, /* disable */ 0);
		break;

	case SND_PCM_STATE_SUSPENDED:
		err = snd_pcm_resume(capture_handle);
		if (err == -EAGAIN)
			return 0;
		/* fall-through to snd_pcm_prepare: */
293 294 295
#if GCC_CHECK_VERSION(7,0)
		[[fallthrough]];
#endif
296 297 298 299
	case SND_PCM_STATE_OPEN:
	case SND_PCM_STATE_SETUP:
	case SND_PCM_STATE_XRUN:
		err = snd_pcm_prepare(capture_handle);
300 301
		if (err == 0)
			err = snd_pcm_start(capture_handle);
302 303 304 305
		break;

	case SND_PCM_STATE_DISCONNECTED:
		break;
306

307 308 309 310 311
	case SND_PCM_STATE_PREPARED:
	case SND_PCM_STATE_RUNNING:
	case SND_PCM_STATE_DRAINING:
		/* this is no error, so just keep running */
		err = 0;
312
		break;
313 314 315 316 317 318

	default:
		/* this default case is just here to work around
		   -Wswitch due to SND_PCM_STATE_PRIVATE1 (libasound
		   1.1.6) */
		break;
319
	}
320

321 322 323
	return err;
}

324 325
void
AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
326 327
{
	int err;
328 329

	snd_pcm_hw_params_t *hw_params;
330
	snd_pcm_hw_params_alloca(&hw_params);
331

332 333 334
	if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
		throw FormatRuntimeError("Cannot initialize hardware parameter structure (%s)",
					 snd_strerror(err));
335

336 337
	if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params,
	                                       SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
338 339
		throw FormatRuntimeError("Cannot set access type (%s)",
					 snd_strerror(err));
340

341 342
	if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params,
	                               	ToAlsaPcmFormat(audio_format.format))) < 0)
343 344
		throw FormatRuntimeError("Cannot set sample format (%s)",
					 snd_strerror(err));
345

346 347
	if ((err = snd_pcm_hw_params_set_channels(capture_handle,
	                                    hw_params, audio_format.channels)) < 0)
348 349
		throw FormatRuntimeError("Cannot set channels (%s)",
					 snd_strerror(err));
350

351 352
	if ((err = snd_pcm_hw_params_set_rate(capture_handle,
	                              hw_params, audio_format.sample_rate, 0)) < 0)
353 354
		throw FormatRuntimeError("Cannot set sample rate (%s)",
					 snd_strerror(err));
355

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
	snd_pcm_uframes_t buffer_size_min, buffer_size_max;
	snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min);
	snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max);
	unsigned buffer_time_min, buffer_time_max;
	snd_pcm_hw_params_get_buffer_time_min(hw_params, &buffer_time_min, 0);
	snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time_max, 0);
	FormatDebug(alsa_input_domain, "buffer: size=%u..%u time=%u..%u",
		    (unsigned)buffer_size_min, (unsigned)buffer_size_max,
		    buffer_time_min, buffer_time_max);

	snd_pcm_uframes_t period_size_min, period_size_max;
	snd_pcm_hw_params_get_period_size_min(hw_params, &period_size_min, 0);
	snd_pcm_hw_params_get_period_size_max(hw_params, &period_size_max, 0);
	unsigned period_time_min, period_time_max;
	snd_pcm_hw_params_get_period_time_min(hw_params, &period_time_min, 0);
	snd_pcm_hw_params_get_period_time_max(hw_params, &period_time_max, 0);
	FormatDebug(alsa_input_domain, "period: size=%u..%u time=%u..%u",
		    (unsigned)period_size_min, (unsigned)period_size_max,
		    period_time_min, period_time_max);

376 377 378 379 380 381 382 383 384 385 386
	/* choose the maximum possible buffer_size ... */
	snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params,
					  buffer_size_max);

	/* ... and calculate the period_size to have four periods in
	   one buffer; this way, we get woken up often enough to avoid
	   buffer overruns, but not too often */
	snd_pcm_uframes_t buffer_size;
	if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
		snd_pcm_uframes_t period_size = buffer_size / 4;
		int direction = -1;
387 388
		if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
		                             hw_params, &period_size, &direction)) < 0)
389 390 391
			throw FormatRuntimeError("Cannot set period size (%s)",
						 snd_strerror(err));
	}
392

393 394 395
	if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0)
		throw FormatRuntimeError("Cannot set parameters (%s)",
					 snd_strerror(err));
396

397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
	snd_pcm_uframes_t alsa_buffer_size;
	err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa_buffer_size);
	if (err < 0)
		throw FormatRuntimeError("snd_pcm_hw_params_get_buffer_size() failed: %s",
					 snd_strerror(-err));

	snd_pcm_uframes_t alsa_period_size;
	err = snd_pcm_hw_params_get_period_size(hw_params, &alsa_period_size,
						nullptr);
	if (err < 0)
		throw FormatRuntimeError("snd_pcm_hw_params_get_period_size() failed: %s",
					 snd_strerror(-err));

	FormatDebug(alsa_input_domain, "buffer_size=%u period_size=%u",
		    (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);

413
	snd_pcm_sw_params_t *sw_params;
414
	snd_pcm_sw_params_alloca(&sw_params);
415 416 417

	snd_pcm_sw_params_current(capture_handle, sw_params);

418 419 420
	if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
		throw FormatRuntimeError("unable to install sw params (%s)",
					 snd_strerror(err));
421 422
}

423 424
inline void
AlsaInputStream::OpenDevice(const SourceSpec &spec)
425 426
{
	int err;
427 428

	if ((err = snd_pcm_open(&capture_handle, spec.GetDeviceName(),
429
				SND_PCM_STREAM_CAPTURE,
430
				SND_PCM_NONBLOCK | global_config.mode)) < 0)
431
		throw FormatRuntimeError("Failed to open device: %s (%s)",
432
					 spec.GetDeviceName(), snd_strerror(err));
433

434
	try {
435
		ConfigureCapture(spec.GetAudioFormat());
436
	} catch (...) {
437
		snd_pcm_close(capture_handle);
438
		throw;
439 440
	}

441 442 443 444 445
	snd_pcm_prepare(capture_handle);
}

/*#########################  Plugin Functions  ##############################*/

446 447

static void
448
alsa_input_init(EventLoop &event_loop, const ConfigBlock &block)
449
{
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
	global_config.event_loop = &event_loop;
	global_config.default_device = block.GetBlockValue("default_device", BUILTIN_DEFAULT_DEVICE);
	global_config.default_format = block.GetBlockValue("default_format", BUILTIN_DEFAULT_FORMAT);
	global_config.mode = 0;

#ifdef SND_PCM_NO_AUTO_RESAMPLE
	if (!block.GetBlockValue("auto_resample", true))
		global_config.mode |= SND_PCM_NO_AUTO_RESAMPLE;
#endif

#ifdef SND_PCM_NO_AUTO_CHANNELS
	if (!block.GetBlockValue("auto_channels", true))
		global_config.mode |= SND_PCM_NO_AUTO_CHANNELS;
#endif

#ifdef SND_PCM_NO_AUTO_FORMAT
	if (!block.GetBlockValue("auto_format", true))
		global_config.mode |= SND_PCM_NO_AUTO_FORMAT;
#endif
469 470
}

471
static InputStreamPtr
472
alsa_input_open(const char *uri, Mutex &mutex)
473
{
474
	return AlsaInputStream::Create(*global_config.event_loop, uri,
475
				       mutex);
476 477
}

478
static constexpr const char *alsa_prefixes[] = {
479
	ALSA_URI_PREFIX,
480 481 482
	nullptr
};

483
const struct InputPlugin input_plugin_alsa = {
484
	"alsa",
485
	alsa_prefixes,
486
	alsa_input_init,
487 488
	nullptr,
	alsa_input_open,
489
};