[FFmpeg-devel] [PATCH] ALSA for libavdevice

Nicolas George nicolas.george
Mon Dec 15 00:05:31 CET 2008


Le tridi 23 frimaire, an CCXVII, Michael Niedermayer a ?crit?:
> hmm, your arguments are good ...
> maybe samplerate*128 could be used though, this would allow exact timestamps
> for synthetically generated sound or where the systemclock is locked to the
> samplerate, or when things are resampled later but the timebase is left.

The timestamps from ALSA come in the form seconds+nanoseconds, not in
samples, and as far as I could see in the kernel driver part, they are in
that format from the very start, so I am afraid that converting to
samplerate*128 is more likely to accumulate conversion errors than correct
them.

Furthermore, on of the main advantages of time-of-day based timestamps is to
be able to pass them back to localtime to display the time of recording.
Converting the timestamps into another unit makes this (a little) harder.

> can this change the samplerate/codec_id for the recording case?
> If so how does that work, the code throws the value away afterwards

The sample rate can be slightly changed depending on the hardware
capabilities. The codec_id can be changed from NONE to the default one. The
values are not thrown away afterwards, audio_read_header uses them to set
st->codec->codec_type and st->codec->sample_rate.

For playback, on the other hand, if the sample rate is changed, the
resulting value is discarded. It may be better to fail in that case, or to
fail if the change is too big or something; I believe it is a matter of
taste.

> the indention looks odd

Fixed.

Regards,

-- 
  Nicolas George
-------------- next part --------------
diff --git a/configure b/configure
index 7b0f615..24a8aa7 100755
--- a/configure
+++ b/configure
@@ -819,6 +819,7 @@ ARCH_EXT_LIST='
 HAVE_LIST="
     $ARCH_EXT_LIST
     $THREADS_LIST
+    alsa_asoundlib_h
     altivec_h
     arpa_inet_h
     bswap
@@ -1037,6 +1038,10 @@ mpeg4aac_decoder_deps="libfaad"
 
 # demuxers / muxers
 ac3_demuxer_deps="ac3_parser"
+alsa_demuxer_deps="alsa_asoundlib_h"
+alsa_demuxer_extralibs="-lasound"
+alsa_muxer_deps="alsa_asoundlib_h"
+alsa_muxer_extralibs="-lasound"
 audio_beos_demuxer_deps="audio_beos"
 audio_beos_demuxer_extralibs="-lmedia -lbe"
 audio_beos_muxer_deps="audio_beos"
@@ -2007,6 +2012,8 @@ check_header dev/ic/bt8xx.h
 check_header sys/soundcard.h
 check_header soundcard.h
 
+check_header alsa/asoundlib.h
+
 # deal with the X11 frame grabber
 enabled x11grab                         &&
 check_header X11/Xlib.h                 &&
diff --git a/libavdevice/Makefile b/libavdevice/Makefile
index 655c033..3ab27a0 100644
--- a/libavdevice/Makefile
+++ b/libavdevice/Makefile
@@ -8,6 +8,8 @@ HEADERS = avdevice.h
 OBJS    = alldevices.o
 
 # input/output devices
+OBJS-$(CONFIG_ALSA_DEMUXER)              += alsa-audio-common.o alsa-audio-dec.o
+OBJS-$(CONFIG_ALSA_MUXER)                += alsa-audio-common.o alsa-audio-enc.o
 OBJS-$(CONFIG_BKTR_DEMUXER)              += bktr.o
 OBJS-$(CONFIG_DV1394_DEMUXER)            += dv1394.o
 OBJS-$(CONFIG_OSS_DEMUXER)               += audio.o
diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
index b94db63..bfce1cd 100644
--- a/libavdevice/alldevices.c
+++ b/libavdevice/alldevices.c
@@ -44,6 +44,7 @@ void avdevice_register_all(void)
     initialized = 1;
 
     /* devices */
+    REGISTER_MUXDEMUX (ALSA, alsa);
     REGISTER_MUXDEMUX (AUDIO_BEOS, audio_beos);
     REGISTER_DEMUXER  (BKTR, bktr);
     REGISTER_DEMUXER  (DV1394, dv1394);
diff --git a/libavdevice/alsa-audio-common.c b/libavdevice/alsa-audio-common.c
new file mode 100644
index 0000000..2cef1fa
--- /dev/null
+++ b/libavdevice/alsa-audio-common.c
@@ -0,0 +1,188 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ * Copyright (c) 2007 Benoit Fouet ( benoit fouet free fr )
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file alsa-audio-common.c
+ * ALSA input and output: common code
+ * @author Luca Abeni ( lucabe72 email it )
+ * @author Benoit Fouet ( benoit fouet free fr )
+ * @author Nicolas George ( nicolas george normalesup org )
+ */
+
+#include "libavformat/avformat.h"
+#include <alsa/asoundlib.h>
+
+#include "alsa-audio.h"
+
+static snd_pcm_format_t codec_id_to_pcm_format(int codec_id)
+{
+    switch(codec_id) {
+        case CODEC_ID_PCM_S16LE: return SND_PCM_FORMAT_S16_LE;
+        case CODEC_ID_PCM_S16BE: return SND_PCM_FORMAT_S16_BE;
+        case CODEC_ID_PCM_S8:    return SND_PCM_FORMAT_S8;
+        default:                 return SND_PCM_FORMAT_UNKNOWN;
+    }
+}
+
+int ff_alsa_open(AVFormatContext *ctx, int mode, unsigned int *sample_rate,
+                 int channels, int *codec_id)
+{
+    AlsaData *s = ctx->priv_data;
+    const char *audio_device;
+    int res, flags = 0;
+    snd_pcm_format_t format;
+    snd_pcm_t *h;
+    snd_pcm_hw_params_t *hw_params;
+    snd_pcm_uframes_t buffer_size, period_size;
+
+    if (ctx->filename[0] == 0) {
+        audio_device = "default";
+    } else {
+        audio_device = ctx->filename;
+    }
+
+    if (*codec_id == CODEC_ID_NONE)
+        *codec_id = DEFAULT_CODEC_ID;
+    format = codec_id_to_pcm_format(*codec_id);
+    if (format == SND_PCM_FORMAT_UNKNOWN) {
+        av_log(ctx, AV_LOG_ERROR, "sample format 0x%04x is not supported\n", *codec_id);
+        return AVERROR(ENOSYS);
+    }
+    s->frame_size = av_get_bits_per_sample(*codec_id) / 8 * channels;
+
+    if (ctx->flags & AVFMT_FLAG_NONBLOCK) {
+        flags = O_NONBLOCK;
+    }
+    res = snd_pcm_open(&h, audio_device, mode, flags);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot open audio device %s (%s)\n",
+               audio_device, snd_strerror(res));
+        return AVERROR_IO;
+    }
+
+    res = snd_pcm_hw_params_malloc(&hw_params);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot allocate hardware parameter structure (%s)\n",
+               snd_strerror(res));
+        goto fail1;
+    }
+
+    res = snd_pcm_hw_params_any(h, hw_params);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot initialize hardware parameter structure (%s)\n",
+               snd_strerror(res));
+        goto fail;
+    }
+
+    res = snd_pcm_hw_params_set_access(h, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot set access type (%s)\n",
+               snd_strerror(res));
+        goto fail;
+    }
+
+    res = snd_pcm_hw_params_set_format(h, hw_params, format);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot set sample format 0x%04x %d (%s)\n",
+               *codec_id, format, snd_strerror(res));
+        goto fail;
+    }
+
+    res = snd_pcm_hw_params_set_rate_near(h, hw_params, sample_rate, 0);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot set sample rate (%s)\n",
+               snd_strerror(res));
+        goto fail;
+    }
+
+    res = snd_pcm_hw_params_set_channels(h, hw_params, channels);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot set channel count to %d (%s)\n",
+               channels, snd_strerror(res));
+        goto fail;
+    }
+
+    snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
+    /* TODO: maybe use ctx->max_picture_buffer somehow */
+    res = snd_pcm_hw_params_set_buffer_size_near(h, hw_params, &buffer_size);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot set ALSA buffer size (%s)\n",
+               snd_strerror(res));
+        goto fail;
+    }
+
+    snd_pcm_hw_params_get_period_size_min(hw_params, &period_size, NULL);
+    res = snd_pcm_hw_params_set_period_size_near(h, hw_params, &period_size, NULL);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot set ALSA period size (%s)\n",
+               snd_strerror(res));
+        goto fail;
+    }
+    s->period_size = period_size;
+
+    res = snd_pcm_hw_params(h, hw_params);
+    if (res < 0) {
+        av_log(ctx, AV_LOG_ERROR, "cannot set parameters (%s)\n",
+               snd_strerror(res));
+        goto fail;
+    }
+
+    snd_pcm_hw_params_free(hw_params);
+
+    s->h = h;
+    return 0;
+
+fail:
+    snd_pcm_hw_params_free(hw_params);
+fail1:
+    snd_pcm_close(h);
+    return AVERROR_IO;
+}
+
+int ff_alsa_close(AVFormatContext *s1)
+{
+    AlsaData *s = s1->priv_data;
+
+    snd_pcm_close(s->h);
+    return 0;
+}
+
+int ff_alsa_xrun_recover(AVFormatContext *s1, int err)
+{
+    AlsaData *s = s1->priv_data;
+    snd_pcm_t *handle = s->h;
+
+    av_log(s1, AV_LOG_WARNING, "ALSA buffer xrun.\n");
+    if (err == -EPIPE) {
+        err = snd_pcm_prepare(handle);
+        if (err < 0) {
+            av_log(s1, AV_LOG_ERROR, "cannot recover from underrun (snd_pcm_prepare failed: %s)\n", snd_strerror(err));
+
+            return AVERROR_IO;
+        }
+    } else if (err == -ESTRPIPE) {
+        av_log(NULL, AV_LOG_ERROR, "-ESTRPIPE... Unsupported!\n");
+
+        return -1;
+    }
+    return err;
+}
diff --git a/libavdevice/alsa-audio-dec.c b/libavdevice/alsa-audio-dec.c
new file mode 100644
index 0000000..f57ae5a
--- /dev/null
+++ b/libavdevice/alsa-audio-dec.c
@@ -0,0 +1,160 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ * Copyright (c) 2007 Benoit Fouet ( benoit fouet free fr )
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file alsa-audio-dec.c
+ * ALSA input and output: input
+ * @author Luca Abeni ( lucabe72 email it )
+ * @author Benoit Fouet ( benoit fouet free fr )
+ * @author Nicolas George ( nicolas george normalesup org )
+ */
+
+#include "libavformat/avformat.h"
+#include <alsa/asoundlib.h>
+
+#include "alsa-audio.h"
+
+static int audio_read_header(AVFormatContext *s1, AVFormatParameters *ap)
+{
+    AlsaData *s = s1->priv_data;
+    AVStream *st;
+    int ret;
+    unsigned int sample_rate;
+    int codec_id;
+    snd_pcm_sw_params_t *sw_params;
+
+    if (ap->sample_rate <= 0) {
+        av_log(s1, AV_LOG_ERROR, "Bad sample rate %d\n", ap->sample_rate);
+
+        return AVERROR(EIO);
+    }
+
+    if (ap->channels <= 0) {
+        av_log(s1, AV_LOG_ERROR, "Bad channels number %d\n", ap->channels);
+
+        return AVERROR(EIO);
+    }
+
+    st = av_new_stream(s1, 0);
+    if (st == NULL) {
+        av_log(s1, AV_LOG_ERROR, "Cannot add stream\n");
+
+        return AVERROR(ENOMEM);
+    }
+    sample_rate = ap->sample_rate;
+    codec_id = ap->audio_codec_id;
+
+    ret = ff_alsa_open(s1, SND_PCM_STREAM_CAPTURE, &sample_rate, ap->channels,
+        &codec_id);
+    if (ret < 0) {
+        return AVERROR(EIO);
+    }
+
+    ret = snd_pcm_sw_params_malloc(&sw_params);
+    if (ret < 0) {
+        av_log(s1, AV_LOG_ERROR, "cannot allocate software parameters structure (%s)\n",
+               snd_strerror(ret));
+        goto fail;
+    }
+
+    snd_pcm_sw_params_current(s->h, sw_params);
+
+    ret = snd_pcm_sw_params_set_tstamp_mode(s->h, sw_params,
+        SND_PCM_TSTAMP_ENABLE);
+    if (ret < 0) {
+        av_log(s1, AV_LOG_ERROR, "cannot set ALSA timestamp mode (%s)\n",
+               snd_strerror(ret));
+        goto fail;
+    }
+
+    ret = snd_pcm_sw_params(s->h, sw_params);
+    snd_pcm_sw_params_free(sw_params);
+    if (ret < 0) {
+        av_log(s1, AV_LOG_ERROR, "cannot install ALSA software parameters (%s)\n",
+               snd_strerror(ret));
+        goto fail;
+    }
+
+    /* take real parameters */
+    st->codec->codec_type = CODEC_TYPE_AUDIO;
+    st->codec->codec_id = codec_id;
+    st->codec->sample_rate = sample_rate;
+    st->codec->channels = ap->channels;
+
+    av_log(NULL, AV_LOG_INFO, "sample rate: %d\n", sample_rate);
+    av_set_pts_info(st, 64, 1, 1000000);  /* 64 bits pts in us */
+
+    return 0;
+
+fail:
+    snd_pcm_close(s->h);
+    return AVERROR(EIO);
+}
+
+static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt)
+{
+    AlsaData *s = s1->priv_data;
+    AVStream *st = s1->streams[0];
+    int res;
+    snd_htimestamp_t timestamp;
+    snd_pcm_uframes_t ts_delay;
+
+    if (av_new_packet(pkt, s->period_size) < 0) {
+        return AVERROR(EIO);
+    }
+
+    while ((res = snd_pcm_readi(s->h, pkt->data, pkt->size / s->frame_size)) < 0) {
+        if (res == -EAGAIN) {
+            pkt->size = 0;
+            av_free_packet(pkt);
+
+            return AVERROR(EAGAIN);
+        }
+        if (ff_alsa_xrun_recover(s1, res) < 0) {
+            av_log(s1, AV_LOG_ERROR, "Alsa read error: %s\n",
+                   snd_strerror(res));
+            av_free_packet(pkt);
+
+            return AVERROR(EIO);
+        }
+    }
+
+    snd_pcm_htimestamp(s->h, &ts_delay, &timestamp);
+    ts_delay += res;
+    pkt->pts = (int64_t)timestamp.tv_sec * 1000000 + timestamp.tv_nsec / 1000;
+    pkt->pts -= (int64_t)ts_delay * 1000000 / st->codec->sample_rate;
+
+    pkt->size = res * s->frame_size;
+
+    return 0;
+}
+
+AVInputFormat alsa_demuxer = {
+    "alsa",
+    NULL_IF_CONFIG_SMALL("Alsa audio input"),
+    sizeof(AlsaData),
+    NULL,
+    audio_read_header,
+    audio_read_packet,
+    ff_alsa_close,
+    .flags = AVFMT_NOFILE,
+};
diff --git a/libavdevice/alsa-audio-enc.c b/libavdevice/alsa-audio-enc.c
new file mode 100644
index 0000000..67bf238
--- /dev/null
+++ b/libavdevice/alsa-audio-enc.c
@@ -0,0 +1,87 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ * Copyright (c) 2007 Benoit Fouet ( benoit fouet free fr )
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file alsa-audio-enc.c
+ * ALSA input and output: output
+ * @author Luca Abeni ( lucabe72 email it )
+ * @author Benoit Fouet ( benoit fouet free fr )
+ */
+
+#include "libavformat/avformat.h"
+#include <alsa/asoundlib.h>
+
+#include "alsa-audio.h"
+
+static int audio_write_header(AVFormatContext *s1)
+{
+    AVStream *st;
+    unsigned int sample_rate;
+    int codec_id;
+    int res;
+
+    st = s1->streams[0];
+    sample_rate = st->codec->sample_rate;
+    codec_id = st->codec->codec_id;
+    res = ff_alsa_open(s1, SND_PCM_STREAM_PLAYBACK, &sample_rate,
+        st->codec->channels, &codec_id);
+
+    return res;
+}
+
+static int audio_write_packet(AVFormatContext *s1, AVPacket *pkt)
+{
+    AlsaData *s = s1->priv_data;
+    int res;
+    int size= pkt->size;
+    uint8_t *buf= pkt->data;
+
+    while((res = snd_pcm_writei(s->h, buf, size / s->frame_size)) < 0) {
+        if (res == -EAGAIN) {
+
+            return AVERROR(EAGAIN);
+        }
+
+        if (ff_alsa_xrun_recover(s1, res) < 0) {
+            av_log(s1, AV_LOG_ERROR, "Alsa write error: %s\n",
+                   snd_strerror(res));
+
+            return AVERROR(EIO);
+        }
+    }
+
+    return 0;
+}
+
+AVOutputFormat alsa_muxer = {
+    "alsa",
+    NULL_IF_CONFIG_SMALL("Alsa audio output"),
+    "",
+    "",
+    sizeof(AlsaData),
+    DEFAULT_CODEC_ID,
+    CODEC_ID_NONE,
+    audio_write_header,
+    audio_write_packet,
+    ff_alsa_close,
+    .flags = AVFMT_NOFILE,
+};
diff --git a/libavdevice/alsa-audio.h b/libavdevice/alsa-audio.h
new file mode 100644
index 0000000..9547f79
--- /dev/null
+++ b/libavdevice/alsa-audio.h
@@ -0,0 +1,84 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ * Copyright (c) 2007 Benoit Fouet ( benoit fouet free fr )
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file alsa-audio.h
+ * ALSA input and output: definitions and structures
+ * @author Luca Abeni ( lucabe72 email it )
+ * @author Benoit Fouet ( benoit fouet free fr )
+ */
+
+#ifndef AVDEVICE_ALSA_AUDIO_H
+#define AVDEVICE_ALSA_AUDIO_H
+
+/* XXX: we make the assumption that the soundcard accepts this format */
+/* XXX: find better solution with "preinit" method, needed also in
+        other formats */
+#ifdef WORDS_BIGENDIAN
+#define DEFAULT_CODEC_ID CODEC_ID_PCM_S16BE
+#else
+#define DEFAULT_CODEC_ID CODEC_ID_PCM_S16LE
+#endif
+
+typedef struct {
+    snd_pcm_t *h;
+    int frame_size;  ///< preferred size for reads and writes
+    int period_size; ///< bytes per sample * channels
+} AlsaData;
+
+/**
+ * Opens an ALSA PCM.
+ *
+ * @param s media file handle
+ * @param mode either SND_PCM_STREAM_CAPTURE or SND_PCM_STREAM_PLAYBACK
+ * @param sample_rate in: requested sample rate;
+ *                    out: actually selected sample rate
+ * @param channels number of channels
+ * @param codec_id in: requested CodecID or CODEC_ID_NONE;
+ *                 out: actually selected CodecID, changed only if
+ *                 CODEC_ID_NONE was requested
+ *
+ * @return 0 if OK, AVERROR_xxx on error
+ */
+int ff_alsa_open(AVFormatContext *s, int mode, unsigned int *sample_rate,
+                 int channels, int *codec_id);
+
+/**
+ * Closes the ALSA PCM.
+ *
+ * @param s1 media file handle
+ *
+ * @return 0
+ */
+int ff_alsa_close(AVFormatContext *s1);
+
+/**
+ * Tries to recover from ALSA buffer underrun.
+ *
+ * @param s1 media file handle
+ * @param err error code reported by the previous ALSA call
+ *
+ * @return 0 if OK, AVERROR_xxx on error
+ */
+int ff_alsa_xrun_recover(AVFormatContext *s1, int err);
+
+#endif /* AVDEVICE_ALSA_AUDIO_H */
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 197 bytes
Desc: Digital signature
URL: <http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/attachments/20081215/e0eb9589/attachment.pgp>



More information about the ffmpeg-devel mailing list