]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/speex'
authorAndre Noll <maan@systemlinux.org>
Sun, 1 Aug 2010 11:26:42 +0000 (13:26 +0200)
committerAndre Noll <maan@systemlinux.org>
Sun, 1 Aug 2010 11:26:42 +0000 (13:26 +0200)
22 files changed:
CREDITS
FEATURES
Makefile.in
afh.h
afh_common.c
alsa_write.c
command.c
configure.ac
error.h
ogg_afh.c
ogg_afh_common.c [new file with mode: 0644]
ogg_afh_common.h [new file with mode: 0644]
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]
vss.c
vss.h
web/manual.m4
write.c
write_common.h

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 639464aa133b0d33580afe1c4a403e021a83379e..1a0c1fa34a3e7cecf0c5ee9cc77b34c4cda29d9f 100644 (file)
@@ -153,13 +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) @ogg_cppflags@ $<
 
 $(object_dir)/mp3dec_filter.o: mp3dec_filter.c | $(object_dir)
        @[ -z "$(Q)" ] || echo 'CC $<'
diff --git a/afh.h b/afh.h
index 95cd0514224f0ab462daeff25011f742bbad44d3..6dfd03ed0d89d15568fa2096ada83909531d5bab 100644 (file)
--- a/afh.h
+++ b/afh.h
@@ -6,23 +6,6 @@
 
 /** \file afh.h structures for audio format handling (para_server) */
 
-/** \cond */
-#ifdef HAVE_OGGVORBIS
-#define OV_AUDIO_FORMAT " ogg"
-#else
-#define OV_AUDIO_FORMAT ""
-#endif
-
-#ifdef HAVE_FAAD
-#define AAC_AUDIO_FORMAT " aac"
-#else
-#define AAC_AUDIO_FORMAT ""
-#endif
-
-#define SUPPORTED_AUDIO_FORMATS "mp3 " OV_AUDIO_FORMAT AAC_AUDIO_FORMAT " wma "
-
-/** \endcond */
-
 /**
  * The tags used by all audio format handlers.
  *
index f28acaafda9f74138b5871d3d1d2590608135054..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,
        }
@@ -89,7 +98,7 @@ void afh_init(void)
        int i;
 
        PARA_INFO_LOG("supported audio formats: %s\n",
-               SUPPORTED_AUDIO_FORMATS);
+               SERVER_AUDIO_FORMATS);
        FOR_EACH_AUDIO_FORMAT(i) {
                PARA_NOTICE_LOG("initializing %s handler\n",
                        audio_format_name(i));
index 7f8aae01c22a6b9fcc2a48712c490b6bde73b40e..d115a52db0ae899be1ec003d4f7e060611b0ee94 100644 (file)
@@ -46,7 +46,6 @@ struct private_alsa_write_data {
         * of the writer node group.
         */
        unsigned sample_rate;
-
        snd_pcm_format_t sample_format;
        /**
         * The number of channels, given by command line option or the
index bcbbc3406def48eba64adeb31eecd81997f634b2..1ca5db3c97aea79477fd935569e989de7052064d 100644 (file)
--- a/command.c
+++ b/command.c
@@ -268,7 +268,7 @@ int com_si(struct rc4_context *rc4c, int argc, __a_unused char * const * argv)
                mmd->num_commands,
                mmd->num_connects,
                conf.loglevel_arg,
-               supported_audio_formats(),
+               SERVER_AUDIO_FORMATS,
                sender_info
        );
        mutex_unlock(mmd_mutex);
index 8fbf49a6e222e253c17a686fa07ed013b7e4ce42..a7bd3e1ab5e96bb956b8598af2786aadca2f106c 100644 (file)
@@ -133,7 +133,7 @@ server_errlist_objs="server afh_common mp3_afh vss command net string signal
        blob playlist sha1 sched acl send_common udp_send color fec
        server_command_list afs_command_list wma_afh wma_common"
 server_ldflags="-losl"
-server_audio_formats=" mp3 wma"
+server_audio_formats="mp3 wma"
 
 write_cmdline_objs="add_cmdline(write file_write)"
 write_errlist_objs="write write_common file_write time fd string sched stdin
@@ -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"
-       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"
-       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"
-       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"
@@ -689,6 +728,9 @@ done
 AC_DEFINE_UNQUOTED(STATUS_ITEM_ARRAY, [$result],
        [char * array of all status items])
 
+AC_DEFINE_UNQUOTED(SERVER_AUDIO_FORMATS, "$server_audio_formats",
+       [formats supported by para_server and para_afh])
+
 
 AC_SUBST(executables, add_para($executables))
 
diff --git a/error.h b/error.h
index a24bf20c9319a5233bd71cfcc299444a81732805..5db5e1749a5d915dc3f55f956c423399613da7bf 100644 (file)
--- a/error.h
+++ b/error.h
@@ -36,16 +36,39 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 #define FILE_WRITE_ERRORS
 #define STDIN_ERRORS
 
-
 extern const char **para_errlist[];
 
+#define OGG_AFH_COMMON_ERRORS \
+       PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \
+       PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
+       PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error (first page)"), \
+       PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
+       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"), \
        PARA_ERROR(BTR_NAVAIL, "btr node: value currently unavailable"), \
 
 
-
 #define BITSTREAM_ERRORS \
        PARA_ERROR(VLC, "invalid vlc code"), \
 
@@ -326,12 +349,7 @@ extern const char **para_errlist[];
 
 
 #define OGG_AFH_ERRORS \
-       PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
-       PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error (first page)"), \
-       PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \
        PARA_ERROR(VORBIS, "vorbis synthesis header-in error (not vorbis?)"), \
-       PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \
-       PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \
 
 
 #define VSS_ERRORS \
index 4b44ea16664948da6cd52adca357b5cb037c3223..744a1331c7e794bb0300e59bce950f50cbffe47a 100644 (file)
--- a/ogg_afh.c
+++ b/ogg_afh.c
@@ -4,10 +4,8 @@
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
-/** \file ogg_afh.c Audio format handler for ogg vorbis files. */
+/** \file ogg_afh.c Audio format handler for ogg/vorbis files. */
 
-#include <inttypes.h>
-#include <ogg/ogg.h>
 #include <vorbis/codec.h>
 #include <regex.h>
 
 #include "afh.h"
 #include "error.h"
 #include "string.h"
+#include "ogg_afh_common.h"
 
-/* Taken from decoder_example.c of libvorbis-1.2.3. */
-static int read_vorbis_comment(ogg_sync_state *oss, ogg_stream_state *stream,
-               vorbis_info *vi, vorbis_comment *vc)
-{
-       ogg_page page;
-       ogg_packet packet;
-       int i = 0;
-
-       while (i < 2) {
-               while (i < 2) {
-                       int ret = ogg_sync_pageout(oss, &page);
-                       if (ret == 0)
-                               break; /* Need more data */
-                       if (ret != 1)
-                               continue;
-                       /*
-                        * We can ignore any errors here as they'll also become
-                        * apparent at packetout.
-                        */
-                       ogg_stream_pagein(stream, &page);
-                       while (i < 2) {
-                               ret = ogg_stream_packetout(stream, &packet);
-                               if (ret == 0)
-                                       break;
-                               if (ret < 0)
-                                       return -E_STREAM_PACKETOUT;
-                               ret = vorbis_synthesis_headerin(vi, vc,
-                                       &packet);
-                               if (ret < 0)
-                                       return -E_VORBIS;
-                               i++;
-                       }
-               }
-       }
-       return 1;
-}
-
-static int read_vorbis_info(ogg_sync_state *oss, struct afh_info *afhi)
-{
-       vorbis_comment vc;
+struct private_vorbis_data {
        vorbis_info vi;
-       ogg_packet packet;
-       ogg_stream_state stream;
-       ogg_page page;
-       int ret;
-
-       vorbis_info_init(&vi);
-       vorbis_comment_init(&vc);
-
-       ret = -E_SYNC_PAGEOUT;
-       if (ogg_sync_pageout(oss, &page) != 1)
-               goto out;
-
-       ret = ogg_page_serialno(&page);
-       ogg_stream_init(&stream, ret);
-
-       ret = -E_STREAM_PAGEIN;
-       if (ogg_stream_pagein(&stream, &page) < 0)
-               goto out;
-
-       ret = -E_STREAM_PACKETOUT;
-       if (ogg_stream_packetout(&stream, &packet) != 1)
-               goto out;
-
-       ret = -E_VORBIS;
-       if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0)
-               goto out;
-       if (vi.rate == 0)
-               goto out;
-       afhi->channels = vi.channels;
-       afhi->frequency = vi.rate;
-       afhi->bitrate = vi.bitrate_nominal / 1000;
-       PARA_DEBUG_LOG("channels: %i, sampling rate: %i, bitrate: %i\n",
-               afhi->channels, afhi->frequency, afhi->bitrate);
-       ret = read_vorbis_comment(oss, &stream, &vi, &vc);
-       if (ret < 0)
-               goto out;
-       afhi->tags.artist = para_strdup(vorbis_comment_query(&vc, "artist", 0));
-       afhi->tags.title = para_strdup(vorbis_comment_query(&vc, "title", 0));
-       afhi->tags.album = para_strdup(vorbis_comment_query(&vc, "album", 0));
-       afhi->tags.year = para_strdup(vorbis_comment_query(&vc, "year", 0));
-       afhi->tags.comment = para_strdup(vorbis_comment_query(&vc, "comment", 0));
-
-       afhi->header_offset = 0;
-       afhi->header_len = oss->returned;
-       ret = 1;
-out:
-       vorbis_info_clear(&vi);
-       vorbis_comment_clear(&vc);
-       //ogg_stream_clear(&stream);
-       return ret;
-}
+       vorbis_comment vc;
+};
 
-static void set_chunk_tv(int num_frames, int num_chunks, int frequency,
-               struct timeval *result)
+static int vorbis_packet_callback(ogg_packet *packet, int packet_num,
+               struct afh_info *afhi, void *private_data)
 {
-       uint64_t x = (uint64_t)num_frames * 1000 * 1000
-               / frequency / num_chunks;
-
-       result->tv_sec = x / 1000 / 1000;
-       result->tv_usec = x % (1000 * 1000);
-       PARA_INFO_LOG("%d chunks, chunk time: %lums\n", num_chunks,
-               tv2ms(result));
+       struct private_vorbis_data *pvd = private_data;
+
+       if (vorbis_synthesis_headerin(&pvd->vi, &pvd->vc, packet) < 0)
+               return -E_VORBIS;
+       if (packet_num == 0) {
+               if (pvd->vi.rate == 0)
+                       return -E_VORBIS;
+               afhi->channels = pvd->vi.channels;
+               afhi->frequency = pvd->vi.rate;
+               afhi->bitrate = pvd->vi.bitrate_nominal / 1000;
+               PARA_DEBUG_LOG("channels: %i, sampling rate: %i, bitrate: %i\n",
+                       afhi->channels, afhi->frequency, afhi->bitrate);
+               return 1;
+       }
+       if (packet_num == 1)
+               return 1; /* we also want to have packet #2 */
+       afhi->tags.artist = para_strdup(vorbis_comment_query(&pvd->vc, "artist", 0));
+       afhi->tags.title = para_strdup(vorbis_comment_query(&pvd->vc, "title", 0));
+       afhi->tags.album = para_strdup(vorbis_comment_query(&pvd->vc, "album", 0));
+       afhi->tags.year = para_strdup(vorbis_comment_query(&pvd->vc, "year", 0));
+       afhi->tags.comment = para_strdup(vorbis_comment_query(&pvd->vc, "comment", 0));
+       return 0;
 }
 
-/* Write tech data to given audio format handler struct. */
-static int ogg_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+static int ogg_vorbis_get_file_info(char *map, size_t numbytes, __a_unused int fd,
                struct afh_info *afhi)
 {
-       ogg_sync_state oss;
-       ogg_page op;
-       long len = numbytes;
-       char *buf;
-       int ret, i, j, frames_per_chunk, ct_size;
-       long long unsigned num_frames = 0;
-
-       ogg_sync_init(&oss);
-       ret = -E_OGG_SYNC;
-       buf = ogg_sync_buffer(&oss, len);
-       if (!buf)
-               goto out;
-       memcpy(buf, map, len);
-       ret = -E_OGG_SYNC;
-       if (ogg_sync_wrote(&oss, len) < 0)
-               goto out;
-       ret = read_vorbis_info(&oss, afhi);
-       if (ret < 0)
-               goto out;
-       oss.returned = 0;
-       oss.fill = numbytes;
-       /* count ogg packages and get duration of the file */
-       for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++)
-               num_frames = ogg_page_granulepos(&op);
-       PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames);
-       ret = -E_OGG_EMPTY;
-       if (i == 0)
-               goto out;
-       afhi->seconds_total = num_frames / afhi->frequency;
-       /* use roughly one page per chunk */
-       frames_per_chunk = num_frames / i;
-       PARA_INFO_LOG("%lu seconds, %d frames/chunk\n",
-               afhi->seconds_total, frames_per_chunk);
-       ct_size = 250;
-       afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
-       afhi->chunk_table[0] = 0;
-       afhi->chunk_table[1] = afhi->header_len;
-       oss.returned = afhi->header_len;
-       oss.fill = numbytes;
-       for (i = 0, j = 1; ogg_sync_pageseek(&oss, &op) > 0; i++) {
-               int granule = ogg_page_granulepos(&op);
-
-               while (granule > j * frames_per_chunk) {
-                       j++;
-                       if (j >= ct_size) {
-                               ct_size *= 2;
-                               afhi->chunk_table = para_realloc(
-                                       afhi->chunk_table,
-                                       ct_size * sizeof(uint32_t));
-                       }
-                       afhi->chunk_table[j] = oss.returned;
-               }
-       }
-       afhi->chunks_total = j;
-       set_chunk_tv(num_frames, j, afhi->frequency, &afhi->chunk_tv);
-       ret = 0;
-out:
-       ogg_sync_clear(&oss);
+       int ret;
+       struct private_vorbis_data pvd;
+       struct ogg_afh_callback_info vorbis_callback_info = {
+               .packet_callback = vorbis_packet_callback,
+               .private_data = &pvd,
+       };
+
+       vorbis_info_init(&pvd.vi);
+       vorbis_comment_init(&pvd.vc);
+       ret = ogg_get_file_info(map, numbytes, afhi, &vorbis_callback_info);
+       vorbis_info_clear(&pvd.vi);
+       vorbis_comment_clear(&pvd.vc);
        return ret;
 }
 
@@ -195,6 +74,6 @@ static const char* ogg_suffixes[] = {"ogg", NULL};
  */
 void ogg_init(struct audio_format_handler *afh)
 {
-       afh->get_file_info = ogg_get_file_info,
+       afh->get_file_info = ogg_vorbis_get_file_info,
        afh->suffixes = ogg_suffixes;
 }
diff --git a/ogg_afh_common.c b/ogg_afh_common.c
new file mode 100644 (file)
index 0000000..353bc9b
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2004-2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file ogg_afh_common.c Functions common to ogg/vorbis and ogg/speex. */
+
+#include <ogg/ogg.h>
+#include <regex.h>
+
+#include "para.h"
+#include "afh.h"
+#include "error.h"
+#include "string.h"
+#include "ogg_afh_common.h"
+
+
+/* Taken from decoder_example.c of libvorbis-1.2.3. */
+static int process_packets_2_and_3(ogg_sync_state *oss,
+               ogg_stream_state *stream, struct afh_info *afhi,
+               struct ogg_afh_callback_info *ci)
+{
+       ogg_page page;
+       ogg_packet packet;
+       int i = 0;
+
+       while (i < 2) {
+               while (i < 2) {
+                       int ret = ogg_sync_pageout(oss, &page);
+                       if (ret == 0)
+                               break; /* Need more data */
+                       if (ret != 1)
+                               continue;
+                       /*
+                        * We can ignore any errors here as they'll also become
+                        * apparent at packetout.
+                        */
+                       ogg_stream_pagein(stream, &page);
+                       while (i < 2) {
+                               ret = ogg_stream_packetout(stream, &packet);
+                               if (ret == 0)
+                                       break;
+                               if (ret < 0)
+                                       return -E_STREAM_PACKETOUT;
+                               ret = ci->packet_callback(&packet, i + 1, afhi,
+                                       ci->private_data);
+                               if (ret < 0)
+                                       return ret;
+                               if (ret == 0) /* header complete */
+                                       return 1;
+                               i++;
+                       }
+               }
+       }
+       return 1;
+}
+
+static int process_ogg_packets(ogg_sync_state *oss, struct afh_info *afhi,
+               struct ogg_afh_callback_info *ci)
+{
+       ogg_packet packet;
+       ogg_stream_state stream;
+       ogg_page page;
+       int ret;
+
+       ret = -E_SYNC_PAGEOUT;
+       if (ogg_sync_pageout(oss, &page) != 1)
+               goto out;
+
+       ret = ogg_page_serialno(&page);
+       ogg_stream_init(&stream, ret);
+
+       ret = -E_STREAM_PAGEIN;
+       if (ogg_stream_pagein(&stream, &page) < 0)
+               goto out;
+
+       ret = -E_STREAM_PACKETOUT;
+       if (ogg_stream_packetout(&stream, &packet) != 1)
+               goto out;
+       ret = ci->packet_callback(&packet, 0, afhi, ci->private_data);
+       if (ret < 0)
+               goto out;
+       ret = process_packets_2_and_3(oss, &stream, afhi, ci);
+       if (ret < 0)
+               goto out;
+       afhi->header_offset = 0;
+       afhi->header_len = oss->returned;
+       ret = 1;
+out:
+       ogg_stream_clear(&stream);
+       return ret;
+}
+
+static void set_chunk_tv(int num_frames, int num_chunks, int frequency,
+               struct timeval *result)
+{
+       uint64_t x = (uint64_t)num_frames * 1000 * 1000
+               / frequency / num_chunks;
+
+       result->tv_sec = x / 1000 / 1000;
+       result->tv_usec = x % (1000 * 1000);
+       PARA_INFO_LOG("%d chunks, chunk time: %lums\n", num_chunks,
+               tv2ms(result));
+}
+
+/**
+ * Pass first three ogg packets to callback and build the chunk table.
+ *
+ * This function extracts the first three ogg packets of the audio data
+ * given by \a map and \a numbytes and passes each packet to the callback
+ * defined by \a ci.
+ *
+ * If the packet callback indicates success, the chunk table is built.  Chunk
+ * zero contains the first three ogg packets while all other chunks consist of
+ * exactly one ogg page.
+ *
+ * \param map Audio file data.
+ * \param numbytes The length of \a map.
+ * \param afhi Passed to the packet callback, contains chunk table.
+ * \param ci The callback structure.
+ *
+ * \return Standard.
+ */
+int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
+               struct ogg_afh_callback_info *ci)
+{
+       ogg_sync_state oss;
+       ogg_page op;
+       long len = numbytes;
+       char *buf;
+       int ret, i, j, frames_per_chunk, ct_size;
+       long long unsigned num_frames = 0;
+
+       ogg_sync_init(&oss);
+       ret = -E_OGG_SYNC;
+       buf = ogg_sync_buffer(&oss, len);
+       if (!buf)
+               goto out;
+       memcpy(buf, map, len);
+       ret = -E_OGG_SYNC;
+       if (ogg_sync_wrote(&oss, len) < 0)
+               goto out;
+       ret = process_ogg_packets(&oss, afhi, ci);
+       if (ret < 0)
+               goto out;
+       oss.returned = 0;
+       oss.fill = numbytes;
+       /* count ogg packages and get duration of the file */
+       for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++)
+               num_frames = ogg_page_granulepos(&op);
+       PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames);
+       ret = -E_OGG_EMPTY;
+       if (i == 0)
+               goto out;
+       afhi->seconds_total = num_frames / afhi->frequency;
+       /* use roughly one page per chunk */
+       frames_per_chunk = num_frames / i;
+       PARA_INFO_LOG("%lu seconds, %d frames/chunk\n",
+               afhi->seconds_total, frames_per_chunk);
+       ct_size = 250;
+       afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t));
+       afhi->chunk_table[0] = 0;
+       afhi->chunk_table[1] = afhi->header_len;
+       oss.returned = afhi->header_len;
+       oss.fill = numbytes;
+       for (i = 0, j = 1; ogg_sync_pageseek(&oss, &op) > 0; i++) {
+               int granule = ogg_page_granulepos(&op);
+
+               while (granule > j * frames_per_chunk) {
+                       j++;
+                       if (j >= ct_size) {
+                               ct_size *= 2;
+                               afhi->chunk_table = para_realloc(
+                                       afhi->chunk_table,
+                                       ct_size * sizeof(uint32_t));
+                       }
+                       afhi->chunk_table[j] = oss.returned;
+               }
+       }
+       afhi->chunks_total = j;
+       set_chunk_tv(num_frames, j, afhi->frequency, &afhi->chunk_tv);
+       ret = 0;
+out:
+       ogg_sync_clear(&oss);
+       return ret;
+}
diff --git a/ogg_afh_common.h b/ogg_afh_common.h
new file mode 100644 (file)
index 0000000..c7eb167
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/**
+ * \file ogg_afh_common.h Structures and prototypes common to audio format
+ * handlers that use the ogg container format.
+ */
+
+/**
+ * Callback structure provided by vorbis/speex audio format handlers.
+ *
+ * Both audio formats utilize the ogg container format. Meta info about
+ * the audio file is contained in the first three ogg packets.
+ */
+struct ogg_afh_callback_info {
+       /**
+         * ogg_get_file_info() calls this function for each of the three
+         * header packets. If this callback returns a negative value, the
+         * audio file is considered invalid and the chunk table is not
+         * created. If it returns zero, the end of the header has been
+         * reached and no further ogg packets should be processed.
+         */
+       int (*packet_callback)(ogg_packet *packet, int packet_num,
+               struct afh_info *afhi, void *private_data);
+       /** Vorbis/speex specific data. */
+       void *private_data;
+};
+
+int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
+               struct ogg_afh_callback_info *ci);
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;
+}
diff --git a/vss.c b/vss.c
index adc0cb63cea1204d3bfef811c492b807b74001c2..e26e8c8303c2c228a59adfaf64e6b7c5fd7b0fdf 100644 (file)
--- a/vss.c
+++ b/vss.c
@@ -636,18 +636,6 @@ static void vss_eof(struct vss_task *vsst)
        mmd->events++;
 }
 
-/**
- * Get the list of all supported audio formats.
- *
- * \return Aa space separated list of all supported audio formats
- * It is not allocated at runtime, i.e. there is no need to free
- * the returned string in the caller.
- */
-const char *supported_audio_formats(void)
-{
-       return SUPPORTED_AUDIO_FORMATS;
-}
-
 static int need_to_request_new_audio_file(struct vss_task *vsst)
 {
        struct timeval diff;
diff --git a/vss.h b/vss.h
index 9412da81a3841425258c07a52e8a1efeca6f5beb..c7e41b9d6159c8f93faae559e4a1e16645391554 100644 (file)
--- a/vss.h
+++ b/vss.h
@@ -13,7 +13,6 @@ unsigned int vss_repos(void);
 unsigned int vss_paused(void);
 unsigned int vss_stopped(void);
 struct timeval *vss_chunk_time(void);
-const char *supported_audio_formats(void);
 
 /** Stop playing after current audio file. */
 #define VSS_NOMORE 1
index 0ff7e9b73af974d4db454a00eea12ac0755fd6ec..cd06cccd192a7ccf3791d7c9f8ae31fca23c8f09 100644 (file)
@@ -77,11 +77,11 @@ The paraslash executables
 
 *para_server*
 
-para_server streams binary audio data (MP3, OGG/Vorbis, M4A, WMA
-files) over local and/or remote networks. It listens on a TCP port and
-accepts commands such as play, stop, pause, next from authenticated
-clients. There are many more commands though, see the man page of
-para_server for a description of all commands.
+para_server streams binary audio data (MP3, OGG/Vorbis, OGG/Speex,
+M4A, WMA files) over local and/or remote networks. It listens on a
+TCP port and accepts commands such as play, stop, pause, next from
+authenticated clients. There are many more commands though, see the
+man page of para_server for a description of all commands.
 
 It supports three built-in network streaming protocols
 (senders/receivers): HTTP, DCCP, or UDP. This is explained in more
@@ -145,11 +145,11 @@ compatible with arbitrary HTTP streaming sources (e.g. icecast).
 *para_filter*
 
 A filter program that reads from STDIN and writes to STDOUT.
-Like para_recv, this is an atomic building block which can be used
-to assemble higher-level audio receiving facilities. It combines
-several different functionalities in one tool: decoders for multiple
-audio formats (MP3, OGG/Vorbis, AAC, WMA) and a number of processing
-filters, among these a normalizer for audio volume.
+Like para_recv, this is an atomic building block which can be used to
+assemble higher-level audio receiving facilities. It combines several
+different functionalities in one tool: decoders for multiple audio
+formats and a number of processing filters, among these a normalizer
+for audio volume.
 
 *para_afh*
 
@@ -244,6 +244,9 @@ Optional:
        - XREFERENCE(http://www.audiocoding.com/, libfaad).  For aac
        files (m4a) you'll need libfaad (libfaad-dev).
 
+       - XREFERENCE(http://www.speex.org/, speex). In order to stream
+       or decode speex files, libspeex (libspeex-dev) is required.
+
        - XREFERENCE(ftp://ftp.alsa-project.org/pub/lib/, alsa-lib). On
        Linux, you'll need to have ALSA's development package
        libasound2-dev installed.
@@ -255,10 +258,10 @@ First make sure all non-optional packages listed in the section on
 REFERENCE(Requirements, required software) are installed on your
 system.
 
-You don't need everything listed there. In particular, MP3, OGG/Vorbis
-and AAC support are all optional. The configure script will detect
-what is installed on your system and will only try to build those
-executables that can be built with your setup.
+You don't need everything listed there. In particular, MP3, OGG/Vorbis,
+OGG/Speex and AAC support are all optional. The configure script will
+detect what is installed on your system and will only try to build
+those executables that can be built with your setup.
 
 Note that no special decoder library (not even the MP3 decoding library
 libmad) is needed for para_server if you only want to stream MP3 or WMA
@@ -665,7 +668,7 @@ file. In particular the following data is stored for each audio file.
        - The lyrics id which describes the lyrics associated with
        this audio file.
 
-       - The audio format id (MP3, OGG, AAC, WMA).
+       - The audio format id (MP3, OGG, ...).
 
        - An amplification value that can be used by the amplification
        filter to pre-amplify the decoded audio stream.
@@ -1076,6 +1079,15 @@ chunks called OGG pages. A typical OGG page is about 4KB large. The
 Vorbis codec creates variable-bitrate (VBR) data, where the bitrate
 may vary considerably.
 
+*OGG/Speex*
+
+Speex is an open-source speech codec that is based on CELP (Code
+Excited Linear Prediction) coding. It is designed for voice
+over IP applications, has modest complexity and a small memory
+footprint. Wideband and narrowband (telephone quality) speech are
+supported. As for Vorbis audio, Speex bit-streams are often stored
+in OGG files.
+
 *AAC*
 
 Advanced Audio Coding (AAC) is a standardized, lossy compression
@@ -1122,11 +1134,11 @@ and stored in the audio file table.
 Chunks and chunk tables
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-paraslash uses the word "chunk" as common term for the building
-blocks of an audio file. For MP3 files, a chunk is the same as an
-MP3 frame, while for OGG/Vorbis files, a chunk is an OGG page, etc.
-Therefore the chunk size varies considerably between audio formats,
-from a few hundred bytes (MP3) up to 8K (WMA).
+paraslash uses the word "chunk" as common term for the building blocks
+of an audio file. For MP3 files, a chunk is the same as an MP3 frame,
+while for OGG files a chunk is an OGG page, etc.  Therefore the chunk
+size varies considerably between audio formats, from a few hundred
+bytes (MP3) up to 8K (WMA).
 
 The chunk table contains the offsets within the audio file that
 correspond to the chunk boundaries of the file. Like the meta data,
diff --git a/write.c b/write.c
index 8411003027e33ad3a9bcc43962efcf65547dea04..424286b5a4c21613517d632777d3052578e869fd 100644 (file)
--- a/write.c
+++ b/write.c
@@ -37,8 +37,9 @@ struct check_wav_task {
        int state;
        /** Number of channels. */
        unsigned channels;
-       unsigned sample_rate;
        unsigned sample_format;
+       /** Sample rate specified in wav header given by \a buf. */
+       unsigned sample_rate;
        /** The task structure used by the scheduler. */
        struct task task;
        struct btr_node *btrn;
index d26d42c4d8e6e4f775aa6e7df57300cd587daa6b..331fa460c3c484da1244e9999d12300455fd017d 100644 (file)
@@ -15,4 +15,3 @@ int setup_writer_node(const char *arg, struct btr_node *parent,
 void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
 void get_btr_channels(struct btr_node *btrn, int32_t *result);
 void get_btr_sample_format(struct btr_node *btrn, int32_t *result);
-