2 * Copyright (C) 2002-2006 Jean-Marc Valin
3 * Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>
5 * Licensed under the GPL v2. For licencing details see COPYING.
8 /** \file spxdec_filter.c Paraslash's ogg/speex decoder. */
10 /* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
12 /* Copyright (C) 2002-2006 Jean-Marc Valin
15 Redistribution and use in source and binary forms, with or without
16 modification, are permitted provided that the following conditions
19 - Redistributions of source code must retain the above copyright
20 notice, this list of conditions and the following disclaimer.
22 - Redistributions in binary form must reproduce the above copyright
23 notice, this list of conditions and the following disclaimer in the
24 documentation and/or other materials provided with the distribution.
26 - Neither the name of the Xiph.org Foundation nor the names of its
27 contributors may be used to endorse or promote products derived from
28 this software without specific prior written permission.
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
34 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
35 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
36 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
37 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
38 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
39 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
40 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45 #include <speex/speex.h>
46 #include <speex/speex_header.h>
47 #include <speex/speex_stereo.h>
48 #include <speex/speex_callbacks.h>
54 #include "buffer_tree.h"
60 /** Data specific to the speexdec filter. */
61 struct private_spxdec_data {
62 /** Header information obtained from the first ogg packet. */
63 struct spx_header_info shi;
64 /** Read from and written to by the Speex routines. */
66 /* Ogg pages are retrieved from this structure. */
68 /** Extracted from header. */
70 /** Total number of ogg packets retrieved so far. */
72 /** Needed to find out how much to skip. */
73 ogg_int64_t last_granule;
74 /** Also needed for skipping packets. */
76 /** The state information about the current stream. */
78 /** Whether \a os initialized. */
82 static void spxdec_open(struct filter_node *fn)
84 struct private_spxdec_data *psd = para_calloc(sizeof(*psd));
86 fn->private_data = psd;
88 psd->speex_serialno = -1;
91 static void speexdec_close(struct filter_node *fn)
93 struct private_spxdec_data *psd = fn->private_data;
96 /* Destroy the decoder state */
97 speex_decoder_destroy(psd->shi.state);
98 /* Destroy the bit-stream struct */
99 speex_bits_destroy(&psd->bits);
102 fn->private_data = NULL;
105 static int speexdec_execute(struct btr_node *btrn, const char *cmd,
108 struct filter_node *fn = btr_context(btrn);
109 struct private_spxdec_data *psd = fn->private_data;
111 return decoder_execute(cmd, psd->shi.sample_rate, psd->shi.channels,
115 static int speexdec_init(struct filter_node *fn)
117 struct private_spxdec_data *psd = fn->private_data;
119 PARA_INFO_LOG("init\n");
120 ogg_sync_init(&psd->oy);
121 speex_bits_init(&psd->bits);
125 #if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) )
126 #define le_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8))
128 #define le_short(s) ((short) (s))
131 #define MAX_FRAME_SIZE 2000
132 /* Copy Ogg packet to Speex bitstream */
133 static int speexdec_write_frames(int packet_no,
134 struct private_spxdec_data *psd, int skip_samples,
135 int page_nb_packets, struct btr_node *btrn)
139 for (j = 0; j != psd->shi.nframes; j++) {
140 short output[MAX_FRAME_SIZE], *btr_output;
141 int skip = skip_samples + psd->lookahead, skip_idx = 0;
142 int samples, new_frame_size = psd->shi.frame_size;
144 if (speex_decode_int(psd->shi.state, &psd->bits, output) < 0)
145 return -E_SPX_DECODE;
146 if (speex_bits_remaining(&psd->bits) < 0)
147 return -E_SPX_DECODE_OVERFLOW;
148 if (psd->shi.channels == 2)
149 speex_decode_stereo_int(output, psd->shi.frame_size,
151 if (packet_no == 1 && j == 0 && skip_samples > 0) {
152 new_frame_size -= skip;
153 skip_idx = skip * psd->shi.channels;
155 if (packet_no == page_nb_packets && skip_samples < 0) {
156 new_frame_size = psd->shi.nframes * psd->shi.frame_size
157 + skip - j * psd->shi.frame_size;
158 if (new_frame_size < 0)
160 if (new_frame_size > psd->shi.frame_size)
161 new_frame_size = psd->shi.frame_size;
163 if (new_frame_size <= 0)
165 samples = new_frame_size * psd->shi.channels;
166 btr_output = para_malloc(2 * samples);
167 for (i = 0; i < samples; i++)
168 btr_output[i] = le_short(output[i + skip_idx]);
169 btr_add_output((char *)btr_output, samples * 2, btrn);
174 /* Extract all available packets */
175 static int speexdec_extract_packets(struct private_spxdec_data *psd,
176 ogg_stream_state *os, int skip_samples, int page_nb_packets,
177 struct btr_node *btrn)
182 for (packet_no = 0;; psd->packet_count++) {
185 if (ogg_stream_packetout(os, &op) != 1)
187 if (op.bytes >= 5 && !memcmp(op.packet, "Speex", 5))
188 psd->speex_serialno = os->serialno;
189 if (psd->speex_serialno == -1)
191 if (os->serialno != psd->speex_serialno)
193 /* If first packet, process as Speex header */
194 if (psd->packet_count == 0) {
195 ret = spx_process_header(op.packet, op.bytes, &psd->shi);
198 ret = speex_decoder_ctl(psd->shi.state, SPEEX_GET_LOOKAHEAD,
202 if (!psd->shi.nframes)
203 psd->shi.nframes = 1;
204 PARA_INFO_LOG("frame size: %d\n", psd->shi.frame_size);
207 if (psd->packet_count == 1) /* ignore comments */
209 if (psd->packet_count <= 1 + psd->shi.extra_headers)
210 continue; /* Ignore extra headers */
212 /* check end of stream condition */
213 if (op.e_o_s && os->serialno == psd->speex_serialno)
215 speex_bits_read_from(&psd->bits, (char *)op.packet,
217 ret = speexdec_write_frames(packet_no, psd,
218 skip_samples, page_nb_packets, btrn);
226 static int compute_skip_samples(ogg_page *og, struct private_spxdec_data *psd)
228 int ret, page_granule = ogg_page_granulepos(og);
230 if (page_granule <= 0)
232 if (psd->shi.frame_size == 0)
234 ret = ogg_page_packets(og) * psd->shi.frame_size * psd->shi.nframes
235 - page_granule + psd->last_granule;
236 if (ogg_page_eos(og))
241 static int speexdec_post_select(__a_unused struct sched *s, void *context)
243 struct filter_node *fn = context;
244 struct private_spxdec_data *psd = fn->private_data;
245 struct btr_node *btrn = fn->btrn;
252 ret = ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
253 btr_merge(btrn, fn->min_iqs);
254 if (!psd->shi.state) {
257 ret = speexdec_init(fn);
261 nbytes = btr_next_buffer(btrn, &btr_buf);
262 nbytes = PARA_MIN(nbytes, (size_t)200);
264 char *data = ogg_sync_buffer(&psd->oy, nbytes);
265 memcpy(data, btr_buf, nbytes);
266 btr_consume(btrn, nbytes);
267 ogg_sync_wrote(&psd->oy, nbytes);
269 /* Loop for all complete pages we got */
270 while (ogg_sync_pageout(&psd->oy, &og) == 1) {
273 if (psd->stream_init == false) {
274 ogg_stream_init(&psd->os, ogg_page_serialno(&og));
275 psd->stream_init = true;
277 if (ogg_page_serialno(&og) != psd->os.serialno)
278 ogg_stream_reset_serialno(&psd->os, ogg_page_serialno(&og));
279 /* Add page to the bitstream */
280 ogg_stream_pagein(&psd->os, &og);
281 skip_samples = compute_skip_samples(&og, psd);
282 psd->last_granule = ogg_page_granulepos(&og);
283 ret = speexdec_extract_packets(psd, &psd->os, skip_samples,
284 ogg_page_packets(&og), btrn);
293 btr_remove_node(&fn->btrn);
298 * The init function of the ogg/speex decoder.
300 * \param f Its fields are filled in by the function.
302 void spxdec_filter_init(struct filter *f)
304 f->open = spxdec_open;
305 f->close = speexdec_close;
306 f->pre_select = generic_filter_pre_select;
307 f->post_select = speexdec_post_select;
308 f->execute = speexdec_execute;