rockbox/apps/codecs/speex.c
Daniel Stenberg 2acc0ac542 Updated our source code header to explicitly mention that we are GPL v2 or
later. We still need to hunt down snippets used that are not. 1324 modified
files...
http://www.rockbox.org/mail/archive/rockbox-dev-archive-2008-06/0060.shtml


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17847 a1c6a512-1295-4272-9138-f99709370657
2008-06-28 18:10:04 +00:00

583 lines
17 KiB
C

/**************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2006 Frederik M.J. Vestre
* Based on vorbis.c codec interface:
* Copyright (C) 2002 Bjrn 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
spx_int16_t output[MAX_FRAME_SIZE] IBSS_ATTR;
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:%d\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:%d,%d,%d,%d\n",
ret,lastgranule,wantedpos,avgpagelen);
return ret;
}
break;
} else{ /*if (spx_ogg_page_granulepos(&og)<wantedpos)*/
/*too early*/
offset = ret;
continue;
}
} else if (ret == -3)
return(-3);
else if (ret<=0)
break;
else if (*curoffset < end) {
/*this should not be possible*/
//LOGF("Seek:get_earlier_page:Offset:not_cached by granule:"\"%d,%d,%d,%d,%d\n",*curoffset,end,begin,wantedpos,curpos);
offset=ret;
}
}
}
return -1;
}
int speex_seek_page_granule(spx_int64_t pos, spx_int64_t curpos,
spx_ogg_sync_state *oy,
spx_int64_t headerssize)
{
/* TODO: Someone may want to try to implement seek to packet,
instead of just to page (should be more accurate, not be any
faster) */
spx_int64_t crofs;
spx_int64_t *curbyteoffset = &crofs;
*curbyteoffset = ci->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:%d->-<-%d,%d:%d,%d,%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:%d->-<-%d,%d:%d,%d,%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:%d\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(void)
{
SpeexBits bits;
int error = 0;
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, cur_granule = 0;
int enh_enabled = 1;
int nframes = 2;
int eos = 0;
SpeexStereoState *stereo;
int channels = -1;
int rate = 0, samplerate = 0;
int extra_headers = 0;
int stream_init = 0;
int page_nb_packets, frame_size, packet_count = 0;
int lookahead;
int headerssize = -1;
unsigned long strtoffset = 0;
void *st = NULL;
int j = 0;
/* Ogg handling still uses mallocs, so reset the malloc buffer per track */
next_track:
if (codec_init()) {
error = CODEC_ERROR;
goto exit;
}
stereo = speex_stereo_state_init();
strtoffset = ci->id3->offset;
while (!*ci->taginfo_ready && !ci->stop_codec)
ci->sleep(1);
spx_ogg_sync_init(&oy);
spx_ogg_alloc_buffer(&oy,2*CHUNKSIZE);
samplerate = ci->id3->frequency;
codec_set_replaygain(ci->id3);
eof = 0;
while (!eof) {
ci->yield();
if (ci->stop_codec || ci->new_track)
break;
/*seek (seeks to the page before the position) */
if (ci->seek_time) {
if(samplerate!=0&&packet_count>1){
LOGF("Speex seek page:%d,%d,%d,%d\n",
((spx_int64_t)ci->seek_time/1000) *
(spx_int64_t)samplerate,
page_granule, ci->seek_time,
(page_granule/samplerate)*1000, samplerate);
speex_seek_page_granule(((spx_int64_t)ci->seek_time/1000) *
(spx_int64_t)samplerate,
page_granule, &oy, headerssize);
ci->seek_complete();
}
}
next_page:
/*Get the ogg buffer for writing*/
if(get_more_data(&oy)<1){/*read error*/
error=CODEC_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){
error=CODEC_ERROR;
goto exit;
}
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++;
}
}
}
done:
if (ci->request_next_track()) {
/* Clean things up for the next track */
speex_decoder_destroy(st);
if (stream_init == 1)
spx_ogg_stream_reset(&os);
spx_ogg_sync_reset(&oy);
cur_granule = stream_init = rate = samplerate = headerssize
= packet_count = eos = 0;
goto next_track;
}
error = CODEC_OK;
exit:
speex_bits_destroy(&bits);
if (stream_init)
spx_ogg_stream_destroy(&os);
spx_ogg_sync_destroy(&oy);
return error;
}