Improved FPS test plugin: * Better precision for low frame rates (take extra ticks...
[Rockbox.git] / apps / codecs / speex.c
blobd26d6f9d83572e510852296e06de6fa67b921ecd
1 /**************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2006 Frederik M.J. Vestre
10 * Based on vorbis.c codec interface:
11 * Copyright (C) 2002 Bjrn Stenberg
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
19 ***************************************************************************/
21 #include "libspeex/speex/ogg.h"
22 #include "libspeex/speex/speex.h"
23 #include "libspeex/speex/speex_callbacks.h"
24 #include "libspeex/speex/speex_header.h"
25 #include "libspeex/speex/speex_stereo.h"
26 #include "libspeex/speex/speex_config_types.h"
27 #include "codeclib.h"
29 #define MAX_FRAME_SIZE 2000
30 #define CHUNKSIZE 10000 /*2kb*/
31 #define SEEK_CHUNKSIZE 7*CHUNKSIZE
33 CODEC_HEADER
35 spx_int16_t output[MAX_FRAME_SIZE] IBSS_ATTR;
37 int get_more_data(spx_ogg_sync_state *oy)
39 int bytes;
40 char *buffer;
42 buffer = (char *)spx_ogg_sync_buffer(oy,CHUNKSIZE);
44 bytes = ci->read_filebuf(buffer, sizeof(char)*CHUNKSIZE);
46 spx_ogg_sync_wrote(oy,bytes);
48 return bytes;
51 /* The read/seek functions track absolute position within the stream */
53 static spx_int64_t get_next_page(spx_ogg_sync_state *oy,spx_ogg_page *og,
54 spx_int64_t boundary)
56 spx_int64_t localoffset = ci->curpos;
57 long more;
58 long ret;
60 if (boundary > 0)
61 boundary += ci->curpos;
63 while (1) {
64 more = spx_ogg_sync_pageseek(oy,og);
66 if (more < 0) {
67 /* skipped n bytes */
68 localoffset-=more;
69 } else {
70 if (more == 0) {
71 /* send more paramedics */
72 if(!boundary)return(-1);
74 ret = get_more_data(oy);
75 if (ret == 0)
76 return(-2);
78 if (ret < 0)
79 return(-3);
81 } else {
82 /* got a page. Return the offset at the page beginning,
83 advance the internal offset past the page end */
85 spx_int64_t ret=localoffset;
87 return(ret);
93 static spx_int64_t seek_backwards(spx_ogg_sync_state *oy, spx_ogg_page *og,
94 spx_int64_t wantedpos)
96 spx_int64_t crofs;
97 spx_int64_t *curoffset=&crofs;
98 *curoffset=ci->curpos;
99 spx_int64_t begin=*curoffset;
100 spx_int64_t end=begin;
101 spx_int64_t ret;
102 spx_int64_t offset=-1;
103 spx_int64_t avgpagelen=-1;
104 spx_int64_t lastgranule=-1;
106 short time = -1;
108 while (offset == -1) {
110 begin -= SEEK_CHUNKSIZE;
112 if (begin < 0) {
113 if (time < 0) {
114 begin = 0;
115 time++;
116 } else {
117 LOGF("Can't seek that early:%d\n",begin);
118 return -3; /* too early */
122 *curoffset = begin;
124 ci->seek_buffer(*curoffset);
126 spx_ogg_sync_reset(oy);
128 lastgranule = -1;
130 while (*curoffset < end) {
131 ret = get_next_page(oy,og,end-*curoffset);
133 if (ret > 0) {
134 if (lastgranule != -1) {
135 if (avgpagelen < 0)
136 avgpagelen = (spx_ogg_page_granulepos(og)-lastgranule);
137 else
138 avgpagelen=((spx_ogg_page_granulepos(og)-lastgranule)
139 + avgpagelen) / 2;
142 lastgranule=spx_ogg_page_granulepos(og);
144 if ((lastgranule - (avgpagelen/4)) < wantedpos &&
145 (lastgranule + avgpagelen + (avgpagelen/4)) > wantedpos) {
147 /*wanted offset found Yeay!*/
149 /*LOGF("GnPagefound:%d,%d,%d,%d\n",ret,
150 lastgranule,wantedpos,avgpagelen);*/
152 return ret;
154 } else if (lastgranule > wantedpos) { /*too late, seek more*/
155 if (offset != -1) {
156 LOGF("Toolate, returnanyway:%d,%d,%d,%d\n",
157 ret,lastgranule,wantedpos,avgpagelen);
158 return ret;
160 break;
161 } else{ /*if (spx_ogg_page_granulepos(&og)<wantedpos)*/
162 /*too early*/
163 offset = ret;
164 continue;
166 } else if (ret == -3)
167 return(-3);
168 else if (ret<=0)
169 break;
170 else if (*curoffset < end) {
171 /*this should not be possible*/
173 //LOGF("Seek:get_earlier_page:Offset:not_cached by granule:"\"%d,%d,%d,%d,%d\n",*curoffset,end,begin,wantedpos,curpos);
175 offset=ret;
179 return -1;
182 int speex_seek_page_granule(spx_int64_t pos, spx_int64_t curpos,
183 spx_ogg_sync_state *oy,
184 spx_int64_t headerssize)
186 /* TODO: Someone may want to try to implement seek to packet,
187 instead of just to page (should be more accurate, not be any
188 faster) */
190 spx_int64_t crofs;
191 spx_int64_t *curbyteoffset = &crofs;
192 *curbyteoffset = ci->curpos;
193 spx_int64_t curoffset;
194 curoffset = *curbyteoffset;
195 spx_int64_t offset = 0;
196 spx_ogg_page og = {0,0,0,0};
197 spx_int64_t avgpagelen = -1;
198 spx_int64_t lastgranule = -1;
200 if(abs(pos-curpos)>10000 && headerssize>0 && curoffset-headerssize>10000) {
201 /* if seeking for more that 10sec,
202 headersize is known & more than 10kb is played,
203 try to guess a place to seek from the number of
204 bytes playe for this position, this works best when
205 the bitrate is relativly constant.
208 curoffset = (int)((((float)(*curbyteoffset-(headerssize)) *
209 (float)pos)/(float)curpos)*0.98);
211 if (curoffset < 0)
212 curoffset=0;
214 //spx_int64_t toffset=curoffset;
216 ci->seek_buffer(curoffset);
218 spx_ogg_sync_reset(oy);
220 offset = get_next_page(oy,&og,-1);
222 if (offset < 0) { /* could not find new page,use old offset */
223 LOGF("Seek/guess/fault:%d->-<-%d,%d:%d,%d,%d\n",
224 curpos,0,pos,offset,0,
225 ci->curpos,/*stream_length*/0);
227 curoffset = *curbyteoffset;
229 ci->seek_buffer(curoffset);
231 spx_ogg_sync_reset(oy);
232 } else {
233 if (spx_ogg_page_granulepos(&og) == 0 && pos > 5000) {
234 LOGF("SEEK/guess/fault:%d->-<-%d,%d:%d,%d,%d\n",
235 curpos,spx_ogg_page_granulepos(&og),pos,
236 offset,0,ci->curpos,/*stream_length*/0);
238 curoffset = *curbyteoffset;
240 ci->seek_buffer(curoffset);
242 spx_ogg_sync_reset(oy);
243 } else {
244 curoffset = offset;
245 curpos = spx_ogg_page_granulepos(&og);
250 /* which way do we want to seek? */
252 if (curpos > pos) { /* backwards */
253 offset = seek_backwards(oy,&og,pos);
255 if (offset > 0) {
256 *curbyteoffset = curoffset;
257 return 1;
259 } else { /* forwards */
261 while ( (offset = get_next_page(oy,&og,-1)) > 0) {
262 if (lastgranule != -1) {
263 if (avgpagelen < 0)
264 avgpagelen = (spx_ogg_page_granulepos(&og) - lastgranule);
265 else
266 avgpagelen = ((spx_ogg_page_granulepos(&og) - lastgranule)
267 + avgpagelen) / 2;
270 lastgranule = spx_ogg_page_granulepos(&og);
272 if ( ((lastgranule - (avgpagelen/4)) < pos && ( lastgranule +
273 avgpagelen + (avgpagelen / 4)) > pos) ||
274 lastgranule > pos) {
276 /*wanted offset found Yeay!*/
278 *curbyteoffset = offset;
280 return offset;
285 ci->seek_buffer(*curbyteoffset);
287 spx_ogg_sync_reset(oy);
289 LOGF("Seek failed:%d\n", offset);
291 return -1;
294 static void *process_header(spx_ogg_packet *op,
295 int enh_enabled,
296 int *frame_size,
297 int *rate,
298 int *nframes,
299 int *channels,
300 SpeexStereoState *stereo,
301 int *extra_headers
304 void *st;
305 const SpeexMode *mode;
306 SpeexHeader *header;
307 int modeID;
308 SpeexCallback callback;
310 header = speex_packet_to_header((char*)op->packet, op->bytes);
312 if (!header){
313 DEBUGF ("Cannot read header\n");
314 return NULL;
317 if (header->mode >= SPEEX_NB_MODES){
318 DEBUGF ("Mode does not exist\n");
319 return NULL;
322 modeID = header->mode;
324 mode = speex_lib_get_mode(modeID);
326 if (header->speex_version_id > 1) {
327 DEBUGF("Undecodeable bitstream");
328 return NULL;
331 if (mode->bitstream_version < header->mode_bitstream_version){
332 DEBUGF("Undecodeable bitstream, newer bitstream");
333 return NULL;
336 if (mode->bitstream_version > header->mode_bitstream_version){
337 DEBUGF("Too old bitstream");
338 return NULL;
341 st = speex_decoder_init(mode);
342 if (!st){
343 DEBUGF("Decoder init failed");
344 return NULL;
346 speex_decoder_ctl(st, SPEEX_SET_ENH, &enh_enabled);
347 speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, frame_size);
349 if (!(*channels==1)){
350 callback.callback_id = SPEEX_INBAND_STEREO;
351 callback.func = speex_std_stereo_request_handler;
352 callback.data = stereo;
353 speex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback);
355 if (!*rate)
356 *rate = header->rate;
358 speex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, rate);
360 *nframes = header->frames_per_packet;
362 if (*channels == -1)
363 *channels = header->nb_channels;
365 *extra_headers = header->extra_headers;
367 codec_free(header);
368 return st;
371 /* this is the codec entry point */
372 enum codec_status codec_main(void)
374 SpeexBits bits;
375 int error = 0;
376 int eof = 0;
377 spx_ogg_sync_state oy;
378 spx_ogg_page og;
379 spx_ogg_packet op;
380 spx_ogg_stream_state os;
381 spx_int64_t page_granule = 0, cur_granule = 0;
382 int enh_enabled = 1;
383 int nframes = 2;
384 int eos = 0;
385 SpeexStereoState stereo = SPEEX_STEREO_STATE_INIT;
386 int channels = -1;
387 int rate = 0, samplerate = 0;
388 int extra_headers = 0;
389 int stream_init = 0;
390 int page_nb_packets, frame_size, packet_count = 0;
391 int lookahead = 0;
392 int headerssize = -1;
393 unsigned long strtoffset = 0;
394 void *st = NULL;
395 int j = 0;
397 /* We need to flush reserver memory every track load. */
398 next_track:
400 if (codec_init()) {
401 error = CODEC_ERROR;
402 goto exit;
405 strtoffset = ci->id3->offset;
407 while (!*ci->taginfo_ready && !ci->stop_codec)
408 ci->sleep(1);
410 spx_ogg_sync_init(&oy);
411 spx_ogg_alloc_buffer(&oy,2*CHUNKSIZE);
413 samplerate = ci->id3->frequency;
414 codec_set_replaygain(ci->id3);
416 speex_bits_init(&bits);
418 eof = 0;
419 while (!eof) {
420 ci->yield();
421 if (ci->stop_codec || ci->new_track)
422 break;
424 /*seek (seeks to the page before the position) */
425 if (ci->seek_time) {
426 if(samplerate!=0&&packet_count>1){
427 LOGF("Speex seek page:%d,%d,%d,%d\n",
428 ((spx_int64_t)ci->seek_time/1000) *
429 (spx_int64_t)samplerate,
430 page_granule, ci->seek_time,
431 (page_granule/samplerate)*1000, samplerate);
433 speex_seek_page_granule(((spx_int64_t)ci->seek_time/1000) *
434 (spx_int64_t)samplerate,
435 page_granule, &oy, headerssize);
436 ci->seek_complete();
440 next_page:
441 /*Get the ogg buffer for writing*/
442 if(get_more_data(&oy)<1){/*read error*/
443 error=CODEC_ERROR;
444 goto done;
447 /* Loop for all complete pages we got (most likely only one) */
448 while (spx_ogg_sync_pageout(&oy, &og) == 1) {
449 int packet_no;
450 if (stream_init == 0) {
451 spx_ogg_stream_init(&os, spx_ogg_page_serialno(&og));
452 stream_init = 1;
455 /* Add page to the bitstream */
456 spx_ogg_stream_pagein(&os, &og);
458 page_granule = spx_ogg_page_granulepos(&og);
459 page_nb_packets = spx_ogg_page_packets(&og);
461 cur_granule = page_granule;
463 /* Extract all available packets */
464 packet_no=0;
466 while (!eos && spx_ogg_stream_packetout(&os, &op)==1){
467 /* If first packet, process as Speex header */
468 if (packet_count==0){
469 st = process_header(&op, enh_enabled, &frame_size,
470 &samplerate, &nframes, &channels,
471 &stereo, &extra_headers);
473 speex_decoder_ctl(st, SPEEX_GET_LOOKAHEAD, &lookahead);
475 if (!nframes)
476 nframes=1;
478 if (!st){
479 error=CODEC_ERROR;
480 goto exit;
483 ci->id3->vbr = true;
484 ci->id3->frequency = samplerate;
485 ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency);
486 ci->configure(DSP_SET_SAMPLE_DEPTH, 16);
487 if (channels == 2) {
488 ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED);
489 } else if (channels == 1) {
490 ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO);
493 /* Speex header in its own page, add the whole page
494 headersize */
495 headerssize += og.header_len+og.body_len;
497 } else if (packet_count<=1+extra_headers){
498 /* add packet to headersize */
499 headerssize += op.bytes;
501 /* Ignore extra headers */
502 } else {
503 if (packet_count <= 2+extra_headers) {
504 if (strtoffset) {
505 ci->seek_buffer(strtoffset);
506 spx_ogg_sync_reset(&oy);
507 packet_count++;
508 goto next_page;
511 packet_no++;
513 if (op.e_o_s) /* End of stream condition */
514 eos=1;
516 /* Copy Ogg packet to Speex bitstream */
518 speex_bits_read_from(&bits, (char*)op.packet, op.bytes);
520 for (j = 0; j != nframes; j++){
521 int ret;
523 /* Decode frame */
524 ret = speex_decode_int(st, &bits, output);
526 if (ret == -1)
527 break;
529 if (ret == -2)
530 break;
532 if (speex_bits_remaining(&bits) < 0)
533 break;
535 if (channels == 2)
536 speex_decode_stereo_int(output, frame_size, &stereo);
538 int new_frame_size = frame_size;
540 if (new_frame_size > 0) {
541 ci->pcmbuf_insert(output, NULL, new_frame_size);
543 /* 2 bytes/sample */
544 cur_granule += new_frame_size / 2;
546 ci->set_offset((long) ci->curpos);
548 ci->set_elapsed((samplerate == 0) ? 0 :
549 cur_granule * 1000 / samplerate);
553 packet_count++;
558 done:
559 if (ci->request_next_track()) {
561 /* Clean things up for the next track */
563 speex_decoder_destroy(st);
564 speex_bits_reset(&bits);
566 if (stream_init == 1)
567 spx_ogg_stream_reset(&os);
569 spx_ogg_sync_reset(&oy);
571 cur_granule = stream_init = rate = samplerate = headerssize
572 = packet_count = eos = 0;
574 stereo.balance = stereo.smooth_left = stereo.smooth_right = 1;
575 stereo.e_ratio = .5;
576 stereo.reserved1 = stereo.reserved2 = 0;
578 goto next_track;
581 error = CODEC_OK;
583 exit:
584 speex_bits_destroy(&bits);
586 if (stream_init)
587 spx_ogg_stream_destroy(&os);
589 spx_ogg_sync_destroy(&oy);
591 return error;