Linus Torvalds <torvalds@osdl.org> (for giving us one hell of an
operating system [quote taken from README.linux for DOOM v1.666])
+
+Jean-Marc Valin (speex)
* Runs on Linux, Mac OS, FreeBSD, NetBSD, Solaris and probably other
Unixes
- * Mp3, ogg vorbis, aac (m4a) and wma support
+ * Mp3, ogg/vorbis, ogg/speex, aac (m4a) and wma support
* Local or remote http, dccp, and udp network audio streaming
* IPv6 support
* Forward error correction allows receivers to recover from packet losses
$(man_dir):
mkdir -p $@
+$(object_dir)/spx_common.o: spx_common.c | $(object_dir)
+ @[ -z "$(Q)" ] || echo 'CC $<'
+ $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
+
+$(object_dir)/spxdec_filter.o: spxdec_filter.c | $(object_dir)
+ @[ -z "$(Q)" ] || echo 'CC $<'
+ $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
+
+$(object_dir)/spx_afh.o: spx_afh.c | $(object_dir)
+ @[ -z "$(Q)" ] || echo 'CC $<'
+ $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
+
$(object_dir)/oggdec_filter.o: oggdec_filter.c | $(object_dir)
@[ -z "$(Q)" ] || echo 'CC $<'
- $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
+ $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
$(object_dir)/ogg_afh.o: ogg_afh.c | $(object_dir)
@[ -z "$(Q)" ] || echo 'CC $<'
- $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $<
+ $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
+
+$(object_dir)/ogg_afh_common.o: ogg_afh_common.c | $(object_dir)
+ @[ -z "$(Q)" ] || echo 'CC $<'
+ $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $<
$(object_dir)/mp3dec_filter.o: mp3dec_filter.c | $(object_dir)
@[ -z "$(Q)" ] || echo 'CC $<'
/** \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.
*
#ifdef HAVE_FAAD
void aac_afh_init(struct audio_format_handler *);
#endif
+#ifdef HAVE_SPEEX
+ void spx_afh_init(struct audio_format_handler *);
+#endif
void wma_afh_init(struct audio_format_handler *);
/**
.name = "wma",
.init = wma_afh_init,
},
+ {
+ .name = "spx",
+#ifdef HAVE_SPEEX
+ .init = spx_afh_init,
+#endif
+ },
{
.name = NULL,
}
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));
* 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
mmd->num_commands,
mmd->num_connects,
conf.loglevel_arg,
- supported_audio_formats(),
+ SERVER_AUDIO_FORMATS,
sender_info
);
mutex_unlock(mmd_mutex);
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
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"
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))
#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"), \
#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 \
* 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;
}
*/
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;
}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
* The gory details, listed by topic:
*
* - Audio format handlers: \ref send_common.c \ref mp3_afh.c,
- * \ref ogg_afh.c, \ref aac_afh.c, \ref wma_afh.c,
+ * \ref ogg_afh.c, \ref aac_afh.c, \ref wma_afh.c, \ref spx_afh.c
* - Decoders: \ref mp3dec_filter.c, \ref oggdec_filter.c,
- * \ref aacdec_filter.c, \ref wmadec_filter.c,
+ * \ref aacdec_filter.c, \ref wmadec_filter.c, spxdec_filter.c,
* - Volume normalizer: \ref compress_filter.c,
* - Output: \ref alsa_write.c, \ref osx_write.c, \ref oss_write.c,
* - http: \ref http_recv.c, \ref http_send.c,
--- /dev/null
+/*
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/**
+ * \file spx.h Structures and prototypes common to the speex audio format
+ * handler and the speex decoder.
+ */
+
+/**
+ * Information extracted from the first ogg packet.
+ *
+ * It contains tech data but not the content of the attached comment tags.
+ */
+struct spx_header_info {
+ /** Holds the state of the decoder. */
+ void *state;
+ /** Extracted from header. */
+ int frame_size;
+ /** Current sample rate in Hz. */
+ spx_int32_t sample_rate;
+ /** Current bitrate used by the decoder. */
+ int bitrate;
+ /** Number of frames per packet, extracted from header. */
+ int nframes;
+ /** The number of channels of the current stream. */
+ int channels;
+ /** Only needed for stereo streams. */
+ SpeexStereoState stereo;
+ /** Must be skipped during decode. */
+ int extra_headers;
+ /** Narrow/wide/ultrawide band, bitstream version. */
+ const SpeexMode *mode;
+};
+
+int spx_process_header(unsigned char *packet, long bytes,
+ struct spx_header_info *shi);
+int spx_ctl(void *state, int request, void *ptr);
--- /dev/null
+/*
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
+
+/* Copyright (C) 2002-2006 Jean-Marc Valin
+ File: speexdec.c
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+/** \file spx_afh.c Audio format handler for ogg/speex files. */
+
+#include <stdbool.h>
+#include <ogg/ogg.h>
+#include <regex.h>
+#include <speex/speex.h>
+#include <speex/speex_header.h>
+#include <speex/speex_stereo.h>
+
+#include "para.h"
+#include "afh.h"
+#include "error.h"
+#include "portable_io.h"
+#include "string.h"
+#include "spx.h"
+#include "ogg_afh_common.h"
+
+struct private_spx_data {
+ struct spx_header_info shi;
+};
+
+
+static char *copy_comment(const char *src, int len)
+{
+ char *p = para_malloc(len + 1);
+
+ if (len > 0)
+ memcpy(p, src, len);
+ p[len] = '\0';
+ PARA_DEBUG_LOG("%s\n", p);
+ return p;
+}
+
+static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
+ char **p)
+{
+ int len = strlen(type);
+
+ if (taglen <= len)
+ return false;
+ if (strncasecmp(tag, type, len))
+ return false;
+ if (tag[len] != '=')
+ return false;
+ free(*p);
+ *p = copy_comment(tag + len + 1, taglen - len - 1);
+ return true;
+}
+
+static int spx_get_comments(unsigned char *comments, int length,
+ struct taginfo *tags)
+{
+ char *c = (char *)comments;
+ uint32_t len, nb_fields;
+ int i;
+ char *end;
+
+ if (length < 8)
+ return -E_SPX_COMMENT;
+ end = c + length;
+ len = read_u32(c);
+ c += 4;
+ if (c + len > end)
+ return -E_SPX_COMMENT;
+ tags->comment = copy_comment(c, len);
+
+ c += len;
+ if (c + 4 > end)
+ return -E_SPX_COMMENT;
+ nb_fields = read_u32(c);
+ PARA_DEBUG_LOG("%d comment(s)\n", nb_fields);
+ c += 4;
+ for (i = 0; i < nb_fields; i++, c += len) {
+ char *tag;
+
+ if (c + 4 > end)
+ return -E_SPX_COMMENT;
+ len = read_u32(c);
+ c += 4;
+ if (c + len > end)
+ return -E_SPX_COMMENT;
+ if (copy_if_tag_type(c, len, "author", &tags->artist))
+ continue;
+ if (copy_if_tag_type(c, len, "artist", &tags->artist))
+ continue;
+ if (copy_if_tag_type(c, len, "title", &tags->title))
+ continue;
+ if (copy_if_tag_type(c, len, "album", &tags->album))
+ continue;
+ if (copy_if_tag_type(c, len, "year", &tags->year))
+ continue;
+ if (copy_if_tag_type(c, len, "comment", &tags->comment))
+ continue;
+ tag = copy_comment(c, len);
+ PARA_NOTICE_LOG("unrecognized comment: %s\n", tag);
+ free(tag);
+ }
+ return 1;
+}
+
+static const char* speex_suffixes[] = {"spx", "speex", NULL};
+
+static int spx_packet_callback(ogg_packet *packet, int packet_num,
+ struct afh_info *afhi, void *private_data)
+{
+ struct private_spx_data *psd = private_data;
+ int ret;
+
+ if (packet_num == 0) {
+ ret = spx_process_header(packet->packet, packet->bytes,
+ &psd->shi);
+ if (ret < 0)
+ return ret;
+ afhi->channels = psd->shi.channels;
+ afhi->frequency = psd->shi.sample_rate;
+ afhi->bitrate = psd->shi.bitrate / 1000;
+ afhi->techinfo = make_message("%s, v%d", psd->shi.mode->modeName,
+ psd->shi.mode->bitstream_version);
+ return 1;
+ }
+ if (packet_num == 1) {
+ ret = spx_get_comments(packet->packet, packet->bytes,
+ &afhi->tags);
+ if (ret < 0)
+ return ret;
+ return 0; /* header complete */
+ }
+ /* never reached */
+ return 0;
+}
+
+static int spx_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+ struct afh_info *afhi)
+{
+ struct private_spx_data psd;
+ struct ogg_afh_callback_info spx_callback_info = {
+ .packet_callback = spx_packet_callback,
+ .private_data = &psd,
+ };
+
+ memset(&psd, 0, sizeof(psd));
+ return ogg_get_file_info(map, numbytes, afhi, &spx_callback_info);
+}
+
+/**
+ * The init function of the ogg/speex audio format handler.
+ *
+ * \param afh Pointer to the struct to initialize.
+ */
+void spx_afh_init(struct audio_format_handler *afh)
+{
+ afh->get_file_info = spx_get_file_info,
+ afh->suffixes = speex_suffixes;
+}
--- /dev/null
+/*
+ * Copyright (C) 2002-2006 Jean-Marc Valin
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/**
+ * \file spx_common.c Functions used by the speex decoder and the speex audio
+ * format handler.
+ */
+
+/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
+
+/* Copyright (C) 2002-2006 Jean-Marc Valin
+ File: speexdec.c
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#include <regex.h>
+#include <speex/speex_header.h>
+#include <speex/speex_stereo.h>
+#include <speex/speex_callbacks.h>
+
+#include "para.h"
+#include "error.h"
+#include "spx.h"
+
+/**
+ * Wrapper for speex_decoder_ctl().
+ *
+ * \param state Decoder state.
+ * \param request ioctl-type request.
+ * \param ptr Value-result pointer.
+ *
+ * \return Standard.
+ */
+int spx_ctl(void *state, int request, void *ptr)
+{
+ int ret = speex_decoder_ctl(state, request, ptr);
+
+ if (ret == 0) /* success */
+ return 1;
+ if (ret == -1)
+ return -E_SPX_CTL_BAD_RQ;
+ return -E_SPX_CTL_INVAL;
+}
+
+/**
+ * Obtain information about a speex file from an ogg packet.
+ *
+ * \param packet Start of the ogg packet.
+ * \param bytes Length of the ogg packet.
+ * \param shi Result pointer.
+ *
+ * \return Standard.
+ */
+int spx_process_header(unsigned char *packet, long bytes,
+ struct spx_header_info *shi)
+{
+ int ret;
+ spx_int32_t enh_enabled = 1;
+ SpeexHeader *h = speex_packet_to_header((char *)packet, bytes);
+
+ if (!h)
+ return -E_SPX_HEADER;
+ ret = -E_SPX_HEADER_MODE;
+ if (h->mode >= SPEEX_NB_MODES || h->mode < 0)
+ goto out;
+ shi->mode = speex_lib_get_mode(h->mode);
+
+ ret = -E_SPX_VERSION;
+ if (h->speex_version_id > 1)
+ goto out;
+ if (shi->mode->bitstream_version < h->mode_bitstream_version)
+ goto out;
+ if (shi->mode->bitstream_version > h->mode_bitstream_version)
+ goto out;
+
+ ret = -E_SPX_DECODER_INIT;
+ shi->state = speex_decoder_init(shi->mode);
+ if (!shi->state)
+ goto out;
+
+ ret = spx_ctl(shi->state, SPEEX_SET_ENH, &enh_enabled);
+ if (ret < 0)
+ goto out;
+ ret = spx_ctl(shi->state, SPEEX_GET_FRAME_SIZE, &shi->frame_size);
+ if (ret < 0)
+ goto out;
+ shi->sample_rate = h->rate;
+ ret = spx_ctl(shi->state, SPEEX_SET_SAMPLING_RATE, &shi->sample_rate);
+ if (ret < 0)
+ goto out;
+ shi->nframes = h->frames_per_packet;
+ shi->channels = h->nb_channels;
+ if (shi->channels != 1) {
+ shi->stereo = (SpeexStereoState)SPEEX_STEREO_STATE_INIT;
+ SpeexCallback callback = {
+ .callback_id = SPEEX_INBAND_STEREO,
+ .func = speex_std_stereo_request_handler,
+ .data = &shi->stereo,
+ };
+ ret = spx_ctl(shi->state, SPEEX_SET_HANDLER, &callback);
+ if (ret < 0)
+ goto out;
+ shi->channels = 2;
+ }
+ ret = spx_ctl(shi->state, SPEEX_GET_BITRATE, &shi->bitrate);
+ if (ret < 0)
+ goto out;
+ PARA_NOTICE_LOG("%d Hz, %s, %s, %s, %d bits/s\n",
+ shi->sample_rate, shi->mode->modeName,
+ shi->channels == 1? "mono" : "stereo",
+ h->vbr? "vbr" : "cbr",
+ shi->bitrate
+ );
+ shi->extra_headers = h->extra_headers;
+ ret = 1;
+out:
+ free(h);
+ return ret;
+}
--- /dev/null
+/*
+ * Copyright (C) 2002-2006 Jean-Marc Valin
+ * Copyright (C) 2010 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file spxdec_filter.c Paraslash's ogg/speex decoder. */
+
+/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
+
+/* Copyright (C) 2002-2006 Jean-Marc Valin
+ File: speexdec.c
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ - Neither the name of the Xiph.org Foundation nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <regex.h>
+#include <ogg/ogg.h>
+#include <speex/speex.h>
+#include <stdbool.h>
+#include <speex/speex_header.h>
+#include <speex/speex_stereo.h>
+#include <speex/speex_callbacks.h>
+
+#include "para.h"
+#include "list.h"
+#include "sched.h"
+#include "ggo.h"
+#include "buffer_tree.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+#include "spx.h"
+
+/** Data specific to the speexdec filter. */
+struct private_spxdec_data {
+ /** Header information obtained from the first ogg packet. */
+ struct spx_header_info shi;
+ /** Read from and written to by the Speex routines. */
+ SpeexBits bits;
+ /* Ogg pages are retrieved from this structure. */
+ ogg_sync_state oy;
+ /** Extracted from header. */
+ int speex_serialno;
+ /** Total number of ogg packets retrieved so far. */
+ int packet_count;
+ /** Needed to find out how much to skip. */
+ ogg_int64_t last_granule;
+ /** Also needed for skipping packets. */
+ int lookahead;
+ /** The state information about the current stream. */
+ ogg_stream_state os;
+ /** Whether \a os initialized. */
+ bool stream_init;
+};
+
+static void spxdec_open(struct filter_node *fn)
+{
+ struct private_spxdec_data *psd = para_calloc(sizeof(*psd));
+
+ fn->private_data = psd;
+ fn->min_iqs = 200;
+ psd->speex_serialno = -1;
+}
+
+static void speexdec_close(struct filter_node *fn)
+{
+ struct private_spxdec_data *psd = fn->private_data;
+
+ if (psd->shi.state) {
+ /* Destroy the decoder state */
+ speex_decoder_destroy(psd->shi.state);
+ /* Destroy the bit-stream struct */
+ speex_bits_destroy(&psd->bits);
+ }
+ free(psd);
+ fn->private_data = NULL;
+}
+
+static int speexdec_execute(struct btr_node *btrn, const char *cmd,
+ char **result)
+{
+ struct filter_node *fn = btr_context(btrn);
+ struct private_spxdec_data *psd = fn->private_data;
+
+ return decoder_execute(cmd, psd->shi.sample_rate, psd->shi.channels,
+ result);
+}
+
+static int speexdec_init(struct filter_node *fn)
+{
+ struct private_spxdec_data *psd = fn->private_data;
+
+ PARA_INFO_LOG("init\n");
+ ogg_sync_init(&psd->oy);
+ speex_bits_init(&psd->bits);
+ return 1;
+}
+
+#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) )
+#define le_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8))
+#else
+#define le_short(s) ((short) (s))
+#endif
+
+#define MAX_FRAME_SIZE 2000
+/* Copy Ogg packet to Speex bitstream */
+static int speexdec_write_frames(int packet_no,
+ struct private_spxdec_data *psd, int skip_samples,
+ int page_nb_packets, struct btr_node *btrn)
+{
+ int i, j;
+
+ for (j = 0; j != psd->shi.nframes; j++) {
+ short output[MAX_FRAME_SIZE], *btr_output;
+ int skip = skip_samples + psd->lookahead, skip_idx = 0;
+ int samples, new_frame_size = psd->shi.frame_size;
+
+ if (speex_decode_int(psd->shi.state, &psd->bits, output) < 0)
+ return -E_SPX_DECODE;
+ if (speex_bits_remaining(&psd->bits) < 0)
+ return -E_SPX_DECODE_OVERFLOW;
+ if (psd->shi.channels == 2)
+ speex_decode_stereo_int(output, psd->shi.frame_size,
+ &psd->shi.stereo);
+ if (packet_no == 1 && j == 0 && skip_samples > 0) {
+ new_frame_size -= skip;
+ skip_idx = skip * psd->shi.channels;
+ }
+ if (packet_no == page_nb_packets && skip_samples < 0) {
+ new_frame_size = psd->shi.nframes * psd->shi.frame_size
+ + skip - j * psd->shi.frame_size;
+ if (new_frame_size < 0)
+ new_frame_size = 0;
+ if (new_frame_size > psd->shi.frame_size)
+ new_frame_size = psd->shi.frame_size;
+ }
+ if (new_frame_size <= 0)
+ continue;
+ samples = new_frame_size * psd->shi.channels;
+ btr_output = para_malloc(2 * samples);
+ for (i = 0; i < samples; i++)
+ btr_output[i] = le_short(output[i + skip_idx]);
+ btr_add_output((char *)btr_output, samples * 2, btrn);
+ }
+ return 1;
+}
+
+/* Extract all available packets */
+static int speexdec_extract_packets(struct private_spxdec_data *psd,
+ ogg_stream_state *os, int skip_samples, int page_nb_packets,
+ struct btr_node *btrn)
+{
+ int ret, packet_no;
+ bool eos = false;
+
+ for (packet_no = 0;; psd->packet_count++) {
+ ogg_packet op;
+
+ if (ogg_stream_packetout(os, &op) != 1)
+ return 0;
+ if (op.bytes >= 5 && !memcmp(op.packet, "Speex", 5))
+ psd->speex_serialno = os->serialno;
+ if (psd->speex_serialno == -1)
+ return 0;
+ if (os->serialno != psd->speex_serialno)
+ return 0;
+ /* If first packet, process as Speex header */
+ if (psd->packet_count == 0) {
+ ret = spx_process_header(op.packet, op.bytes, &psd->shi);
+ if (ret < 0)
+ return ret;
+ ret = speex_decoder_ctl(psd->shi.state, SPEEX_GET_LOOKAHEAD,
+ &psd->lookahead);
+ if (ret < 0)
+ return ret;
+ if (!psd->shi.nframes)
+ psd->shi.nframes = 1;
+ PARA_INFO_LOG("frame size: %d\n", psd->shi.frame_size);
+ continue;
+ }
+ if (psd->packet_count == 1) /* ignore comments */
+ continue;
+ if (psd->packet_count <= 1 + psd->shi.extra_headers)
+ continue; /* Ignore extra headers */
+ packet_no++;
+ /* check end of stream condition */
+ if (op.e_o_s && os->serialno == psd->speex_serialno)
+ eos = true;
+ speex_bits_read_from(&psd->bits, (char *)op.packet,
+ op.bytes);
+ ret = speexdec_write_frames(packet_no, psd,
+ skip_samples, page_nb_packets, btrn);
+ if (ret < 0)
+ return ret;
+ if (eos == true)
+ return -E_SPX_EOS;
+ }
+}
+
+static int compute_skip_samples(ogg_page *og, struct private_spxdec_data *psd)
+{
+ int ret, page_granule = ogg_page_granulepos(og);
+
+ if (page_granule <= 0)
+ return 0;
+ if (psd->shi.frame_size == 0)
+ return 0;
+ ret = ogg_page_packets(og) * psd->shi.frame_size * psd->shi.nframes
+ - page_granule + psd->last_granule;
+ if (ogg_page_eos(og))
+ ret = -ret;
+ return ret;
+}
+
+static void speexdec_post_select(__a_unused struct sched *s, struct task *t)
+{
+ struct filter_node *fn = container_of(t, struct filter_node, task);
+ struct private_spxdec_data *psd = fn->private_data;
+ struct btr_node *btrn = fn->btrn;
+ int ret, ns;
+ ogg_page og;
+ char *btr_buf;
+ size_t nbytes;
+
+next_buffer:
+ t->error = 0;
+ ret = ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+ btr_merge(btrn, fn->min_iqs);
+ if (!psd->shi.state) {
+ if (ret <= 0)
+ goto fail;
+ ret = speexdec_init(fn);
+ if (ret <= 0)
+ goto fail;
+ }
+ nbytes = btr_next_buffer(btrn, &btr_buf);
+ nbytes = PARA_MIN(nbytes, (size_t)200);
+ if (nbytes > 0) {
+ char *data = ogg_sync_buffer(&psd->oy, nbytes);
+ memcpy(data, btr_buf, nbytes);
+ btr_consume(btrn, nbytes);
+ ogg_sync_wrote(&psd->oy, nbytes);
+ }
+ /* Loop for all complete pages we got */
+ while (ogg_sync_pageout(&psd->oy, &og) == 1) {
+ int skip_samples;
+
+ if (psd->stream_init == false) {
+ ogg_stream_init(&psd->os, ogg_page_serialno(&og));
+ psd->stream_init = true;
+ }
+ if (ogg_page_serialno(&og) != psd->os.serialno)
+ ogg_stream_reset_serialno(&psd->os, ogg_page_serialno(&og));
+ /* Add page to the bitstream */
+ ogg_stream_pagein(&psd->os, &og);
+ skip_samples = compute_skip_samples(&og, psd);
+ psd->last_granule = ogg_page_granulepos(&og);
+ ret = speexdec_extract_packets(psd, &psd->os, skip_samples,
+ ogg_page_packets(&og), btrn);
+ if (ret < 0)
+ goto fail;
+ }
+ if (ns > 0)
+ goto next_buffer;
+ ret = ns;
+fail:
+ if (ret < 0) {
+ t->error = ret;
+ btr_remove_node(btrn);
+ }
+}
+
+/**
+ * The init function of the ogg/speex decoder.
+ *
+ * \param f Its fields are filled in by the function.
+ */
+void spxdec_filter_init(struct filter *f)
+{
+ f->open = spxdec_open;
+ f->close = speexdec_close;
+ f->pre_select = generic_filter_pre_select;
+ f->post_select = speexdec_post_select;
+ f->execute = speexdec_execute;
+}
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;
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
*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
*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*
- 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.
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
- 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.
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
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,
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;
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);
-