[FFmpeg-devel] [PATCH 4/4] lavc: Opus decoder using libopus.

Nicolas George nicolas.george at normalesup.org
Wed Jul 4 13:57:07 CEST 2012


Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
---
 configure                |    5 ++
 libavcodec/Makefile      |    1 +
 libavcodec/allcodecs.c   |    1 +
 libavcodec/libopus_dec.c |  175 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 182 insertions(+)
 create mode 100644 libavcodec/libopus_dec.c


Note: this decoder suffers from the same preroll problem Dale wrote about
yesterday, it is part of the design and the API, and the preroll value is
stored in the header. I had started implementing a custom way of dealing
with it, but I disabled it when I read Dale's mail until a more generic
solution is found.


diff --git a/configure b/configure
index cb8a04d..c3361a6 100755
--- a/configure
+++ b/configure
@@ -189,6 +189,7 @@ External library support:
   --enable-libopencore-amrwb enable AMR-WB decoding via libopencore-amrwb [no]
   --enable-libopencv       enable video filtering via libopencv [no]
   --enable-libopenjpeg     enable JPEG 2000 encoding/decoding via OpenJPEG [no]
+  --enable-libopus         enable Opus decoding via libopus [no]
   --enable-libpulse        enable Pulseaudio input via libpulse [no]
   --enable-librtmp         enable RTMP[E] support via librtmp [no]
   --enable-libschroedinger enable Dirac support via libschroedinger [no]
@@ -1063,6 +1064,7 @@ CONFIG_LIST="
     libopencore_amrwb
     libopencv
     libopenjpeg
+    libopus
     libpulse
     librtmp
     libschroedinger
@@ -1618,6 +1620,7 @@ libopencore_amrnb_encoder_deps="libopencore_amrnb"
 libopencore_amrwb_decoder_deps="libopencore_amrwb"
 libopenjpeg_decoder_deps="libopenjpeg"
 libopenjpeg_encoder_deps="libopenjpeg"
+libopus_decoder_deps="libopus"
 libschroedinger_decoder_deps="libschroedinger"
 libschroedinger_encoder_deps="libschroedinger"
 libspeex_decoder_deps="libspeex"
@@ -3302,6 +3305,7 @@ enabled libopencore_amrnb  && require libopencore_amrnb opencore-amrnb/interf_de
 enabled libopencore_amrwb  && require libopencore_amrwb opencore-amrwb/dec_if.h D_IF_init -lopencore-amrwb
 enabled libopencv  && require_pkg_config opencv opencv/cxcore.h cvCreateImageHeader
 enabled libopenjpeg && require libopenjpeg openjpeg.h opj_version -lopenjpeg
+enabled libopus    && require_pkg_config opus opus_multistream.h opus_multistream_decoder_create
 enabled libpulse && require_pkg_config libpulse-simple pulse/simple.h pa_simple_new
 enabled librtmp    && require_pkg_config librtmp librtmp/rtmp.h RTMP_Socket
 enabled libschroedinger && require_pkg_config schroedinger-1.0 schroedinger/schro.h schro_init
@@ -3677,6 +3681,7 @@ echo "libopencore-amrnb support ${libopencore_amrnb-no}"
 echo "libopencore-amrwb support ${libopencore_amrwb-no}"
 echo "libopencv support         ${libopencv-no}"
 echo "libopenjpeg enabled       ${libopenjpeg-no}"
+echo "libopus enabled           ${libopus-no}"
 echo "libpulse enabled          ${libpulse-no}"
 echo "librtmp enabled           ${librtmp-no}"
 echo "libschroedinger enabled   ${libschroedinger-no}"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index a3891c2..2f5ddb5 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -676,6 +676,7 @@ OBJS-$(CONFIG_LIBOPENCORE_AMRNB_ENCODER)  += libopencore-amr.o \
 OBJS-$(CONFIG_LIBOPENCORE_AMRWB_DECODER)  += libopencore-amr.o
 OBJS-$(CONFIG_LIBOPENJPEG_DECODER)        += libopenjpegdec.o
 OBJS-$(CONFIG_LIBOPENJPEG_ENCODER)        += libopenjpegenc.o
+OBJS-$(CONFIG_LIBOPUS_DECODER)            += libopus_dec.o
 OBJS-$(CONFIG_LIBSCHROEDINGER_DECODER)    += libschroedingerdec.o \
                                              libschroedinger.o
 OBJS-$(CONFIG_LIBSCHROEDINGER_ENCODER)    += libschroedingerenc.o \
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index e7c1103..b7d8974 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -421,6 +421,7 @@ void avcodec_register_all(void)
     REGISTER_ENCDEC  (LIBOPENCORE_AMRNB, libopencore_amrnb);
     REGISTER_DECODER (LIBOPENCORE_AMRWB, libopencore_amrwb);
     REGISTER_ENCDEC  (LIBOPENJPEG, libopenjpeg);
+    REGISTER_DECODER (LIBOPUS, libopus);
     REGISTER_ENCDEC  (LIBSCHROEDINGER, libschroedinger);
     REGISTER_ENCDEC  (LIBSPEEX, libspeex);
     REGISTER_DECODER (LIBSTAGEFRIGHT_H264, libstagefright_h264);
diff --git a/libavcodec/libopus_dec.c b/libavcodec/libopus_dec.c
new file mode 100644
index 0000000..afe2e7f
--- /dev/null
+++ b/libavcodec/libopus_dec.c
@@ -0,0 +1,175 @@
+/*
+ * Opus decoder using libopus
+ * Copyright (c) 2012 Nicolas George
+ *
+ * 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
+ */
+
+#include <opus.h>
+#include <opus_multistream.h>
+#include "avcodec.h"
+#include "vorbis.h"
+#include "libavutil/intreadwrite.h"
+
+struct libopus_context {
+    OpusMSDecoder *dec;
+    AVFrame frame;
+};
+
+/* 8000-16000 are oficially supported but seem to be bogus */
+static const int sample_rates[] = { /*8000, 12000, 16000,*/ 24000, 48000 };
+
+static int ff_opus_error_to_averror(int err)
+{
+    switch (err) {
+        case OPUS_BAD_ARG:          return AVERROR(EINVAL);
+        case OPUS_BUFFER_TOO_SMALL: return AVERROR_BUFFER_TOO_SMALL;
+        case OPUS_INTERNAL_ERROR:   return AVERROR(EFAULT);
+        case OPUS_INVALID_PACKET:   return AVERROR_INVALIDDATA;
+        case OPUS_UNIMPLEMENTED:    return AVERROR(ENOSYS);
+        case OPUS_INVALID_STATE:    return AVERROR_EXTERNAL;
+        case OPUS_ALLOC_FAIL:       return AVERROR(ENOMEM);
+        default:                    return AVERROR(EINVAL);
+    }
+}
+
+static inline void reorder(uint8_t *data, unsigned channels, unsigned bps,
+                           unsigned samples, const uint8_t *map)
+{
+    uint8_t tmp[8 * 4];
+    unsigned i;
+
+    for (; samples > 0; samples--) {
+        for (i = 0; i < channels; i++)
+            memcpy(tmp + bps * i, data + bps * map[i], bps);
+        memcpy(data, tmp, bps * channels);
+        data += bps * channels;
+    }
+}
+
+static av_cold int libopus_dec_init(AVCodecContext *avc)
+{
+    struct libopus_context *opus = avc->priv_data;
+    int err, rate_idx;
+    int nb_streams, nb_coupled;
+    uint8_t mapping_stereo[] = { 0, 1 }, *mapping;
+
+    rate_idx = FF_ARRAY_ELEMS(sample_rates) - 1;
+    if (avc->sample_rate) {
+        while (rate_idx > 0 && sample_rates[rate_idx - 1] >= avc->sample_rate)
+            rate_idx--;
+        if (avc->sample_rate != sample_rates[rate_idx])
+            av_log(avc, AV_LOG_WARNING, "Selected %d Hz instead of %d Hz.\n",
+                   sample_rates[rate_idx], avc->sample_rate);
+    }
+    avc->sample_rate = sample_rates[rate_idx];
+    avc->sample_fmt = avc->request_sample_fmt == AV_SAMPLE_FMT_FLT ?
+                      AV_SAMPLE_FMT_FLT : AV_SAMPLE_FMT_S16;
+    avc->channel_layout = avc->channels > 8 ? 0 :
+                          ff_vorbis_channel_layouts[avc->channels - 1];
+
+    if (avc->extradata_size >= 2 + avc->channels) {
+        nb_streams = avc->extradata[0];
+        nb_coupled = avc->extradata[1];
+        if (nb_streams + nb_coupled != avc->channels)
+            av_log(avc, AV_LOG_WARNING, "Inconsistent channel mapping.\n");
+        mapping = avc->extradata + 2;
+    } else {
+        if (avc->channels > 2) {
+            av_log(avc, AV_LOG_ERROR,
+                   "No channel mapping for %d channels.\n", avc->channels);
+            return AVERROR(EINVAL);
+        }
+        nb_streams = 1;
+        nb_coupled = avc->channels > 1;
+        mapping = mapping_stereo;
+    }
+
+    opus->dec = opus_multistream_decoder_create(
+        avc->sample_rate, avc->channels,
+        nb_streams, nb_coupled, mapping, &err);
+    if (!opus->dec) {
+        av_log(avc, AV_LOG_ERROR, "Unable to create decoder: %s\n",
+               opus_strerror(err));
+        return ff_opus_error_to_averror(err);
+    }
+    avcodec_get_frame_defaults(&opus->frame);
+    avc->coded_frame = &opus->frame;
+    return 0;
+}
+
+static av_cold int libopus_dec_close(AVCodecContext *avc)
+{
+    struct libopus_context *opus = avc->priv_data;
+
+    opus_multistream_decoder_destroy(opus->dec);
+    return 0;
+}
+
+#define MAX_FRAME_SIZE (960*6)
+
+static int libopus_dec_decode(AVCodecContext *avc, void *frame,
+                              int *got_frame_ptr, AVPacket *pkt)
+{
+    struct libopus_context *opus = avc->priv_data;
+    int ret;
+
+    opus->frame.nb_samples = MAX_FRAME_SIZE;
+    ret = avc->get_buffer(avc, &opus->frame);
+    if (ret < 0) {
+        av_log(avc, AV_LOG_ERROR, "get_buffer() failed\n");
+        return ret;
+    }
+
+    ret = avc->sample_fmt == AV_SAMPLE_FMT_S16 ?
+          opus_multistream_decode      (opus->dec, pkt->data, pkt->size,
+                                        (void *)opus->frame.data[0],
+                                        opus->frame.nb_samples, 0) :
+          opus_multistream_decode_float(opus->dec, pkt->data, pkt->size,
+                                        (void *)opus->frame.data[0],
+                                        opus->frame.nb_samples, 0);
+    if (ret < 0) {
+        av_log(avc, AV_LOG_ERROR, "Decoding error: %s\n", opus_strerror(ret));
+        return ff_opus_error_to_averror(ret);
+    }
+
+    if (avc->channels > 3 && avc->channels <= 8) {
+        const uint8_t *m = ff_vorbis_channel_layout_offsets[avc->channels - 1];
+        if (avc->sample_fmt == AV_SAMPLE_FMT_S16)
+            reorder(opus->frame.data[0], avc->channels, 2, ret, m);
+        else
+            reorder(opus->frame.data[0], avc->channels, 4, ret, m);
+    }
+
+    /* TODO discard */
+    opus->frame.nb_samples = ret;
+    *(AVFrame *)frame = opus->frame;
+    *got_frame_ptr = 1;
+    return ret;
+}
+
+AVCodec ff_libopus_decoder = {
+    .name           = "libopus",
+    .type           = AVMEDIA_TYPE_AUDIO,
+    .id             = CODEC_ID_OPUS,
+    .priv_data_size = sizeof(struct libopus_context),
+    .init           = libopus_dec_init,
+    .close          = libopus_dec_close,
+    .decode         = libopus_dec_decode,
+    .capabilities   = CODEC_CAP_DR1,
+    .long_name      = NULL_IF_CONFIG_SMALL("Opus decoder using libopus"),
+};
-- 
1.7.10



More information about the ffmpeg-devel mailing list