[FFmpeg-devel] [PATCH] ALSA for libavdevice

Nicolas George nicolas.george
Tue Dec 9 19:17:07 CET 2008


Hi.

The attached patch is the ALSA code from Luca Abeni (Roundup issue 247),
updated for the current code base and somewhat reworked. It works on my
system.

To record and then playback;

ffmpeg -f alsa -i default foo.wav
ffmpeg -i foo.wav -f alsa default

Note to Luca and anyone who have read his code: the biggest change is the
period / buffer size handling: instead of a fixed-size period, I used the
smallest possible, aiming for low latency. I also moved the codec-format
mapping a bit.

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..6b03300 100644
--- a/libavdevice/Makefile
+++ b/libavdevice/Makefile
@@ -20,6 +20,9 @@ OBJS-$(CONFIG_X11_GRAB_DEVICE_DEMUXER)   += x11grab.o
 # external libraries
 OBJS-$(CONFIG_LIBDC1394_DEMUXER)         += libdc1394.o
 
+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_AUDIO_BEOS_DEMUXER)        += beosaudio.o
 OBJS-$(CONFIG_AUDIO_BEOS_MUXER)          += beosaudio.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..ff1453c
--- /dev/null
+++ b/libavdevice/alsa-audio-common.c
@@ -0,0 +1,188 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ *
+ * 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 Nicolas George ( nicolas george normalesup org )
+ */ 
+
+#include "libavformat/avformat.h"
+#include <alsa/asoundlib.h>
+
+#include "alsa-audio.h"
+
+static snd_pcm_format_t alsa_codec_id(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)
+{
+    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 (s->codec_id == CODEC_ID_NONE)
+        s->codec_id = DEFAULT_CODEC_ID;
+    format = alsa_codec_id(s->codec_id);
+    if (format == SND_PCM_FORMAT_UNKNOWN) {
+        av_log(NULL, AV_LOG_ERROR, "sample format %x is not supported\n", s->codec_id);
+        return AVERROR(ENOSYS);
+    }
+    s->frame_size = av_get_bits_per_sample(s->codec_id) / 8 * s->channels;
+
+    if (ctx->flags & AVFMT_FLAG_NONBLOCK) {
+        flags = O_NONBLOCK;
+    }
+    res = snd_pcm_open(&h, audio_device, mode, flags);
+    if (res < 0) {
+        av_log(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, AV_LOG_ERROR, "cannot set sample format %d %d (%s)\n",
+                         s->codec_id, format, snd_strerror(res));
+        goto fail;
+    }
+
+    res = snd_pcm_hw_params_set_rate_near(h, hw_params, &s->sample_rate, 0);
+    if (res < 0) {
+        av_log(NULL, AV_LOG_ERROR, "cannot set sample rate (%s)\n",
+                         snd_strerror(res));
+        goto fail;
+    }
+
+    res = snd_pcm_hw_params_set_channels(h, hw_params, s->channels);
+    if (res < 0) {
+        av_log(NULL, AV_LOG_ERROR, "cannot set channel count to %d (%s)\n",
+                         s->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(NULL, 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(NULL, 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(NULL, 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(AlsaData *s)
+{
+    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, "XRUN!!!\n");
+    if (err == -EPIPE) {
+        err = snd_pcm_prepare(handle);
+        if (err < 0) {
+            av_log(NULL, AV_LOG_ERROR, "cannot recover from underrun (snd_pcm_prepare failed: %s)\n", snd_strerror(err));
+
+            return 0;
+        }
+    } else if (err == -ESTRPIPE) {
+        av_log(NULL, AV_LOG_ERROR, "-ESTPIPE... 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..1f019e6
--- /dev/null
+++ b/libavdevice/alsa-audio-dec.c
@@ -0,0 +1,120 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ *
+ * 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 )
+ */ 
+
+#include "libavformat/avformat.h"
+#include <alsa/asoundlib.h>
+
+#include "alsa-audio.h"
+
+static int audio_read_close(AVFormatContext *s1)
+{
+    AlsaData *s = s1->priv_data;
+
+    ff_alsa_close(s);
+    return 0;
+}
+
+static int audio_read_header(AVFormatContext *s1, AVFormatParameters *ap)
+{
+    AlsaData *s = s1->priv_data;
+    AVStream *st;
+    int ret;
+
+    if (ap->sample_rate <= 0 || ap->channels <= 0) {
+        av_log(s1, AV_LOG_ERROR, "Bad sample rate %d or channels number %d\n",
+                   ap->sample_rate, 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);
+    }
+    s->sample_rate = ap->sample_rate;
+    s->channels = ap->channels;
+    s->codec_id = ap->audio_codec_id;
+
+    ret = ff_alsa_open(s1, SND_PCM_STREAM_CAPTURE);
+    if (ret < 0) {
+        av_free(st);
+
+        return AVERROR(EIO);
+    }
+
+    /* take real parameters */
+    st->codec->codec_type = CODEC_TYPE_AUDIO;
+    st->codec->codec_id = s->codec_id;
+    st->codec->sample_rate = s->sample_rate;
+    st->codec->channels = s->channels;
+
+    av_set_pts_info(st, 64, 1, 1000000);  /* 64 bits pts in us */
+
+    return 0;
+}
+
+static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt)
+{
+    AlsaData *s = s1->priv_data;
+    int res;
+
+    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);
+        }
+    }
+    pkt->size = res * s->frame_size;
+    pkt->pts = av_gettime();   /* FIXME: We might need something better... */
+
+    return 0;
+}
+
+AVInputFormat alsa_demuxer = {
+    "alsa",
+    "Alsa audio input",
+    sizeof(AlsaData),
+    NULL,
+    audio_read_header,
+    audio_read_packet,
+    audio_read_close,
+    .flags = AVFMT_NOFILE,
+};
diff --git a/libavdevice/alsa-audio-enc.c b/libavdevice/alsa-audio-enc.c
new file mode 100644
index 0000000..f398a9f
--- /dev/null
+++ b/libavdevice/alsa-audio-enc.c
@@ -0,0 +1,96 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ *
+ * 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 )
+ */ 
+
+#include "libavformat/avformat.h"
+#include <alsa/asoundlib.h>
+
+#include "alsa-audio.h"
+
+static int audio_write_header(AVFormatContext *s1)
+{
+    AlsaData *s = s1->priv_data;
+    AVStream *st;
+    int res;
+
+    st = s1->streams[0];
+    s->sample_rate = st->codec->sample_rate;
+    s->channels = st->codec->channels;
+    if (!ff_alsa_supported_codec_id(st->codec->codec_id)) {
+        av_log(NULL, AV_LOG_ERROR, "sample format %x is not supported\n", st->codec->codec_id);
+        return AVERROR(ENOSYS);
+    }
+    s->codec_id = st->codec->codec_id;
+    res = ff_alsa_open(s1, SND_PCM_STREAM_PLAYBACK);
+
+    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;
+}
+
+static int audio_write_trailer(AVFormatContext *s1)
+{
+    AlsaData *s = s1->priv_data;
+
+    ff_alsa_close(s);
+    return 0;
+}
+
+AVOutputFormat alsa_muxer = {
+    "alsa",
+    "Alsa audio output",
+    "",
+    "",
+    sizeof(AlsaData),
+    DEFAULT_CODEC_ID,
+    CODEC_ID_NONE,
+    audio_write_header,
+    audio_write_packet,
+    audio_write_trailer,
+    .flags = AVFMT_NOFILE,
+};
diff --git a/libavdevice/alsa-audio.h b/libavdevice/alsa-audio.h
new file mode 100644
index 0000000..283ddfa
--- /dev/null
+++ b/libavdevice/alsa-audio.h
@@ -0,0 +1,53 @@
+/*
+ * ALSA input and output
+ * Copyright (c) 2007 Luca Abeni ( lucabe72 email it )
+ *
+ * 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 )
+ */ 
+
+#ifndef ALSA_AUDIO_H
+#define 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;
+    unsigned int sample_rate;
+    int channels;
+    int frame_size; /* preferred size for reads and writes */
+    int period_size; /* bytes per sample * channels */
+    int codec_id;
+} AlsaData;
+
+extern int ff_alsa_open(AVFormatContext *s, int mode);
+extern int ff_alsa_close(AlsaData *s);
+extern int ff_alsa_xrun_recover(AVFormatContext *s1, int err);
+
+#endif /* 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/20081209/8d648578/attachment.pgp>



More information about the ffmpeg-devel mailing list