qsa.c 21.5 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 371
} MSQSAWriteData;

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

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

Ghislain MARY's avatar
Ghislain MARY committed
378 379 380 381 382 383 384 385
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
386 387 388
	if (err != 0) {
		ms_error("%s: snd_pcm_open_preferred() failed: %s", __FUNCTION__, snd_strerror(err));
	} else {
Ghislain MARY's avatar
Ghislain MARY committed
389 390
		memset(&d->info, 0, sizeof(d->info));
		d->info.channel = SND_PCM_CHANNEL_PLAYBACK;
Ghislain MARY's avatar
Ghislain MARY committed
391 392 393 394
		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
395 396 397
			d->nchannels = d->info.max_voices;
			d->rate = d->info.max_rate;
			d->initialized = TRUE;
Ghislain MARY's avatar
Ghislain MARY committed
398
			ms_qsa_write_set_sample_rate(f, &card->preferred_sample_rate);
Ghislain MARY's avatar
Ghislain MARY committed
399 400 401 402 403
		}
		snd_pcm_close(handle);
	}

	return f;
404 405
}

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

static void ms_qsa_write_process(MSFilter *f) {
413 414
	snd_pcm_info_t info;
	snd_pcm_channel_info_t pi;
Ghislain MARY's avatar
Ghislain MARY committed
415 416
	snd_pcm_channel_params_t params;
	snd_pcm_channel_status_t status;
Ghislain MARY's avatar
Ghislain MARY committed
417 418
	snd_pcm_channel_setup_t setup;
	snd_mixer_group_t group;
Ghislain MARY's avatar
Ghislain MARY committed
419 420
	int written;
	int err;
421 422
	fd_set fdset;
	struct timeval timeout;
Ghislain MARY's avatar
Ghislain MARY committed
423 424 425 426 427 428 429
	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
430
			ms_error("%s: snd_pcm_open_name(%s) failed: %s", __FUNCTION__, d->pcmdev, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
431 432
			goto setup_failure;
		}
433 434
		err = snd_pcm_info(d->handle, &info);
		if (err < 0) {
Ghislain MARY's avatar
Ghislain MARY committed
435
			ms_error("%s: snd_pcm_info() failed: %s", __FUNCTION__, snd_strerror(err));
436 437 438
			goto setup_failure;
		}
		d->card = info.card;
439 440 441 442 443 444
		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
445
		err = snd_pcm_plugin_set_disable(d->handle, PLUGIN_DISABLE_MMAP);
Ghislain MARY's avatar
Ghislain MARY committed
446 447 448 449
		if (err < 0) {
			ms_error("%s: snd_pcm_plugin_set_disable() failed: %s", __FUNCTION__, snd_strerror(err));
			goto setup_failure;
		}
450 451 452
		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
453
		if (err != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
454
			ms_error("%s: snd_pcm_plugin_info() failed: %s", __FUNCTION__, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
455 456 457 458 459
			goto setup_failure;
		}
		memset(&params, 0, sizeof(params));
		params.channel = SND_PCM_CHANNEL_PLAYBACK;
		params.mode = SND_PCM_MODE_BLOCK;
460
		params.start_mode = SND_PCM_START_DATA;
Ghislain MARY's avatar
Ghislain MARY committed
461
		params.stop_mode = SND_PCM_STOP_STOP;
462
		params.buf.block.frag_size = pi.max_fragment_size;
463
		params.buf.block.frags_min = 1;
Ghislain MARY's avatar
Ghislain MARY committed
464 465 466 467 468
		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;
469
		strcpy(params.sw_mixer_subchn_name, "Wave playback channel");
Ghislain MARY's avatar
Ghislain MARY committed
470 471
		err = snd_pcm_plugin_params(d->handle, &params);
		if (err != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
472
			ms_error("%s: snd_pcm_plugin_params() failed: %s", __FUNCTION__, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
473 474 475 476
			goto setup_failure;
		}
		err = snd_pcm_plugin_prepare(d->handle, SND_PCM_CHANNEL_PLAYBACK);
		if (err != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
477 478 479 480 481 482 483 484 485 486
			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
487 488
			goto setup_failure;
		}
489
		if (group.gid.name[0] == 0) {
Ghislain MARY's avatar
Ghislain MARY committed
490
			ms_error("%s: Mixer Pcm Group Not Set", __FUNCTION__);
491 492 493 494 495 496 497
			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
498 499 500 501 502 503 504

		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);
505

506
		d->buffer_size = setup.buf.block.frag_size * 3;
507
		d->buffer = ms_malloc(d->buffer_size);
Ghislain MARY's avatar
Ghislain MARY committed
508 509 510 511
	}

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

512 513 514 515 516 517 518 519 520 521 522
	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) {
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
		int size = ms_bufferizer_get_avail(d->bufferizer);
		if (size > d->buffer_size) {
			size = d->buffer_size;
		}
		ms_bufferizer_read(d->bufferizer, d->buffer, size);

		written = snd_pcm_plugin_write(d->handle, d->buffer, d->buffer_size);
		if (written < d->buffer_size) {
			ms_warning("%s: snd_pcm_plugin_write(%d) failed: %s", __FUNCTION__, d->buffer_size, strerror(errno));
			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) || (status.status == SND_PCM_STATUS_UNDERRUN)) {
				err = snd_pcm_plugin_prepare(d->handle, SND_PCM_CHANNEL_PLAYBACK);
Ghislain MARY's avatar
Ghislain MARY committed
541
				if (err != 0) {
542
					ms_error("%s: snd_pcm_plugin_prepare() failed: %s", __FUNCTION__, snd_strerror(err));
Ghislain MARY's avatar
Ghislain MARY committed
543 544 545
					goto setup_failure;
				}
			}
546
			if (written < 0) written = 0;
Ghislain MARY's avatar
Ghislain MARY committed
547
		}
548 549 550 551 552
	}

	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
553 554 555 556
	}
	return;

setup_failure:
557 558 559 560
	if (d->mixer_handle != NULL) {
		snd_mixer_close(d->mixer_handle);
		d->mixer_handle = NULL;
	}
Ghislain MARY's avatar
Ghislain MARY committed
561 562 563 564 565
	if (d->handle != NULL) {
		snd_pcm_close(d->handle);
		d->handle = NULL;
	}
	ms_queue_flush(f->inputs[0]);
566 567 568
}

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

571 572 573 574
	if (d->mixer_handle != NULL) {
		snd_mixer_close(d->mixer_handle);
		d->mixer_handle = NULL;
	}
Ghislain MARY's avatar
Ghislain MARY committed
575 576 577 578 579
	if (d->handle != NULL) {
		snd_pcm_plugin_flush(d->handle, SND_PCM_CHANNEL_PLAYBACK);
		snd_pcm_close(d->handle);
		d->handle = NULL;
	}
580 581 582 583
}

static void ms_qsa_write_uninit(MSFilter *f) {
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
Ghislain MARY's avatar
Ghislain MARY committed
584 585 586 587
	if (d->pcmdev != NULL) {
		ms_free(d->pcmdev);
		d->pcmdev = NULL;
	}
588 589 590 591 592
	if (d->buffer != NULL) {
		ms_free(d->buffer);
		d->buffer = NULL;
	}
	ms_bufferizer_destroy(d->bufferizer);
593 594 595 596 597 598 599 600 601
	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
602
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
Ghislain MARY's avatar
Ghislain MARY committed
603 604 605 606
	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
607 608 609
		return 0;
	}
	return -1;
610 611 612
}

static int ms_qsa_write_get_sample_rate(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
613 614
	MSQSAWriteData * d = (MSQSAWriteData *)f->data;
	*((int*)arg) = d->rate;
615 616 617 618
	return 0;
}

static int ms_qsa_write_set_nchannels(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
619 620 621 622 623 624 625
	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;
626 627 628
}

static int ms_qsa_write_get_nchannels(MSFilter *f, void *arg) {
Ghislain MARY's avatar
Ghislain MARY committed
629 630
	MSQSAWriteData *d = (MSQSAWriteData *)f->data;
	*((int *)arg) = d->nchannels;
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
	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
649 650 651 652 653 654
	.name = "MSQSAWrite",
	.text = "QSA sound playback.",
	.category = MS_FILTER_OTHER,
	.enc_fmt = NULL,
	.ninputs = 1,
	.noutputs = 0,
655 656 657 658 659
	.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
660
	.flags = 0
661 662 663 664 665 666 667 668 669 670 671 672 673 674
};

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
675 676 677 678 679
typedef struct _MSQSAData {
	char *pcmdev;
	char *mixdev;
} MSQSAData;

680 681 682 683
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
684
	.create_writer = ms_qsa_card_create_writer
685 686 687
};

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

Ghislain MARY's avatar
Ghislain MARY committed
690 691 692 693 694 695
	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);
696
	}
Ghislain MARY's avatar
Ghislain MARY committed
697 698 699 700 701 702
	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
703
	}
704 705 706
}

static MSFilter * ms_qsa_card_create_reader(MSSndCard *card) {
Ghislain MARY's avatar
Ghislain MARY committed
707
	return ms_qsa_read_new(card);
708 709 710
}

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