]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - spxdec_filter.c
Add support for the speex codec.
[paraslash.git] / spxdec_filter.c
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;
+}