From f40bfc9267b13b54e6379dfe7539447662879d24 Mon Sep 17 00:00:00 2001 From: Sean Bartell Date: Sat, 25 Jun 2011 21:32:25 -0400 Subject: Add codecs to librbcodec. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Id7f4717d51ed02d67cb9f9cb3c0ada4a81843f97 Reviewed-on: http://gerrit.rockbox.org/137 Reviewed-by: Nils Wallménius Tested-by: Nils Wallménius --- lib/rbcodec/codecs/speex.c | 583 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 583 insertions(+) create mode 100644 lib/rbcodec/codecs/speex.c (limited to 'lib/rbcodec/codecs/speex.c') 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 @@ +/************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2006 Frederik M.J. Vestre + * Based on vorbis.c codec interface: + * Copyright (C) 2002 Björn Stenberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "libspeex/speex/ogg.h" +#include "libspeex/speex/speex.h" +#include "libspeex/speex/speex_callbacks.h" +#include "libspeex/speex/speex_header.h" +#include "libspeex/speex/speex_stereo.h" +#include "libspeex/speex/speex_config_types.h" +#include "codeclib.h" + +/* Room for one stereo frame of max size, 2*640 */ +#define MAX_FRAME_SIZE 1280 +#define CHUNKSIZE 10000 /*2kb*/ +#define SEEK_CHUNKSIZE 7*CHUNKSIZE + +CODEC_HEADER + +static spx_int16_t output[MAX_FRAME_SIZE] IBSS_ATTR; + +static int get_more_data(spx_ogg_sync_state *oy) +{ + int bytes; + char *buffer; + + buffer = (char *)spx_ogg_sync_buffer(oy,CHUNKSIZE); + + bytes = ci->read_filebuf(buffer, sizeof(char)*CHUNKSIZE); + + spx_ogg_sync_wrote(oy,bytes); + + return bytes; +} + +/* The read/seek functions track absolute position within the stream */ + +static spx_int64_t get_next_page(spx_ogg_sync_state *oy,spx_ogg_page *og, + spx_int64_t boundary) +{ + spx_int64_t localoffset = ci->curpos; + long more; + long ret; + + if (boundary > 0) + boundary += ci->curpos; + + while (1) { + more = spx_ogg_sync_pageseek(oy,og); + + if (more < 0) { + /* skipped n bytes */ + localoffset-=more; + } else { + if (more == 0) { + /* send more paramedics */ + if(!boundary)return(-1); + { + ret = get_more_data(oy); + if (ret == 0) + return(-2); + + if (ret < 0) + return(-3); + } + } else { + /* got a page. Return the offset at the page beginning, + advance the internal offset past the page end */ + + spx_int64_t ret=localoffset; + + return(ret); + } + } + } +} + +static spx_int64_t seek_backwards(spx_ogg_sync_state *oy, spx_ogg_page *og, + spx_int64_t wantedpos) +{ + spx_int64_t crofs; + spx_int64_t *curoffset=&crofs; + *curoffset=ci->curpos; + spx_int64_t begin=*curoffset; + spx_int64_t end=begin; + spx_int64_t ret; + spx_int64_t offset=-1; + spx_int64_t avgpagelen=-1; + spx_int64_t lastgranule=-1; + + short time = -1; + + while (offset == -1) { + + begin -= SEEK_CHUNKSIZE; + + if (begin < 0) { + if (time < 0) { + begin = 0; + time++; + } else { + LOGF("Can't seek that early:%lld\n",begin); + return -3; /* too early */ + } + } + + *curoffset = begin; + + ci->seek_buffer(*curoffset); + + spx_ogg_sync_reset(oy); + + lastgranule = -1; + + while (*curoffset < end) { + ret = get_next_page(oy,og,end-*curoffset); + + if (ret > 0) { + if (lastgranule != -1) { + if (avgpagelen < 0) + avgpagelen = (spx_ogg_page_granulepos(og)-lastgranule); + else + avgpagelen=((spx_ogg_page_granulepos(og)-lastgranule) + + avgpagelen) / 2; + } + + lastgranule=spx_ogg_page_granulepos(og); + + if ((lastgranule - (avgpagelen/4)) < wantedpos && + (lastgranule + avgpagelen + (avgpagelen/4)) > wantedpos) { + + /*wanted offset found Yeay!*/ + + /*LOGF("GnPagefound:%d,%d,%d,%d\n",ret, + lastgranule,wantedpos,avgpagelen);*/ + + return ret; + + } else if (lastgranule > wantedpos) { /*too late, seek more*/ + if (offset != -1) { + LOGF("Toolate, returnanyway:%lld,%lld,%lld,%lld\n", + ret,lastgranule,wantedpos,avgpagelen); + return ret; + } + break; + } else{ /*if (spx_ogg_page_granulepos(&og)curpos; + spx_int64_t curoffset; + curoffset = *curbyteoffset; + spx_int64_t offset = 0; + spx_ogg_page og = {0,0,0,0}; + spx_int64_t avgpagelen = -1; + spx_int64_t lastgranule = -1; + + if(abs(pos-curpos)>10000 && headerssize>0 && curoffset-headerssize>10000) { + /* if seeking for more that 10sec, + headersize is known & more than 10kb is played, + try to guess a place to seek from the number of + bytes playe for this position, this works best when + the bitrate is relativly constant. + */ + + curoffset = (((*curbyteoffset-headerssize) * pos)/curpos)*98/100; + if (curoffset < 0) + curoffset=0; + + //spx_int64_t toffset=curoffset; + + ci->seek_buffer(curoffset); + + spx_ogg_sync_reset(oy); + + offset = get_next_page(oy,&og,-1); + + if (offset < 0) { /* could not find new page,use old offset */ + LOGF("Seek/guess/fault:%lld->-<-%d,%lld:%lld,%d,%ld,%d\n", + curpos,0,pos,offset,0, + ci->curpos,/*stream_length*/0); + + curoffset = *curbyteoffset; + + ci->seek_buffer(curoffset); + + spx_ogg_sync_reset(oy); + } else { + if (spx_ogg_page_granulepos(&og) == 0 && pos > 5000) { + LOGF("SEEK/guess/fault:%lld->-<-%lld,%lld:%lld,%d,%ld,%d\n", + curpos,spx_ogg_page_granulepos(&og),pos, + offset,0,ci->curpos,/*stream_length*/0); + + curoffset = *curbyteoffset; + + ci->seek_buffer(curoffset); + + spx_ogg_sync_reset(oy); + } else { + curoffset = offset; + curpos = spx_ogg_page_granulepos(&og); + } + } + } + + /* which way do we want to seek? */ + + if (curpos > pos) { /* backwards */ + offset = seek_backwards(oy,&og,pos); + + if (offset > 0) { + *curbyteoffset = curoffset; + return 1; + } + } else { /* forwards */ + + while ( (offset = get_next_page(oy,&og,-1)) > 0) { + if (lastgranule != -1) { + if (avgpagelen < 0) + avgpagelen = (spx_ogg_page_granulepos(&og) - lastgranule); + else + avgpagelen = ((spx_ogg_page_granulepos(&og) - lastgranule) + + avgpagelen) / 2; + } + + lastgranule = spx_ogg_page_granulepos(&og); + + if ( ((lastgranule - (avgpagelen/4)) < pos && ( lastgranule + + avgpagelen + (avgpagelen / 4)) > pos) || + lastgranule > pos) { + + /*wanted offset found Yeay!*/ + + *curbyteoffset = offset; + + return offset; + } + } + } + + ci->seek_buffer(*curbyteoffset); + + spx_ogg_sync_reset(oy); + + LOGF("Seek failed:%lld\n", offset); + + return -1; +} + +static void *process_header(spx_ogg_packet *op, + int enh_enabled, + int *frame_size, + int *rate, + int *nframes, + int *channels, + SpeexStereoState *stereo, + int *extra_headers + ) +{ + void *st; + const SpeexMode *mode; + SpeexHeader *header; + int modeID; + SpeexCallback callback; + + header = speex_packet_to_header((char*)op->packet, op->bytes); + + if (!header){ + DEBUGF ("Cannot read header\n"); + return NULL; + } + + if (header->mode >= SPEEX_NB_MODES){ + DEBUGF ("Mode does not exist\n"); + return NULL; + } + + modeID = header->mode; + + mode = speex_lib_get_mode(modeID); + + if (header->speex_version_id > 1) { + DEBUGF("Undecodeable bitstream"); + return NULL; + } + + if (mode->bitstream_version < header->mode_bitstream_version){ + DEBUGF("Undecodeable bitstream, newer bitstream"); + return NULL; + } + + if (mode->bitstream_version > header->mode_bitstream_version){ + DEBUGF("Too old bitstream"); + return NULL; + } + + st = speex_decoder_init(mode); + if (!st){ + DEBUGF("Decoder init failed"); + return NULL; + } + speex_decoder_ctl(st, SPEEX_SET_ENH, &enh_enabled); + speex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, frame_size); + + if (header->nb_channels!=1){ + callback.callback_id = SPEEX_INBAND_STEREO; + callback.func = speex_std_stereo_request_handler; + callback.data = stereo; + speex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback); + } + *channels = header->nb_channels; + + if (!*rate) + *rate = header->rate; + + speex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, rate); + + *nframes = header->frames_per_packet; + + *extra_headers = header->extra_headers; + + return st; +} + +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + /* Nothing to do */ + return CODEC_OK; + (void)reason; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + int error = CODEC_ERROR; + + SpeexBits bits; + int eof = 0; + spx_ogg_sync_state oy; + spx_ogg_page og; + spx_ogg_packet op; + spx_ogg_stream_state os; + spx_int64_t page_granule = 0; + spx_int64_t cur_granule = 0; + int enh_enabled = 1; + int nframes = 2; + int eos = 0; + SpeexStereoState *stereo; + int channels = -1; + int samplerate = ci->id3->frequency; + int extra_headers = 0; + int stream_init = 0; + /* rockbox: comment 'set but unused' variables + int page_nb_packets; + */ + int frame_size; + int packet_count = 0; + int lookahead; + int headerssize = 0; + unsigned long strtoffset = ci->id3->offset; + void *st = NULL; + int j = 0; + intptr_t param; + + memset(&bits, 0, sizeof(bits)); + memset(&oy, 0, sizeof(oy)); + + /* Ogg handling still uses mallocs, so reset the malloc buffer per track */ + if (codec_init()) { + goto exit; + } + + ci->seek_buffer(0); + ci->set_elapsed(0); + + stereo = speex_stereo_state_init(); + spx_ogg_sync_init(&oy); + spx_ogg_alloc_buffer(&oy,2*CHUNKSIZE); + + codec_set_replaygain(ci->id3); + + eof = 0; + while (!eof) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + + /*seek (seeks to the page before the position) */ + if (action == CODEC_ACTION_SEEK_TIME) { + if(samplerate!=0&&packet_count>1){ + LOGF("Speex seek page:%lld,%lld,%ld,%lld,%d\n", + ((spx_int64_t)param/1000) * + (spx_int64_t)samplerate, + page_granule, (long)param, + (page_granule/samplerate)*1000, samplerate); + + speex_seek_page_granule(((spx_int64_t)param/1000) * + (spx_int64_t)samplerate, + page_granule, &oy, headerssize); + } + + ci->set_elapsed(param); + ci->seek_complete(); + } + +next_page: + /*Get the ogg buffer for writing*/ + if(get_more_data(&oy)<1){/*read error*/ + goto done; + } + + /* Loop for all complete pages we got (most likely only one) */ + while (spx_ogg_sync_pageout(&oy, &og) == 1) { + int packet_no; + if (stream_init == 0) { + spx_ogg_stream_init(&os, spx_ogg_page_serialno(&og)); + stream_init = 1; + } + + /* Add page to the bitstream */ + spx_ogg_stream_pagein(&os, &og); + + page_granule = spx_ogg_page_granulepos(&og); + /* page_nb_packets = spx_ogg_page_packets(&og); */ + + cur_granule = page_granule; + + /* Extract all available packets */ + packet_no=0; + + while (!eos && spx_ogg_stream_packetout(&os, &op)==1){ + /* If first packet, process as Speex header */ + if (packet_count==0){ + st = process_header(&op, enh_enabled, &frame_size, + &samplerate, &nframes, &channels, + stereo, &extra_headers); + + speex_decoder_ctl(st, SPEEX_GET_LOOKAHEAD, &lookahead); + if (!nframes) + nframes=1; + + if (!st){ + goto done; + } + + ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency); + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + if (channels == 2) { + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + } else if (channels == 1) { + ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); + } + + /* Speex header in its own page, add the whole page + headersize */ + headerssize += og.header_len+og.body_len; + + } else if (packet_count<=1+extra_headers){ + /* add packet to headersize */ + headerssize += op.bytes; + + /* Ignore extra headers */ + } else { + if (packet_count <= 2+extra_headers) { + if (strtoffset) { + ci->seek_buffer(strtoffset); + spx_ogg_sync_reset(&oy); + packet_count++; + goto next_page; + } + } + packet_no++; + + if (op.e_o_s) /* End of stream condition */ + eos=1; + + /* Set Speex bitstream to point to Ogg packet */ + speex_bits_set_bit_buffer(&bits, (char *)op.packet, + op.bytes); + for (j = 0; j != nframes; j++){ + int ret; + + /* Decode frame */ + ret = speex_decode_int(st, &bits, output); + + if (ret == -1) + break; + + if (ret == -2) + break; + + if (speex_bits_remaining(&bits) < 0) + break; + + if (channels == 2) + speex_decode_stereo_int(output, frame_size, stereo); + + if (frame_size > 0) { + spx_int16_t *frame_start = output + lookahead; + + if (channels == 2) + frame_start += lookahead; + ci->pcmbuf_insert(frame_start, NULL, + frame_size - lookahead); + lookahead = 0; + /* 2 bytes/sample */ + cur_granule += frame_size / 2; + + ci->set_offset((long) ci->curpos); + + ci->set_elapsed((samplerate == 0) ? 0 : + cur_granule * 1000 / samplerate); + } + } + } + packet_count++; + } + } + } + + error = CODEC_OK; +done: + /* Clean things up for the next track */ + speex_bits_destroy(&bits); + + if (st) + speex_decoder_destroy(st); + + if (stream_init) + spx_ogg_stream_destroy(&os); + + spx_ogg_sync_destroy(&oy); + +exit: + return error; +} -- cgit v1.2.3