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)
* 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
$(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 $<'
#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 *);
/**
.name = "wma",
.init = wma_afh_init,
},
+ {
+ .name = "spx",
+#ifdef HAVE_SPEEX
+ .init = spx_afh_init,
+#endif
+ },
{
.name = NULL,
}
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"
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"), \
* 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,
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}