Patchwork lavc: Opus decoder using libopus.

login
register
mail settings
Submitter Nicolas George
Date July 20, 2012, 9:28 a.m.
Message ID <20120720092859.98FEE14C0A4@albiero.videolan.org>
Download mbox | patch
Permalink /patch/24558/
State New
Headers show

Comments

Nicolas George - July 20, 2012, 9:28 a.m.
ffmpeg | branch: master | Nicolas George <nicolas.george@normalesup.org> | Sun Jun 24 11:39:05 2012 +0200| [a6cf296bd90ae4a8097f1681f86bfa42b32fe074] | committer: Nicolas George

lavc: Opus decoder using libopus.

> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=a6cf296bd90ae4a8097f1681f86bfa42b32fe074
---

 Changelog                |    1 +
 configure                |    5 ++
 libavcodec/Makefile      |    1 +
 libavcodec/allcodecs.c   |    1 +
 libavcodec/libopus_dec.c |  221 ++++++++++++++++++++++++++++++++++++++++++++++
 libavcodec/version.h     |    2 +-
 6 files changed, 230 insertions(+), 1 deletion(-)

Patch

diff --git a/Changelog b/Changelog
index 4242cea..df43e19 100644
--- a/Changelog
+++ b/Changelog
@@ -32,6 +32,7 @@  version next:
 - 3GPP Timed Text decoder
 - GeoTIFF decoder support
 - ffmpeg -(no)stdin option
+- Opus decoder using libopus
 
 
 version 0.11:
diff --git a/configure b/configure
index 300f128..bc46bb7 100755
--- a/configure
+++ b/configure
@@ -191,6 +191,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 de/encoding 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 de/encoding via libschroedinger [no]
@@ -1066,6 +1067,7 @@  CONFIG_LIST="
     libopencore_amrwb
     libopencv
     libopenjpeg
+    libopus
     libpulse
     librtmp
     libschroedinger
@@ -1635,6 +1637,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"
@@ -3321,6 +3324,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
@@ -3699,6 +3703,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 45dee19..0da7f52 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -684,6 +684,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 vorbis_data.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 3b18f57..bc37907 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -427,6 +427,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..01e8c00
--- /dev/null
+++ b/libavcodec/libopus_dec.c
@@ -0,0 +1,221 @@ 
+/*
+ * 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 "internal.h"
+#include "vorbis.h"
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+
+struct libopus_context {
+    OpusMSDecoder *dec;
+    AVFrame frame;
+    int pre_skip;
+#ifndef OPUS_SET_GAIN
+    union { int i; double d; } gain;
+#endif
+};
+
+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;
+
+    av_assert1(channels * bps <= sizeof(tmp));
+    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;
+    }
+}
+
+#define OPUS_HEAD_SIZE 19
+
+static av_cold int libopus_dec_init(AVCodecContext *avc)
+{
+    struct libopus_context *opus = avc->priv_data;
+    int ret, channel_map = 0, gain_db = 0, nb_streams, nb_coupled;
+    uint8_t mapping_stereo[] = { 0, 1 }, *mapping;
+
+    avc->sample_rate = 48000;
+    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 >= OPUS_HEAD_SIZE) {
+        opus->pre_skip = AV_RL16(avc->extradata + 10);
+        gain_db        = AV_RL16(avc->extradata + 16);
+        channel_map    = AV_RL8 (avc->extradata + 18);
+        gain_db -= (gain_db & 0x8000) << 1; /* signed */
+    }
+    if (avc->extradata_size >= OPUS_HEAD_SIZE + 2 + avc->channels) {
+        nb_streams = avc->extradata[OPUS_HEAD_SIZE + 0];
+        nb_coupled = avc->extradata[OPUS_HEAD_SIZE + 1];
+        if (nb_streams + nb_coupled != avc->channels)
+            av_log(avc, AV_LOG_WARNING, "Inconsistent channel mapping.\n");
+        mapping = avc->extradata + OPUS_HEAD_SIZE + 2;
+    } else {
+        if (avc->channels > 2 || channel_map) {
+            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, &ret);
+    if (!opus->dec) {
+        av_log(avc, AV_LOG_ERROR, "Unable to create decoder: %s\n",
+               opus_strerror(ret));
+        return ff_opus_error_to_averror(ret);
+    }
+
+#ifdef OPUS_SET_GAIN
+    ret = opus_multistream_decoder_ctl(opus->dec, OPUS_SET_GAIN(gain_db));
+    if (ret != OPUS_OK)
+        av_log(avc, AV_LOG_WARNING, "Failed to set gain: %s\n",
+               opus_strerror(ret));
+#else
+    {
+        double gain_lin = pow(10, gain_db / (20.0 * 256));
+        if (avc->sample_fmt == AV_SAMPLE_FMT_FLT)
+            opus->gain.d = gain_lin;
+        else
+            opus->gain.i = FFMIN(gain_lin * 65536, INT_MAX);
+    }
+#endif
+
+    avc->internal->skip_samples = opus->pre_skip;
+    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, nb_samples;
+
+    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;
+    }
+
+    nb_samples = 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 (nb_samples < 0) {
+        av_log(avc, AV_LOG_ERROR, "Decoding error: %s\n",
+               opus_strerror(nb_samples));
+        return ff_opus_error_to_averror(nb_samples);
+    }
+
+    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, nb_samples, m);
+        else
+            reorder(opus->frame.data[0], avc->channels, 4, nb_samples, m);
+    }
+
+#ifndef OPUS_SET_GAIN
+    {
+        int i = avc->channels * nb_samples;
+        if (avc->sample_fmt == AV_SAMPLE_FMT_FLT) {
+            float *pcm = (float *)opus->frame.data[0];
+            for (; i > 0; i--, pcm++)
+                *pcm = av_clipf(*pcm * opus->gain.d, -1, 1);
+        } else {
+            int16_t *pcm = (int16_t *)opus->frame.data[0];
+            for (; i > 0; i--, pcm++)
+                *pcm = av_clip_int16(((int64_t)opus->gain.i * *pcm) >> 16);
+        }
+    }
+#endif
+
+    opus->frame.nb_samples = nb_samples;
+    *(AVFrame *)frame = opus->frame;
+    *got_frame_ptr = 1;
+    return pkt->size;
+}
+
+static void libopus_dec_flush(AVCodecContext *avc)
+{
+    struct libopus_context *opus = avc->priv_data;
+
+    opus_multistream_decoder_ctl(opus->dec, OPUS_RESET_STATE);
+    /* The stream can have been extracted by a tool that is not Opus-aware.
+       Therefore, any packet can become the first of the stream. */
+    avc->internal->skip_samples = opus->pre_skip;
+}
+
+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,
+    .flush          = libopus_dec_flush,
+    .capabilities   = CODEC_CAP_DR1,
+    .long_name      = NULL_IF_CONFIG_SMALL("libopus Opus"),
+};
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 7538125..2f829171 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -27,7 +27,7 @@ 
  */
 
 #define LIBAVCODEC_VERSION_MAJOR 54
-#define LIBAVCODEC_VERSION_MINOR  41
+#define LIBAVCODEC_VERSION_MINOR  42
 #define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \