alsa_plugin.c 15.7 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2010 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * 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.
18 19
 */

20 21
#include "config.h"
#include "output_api.h"
22
#include "mixer_list.h"
23

24
#include <glib.h>
25
#include <alsa/asoundlib.h>
26

Max Kellermann's avatar
Max Kellermann committed
27 28 29
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "alsa"

30 31 32
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API

33 34
static const char default_device[] = "default";

35 36 37 38
enum {
	MPD_ALSA_BUFFER_TIME_US = 500000,
};

39
#define MPD_ALSA_RETRY_NR 5
40

Avuton Olrich's avatar
Avuton Olrich committed
41 42
typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
					snd_pcm_uframes_t size);
43

Max Kellermann's avatar
Max Kellermann committed
44
struct alsa_data {
Max Kellermann's avatar
Max Kellermann committed
45 46
	/** the configured name of the ALSA device; NULL for the
	    default device */
47
	char *device;
48

Max Kellermann's avatar
Max Kellermann committed
49 50 51 52 53 54 55 56 57
	/** use memory mapped I/O? */
	bool use_mmap;

	/** libasound's buffer_time setting (in microseconds) */
	unsigned int buffer_time;

	/** libasound's period_time setting (in microseconds) */
	unsigned int period_time;

58 59 60
	/** the mode flags passed to snd_pcm_open */
	int mode;

Max Kellermann's avatar
Max Kellermann committed
61
	/** the libasound PCM device handle */
Max Kellermann's avatar
Max Kellermann committed
62
	snd_pcm_t *pcm;
Max Kellermann's avatar
Max Kellermann committed
63 64 65 66 67 68

	/**
	 * a pointer to the libasound writei() function, which is
	 * snd_pcm_writei() or snd_pcm_mmap_writei(), depending on the
	 * use_mmap configuration
	 */
Avuton Olrich's avatar
Avuton Olrich committed
69
	alsa_writei_t *writei;
Max Kellermann's avatar
Max Kellermann committed
70 71

	/** the size of one audio frame */
72
	size_t frame_size;
73 74 75 76 77 78 79 80 81 82

	/**
	 * The size of one period, in number of frames.
	 */
	snd_pcm_uframes_t period_frames;

	/**
	 * The number of frames written in the current period.
	 */
	snd_pcm_uframes_t period_position;
Max Kellermann's avatar
Max Kellermann committed
83
};
84

85 86 87 88 89 90 91 92 93
/**
 * The quark used for GError.domain.
 */
static inline GQuark
alsa_output_quark(void)
{
	return g_quark_from_static_string("alsa_output");
}

94
static const char *
Max Kellermann's avatar
Max Kellermann committed
95
alsa_device(const struct alsa_data *ad)
96 97 98 99
{
	return ad->device != NULL ? ad->device : default_device;
}

Max Kellermann's avatar
Max Kellermann committed
100 101
static struct alsa_data *
alsa_data_new(void)
Avuton Olrich's avatar
Avuton Olrich committed
102
{
Max Kellermann's avatar
Max Kellermann committed
103
	struct alsa_data *ret = g_new(struct alsa_data, 1);
104

105
	ret->mode = 0;
106 107 108 109 110
	ret->writei = snd_pcm_writei;

	return ret;
}

Max Kellermann's avatar
Max Kellermann committed
111 112
static void
alsa_data_free(struct alsa_data *ad)
Avuton Olrich's avatar
Avuton Olrich committed
113
{
114
	g_free(ad->device);
115
	g_free(ad);
116 117
}

118
static void
119
alsa_configure(struct alsa_data *ad, const struct config_param *param)
120
{
121
	ad->device = config_dup_block_string(param, "device", NULL);
122

Max Kellermann's avatar
Max Kellermann committed
123
	ad->use_mmap = config_get_block_bool(param, "use_mmap", false);
124

125 126
	ad->buffer_time = config_get_block_unsigned(param, "buffer_time",
			MPD_ALSA_BUFFER_TIME_US);
127
	ad->period_time = config_get_block_unsigned(param, "period_time", 0);
128

129
#ifdef SND_PCM_NO_AUTO_RESAMPLE
130
	if (!config_get_block_bool(param, "auto_resample", true))
131
		ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
132
#endif
133

134
#ifdef SND_PCM_NO_AUTO_CHANNELS
135
	if (!config_get_block_bool(param, "auto_channels", true))
136
		ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
137
#endif
138

139
#ifdef SND_PCM_NO_AUTO_FORMAT
140
	if (!config_get_block_bool(param, "auto_format", true))
141
		ad->mode |= SND_PCM_NO_AUTO_FORMAT;
142
#endif
143 144
}

145
static void *
146
alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format,
147 148
	  const struct config_param *param,
	  G_GNUC_UNUSED GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
149
{
Max Kellermann's avatar
Max Kellermann committed
150
	struct alsa_data *ad = alsa_data_new();
151

Max Kellermann's avatar
Max Kellermann committed
152
	alsa_configure(ad, param);
153

154
	return ad;
155 156
}

Max Kellermann's avatar
Max Kellermann committed
157 158
static void
alsa_finish(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
159
{
Max Kellermann's avatar
Max Kellermann committed
160
	struct alsa_data *ad = data;
161

Max Kellermann's avatar
Max Kellermann committed
162
	alsa_data_free(ad);
163 164 165

	/* free libasound's config cache */
	snd_config_update_free_global();
166 167
}

Max Kellermann's avatar
Max Kellermann committed
168 169
static bool
alsa_test_default_device(void)
170
{
Avuton Olrich's avatar
Avuton Olrich committed
171
	snd_pcm_t *handle;
172

173 174
	int ret = snd_pcm_open(&handle, default_device,
	                       SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
Avuton Olrich's avatar
Avuton Olrich committed
175
	if (ret) {
Max Kellermann's avatar
Max Kellermann committed
176 177
		g_message("Error opening default ALSA device: %s\n",
			  snd_strerror(-ret));
178
		return false;
Avuton Olrich's avatar
Avuton Olrich committed
179 180
	} else
		snd_pcm_close(handle);
181

182
	return true;
183 184
}

Max Kellermann's avatar
Max Kellermann committed
185
static snd_pcm_format_t
186
get_bitformat(enum sample_format sample_format)
187
{
188
	switch (sample_format) {
189 190 191 192 193 194 195 196 197
	case SAMPLE_FORMAT_S8:
		return SND_PCM_FORMAT_S8;

	case SAMPLE_FORMAT_S16:
		return SND_PCM_FORMAT_S16;

	case SAMPLE_FORMAT_S24_P32:
		return SND_PCM_FORMAT_S24;

198 199 200 201 202
	case SAMPLE_FORMAT_S24:
		return G_BYTE_ORDER == G_BIG_ENDIAN
			? SND_PCM_FORMAT_S24_3BE
			: SND_PCM_FORMAT_S24_3LE;

203 204 205 206 207
	case SAMPLE_FORMAT_S32:
		return SND_PCM_FORMAT_S32;

	default:
		return SND_PCM_FORMAT_UNKNOWN;
208 209 210
	}
}

211 212 213 214 215 216 217 218 219
static snd_pcm_format_t
byteswap_bitformat(snd_pcm_format_t fmt)
{
	switch(fmt) {
	case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
	case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
	case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
	case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
	case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
220 221 222 223 224 225 226

	case SND_PCM_FORMAT_S24_3BE:
		return SND_PCM_FORMAT_S24_3LE;

	case SND_PCM_FORMAT_S24_3LE:
		return SND_PCM_FORMAT_S24_3BE;

227 228 229 230
	case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
	default: return SND_PCM_FORMAT_UNKNOWN;
	}
}
231

232 233 234 235 236 237 238 239 240 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 266 267 268 269 270 271 272 273
/**
 * Attempts to configure the specified sample format.
 */
static int
alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
		       struct audio_format *audio_format,
		       enum sample_format sample_format)
{
	snd_pcm_format_t alsa_format = get_bitformat(sample_format);
	if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
		return -EINVAL;

	int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format);
	if (err == 0)
		audio_format->format = sample_format;

	return err;
}

/**
 * Attempts to configure the specified sample format with reversed
 * host byte order.
 */
static int
alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
		       struct audio_format *audio_format,
		       enum sample_format sample_format)
{
	snd_pcm_format_t alsa_format =
		byteswap_bitformat(get_bitformat(sample_format));
	if (alsa_format == SND_PCM_FORMAT_UNKNOWN)
		return -EINVAL;

	int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format);
	if (err == 0) {
		audio_format->format = sample_format;
		audio_format->reverse_endian = true;
	}

	return err;
}

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
/**
 * Attempts to configure the specified sample format, and tries the
 * reversed host byte order if was not supported.
 */
static int
alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
			    struct audio_format *audio_format,
			    enum sample_format sample_format)
{
	int err = alsa_output_try_format(pcm, hwparams, audio_format,
					 sample_format);
	if (err == -EINVAL)
		err = alsa_output_try_reverse(pcm, hwparams, audio_format,
					      sample_format);

	return err;
}

292
/**
293
 * Configure a sample format, and probe other formats if that fails.
294
 */
295 296 297
static int
alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams,
			 struct audio_format *audio_format)
298
{
299
	/* try the input format first */
300

301 302
	int err = alsa_output_try_format_both(pcm, hwparams, audio_format,
					      audio_format->format);
303 304
	if (err != -EINVAL)
		return err;
305

306
	/* if unsupported by the hardware, try other formats */
307

308 309 310
	static const enum sample_format probe_formats[] = {
		SAMPLE_FORMAT_S24_P32,
		SAMPLE_FORMAT_S32,
311
		SAMPLE_FORMAT_S24,
312 313 314 315
		SAMPLE_FORMAT_S16,
		SAMPLE_FORMAT_S8,
		SAMPLE_FORMAT_UNDEFINED,
	};
316

317 318 319
	for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) {
		if (probe_formats[i] == audio_format->format)
			continue;
320

321 322
		err = alsa_output_try_format_both(pcm, hwparams, audio_format,
						  probe_formats[i]);
323 324
		if (err != -EINVAL)
			return err;
325
	}
326

327
	return -EINVAL;
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 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 378 379 380
}

/**
 * Set up the snd_pcm_t object which was opened by the caller.  Set up
 * the configured settings and the audio format.
 */
static bool
alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
	   GError **error)
{
	snd_pcm_hw_params_t *hwparams;
	snd_pcm_sw_params_t *swparams;
	unsigned int sample_rate = audio_format->sample_rate;
	unsigned int channels = audio_format->channels;
	snd_pcm_uframes_t alsa_buffer_size;
	snd_pcm_uframes_t alsa_period_size;
	int err;
	const char *cmd = NULL;
	int retry = MPD_ALSA_RETRY_NR;
	unsigned int period_time, period_time_ro;
	unsigned int buffer_time;

	period_time_ro = period_time = ad->period_time;
configure_hw:
	/* configure HW params */
	snd_pcm_hw_params_alloca(&hwparams);
	cmd = "snd_pcm_hw_params_any";
	err = snd_pcm_hw_params_any(ad->pcm, hwparams);
	if (err < 0)
		goto error;

	if (ad->use_mmap) {
		err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
						   SND_PCM_ACCESS_MMAP_INTERLEAVED);
		if (err < 0) {
			g_warning("Cannot set mmap'ed mode on ALSA device \"%s\":  %s\n",
				  alsa_device(ad), snd_strerror(-err));
			g_warning("Falling back to direct write mode\n");
			ad->use_mmap = false;
		} else
			ad->writei = snd_pcm_mmap_writei;
	}

	if (!ad->use_mmap) {
		cmd = "snd_pcm_hw_params_set_access";
		err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
						   SND_PCM_ACCESS_RW_INTERLEAVED);
		if (err < 0)
			goto error;
		ad->writei = snd_pcm_writei;
	}

	err = alsa_output_setup_format(ad->pcm, hwparams, audio_format);
Avuton Olrich's avatar
Avuton Olrich committed
381
	if (err < 0) {
382
		g_set_error(error, alsa_output_quark(), err,
383 384 385
			    "ALSA device \"%s\" does not support format %s: %s",
			    alsa_device(ad),
			    sample_format_to_string(audio_format->format),
386
			    snd_strerror(-err));
387
		return false;
388 389
	}

Max Kellermann's avatar
Max Kellermann committed
390
	err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
Avuton Olrich's avatar
Avuton Olrich committed
391 392
						  &channels);
	if (err < 0) {
393 394 395 396
		g_set_error(error, alsa_output_quark(), err,
			    "ALSA device \"%s\" does not support %i channels: %s",
			    alsa_device(ad), (int)audio_format->channels,
			    snd_strerror(-err));
397
		return false;
398
	}
Max Kellermann's avatar
Max Kellermann committed
399
	audio_format->channels = (int8_t)channels;
400

Max Kellermann's avatar
Max Kellermann committed
401
	err = snd_pcm_hw_params_set_rate_near(ad->pcm, hwparams,
402 403
					      &sample_rate, NULL);
	if (err < 0 || sample_rate == 0) {
404 405 406
		g_set_error(error, alsa_output_quark(), err,
			    "ALSA device \"%s\" does not support %u Hz audio",
			    alsa_device(ad), audio_format->sample_rate);
407
		return false;
408
	}
Max Kellermann's avatar
Max Kellermann committed
409
	audio_format->sample_rate = sample_rate;
410

411 412 413
	if (ad->buffer_time > 0) {
		buffer_time = ad->buffer_time;
		cmd = "snd_pcm_hw_params_set_buffer_time_near";
Max Kellermann's avatar
Max Kellermann committed
414
		err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm, hwparams,
415 416 417
							     &buffer_time, NULL);
		if (err < 0)
			goto error;
418 419 420 421 422
	} else {
		err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
							NULL);
		if (err < 0)
			buffer_time = 0;
423
	}
424

425 426 427 428 429 430 431
	if (period_time_ro == 0 && buffer_time >= 10000) {
		period_time_ro = period_time = buffer_time / 4;

		g_debug("default period_time = buffer_time/4 = %u/4 = %u",
			buffer_time, period_time);
	}

432 433 434
	if (period_time_ro > 0) {
		period_time = period_time_ro;
		cmd = "snd_pcm_hw_params_set_period_time_near";
Max Kellermann's avatar
Max Kellermann committed
435
		err = snd_pcm_hw_params_set_period_time_near(ad->pcm, hwparams,
436 437 438 439
							     &period_time, NULL);
		if (err < 0)
			goto error;
	}
440

441
	cmd = "snd_pcm_hw_params";
Max Kellermann's avatar
Max Kellermann committed
442
	err = snd_pcm_hw_params(ad->pcm, hwparams);
443
	if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
444
		period_time_ro = period_time_ro >> 1;
445 446
		goto configure_hw;
	} else if (err < 0)
Avuton Olrich's avatar
Avuton Olrich committed
447
		goto error;
448
	if (retry != MPD_ALSA_RETRY_NR)
Max Kellermann's avatar
Max Kellermann committed
449
		g_debug("ALSA period_time set to %d\n", period_time);
450

451
	cmd = "snd_pcm_hw_params_get_buffer_size";
452
	err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
Avuton Olrich's avatar
Avuton Olrich committed
453 454
	if (err < 0)
		goto error;
455

456
	cmd = "snd_pcm_hw_params_get_period_size";
457
	err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
Avuton Olrich's avatar
Avuton Olrich committed
458 459 460
						NULL);
	if (err < 0)
		goto error;
461

462
	/* configure SW params */
463 464
	snd_pcm_sw_params_alloca(&swparams);

465
	cmd = "snd_pcm_sw_params_current";
Max Kellermann's avatar
Max Kellermann committed
466
	err = snd_pcm_sw_params_current(ad->pcm, swparams);
Avuton Olrich's avatar
Avuton Olrich committed
467 468
	if (err < 0)
		goto error;
469 470

	cmd = "snd_pcm_sw_params_set_start_threshold";
Max Kellermann's avatar
Max Kellermann committed
471
	err = snd_pcm_sw_params_set_start_threshold(ad->pcm, swparams,
Avuton Olrich's avatar
Avuton Olrich committed
472 473 474 475
						    alsa_buffer_size -
						    alsa_period_size);
	if (err < 0)
		goto error;
476

477
	cmd = "snd_pcm_sw_params_set_avail_min";
Max Kellermann's avatar
Max Kellermann committed
478
	err = snd_pcm_sw_params_set_avail_min(ad->pcm, swparams,
Avuton Olrich's avatar
Avuton Olrich committed
479 480 481
					      alsa_period_size);
	if (err < 0)
		goto error;
482 483

	cmd = "snd_pcm_sw_params";
Max Kellermann's avatar
Max Kellermann committed
484
	err = snd_pcm_sw_params(ad->pcm, swparams);
Avuton Olrich's avatar
Avuton Olrich committed
485 486 487
	if (err < 0)
		goto error;

488 489 490
	g_debug("buffer_size=%u period_size=%u",
		(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);

491 492 493
	ad->period_frames = alsa_period_size;
	ad->period_position = 0;

494
	return true;
495

496
error:
497 498 499
	g_set_error(error, alsa_output_quark(), err,
		    "Error opening ALSA device \"%s\" (%s): %s",
		    alsa_device(ad), cmd, snd_strerror(-err));
500 501 502 503
	return false;
}

static bool
504
alsa_open(void *data, struct audio_format *audio_format, GError **error)
505 506 507 508 509 510 511 512
{
	struct alsa_data *ad = data;
	int err;
	bool success;

	err = snd_pcm_open(&ad->pcm, alsa_device(ad),
			   SND_PCM_STREAM_PLAYBACK, ad->mode);
	if (err < 0) {
513 514 515
		g_set_error(error, alsa_output_quark(), err,
			    "Failed to open ALSA device \"%s\": %s",
			    alsa_device(ad), snd_strerror(err));
516
		return false;
517
	}
518

519
	success = alsa_setup(ad, audio_format, error);
520
	if (!success) {
Max Kellermann's avatar
Max Kellermann committed
521
		snd_pcm_close(ad->pcm);
522 523 524 525 526 527
		return false;
	}

	ad->frame_size = audio_format_frame_size(audio_format);

	return true;
528 529
}

Max Kellermann's avatar
Max Kellermann committed
530 531
static int
alsa_recover(struct alsa_data *ad, int err)
Avuton Olrich's avatar
Avuton Olrich committed
532 533
{
	if (err == -EPIPE) {
Max Kellermann's avatar
Max Kellermann committed
534
		g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad));
Avuton Olrich's avatar
Avuton Olrich committed
535
	} else if (err == -ESTRPIPE) {
Max Kellermann's avatar
Max Kellermann committed
536
		g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad));
537 538
	}

Max Kellermann's avatar
Max Kellermann committed
539
	switch (snd_pcm_state(ad->pcm)) {
Warren Dukes's avatar
Warren Dukes committed
540
	case SND_PCM_STATE_PAUSED:
Max Kellermann's avatar
Max Kellermann committed
541
		err = snd_pcm_pause(ad->pcm, /* disable */ 0);
Warren Dukes's avatar
Warren Dukes committed
542 543
		break;
	case SND_PCM_STATE_SUSPENDED:
Max Kellermann's avatar
Max Kellermann committed
544
		err = snd_pcm_resume(ad->pcm);
545 546 547
		if (err == -EAGAIN)
			return 0;
		/* fall-through to snd_pcm_prepare: */
548 549
	case SND_PCM_STATE_SETUP:
	case SND_PCM_STATE_XRUN:
550
		ad->period_position = 0;
Max Kellermann's avatar
Max Kellermann committed
551
		err = snd_pcm_prepare(ad->pcm);
Warren Dukes's avatar
Warren Dukes committed
552
		break;
553 554
	case SND_PCM_STATE_DISCONNECTED:
		break;
Max Kellermann's avatar
Max Kellermann committed
555 556 557 558
	/* this is no error, so just keep running */
	case SND_PCM_STATE_RUNNING:
		err = 0;
		break;
559 560 561
	default:
		/* unknown state, do nothing */
		break;
562 563 564 565 566
	}

	return err;
}

567 568 569 570 571
static void
alsa_drain(void *data)
{
	struct alsa_data *ad = data;

572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
	if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
		return;

	if (ad->period_position > 0) {
		/* generate some silence to finish the partial
		   period */
		snd_pcm_uframes_t nframes =
			ad->period_frames - ad->period_position;
		size_t nbytes = nframes * ad->frame_size;
		void *buffer = g_malloc(nbytes);
		snd_pcm_hw_params_t *params;
		snd_pcm_format_t format;
		unsigned channels;

		snd_pcm_hw_params_alloca(&params);
		snd_pcm_hw_params_current(ad->pcm, params);
		snd_pcm_hw_params_get_format(params, &format);
		snd_pcm_hw_params_get_channels(params, &channels);

		snd_pcm_format_set_silence(format, buffer, nframes * channels);
		ad->writei(ad->pcm, buffer, nframes);
		g_free(buffer);
	}

	snd_pcm_drain(ad->pcm);

	ad->period_position = 0;
599 600
}

Max Kellermann's avatar
Max Kellermann committed
601 602
static void
alsa_cancel(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
603
{
Max Kellermann's avatar
Max Kellermann committed
604
	struct alsa_data *ad = data;
605

606 607
	ad->period_position = 0;

608
	snd_pcm_drop(ad->pcm);
609 610
}

Max Kellermann's avatar
Max Kellermann committed
611 612
static void
alsa_close(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
613
{
Max Kellermann's avatar
Max Kellermann committed
614
	struct alsa_data *ad = data;
615

616
	snd_pcm_close(ad->pcm);
617 618
}

619
static size_t
620
alsa_play(void *data, const void *chunk, size_t size, GError **error)
621
{
Max Kellermann's avatar
Max Kellermann committed
622
	struct alsa_data *ad = data;
623

Max Kellermann's avatar
Max Kellermann committed
624
	size /= ad->frame_size;
625

626
	while (true) {
627
		snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
628 629 630
		if (ret > 0) {
			ad->period_position = (ad->period_position + ret)
				% ad->period_frames;
631
			return ret * ad->frame_size;
632
		}
633 634 635

		if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
		    alsa_recover(ad, ret) < 0) {
636 637
			g_set_error(error, alsa_output_quark(), errno,
				    "%s", snd_strerror(-errno));
638
			return 0;
639 640 641 642
		}
	}
}

643
const struct audio_output_plugin alsaPlugin = {
644
	.name = "alsa",
Max Kellermann's avatar
Max Kellermann committed
645 646 647 648 649
	.test_default_device = alsa_test_default_device,
	.init = alsa_init,
	.finish = alsa_finish,
	.open = alsa_open,
	.play = alsa_play,
650
	.drain = alsa_drain,
Max Kellermann's avatar
Max Kellermann committed
651 652
	.cancel = alsa_cancel,
	.close = alsa_close,
653 654

	.mixer_plugin = &alsa_mixer_plugin,
655
};