]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/opus'
authorAndre Noll <maan@systemlinux.org>
Sat, 6 Jul 2013 00:24:48 +0000 (02:24 +0200)
committerAndre Noll <maan@systemlinux.org>
Sat, 6 Jul 2013 00:27:58 +0000 (02:27 +0200)
Was cooking for two weeks with no problems.

273756 The opus decoder.
7007ae The opus audio format handler.
8bcf75 ogg/opus: Infrastructure.
53133e speex: Don't export spx_ctl().

NEWS
afh_common.c
configure.ac
error.h
ogg_afh_common.c
opus_afh.c [new file with mode: 0644]
opus_common.c [new file with mode: 0644]
opus_common.h [new file with mode: 0644]
opusdec_filter.c [new file with mode: 0644]
spx.h
spx_common.c

diff --git a/NEWS b/NEWS
index 866904e32955d84a72ff8ba5d9e811a99de975fb..19c98a4be597bf6d156ce29006f11944b7b7903c 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,7 +1,10 @@
 ------------------------------------------
 0.?.? (to be announced) "spectral gravity"
 ------------------------------------------
+A new audio format, UTF-8 support, and tons of other improvements
+and fixes all over the place.
 
+       - New audio format: ogg/opus.
        - UTF8 support for para_gui and the mp3 audio format handler.
        - Scheduler improvements and fixes.
        - The obsolete gettimeofday() function has been replaced
index 6c161a7c7ea2a30c0abb6a87c91ef5075d5dbf06..5be43550c202b516aaedf332dbbfba341da20dcb 100644 (file)
@@ -31,6 +31,10 @@ void mp3_init(struct audio_format_handler *);
        void flac_afh_init(struct audio_format_handler *);
 #endif
 
+#ifdef HAVE_OPUS
+       void opus_afh_init(struct audio_format_handler *);
+#endif
+
 void wma_afh_init(struct audio_format_handler *);
 
 /** The list of all status items */
@@ -79,6 +83,12 @@ static struct audio_format_handler afl[] = {
                .name = "flac",
 #ifdef HAVE_FLAC
                .init = flac_afh_init,
+#endif
+       },
+       {
+               .name = "opus",
+#ifdef HAVE_OPUS
+               .init = opus_afh_init,
 #endif
        },
        {
index 3a0a36f62f002a9bf19d3d9e66d495fc27b942b0..b3332c670f6eb610afa05f84adca763490e1e4c7 100644 (file)
@@ -629,7 +629,7 @@ if test ${have_core_audio} = yes; then
        default_writer="OSX_WRITE"
        AC_DEFINE(HAVE_CORE_AUDIO, 1, define to 1 on Mac Os X)
 fi
-########################################################### ogg/vorbis/speex
+####################################################### ogg/vorbis/speex/opus
 have_ogg="yes"
 OLD_CPPFLAGS="$CPPFLAGS"
 OLD_LD_FLAGS="$LDFLAGS"
@@ -646,6 +646,10 @@ AC_ARG_WITH(speex_headers, [AS_HELP_STRING(--with-speex-headers=dir,
        [look for speex headers also in dir])])
 AC_ARG_WITH(speex_libs, [AS_HELP_STRING(--with-speex-libs=dir,
        [look for speex libs also in dir])])
+AC_ARG_WITH(opus_headers, [AS_HELP_STRING(--with-opus-headers=dir,
+       [look for opus headers also in dir])])
+AC_ARG_WITH(opus_libs, [AS_HELP_STRING(--with-opus-libs=dir,
+       [look for opus libs also in dir])])
 
 if test -n "$with_ogg_headers"; then
        ogg_cppflags="-I$with_ogg_headers"
@@ -660,6 +664,7 @@ AC_CHECK_LIB([ogg], [ogg_stream_init], [], [ have_ogg="no" ])
 
 have_vorbis="yes"
 have_speex="yes"
+have_opus="yes"
 if test "$have_ogg" = "yes"; then
        # vorbis
        if test -n "$with_vorbis_headers"; then
@@ -684,14 +689,29 @@ if test "$have_ogg" = "yes"; then
        fi
        AC_CHECK_LIB([speex], [speex_decoder_init], [], [ have_speex="no" ])
        AC_CHECK_HEADERS([speex/speex.h], [], [ have_speex="no" ])
+
+       # opus
+       if test -n "$with_opus_headers"; then
+               opus_cppflags="-I$with_opus_headers"
+               CPPFLAGS="$CPPFLAGS $opus_cppflags"
+       fi
+       if test -n "$with_opus_libs"; then
+               speex_libs="-L$with_opus_libs"
+               LDFLAGS="$LDFLAGS $opus_libs"
+       fi
+       AC_CHECK_LIB([opus], [opus_multistream_decode], [], [ have_opus="no" ])
+       AC_CHECK_HEADERS([opus/opus.h], [], [ have_opus="no" ])
 else
-       AC_MSG_WARN([vorbis/speex depend on libogg, which was not detected])
+       AC_MSG_WARN([vorbis/speex/opus depend on libogg, which was not detected])
        have_vorbis="no"
        have_speex="no"
+       have_opus="no"
 fi
 
 msg="support in para_server/para_filter/para_afh"
-if test "$have_vorbis" = "yes" || test "$have_speex" = "yes"; then
+if test "$have_vorbis" = "yes" || \
+               test "$have_speex" = "yes" || \
+               test "$have_opus" = "yes"; then
        AC_SUBST(ogg_cppflags)
        ogg_libs="$ogg_libs -logg"
        if test "$OSTYPE" = "Darwin"; then
@@ -757,6 +777,30 @@ if test "$have_speex" = "yes"; then
 else
        AC_MSG_WARN([no ogg/speex $msg])
 fi
+if test "$have_opus" = "yes"; then
+       all_errlist_objs="$all_errlist_objs opusdec_filter opus_afh opus_common"
+       AC_DEFINE(HAVE_OPUS, 1, define to 1 to turn on ogg/opus support)
+       filters="$filters opusdec"
+       opus_libs="-lopus"
+       server_ldflags="$server_ldflags $opus_libs"
+       filter_ldflags="$filter_ldflags $opus_libs"
+       audiod_ldflags="$audiod_ldflags $opus_libs"
+       afh_ldflags="$afh_ldflags $opus_libs"
+       play_ldflags="$play_ldflags $opus_libs"
+       recv_ldflags="$recv_ldflags $opus_libs"
+
+       server_errlist_objs="$server_errlist_objs opus_afh opus_common"
+       filter_errlist_objs="$filter_errlist_objs opusdec_filter opus_common"
+       audiod_errlist_objs="$audiod_errlist_objs opusdec_filter opus_common"
+       afh_errlist_objs="$afh_errlist_objs opus_afh opus_common"
+       play_errlist_objs="$play_errlist_objs opusdec_filter opus_afh opus_common"
+       recv_errlist_objs="$recv_errlist_objs opus_afh opus_common"
+
+       audiod_audio_formats="$audiod_audio_formats opus"
+       server_audio_formats="$server_audio_formats opus"
+else
+       AC_MSG_WARN([no ogg/opus $msg])
+fi
 CPPFLAGS="$OLD_CPPFLAGS"
 LDFLAGS="$OLD_LDFLAGS"
 LIBS="$OLD_LIBS"
diff --git a/error.h b/error.h
index 41a7fe12180473b65976d456ae228ad8561665b9..6734040bec1a99f7dda774a9d2655cabef4cac18 100644 (file)
--- a/error.h
+++ b/error.h
@@ -41,6 +41,7 @@ extern const char **para_errlist[];
 #define OSS_MIX_ERRORS \
        PARA_ERROR(OSS_MIXER_CHANNEL, "invalid mixer channel"), \
 
+
 #define ALSA_MIX_ERRORS \
        PARA_ERROR(ALSA_MIX_OPEN, "could not open mixer"), \
        PARA_ERROR(ALSA_MIX_BAD_ELEM, "invalid/unsupported control element"), \
@@ -53,6 +54,19 @@ extern const char **para_errlist[];
        PARA_ERROR(LIBSAMPLERATE, "secret rabbit code error"), \
 
 
+#define OPUS_COMMON_ERRORS \
+       PARA_ERROR(OPUS_HEADER, "invalid opus header"), \
+
+
+#define OPUS_AFH_ERRORS \
+       PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
+
+
+#define OPUSDEC_FILTER_ERRORS \
+       PARA_ERROR(CREATE_OPUS_DECODER, "could not create opus decoder"), \
+       PARA_ERROR(OPUS_SET_GAIN, "opus: could not set gain"), \
+       PARA_ERROR(OPUS_DECODE, "opus decode error"), \
+
 #define SIDEBAND_ERRORS \
        PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \
        PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
index 9cdb8092afb312abfa3bc4d710c08cfbde529c9b..ac70eb3f0fc543a316f636c7a589cfaa60018c5c 100644 (file)
@@ -4,7 +4,7 @@
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
-/** \file ogg_afh_common.c Functions common to ogg/vorbis and ogg/speex. */
+/** \file ogg_afh_common.c Functions common to all ogg/ codecs. */
 
 #include <ogg/ogg.h>
 #include <regex.h>
diff --git a/opus_afh.c b/opus_afh.c
new file mode 100644 (file)
index 0000000..b079bcd
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012-2013 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file opus_afh.c Audio format handler for ogg/opus files. */
+
+#include <ogg/ogg.h>
+#include <regex.h>
+
+#include "para.h"
+#include "afh.h"
+#include "error.h"
+#include "portable_io.h"
+#include "string.h"
+#include "opus_common.h"
+#include "ogg_afh_common.h"
+
+static const char* opus_suffixes[] = {"opus", NULL};
+
+static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
+               char **p)
+{
+       char *q = key_value_copy(tag, taglen, type);
+       if (!q)
+               return false;
+       free(*p);
+       *p = q;
+       return true;
+}
+
+static int opus_get_comments(char *comments, int length,
+               struct taginfo *tags)
+{
+       char *p = comments, *end = comments + length;
+       int i;
+       uint32_t val, ntags;
+
+       /* min size of a opus header is 16 bytes */
+       if (length < 16)
+               return -E_OPUS_COMMENT;
+       if (memcmp(p, "OpusTags", 8) != 0)
+               return -E_OPUS_COMMENT;
+       p += 8;
+       val = read_u32(p);
+       p += 4;
+       if (p + val > end)
+               return -E_OPUS_COMMENT;
+       tags->comment = safe_strdup(p, val);
+       p += val;
+       ntags = read_u32(p);
+       p += 4;
+       if (p + ntags * 4 > end)
+               return -E_OPUS_COMMENT;
+       PARA_INFO_LOG("found %d tag(s)\n", ntags);
+       for (i = 0; i < ntags; i++, p += val) {
+               char *tag;
+
+               if (p + 4 > end)
+                       return -E_OPUS_COMMENT;
+               val = read_u32(p);
+               p += 4;
+               if (p + val > end)
+                       return -E_OPUS_COMMENT;
+               if (copy_if_tag_type(p, val, "author", &tags->artist))
+                       continue;
+               if (copy_if_tag_type(p, val, "artist", &tags->artist))
+                       continue;
+               if (copy_if_tag_type(p, val, "title", &tags->title))
+                       continue;
+               if (copy_if_tag_type(p, val, "album", &tags->album))
+                       continue;
+               if (copy_if_tag_type(p, val, "year", &tags->year))
+                       continue;
+               if (copy_if_tag_type(p, val, "comment", &tags->comment))
+                       continue;
+               tag = safe_strdup(p, val);
+               PARA_NOTICE_LOG("unrecognized tag: %s\n", tag);
+               free(tag);
+       }
+       return 1;
+}
+
+static int opus_packet_callback(ogg_packet *packet, int packet_num,
+               __a_unused int serial, struct afh_info *afhi,
+               void *private_data)
+{
+       int ret;
+       struct opus_header *oh = private_data;
+
+       if (packet_num == 0) {
+               ret = opus_parse_header((char *)packet->packet, packet->bytes, oh);
+               if (ret < 0)
+                       return ret;
+               afhi->channels = oh->channels;
+               afhi->techinfo = make_message("header version %d, input sample rate: %dHz",
+                       oh->version, oh->input_sample_rate);
+               /*
+                * The input sample rate is irrelevant for afhi->frequency as
+                * we always decode to 48kHz.
+                */
+               afhi->frequency = 48000;
+               return 1;
+       }
+       if (packet_num == 1) {
+               ret = opus_get_comments((char *)packet->packet, packet->bytes,
+                       &afhi->tags);
+               if (ret < 0)
+                       return ret;
+               return 0; /* header complete */
+       }
+       /* never reached */
+       assert(0);
+}
+
+static int opus_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+               struct afh_info *afhi)
+{
+       int ret, ms;
+       struct opus_header oh = {.version = 0};
+
+       struct ogg_afh_callback_info opus_callback_info = {
+               .packet_callback = opus_packet_callback,
+               .private_data = &oh,
+       };
+       ret = ogg_get_file_info(map, numbytes, afhi, &opus_callback_info);
+       if (ret < 0)
+               return ret;
+       ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */
+       ms = tv2ms(&afhi->chunk_tv) * afhi->chunks_total;
+       afhi->bitrate = ret / ms;
+       return 1;
+}
+
+/**
+ * The init function of the ogg/opus audio format handler.
+ *
+ * \param afh Pointer to the struct to initialize.
+ */
+void opus_afh_init(struct audio_format_handler *afh)
+{
+       afh->get_file_info = opus_get_file_info,
+       afh->suffixes = opus_suffixes;
+}
diff --git a/opus_common.c b/opus_common.c
new file mode 100644 (file)
index 0000000..927df1f
--- /dev/null
@@ -0,0 +1,169 @@
+/* Copyright (C)2012 Xiph.Org Foundation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file opus_common.c Common functions of the opus decoder and audio format
+ * handler.
+ */
+
+#include <ogg/ogg.h>
+
+#include "para.h"
+#include "error.h"
+#include "opus_common.h"
+#include "portable_io.h"
+
+struct packet {
+       const char *data;
+       int maxlen;
+       int pos;
+};
+
+static int read_chars(struct packet *p, unsigned char *str, int nb_chars)
+{
+       int i;
+
+       if (p->pos > p->maxlen - nb_chars)
+               return 0;
+       for (i = 0; i < nb_chars; i++)
+               str[i] = p->data[p->pos++];
+       return 1;
+}
+
+static int read_uint32(struct packet *p, ogg_uint32_t *val)
+{
+       if (p->pos > p->maxlen - 4)
+               return 0;
+       *val = read_u32(p->data + p->pos);
+       p->pos += 4;
+       return 1;
+}
+
+static int read_uint16(struct packet *p, ogg_uint16_t *val)
+{
+       if (p->pos > p->maxlen - 2)
+               return 0;
+       *val = read_u16(p->data + p->pos);
+       p->pos += 2;
+       return 1;
+}
+
+/**
+ * Get metadata of an opus stream.
+ *
+ * This is called from both the audio format handler (which passes ogg packet
+ * 0) and from the decoder.
+ *
+ * \param packet Start of the packet.
+ * \param len Number of bytes.
+ * \param h Result.
+ *
+ * \return Standard.
+ */
+int opus_parse_header(const char *packet, int len, struct opus_header *h)
+{
+       int i;
+       char str[9];
+       struct packet p;
+       unsigned char ch, channel_mapping;
+       ogg_uint16_t shortval;
+
+       p.data = packet;
+       p.maxlen = len;
+       p.pos = 0;
+       str[8] = 0;
+       if (len < 19)
+               return -E_OPUS_HEADER;
+       read_chars(&p, (unsigned char*)str, 8);
+       if (memcmp(str, "OpusHead", 8) != 0)
+               return -E_OPUS_HEADER;
+
+       if (!read_chars(&p, &ch, 1))
+               return -E_OPUS_HEADER;
+       h->version = ch;
+       if((h->version & 240) != 0) /* Only major version 0 supported. */
+               return -E_OPUS_HEADER;
+
+       if (!read_chars(&p, &ch, 1))
+               return -E_OPUS_HEADER;
+       h->channels = ch;
+       if (h->channels == 0)
+               return -E_OPUS_HEADER;
+
+       if (!read_uint16(&p, &shortval))
+               return -E_OPUS_HEADER;
+       h->preskip = shortval;
+
+       if (!read_uint32(&p, &h->input_sample_rate))
+               return -E_OPUS_HEADER;
+
+       if (!read_uint16(&p, &shortval))
+               return -E_OPUS_HEADER;
+       h->gain = (short)shortval;
+
+       if (!read_chars(&p, &ch, 1))
+               return -E_OPUS_HEADER;
+       channel_mapping = ch;
+
+       if (channel_mapping != 0) {
+               if (!read_chars(&p, &ch, 1))
+                       return -E_OPUS_HEADER;
+
+               if (ch < 1)
+                       return -E_OPUS_HEADER;
+               h->nb_streams = ch;
+
+               if (!read_chars(&p, &ch, 1))
+                       return -E_OPUS_HEADER;
+
+               if (ch > h->nb_streams || (ch + h->nb_streams) > 255)
+                       return -E_OPUS_HEADER;
+               h->nb_coupled = ch;
+
+               /* Multi-stream support */
+               for (i = 0; i < h->channels; i++) {
+                       if (!read_chars(&p, &h->stream_map[i], 1))
+                               return -E_OPUS_HEADER;
+                       if (h->stream_map[i] > (h->nb_streams + h->nb_coupled)
+                                       && h->stream_map[i] != 255)
+                               return -E_OPUS_HEADER;
+               }
+       } else {
+               if (h->channels > 2)
+                       return -E_OPUS_HEADER;
+               h->nb_streams = 1;
+               h->nb_coupled = h->channels > 1;
+               h->stream_map[0] = 0;
+               h->stream_map[1] = 1;
+       }
+       /*
+        * For version 0/1 we know there won't be any more data so reject any
+        * that have data past the end.
+        */
+       if ((h->version == 0 || h->version == 1) && p.pos != len)
+               return -E_OPUS_HEADER;
+       return 1;
+}
diff --git a/opus_common.h b/opus_common.h
new file mode 100644 (file)
index 0000000..71923f1
--- /dev/null
@@ -0,0 +1,21 @@
+/** Various bits stored in the header of an opus stream. */
+struct opus_header {
+       /** lower 4 bits of the version byte, must be 0. */
+       int version;
+       /** 1..255 */
+       int channels;
+       /** Number of bytes to skip from the beginning. */
+       int preskip;
+       /** Sample rate of the input stream, used by the audio format handler. */
+       ogg_uint32_t input_sample_rate;
+       /** In dB, should be zero whenever possible. */
+       int gain;
+       /** Number of logical streams (usually 1). */
+       int nb_streams;
+       /** Number of streams to decode as 2 channel streams. */
+       int nb_coupled;
+       /** Mapping from coded channels to output channels. */
+       unsigned char stream_map[255];
+};
+
+int opus_parse_header(const char *packet, int len, struct opus_header *h);
diff --git a/opusdec_filter.c b/opusdec_filter.c
new file mode 100644 (file)
index 0000000..482108a
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2002-2007 Jean-Marc Valin
+ * Copyright (c) 2008 CSIRO
+ * Copyright (c) 2007-2012 Xiph.Org Foundation
+ * Copyright (C) 2012-2013 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file opusdec_filter.c The ogg/opus decoder. */
+
+/* This file is based on opusdec.c, by Jean-Marc Valin, see below. */
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <regex.h>
+#include <opus/opus.h>
+#include <opus/opus_multistream.h>
+#include <ogg/ogg.h>
+#include <math.h>
+
+#include "para.h"
+#include "list.h"
+#include "sched.h"
+#include "ggo.h"
+#include "buffer_tree.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+#include "opus_common.h"
+
+/** 120ms at 48000 */
+#define MAX_FRAME_SIZE (960*6)
+
+struct opusdec_context {
+       OpusMSDecoder *st;
+       opus_int64 packet_count;
+       int total_links;
+       bool stream_init;
+       ogg_sync_state oy;
+       ogg_stream_state os;
+       ogg_page ogg_page;
+       bool eos;
+       int channels;
+       int preskip;
+       bool have_opus_stream;
+       ogg_int32_t opus_serialno;
+};
+
+static int opusdec_execute(struct btr_node *btrn, const char *cmd,
+               char **result)
+{
+       struct filter_node *fn = btr_context(btrn);
+       struct opusdec_context *ctx = fn->private_data;
+
+       return decoder_execute(cmd, 48000, ctx->channels, result);
+}
+
+static void opusdec_open(struct filter_node *fn)
+{
+       struct opusdec_context *ctx = para_calloc(sizeof(*ctx));
+
+       ogg_sync_init(&ctx->oy);
+       fn->private_data = ctx;
+}
+
+static void opusdec_close(struct filter_node *fn)
+{
+       struct opusdec_context *ctx = fn->private_data;
+
+       if (ctx->st) {
+               opus_multistream_decoder_destroy(ctx->st);
+               if (ctx->stream_init)
+                       ogg_stream_clear(&ctx->os);
+               ogg_sync_clear(&ctx->oy);
+       }
+       free(ctx);
+       fn->private_data = NULL;
+}
+
+/* Process an Opus header and setup the opus decoder based on it. */
+static int opusdec_init(ogg_packet *op, struct opusdec_context *ctx)
+{
+       int ret;
+       struct opus_header header;
+
+       ctx->st = NULL;
+       ret = opus_parse_header((char *)op->packet, op->bytes, &header);
+       if (ret < 0)
+               return ret;
+       PARA_INFO_LOG("detected header v%d\n", header.version);
+       ctx->channels = header.channels;
+       ctx->preskip = header.preskip;
+       ctx->st = opus_multistream_decoder_create(48000, header.channels,
+               header.nb_streams, header.nb_coupled, header.stream_map, &ret);
+       if (ret != OPUS_OK || !ctx->st) {
+               PARA_ERROR_LOG("%s\n", opus_strerror(ret));
+               return -E_CREATE_OPUS_DECODER;
+       }
+       if (header.gain != 0) {
+               ret = opus_multistream_decoder_ctl(ctx->st,
+                       OPUS_SET_GAIN(header.gain));
+               if (ret != OPUS_OK) {
+                       PARA_ERROR_LOG("%s\n", opus_strerror(ret));
+                       return -E_OPUS_SET_GAIN;
+               }
+               PARA_INFO_LOG("playback gain: %fdB\n", header.gain / 256.);
+       }
+       PARA_INFO_LOG("%d channel(s), 48KHz\n", ctx->channels);
+       return 1;
+}
+
+static void opusdec_add_output(short *pcm, int frames_available,
+               struct btr_node *btrn, struct opusdec_context *ctx)
+{
+       int tmp_skip, num_frames, bytes;
+
+       tmp_skip = PARA_MIN(ctx->preskip, frames_available);
+       ctx->preskip -= tmp_skip;
+       num_frames = frames_available - tmp_skip;
+       if (num_frames <= 0)
+               return;
+       bytes = sizeof(short) * num_frames * ctx->channels;
+
+       if (tmp_skip > 0) {
+               short *in = pcm + ctx->channels * tmp_skip;
+               short *out = para_malloc(bytes);
+               memcpy(out, in, bytes);
+               free(pcm);
+               pcm = out;
+       }
+       btr_add_output((char *)pcm, bytes, btrn);
+}
+
+/* returns > 1 if packet was decoded, 0 if it was ignored, negative on errors. */
+static int decode_packet(struct opusdec_context *ctx, ogg_packet *op,
+               struct btr_node *btrn)
+{
+       int ret;
+       short *output;
+
+       /*
+        * OggOpus streams are identified by a magic string in the initial
+        * stream header.
+        */
+       if (op->b_o_s && op->bytes >= 8 && !memcmp(op->packet, "OpusHead", 8)) {
+               if (!ctx->have_opus_stream) {
+                       ctx->opus_serialno = ctx->os.serialno;
+                       ctx->have_opus_stream = true;
+                       ctx->packet_count = 0;
+                       ctx->eos = false;
+                       ctx->total_links++;
+               } else
+                       PARA_NOTICE_LOG("ignoring opus stream %llu\n",
+                               (long long unsigned)ctx->os.serialno);
+       }
+       if (!ctx->have_opus_stream || ctx->os.serialno != ctx->opus_serialno)
+               return 0;
+       /* If first packet in a logical stream, process the Opus header. */
+       if (ctx->packet_count == 0)
+               return opusdec_init(op, ctx);
+       if (ctx->packet_count == 1)
+               return 1;
+       /* don't care for anything except opus eos */
+       if (op->e_o_s && ctx->os.serialno == ctx->opus_serialno)
+               ctx->eos = true;
+       output = para_malloc(sizeof(short) * MAX_FRAME_SIZE * ctx->channels);
+       ret = opus_multistream_decode(ctx->st, (unsigned char *)op->packet,
+               op->bytes, output, MAX_FRAME_SIZE, 0);
+       if (ret < 0) {
+               PARA_ERROR_LOG("%s\n", opus_strerror(ret));
+               free(output);
+               return -E_OPUS_DECODE;
+       }
+       opusdec_add_output(output, ret, btrn, ctx);
+       return 1;
+}
+
+static void opusdec_pre_select(struct sched *s, struct task *t)
+{
+       struct filter_node *fn = container_of(t, struct filter_node, task);
+       struct btr_node *btrn = fn->btrn;
+       int ns;
+
+       ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+       if (ns != 0)
+               return sched_min_delay(s);
+       sched_request_timeout_ms(100, s);
+}
+
+static int opusdec_post_select(__a_unused struct sched *s, struct task *t)
+{
+       struct filter_node *fn = container_of(t, struct filter_node, task);
+       struct opusdec_context *ctx = fn->private_data;
+       struct btr_node *btrn = fn->btrn;
+       int ret;
+       char *btr_buf, *data;
+       size_t nbytes;
+       ogg_packet op;
+
+       ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+       if (ret <= 0)
+               goto out;
+       btr_merge(btrn, fn->min_iqs);
+       nbytes = btr_next_buffer(btrn, &btr_buf);
+       nbytes = PARA_MIN(nbytes, (size_t)32768);
+       ret = 0;
+       if (nbytes == 0)
+               goto out;
+       data = ogg_sync_buffer(&ctx->oy, nbytes);
+       memcpy(data, btr_buf, nbytes);
+       btr_consume(btrn, nbytes);
+       ogg_sync_wrote(&ctx->oy, nbytes);
+       for (;;) { /* loop over all ogg pages we got */
+               ret = 0;
+               if (ogg_sync_pageout(&ctx->oy, &ctx->ogg_page) != 1)
+                       goto out;
+               if (!ctx->stream_init) {
+                       ogg_stream_init(&ctx->os, ogg_page_serialno(&ctx->ogg_page));
+                       ctx->stream_init = true;
+               }
+               if (ogg_page_serialno(&ctx->ogg_page) != ctx->os.serialno)
+                       ogg_stream_reset_serialno(&ctx->os,
+                               ogg_page_serialno(&ctx->ogg_page));
+               /* Add page to the bitstream */
+               ogg_stream_pagein(&ctx->os, &ctx->ogg_page);
+               for (;;) { /* loop over all opus packets */
+                       ret = ogg_stream_packetout(&ctx->os, &op);
+                       if (ret != 1)
+                               break;
+                       ret = decode_packet(ctx, &op, btrn);
+                       if (ret < 0)
+                               goto out;
+                       ctx->packet_count++;
+                       if (ctx->eos)
+                               ctx->have_opus_stream = false;
+               }
+       }
+out:
+       if (ret < 0)
+               btr_remove_node(&fn->btrn);
+       return ret;
+}
+
+/**
+ * The init function of the opusdec filter.
+ *
+ * \param f Pointer to the filter struct to initialize.
+ *
+ * \sa filter::init.
+ */
+void opusdec_filter_init(struct filter *f)
+{
+       f->open = opusdec_open;
+       f->close = opusdec_close;
+       f->pre_select = generic_filter_pre_select;
+       f->pre_select = opusdec_pre_select;
+       f->post_select = opusdec_post_select;
+       f->execute = opusdec_execute;
+}
diff --git a/spx.h b/spx.h
index 6bcf17747556e6e071913683499c81828fed073d..c4999f07a6a32c2229afa8187c13871c64b3a7f8 100644 (file)
--- a/spx.h
+++ b/spx.h
@@ -37,4 +37,3 @@ struct spx_header_info {
 
 int spx_process_header(unsigned char *packet, long bytes,
                struct spx_header_info *shi);
-int spx_ctl(void *state, int request, void *ptr);
index 3a4d5dd1a5fa7a13811c16cd59fbb510b8447adc..3bd3d48d2b9fbc70fe8c8283a3ab47421ef1c463 100644 (file)
@@ -60,7 +60,7 @@
  *
  * \return Standard.
  */
-int spx_ctl(void *state, int request, void *ptr)
+static int spx_ctl(void *state, int request, void *ptr)
 {
        int ret = speex_decoder_ctl(state, request, ptr);