summaryrefslogtreecommitdiff
path: root/lib/rbcodec/codecs/speex.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbcodec/codecs/speex.c')
-rw-r--r--lib/rbcodec/codecs/speex.c583
1 files changed, 583 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/speex.c b/lib/rbcodec/codecs/speex.c
new file mode 100644
index 0000000000..ac3bc963b1
--- /dev/null
+++ b/lib/rbcodec/codecs/speex.c
@@ -0,0 +1,583 @@
1/**************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2006 Frederik M.J. Vestre
10 * Based on vorbis.c codec interface:
11 * Copyright (C) 2002 Björn Stenberg
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "libspeex/speex/ogg.h"
24#include "libspeex/speex/speex.h"
25#include "libspeex/speex/speex_callbacks.h"
26#include "libspeex/speex/speex_header.h"
27#include "libspeex/speex/speex_stereo.h"
28#include "libspeex/speex/speex_config_types.h"
29#include "codeclib.h"
30
31/* Room for one stereo frame of max size, 2*640 */
32#define MAX_FRAME_SIZE 1280
33#define CHUNKSIZE 10000 /*2kb*/
34#define SEEK_CHUNKSIZE 7*CHUNKSIZE
35
36CODEC_HEADER
37
38static spx_int16_t output[MAX_FRAME_SIZE] IBSS_ATTR;
39
40static int get_more_data(spx_ogg_sync_state *oy)
41{
42 int bytes;
43 char *buffer;
44
45 buffer = (char *)spx_ogg_sync_buffer(oy,CHUNKSIZE);
46
47 bytes = ci->read_filebuf(buffer, sizeof(char)*CHUNKSIZE);
48
49 spx_ogg_sync_wrote(oy,bytes);
50
51 return bytes;
52}
53
54/* The read/seek functions track absolute position within the stream */
55
56static spx_int64_t get_next_page(spx_ogg_sync_state *oy,spx_ogg_page *og,
57 spx_int64_t boundary)
58{
59 spx_int64_t localoffset = ci->curpos;
60 long more;
61 long ret;
62
63 if (boundary > 0)
64 boundary += ci->curpos;
65
66 while (1) {
67 more = spx_ogg_sync_pageseek(oy,og);
68
69 if (more < 0) {
70 /* skipped n bytes */
71 localoffset-=more;
72 } else {
73 if (more == 0) {
74 /* send more paramedics */
75 if(!boundary)return(-1);
76 {
77 ret = get_more_data(oy);
78 if (ret == 0)
79 return(-2);
80
81 if (ret < 0)
82 return(-3);
83 }
84 } else {
85 /* got a page. Return the offset at the page beginning,
86 advance the internal offset past the page end */
87
88 spx_int64_t ret=localoffset;
89
90 return(ret);
91 }
92 }
93 }
94}
95
96static spx_int64_t seek_backwards(spx_ogg_sync_state *oy, spx_ogg_page *og,
97 spx_int64_t wantedpos)
98{
99 spx_int64_t crofs;
100 spx_int64_t *curoffset=&crofs;
101 *curoffset=ci->curpos;
102 spx_int64_t begin=*curoffset;
103 spx_int64_t end=begin;
104 spx_int64_t ret;
105 spx_int64_t offset=-1;
106 spx_int64_t avgpagelen=-1;
107 spx_int64_t lastgranule=-1;
108
109 short time = -1;
110
111 while (offset == -1) {
112
113 begin -= SEEK_CHUNKSIZE;
114
115 if (begin < 0) {
116 if (time < 0) {
117 begin = 0;
118 time++;
119 } else {
120 LOGF("Can't seek that early:%lld\n",begin);
121 return -3; /* too early */
122 }
123 }
124
125 *curoffset = begin;
126
127 ci->seek_buffer(*curoffset);
128
129 spx_ogg_sync_reset(oy);
130
131 lastgranule = -1;
132
133 while (*curoffset < end) {
134 ret = get_next_page(oy,og,end-*curoffset);
135
136 if (ret > 0) {
137 if (lastgranule != -1) {
138 if (avgpagelen < 0)
139 avgpagelen = (spx_ogg_page_granulepos(og)-lastgranule);
140 else
141 avgpagelen=((spx_ogg_page_granulepos(og)-lastgranule)
142 + avgpagelen) / 2;
143 }
144
145 lastgranule=spx_ogg_page_granulepos(og);
146
147 if ((lastgranule - (avgpagelen/4)) < wantedpos &&
148 (lastgranule + avgpagelen + (avgpagelen/4)) > wantedpos) {
149
150 /*wanted offset found Yeay!*/
151
152 /*LOGF("GnPagefound:%d,%d,%d,%d\n",ret,
153 lastgranule,wantedpos,avgpagelen);*/
154
155 return ret;
156
157 } else if (lastgranule > wantedpos) { /*too late, seek more*/
158 if (offset != -1) {
159 LOGF("Toolate, returnanyway:%lld,%lld,%lld,%lld\n",
160 ret,lastgranule,wantedpos,avgpagelen);
161 return ret;
162 }
163 break;
164 } else{ /*if (spx_ogg_page_granulepos(&og)<wantedpos)*/
165 /*too early*/
166 offset = ret;
167 continue;
168 }
169 } else if (ret == -3)
170 return(-3);
171 else if (ret<=0)
172 break;
173 else if (*curoffset < end) {
174 /*this should not be possible*/
175
176 //LOGF("Seek:get_earlier_page:Offset:not_cached by granule:"\"%d,%d,%d,%d,%d\n",*curoffset,end,begin,wantedpos,curpos);
177
178 offset=ret;
179 }
180 }
181 }
182 return -1;
183}
184
185static int speex_seek_page_granule(spx_int64_t pos, spx_int64_t curpos,
186 spx_ogg_sync_state *oy,
187 spx_int64_t headerssize)
188{
189 /* TODO: Someone may want to try to implement seek to packet,
190 instead of just to page (should be more accurate, not be any
191 faster) */
192
193 spx_int64_t crofs;
194 spx_int64_t *curbyteoffset = &crofs;
195 *curbyteoffset = ci->curpos;
196 spx_int64_t curoffset;
197 curoffset = *curbyteoffset;
198 spx_int64_t offset = 0;
199 spx_ogg_page og = {0,0,0,0};
200 spx_int64_t avgpagelen = -1;
201 spx_int64_t lastgranule = -1;
202
203 if(abs(pos-curpos)>10000 && headerssize>0 && curoffset-headerssize>10000) {
204 /* if seeking for more that 10sec,
205 headersize is known & more than 10kb is played,
206 try to guess a place to seek from the number of
207 bytes playe for this position, this works best when
208 the bitrate is relativly constant.
209 */
210
211 curoffset = (((*curbyteoffset-headerssize) * pos)/curpos)*98/100;
212 if (curoffset < 0)
213 curoffset=0;
214
215 //spx_int64_t toffset=curoffset;
216
217 ci->seek_buffer(curoffset);
218
219 spx_ogg_sync_reset(oy);
220
221 offset = get_next_page(oy,&og,-1);
222
223 if (offset < 0) { /* could not find new page,use old offset */
224 LOGF("Seek/guess/fault:%lld->-<-%d,%lld:%lld,%d,%ld,%d\n",
225 curpos,0,pos,offset,0,
226 ci->curpos,/*stream_length*/0);
227
228 curoffset = *curbyteoffset;
229
230 ci->seek_buffer(curoffset);
231
232 spx_ogg_sync_reset(oy);
233 } else {
234 if (spx_ogg_page_granulepos(&og) == 0 && pos > 5000) {
235 LOGF("SEEK/guess/fault:%lld->-<-%lld,%lld:%lld,%d,%ld,%d\n",
236 curpos,spx_ogg_page_granulepos(&og),pos,
237 offset,0,ci->curpos,/*stream_length*/0);
238
239 curoffset = *curbyteoffset;
240
241 ci->seek_buffer(curoffset);
242
243 spx_ogg_sync_reset(oy);
244 } else {
245 curoffset = offset;
246 curpos = spx_ogg_page_granulepos(&og);
247 }
248 }
249 }
250
251 /* which way do we want to seek? */
252
253 if (curpos > pos) { /* backwards */
254 offset = seek_backwards(oy,&og,pos);
255
256 if (offset > 0) {
257 *curbyteoffset = curoffset;
258 return 1;
259 }
260 } else { /* forwards */
261
262 while ( (offset = get_next_page(oy,&og,-1)) > 0) {
263 if (lastgranule != -1) {
264 if (avgpagelen < 0)
265 avgpagelen = (spx_ogg_page_granulepos(&og) - lastgranule);
266 else
267 avgpagelen = ((spx_ogg_page_granulepos(&og) - lastgranule)
268 + avgpagelen) / 2;
269 }
270
271 lastgranule = spx_ogg_page_granulepos(&og);
272
273 if ( ((lastgranule - (avgpagelen/4)) < pos && ( lastgranule +
274 avgpagelen + (avgpagelen / 4)) > pos) ||
275 lastgranule > pos) {
276
277 /*wanted offset found Yeay!*/
278
279 *curbyteoffset = offset;
280
281 return offset;
282 }
283 }
284 }
285
286 ci->seek_buffer(*curbyteoffset);
287
288 spx_ogg_sync_reset(oy);
289
290 LOGF("Seek failed:%lld\n", offset);
291
292 return -1;
293}
294
295static void *process_header(spx_ogg_packet *op,
296 int enh_enabled,
297 int *frame_size,
298 int *rate,
299 int *nframes,
300 int *channels,
301 SpeexStereoState *stereo,
302 int *extra_headers
303 )
304{
305 void *st;
306 const SpeexMode *mode;
307 SpeexHeader *header;
308 int modeID;
309 SpeexCallback callback;
310
311 header = speex_packet_to_header((char*)op->packet, op->bytes);
312
313 if (!header){
314 DEBUGF ("Cannot read header\n");
315 return NULL;
316 }
317
318 if (header->mode >= SPEEX_NB_MODES){
319 DEBUGF ("Mode does not exist\n");
320 return NULL;
321 }
322
323 modeID = header->mode;
324
325 mode = speex_lib_get_mode(modeID);
326
327 if (header->speex_version_id > 1) {
328 DEBUGF("Undecodeable bitstream");
329 return NULL;
330 }
331
332 if (mode->bitstream_version < header->mode_bitstream_version){
333 DEBUGF("Undecodeable bitstream, newer bitstream");
334 return NULL;
335 }
336
337 if (mode->bitstream_version > header->mode_bitstream_version){
338 DEBUGF("Too old bitstream");
339 return NULL;
340 }
341
342 st = speex_decoder_init(mode);
343 if (!st){
344 DEBUGF("Decoder init failed");
345 return NULL;
346 }
347 speex_decoder_ctl(st, SPEEX_SET_ENH, &enh_enabled);
348 speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, frame_size);
349
350 if (header->nb_channels!=1){
351 callback.callback_id = SPEEX_INBAND_STEREO;
352 callback.func = speex_std_stereo_request_handler;
353 callback.data = stereo;
354 speex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback);
355 }
356 *channels = header->nb_channels;
357
358 if (!*rate)
359 *rate = header->rate;
360
361 speex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, rate);
362
363 *nframes = header->frames_per_packet;
364
365 *extra_headers = header->extra_headers;
366
367 return st;
368}
369
370/* this is the codec entry point */
371enum codec_status codec_main(enum codec_entry_call_reason reason)
372{
373 /* Nothing to do */
374 return CODEC_OK;
375 (void)reason;
376}
377
378/* this is called for each file to process */
379enum codec_status codec_run(void)
380{
381 int error = CODEC_ERROR;
382
383 SpeexBits bits;
384 int eof = 0;
385 spx_ogg_sync_state oy;
386 spx_ogg_page og;
387 spx_ogg_packet op;
388 spx_ogg_stream_state os;
389 spx_int64_t page_granule = 0;
390 spx_int64_t cur_granule = 0;
391 int enh_enabled = 1;
392 int nframes = 2;
393 int eos = 0;
394 SpeexStereoState *stereo;
395 int channels = -1;
396 int samplerate = ci->id3->frequency;
397 int extra_headers = 0;
398 int stream_init = 0;
399 /* rockbox: comment 'set but unused' variables
400 int page_nb_packets;
401 */
402 int frame_size;
403 int packet_count = 0;
404 int lookahead;
405 int headerssize = 0;
406 unsigned long strtoffset = ci->id3->offset;
407 void *st = NULL;
408 int j = 0;
409 intptr_t param;
410
411 memset(&bits, 0, sizeof(bits));
412 memset(&oy, 0, sizeof(oy));
413
414 /* Ogg handling still uses mallocs, so reset the malloc buffer per track */
415 if (codec_init()) {
416 goto exit;
417 }
418
419 ci->seek_buffer(0);
420 ci->set_elapsed(0);
421
422 stereo = speex_stereo_state_init();
423 spx_ogg_sync_init(&oy);
424 spx_ogg_alloc_buffer(&oy,2*CHUNKSIZE);
425
426 codec_set_replaygain(ci->id3);
427
428 eof = 0;
429 while (!eof) {
430 enum codec_command_action action = ci->get_command(&param);
431
432 if (action == CODEC_ACTION_HALT)
433 break;
434
435 /*seek (seeks to the page before the position) */
436 if (action == CODEC_ACTION_SEEK_TIME) {
437 if(samplerate!=0&&packet_count>1){
438 LOGF("Speex seek page:%lld,%lld,%ld,%lld,%d\n",
439 ((spx_int64_t)param/1000) *
440 (spx_int64_t)samplerate,
441 page_granule, (long)param,
442 (page_granule/samplerate)*1000, samplerate);
443
444 speex_seek_page_granule(((spx_int64_t)param/1000) *
445 (spx_int64_t)samplerate,
446 page_granule, &oy, headerssize);
447 }
448
449 ci->set_elapsed(param);
450 ci->seek_complete();
451 }
452
453next_page:
454 /*Get the ogg buffer for writing*/
455 if(get_more_data(&oy)<1){/*read error*/
456 goto done;
457 }
458
459 /* Loop for all complete pages we got (most likely only one) */
460 while (spx_ogg_sync_pageout(&oy, &og) == 1) {
461 int packet_no;
462 if (stream_init == 0) {
463 spx_ogg_stream_init(&os, spx_ogg_page_serialno(&og));
464 stream_init = 1;
465 }
466
467 /* Add page to the bitstream */
468 spx_ogg_stream_pagein(&os, &og);
469
470 page_granule = spx_ogg_page_granulepos(&og);
471 /* page_nb_packets = spx_ogg_page_packets(&og); */
472
473 cur_granule = page_granule;
474
475 /* Extract all available packets */
476 packet_no=0;
477
478 while (!eos && spx_ogg_stream_packetout(&os, &op)==1){
479 /* If first packet, process as Speex header */
480 if (packet_count==0){
481 st = process_header(&op, enh_enabled, &frame_size,
482 &samplerate, &nframes, &channels,
483 stereo, &extra_headers);
484
485 speex_decoder_ctl(st, SPEEX_GET_LOOKAHEAD, &lookahead);
486 if (!nframes)
487 nframes=1;
488
489 if (!st){
490 goto done;
491 }
492
493 ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency);
494 ci->configure(DSP_SET_SAMPLE_DEPTH, 16);
495 if (channels == 2) {
496 ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED);
497 } else if (channels == 1) {
498 ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO);
499 }
500
501 /* Speex header in its own page, add the whole page
502 headersize */
503 headerssize += og.header_len+og.body_len;
504
505 } else if (packet_count<=1+extra_headers){
506 /* add packet to headersize */
507 headerssize += op.bytes;
508
509 /* Ignore extra headers */
510 } else {
511 if (packet_count <= 2+extra_headers) {
512 if (strtoffset) {
513 ci->seek_buffer(strtoffset);
514 spx_ogg_sync_reset(&oy);
515 packet_count++;
516 goto next_page;
517 }
518 }
519 packet_no++;
520
521 if (op.e_o_s) /* End of stream condition */
522 eos=1;
523
524 /* Set Speex bitstream to point to Ogg packet */
525 speex_bits_set_bit_buffer(&bits, (char *)op.packet,
526 op.bytes);
527 for (j = 0; j != nframes; j++){
528 int ret;
529
530 /* Decode frame */
531 ret = speex_decode_int(st, &bits, output);
532
533 if (ret == -1)
534 break;
535
536 if (ret == -2)
537 break;
538
539 if (speex_bits_remaining(&bits) < 0)
540 break;
541
542 if (channels == 2)
543 speex_decode_stereo_int(output, frame_size, stereo);
544
545 if (frame_size > 0) {
546 spx_int16_t *frame_start = output + lookahead;
547
548 if (channels == 2)
549 frame_start += lookahead;
550 ci->pcmbuf_insert(frame_start, NULL,
551 frame_size - lookahead);
552 lookahead = 0;
553 /* 2 bytes/sample */
554 cur_granule += frame_size / 2;
555
556 ci->set_offset((long) ci->curpos);
557
558 ci->set_elapsed((samplerate == 0) ? 0 :
559 cur_granule * 1000 / samplerate);
560 }
561 }
562 }
563 packet_count++;
564 }
565 }
566 }
567
568 error = CODEC_OK;
569done:
570 /* Clean things up for the next track */
571 speex_bits_destroy(&bits);
572
573 if (st)
574 speex_decoder_destroy(st);
575
576 if (stream_init)
577 spx_ogg_stream_destroy(&os);
578
579 spx_ogg_sync_destroy(&oy);
580
581exit:
582 return error;
583}