Add support for the speex codec.
authorAndre Noll <maan@systemlinux.org>
Tue, 22 Jun 2010 22:53:46 +0000 (00:53 +0200)
committerAndre Noll <maan@systemlinux.org>
Fri, 16 Jul 2010 08:41:19 +0000 (10:41 +0200)
This patch adds support for yet another audio format: speex, a
CELP-based codec designed for speech.

As speex is usually used in combination with the OGG container format,
we only support this combination. The new OGG/Speex audio format
handler provides a callback structure for the generic ogg code,
very similar to the OGG/Vorbis audio format handler.

The new speex code is split over three source files: spx_afh.c contains
the audio format handler, spxdec_filter.c the decoder and spx_common.c
common functions used by both audio format handler and decoder.

Many thanks to Jean-Marc Valin, the author of the reference
implementation of the speex decoder. Reusing parts of his code made
it easy to support speex within paraslash.

CREDITS
FEATURES
Makefile.in
afh_common.c
configure.ac
error.h
server.c
spx.h [new file with mode: 0644]
spx_afh.c [new file with mode: 0644]
spx_common.c [new file with mode: 0644]
spxdec_filter.c [new file with mode: 0644]

diff --git a/CREDITS b/CREDITS
index 15313f9658db8f883712c3454e0c0068b46cadf6..1d8ad0ce3867b6ce1a56f30936fef10a15392a01 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -62,3 +62,5 @@ Cedric Tefft <cedric@earthling.net> (mp3info)
 
 Linus Torvalds <torvalds@osdl.org> (for giving us one hell of an
 operating system [quote taken from README.linux for DOOM v1.666])
+
+Jean-Marc Valin (speex)
index dc7f8537af6078c4c0e737f61728a7d8b8c907e3..5b5f4699d5123abc5919aaf45369bd804dfefde5 100644 (file)
--- a/FEATURES
+++ b/FEATURES
@@ -5,7 +5,7 @@ Features
 
        * Runs on Linux, Mac OS, FreeBSD, NetBSD, Solaris and probably other
          Unixes
-       * Mp3, ogg vorbis, aac (m4a) and wma support
+       * Mp3, ogg/vorbis, ogg/speex, aac (m4a) and wma support
        * Local or remote http, dccp, and udp network audio streaming
        * IPv6 support
        * Forward error correction allows receivers to recover from packet losses
index e457e490858eea0fc4bbee90b0088347a53c2a52..1a0c1fa34a3e7cecf0c5ee9cc77b34c4cda29d9f 100644 (file)
@@ -153,17 +153,29 @@ $(object_dir):
 $(man_dir):
        mkdir -p $@
 
+$(object_dir)/spx_common.o: spx_common.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
+
+$(object_dir)/spxdec_filter.o: spxdec_filter.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
+
+$(object_dir)/spx_afh.o: spx_afh.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
+
 $(object_dir)/oggdec_filter.o: oggdec_filter.c | $(object_dir)
        @[ -z "$(Q)" ] || echo 'CC $<'
-       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
 
 $(object_dir)/ogg_afh.o: ogg_afh.c | $(object_dir)
        @[ -z "$(Q)" ] || echo 'CC $<'
-       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
 
 $(object_dir)/ogg_afh_common.o: ogg_afh_common.c | $(object_dir)
        @[ -z "$(Q)" ] || echo 'CC $<'
-       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
 
 $(object_dir)/mp3dec_filter.o: mp3dec_filter.c | $(object_dir)
        @[ -z "$(Q)" ] || echo 'CC $<'
index 498f492142d9fe14ff562dc33c8c7e2acb4cc2db..4464987a61fc5172e2601cded9aac0998c9cfda2 100644 (file)
@@ -26,6 +26,9 @@ void mp3_init(struct audio_format_handler *);
 #ifdef HAVE_FAAD
        void aac_afh_init(struct audio_format_handler *);
 #endif
+#ifdef HAVE_SPEEX
+       void spx_afh_init(struct audio_format_handler *);
+#endif
 
 void wma_afh_init(struct audio_format_handler *);
 /**
@@ -61,6 +64,12 @@ static struct audio_format_handler afl[] = {
                .name = "wma",
                .init = wma_afh_init,
        },
+       {
+               .name = "spx",
+#ifdef HAVE_SPEEX
+               .init = spx_afh_init,
+#endif
+       },
        {
                .name = NULL,
        }
index 49fb098753f6946c2cd17296144e7e82f56b595b..5322e7d1be6a12b5230d15599fc6b323e00e3a0d 100644 (file)
@@ -423,49 +423,88 @@ 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
+########################################################### ogg/vorbis/speex
 have_ogg="yes"
+have_vorbis="yes"
+have_speex="yes"
 OLD_CPPFLAGS="$CPPFLAGS"
 OLD_LD_FLAGS="$LDFLAGS"
 OLD_LIBS="$LIBS"
 AC_ARG_WITH(oggvorbis_headers, [AC_HELP_STRING(--with-oggvorbis-headers=dir,
-       [look for vorbis/codec.h also in dir])])
+       [look for ogg/vorbis/speex headers also in dir])])
 if test -n "$with_oggvorbis_headers"; then
-       oggvorbis_cppflags="-I$with_oggvorbis_headers"
-       CPPFLAGS="$CPPFLAGS $oggvorbis_cppflags"
+       ogg_cppflags="-I$with_oggvorbis_headers"
+       CPPFLAGS="$CPPFLAGS $ogg_cppflags"
 fi
 AC_ARG_WITH(oggvorbis_libs, [AC_HELP_STRING(--with-oggvorbis-libs=dir,
-       [look for oggvorbis libs also in dir])])
+       [look for ogg/vorbis/speex libs also in dir])])
 if test -n "$with_oggvorbis_libs"; then
-       oggvorbis_libs="-L$with_oggvorbis_libs"
-       LDFLAGS="$LDFLAGS $oggvorbis_libs"
+       ogg_libs="-L$with_oggvorbis_libs"
+       LDFLAGS="$LDFLAGS $ogg_libs"
 fi
 
 AC_CHECK_LIB([ogg], [ogg_stream_init], [], [ have_ogg="no" ])
-AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [ have_ogg="no" ])
-AC_CHECK_HEADERS([ogg/ogg.h vorbis/codec.h], [], [ have_ogg="no" ])
-if test "$have_ogg" = "yes"; then
-       all_errlist_objs="$all_errlist_objs oggdec_filter ogg_afh ogg_afh_common"
-       AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg vorbis support)
-       filters="$filters oggdec"
+AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [ have_vorbis="no" ])
+AC_CHECK_LIB([speex], [speex_decoder_init], [], [ have_speex="no" ])
+AC_CHECK_HEADERS([ogg/ogg.h], [], [ have_ogg="no"; ])
+AC_CHECK_HEADERS([vorbis/codec.h], [], [ have_vorbis="no" ])
+AC_CHECK_HEADERS([speex/speex.h], [], [ have_speex="no" ])
+msg="support in para_server/para_filter/para_afh"
+if test "$have_ogg" = "yes" && { test "$have_vorbis" = "yes" || test "$have_speex" = "yes"; }; then
+       AC_SUBST(ogg_cppflags)
+       ogg_libs="$ogg_libs -logg"
        if test "$OSTYPE" = "Darwin"; then
-               oggvorbis_libs="-Wl,-bind_at_load  $oggvorbis_libs"
+               ogg_libs="-Wl,-bind_at_load $ogg_libs $ogg_libs"
+       fi
+       server_ldflags="$server_ldflags $ogg_libs"
+       filter_ldflags="$filter_ldflags $ogg_libs"
+       audiod_ldflags="$audiod_ldflags $ogg_libs"
+       all_errlist_objs="$all_errlist_objs ogg_afh_common"
+       afh_ldflags="$afh_ldflags $ogg_libs"
+       afh_errlist_objs="$afh_errlist_objs ogg_afh_common"
+       server_errlist_objs="$server_errlist_objs ogg_afh_common"
+       if test "$have_vorbis" = "yes"; then
+               all_errlist_objs="$all_errlist_objs oggdec_filter ogg_afh"
+               AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg/vorbis support)
+               filters="$filters oggdec"
+               vorbis_libs="-lvorbis -lvorbisfile"
+               server_ldflags="$server_ldflags $vorbis_libs"
+               filter_ldflags="$filter_ldflags $vorbis_libs"
+               audiod_ldflags="$audiod_ldflags $vorbis_libs"
+               afh_ldflags="$afh_ldflags $vorbis_libs"
+
+               server_errlist_objs="$server_errlist_objs ogg_afh"
+               filter_errlist_objs="$filter_errlist_objs oggdec_filter"
+               audiod_errlist_objs="$audiod_errlist_objs oggdec_filter"
+               afh_errlist_objs="$afh_errlist_objs ogg_afh"
+
+               audiod_audio_formats="$audiod_audio_formats ogg"
+               server_audio_formats="$server_audio_formats ogg"
+       else
+               AC_MSG_WARN([no ogg/vorbis $msg])
+       fi
+       if test "$have_speex" = "yes"; then
+               all_errlist_objs="$all_errlist_objs spxdec_filter spx_afh spx_common"
+               AC_DEFINE(HAVE_SPEEX, 1, define to 1 to turn on ogg/speex support)
+               filters="$filters spxdec"
+               speex_libs="-lspeex"
+               server_ldflags="$server_ldflags $speex_libs"
+               filter_ldflags="$filter_ldflags $speex_libs"
+               audiod_ldflags="$audiod_ldflags $speex_libs"
+               afh_ldflags="$afh_ldflags $speex_libs"
+
+               server_errlist_objs="$server_errlist_objs spx_afh spx_common"
+               filter_errlist_objs="$filter_errlist_objs spxdec_filter spx_common"
+               audiod_errlist_objs="$audiod_errlist_objs spxdec_filter spx_common"
+               afh_errlist_objs="$afh_errlist_objs spx_afh spx_common"
+
+               audiod_audio_formats="$audiod_audio_formats spx"
+               server_audio_formats="$server_audio_formats spx"
+       else
+               AC_MSG_WARN([no ogg/speex $msg])
        fi
-       server_ldflags="$server_ldflags $oggvorbis_libs -logg -lvorbis -lvorbisfile"
-       filter_ldflags="$filter_ldflags $oggvorbis_libs -lvorbis -lvorbisfile"
-       audiod_ldflags="$audiod_ldflags $oggvorbis_libs -lvorbis -lvorbisfile"
-       afh_ldflags="$afh_ldflags $oggvorbis_libs -logg -lvorbis -lvorbisfile"
-
-       server_errlist_objs="$server_errlist_objs ogg_afh ogg_afh_common"
-       filter_errlist_objs="$filter_errlist_objs oggdec_filter"
-       audiod_errlist_objs="$audiod_errlist_objs oggdec_filter"
-       afh_errlist_objs="$afh_errlist_objs ogg_afh ogg_afh_common"
-
-       audiod_audio_formats="$audiod_audio_formats ogg"
-       server_audio_formats="$server_audio_formats ogg"
-       AC_SUBST(oggvorbis_cppflags)
 else
-       AC_MSG_WARN([no ogg vorbis support in para_server/para_filter])
+       AC_MSG_WARN([no ogg/vorbis ogg/speex $msg])
 fi
 CPPFLAGS="$OLD_CPPFLAGS"
 LDFLAGS="$OLD_LDFLAGS"
diff --git a/error.h b/error.h
index 5b1ce07726d619f32276ab750443aa3db81059e9..5db5e1749a5d915dc3f55f956c423399613da7bf 100644 (file)
--- a/error.h
+++ b/error.h
@@ -46,6 +46,23 @@ extern const char **para_errlist[];
        PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \
 
 
+#define SPX_AFH_ERRORS \
+       PARA_ERROR(SPX_COMMENT, "invalid speex comment"), \
+
+
+#define SPX_COMMON_ERRORS \
+       PARA_ERROR(SPX_HEADER, "can not read speex header"), \
+       PARA_ERROR(SPX_HEADER_MODE, "invalid speex mode in header"), \
+       PARA_ERROR(SPX_VERSION, "incompatible speex bit stream version"), \
+       PARA_ERROR(SPX_DECODER_INIT, "speex decoder initialization failed"), \
+       PARA_ERROR(SPX_CTL_BAD_RQ, "speex_decoder_ctl: invalid request"), \
+       PARA_ERROR(SPX_CTL_INVAL, "speex_decoder_ctl: invalid argument"), \
+
+#define SPXDEC_FILTER_ERRORS \
+       PARA_ERROR(SPX_DECODE, "speex decoding error"), \
+       PARA_ERROR(SPX_DECODE_OVERFLOW, "speex decoding overflow"), \
+       PARA_ERROR(SPX_EOS, "speex: end of stream"), \
+
 #define BUFFER_TREE_ERRORS \
        PARA_ERROR(BTR_EOF, "buffer tree: end of file"), \
        PARA_ERROR(BTR_NO_CHILD, "btr node has no children"), \
index 0e256053e81f531a2cb178fd57f45ff935b437e6..48ef55e84e7afdbbf13b4f0cd0383c1c837d60fc 100644 (file)
--- a/server.c
+++ b/server.c
@@ -23,9 +23,9 @@
  * The gory details, listed by topic:
  *
  *     - Audio format handlers: \ref send_common.c \ref mp3_afh.c,
- *       \ref ogg_afh.c, \ref aac_afh.c, \ref wma_afh.c,
+ *       \ref ogg_afh.c, \ref aac_afh.c, \ref wma_afh.c, \ref spx_afh.c
  *     - Decoders: \ref mp3dec_filter.c, \ref oggdec_filter.c,
- *       \ref aacdec_filter.c, \ref wmadec_filter.c,
+ *       \ref aacdec_filter.c, \ref wmadec_filter.c, spxdec_filter.c,
  *     - Volume normalizer: \ref compress_filter.c,
  *     - Output: \ref alsa_write.c, \ref osx_write.c, \ref oss_write.c,
  *     - http: \ref http_recv.c, \ref http_send.c,
diff --git a/spx.h b/spx.h
new file mode 100644 (file)
index 0000000..6ffa80e
--- /dev/null
+++ b/spx.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/**
+ *  \file spx.h Structures and prototypes common to the speex audio format
+ *  handler and the speex decoder.
+ */
+
+/**
+ * Information extracted from the first ogg packet.
+ *
+ * It contains tech data but not the content of the attached comment tags.
+ */
+struct spx_header_info {
+       /** Holds the state of the decoder. */
+       void *state;
+       /** Extracted from header. */
+       int frame_size;
+       /** Current sample rate in Hz. */
+       spx_int32_t sample_rate;
+       /** Current bitrate used by the decoder. */
+       int bitrate;
+       /** Number of frames per packet, extracted from header. */
+       int nframes;
+       /** The number of channels of the current stream. */
+       int channels;
+       /** Only needed for stereo streams. */
+       SpeexStereoState stereo;
+       /** Must be skipped during decode. */
+       int extra_headers;
+       /** Narrow/wide/ultrawide band, bitstream version. */
+       const SpeexMode *mode;
+};
+
+int spx_process_header(unsigned char *packet, long bytes,
+               struct spx_header_info *shi);
+int spx_ctl(void *state, int request, void *ptr);
diff --git a/spx_afh.c b/spx_afh.c
new file mode 100644 (file)
index 0000000..472c2c8
--- /dev/null
+++ b/spx_afh.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
+
+/* Copyright (C) 2002-2006 Jean-Marc Valin
+   File: speexdec.c
+
+   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.
+   
+   - Neither the name of the Xiph.org Foundation nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+   
+   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 spx_afh.c Audio format handler for ogg/speex files. */
+
+#include <stdbool.h>
+#include <ogg/ogg.h>
+#include <regex.h>
+#include <speex/speex.h>
+#include <speex/speex_header.h>
+#include <speex/speex_stereo.h>
+
+#include "para.h"
+#include "afh.h"
+#include "error.h"
+#include "portable_io.h"
+#include "string.h"
+#include "spx.h"
+#include "ogg_afh_common.h"
+
+struct private_spx_data {
+       struct spx_header_info shi;
+};
+
+
+static char *copy_comment(const char *src, int len)
+{
+       char *p = para_malloc(len + 1);
+
+       if (len > 0)
+               memcpy(p, src, len);
+       p[len] = '\0';
+       PARA_DEBUG_LOG("%s\n", p);
+       return p;
+}
+
+static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
+               char **p)
+{
+       int len = strlen(type);
+
+       if (taglen <= len)
+               return false;
+       if (strncasecmp(tag, type, len))
+               return false;
+       if (tag[len] != '=')
+               return false;
+       free(*p);
+       *p = copy_comment(tag + len + 1, taglen - len - 1);
+       return true;
+}
+
+static int spx_get_comments(unsigned char *comments, int length,
+               struct taginfo *tags)
+{
+       char *c = (char *)comments;
+       uint32_t len, nb_fields;
+       int i;
+       char *end;
+
+       if (length < 8)
+               return -E_SPX_COMMENT;
+       end = c + length;
+       len = read_u32(c);
+       c += 4;
+       if (c + len > end)
+               return -E_SPX_COMMENT;
+       tags->comment = copy_comment(c, len);
+
+       c += len;
+       if (c + 4 > end)
+               return -E_SPX_COMMENT;
+       nb_fields = read_u32(c);
+       PARA_DEBUG_LOG("%d comment(s)\n", nb_fields);
+       c += 4;
+       for (i = 0; i < nb_fields; i++, c += len) {
+               char *tag;
+
+               if (c + 4 > end)
+                       return -E_SPX_COMMENT;
+               len = read_u32(c);
+               c += 4;
+               if (c + len > end)
+                       return -E_SPX_COMMENT;
+               if (copy_if_tag_type(c, len, "author", &tags->artist))
+                       continue;
+               if (copy_if_tag_type(c, len, "artist", &tags->artist))
+                       continue;
+               if (copy_if_tag_type(c, len, "title", &tags->title))
+                       continue;
+               if (copy_if_tag_type(c, len, "album", &tags->album))
+                       continue;
+               if (copy_if_tag_type(c, len, "year", &tags->year))
+                       continue;
+               if (copy_if_tag_type(c, len, "comment", &tags->comment))
+                       continue;
+               tag = copy_comment(c, len);
+               PARA_NOTICE_LOG("unrecognized comment: %s\n", tag);
+               free(tag);
+       }
+       return 1;
+}
+
+static const char* speex_suffixes[] = {"spx", "speex", NULL};
+
+static int spx_packet_callback(ogg_packet *packet, int packet_num,
+               struct afh_info *afhi, void *private_data)
+{
+       struct private_spx_data *psd = private_data;
+       int ret;
+
+       if (packet_num == 0) {
+               ret = spx_process_header(packet->packet, packet->bytes,
+                       &psd->shi);
+               if (ret < 0)
+                       return ret;
+               afhi->channels = psd->shi.channels;
+               afhi->frequency = psd->shi.sample_rate;
+               afhi->bitrate = psd->shi.bitrate / 1000;
+               afhi->techinfo = make_message("%s, v%d", psd->shi.mode->modeName,
+                       psd->shi.mode->bitstream_version);
+               return 1;
+       }
+       if (packet_num == 1) {
+               ret = spx_get_comments(packet->packet, packet->bytes,
+                       &afhi->tags);
+               if (ret < 0)
+                       return ret;
+               return 0; /* header complete */
+       }
+       /* never reached */
+       return 0;
+}
+
+static int spx_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+               struct afh_info *afhi)
+{
+       struct private_spx_data psd;
+       struct ogg_afh_callback_info spx_callback_info = {
+               .packet_callback = spx_packet_callback,
+               .private_data = &psd,
+       };
+
+       memset(&psd, 0, sizeof(psd));
+       return ogg_get_file_info(map, numbytes, afhi, &spx_callback_info);
+}
+
+/**
+ * The init function of the ogg/speex audio format handler.
+ *
+ * \param afh Pointer to the struct to initialize.
+ */
+void spx_afh_init(struct audio_format_handler *afh)
+{
+       afh->get_file_info = spx_get_file_info,
+       afh->suffixes = speex_suffixes;
+}
diff --git a/spx_common.c b/spx_common.c
new file mode 100644 (file)
index 0000000..ce01e23
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2002-2006 Jean-Marc Valin
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/**
+ * \file spx_common.c Functions used by the speex decoder and the speex audio
+ * format handler.
+ */
+
+/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
+
+/* Copyright (C) 2002-2006 Jean-Marc Valin
+   File: speexdec.c
+
+   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.
+   
+   - Neither the name of the Xiph.org Foundation nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+   
+   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 <speex/speex_header.h>
+#include <speex/speex_stereo.h>
+#include <speex/speex_callbacks.h>
+
+#include "para.h"
+#include "error.h"
+#include "spx.h"
+
+/**
+ * Wrapper for speex_decoder_ctl().
+ *
+ * \param state Decoder state.
+ * \param request ioctl-type request.
+ * \param ptr Value-result pointer.
+ *
+ * \return Standard.
+ */
+int spx_ctl(void *state, int request, void *ptr)
+{
+       int ret = speex_decoder_ctl(state, request, ptr);
+
+       if (ret == 0) /* success */
+               return 1;
+       if (ret == -1)
+               return -E_SPX_CTL_BAD_RQ;
+       return -E_SPX_CTL_INVAL;
+}
+
+/**
+ * Obtain information about a speex file from an ogg packet.
+ *
+ * \param packet Start of the ogg packet.
+ * \param bytes Length of the ogg packet.
+ * \param shi Result pointer.
+ *
+ * \return Standard.
+ */
+int spx_process_header(unsigned char *packet, long bytes,
+               struct spx_header_info *shi)
+{
+       int ret;
+       spx_int32_t enh_enabled = 1;
+       SpeexHeader *h = speex_packet_to_header((char *)packet, bytes);
+
+       if (!h)
+               return -E_SPX_HEADER;
+       ret = -E_SPX_HEADER_MODE;
+       if (h->mode >= SPEEX_NB_MODES || h->mode < 0)
+               goto out;
+       shi->mode = speex_lib_get_mode(h->mode);
+
+       ret = -E_SPX_VERSION;
+       if (h->speex_version_id > 1)
+               goto out;
+       if (shi->mode->bitstream_version < h->mode_bitstream_version)
+               goto out;
+       if (shi->mode->bitstream_version > h->mode_bitstream_version)
+               goto out;
+
+       ret = -E_SPX_DECODER_INIT;
+       shi->state = speex_decoder_init(shi->mode);
+       if (!shi->state)
+               goto out;
+
+       ret = spx_ctl(shi->state, SPEEX_SET_ENH, &enh_enabled);
+       if (ret < 0)
+               goto out;
+       ret = spx_ctl(shi->state, SPEEX_GET_FRAME_SIZE, &shi->frame_size);
+       if (ret < 0)
+               goto out;
+       shi->sample_rate = h->rate;
+       ret = spx_ctl(shi->state, SPEEX_SET_SAMPLING_RATE, &shi->sample_rate);
+       if (ret < 0)
+               goto out;
+       shi->nframes = h->frames_per_packet;
+       shi->channels = h->nb_channels;
+       if (shi->channels != 1) {
+               shi->stereo = (SpeexStereoState)SPEEX_STEREO_STATE_INIT;
+               SpeexCallback callback = {
+                       .callback_id = SPEEX_INBAND_STEREO,
+                       .func = speex_std_stereo_request_handler,
+                       .data = &shi->stereo,
+               };
+               ret = spx_ctl(shi->state, SPEEX_SET_HANDLER, &callback);
+               if (ret < 0)
+                       goto out;
+               shi->channels = 2;
+       }
+       ret = spx_ctl(shi->state, SPEEX_GET_BITRATE, &shi->bitrate);
+       if (ret < 0)
+               goto out;
+       PARA_NOTICE_LOG("%d Hz, %s, %s, %s, %d bits/s\n",
+               shi->sample_rate, shi->mode->modeName,
+               shi->channels == 1? "mono" : "stereo",
+               h->vbr? "vbr" : "cbr",
+               shi->bitrate
+       );
+       shi->extra_headers = h->extra_headers;
+       ret = 1;
+out:
+       free(h);
+       return ret;
+}
diff --git a/spxdec_filter.c b/spxdec_filter.c
new file mode 100644 (file)
index 0000000..da2d5da
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2002-2006 Jean-Marc Valin
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file spxdec_filter.c Paraslash's ogg/speex decoder. */
+
+/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
+
+/* Copyright (C) 2002-2006 Jean-Marc Valin
+   File: speexdec.c
+
+   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.
+   
+   - Neither the name of the Xiph.org Foundation nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+   
+   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 <ogg/ogg.h>
+#include <speex/speex.h>
+#include <stdbool.h>
+#include <speex/speex_header.h>
+#include <speex/speex_stereo.h>
+#include <speex/speex_callbacks.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 "spx.h"
+
+/** Data specific to the speexdec filter. */
+struct private_spxdec_data {
+       /** Header information obtained from the first ogg packet. */
+       struct spx_header_info shi;
+       /** Read from and written to by the Speex routines. */
+       SpeexBits bits;
+       /* Ogg pages are retrieved from this structure. */
+       ogg_sync_state oy;
+       /** Extracted from header. */
+       int speex_serialno;
+       /** Total number of ogg packets retrieved so far. */
+       int packet_count;
+       /** Needed to find out how much to skip. */
+       ogg_int64_t last_granule;
+       /** Also needed for skipping packets. */
+       int lookahead;
+       /** The state information about the current stream. */
+       ogg_stream_state os;
+       /** Whether \a os initialized. */
+       bool stream_init;
+};
+
+static void spxdec_open(struct filter_node *fn)
+{
+       struct private_spxdec_data *psd = para_calloc(sizeof(*psd));
+
+       fn->private_data = psd;
+       fn->min_iqs = 200;
+       psd->speex_serialno = -1;
+}
+
+static void speexdec_close(struct filter_node *fn)
+{
+       struct private_spxdec_data *psd = fn->private_data;
+
+       if (psd->shi.state) {
+               /* Destroy the decoder state */
+               speex_decoder_destroy(psd->shi.state);
+               /* Destroy the bit-stream struct */
+               speex_bits_destroy(&psd->bits);
+       }
+       free(psd);
+       fn->private_data = NULL;
+}
+
+static int speexdec_execute(struct btr_node *btrn, const char *cmd,
+               char **result)
+{
+       struct filter_node *fn = btr_context(btrn);
+       struct private_spxdec_data *psd = fn->private_data;
+
+       return decoder_execute(cmd, psd->shi.sample_rate, psd->shi.channels,
+               result);
+}
+
+static int speexdec_init(struct filter_node *fn)
+{
+       struct private_spxdec_data *psd = fn->private_data;
+
+       PARA_INFO_LOG("init\n");
+       ogg_sync_init(&psd->oy);
+       speex_bits_init(&psd->bits);
+       return 1;
+}
+
+#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) )
+#define le_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8))
+#else
+#define le_short(s) ((short) (s))
+#endif
+
+#define MAX_FRAME_SIZE 2000
+/* Copy Ogg packet to Speex bitstream */
+static int speexdec_write_frames(int packet_no,
+               struct private_spxdec_data *psd, int skip_samples,
+               int page_nb_packets, struct btr_node *btrn)
+{
+       int i, j;
+
+       for (j = 0; j != psd->shi.nframes; j++) {
+               short output[MAX_FRAME_SIZE], *btr_output;
+               int skip = skip_samples + psd->lookahead, skip_idx = 0;
+               int samples, new_frame_size = psd->shi.frame_size;
+
+               if (speex_decode_int(psd->shi.state, &psd->bits, output) < 0)
+                       return -E_SPX_DECODE;
+               if (speex_bits_remaining(&psd->bits) < 0)
+                       return -E_SPX_DECODE_OVERFLOW;
+               if (psd->shi.channels == 2)
+                       speex_decode_stereo_int(output, psd->shi.frame_size,
+                               &psd->shi.stereo);
+               if (packet_no == 1 && j == 0 && skip_samples > 0) {
+                       new_frame_size -= skip;
+                       skip_idx = skip * psd->shi.channels;
+               }
+               if (packet_no == page_nb_packets && skip_samples < 0) {
+                       new_frame_size = psd->shi.nframes * psd->shi.frame_size
+                               + skip - j * psd->shi.frame_size;
+                       if (new_frame_size < 0)
+                               new_frame_size = 0;
+                       if (new_frame_size > psd->shi.frame_size)
+                               new_frame_size = psd->shi.frame_size;
+               }
+               if (new_frame_size <= 0)
+                       continue;
+               samples = new_frame_size * psd->shi.channels;
+               btr_output = para_malloc(2 * samples);
+               for (i = 0; i < samples; i++)
+                       btr_output[i] = le_short(output[i + skip_idx]);
+               btr_add_output((char *)btr_output, samples * 2, btrn);
+       }
+       return 1;
+}
+
+/* Extract all available packets */
+static int speexdec_extract_packets(struct private_spxdec_data *psd,
+               ogg_stream_state *os, int skip_samples, int page_nb_packets,
+               struct btr_node *btrn)
+{
+       int ret, packet_no;
+       bool eos = false;
+
+       for (packet_no = 0;; psd->packet_count++) {
+               ogg_packet op;
+
+               if (ogg_stream_packetout(os, &op) != 1)
+                       return 0;
+               if (op.bytes >= 5 && !memcmp(op.packet, "Speex", 5))
+                       psd->speex_serialno = os->serialno;
+               if (psd->speex_serialno == -1)
+                       return 0;
+               if (os->serialno != psd->speex_serialno)
+                       return 0;
+               /* If first packet, process as Speex header */
+               if (psd->packet_count == 0) {
+                       ret = spx_process_header(op.packet, op.bytes, &psd->shi);
+                       if (ret < 0)
+                               return ret;
+                       ret = speex_decoder_ctl(psd->shi.state, SPEEX_GET_LOOKAHEAD,
+                               &psd->lookahead);
+                       if (ret < 0)
+                               return ret;
+                       if (!psd->shi.nframes)
+                               psd->shi.nframes = 1;
+                       PARA_INFO_LOG("frame size: %d\n", psd->shi.frame_size);
+                       continue;
+               }
+               if (psd->packet_count == 1) /* ignore comments */
+                       continue;
+               if (psd->packet_count <= 1 + psd->shi.extra_headers)
+                       continue; /* Ignore extra headers */
+               packet_no++;
+               /* check end of stream condition */
+               if (op.e_o_s && os->serialno == psd->speex_serialno)
+                       eos = true;
+               speex_bits_read_from(&psd->bits, (char *)op.packet,
+                       op.bytes);
+               ret = speexdec_write_frames(packet_no, psd,
+                       skip_samples, page_nb_packets, btrn);
+               if (ret < 0)
+                       return ret;
+               if (eos == true)
+                       return -E_SPX_EOS;
+       }
+}
+
+static int compute_skip_samples(ogg_page *og, struct private_spxdec_data *psd)
+{
+       int ret, page_granule = ogg_page_granulepos(og);
+
+       if (page_granule <= 0)
+               return 0;
+       if (psd->shi.frame_size == 0)
+               return 0;
+       ret = ogg_page_packets(og) * psd->shi.frame_size * psd->shi.nframes
+               - page_granule + psd->last_granule;
+       if (ogg_page_eos(og))
+               ret = -ret;
+       return ret;
+}
+
+static void speexdec_post_select(__a_unused struct sched *s, struct task *t)
+{
+       struct filter_node *fn = container_of(t, struct filter_node, task);
+       struct private_spxdec_data *psd = fn->private_data;
+       struct btr_node *btrn = fn->btrn;
+       int ret, ns;
+       ogg_page og;
+       char *btr_buf;
+       size_t nbytes;
+
+next_buffer:
+       t->error = 0;
+       ret = ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+       btr_merge(btrn, fn->min_iqs);
+       if (!psd->shi.state) {
+               if (ret <= 0)
+                       goto fail;
+               ret = speexdec_init(fn);
+               if (ret <= 0)
+                       goto fail;
+       }
+       nbytes = btr_next_buffer(btrn, &btr_buf);
+       nbytes = PARA_MIN(nbytes, (size_t)200);
+       if (nbytes > 0) {
+               char *data = ogg_sync_buffer(&psd->oy, nbytes);
+               memcpy(data, btr_buf, nbytes);
+               btr_consume(btrn, nbytes);
+               ogg_sync_wrote(&psd->oy, nbytes);
+       }
+       /* Loop for all complete pages we got */
+       while (ogg_sync_pageout(&psd->oy, &og) == 1) {
+               int skip_samples;
+
+               if (psd->stream_init == false) {
+                       ogg_stream_init(&psd->os, ogg_page_serialno(&og));
+                       psd->stream_init = true;
+               }
+               if (ogg_page_serialno(&og) != psd->os.serialno)
+                       ogg_stream_reset_serialno(&psd->os, ogg_page_serialno(&og));
+               /* Add page to the bitstream */
+               ogg_stream_pagein(&psd->os, &og);
+               skip_samples = compute_skip_samples(&og, psd);
+               psd->last_granule = ogg_page_granulepos(&og);
+               ret = speexdec_extract_packets(psd, &psd->os, skip_samples,
+                       ogg_page_packets(&og), btrn);
+               if (ret < 0)
+                       goto fail;
+       }
+       if (ns > 0)
+               goto next_buffer;
+       ret = ns;
+fail:
+       if (ret < 0) {
+               t->error = ret;
+               btr_remove_node(btrn);
+       }
+}
+
+/**
+ * The init function of the ogg/speex decoder.
+ *
+ * \param f Its fields are filled in by the function.
+ */
+void spxdec_filter_init(struct filter *f)
+{
+       f->open = spxdec_open;
+       f->close = speexdec_close;
+       f->pre_select = generic_filter_pre_select;
+       f->post_select = speexdec_post_select;
+       f->execute = speexdec_execute;
+}