1 /*
2 * import_alsa.c -- module for importing audio through ALSA
3 * (C) 2008-2010 - Francesco Romani <fromani at gmail dot com>
4 *
5 * This file is part of transcode, a video stream processing tool.
6 *
7 * transcode is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * transcode is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22
23 #include "transcode.h"
24 #include "libtc/optstr.h"
25
26 #include "libtc/tcmodule-plugin.h"
27
28 #include "config.h"
29
30 #include <alsa/asoundlib.h>
31 #ifdef HAVE_GETTIMEOFDAY
32 # include <sys/time.h>
33 # include <time.h>
34 #endif
35 #include <string.h>
36
37 /*%*
38 *%* DESCRIPTION
39 *%* This module reads audio samples from an ALSA device using libalsa.
40 *%*
41 *%* BUILD-DEPENDS
42 *%* alsa-lib >= 1.0.0
43 *%*
44 *%* #DEPENDS
45 *%*
46 *%* PROCESSING
47 *%* import/demuxer
48 *%*
49 *%* MEDIA
50 *%* audio
51 *%*
52 *%* #INPUT
53 *%*
54 *%* OUTPUT
55 *%* PCM*
56 *%*
57 *%* OPTION
58 *%* device (string)
59 *%* selects ALSA device to use for capturing audio.
60 *%*/
61
62 #define LEGACY 1
63
64 #ifdef LEGACY
65 # define MOD_NAME "import_alsa.so"
66 #else
67 # define MOD_NAME "demultiplex_alsa.so"
68 #endif
69
70 #define MOD_VERSION "v0.0.5 (2007-05-12)"
71 #define MOD_CAP "capture audio using ALSA"
72
73 #define MOD_FEATURES \
74 TC_MODULE_FEATURE_DEMULTIPLEX|TC_MODULE_FEATURE_AUDIO
75 #define MOD_FLAGS \
76 TC_MODULE_FLAG_RECONFIGURABLE
77
78 static const char tc_alsa_help[] = ""
79 "Overview:\n"
80 " This module reads audio samples from an ALSA device using libalsa.\n"
81 "Options:\n"
82 " device=dev selects ALSA device to use\n"
83 " help produce module overview and options explanations\n";
84
85
86 /*
87 * TODO:
88 * - device naming fix (this will likely require some core changes)
89 * - probing/integration with core
90 * - suspend recovery?
91 * - smarter resync?
92 */
93
94 /*************************************************************************/
95
96 typedef struct tcalsasource_ TCALSASource;
97 struct tcalsasource_ {
98 snd_pcm_t *pcm;
99
100 int rate;
101 int channels;
102 int precision;
103 };
104
105
106 /*************************************************************************/
107 /* some support functions shamelessly borrowed^Hinspired from alsa-utils */
108 /*************************************************************************/
109
110 #ifdef HAVE_GETTIMEOFDAY
111
112 #define TIMERSUB(a, b, result) do { \
113 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
114 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
115 if ((result)->tv_usec < 0) { \
116 --(result)->tv_sec; \
117 (result)->tv_usec += 1000000; \
118 } \
119 } while (0)
120
121 #endif
122
123 #define ALSA_PREPARE(HANDLE) do { \
124 int ret = snd_pcm_prepare((HANDLE)->pcm); \
125 if (ret < 0) { \
126 tc_log_error(MOD_NAME, "ALSA prepare error: %s", snd_strerror(ret)); \
127 return TC_ERROR; \
128 } \
129 } while (0)
130
131 /* I/O error handler */
alsa_source_xrun(TCALSASource * handle)132 static int alsa_source_xrun(TCALSASource *handle)
133 {
134 snd_pcm_status_t *status = NULL;
135 snd_pcm_state_t state = 0;
136 int ret = 0;
137
138 TC_MODULE_SELF_CHECK(handle, "alsa_source_xrun");
139
140 snd_pcm_status_alloca(&status);
141 ret = snd_pcm_status(handle->pcm, status);
142 if (ret < 0) {
143 tc_log_error(__FILE__, "error while fetching status: %s",
144 snd_strerror(ret));
145 return TC_ERROR;
146 }
147
148 state = snd_pcm_status_get_state(status);
149
150 if (state == SND_PCM_STATE_XRUN) {
151 #ifdef HAVE_GETTIMEOFDAY
152 struct timeval now, diff, tstamp;
153
154 gettimeofday(&now, NULL);
155 snd_pcm_status_get_trigger_tstamp(status, &tstamp);
156 TIMERSUB(&now, &tstamp, &diff);
157
158 tc_log_warn(__FILE__, "overrun at least %.3f ms long",
159 diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
160 #else /* ! HAVE_GETTIMEOFDAY */
161 tc_log_warn(__FILE__, "overrun");
162 #endif /* HAVE_GETTIMEOFDAY */
163 ALSA_PREPARE(handle);
164 } else if (state == SND_PCM_STATE_DRAINING) {
165 tc_log_warn(__FILE__, "capture stream format change? attempting recover...");
166 ALSA_PREPARE(handle);
167 } else { /* catch all */
168 tc_log_error(__FILE__, "read error, state = %s", snd_pcm_state_name(state));
169 return TC_ERROR;
170 }
171 return TC_OK;
172 }
173
174
175 #define RETURN_IF_ALSA_FAIL(RET, MSG) do { \
176 if ((RET) < 0) { \
177 tc_log_error(__FILE__, "%s (%s)", (MSG), snd_strerror((RET))); \
178 return TC_ERROR; \
179 } \
180 } while (0)
181
tc_alsa_source_open(TCALSASource * handle,const char * dev,int rate,int precision,int channels)182 static int tc_alsa_source_open(TCALSASource *handle, const char *dev,
183 int rate, int precision, int channels)
184 {
185 int ret = 0, alsa_rate = rate;
186 snd_pcm_hw_params_t *hwparams = NULL;
187
188 TC_MODULE_SELF_CHECK(handle, "alsa_source_open");
189
190 /* some basic sanity checks */
191 if (!strcmp(dev, "/dev/null") || !strcmp(dev, "/dev/zero")) {
192 return TC_OK;
193 }
194
195 if (!dev || !strlen(dev)) {
196 tc_log_warn(__FILE__, "bad ALSA device");
197 return TC_ERROR;
198 }
199 if (precision != 8 && precision != 16) {
200 tc_log_warn(__FILE__, "bits/sample must be 8 or 16");
201 return TC_ERROR;
202 }
203
204 handle->rate = rate;
205 handle->channels = channels;
206 handle->precision = precision;
207
208 /* ok, time to rock */
209 snd_pcm_hw_params_alloca(&(hwparams));
210 if (hwparams == NULL) {
211 tc_log_warn(__FILE__, "cannot allocate ALSA HW parameters");
212 return TC_ERROR;
213 }
214
215 tc_log_info(__FILE__, "using PCM capture device: %s", dev);
216 ret = snd_pcm_open(&(handle->pcm), dev, SND_PCM_STREAM_CAPTURE, 0);
217 if (ret < 0) {
218 tc_log_warn(__FILE__, "error opening PCM device %s\n", dev);
219 return TC_ERROR;
220 }
221
222 ret = snd_pcm_hw_params_any(handle->pcm, hwparams);
223 RETURN_IF_ALSA_FAIL(ret, "cannot preconfigure PCM device");
224
225 ret = snd_pcm_hw_params_set_access(handle->pcm, hwparams,
226 SND_PCM_ACCESS_RW_INTERLEAVED);
227 RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM access");
228
229 ret = snd_pcm_hw_params_set_format(handle->pcm, hwparams,
230 (precision == 16) ?SND_PCM_FORMAT_S16_LE
231 :SND_PCM_FORMAT_S8);
232 RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM format");
233
234 ret = snd_pcm_hw_params_set_rate_near(handle->pcm, hwparams, &alsa_rate, 0);
235 RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM rate");
236
237 if (rate != alsa_rate) {
238 tc_log_warn(__FILE__, "rate %d Hz unsupported by hardware, using %d Hz instead",
239 rate, alsa_rate);
240 }
241
242 ret = snd_pcm_hw_params_set_channels(handle->pcm, hwparams, channels);
243 RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM channels");
244
245 ret = snd_pcm_hw_params(handle->pcm, hwparams);
246 RETURN_IF_ALSA_FAIL(ret, "cannot setup hardware parameters");
247
248 tc_log_info(__FILE__, "ALSA audio capture: "
249 "%i Hz, %i bps, %i channels",
250 alsa_rate, precision, channels);
251
252 return TC_OK;
253 }
254
255 /* frame size = sample size (bytes) * sample number (= channels number) */
256 #define ALSA_FRAME_SIZE(HANDLE) \
257 ((HANDLE)->channels * (HANDLE)->precision / 8)
258
259
tc_alsa_source_grab(TCALSASource * handle,uint8_t * buf,size_t bufsize,size_t * buflen)260 static int tc_alsa_source_grab(TCALSASource *handle, uint8_t *buf,
261 size_t bufsize, size_t *buflen)
262 {
263 snd_pcm_uframes_t frames = bufsize / ALSA_FRAME_SIZE(handle);
264 snd_pcm_sframes_t ret = 0;
265
266 TC_MODULE_SELF_CHECK(handle, "alsa_source_grab");
267 TC_MODULE_SELF_CHECK(buf, "alsa_source_grab");
268
269 ret = snd_pcm_readi(handle->pcm, buf, frames);
270 if (ret == -EAGAIN || (ret >= 0 && (snd_pcm_uframes_t)ret < frames)) {
271 /* this can really happen? */
272 snd_pcm_wait(handle->pcm, -1);
273 } else if (ret == -EPIPE) { /* xrun (overrun) */
274 return alsa_source_xrun(handle);
275 } else if (ret == -ESTRPIPE) { /* suspend */
276 tc_log_error(__FILE__, "stream suspended (unrecoverable, yet)");
277 return TC_ERROR;
278 } else if (ret < 0) {
279 tc_log_error(__FILE__, "ALSA read error: %s", snd_strerror(ret));
280 return TC_ERROR;
281 }
282
283 if (buflen != NULL) {
284 *buflen = (size_t)ret;
285 }
286 return TC_OK;
287 }
288
tc_alsa_source_close(TCALSASource * handle)289 static int tc_alsa_source_close(TCALSASource *handle)
290 {
291 TC_MODULE_SELF_CHECK(handle, "alsa_source_close");
292
293 if (handle->pcm != NULL) {
294 snd_pcm_close(handle->pcm);
295 handle->pcm = NULL;
296 }
297 return TC_OK;
298 }
299
300 #undef RETURN_IF_ALSA_FAIL
301
302
303 /* ------------------------------------------------------------
304 * New-Style module interface
305 * ------------------------------------------------------------*/
306
307 typedef struct tcalsaprivatedata_ TCALSAPrivateData;
308 struct tcalsaprivatedata_ {
309 TCALSASource handle;
310 };
311
312
tc_alsa_init(TCModuleInstance * self,uint32_t features)313 static int tc_alsa_init(TCModuleInstance *self, uint32_t features)
314 {
315 TCALSAPrivateData *priv = NULL;
316
317 TC_MODULE_SELF_CHECK(self, "init");
318 TC_MODULE_INIT_CHECK(self, MOD_FEATURES, features);
319
320 if (verbose) {
321 tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP);
322 }
323 priv = tc_zalloc(sizeof(TCALSAPrivateData));
324 if (priv == NULL) {
325 return TC_ERROR;
326 }
327
328 self->userdata = priv;
329 return TC_OK;
330 }
331
tc_alsa_fini(TCModuleInstance * self)332 static int tc_alsa_fini(TCModuleInstance *self)
333 {
334 TC_MODULE_SELF_CHECK(self, "fini");
335
336 tc_free(self->userdata);
337 self->userdata = NULL;
338
339 return TC_OK;
340 }
341
tc_alsa_configure(TCModuleInstance * self,const char * options,vob_t * vob)342 static int tc_alsa_configure(TCModuleInstance *self,
343 const char *options, vob_t *vob)
344 {
345 TCALSAPrivateData *priv = NULL;
346 int ret = 0;
347 char device[1024];
348
349 TC_MODULE_SELF_CHECK(self, "configure");
350
351 priv = self->userdata;
352
353 strlcpy(device, "default", TC_BUF_MAX);
354 if (options != NULL) {
355 optstr_get(options, "device", "%1024s", device);
356 device[1024-1] = '\0';
357 /* yeah, this is pretty ugly -- FR */
358 }
359
360 /* it would be nice to have some more validation in here */
361 ret = tc_alsa_source_open(&(priv->handle), device,
362 vob->a_rate, vob->a_bits, vob->a_chan);
363 if (ret != 0) {
364 tc_log_error(MOD_NAME, "configure: failed to open ALSA device"
365 "'%s'", device);
366 return TC_ERROR;
367 }
368 return TC_OK;
369 }
370
tc_alsa_inspect(TCModuleInstance * self,const char * param,const char ** value)371 static int tc_alsa_inspect(TCModuleInstance *self,
372 const char *param, const char **value)
373 {
374 TC_MODULE_SELF_CHECK(self, "inspect");
375
376 if (optstr_lookup(param, "help")) {
377 *value = tc_alsa_help;
378 }
379
380 return TC_OK;
381 }
382
tc_alsa_stop(TCModuleInstance * self)383 static int tc_alsa_stop(TCModuleInstance *self)
384 {
385 TCALSAPrivateData *priv = NULL;
386 int ret = 0;
387
388 TC_MODULE_SELF_CHECK(self, "stop");
389
390 priv = self->userdata;
391
392 ret = tc_alsa_source_close(&(priv->handle));
393 if (ret != TC_OK) {
394 tc_log_error(MOD_NAME, "stop: failed to close ALSA device");
395 return TC_ERROR;
396 }
397
398 return TC_OK;
399 }
400
tc_alsa_demultiplex(TCModuleInstance * self,vframe_list_t * vframe,aframe_list_t * aframe)401 static int tc_alsa_demultiplex(TCModuleInstance *self,
402 vframe_list_t *vframe, aframe_list_t *aframe)
403 {
404 TCALSAPrivateData *priv = NULL;
405 int ret = TC_OK;
406 size_t len = 0;
407
408 TC_MODULE_SELF_CHECK(self, "demultiplex");
409
410 priv = self->userdata;
411
412 if (vframe != NULL) {
413 vframe->video_len = 0; /* no audio from here */
414 ret = TC_OK;
415 }
416
417 if (aframe != NULL) {
418 ret = tc_alsa_source_grab(&(priv->handle), aframe->audio_buf,
419 aframe->audio_size, &len);
420 aframe->audio_len = (size_t)len;
421 }
422 return ret;
423 }
424
425 /*************************************************************************/
426
427 static const TCCodecID tc_alsa_codecs_in[] = { TC_CODEC_ERROR };
428
429 /* a multiplexor is at the end of pipeline */
430 static const TCCodecID tc_alsa_codecs_out[] = {
431 TC_CODEC_PCM,
432 TC_CODEC_ERROR,
433 };
434
435 static const TCFormatID tc_alsa_formats_in[] = {
436 TC_FORMAT_ALSA,
437 TC_FORMAT_ERROR,
438 };
439
440 static const TCFormatID tc_alsa_formats_out[] = { TC_FORMAT_ERROR };
441
442 static const TCModuleInfo tc_alsa_info = {
443 .features = MOD_FEATURES,
444 .flags = MOD_FLAGS,
445 .name = MOD_NAME,
446 .version = MOD_VERSION,
447 .description = MOD_CAP,
448 .codecs_in = tc_alsa_codecs_in,
449 .codecs_out = tc_alsa_codecs_out,
450 .formats_in = tc_alsa_formats_in,
451 .formats_out = tc_alsa_formats_out
452 };
453
454 static const TCModuleClass tc_alsa_class = {
455 TC_MODULE_CLASS_HEAD(tc_alsa),
456
457 .init = tc_alsa_init,
458 .fini = tc_alsa_fini,
459 .configure = tc_alsa_configure,
460 .stop = tc_alsa_stop,
461 .inspect = tc_alsa_inspect,
462
463 .demultiplex = tc_alsa_demultiplex,
464 };
465
466 TC_MODULE_ENTRY_POINT(tc_alsa)
467
468 /*************************************************************************/
469
470 /* ------------------------------------------------------------
471 * Old-Style module interface
472 * ------------------------------------------------------------*/
473
474 static int verbose_flag = TC_QUIET;
475 static int capability_flag = TC_CAP_PCM;
476
477 #define MOD_PRE alsa
478 #define MOD_CODEC "(audio) pcm"
479
480 #include "import_def.h"
481
482
483 static TCALSASource handle = {
484 .pcm = NULL,
485 .rate = RATE,
486 .channels = CHANNELS,
487 .precision = BITS,
488 };
489
490
491 MOD_open
492 {
493 int ret = TC_ERROR;
494 char device[1024];
495
496 switch (param->flag) {
497 case TC_VIDEO:
498 tc_log_warn(MOD_NAME, "unsupported request (init video)");
499 break;
500 case TC_AUDIO:
501 if (verbose_flag & TC_DEBUG) {
502 tc_log_info(MOD_NAME, "ALSA audio grabbing");
503 }
504
505 strlcpy(device, "default", 1024);
506 if (vob->im_a_string != NULL) {
507 optstr_get(vob->im_a_string, "device", "%1024s", device);
508 device[1024-1] = '\0';
509 /* yeah, this too is pretty ugly -- FR */
510 }
511
512 ret = tc_alsa_source_open(&handle, device,
513 vob->a_rate, vob->a_bits, vob->a_chan);
514 break;
515 default:
516 tc_log_warn(MOD_NAME, "unsupported request (init)");
517 break;
518 }
519
520 return ret;
521 }
522
523
524 MOD_decode
525 {
526 int ret = TC_ERROR;
527
528 switch (param->flag) {
529 case TC_VIDEO:
530 tc_log_warn(MOD_NAME, "unsupported request (decode video)");
531 break;
532 case TC_AUDIO:
533 ret = tc_alsa_source_grab(&handle, param->buffer,
534 param->size, NULL);
535 break;
536 default:
537 tc_log_warn(MOD_NAME, "unsupported request (decode)");
538 break;
539 }
540
541 return ret;
542 }
543
544
545 MOD_close
546 {
547 int ret = TC_ERROR;
548
549 switch (param->flag) {
550 case TC_VIDEO:
551 tc_log_warn(MOD_NAME, "unsupported request (close video)");
552 break;
553 case TC_AUDIO:
554 ret = tc_alsa_source_close(&handle);
555 break;
556 default:
557 tc_log_warn(MOD_NAME, "unsupported request (close)");
558 break;
559 }
560
561 return ret;
562 }
563
564 /*************************************************************************/
565
566 /*
567 * Local variables:
568 * c-file-style: "stroustrup"
569 * c-file-offsets: ((case-label . *) (statement-case-intro . *))
570 * indent-tabs-mode: nil
571 * End:
572 *
573 * vim: expandtab shiftwidth=4:
574 */
575