forked from len0rd/rockbox
Initial opus codec support
Synchronised with opus repo on github (https://github.com/freqmod/rockbox-opus) Status: * Seeking ported from speex, but fails on some cases (e.g. seek to granule 0) * ReplayGain parsing needs to be reworked, we do vorbis-style replaygain now. http://wiki.xiph.org/OggOpus#Comment_Header explicitly forbids these in favour of R128_TRACK_GAIN tag. * No optimisation yet, source files still nearly identical to opus upstream * Multi-stream opus files may not be parsed correctly Change-Id: Ia66f1027dc1d288083e3c57b2816700078376f9a Reviewed-on: http://gerrit.rockbox.org/300 Reviewed-by: Bertrik Sikken <bertrik@sikken.nl> Tested-by: Bertrik Sikken <bertrik@sikken.nl>
This commit is contained in:
parent
72ebcbf73b
commit
1b8e3801b2
127 changed files with 28373 additions and 2 deletions
461
lib/rbcodec/codecs/opus.c
Normal file
461
lib/rbcodec/codecs/opus.c
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2012 Frederik M.J. Vestre
|
||||
* Based on speex.c codec interface:
|
||||
* 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 "codeclib.h"
|
||||
#include "inttypes.h"
|
||||
#include "libopus/opus.h"
|
||||
#include "libopus/opus_header.h"
|
||||
|
||||
|
||||
#include "libopus/ogg/ogg.h"
|
||||
#ifdef SIMULATOR
|
||||
#include <tlsf.h>
|
||||
#endif
|
||||
|
||||
CODEC_HEADER
|
||||
|
||||
#define SEEK_REWIND 3840 /* 80 ms @ 48 kHz */
|
||||
|
||||
/* the opus pseudo stack pointer */
|
||||
extern char *global_stack;
|
||||
|
||||
/* Room for 120 ms of stereo audio at 48 kHz */
|
||||
#define MAX_FRAME_SIZE (2*120*48)
|
||||
#define CHUNKSIZE (16*1024)
|
||||
#define SEEK_CHUNKSIZE 7*CHUNKSIZE
|
||||
|
||||
static int get_more_data(ogg_sync_state *oy)
|
||||
{
|
||||
int bytes;
|
||||
char *buffer;
|
||||
|
||||
buffer = (char *)ogg_sync_buffer(oy, CHUNKSIZE);
|
||||
bytes = ci->read_filebuf(buffer, CHUNKSIZE);
|
||||
ogg_sync_wrote(oy,bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
/* The read/seek functions track absolute position within the stream */
|
||||
static int64_t get_next_page(ogg_sync_state *oy, ogg_page *og,
|
||||
int64_t boundary)
|
||||
{
|
||||
int64_t localoffset = ci->curpos;
|
||||
long more;
|
||||
long ret;
|
||||
|
||||
if (boundary > 0)
|
||||
boundary += ci->curpos;
|
||||
|
||||
while (1) {
|
||||
more = ogg_sync_pageseek(oy,og);
|
||||
|
||||
if (more < 0) {
|
||||
/* skipped n bytes */
|
||||
localoffset-=more;
|
||||
} else {
|
||||
if (more == 0) {
|
||||
/* send more data */
|
||||
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 */
|
||||
|
||||
int64_t ret=localoffset;
|
||||
|
||||
return(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t seek_backwards(ogg_sync_state *oy, ogg_page *og,
|
||||
int64_t wantedpos)
|
||||
{
|
||||
int64_t crofs;
|
||||
int64_t *curoffset=&crofs;
|
||||
*curoffset=ci->curpos;
|
||||
int64_t begin=*curoffset;
|
||||
int64_t end=begin;
|
||||
int64_t ret;
|
||||
int64_t offset=-1;
|
||||
int64_t avgpagelen=-1;
|
||||
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);
|
||||
|
||||
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 = (ogg_page_granulepos(og)-lastgranule);
|
||||
else
|
||||
avgpagelen=((ogg_page_granulepos(og)-lastgranule)
|
||||
+ avgpagelen) / 2;
|
||||
}
|
||||
|
||||
lastgranule=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 (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;
|
||||
}
|
||||
|
||||
static int speex_seek_page_granule(int64_t pos, int64_t curpos,
|
||||
ogg_sync_state *oy,
|
||||
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) */
|
||||
|
||||
int64_t crofs;
|
||||
int64_t *curbyteoffset = &crofs;
|
||||
*curbyteoffset = ci->curpos;
|
||||
int64_t curoffset;
|
||||
curoffset = *curbyteoffset;
|
||||
int64_t offset = 0;
|
||||
ogg_page og = {0,0,0,0};
|
||||
int64_t avgpagelen = -1;
|
||||
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;
|
||||
|
||||
//int64_t toffset=curoffset;
|
||||
|
||||
ci->seek_buffer(curoffset);
|
||||
|
||||
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);
|
||||
|
||||
ogg_sync_reset(oy);
|
||||
} else {
|
||||
if (ogg_page_granulepos(&og) == 0 && pos > 5000) {
|
||||
LOGF("SEEK/guess/fault:%lld->-<-%lld,%lld:%lld,%d,%ld,%d\n",
|
||||
curpos,ogg_page_granulepos(&og),pos,
|
||||
offset,0,ci->curpos,/*stream_length*/0);
|
||||
|
||||
curoffset = *curbyteoffset;
|
||||
|
||||
ci->seek_buffer(curoffset);
|
||||
|
||||
ogg_sync_reset(oy);
|
||||
} else {
|
||||
curoffset = offset;
|
||||
curpos = 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 = (ogg_page_granulepos(&og) - lastgranule);
|
||||
else
|
||||
avgpagelen = ((ogg_page_granulepos(&og) - lastgranule)
|
||||
+ avgpagelen) / 2;
|
||||
}
|
||||
|
||||
lastgranule = 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);
|
||||
|
||||
ogg_sync_reset(oy);
|
||||
|
||||
LOGF("Seek failed:%lld\n", offset);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* this is the codec entry point */
|
||||
enum codec_status codec_main(enum codec_entry_call_reason reason)
|
||||
{
|
||||
(void)reason;
|
||||
|
||||
return CODEC_OK;
|
||||
}
|
||||
|
||||
/* this is called for each file to process */
|
||||
enum codec_status codec_run(void)
|
||||
{
|
||||
int error = CODEC_ERROR;
|
||||
intptr_t param;
|
||||
ogg_sync_state oy;
|
||||
ogg_page og;
|
||||
ogg_packet op;
|
||||
ogg_stream_state os;
|
||||
int64_t page_granule = 0;
|
||||
int stream_init = 0;
|
||||
int sample_rate = 48000;
|
||||
OpusDecoder *st = NULL;
|
||||
OpusHeader header;
|
||||
int ret;
|
||||
unsigned long strtoffset = ci->id3->offset;
|
||||
int skip = 0;
|
||||
int64_t seek_target;
|
||||
uint64_t granule_pos;
|
||||
|
||||
/* reset our simple malloc */
|
||||
if (codec_init()) {
|
||||
goto done;
|
||||
}
|
||||
global_stack = 0;
|
||||
|
||||
/* pre-init the ogg_sync_state buffer, so it won't need many reallocs */
|
||||
ogg_sync_init(&oy);
|
||||
oy.storage = 64*1024;
|
||||
oy.data = codec_malloc(oy.storage);
|
||||
|
||||
/* allocate output buffer */
|
||||
uint16_t *output = (uint16_t*) codec_malloc(MAX_FRAME_SIZE*sizeof(uint16_t));
|
||||
|
||||
ci->seek_buffer(0);
|
||||
ci->set_elapsed(0);
|
||||
|
||||
while (1) {
|
||||
enum codec_command_action action = ci->get_command(¶m);
|
||||
|
||||
if (action == CODEC_ACTION_HALT)
|
||||
break;
|
||||
|
||||
if (action == CODEC_ACTION_SEEK_TIME) {
|
||||
if (st != NULL) {
|
||||
/* calculate granule to seek to (including seek rewind) */
|
||||
seek_target = (48LL * param) + header.preskip;
|
||||
skip = MIN(seek_target, SEEK_REWIND);
|
||||
seek_target -= skip;
|
||||
|
||||
LOGF("Opus seek page:%lld,%lld,%ld\n",
|
||||
seek_target, page_granule, (long)param);
|
||||
speex_seek_page_granule(seek_target, page_granule, &oy, 0);
|
||||
}
|
||||
|
||||
ci->set_elapsed(param);
|
||||
ci->seek_complete();
|
||||
}
|
||||
|
||||
/*Get the ogg buffer for writing*/
|
||||
if (get_more_data(&oy) < 1) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Loop for all complete pages we got (most likely only one) */
|
||||
while (ogg_sync_pageout(&oy, &og) == 1) {
|
||||
if (stream_init == 0) {
|
||||
ogg_stream_init(&os, ogg_page_serialno(&og));
|
||||
stream_init = 1;
|
||||
}
|
||||
|
||||
/* Add page to the bitstream */
|
||||
ogg_stream_pagein(&os, &og);
|
||||
|
||||
page_granule = ogg_page_granulepos(&og);
|
||||
granule_pos = page_granule;
|
||||
|
||||
while ((ogg_stream_packetout(&os, &op) == 1) && !op.e_o_s) {
|
||||
if (op.packetno == 0){
|
||||
/* identification header */
|
||||
|
||||
if (opus_header_parse(op.packet, op.bytes, &header) == 0) {
|
||||
LOGF("Could not parse header");
|
||||
goto done;
|
||||
}
|
||||
skip = header.preskip;
|
||||
|
||||
st = opus_decoder_create(sample_rate, header.channels, &ret);
|
||||
if (ret != OPUS_OK) {
|
||||
LOGF("opus_decoder_create failed %d", ret);
|
||||
goto done;
|
||||
}
|
||||
LOGF("Decoder inited");
|
||||
|
||||
codec_set_replaygain(ci->id3);
|
||||
|
||||
opus_decoder_ctl(st, OPUS_SET_GAIN(header.gain));
|
||||
|
||||
ci->configure(DSP_SET_FREQUENCY, sample_rate);
|
||||
ci->configure(DSP_SET_SAMPLE_DEPTH, 16);
|
||||
ci->configure(DSP_SET_STEREO_MODE, (header.channels == 2) ?
|
||||
STEREO_INTERLEAVED : STEREO_MONO);
|
||||
|
||||
} else if (op.packetno == 1) {
|
||||
/* Comment header */
|
||||
} else {
|
||||
if (strtoffset) {
|
||||
ci->seek_buffer(strtoffset);
|
||||
ogg_sync_reset(&oy);
|
||||
strtoffset = 0;
|
||||
break;//next page
|
||||
}
|
||||
|
||||
/* report progress */
|
||||
ci->set_elapsed((granule_pos - header.preskip) / 48);
|
||||
|
||||
/* Decode audio packets */
|
||||
ret = opus_decode(st, op.packet, op.bytes, output, MAX_FRAME_SIZE, 0);
|
||||
|
||||
if (ret > 0) {
|
||||
if (skip > 0) {
|
||||
if (ret <= skip) {
|
||||
/* entire output buffer is skipped */
|
||||
skip -= ret;
|
||||
ret = 0;
|
||||
} else {
|
||||
/* part of output buffer is played */
|
||||
ret -= skip;
|
||||
ci->pcmbuf_insert(&output[skip * header.channels], NULL, ret);
|
||||
skip = 0;
|
||||
}
|
||||
} else {
|
||||
/* entire buffer is played */
|
||||
ci->pcmbuf_insert(output, NULL, ret);
|
||||
}
|
||||
granule_pos += ret;
|
||||
} else {
|
||||
if (ret < 0) {
|
||||
LOGF("opus_decode failed %d", ret);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGF("Returned OK");
|
||||
error = CODEC_OK;
|
||||
done:
|
||||
return error;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue