+/*
+ * 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;
+}