AlsaInputPlugin.cxx 10.2 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 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 28
 * 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 "config.h"
#include "AlsaInputPlugin.hxx"
Max Kellermann's avatar
Max Kellermann committed
29 30
#include "../InputPlugin.hxx"
#include "../InputStream.hxx"
31 32 33
#include "util/Domain.hxx"
#include "util/Error.hxx"
#include "util/StringUtil.hxx"
34
#include "util/ReusableArray.hxx"
35

36
#include "Log.hxx"
37 38 39 40 41 42
#include "event/MultiSocketMonitor.hxx"
#include "event/DeferredMonitor.hxx"
#include "event/Call.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "IOThread.hxx"
43 44 45

#include <alsa/asoundlib.h>

46 47
#include <atomic>

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

51 52 53 54 55 56 57 58 59
static constexpr Domain alsa_input_domain("alsa");

static constexpr const char *default_device = "hw:0,0";

// the following defaults are because the PcmDecoderPlugin forces CD format
static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16;
static constexpr int default_channels = 2; // stereo
static constexpr unsigned int default_rate = 44100; // cd quality

60 61 62 63 64 65 66 67 68
/**
 * This value should be the same as the read buffer size defined in
 * PcmDecoderPlugin.cxx:pcm_stream_decode().
 * We use it to calculate how many audio frames to buffer in the alsa driver
 * before reading from the device. snd_pcm_readi() blocks until that many
 * frames are ready.
 */
static constexpr size_t read_buffer_size = 4096;

69 70 71
class AlsaInputStream final
	: public InputStream,
	  MultiSocketMonitor, DeferredMonitor {
72 73
	snd_pcm_t *capture_handle;
	size_t frame_size;
74 75 76 77 78 79 80 81 82 83 84 85 86
	int frames_to_read;
	bool eof;

	/**
	 * Is somebody waiting for data?  This is set by method
	 * Available().
	 */
	std::atomic_bool waiting;

	ReusableArray<pollfd> pfd_buffer;

public:
	AlsaInputStream(EventLoop &loop,
87
			const char *_uri, Mutex &_mutex, Cond &_cond,
88
			snd_pcm_t *_handle, int _frame_size)
89
		:InputStream(_uri, _mutex, _cond),
90
		 MultiSocketMonitor(loop),
91 92 93 94 95
		 DeferredMonitor(loop),
		 capture_handle(_handle),
		 frame_size(_frame_size),
		 eof(false)
	{
96
		assert(_uri != nullptr);
97 98 99 100 101
		assert(_handle != nullptr);

		/* this mime type forces use of the PcmDecoderPlugin.
		   Needs to be generalised when/if that decoder is
		   updated to support other audio formats */
102 103
		SetMimeType("audio/x-mpd-cdda-pcm");
		InputStream::SetReady();
104

105 106 107 108 109
		frames_to_read = read_buffer_size / frame_size;

		snd_pcm_start(capture_handle);

		DeferredMonitor::Schedule();
110 111 112 113 114
	}

	~AlsaInputStream() {
		snd_pcm_close(capture_handle);
	}
115 116 117 118 119 120

	using DeferredMonitor::GetEventLoop;

	static InputStream *Create(const char *uri, Mutex &mutex, Cond &cond,
				   Error &error);

121 122 123 124 125 126 127
	/* virtual methods from InputStream */

	bool IsEOF() override {
		return eof;
	}

	bool IsAvailable() override {
128 129 130 131 132 133 134 135 136
		if (snd_pcm_avail(capture_handle) > frames_to_read)
			return true;

		if (!waiting.exchange(true))
			SafeInvalidateSockets();

		return false;
	}

137
	size_t Read(void *ptr, size_t size, Error &error) override;
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

private:
	static snd_pcm_t *OpenDevice(const char *device, int rate,
				     snd_pcm_format_t format, int channels,
				     Error &error);

	int Recover(int err);

	void SafeInvalidateSockets() {
		DeferredMonitor::Schedule();
	}

	virtual void RunDeferred() override {
		InvalidateSockets();
	}

	virtual int PrepareSockets() override;
	virtual void DispatchSockets() override;
156 157
};

158 159 160
inline InputStream *
AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond,
			Error &error)
161
{
162 163
	const char *const scheme = "alsa://";
	if (!StringStartsWith(uri, scheme))
164 165
		return nullptr;

166 167
	const char *device = uri + strlen(scheme);
	if (strlen(device) == 0)
168 169
		device = default_device;

170 171 172 173 174 175 176 177 178 179 180 181 182
	/* placeholders - eventually user-requested audio format will
	   be passed via the URI. For now we just force the
	   defaults */
	int rate = default_rate;
	snd_pcm_format_t format = default_format;
	int channels = default_channels;

	snd_pcm_t *handle = OpenDevice(device, rate, format, channels,
				       error);
	if (handle == nullptr)
		return nullptr;

	int frame_size = snd_pcm_format_width(format) / 8 * channels;
183 184 185
	return new AlsaInputStream(io_thread_get(),
				   uri, mutex, cond,
				   handle, frame_size);
186 187
}

188
size_t
189
AlsaInputStream::Read(void *ptr, size_t read_size, Error &error)
190 191 192
{
	assert(ptr != nullptr);

193
	int num_frames = read_size / frame_size;
194 195 196 197 198 199 200 201 202 203 204
	int ret;
	while ((ret = snd_pcm_readi(capture_handle, ptr, num_frames)) < 0) {
		if (Recover(ret) < 0) {
			eof = true;
			error.Format(alsa_input_domain,
				     "PCM error - stream aborted");
			return 0;
		}
	}

	size_t nbytes = ret * frame_size;
205
	offset += nbytes;
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
	return nbytes;
}

int
AlsaInputStream::PrepareSockets()
{
	if (!waiting) {
		ClearSocketList();
		return -1;
	}

	int count = snd_pcm_poll_descriptors_count(capture_handle);
	if (count < 0) {
		ClearSocketList();
		return -1;
	}

	struct pollfd *pfds = pfd_buffer.Get(count);

	count = snd_pcm_poll_descriptors(capture_handle, pfds, count);
	if (count < 0)
		count = 0;

	ReplaceSocketList(pfds, count);
	return -1;
}

void
AlsaInputStream::DispatchSockets()
{
	waiting = false;

238
	const ScopeLock protect(mutex);
239
	/* wake up the thread that is waiting for more data */
240
	cond.broadcast();
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
}

inline int
AlsaInputStream::Recover(int err)
{
	switch(err) {
	case -EPIPE:
		LogDebug(alsa_input_domain, "Buffer Overrun");
		// drop through
	case -ESTRPIPE:
	case -EINTR:
		err = snd_pcm_recover(capture_handle, err, 1);
		break;
	default:
		// something broken somewhere, give up
		err = -1;
	}
	return err;
}

inline snd_pcm_t *
AlsaInputStream::OpenDevice(const char *device,
			    int rate, snd_pcm_format_t format, int channels,
			    Error &error)
{
266
	snd_pcm_t *capture_handle;
267
	int err;
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
	if ((err = snd_pcm_open(&capture_handle, device,
				SND_PCM_STREAM_CAPTURE, 0)) < 0) {
		error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err));
		return nullptr;
	}

	snd_pcm_hw_params_t *hw_params;
	if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
		error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err));
		snd_pcm_close(capture_handle);
		return nullptr;
	}

	if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
		error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err));
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(capture_handle);
		return nullptr;
	}

	if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
		error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err));
290
		snd_pcm_hw_params_free(hw_params);
291 292 293 294
		snd_pcm_close(capture_handle);
		return nullptr;
	}

295
	if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
296 297 298 299 300 301
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(capture_handle);
		error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err));
		return nullptr;
	}

302
	if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0) {
303 304 305 306 307 308
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(capture_handle);
		error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err));
		return nullptr;
	}

309
	if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0) {
310 311 312 313 314 315
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(capture_handle);
		error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err));
		return nullptr;
	}

316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	/* period needs to be big enough so that poll() doesn't fire too often,
	 * but small enough that buffer overruns don't occur if Read() is not
	 * invoked often enough.
	 * the calculation here is empirical; however all measurements were
	 * done using 44100:16:2. When we extend this plugin to support
	 * other audio formats then this may need to be revisited */
	snd_pcm_uframes_t period = read_buffer_size * 2;
	int direction = -1;
	if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
							  &period, &direction)) < 0) {
		error.Format(alsa_input_domain, "Cannot set period size (%s)",
			     snd_strerror(err));
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(capture_handle);
		return nullptr;
	}

333
	if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
334 335
		error.Format(alsa_input_domain, "Cannot set parameters (%s)",
			     snd_strerror(err));
336 337 338 339 340 341 342
		snd_pcm_hw_params_free(hw_params);
		snd_pcm_close(capture_handle);
		return nullptr;
	}

	snd_pcm_hw_params_free (hw_params);

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
	snd_pcm_sw_params_t *sw_params;

	snd_pcm_sw_params_malloc(&sw_params);
	snd_pcm_sw_params_current(capture_handle, sw_params);

	if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
							 period)) < 0)  {
		error.Format(alsa_input_domain,
			     "unable to set start threshold (%s)", snd_strerror(err));
		snd_pcm_sw_params_free(sw_params);
		snd_pcm_close(capture_handle);
		return nullptr;
	}

	if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) {
		error.Format(alsa_input_domain,
			     "unable to install sw params (%s)", snd_strerror(err));
		snd_pcm_sw_params_free(sw_params);
		snd_pcm_close(capture_handle);
		return nullptr;
	}

	snd_pcm_sw_params_free(sw_params);

	snd_pcm_prepare(capture_handle);

	return capture_handle;
}

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

static InputStream *
alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error)
{
	return AlsaInputStream::Create(uri, mutex, cond, error);
378 379 380
}

const struct InputPlugin input_plugin_alsa = {
381 382 383 384
	"alsa",
	nullptr,
	nullptr,
	alsa_input_open,
385
};