From 9ec266a5b67b221d828edc8d4e526460d973859f Mon Sep 17 00:00:00 2001
From: haxar <grezski@gmail.com>
Date: Tue, 22 May 2012 14:47:37 -0700
Subject: [PATCH] Xiph/IETF Opus decoder using libopus.

---
 configure                  |    5 ++
 libavcodec/Makefile        |    1 +
 libavcodec/allcodecs.c     |    1 +
 libavcodec/avcodec.h       |    1 +
 libavcodec/libopusdec.c    |  129 ++++++++++++++++++++++++++++++++++++++++++++
 libavformat/Makefile       |    1 +
 libavformat/oggdec.c       |    1 +
 libavformat/oggdec.h       |    1 +
 libavformat/oggparseopus.c |  103 +++++++++++++++++++++++++++++++++++
 9 files changed, 243 insertions(+)
 create mode 100644 libavcodec/libopusdec.c
 create mode 100644 libavformat/oggparseopus.c

diff --git a/configure b/configure
index e070c0b..4df63ad 100755
--- a/configure
+++ b/configure
@@ -188,6 +188,7 @@ External library support:
   --enable-libnut          enable NUT (de)muxing via libnut,
                            native (de)muxer exists [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]
@@ -1046,6 +1047,7 @@ CONFIG_LIST="
     libopencore_amrwb
     libopencv
     libopenjpeg
+    libopus
     libpulse
     librtmp
     libschroedinger
@@ -1571,6 +1573,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"
@@ -3219,6 +3222,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 libopus opus/opus.h opus_decode -lopus
 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
@@ -3588,6 +3592,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 0a3b351..12ca3f5 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -660,6 +660,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)            += libopusdec.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 ac05b9b..4ae000b 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -413,6 +413,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/avcodec.h b/libavcodec/avcodec.h
index 9dfb64b..4ebf94b 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -400,6 +400,7 @@ enum CodecID {
     CODEC_ID_AAC_LATM,
     CODEC_ID_QDMC,
     CODEC_ID_CELT,
+    CODEC_ID_OPUS,
     CODEC_ID_G723_1,
     CODEC_ID_G729,
     CODEC_ID_8SVX_EXP,
diff --git a/libavcodec/libopusdec.c b/libavcodec/libopusdec.c
new file mode 100644
index 0000000..428583d
--- /dev/null
+++ b/libavcodec/libopusdec.c
@@ -0,0 +1,129 @@
+/*
+ * Xiph/IETF Opus decoder using libopus
+ * Copyright (c) 2011 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/opus.h>
+#include "avcodec.h"
+#include "libavutil/intreadwrite.h"
+
+struct libopus_context {
+    OpusDecoder *dec;
+    AVFrame frame;
+    uint16_t preskip;
+};
+
+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(ENOBUFS);
+        case OPUS_INTERNAL_ERROR:   return AVERROR(EFAULT);
+        case OPUS_INVALID_PACKET:   return AVERROR_INVALIDDATA;
+        case OPUS_UNIMPLEMENTED:    return AVERROR(ENOSYS);
+#ifdef ENOTRECOVERABLE
+        case OPUS_INVALID_STATE:    return AVERROR(ENOTRECOVERABLE);
+#endif
+        case OPUS_ALLOC_FAIL:       return AVERROR(ENOMEM);
+        default:                    return AVERROR(EINVAL);
+    }
+}
+
+static av_cold int libopus_dec_init(AVCodecContext *c)
+{
+    struct libopus_context *opus = c->priv_data;
+    int err;
+
+    if (!c->channels || !c->frame_size ||
+        c->frame_size > INT_MAX / sizeof(int16_t) / c->channels)
+        return AVERROR(EINVAL);
+    opus->dec = opus_decoder_create(48000, c->channels, &err);
+    av_log(c, AV_LOG_DEBUG,
+            "opus_decoder_create: dec=%p channels=%d err=%d\n",
+            opus->dec, c->channels, err);
+    if (!opus->dec)
+        return ff_opus_error_to_averror(err);
+    if (c->extradata_size >= 2) {
+        opus->preskip = AV_RL16(c->extradata);
+        if (opus->preskip >= c->frame_size) {
+            av_log(c, AV_LOG_WARNING,
+                    "Invalid preskip (%d), ignored.\n", opus->preskip);
+            opus->preskip = 0;
+        }
+    }
+    avcodec_get_frame_defaults(&opus->frame);
+    c->coded_frame = &opus->frame;
+    return 0;
+}
+
+static av_cold int libopus_dec_close(AVCodecContext *c)
+{
+    struct libopus_context *opus = c->priv_data;
+
+    opus_decoder_destroy(opus->dec);
+    return 0;
+}
+
+static int libopus_dec_decode(AVCodecContext *c, void *frame,
+                              int *got_frame_ptr, AVPacket *pkt)
+{
+    struct libopus_context *opus = c->priv_data;
+    int16_t *pcm;
+    int ret;
+
+    opus->frame.nb_samples = c->frame_size;
+    ret = c->get_buffer(c, &opus->frame);
+    if (ret < 0) {
+        av_log(c, AV_LOG_ERROR, "get_buffer() failed\n");
+        return ret;
+    }
+    pcm = (int16_t *)opus->frame.data[0];
+    ret = opus_decode(opus->dec, pkt->data, pkt->size, pcm, c->frame_size, 0);
+    av_log(c, AV_LOG_DEBUG,
+            "opus_decode: dec=%p data=%p size=%d pcm=%p frame_size=%d ret=%d\n",
+            opus->dec, pkt->data, pkt->size, pcm, c->frame_size, ret);
+    if (ret < 0)
+        return ff_opus_error_to_averror(ret);
+    opus->frame.nb_samples = ret;
+    av_log(c, AV_LOG_DEBUG,
+            "preskip=%d nb_samples=%d\n", opus->preskip, opus->frame.nb_samples);
+    if (opus->preskip) {
+        if (opus->preskip < opus->frame.nb_samples) {
+            opus->frame.nb_samples -= opus->preskip;
+            memmove(pcm, pcm + opus->preskip * c->channels,
+                    opus->frame.nb_samples * c->channels * sizeof(int16_t));
+        }
+        opus->preskip = 0;
+    }
+    *got_frame_ptr = 1;
+    *(AVFrame *)frame = opus->frame;
+    return pkt->size;
+}
+
+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("libopus Xiph/IETF Opus"),
+};
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 18a0e19..e5e08ee 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -197,6 +197,7 @@ OBJS-$(CONFIG_OGG_DEMUXER)               += oggdec.o         \
                                             oggparsedirac.o  \
                                             oggparseflac.o   \
                                             oggparseogm.o    \
+                                            oggparseopus.o   \
                                             oggparseskeleton.o \
                                             oggparsespeex.o  \
                                             oggparsetheora.o \
diff --git a/libavformat/oggdec.c b/libavformat/oggdec.c
index 6f3a565..b781e0f 100644
--- a/libavformat/oggdec.c
+++ b/libavformat/oggdec.c
@@ -46,6 +46,7 @@ static const struct ogg_codec * const ogg_codecs[] = {
     &ff_theora_codec,
     &ff_flac_codec,
     &ff_celt_codec,
+    &ff_opus_codec,
     &ff_old_dirac_codec,
     &ff_old_flac_codec,
     &ff_ogm_video_codec,
diff --git a/libavformat/oggdec.h b/libavformat/oggdec.h
index 7f5452f..aa94db5 100644
--- a/libavformat/oggdec.h
+++ b/libavformat/oggdec.h
@@ -108,6 +108,7 @@ extern const struct ogg_codec ff_ogm_text_codec;
 extern const struct ogg_codec ff_ogm_video_codec;
 extern const struct ogg_codec ff_old_dirac_codec;
 extern const struct ogg_codec ff_old_flac_codec;
+extern const struct ogg_codec ff_opus_codec;
 extern const struct ogg_codec ff_skeleton_codec;
 extern const struct ogg_codec ff_speex_codec;
 extern const struct ogg_codec ff_theora_codec;
diff --git a/libavformat/oggparseopus.c b/libavformat/oggparseopus.c
new file mode 100644
index 0000000..5d8779b
--- /dev/null
+++ b/libavformat/oggparseopus.c
@@ -0,0 +1,103 @@
+/*
+ * Xiph/IETF Opus parser for Ogg
+ * Copyright (c) 2011 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 <string.h>
+
+#include "libavutil/intreadwrite.h"
+#include "avformat.h"
+#include "internal.h"
+#include "oggdec.h"
+
+struct oggopus_private {
+    int extra_headers_left;
+};
+
+static int opus_header(AVFormatContext *s, int idx)
+{
+    struct ogg *ogg = s->priv_data;
+    struct ogg_stream *os = ogg->streams + idx;
+    AVStream *st = s->streams[idx];
+    struct oggopus_private *priv = os->private;
+    uint8_t *p = os->buf + os->pstart;
+
+    if (os->psize == 19 &&
+        !memcmp(p, ff_opus_codec.magic, ff_opus_codec.magicsize)) {
+        /* Main header */
+
+        uint8_t version, nb_channels, channel_map;
+        uint16_t preskip, output_gain;
+        uint32_t sample_rate;
+        uint8_t *extradata;
+        int extradata_size = sizeof(preskip) + sizeof(output_gain) + sizeof(version);
+
+        extradata = av_malloc(extradata_size + FF_INPUT_BUFFER_PADDING_SIZE);
+        priv = av_malloc(sizeof(struct oggopus_private));
+        if (!extradata || !priv) {
+            av_free(extradata);
+            av_free(priv);
+            return AVERROR(ENOMEM);
+        }
+        version     = AV_RL8(p + 8);
+        nb_channels = AV_RL8(p + 9);
+        preskip     = AV_RL16(p + 10);
+        sample_rate = AV_RL32(p + 12);
+        output_gain = AV_RL16(p + 16);
+        channel_map = AV_RL8(p + 18);
+        st->codec->codec_type     = AVMEDIA_TYPE_AUDIO;
+        st->codec->codec_id       = CODEC_ID_OPUS;
+        st->codec->sample_fmt     = AV_SAMPLE_FMT_S16;
+        st->codec->sample_rate    = 48000;
+        st->codec->channels       = nb_channels;
+        /* allocate with maximum frame size (120ms) */
+        st->codec->frame_size     = 960 * 6;
+        av_free(st->codec->extradata);
+        st->codec->extradata      = extradata;
+        st->codec->extradata_size = extradata_size;
+        avpriv_set_pts_info(st, 64, 1, 48000);
+        priv->extra_headers_left  = 1;
+        av_free(os->private);
+        os->private = priv;
+        AV_WL16(extradata + 0, preskip);
+        AV_WL16(extradata + 2, output_gain);
+        AV_WL8(extradata + 3, version);
+        av_log(s, AV_LOG_DEBUG,
+                "opus_header: OpusHead: version=%hhu nb_channels=%hhu preskip=%hu "
+                "sample_rate=%u output_gain=%hu channel_map=%hhu\n",
+                version, nb_channels, preskip, sample_rate, output_gain,
+                channel_map);
+        return 1;
+    } else if (priv && priv->extra_headers_left &&
+        !memcmp(p, "OpusTags", 8)) {
+        /* Extra headers (vorbiscomment) */
+
+        ff_vorbis_comment(s, &st->metadata, p + 8, os->psize - 8);
+        priv->extra_headers_left--;
+        av_log(s, AV_LOG_DEBUG, "opus_header: OpusTags\n");
+        return 1;
+    }
+    return 0;
+}
+
+const struct ogg_codec ff_opus_codec = {
+    .magic     = "OpusHead",
+    .magicsize = 8,
+    .header    = opus_header,
+};
-- 
1.7.10

