qsa.c 22.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * qsa.c - Audio capture/playback filters using QSA (QNX Sound Architecture).
 *
 * Copyright (C) 2009-2014  Belledonne Communications, Grenoble, France
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#if defined(HAVE_CONFIG_H)
#include "mediastreamer-config.h"
#endif

Ghislain MARY's avatar
Ghislain MARY committed
25
#include <errno.h>
26
#include <sys/asoundlib.h>
Ghislain MARY's avatar
Ghislain MARY committed
27 28
#include <sys/select.h>
#include <sys/time.h>
29 30 31 32 33 34

#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/msticker.h"
#include "mediastreamer2/mssndcard.h"


Ghislain MARY's avatar
Ghislain MARY committed
35 36 37 38
extern MSFilterDesc ms_qsa_read_desc;
extern MSFilterDesc ms_qsa_write_desc;


Ghislain MARY's avatar
Ghislain MARY committed
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
/******************************************************************************
 * Private definitions                                                        *
 *****************************************************************************/

#define DEF_RATE_MAP(rate)	{ rate, SND_PCM_RATE_ ## rate }

struct _rate_map {
	int hz;
	uint32_t pcm_rate;
};

static struct _rate_map rate_map[] = {
	DEF_RATE_MAP(8000),
	DEF_RATE_MAP(11025),
	DEF_RATE_MAP(16000),
	DEF_RATE_MAP(22050),
	DEF_RATE_MAP(32000),
	DEF_RATE_MAP(44100),
	DEF_RATE_MAP(48000),
	{ 0, 0 }
};

Ghislain MARY's avatar
Ghislain MARY committed
61 62 63 64 65 66 67 68 69 70 71
uint32_t get_pcm_rate_from_hz(int hz) {
	struct _rate_map *rm = &rate_map[0];

	while (rm->hz != 0) {
		if (rm->hz == hz) break;
		rm++;
	}

	return rm->pcm_rate;
}

Ghislain MARY's avatar
Ghislain MARY committed
72

73 74 75 76
/**
 * Definition of the private data structure of the QSA playback filter.
 */
typedef struct _MSQSAReadData {
Ghislain MARY's avatar
Ghislain MARY committed
77 78
	snd_pcm_channel_info_t info;
	snd_pcm_t *handle;
Ghislain MARY's avatar
Ghislain MARY committed
79
	snd_mixer_t *mixer_handle;
Ghislain MARY's avatar
Ghislain MARY committed
80
	char *pcmdev;
Ghislain MARY's avatar
Ghislain MARY committed
81
	int card;
Ghislain MARY's avatar
Ghislain MARY committed
82 83 84 85
	int fd;
	int rate;
	int nchannels;
	bool_t initialized;
86 87
} MSQSAReadData;

Ghislain MARY's avatar
Ghislain MARY committed
88 89
static int ms_qsa_read_set_sample_rate(MSFilter *f, void *arg);

90 91 92 93
/******************************************************************************
 * Methods to (de)initialize and run the QSA capture filter                   *
 *****************************************************************************/

Ghislain MARY's avatar
Ghislain MARY committed
94 95 96 97 98 99 100 101
static MSFilter * ms_qsa_read_new(MSSndCard *card) {
	MSFilter *f = ms_filter_new_from_desc(&ms_qsa_read_desc);
	MSQSAReadData *d = (MSQSAReadData *)f->data;
	snd_pcm_t *handle;
	int err;

	d->pcmdev = ms_strdup(card->name);
	err = snd_pcm_open_name(&handle, d->pcmdev, SND_PCM_OPEN_CAPTURE | SND_PCM_OPEN_NONBLOCK);
Ghislain MARY's avatar
Ghislain MARY committed
102 103 104
	if (err < 0) {
		ms_error("%s: snd_pcm_open_name(%s) failed: %s", __FUNCTION__, d->pcmdev, snd_strerror(err));
	} else {
Ghislain MARY's avatar
Ghislain MARY committed
105 106
		memset(&d->info, 0, sizeof(d->info));
		d->info.channel = SND_PCM_CHANNEL_CAPTURE;
Ghislain MARY's avatar
Ghislain MARY committed
107 108 109 110
		err = snd_pcm_plugin_info(handle, &d->info);
		if (err != 0) {
			ms_error("%s: snd_pcm_plugin_info() failed: %s", __FUNCTION__, snd_strerror(err));
		} else {
Ghislain MARY's avatar
Ghislain MARY committed
111 112 113
			d->nchannels = d->info.max_voices;
			d->rate = d->info.max_rate;
			d->initialized = TRUE;
Ghislain MARY's avatar
Ghislain MARY committed
114
			ms_qsa_read_set_sample_rate(f, &card->preferred_sample_rate);
Ghislain MARY's avatar
Ghislain MARY committed
115 116 117 118 119
		}
		snd_pcm_close(handle);
	}

	return f;
120 121
}

Ghislain MARY's avatar
Ghislain MARY committed
122 123 124
static void ms_qsa_read_init(MSFilter *f) {
	MSQSAReadData *d = (MSQSAReadData *)ms_new0(MSQSAReadData, 1);
	f->data = d;
125 126 127
}

static void ms_qsa_read_process(MSFilter *f) {
Ghislain MARY's avatar
Ghislain MARY committed
128 129
	snd_pcm_info_t info;
	snd_pcm_channel_info_t pi;
Ghislain MARY's avatar
Ghislain MARY committed
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
	snd_pcm_channel_params_t params;
	snd_pcm_channel_status_t status;
	snd_pcm_channel_setup_t setup;
	snd_mixer_group_t group;
	mblk_t *om = NULL;
	int readbytes;
	int size;
	int err;
	fd_set fdset;
	struct timeval timeout;
	MSQSAReadData *d = (MSQSAReadData *)f->data;

	if (d->initialized != TRUE) goto setup_failure;

	if ((d->handle == NULL) && (d->pcmdev != NULL)) {
		err = snd_pcm_open_name(&d->handle, d->pcmdev, SND_PCM_OPEN_CAPTURE);
		if (err != 0) {
			ms_error("%s: snd_pcm_open_name(%s) failed: %s", __FUNCTION__, d->pcmdev, snd_strerror(err));
			goto setup_failure;
		}
Ghislain MARY's avatar
Ghislain MARY committed
150 151 152 153 154 155
		err = snd_pcm_info(d->handle, &info);
		if (err < 0) {
			ms_error("%s: snd_pcm_info() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		d->card = info.card;
Ghislain MARY's avatar
Ghislain MARY committed
156 157 158 159 160 161
		err = snd_pcm_file_descriptor(d->handle, SND_PCM_CHANNEL_CAPTURE);
		if (err < 0) {
			ms_error("%s: snd_pcm_file_descriptor() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		d->fd = err;
Ghislain MARY's avatar
Ghislain MARY committed
162
		err = snd_pcm_plugin_set_disable(d->handle, PLUGIN_DISABLE_MMAP);
Ghislain MARY's avatar
Ghislain MARY committed
163 164 165 166 167 168 169
		if (err < 0) {
			ms_error("%s: snd_pcm_plugin_set_disable() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		memset(&pi, 0, sizeof(pi));
		pi.channel = SND_PCM_CHANNEL_CAPTURE;
		err = snd_pcm_plugin_info(d->handle, &pi);
Ghislain MARY's avatar
Ghislain MARY committed
170 171 172 173 174 175 176 177 178
		if (err != 0) {
			ms_error("%s: snd_pcm_plugin_info() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		memset(&params, 0, sizeof(params));
		params.channel = SND_PCM_CHANNEL_CAPTURE;
		params.mode = SND_PCM_MODE_BLOCK;
		params.start_mode = SND_PCM_START_DATA;
		params.stop_mode = SND_PCM_STOP_STOP;
Ghislain MARY's avatar
Ghislain MARY committed
179
		params.buf.block.frag_size = pi.max_fragment_size;
Ghislain MARY's avatar
Ghislain MARY committed
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
		params.buf.block.frags_min = 1;
		params.buf.block.frags_max = -1;
		params.format.interleave = 1;
		params.format.rate = d->rate;
		params.format.voices = d->nchannels;
		params.format.format = SND_PCM_SFMT_S16_LE;
		err = snd_pcm_plugin_params(d->handle, &params);
		if (err != 0) {
			ms_error("%s: snd_pcm_plugin_params() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		err = snd_pcm_plugin_prepare(d->handle, SND_PCM_CHANNEL_CAPTURE);
		if (err != 0) {
			ms_error("%s: snd_pcm_plugin_prepare() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		memset(&setup, 0, sizeof (setup));
		memset(&group, 0, sizeof (group));
		setup.channel = SND_PCM_CHANNEL_CAPTURE;
		setup.mixer_gid = &group.gid;
		err = snd_pcm_plugin_setup(d->handle, &setup);
		if (err < 0) {
			ms_error("%s: snd_pcm_plugin_setup() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
Ghislain MARY's avatar
Ghislain MARY committed
205 206 207 208 209 210 211 212 213 214 215
		if (group.gid.name[0] == 0) {
			ms_error("%s: Mixer Pcm Group Not Set", __FUNCTION__);
			ms_error("%s: Input gain controls disabled", __FUNCTION__);
		} else {
			err = snd_mixer_open(&d->mixer_handle, d->card, setup.mixer_device);
			if (err < 0) {
				ms_error("%s: snd_mixer_open() failed: %s", __FUNCTION__, snd_strerror(err));
				goto setup_failure;
			}
		}

Ghislain MARY's avatar
Ghislain MARY committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 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
		ms_message("Format %s", snd_pcm_get_format_name(setup.format.format));
		ms_message("Frag Size %d", setup.buf.block.frag_size);
		ms_message("Rate %d", setup.format.rate);
	}

	if (d->handle == NULL) goto setup_failure;

	size = (d->rate / 100) * 2 * d->nchannels;
	FD_ZERO(&fdset);
	FD_SET(d->fd, &fdset);
	memset(&timeout, 0, sizeof(timeout));
	err = select(d->fd + 1, &fdset, NULL, NULL, &timeout);
	if (err < 0) {
		ms_error("%s: select() failed: %d", __FUNCTION__, errno);
		goto setup_failure;
	}
	if (FD_ISSET(d->fd, &fdset) > 0) {
		om = allocb(size, 0);
		readbytes = snd_pcm_plugin_read(d->handle, om->b_wptr, size);
		if (readbytes < size) {
			memset(&status, 0, sizeof(status));
			status.channel = SND_PCM_CHANNEL_CAPTURE;
			err = snd_pcm_plugin_status(d->handle, &status);
			if (err != 0) {
				ms_error("%s: snd_pcm_plugin_status() failed: %s", __FUNCTION__, snd_strerror(err));
				goto setup_failure;
			}
			if ((status.status == SND_PCM_STATUS_READY) || (status.status == SND_PCM_STATUS_UNDERRUN)) {
				err = snd_pcm_plugin_prepare(d->handle, SND_PCM_CHANNEL_CAPTURE);
				if (err != 0) {
					ms_error("%s: snd_pcm_plugin_prepare() failed: %s", __FUNCTION__, snd_strerror(err));
					goto setup_failure;
				}
			}
			if (readbytes < 0) readbytes = 0;
		}
		om->b_wptr += readbytes;
		ms_queue_put(f->outputs[0], om);
	}
	return;

setup_failure:
	if (d->handle != NULL) {
		snd_pcm_close(d->handle);
		d->handle = NULL;
	}
262 263 264
}

static void ms_qsa_read_postprocess(MSFilter *f) {
Ghislain MARY's avatar
Ghislain MARY committed
265 266 267 268 269 270 271
	MSQSAReadData *d = (MSQSAReadData *)f->data;

	if (d->handle != NULL) {
		snd_pcm_plugin_flush(d->handle, SND_PCM_CHANNEL_CAPTURE);
		snd_pcm_close(d->handle);
		d->handle = NULL;
	}
272 273 274 275
}

static void ms_qsa_read_uninit(MSFilter *f) {
	MSQSAReadData *d = (MSQSAReadData *)f->data;
Ghislain MARY's avatar
Ghislain MARY committed
276 277 278 279
	if (d->pcmdev != NULL) {
		ms_free(d->pcmdev);
		d->pcmdev = NULL;
	}
280 281 282 283 284 285 286 287 288
	ms_free(d);
}


/******************************************************************************
 * Methods to configure the QSA capture filter                               *
 *****************************************************************************/

static int ms_qsa_read_set_sample_rate(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
289
	MSQSAReadData *d = (MSQSAReadData *)f->data;
Ghislain MARY's avatar
Ghislain MARY committed
290 291 292 293
	int hz = *((int *)arg);
	uint32_t pcm_rate = get_pcm_rate_from_hz(hz);
	if ((pcm_rate != 0) && (d->info.rates & pcm_rate)) {
		d->rate = hz;
Ghislain MARY's avatar
Ghislain MARY committed
294 295 296
		return 0;
	}
	return -1;
297 298 299
}

static int ms_qsa_read_get_sample_rate(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
300 301
	MSQSAReadData * d = (MSQSAReadData *)f->data;
	*((int*)arg) = d->rate;
302 303 304 305
	return 0;
}

static int ms_qsa_read_set_nchannels(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
306 307 308 309 310 311 312
	MSQSAReadData *d = (MSQSAReadData *)f->data;
	int nchannels = *((int *)arg);
	if ((nchannels >= d->info.min_voices) && (nchannels <= d->info.max_voices)) {
		d->nchannels = nchannels;
		return 0;
	}
	return -1;
313 314 315
}

static int ms_qsa_read_get_nchannels(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
316 317
	MSQSAReadData *d = (MSQSAReadData *)f->data;
	*((int *)arg) = d->nchannels;
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
	return 0;
}

static MSFilterMethod ms_qsa_read_methods[] = {
	{ MS_FILTER_SET_SAMPLE_RATE, ms_qsa_read_set_sample_rate },
	{ MS_FILTER_GET_SAMPLE_RATE, ms_qsa_read_get_sample_rate },
	{ MS_FILTER_GET_NCHANNELS,   ms_qsa_read_get_nchannels   },
	{ MS_FILTER_SET_NCHANNELS,   ms_qsa_read_set_nchannels   },
	{ 0,                         NULL                         }
};


/******************************************************************************
 * Definition of the QSA capture filter                                      *
 *****************************************************************************/

MSFilterDesc ms_qsa_read_desc = {
	.id = MS_QSA_READ_ID,
Ghislain MARY's avatar
Ghislain MARY committed
336 337 338 339 340 341
	.name = "MSQSARead",
	.text = "QSA sound capture.",
	.category = MS_FILTER_OTHER,
	.enc_fmt = NULL,
	.ninputs = 0,
	.noutputs = 1,
342 343 344 345 346
	.init = ms_qsa_read_init,
	.process = ms_qsa_read_process,
	.postprocess = ms_qsa_read_postprocess,
	.uninit = ms_qsa_read_uninit,
	.methods = ms_qsa_read_methods,
Ghislain MARY's avatar
Ghislain MARY committed
347
	.flags = 0
348 349 350 351 352 353 354 355 356 357
};

MS_FILTER_DESC_EXPORT(ms_qsa_read_desc)



/**
 * Definition of the private data structure of the QSA playback filter.
 */
typedef struct _MSQSAWriteData {
Ghislain MARY's avatar
Ghislain MARY committed
358 359
	snd_pcm_channel_info_t info;
	snd_pcm_t *handle;
360
	snd_mixer_t *mixer_handle;
361
	MSBufferizer *bufferizer;
Ghislain MARY's avatar
Ghislain MARY committed
362
	uint8_t *buffer;
Ghislain MARY's avatar
Ghislain MARY committed
363
	char *pcmdev;
364
	int buffer_size;
365
	int card;
366
	int fd;
Ghislain MARY's avatar
Ghislain MARY committed
367 368 369
	int rate;
	int nchannels;
	bool_t initialized;
370
	int frags_written;
371 372
} MSQSAWriteData;

Ghislain MARY's avatar
Ghislain MARY committed
373 374
static int ms_qsa_write_set_sample_rate(MSFilter *f, void *arg);

375 376 377 378
/******************************************************************************
 * Methods to (de)initialize and run the QSA playback filter                  *
 *****************************************************************************/

Ghislain MARY's avatar
Ghislain MARY committed
379 380 381 382 383 384 385 386
static MSFilter * ms_qsa_write_new(MSSndCard *card) {
	MSFilter *f = ms_filter_new_from_desc(&ms_qsa_write_desc);
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
	snd_pcm_t *handle;
	int err;

	d->pcmdev = ms_strdup(card->name);
	err = snd_pcm_open_name(&handle, d->pcmdev, SND_PCM_OPEN_PLAYBACK | SND_PCM_OPEN_NONBLOCK);
Ghislain MARY's avatar
Ghislain MARY committed
387 388 389
	if (err != 0) {
		ms_error("%s: snd_pcm_open_preferred() failed: %s", __FUNCTION__, snd_strerror(err));
	} else {
Ghislain MARY's avatar
Ghislain MARY committed
390 391
		memset(&d->info, 0, sizeof(d->info));
		d->info.channel = SND_PCM_CHANNEL_PLAYBACK;
Ghislain MARY's avatar
Ghislain MARY committed
392 393 394 395
		err = snd_pcm_plugin_info(handle, &d->info);
		if (err != 0) {
			ms_error("%s: snd_pcm_plugin_info() failed: %s", __FUNCTION__, snd_strerror(err));
		} else {
Ghislain MARY's avatar
Ghislain MARY committed
396 397 398
			d->nchannels = d->info.max_voices;
			d->rate = d->info.max_rate;
			d->initialized = TRUE;
Ghislain MARY's avatar
Ghislain MARY committed
399
			ms_qsa_write_set_sample_rate(f, &card->preferred_sample_rate);
Ghislain MARY's avatar
Ghislain MARY committed
400 401 402 403 404
		}
		snd_pcm_close(handle);
	}

	return f;
405 406
}

Ghislain MARY's avatar
Ghislain MARY committed
407 408
static void ms_qsa_write_init(MSFilter *f) {
	MSQSAWriteData *d = (MSQSAWriteData *)ms_new0(MSQSAWriteData, 1);
409
	d->bufferizer = ms_bufferizer_new();
Ghislain MARY's avatar
Ghislain MARY committed
410
	f->data = d;
411 412 413
}

static void ms_qsa_write_process(MSFilter *f) {
414 415
	snd_pcm_info_t info;
	snd_pcm_channel_info_t pi;
Ghislain MARY's avatar
Ghislain MARY committed
416 417
	snd_pcm_channel_params_t params;
	snd_pcm_channel_status_t status;
Ghislain MARY's avatar
Ghislain MARY committed
418 419
	snd_pcm_channel_setup_t setup;
	snd_mixer_group_t group;
Ghislain MARY's avatar
Ghislain MARY committed
420 421
	int written;
	int err;
422 423
	fd_set fdset;
	struct timeval timeout;
Ghislain MARY's avatar
Ghislain MARY committed
424 425 426 427 428 429 430
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;

	if (d->initialized != TRUE) goto setup_failure;

	if ((d->handle == NULL) && (d->pcmdev != NULL)) {
		err = snd_pcm_open_name(&d->handle, d->pcmdev, SND_PCM_OPEN_PLAYBACK);
		if (err != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
431
			ms_error("%s: snd_pcm_open_name(%s) failed: %s", __FUNCTION__, d->pcmdev, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
432 433
			goto setup_failure;
		}
434 435
		err = snd_pcm_info(d->handle, &info);
		if (err < 0) {
Ghislain MARY's avatar
Ghislain MARY committed
436
			ms_error("%s: snd_pcm_info() failed: %s", __FUNCTION__, snd_strerror(err));
437 438 439
			goto setup_failure;
		}
		d->card = info.card;
440 441 442 443 444 445
		err = snd_pcm_file_descriptor(d->handle, SND_PCM_CHANNEL_PLAYBACK);
		if (err < 0) {
			ms_error("%s: snd_pcm_file_descriptor() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		d->fd = err;
Ghislain MARY's avatar
Ghislain MARY committed
446
		err = snd_pcm_plugin_set_disable(d->handle, PLUGIN_DISABLE_MMAP);
Ghislain MARY's avatar
Ghislain MARY committed
447 448 449 450
		if (err < 0) {
			ms_error("%s: snd_pcm_plugin_set_disable() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
451
		/*err = snd_pcm_plugin_set_disable(d->handle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS);
452 453 454
		if (err < 0) {
			ms_error("%s: snd_pcm_plugin_set_disable() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
455
		}*/
456
		
457 458 459
		memset(&pi, 0, sizeof(pi));
		pi.channel = SND_PCM_CHANNEL_PLAYBACK;
		err = snd_pcm_plugin_info(d->handle, &pi);
Ghislain MARY's avatar
Ghislain MARY committed
460
		if (err != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
461
			ms_error("%s: snd_pcm_plugin_info() failed: %s", __FUNCTION__, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
462 463 464 465 466
			goto setup_failure;
		}
		memset(&params, 0, sizeof(params));
		params.channel = SND_PCM_CHANNEL_PLAYBACK;
		params.mode = SND_PCM_MODE_BLOCK;
467
		params.start_mode = SND_PCM_START_GO;
Ghislain MARY's avatar
Ghislain MARY committed
468
		params.stop_mode = SND_PCM_STOP_STOP;
469
		params.buf.block.frag_size = pi.max_fragment_size;
470
		params.buf.block.frags_min = 1;
Ghislain MARY's avatar
Ghislain MARY committed
471 472 473 474 475
		params.buf.block.frags_max = -1;
		params.format.interleave = 1;
		params.format.rate = d->rate;
		params.format.voices = d->nchannels;
		params.format.format = SND_PCM_SFMT_S16_LE;
476
		strcpy(params.sw_mixer_subchn_name, "Wave playback channel");
Ghislain MARY's avatar
Ghislain MARY committed
477 478
		err = snd_pcm_plugin_params(d->handle, &params);
		if (err != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
479
			ms_error("%s: snd_pcm_plugin_params() failed: %s", __FUNCTION__, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
480 481 482 483
			goto setup_failure;
		}
		err = snd_pcm_plugin_prepare(d->handle, SND_PCM_CHANNEL_PLAYBACK);
		if (err != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
484 485 486 487 488 489 490 491 492 493
			ms_error("%s: snd_pcm_plugin_prepare() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
		memset(&setup, 0, sizeof (setup));
		memset(&group, 0, sizeof (group));
		setup.channel = SND_PCM_CHANNEL_PLAYBACK;
		setup.mixer_gid = &group.gid;
		err = snd_pcm_plugin_setup(d->handle, &setup);
		if (err < 0) {
			ms_error("%s: snd_pcm_plugin_setup() failed: %s", __FUNCTION__, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
494 495
			goto setup_failure;
		}
496
		if (group.gid.name[0] == 0) {
Ghislain MARY's avatar
Ghislain MARY committed
497
			ms_error("%s: Mixer Pcm Group Not Set", __FUNCTION__);
498 499 500 501 502 503 504
			goto setup_failure;
		}
		err = snd_mixer_open(&d->mixer_handle, d->card, setup.mixer_device);
		if (err < 0) {
			ms_error("%s: snd_mixer_open() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
Ghislain MARY's avatar
Ghislain MARY committed
505 506 507 508 509 510 511

		ms_message("Format %s", snd_pcm_get_format_name(setup.format.format));
		ms_message("Frag Size %d", setup.buf.block.frag_size);
		ms_message("Total Frags %d", setup.buf.block.frags);
		ms_message("Rate %d", setup.format.rate);
		ms_message("Voices %d", setup.format.voices);
		ms_message("%s: Mixer Pcm Group [%s]", __FUNCTION__, group.gid.name);
512

513
		d->buffer_size = setup.buf.block.frag_size;
514
		d->buffer = ms_malloc(d->buffer_size);
515
		d->frags_written = 0;
Ghislain MARY's avatar
Ghislain MARY committed
516 517 518 519
	}

	if (d->handle == NULL) goto setup_failure;

520 521 522 523 524 525 526 527 528 529 530
	ms_bufferizer_put_from_queue(d->bufferizer, f->inputs[0]);

	FD_ZERO(&fdset);
	FD_SET(d->fd, &fdset);
	memset(&timeout, 0, sizeof(timeout));
	err = select(d->fd + 1, NULL, &fdset, NULL, &timeout);
	if (err < 0) {
		ms_error("%s: select() failed: %d", __FUNCTION__, errno);
		goto setup_failure;
	}
	if (FD_ISSET(d->fd, &fdset) > 0) {
531 532 533 534
		if (ms_bufferizer_get_avail(d->bufferizer) >= d->buffer_size) {
			ms_bufferizer_read(d->bufferizer, d->buffer, d->buffer_size);
			written = snd_pcm_plugin_write(d->handle, d->buffer, d->buffer_size);
			if (written < d->buffer_size) {
Sylvain Berfini's avatar
Sylvain Berfini committed
535
				ms_warning("%s: snd_pcm_plugin_write(%d) failed: %s", __FUNCTION__, d->buffer_size, strerror(errno));
536 537 538
				memset(&status, 0, sizeof(status));
				status.channel = SND_PCM_CHANNEL_PLAYBACK;
				err = snd_pcm_plugin_status(d->handle, &status);
Ghislain MARY's avatar
Ghislain MARY committed
539
				if (err != 0) {
540
					ms_error("%s: snd_pcm_plugin_status() failed: %s", __FUNCTION__, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
541 542
					goto setup_failure;
				}
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
				if ((status.status == SND_PCM_STATUS_READY) || (status.status == SND_PCM_STATUS_UNDERRUN)) {
					d->frags_written = 0;
					err = snd_pcm_plugin_prepare(d->handle, SND_PCM_CHANNEL_PLAYBACK);
					if (err != 0) {
						ms_error("%s: snd_pcm_plugin_prepare() failed: %s", __FUNCTION__, snd_strerror(err));
						goto setup_failure;
					}
				}
				if (written < 0) written = 0;
			} else {
				d->frags_written += 1;
				
				memset(&status, 0, sizeof(status));
				status.channel = SND_PCM_CHANNEL_PLAYBACK;
				err = snd_pcm_plugin_status(d->handle, &status);
				if (err != 0) {
					ms_error("%s: snd_pcm_plugin_status() failed: %s", __FUNCTION__, snd_strerror(err));
					goto setup_failure;
				}
				
				if (status.status == SND_PCM_STATUS_READY && d->frags_written >= 3) {
					err = snd_pcm_playback_go(d->handle);
					if (err != 0) {
						ms_error("%s: snd_pcm_playback_go() failed: %s", __FUNCTION__, snd_strerror(err));
						goto setup_failure;
					}
				}
Ghislain MARY's avatar
Ghislain MARY committed
570 571
			}
		}
572 573 574 575 576
	}

	if (ms_bufferizer_get_avail(d->bufferizer) >= (d->rate / 100) * 2 * d->nchannels * 5) {
		ms_warning("%s: Removing extra data for sound card (%i bytes)", __FUNCTION__, ms_bufferizer_get_avail(d->bufferizer));
		ms_bufferizer_flush(d->bufferizer);
Ghislain MARY's avatar
Ghislain MARY committed
577 578 579 580
	}
	return;

setup_failure:
581 582 583 584
	if (d->mixer_handle != NULL) {
		snd_mixer_close(d->mixer_handle);
		d->mixer_handle = NULL;
	}
Ghislain MARY's avatar
Ghislain MARY committed
585 586 587 588 589
	if (d->handle != NULL) {
		snd_pcm_close(d->handle);
		d->handle = NULL;
	}
	ms_queue_flush(f->inputs[0]);
590 591 592
}

static void ms_qsa_write_postprocess(MSFilter *f) {
Ghislain MARY's avatar
Ghislain MARY committed
593 594
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;

595 596 597 598
	if (d->mixer_handle != NULL) {
		snd_mixer_close(d->mixer_handle);
		d->mixer_handle = NULL;
	}
Ghislain MARY's avatar
Ghislain MARY committed
599 600 601 602 603
	if (d->handle != NULL) {
		snd_pcm_plugin_flush(d->handle, SND_PCM_CHANNEL_PLAYBACK);
		snd_pcm_close(d->handle);
		d->handle = NULL;
	}
604 605 606 607
}

static void ms_qsa_write_uninit(MSFilter *f) {
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
Ghislain MARY's avatar
Ghislain MARY committed
608 609 610 611
	if (d->pcmdev != NULL) {
		ms_free(d->pcmdev);
		d->pcmdev = NULL;
	}
612 613 614 615 616
	if (d->buffer != NULL) {
		ms_free(d->buffer);
		d->buffer = NULL;
	}
	ms_bufferizer_destroy(d->bufferizer);
617 618 619 620 621 622 623 624 625
	ms_free(d);
}


/******************************************************************************
 * Methods to configure the QSA playback filter                               *
 *****************************************************************************/

static int ms_qsa_write_set_sample_rate(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
626
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
Ghislain MARY's avatar
Ghislain MARY committed
627 628 629 630
	int hz = *((int *)arg);
	uint32_t pcm_rate = get_pcm_rate_from_hz(hz);
	if ((pcm_rate != 0) && (d->info.rates & pcm_rate)) {
		d->rate = hz;
Ghislain MARY's avatar
Ghislain MARY committed
631 632 633
		return 0;
	}
	return -1;
634 635 636
}

static int ms_qsa_write_get_sample_rate(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
637 638
	MSQSAWriteData * d = (MSQSAWriteData *)f->data;
	*((int*)arg) = d->rate;
639 640 641 642
	return 0;
}

static int ms_qsa_write_set_nchannels(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
643 644 645 646 647 648 649
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
	int nchannels = *((int *)arg);
	if ((nchannels >= d->info.min_voices) && (nchannels <= d->info.max_voices)) {
		d->nchannels = nchannels;
		return 0;
	}
	return -1;
650 651 652
}

static int ms_qsa_write_get_nchannels(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
653 654
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
	*((int *)arg) = d->nchannels;
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
	return 0;
}

static MSFilterMethod ms_qsa_write_methods[] = {
	{ MS_FILTER_SET_SAMPLE_RATE, ms_qsa_write_set_sample_rate },
	{ MS_FILTER_GET_SAMPLE_RATE, ms_qsa_write_get_sample_rate },
	{ MS_FILTER_GET_NCHANNELS,   ms_qsa_write_get_nchannels   },
	{ MS_FILTER_SET_NCHANNELS,   ms_qsa_write_set_nchannels   },
	{ 0,                         NULL                         }
};


/******************************************************************************
 * Definition of the QSA playback filter                                      *
 *****************************************************************************/

MSFilterDesc ms_qsa_write_desc = {
	.id = MS_QSA_WRITE_ID,
Ghislain MARY's avatar
Ghislain MARY committed
673 674 675 676 677 678
	.name = "MSQSAWrite",
	.text = "QSA sound playback.",
	.category = MS_FILTER_OTHER,
	.enc_fmt = NULL,
	.ninputs = 1,
	.noutputs = 0,
679 680 681 682 683
	.init = ms_qsa_write_init,
	.process = ms_qsa_write_process,
	.postprocess = ms_qsa_write_postprocess,
	.uninit = ms_qsa_write_uninit,
	.methods = ms_qsa_write_methods,
Ghislain MARY's avatar
Ghislain MARY committed
684
	.flags = 0
685 686 687 688 689 690 691 692 693 694 695 696 697 698
};

MS_FILTER_DESC_EXPORT(ms_qsa_write_desc)



/******************************************************************************
 * Definition of the QSA sound card                                           *
 *****************************************************************************/

static void ms_qsa_card_detect(MSSndCardManager *m);
static MSFilter * ms_qsa_card_create_reader(MSSndCard *card);
static MSFilter * ms_qsa_card_create_writer(MSSndCard *card);

Ghislain MARY's avatar
Ghislain MARY committed
699 700 701 702 703
typedef struct _MSQSAData {
	char *pcmdev;
	char *mixdev;
} MSQSAData;

704 705 706 707
MSSndCardDesc ms_qsa_card_desc = {
	.driver_type = "QSA",
	.detect = ms_qsa_card_detect,
	.create_reader = ms_qsa_card_create_reader,
Ghislain MARY's avatar
Ghislain MARY committed
708
	.create_writer = ms_qsa_card_create_writer
709 710 711
};

static void ms_qsa_card_detect(MSSndCardManager *m) {
Ghislain MARY's avatar
Ghislain MARY committed
712 713
	MSSndCard *card;

Ghislain MARY's avatar
Ghislain MARY committed
714 715 716 717 718 719
	card = ms_snd_card_new(&ms_qsa_card_desc);
	if (card != NULL) {
		card->name = ms_strdup("pcmPreferred");
		card->capabilities = MS_SND_CARD_CAP_PLAYBACK;
		card->preferred_sample_rate = 48000;
		ms_snd_card_manager_add_card(m, card);
720
	}
Ghislain MARY's avatar
Ghislain MARY committed
721 722 723 724 725 726
	card = ms_snd_card_new(&ms_qsa_card_desc);
	if (card != NULL) {
		card->name = ms_strdup("voice");
		card->capabilities = MS_SND_CARD_CAP_CAPTURE | MS_SND_CARD_CAP_PLAYBACK | MS_SND_CARD_CAP_BUILTIN_ECHO_CANCELLER;
		card->preferred_sample_rate = 48000;
		ms_snd_card_manager_add_card(m, card);
Ghislain MARY's avatar
Ghislain MARY committed
727
	}
728 729 730
}

static MSFilter * ms_qsa_card_create_reader(MSSndCard *card) {
Ghislain MARY's avatar
Ghislain MARY committed
731
	return ms_qsa_read_new(card);
732 733 734
}

static MSFilter * ms_qsa_card_create_writer(MSSndCard *card) {
Ghislain MARY's avatar
Ghislain MARY committed
735
	return ms_qsa_write_new(card);
736
}