diff options
Diffstat (limited to 'debian/transcode/transcode-1.1.7/import/import_alsa.c')
| -rw-r--r-- | debian/transcode/transcode-1.1.7/import/import_alsa.c | 574 |
1 files changed, 574 insertions, 0 deletions
diff --git a/debian/transcode/transcode-1.1.7/import/import_alsa.c b/debian/transcode/transcode-1.1.7/import/import_alsa.c new file mode 100644 index 00000000..2a0b2073 --- /dev/null +++ b/debian/transcode/transcode-1.1.7/import/import_alsa.c @@ -0,0 +1,574 @@ +/* + * import_alsa.c -- module for importing audio through ALSA + * (C) 2008-2010 - Francesco Romani <fromani at gmail dot com> + * + * This file is part of transcode, a video stream processing tool. + * + * transcode 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. + * + * transcode 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, see <http://www.gnu.org/licenses/>. + */ + + + +#include "transcode.h" +#include "libtc/optstr.h" + +#include "libtc/tcmodule-plugin.h" + +#include "config.h" + +#include <alsa/asoundlib.h> +#ifdef HAVE_GETTIMEOFDAY +# include <sys/time.h> +# include <time.h> +#endif +#include <string.h> + +/*%* + *%* DESCRIPTION + *%* This module reads audio samples from an ALSA device using libalsa. + *%* + *%* BUILD-DEPENDS + *%* alsa-lib >= 1.0.0 + *%* + *%* #DEPENDS + *%* + *%* PROCESSING + *%* import/demuxer + *%* + *%* MEDIA + *%* audio + *%* + *%* #INPUT + *%* + *%* OUTPUT + *%* PCM* + *%* + *%* OPTION + *%* device (string) + *%* selects ALSA device to use for capturing audio. + *%*/ + +#define LEGACY 1 + +#ifdef LEGACY +# define MOD_NAME "import_alsa.so" +#else +# define MOD_NAME "demultiplex_alsa.so" +#endif + +#define MOD_VERSION "v0.0.5 (2007-05-12)" +#define MOD_CAP "capture audio using ALSA" + +#define MOD_FEATURES \ + TC_MODULE_FEATURE_DEMULTIPLEX|TC_MODULE_FEATURE_AUDIO +#define MOD_FLAGS \ + TC_MODULE_FLAG_RECONFIGURABLE + +static const char tc_alsa_help[] = "" + "Overview:\n" + " This module reads audio samples from an ALSA device using libalsa.\n" + "Options:\n" + " device=dev selects ALSA device to use\n" + " help produce module overview and options explanations\n"; + + +/* + * TODO: + * - device naming fix (this will likely require some core changes) + * - probing/integration with core + * - suspend recovery? + * - smarter resync? + */ + +/*************************************************************************/ + +typedef struct tcalsasource_ TCALSASource; +struct tcalsasource_ { + snd_pcm_t *pcm; + + int rate; + int channels; + int precision; +}; + + +/*************************************************************************/ +/* some support functions shamelessly borrowed^Hinspired from alsa-utils */ +/*************************************************************************/ + +#ifdef HAVE_GETTIMEOFDAY + +#define TIMERSUB(a, b, result) do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ +} while (0) + +#endif + +#define ALSA_PREPARE(HANDLE) do { \ + int ret = snd_pcm_prepare((HANDLE)->pcm); \ + if (ret < 0) { \ + tc_log_error(MOD_NAME, "ALSA prepare error: %s", snd_strerror(ret)); \ + return TC_ERROR; \ + } \ +} while (0) + +/* I/O error handler */ +static int alsa_source_xrun(TCALSASource *handle) +{ + snd_pcm_status_t *status = NULL; + snd_pcm_state_t state = 0; + int ret = 0; + + TC_MODULE_SELF_CHECK(handle, "alsa_source_xrun"); + + snd_pcm_status_alloca(&status); + ret = snd_pcm_status(handle->pcm, status); + if (ret < 0) { + tc_log_error(__FILE__, "error while fetching status: %s", + snd_strerror(ret)); + return TC_ERROR; + } + + state = snd_pcm_status_get_state(status); + + if (state == SND_PCM_STATE_XRUN) { +#ifdef HAVE_GETTIMEOFDAY + struct timeval now, diff, tstamp; + + gettimeofday(&now, NULL); + snd_pcm_status_get_trigger_tstamp(status, &tstamp); + TIMERSUB(&now, &tstamp, &diff); + + tc_log_warn(__FILE__, "overrun at least %.3f ms long", + diff.tv_sec * 1000 + diff.tv_usec / 1000.0); +#else /* ! HAVE_GETTIMEOFDAY */ + tc_log_warn(__FILE__, "overrun"); +#endif /* HAVE_GETTIMEOFDAY */ + ALSA_PREPARE(handle); + } else if (state == SND_PCM_STATE_DRAINING) { + tc_log_warn(__FILE__, "capture stream format change? attempting recover..."); + ALSA_PREPARE(handle); + } else { /* catch all */ + tc_log_error(__FILE__, "read error, state = %s", snd_pcm_state_name(state)); + return TC_ERROR; + } + return TC_OK; +} + + +#define RETURN_IF_ALSA_FAIL(RET, MSG) do { \ + if ((RET) < 0) { \ + tc_log_error(__FILE__, "%s (%s)", (MSG), snd_strerror((RET))); \ + return TC_ERROR; \ + } \ +} while (0) + +static int tc_alsa_source_open(TCALSASource *handle, const char *dev, + int rate, int precision, int channels) +{ + int ret = 0, alsa_rate = rate; + snd_pcm_hw_params_t *hwparams = NULL; + + TC_MODULE_SELF_CHECK(handle, "alsa_source_open"); + + /* some basic sanity checks */ + if (!strcmp(dev, "/dev/null") || !strcmp(dev, "/dev/zero")) { + return TC_OK; + } + + if (!dev || !strlen(dev)) { + tc_log_warn(__FILE__, "bad ALSA device"); + return TC_ERROR; + } + if (precision != 8 && precision != 16) { + tc_log_warn(__FILE__, "bits/sample must be 8 or 16"); + return TC_ERROR; + } + + handle->rate = rate; + handle->channels = channels; + handle->precision = precision; + + /* ok, time to rock */ + snd_pcm_hw_params_alloca(&(hwparams)); + if (hwparams == NULL) { + tc_log_warn(__FILE__, "cannot allocate ALSA HW parameters"); + return TC_ERROR; + } + + tc_log_info(__FILE__, "using PCM capture device: %s", dev); + ret = snd_pcm_open(&(handle->pcm), dev, SND_PCM_STREAM_CAPTURE, 0); + if (ret < 0) { + tc_log_warn(__FILE__, "error opening PCM device %s\n", dev); + return TC_ERROR; + } + + ret = snd_pcm_hw_params_any(handle->pcm, hwparams); + RETURN_IF_ALSA_FAIL(ret, "cannot preconfigure PCM device"); + + ret = snd_pcm_hw_params_set_access(handle->pcm, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM access"); + + ret = snd_pcm_hw_params_set_format(handle->pcm, hwparams, + (precision == 16) ?SND_PCM_FORMAT_S16_LE + :SND_PCM_FORMAT_S8); + RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM format"); + + ret = snd_pcm_hw_params_set_rate_near(handle->pcm, hwparams, &alsa_rate, 0); + RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM rate"); + + if (rate != alsa_rate) { + tc_log_warn(__FILE__, "rate %d Hz unsupported by hardware, using %d Hz instead", + rate, alsa_rate); + } + + ret = snd_pcm_hw_params_set_channels(handle->pcm, hwparams, channels); + RETURN_IF_ALSA_FAIL(ret, "cannot setup PCM channels"); + + ret = snd_pcm_hw_params(handle->pcm, hwparams); + RETURN_IF_ALSA_FAIL(ret, "cannot setup hardware parameters"); + + tc_log_info(__FILE__, "ALSA audio capture: " + "%i Hz, %i bps, %i channels", + alsa_rate, precision, channels); + + return TC_OK; +} + +/* frame size = sample size (bytes) * sample number (= channels number) */ +#define ALSA_FRAME_SIZE(HANDLE) \ + ((HANDLE)->channels * (HANDLE)->precision / 8) + + +static int tc_alsa_source_grab(TCALSASource *handle, uint8_t *buf, + size_t bufsize, size_t *buflen) +{ + snd_pcm_uframes_t frames = bufsize / ALSA_FRAME_SIZE(handle); + snd_pcm_sframes_t ret = 0; + + TC_MODULE_SELF_CHECK(handle, "alsa_source_grab"); + TC_MODULE_SELF_CHECK(buf, "alsa_source_grab"); + + ret = snd_pcm_readi(handle->pcm, buf, frames); + if (ret == -EAGAIN || (ret >= 0 && (snd_pcm_uframes_t)ret < frames)) { + /* this can really happen? */ + snd_pcm_wait(handle->pcm, -1); + } else if (ret == -EPIPE) { /* xrun (overrun) */ + return alsa_source_xrun(handle); + } else if (ret == -ESTRPIPE) { /* suspend */ + tc_log_error(__FILE__, "stream suspended (unrecoverable, yet)"); + return TC_ERROR; + } else if (ret < 0) { + tc_log_error(__FILE__, "ALSA read error: %s", snd_strerror(ret)); + return TC_ERROR; + } + + if (buflen != NULL) { + *buflen = (size_t)ret; + } + return TC_OK; +} + +static int tc_alsa_source_close(TCALSASource *handle) +{ + TC_MODULE_SELF_CHECK(handle, "alsa_source_close"); + + if (handle->pcm != NULL) { + snd_pcm_close(handle->pcm); + handle->pcm = NULL; + } + return TC_OK; +} + +#undef RETURN_IF_ALSA_FAIL + + +/* ------------------------------------------------------------ + * New-Style module interface + * ------------------------------------------------------------*/ + +typedef struct tcalsaprivatedata_ TCALSAPrivateData; +struct tcalsaprivatedata_ { + TCALSASource handle; +}; + + +static int tc_alsa_init(TCModuleInstance *self, uint32_t features) +{ + TCALSAPrivateData *priv = NULL; + + TC_MODULE_SELF_CHECK(self, "init"); + TC_MODULE_INIT_CHECK(self, MOD_FEATURES, features); + + if (verbose) { + tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP); + } + priv = tc_zalloc(sizeof(TCALSAPrivateData)); + if (priv == NULL) { + return TC_ERROR; + } + + self->userdata = priv; + return TC_OK; +} + +static int tc_alsa_fini(TCModuleInstance *self) +{ + TC_MODULE_SELF_CHECK(self, "fini"); + + tc_free(self->userdata); + self->userdata = NULL; + + return TC_OK; +} + +static int tc_alsa_configure(TCModuleInstance *self, + const char *options, vob_t *vob) +{ + TCALSAPrivateData *priv = NULL; + int ret = 0; + char device[1024]; + + TC_MODULE_SELF_CHECK(self, "configure"); + + priv = self->userdata; + + strlcpy(device, "default", TC_BUF_MAX); + if (options != NULL) { + optstr_get(options, "device", "%1024s", device); + device[1024-1] = '\0'; + /* yeah, this is pretty ugly -- FR */ + } + + /* it would be nice to have some more validation in here */ + ret = tc_alsa_source_open(&(priv->handle), device, + vob->a_rate, vob->a_bits, vob->a_chan); + if (ret != 0) { + tc_log_error(MOD_NAME, "configure: failed to open ALSA device" + "'%s'", device); + return TC_ERROR; + } + return TC_OK; +} + +static int tc_alsa_inspect(TCModuleInstance *self, + const char *param, const char **value) +{ + TC_MODULE_SELF_CHECK(self, "inspect"); + + if (optstr_lookup(param, "help")) { + *value = tc_alsa_help; + } + + return TC_OK; +} + +static int tc_alsa_stop(TCModuleInstance *self) +{ + TCALSAPrivateData *priv = NULL; + int ret = 0; + + TC_MODULE_SELF_CHECK(self, "stop"); + + priv = self->userdata; + + ret = tc_alsa_source_close(&(priv->handle)); + if (ret != TC_OK) { + tc_log_error(MOD_NAME, "stop: failed to close ALSA device"); + return TC_ERROR; + } + + return TC_OK; +} + +static int tc_alsa_demultiplex(TCModuleInstance *self, + vframe_list_t *vframe, aframe_list_t *aframe) +{ + TCALSAPrivateData *priv = NULL; + int ret = TC_OK; + size_t len = 0; + + TC_MODULE_SELF_CHECK(self, "demultiplex"); + + priv = self->userdata; + + if (vframe != NULL) { + vframe->video_len = 0; /* no audio from here */ + ret = TC_OK; + } + + if (aframe != NULL) { + ret = tc_alsa_source_grab(&(priv->handle), aframe->audio_buf, + aframe->audio_size, &len); + aframe->audio_len = (size_t)len; + } + return ret; +} + +/*************************************************************************/ + +static const TCCodecID tc_alsa_codecs_in[] = { TC_CODEC_ERROR }; + +/* a multiplexor is at the end of pipeline */ +static const TCCodecID tc_alsa_codecs_out[] = { + TC_CODEC_PCM, + TC_CODEC_ERROR, +}; + +static const TCFormatID tc_alsa_formats_in[] = { + TC_FORMAT_ALSA, + TC_FORMAT_ERROR, +}; + +static const TCFormatID tc_alsa_formats_out[] = { TC_FORMAT_ERROR }; + +static const TCModuleInfo tc_alsa_info = { + .features = MOD_FEATURES, + .flags = MOD_FLAGS, + .name = MOD_NAME, + .version = MOD_VERSION, + .description = MOD_CAP, + .codecs_in = tc_alsa_codecs_in, + .codecs_out = tc_alsa_codecs_out, + .formats_in = tc_alsa_formats_in, + .formats_out = tc_alsa_formats_out +}; + +static const TCModuleClass tc_alsa_class = { + TC_MODULE_CLASS_HEAD(tc_alsa), + + .init = tc_alsa_init, + .fini = tc_alsa_fini, + .configure = tc_alsa_configure, + .stop = tc_alsa_stop, + .inspect = tc_alsa_inspect, + + .demultiplex = tc_alsa_demultiplex, +}; + +TC_MODULE_ENTRY_POINT(tc_alsa) + +/*************************************************************************/ + +/* ------------------------------------------------------------ + * Old-Style module interface + * ------------------------------------------------------------*/ + +static int verbose_flag = TC_QUIET; +static int capability_flag = TC_CAP_PCM; + +#define MOD_PRE alsa +#define MOD_CODEC "(audio) pcm" + +#include "import_def.h" + + +static TCALSASource handle = { + .pcm = NULL, + .rate = RATE, + .channels = CHANNELS, + .precision = BITS, +}; + + +MOD_open +{ + int ret = TC_ERROR; + char device[1024]; + + switch (param->flag) { + case TC_VIDEO: + tc_log_warn(MOD_NAME, "unsupported request (init video)"); + break; + case TC_AUDIO: + if (verbose_flag & TC_DEBUG) { + tc_log_info(MOD_NAME, "ALSA audio grabbing"); + } + + strlcpy(device, "default", 1024); + if (vob->im_a_string != NULL) { + optstr_get(vob->im_a_string, "device", "%1024s", device); + device[1024-1] = '\0'; + /* yeah, this too is pretty ugly -- FR */ + } + + ret = tc_alsa_source_open(&handle, device, + vob->a_rate, vob->a_bits, vob->a_chan); + break; + default: + tc_log_warn(MOD_NAME, "unsupported request (init)"); + break; + } + + return ret; +} + + +MOD_decode +{ + int ret = TC_ERROR; + + switch (param->flag) { + case TC_VIDEO: + tc_log_warn(MOD_NAME, "unsupported request (decode video)"); + break; + case TC_AUDIO: + ret = tc_alsa_source_grab(&handle, param->buffer, + param->size, NULL); + break; + default: + tc_log_warn(MOD_NAME, "unsupported request (decode)"); + break; + } + + return ret; +} + + +MOD_close +{ + int ret = TC_ERROR; + + switch (param->flag) { + case TC_VIDEO: + tc_log_warn(MOD_NAME, "unsupported request (close video)"); + break; + case TC_AUDIO: + ret = tc_alsa_source_close(&handle); + break; + default: + tc_log_warn(MOD_NAME, "unsupported request (close)"); + break; + } + + return ret; +} + +/*************************************************************************/ + +/* + * Local variables: + * c-file-style: "stroustrup" + * c-file-offsets: ((case-label . *) (statement-case-intro . *)) + * indent-tabs-mode: nil + * End: + * + * vim: expandtab shiftwidth=4: + */ |
