From: Andre Noll Date: Tue, 22 Jun 2010 22:53:46 +0000 (+0200) Subject: Add support for the speex codec. X-Git-Tag: v0.4.4~5^2~1 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=90560de0d7e4d825772270f9ac7cbefbb38aad97 Add support for the speex codec. 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. --- diff --git a/CREDITS b/CREDITS index 15313f96..1d8ad0ce 100644 --- a/CREDITS +++ b/CREDITS @@ -62,3 +62,5 @@ Cedric Tefft (mp3info) Linus Torvalds (for giving us one hell of an operating system [quote taken from README.linux for DOOM v1.666]) + +Jean-Marc Valin (speex) diff --git a/FEATURES b/FEATURES index dc7f8537..5b5f4699 100644 --- 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 diff --git a/Makefile.in b/Makefile.in index e457e490..1a0c1fa3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 $<' diff --git a/afh_common.c b/afh_common.c index 498f4921..4464987a 100644 --- a/afh_common.c +++ b/afh_common.c @@ -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, } diff --git a/configure.ac b/configure.ac index 49fb0987..5322e7d1 100644 --- a/configure.ac +++ b/configure.ac @@ -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 5b1ce077..5db5e174 100644 --- 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"), \ diff --git a/server.c b/server.c index 0e256053..48ef55e8 100644 --- 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 index 00000000..6ffa80eb --- /dev/null +++ b/spx.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010 Andre Noll + * + * 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 index 00000000..472c2c80 --- /dev/null +++ b/spx_afh.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2010 Andre Noll + * + * 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 +#include +#include +#include +#include +#include + +#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 index 00000000..ce01e23a --- /dev/null +++ b/spx_common.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2002-2006 Jean-Marc Valin + * Copyright (C) 2010 Andre Noll + * + * 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 +#include +#include +#include + +#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 index 00000000..da2d5da6 --- /dev/null +++ b/spxdec_filter.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2002-2006 Jean-Marc Valin + * Copyright (C) 2010 Andre Noll + * + * 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 +#include +#include +#include +#include +#include +#include + +#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; +}