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