autodetection: convert path to native separators before displaying it.
[Rockbox.git] / apps / codecs / speex.c
blobdf80f53f768c3406a16cad592a9c4d8058c92da8
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 /* Room for one stereo frame of max size, 2*640 */
30 #define MAX_FRAME_SIZE 1280
31 #define CHUNKSIZE 10000 /*2kb*/
32 #define SEEK_CHUNKSIZE 7*CHUNKSIZE
34 CODEC_HEADER
36 spx_int16_t output[MAX_FRAME_SIZE] IBSS_ATTR;
38 int get_more_data(spx_ogg_sync_state *oy)
40 int bytes;
41 char *buffer;
43 buffer = (char *)spx_ogg_sync_buffer(oy,CHUNKSIZE);
45 bytes = ci->read_filebuf(buffer, sizeof(char)*CHUNKSIZE);
47 spx_ogg_sync_wrote(oy,bytes);
49 return bytes;
52 /* The read/seek functions track absolute position within the stream */
54 static spx_int64_t get_next_page(spx_ogg_sync_state *oy,spx_ogg_page *og,
55 spx_int64_t boundary)
57 spx_int64_t localoffset = ci->curpos;
58 long more;
59 long ret;
61 if (boundary > 0)
62 boundary += ci->curpos;
64 while (1) {
65 more = spx_ogg_sync_pageseek(oy,og);
67 if (more < 0) {
68 /* skipped n bytes */
69 localoffset-=more;
70 } else {
71 if (more == 0) {
72 /* send more paramedics */
73 if(!boundary)return(-1);
75 ret = get_more_data(oy);
76 if (ret == 0)
77 return(-2);
79 if (ret < 0)
80 return(-3);
82 } else {
83 /* got a page. Return the offset at the page beginning,
84 advance the internal offset past the page end */
86 spx_int64_t ret=localoffset;
88 return(ret);
94 static spx_int64_t seek_backwards(spx_ogg_sync_state *oy, spx_ogg_page *og,
95 spx_int64_t wantedpos)
97 spx_int64_t crofs;
98 spx_int64_t *curoffset=&crofs;
99 *curoffset=ci->curpos;
100 spx_int64_t begin=*curoffset;
101 spx_int64_t end=begin;
102 spx_int64_t ret;
103 spx_int64_t offset=-1;
104 spx_int64_t avgpagelen=-1;
105 spx_int64_t lastgranule=-1;
107 short time = -1;
109 while (offset == -1) {
111 begin -= SEEK_CHUNKSIZE;
113 if (begin < 0) {
114 if (time < 0) {
115 begin = 0;
116 time++;
117 } else {
118 LOGF("Can't seek that early:%d\n",begin);
119 return -3; /* too early */
123 *curoffset = begin;
125 ci->seek_buffer(*curoffset);
127 spx_ogg_sync_reset(oy);
129 lastgranule = -1;
131 while (*curoffset < end) {
132 ret = get_next_page(oy,og,end-*curoffset);
134 if (ret > 0) {
135 if (lastgranule != -1) {
136 if (avgpagelen < 0)
137 avgpagelen = (spx_ogg_page_granulepos(og)-lastgranule);
138 else
139 avgpagelen=((spx_ogg_page_granulepos(og)-lastgranule)
140 + avgpagelen) / 2;
143 lastgranule=spx_ogg_page_granulepos(og);
145 if ((lastgranule - (avgpagelen/4)) < wantedpos &&
146 (lastgranule + avgpagelen + (avgpagelen/4)) > wantedpos) {
148 /*wanted offset found Yeay!*/
150 /*LOGF("GnPagefound:%d,%d,%d,%d\n",ret,
151 lastgranule,wantedpos,avgpagelen);*/
153 return ret;
155 } else if (lastgranule > wantedpos) { /*too late, seek more*/
156 if (offset != -1) {
157 LOGF("Toolate, returnanyway:%d,%d,%d,%d\n",
158 ret,lastgranule,wantedpos,avgpagelen);
159 return ret;
161 break;
162 } else{ /*if (spx_ogg_page_granulepos(&og)<wantedpos)*/
163 /*too early*/
164 offset = ret;
165 continue;
167 } else if (ret == -3)
168 return(-3);
169 else if (ret<=0)
170 break;
171 else if (*curoffset < end) {
172 /*this should not be possible*/
174 //LOGF("Seek:get_earlier_page:Offset:not_cached by granule:"\"%d,%d,%d,%d,%d\n",*curoffset,end,begin,wantedpos,curpos);
176 offset=ret;
180 return -1;
183 int speex_seek_page_granule(spx_int64_t pos, spx_int64_t curpos,
184 spx_ogg_sync_state *oy,
185 spx_int64_t headerssize)
187 /* TODO: Someone may want to try to implement seek to packet,
188 instead of just to page (should be more accurate, not be any
189 faster) */
191 spx_int64_t crofs;
192 spx_int64_t *curbyteoffset = &crofs;
193 *curbyteoffset = ci->curpos;
194 spx_int64_t curoffset;
195 curoffset = *curbyteoffset;
196 spx_int64_t offset = 0;
197 spx_ogg_page og = {0,0,0,0};
198 spx_int64_t avgpagelen = -1;
199 spx_int64_t lastgranule = -1;
201 if(abs(pos-curpos)>10000 && headerssize>0 && curoffset-headerssize>10000) {
202 /* if seeking for more that 10sec,
203 headersize is known & more than 10kb is played,
204 try to guess a place to seek from the number of
205 bytes playe for this position, this works best when
206 the bitrate is relativly constant.
209 curoffset = (((*curbyteoffset-headerssize) * pos)/curpos)*98/100;
210 if (curoffset < 0)
211 curoffset=0;
213 //spx_int64_t toffset=curoffset;
215 ci->seek_buffer(curoffset);
217 spx_ogg_sync_reset(oy);
219 offset = get_next_page(oy,&og,-1);
221 if (offset < 0) { /* could not find new page,use old offset */
222 LOGF("Seek/guess/fault:%d->-<-%d,%d:%d,%d,%d\n",
223 curpos,0,pos,offset,0,
224 ci->curpos,/*stream_length*/0);
226 curoffset = *curbyteoffset;
228 ci->seek_buffer(curoffset);
230 spx_ogg_sync_reset(oy);
231 } else {
232 if (spx_ogg_page_granulepos(&og) == 0 && pos > 5000) {
233 LOGF("SEEK/guess/fault:%d->-<-%d,%d:%d,%d,%d\n",
234 curpos,spx_ogg_page_granulepos(&og),pos,
235 offset,0,ci->curpos,/*stream_length*/0);
237 curoffset = *curbyteoffset;
239 ci->seek_buffer(curoffset);
241 spx_ogg_sync_reset(oy);
242 } else {
243 curoffset = offset;
244 curpos = spx_ogg_page_granulepos(&og);
249 /* which way do we want to seek? */
251 if (curpos > pos) { /* backwards */
252 offset = seek_backwards(oy,&og,pos);
254 if (offset > 0) {
255 *curbyteoffset = curoffset;
256 return 1;
258 } else { /* forwards */
260 while ( (offset = get_next_page(oy,&og,-1)) > 0) {
261 if (lastgranule != -1) {
262 if (avgpagelen < 0)
263 avgpagelen = (spx_ogg_page_granulepos(&og) - lastgranule);
264 else
265 avgpagelen = ((spx_ogg_page_granulepos(&og) - lastgranule)
266 + avgpagelen) / 2;
269 lastgranule = spx_ogg_page_granulepos(&og);
271 if ( ((lastgranule - (avgpagelen/4)) < pos && ( lastgranule +
272 avgpagelen + (avgpagelen / 4)) > pos) ||
273 lastgranule > pos) {
275 /*wanted offset found Yeay!*/
277 *curbyteoffset = offset;
279 return offset;
284 ci->seek_buffer(*curbyteoffset);
286 spx_ogg_sync_reset(oy);
288 LOGF("Seek failed:%d\n", offset);
290 return -1;
293 static void *process_header(spx_ogg_packet *op,
294 int enh_enabled,
295 int *frame_size,
296 int *rate,
297 int *nframes,
298 int *channels,
299 SpeexStereoState *stereo,
300 int *extra_headers
303 void *st;
304 const SpeexMode *mode;
305 SpeexHeader *header;
306 int modeID;
307 SpeexCallback callback;
309 header = speex_packet_to_header((char*)op->packet, op->bytes);
311 if (!header){
312 DEBUGF ("Cannot read header\n");
313 return NULL;
316 if (header->mode >= SPEEX_NB_MODES){
317 DEBUGF ("Mode does not exist\n");
318 return NULL;
321 modeID = header->mode;
323 mode = speex_lib_get_mode(modeID);
325 if (header->speex_version_id > 1) {
326 DEBUGF("Undecodeable bitstream");
327 return NULL;
330 if (mode->bitstream_version < header->mode_bitstream_version){
331 DEBUGF("Undecodeable bitstream, newer bitstream");
332 return NULL;
335 if (mode->bitstream_version > header->mode_bitstream_version){
336 DEBUGF("Too old bitstream");
337 return NULL;
340 st = speex_decoder_init(mode);
341 if (!st){
342 DEBUGF("Decoder init failed");
343 return NULL;
345 speex_decoder_ctl(st, SPEEX_SET_ENH, &enh_enabled);
346 speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, frame_size);
348 if (header->nb_channels!=1){
349 callback.callback_id = SPEEX_INBAND_STEREO;
350 callback.func = speex_std_stereo_request_handler;
351 callback.data = stereo;
352 speex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback);
354 *channels = header->nb_channels;
356 if (!*rate)
357 *rate = header->rate;
359 speex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, rate);
361 *nframes = header->frames_per_packet;
363 *extra_headers = header->extra_headers;
365 return st;
368 /* this is the codec entry point */
369 enum codec_status codec_main(void)
371 SpeexBits bits;
372 int error = 0;
373 int eof = 0;
374 spx_ogg_sync_state oy;
375 spx_ogg_page og;
376 spx_ogg_packet op;
377 spx_ogg_stream_state os;
378 spx_int64_t page_granule = 0, cur_granule = 0;
379 int enh_enabled = 1;
380 int nframes = 2;
381 int eos = 0;
382 SpeexStereoState *stereo;
383 int channels = -1;
384 int rate = 0, samplerate = 0;
385 int extra_headers = 0;
386 int stream_init = 0;
387 int page_nb_packets, frame_size, packet_count = 0;
388 int lookahead;
389 int headerssize = -1;
390 unsigned long strtoffset = 0;
391 void *st = NULL;
392 int j = 0;
394 /* Ogg handling still uses mallocs, so reset the malloc buffer per track */
395 next_track:
397 if (codec_init()) {
398 error = CODEC_ERROR;
399 goto exit;
401 stereo = speex_stereo_state_init();
402 strtoffset = ci->id3->offset;
404 while (!*ci->taginfo_ready && !ci->stop_codec)
405 ci->sleep(1);
407 spx_ogg_sync_init(&oy);
408 spx_ogg_alloc_buffer(&oy,2*CHUNKSIZE);
410 samplerate = ci->id3->frequency;
411 codec_set_replaygain(ci->id3);
413 eof = 0;
414 while (!eof) {
415 ci->yield();
416 if (ci->stop_codec || ci->new_track)
417 break;
419 /*seek (seeks to the page before the position) */
420 if (ci->seek_time) {
421 if(samplerate!=0&&packet_count>1){
422 LOGF("Speex seek page:%d,%d,%d,%d\n",
423 ((spx_int64_t)ci->seek_time/1000) *
424 (spx_int64_t)samplerate,
425 page_granule, ci->seek_time,
426 (page_granule/samplerate)*1000, samplerate);
428 speex_seek_page_granule(((spx_int64_t)ci->seek_time/1000) *
429 (spx_int64_t)samplerate,
430 page_granule, &oy, headerssize);
431 ci->seek_complete();
435 next_page:
436 /*Get the ogg buffer for writing*/
437 if(get_more_data(&oy)<1){/*read error*/
438 error=CODEC_ERROR;
439 goto done;
442 /* Loop for all complete pages we got (most likely only one) */
443 while (spx_ogg_sync_pageout(&oy, &og) == 1) {
444 int packet_no;
445 if (stream_init == 0) {
446 spx_ogg_stream_init(&os, spx_ogg_page_serialno(&og));
447 stream_init = 1;
450 /* Add page to the bitstream */
451 spx_ogg_stream_pagein(&os, &og);
453 page_granule = spx_ogg_page_granulepos(&og);
454 page_nb_packets = spx_ogg_page_packets(&og);
456 cur_granule = page_granule;
458 /* Extract all available packets */
459 packet_no=0;
461 while (!eos && spx_ogg_stream_packetout(&os, &op)==1){
462 /* If first packet, process as Speex header */
463 if (packet_count==0){
464 st = process_header(&op, enh_enabled, &frame_size,
465 &samplerate, &nframes, &channels,
466 stereo, &extra_headers);
468 speex_decoder_ctl(st, SPEEX_GET_LOOKAHEAD, &lookahead);
469 if (!nframes)
470 nframes=1;
472 if (!st){
473 error=CODEC_ERROR;
474 goto exit;
477 ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency);
478 ci->configure(DSP_SET_SAMPLE_DEPTH, 16);
479 if (channels == 2) {
480 ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED);
481 } else if (channels == 1) {
482 ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO);
485 /* Speex header in its own page, add the whole page
486 headersize */
487 headerssize += og.header_len+og.body_len;
489 } else if (packet_count<=1+extra_headers){
490 /* add packet to headersize */
491 headerssize += op.bytes;
493 /* Ignore extra headers */
494 } else {
495 if (packet_count <= 2+extra_headers) {
496 if (strtoffset) {
497 ci->seek_buffer(strtoffset);
498 spx_ogg_sync_reset(&oy);
499 packet_count++;
500 goto next_page;
503 packet_no++;
505 if (op.e_o_s) /* End of stream condition */
506 eos=1;
508 /* Set Speex bitstream to point to Ogg packet */
509 speex_bits_set_bit_buffer(&bits, (char *)op.packet,
510 op.bytes);
511 for (j = 0; j != nframes; j++){
512 int ret;
514 /* Decode frame */
515 ret = speex_decode_int(st, &bits, output);
517 if (ret == -1)
518 break;
520 if (ret == -2)
521 break;
523 if (speex_bits_remaining(&bits) < 0)
524 break;
526 if (channels == 2)
527 speex_decode_stereo_int(output, frame_size, stereo);
529 if (frame_size > 0) {
530 spx_int16_t *frame_start = output + lookahead;
532 if (channels == 2)
533 frame_start += lookahead;
534 ci->pcmbuf_insert(frame_start, NULL,
535 frame_size - lookahead);
536 lookahead = 0;
537 /* 2 bytes/sample */
538 cur_granule += frame_size / 2;
540 ci->set_offset((long) ci->curpos);
542 ci->set_elapsed((samplerate == 0) ? 0 :
543 cur_granule * 1000 / samplerate);
547 packet_count++;
552 done:
553 if (ci->request_next_track()) {
555 /* Clean things up for the next track */
557 speex_decoder_destroy(st);
559 if (stream_init == 1)
560 spx_ogg_stream_reset(&os);
562 spx_ogg_sync_reset(&oy);
564 cur_granule = stream_init = rate = samplerate = headerssize
565 = packet_count = eos = 0;
567 goto next_track;
570 error = CODEC_OK;
572 exit:
573 speex_bits_destroy(&bits);
575 if (stream_init)
576 spx_ogg_stream_destroy(&os);
578 spx_ogg_sync_destroy(&oy);
580 return error;