#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#include "ivorbiscodec.h"
#include "ivorbisfile.h"
#include "os.h"
#include "misc.h"
static long _get_data(OggVorbis_File *vf){
errno=0;
if(!(vf->callbacks.read_func))return(-1);
if(vf->datasource){
char *buffer=ogg_sync_buffer(&vf->oy,READSIZE);
long bytes=(vf->callbacks.read_func)(buffer,1,READSIZE,vf->datasource);
if(bytes>0)ogg_sync_wrote(&vf->oy,bytes);
if(bytes==0 && errno)return(-1);
return(bytes);
}else
return(0);
}
static int _seek_helper(OggVorbis_File *vf,ogg_int64_t offset){
if(vf->datasource){
if(vf->offset != offset){
if(!(vf->callbacks.seek_func)||
(vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET) == -1)
return OV_EREAD;
vf->offset=offset;
ogg_sync_reset(&vf->oy);
}
}else{
return OV_EFAULT;
}
return 0;
}
static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og,
ogg_int64_t boundary){
if(boundary>0)boundary+=vf->offset;
while(1){
long more;
if(boundary>0 && vf->offset>=boundary)return(OV_FALSE);
more=ogg_sync_pageseek(&vf->oy,og);
if(more<0){
vf->offset-=more;
}else{
if(more==0){
if(!boundary)return(OV_FALSE);
{
long ret=_get_data(vf);
if(ret==0)return(OV_EOF);
if(ret<0)return(OV_EREAD);
}
}else{
ogg_int64_t ret=vf->offset;
vf->offset+=more;
return(ret);
}
}
}
}
static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_int64_t begin,ogg_page *og){
ogg_int64_t end = begin;
ogg_int64_t ret;
ogg_int64_t offset=-1;
while(offset==-1){
begin-=CHUNKSIZE;
if(begin<0)
begin=0;
ret=_seek_helper(vf,begin);
if(ret)return(ret);
while(vf->offset<end){
memset(og,0,sizeof(*og));
ret=_get_next_page(vf,og,end-vf->offset);
if(ret==OV_EREAD)return(OV_EREAD);
if(ret<0){
break;
}else{
offset=ret;
}
}
}
if(og->header_len==0){
ret=_seek_helper(vf,offset);
if(ret)return(ret);
ret=_get_next_page(vf,og,CHUNKSIZE);
if(ret<0)
return(OV_EFAULT);
}
return(offset);
}
static void _add_serialno(ogg_page *og,ogg_uint32_t **serialno_list, int *n){
ogg_uint32_t s = ogg_page_serialno(og);
(*n)++;
if(*serialno_list){
*serialno_list = _ogg_realloc(*serialno_list, sizeof(**serialno_list)*(*n));
}else{
*serialno_list = _ogg_malloc(sizeof(**serialno_list));
}
(*serialno_list)[(*n)-1] = s;
}
static int _lookup_serialno(ogg_uint32_t s, ogg_uint32_t *serialno_list, int n){
if(serialno_list){
while(n--){
if(*serialno_list == s) return 1;
serialno_list++;
}
}
return 0;
}
static int _lookup_page_serialno(ogg_page *og, ogg_uint32_t *serialno_list, int n){
ogg_uint32_t s = ogg_page_serialno(og);
return _lookup_serialno(s,serialno_list,n);
}
static ogg_int64_t _get_prev_page_serial(OggVorbis_File *vf, ogg_int64_t begin,
ogg_uint32_t *serial_list, int serial_n,
int *serialno, ogg_int64_t *granpos){
ogg_page og;
ogg_int64_t end=begin;
ogg_int64_t ret;
ogg_int64_t prefoffset=-1;
ogg_int64_t offset=-1;
ogg_int64_t ret_serialno=-1;
ogg_int64_t ret_gran=-1;
while(offset==-1){
begin-=CHUNKSIZE;
if(begin<0)
begin=0;
ret=_seek_helper(vf,begin);
if(ret)return(ret);
while(vf->offset<end){
ret=_get_next_page(vf,&og,end-vf->offset);
if(ret==OV_EREAD)return(OV_EREAD);
if(ret<0){
break;
}else{
ret_serialno=ogg_page_serialno(&og);
ret_gran=ogg_page_granulepos(&og);
offset=ret;
if((ogg_uint32_t)ret_serialno == *serialno){
prefoffset=ret;
*granpos=ret_gran;
}
if(!_lookup_serialno((ogg_uint32_t)ret_serialno,serial_list,serial_n)){
prefoffset=-1;
}
}
}
}
if(prefoffset>=0)return(prefoffset);
*serialno = ret_serialno;
*granpos = ret_gran;
return(offset);
}
static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc,
ogg_uint32_t **serialno_list, int *serialno_n,
ogg_page *og_ptr){
ogg_page og;
ogg_packet op;
int i,ret;
int allbos=0;
if(!og_ptr){
ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE);
if(llret==OV_EREAD)return(OV_EREAD);
if(llret<0)return(OV_ENOTVORBIS);
og_ptr=&og;
}
vorbis_info_init(vi);
vorbis_comment_init(vc);
vf->ready_state=OPENED;
while(ogg_page_bos(og_ptr)){
if(serialno_list){
if(_lookup_page_serialno(og_ptr,*serialno_list,*serialno_n)){
if(*serialno_list)_ogg_free(*serialno_list);
*serialno_list=0;
*serialno_n=0;
ret=OV_EBADHEADER;
goto bail_header;
}
_add_serialno(og_ptr,serialno_list,serialno_n);
}
if(vf->ready_state<STREAMSET){
ogg_stream_reset_serialno(&vf->os,ogg_page_serialno(og_ptr));
ogg_stream_pagein(&vf->os,og_ptr);
if(ogg_stream_packetout(&vf->os,&op) > 0 &&
vorbis_synthesis_idheader(&op)){
vf->ready_state=STREAMSET;
if((ret=vorbis_synthesis_headerin(vi,vc,&op))){
ret=OV_EBADHEADER;
goto bail_header;
}
}
}
{
ogg_int64_t llret=_get_next_page(vf,og_ptr,CHUNKSIZE);
if(llret==OV_EREAD){
ret=OV_EREAD;
goto bail_header;
}
if(llret<0){
ret=OV_ENOTVORBIS;
goto bail_header;
}
if(vf->ready_state==STREAMSET &&
vf->os.serialno == ogg_page_serialno(og_ptr)){
ogg_stream_pagein(&vf->os,og_ptr);
break;
}
}
}
if(vf->ready_state!=STREAMSET){
ret = OV_ENOTVORBIS;
goto bail_header;
}
while(1){
i=0;
while(i<2){
while(i<2){
int result=ogg_stream_packetout(&vf->os,&op);
if(result==0)break;
if(result==-1){
ret=OV_EBADHEADER;
goto bail_header;
}
if((ret=vorbis_synthesis_headerin(vi,vc,&op)))
goto bail_header;
i++;
}
while(i<2){
if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){
ret=OV_EBADHEADER;
goto bail_header;
}
if(vf->os.serialno == ogg_page_serialno(og_ptr)){
ogg_stream_pagein(&vf->os,og_ptr);
break;
}
if(ogg_page_bos(og_ptr)){
if(allbos){
ret = OV_EBADHEADER;
goto bail_header;
}else
allbos=1;
}
}
}
return 0;
}
bail_header:
vorbis_info_clear(vi);
vorbis_comment_clear(vc);
vf->ready_state=OPENED;
return ret;
}
static ogg_int64_t _initial_pcmoffset(OggVorbis_File *vf, vorbis_info *vi){
ogg_page og;
ogg_int64_t accumulated=0;
long lastblock=-1;
int result;
int serialno = vf->os.serialno;
while(1){
ogg_packet op;
if(_get_next_page(vf,&og,-1)<0)
break;
if(ogg_page_bos(&og)) break;
if(ogg_page_serialno(&og)!=serialno) continue;
ogg_stream_pagein(&vf->os,&og);
while((result=ogg_stream_packetout(&vf->os,&op))){
if(result>0){
long thisblock=vorbis_packet_blocksize(vi,&op);
if(lastblock!=-1)
accumulated+=(lastblock+thisblock)>>2;
lastblock=thisblock;
}
}
if(ogg_page_granulepos(&og)!=-1){
accumulated= ogg_page_granulepos(&og)-accumulated;
break;
}
}
if(accumulated<0)accumulated=0;
return accumulated;
}
static int _bisect_forward_serialno(OggVorbis_File *vf,
ogg_int64_t begin,
ogg_int64_t searched,
ogg_int64_t end,
ogg_int64_t endgran,
int endserial,
ogg_uint32_t *currentno_list,
int currentnos,
long m){
ogg_int64_t pcmoffset;
ogg_int64_t dataoffset=searched;
ogg_int64_t endsearched=end;
ogg_int64_t next=end;
ogg_int64_t searchgran=-1;
ogg_page og;
ogg_int64_t ret,last;
int serialno = vf->os.serialno;
if(_lookup_serialno(endserial,currentno_list,currentnos)){
searched = end;
while(endserial != serialno){
endserial = serialno;
searched=_get_prev_page_serial(vf,searched,currentno_list,currentnos,&endserial,&endgran);
}
vf->links=m+1;
if(vf->offsets)_ogg_free(vf->offsets);
if(vf->serialnos)_ogg_free(vf->serialnos);
if(vf->dataoffsets)_ogg_free(vf->dataoffsets);
vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets));
vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi));
vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc));
vf->serialnos=_ogg_malloc(vf->links*sizeof(*vf->serialnos));
vf->dataoffsets=_ogg_malloc(vf->links*sizeof(*vf->dataoffsets));
vf->pcmlengths=_ogg_malloc(vf->links*2*sizeof(*vf->pcmlengths));
vf->offsets[m+1]=end;
vf->offsets[m]=begin;
vf->pcmlengths[m*2+1]=(endgran<0?0:endgran);
}else{
ogg_uint32_t *next_serialno_list=NULL;
int next_serialnos=0;
vorbis_info vi;
vorbis_comment vc;
int testserial = serialno+1;
while(searched<endsearched){
ogg_int64_t bisect;
if(endsearched-searched<CHUNKSIZE){
bisect=searched;
}else{
bisect=(searched+endsearched)/2;
}
ret=_seek_helper(vf,bisect);
if(ret)return(ret);
last=_get_next_page(vf,&og,-1);
if(last==OV_EREAD)return(OV_EREAD);
if(last<0 || !_lookup_page_serialno(&og,currentno_list,currentnos)){
endsearched=bisect;
if(last>=0)next=last;
}else{
searched=vf->offset;
}
}
searched = next;
while(testserial != serialno){
testserial = serialno;
searched = _get_prev_page_serial(vf,searched,currentno_list,currentnos,&testserial,&searchgran);
}
ret=_seek_helper(vf,next);
if(ret)return(ret);
ret=_fetch_headers(vf,&vi,&vc,&next_serialno_list,&next_serialnos,NULL);
if(ret)return(ret);
serialno = vf->os.serialno;
dataoffset = vf->offset;
pcmoffset = _initial_pcmoffset(vf,&vi);
ret=_bisect_forward_serialno(vf,next,vf->offset,end,endgran,endserial,
next_serialno_list,next_serialnos,m+1);
if(ret)return(ret);
if(next_serialno_list)_ogg_free(next_serialno_list);
vf->offsets[m+1]=next;
vf->serialnos[m+1]=serialno;
vf->dataoffsets[m+1]=dataoffset;
vf->vi[m+1]=vi;
vf->vc[m+1]=vc;
vf->pcmlengths[m*2+1]=searchgran;
vf->pcmlengths[m*2+2]=pcmoffset;
vf->pcmlengths[m*2+3]-=pcmoffset;
if(vf->pcmlengths[m*2+3]<0)vf->pcmlengths[m*2+3]=0;
}
return(0);
}
static int _make_decode_ready(OggVorbis_File *vf){
if(vf->ready_state>STREAMSET)return 0;
if(vf->ready_state<STREAMSET)return OV_EFAULT;
if(vf->seekable){
if(vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link))
return OV_EBADLINK;
}else{
if(vorbis_synthesis_init(&vf->vd,vf->vi))
return OV_EBADLINK;
}
vorbis_block_init(&vf->vd,&vf->vb);
vf->ready_state=INITSET;
vf->bittrack=0;
vf->samptrack=0;
return 0;
}
static int _open_seekable2(OggVorbis_File *vf){
ogg_int64_t dataoffset=vf->dataoffsets[0],end,endgran=-1;
int endserial=vf->os.serialno;
int serialno=vf->os.serialno;
ogg_int64_t pcmoffset = _initial_pcmoffset(vf,vf->vi);
if(vf->callbacks.seek_func && vf->callbacks.tell_func){
(vf->callbacks.seek_func)(vf->datasource,0,SEEK_END);
vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource);
}else{
vf->offset=vf->end=-1;
}
if(vf->end==-1) return(OV_EINVAL);
end=_get_prev_page_serial(vf,vf->end,vf->serialnos+2,vf->serialnos[1],&endserial,&endgran);
if(end<0)return(end);
if(_bisect_forward_serialno(vf,0,dataoffset,end,endgran,endserial,
vf->serialnos+2,vf->serialnos[1],0)<0)return(OV_EREAD);
vf->offsets[0]=0;
vf->serialnos[0]=serialno;
vf->dataoffsets[0]=dataoffset;
vf->pcmlengths[0]=pcmoffset;
vf->pcmlengths[1]-=pcmoffset;
if(vf->pcmlengths[1]<0)vf->pcmlengths[1]=0;
return(ov_raw_seek(vf,dataoffset));
}
static void _decode_clear(OggVorbis_File *vf){
vorbis_dsp_clear(&vf->vd);
vorbis_block_clear(&vf->vb);
vf->ready_state=OPENED;
}
static int _fetch_and_process_packet(OggVorbis_File *vf,
ogg_packet *op_in,
int readp,
int spanp){
ogg_page og;
while(1){
if(vf->ready_state==STREAMSET){
int ret=_make_decode_ready(vf);
if(ret<0)return ret;
}
if(vf->ready_state==INITSET){
while(1) {
ogg_packet op;
ogg_packet *op_ptr=(op_in?op_in:&op);
int result=ogg_stream_packetout(&vf->os,op_ptr);
ogg_int64_t granulepos;
op_in=NULL;
if(result==-1)return(OV_HOLE);
if(result>0){
granulepos=op_ptr->granulepos;
if(!vorbis_synthesis(&vf->vb,op_ptr)){
{
int oldsamples=vorbis_synthesis_pcmout(&vf->vd,NULL);
if(oldsamples)return(OV_EFAULT);
vorbis_synthesis_blockin(&vf->vd,&vf->vb);
vf->samptrack+=vorbis_synthesis_pcmout(&vf->vd,NULL);
vf->bittrack+=op_ptr->bytes*8;
}
if(granulepos!=-1 && !op_ptr->e_o_s){
int link=(vf->seekable?vf->current_link:0);
int i,samples;
if(vf->seekable && link>0)
granulepos-=vf->pcmlengths[link*2];
if(granulepos<0)granulepos=0;
samples=vorbis_synthesis_pcmout(&vf->vd,NULL);
granulepos-=samples;
for(i=0;i<link;i++)
granulepos+=vf->pcmlengths[i*2+1];
vf->pcm_offset=granulepos;
}
return(1);
}
}
else
break;
}
}
if(vf->ready_state>=OPENED){
ogg_int64_t ret;
while(1){
if(!readp)return(0);
if((ret=_get_next_page(vf,&og,-1))<0){
return(OV_EOF);
}
vf->bittrack+=og.header_len*8;
if(vf->ready_state==INITSET){
if(vf->current_serialno!=ogg_page_serialno(&og)){
if(ogg_page_bos(&og)){
if(!spanp)
return(OV_EOF);
_decode_clear(vf);
if(!vf->seekable){
vorbis_info_clear(vf->vi);
vorbis_comment_clear(vf->vc);
}
break;
}else
continue;
}
}
break;
}
}
if(vf->ready_state!=INITSET){
int link;
if(vf->ready_state<STREAMSET){
if(vf->seekable){
ogg_uint32_t serialno = ogg_page_serialno(&og);
for(link=0;link<vf->links;link++)
if(vf->serialnos[link]==serialno)break;
if(link==vf->links) continue;
vf->current_serialno=serialno;
vf->current_link=link;
ogg_stream_reset_serialno(&vf->os,vf->current_serialno);
vf->ready_state=STREAMSET;
}else{
int ret=_fetch_headers(vf,vf->vi,vf->vc,NULL,NULL,&og);
if(ret)return(ret);
vf->current_serialno=vf->os.serialno;
vf->current_link++;
link=0;
}
}
}
ogg_stream_pagein(&vf->os,&og);
}
}
static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){
if(f==NULL)return(-1);
return fseek(f,off,whence);
}
static int _ov_open1(void *f,OggVorbis_File *vf,const char *initial,
long ibytes, ov_callbacks callbacks){
int offsettest=((f && callbacks.seek_func)?callbacks.seek_func(f,0,SEEK_CUR):-1);
ogg_uint32_t *serialno_list=NULL;
int serialno_list_size=0;
int ret;
memset(vf,0,sizeof(*vf));
vf->datasource=f;
vf->callbacks = callbacks;
ogg_sync_init(&vf->oy);
if(initial){
char *buffer=ogg_sync_buffer(&vf->oy,ibytes);
memcpy(buffer,initial,ibytes);
ogg_sync_wrote(&vf->oy,ibytes);
}
if(offsettest!=-1)vf->seekable=1;
vf->links=1;
vf->vi=_ogg_calloc(vf->links,sizeof(*vf->vi));
vf->vc=_ogg_calloc(vf->links,sizeof(*vf->vc));
ogg_stream_init(&vf->os,-1);
if((ret=_fetch_headers(vf,vf->vi,vf->vc,&serialno_list,&serialno_list_size,NULL))<0){
vf->datasource=NULL;
ov_clear(vf);
}else{
vf->serialnos=_ogg_calloc(serialno_list_size+2,sizeof(*vf->serialnos));
vf->serialnos[0]=vf->current_serialno=vf->os.serialno;
vf->serialnos[1]=serialno_list_size;
memcpy(vf->serialnos+2,serialno_list,serialno_list_size*sizeof(*vf->serialnos));
vf->offsets=_ogg_calloc(1,sizeof(*vf->offsets));
vf->dataoffsets=_ogg_calloc(1,sizeof(*vf->dataoffsets));
vf->offsets[0]=0;
vf->dataoffsets[0]=vf->offset;
vf->ready_state=PARTOPEN;
}
if(serialno_list)_ogg_free(serialno_list);
return(ret);
}
static int _ov_open2(OggVorbis_File *vf){
if(vf->ready_state != PARTOPEN) return OV_EINVAL;
vf->ready_state=OPENED;
if(vf->seekable){
int ret=_open_seekable2(vf);
if(ret){
vf->datasource=NULL;
ov_clear(vf);
}
return(ret);
}else
vf->ready_state=STREAMSET;
return 0;
}
int ov_clear(OggVorbis_File *vf){
if(vf){
vorbis_block_clear(&vf->vb);
vorbis_dsp_clear(&vf->vd);
ogg_stream_clear(&vf->os);
if(vf->vi && vf->links){
int i;
for(i=0;i<vf->links;i++){
vorbis_info_clear(vf->vi+i);
vorbis_comment_clear(vf->vc+i);
}
_ogg_free(vf->vi);
_ogg_free(vf->vc);
}
if(vf->dataoffsets)_ogg_free(vf->dataoffsets);
if(vf->pcmlengths)_ogg_free(vf->pcmlengths);
if(vf->serialnos)_ogg_free(vf->serialnos);
if(vf->offsets)_ogg_free(vf->offsets);
ogg_sync_clear(&vf->oy);
if(vf->datasource && vf->callbacks.close_func)
(vf->callbacks.close_func)(vf->datasource);
memset(vf,0,sizeof(*vf));
}
#ifdef DEBUG_LEAKS
_VDBG_dump();
#endif
return(0);
}
int ov_open_callbacks(void *f,OggVorbis_File *vf,
const char *initial,long ibytes,ov_callbacks callbacks){
int ret=_ov_open1(f,vf,initial,ibytes,callbacks);
if(ret)return ret;
return _ov_open2(vf);
}
int ov_open(FILE *f,OggVorbis_File *vf,const char *initial,long ibytes){
ov_callbacks callbacks = {
(size_t (*)(void *, size_t, size_t, void *)) fread,
(int (*)(void *, ogg_int64_t, int)) _fseek64_wrap,
(int (*)(void *)) fclose,
(long (*)(void *)) ftell
};
return ov_open_callbacks((void *)f, vf, initial, ibytes, callbacks);
}
int ov_fopen(const char *path,OggVorbis_File *vf){
int ret;
FILE *f = fopen(path,"rb");
if(!f) return -1;
ret = ov_open(f,vf,NULL,0);
if(ret) fclose(f);
return ret;
}
int ov_test_callbacks(void *f,OggVorbis_File *vf,
const char *initial,long ibytes,ov_callbacks callbacks)
{
return _ov_open1(f,vf,initial,ibytes,callbacks);
}
int ov_test(FILE *f,OggVorbis_File *vf,const char *initial,long ibytes){
ov_callbacks callbacks = {
(size_t (*)(void *, size_t, size_t, void *)) fread,
(int (*)(void *, ogg_int64_t, int)) _fseek64_wrap,
(int (*)(void *)) fclose,
(long (*)(void *)) ftell
};
return ov_test_callbacks((void *)f, vf, initial, ibytes, callbacks);
}
int ov_test_open(OggVorbis_File *vf){
if(vf->ready_state!=PARTOPEN)return(OV_EINVAL);
return _ov_open2(vf);
}
long ov_streams(OggVorbis_File *vf){
return vf->links;
}
long ov_seekable(OggVorbis_File *vf){
return vf->seekable;
}
long ov_bitrate(OggVorbis_File *vf,int i){
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(i>=vf->links)return(OV_EINVAL);
if(!vf->seekable && i!=0)return(ov_bitrate(vf,0));
if(i<0){
ogg_int64_t bits=0;
int i;
for(i=0;i<vf->links;i++)
bits+=(vf->offsets[i+1]-vf->dataoffsets[i])*8;
return(bits*1000/ov_time_total(vf,-1));
}else{
if(vf->seekable){
return((vf->offsets[i+1]-vf->dataoffsets[i])*8000/ov_time_total(vf,i));
}else{
if(vf->vi[i].bitrate_nominal>0){
return vf->vi[i].bitrate_nominal;
}else{
if(vf->vi[i].bitrate_upper>0){
if(vf->vi[i].bitrate_lower>0){
return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2;
}else{
return vf->vi[i].bitrate_upper;
}
}
return(OV_FALSE);
}
}
}
}
long ov_bitrate_instant(OggVorbis_File *vf){
int link=(vf->seekable?vf->current_link:0);
long ret;
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(vf->samptrack==0)return(OV_FALSE);
ret=vf->bittrack/vf->samptrack*vf->vi[link].rate;
vf->bittrack=0;
vf->samptrack=0;
return(ret);
}
long ov_serialnumber(OggVorbis_File *vf,int i){
if(i>=vf->links)return(ov_serialnumber(vf,vf->links-1));
if(!vf->seekable && i>=0)return(ov_serialnumber(vf,-1));
if(i<0){
return(vf->current_serialno);
}else{
return(vf->serialnos[i]);
}
}
ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i){
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(!vf->seekable || i>=vf->links)return(OV_EINVAL);
if(i<0){
ogg_int64_t acc=0;
int i;
for(i=0;i<vf->links;i++)
acc+=ov_raw_total(vf,i);
return(acc);
}else{
return(vf->offsets[i+1]-vf->offsets[i]);
}
}
ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i){
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(!vf->seekable || i>=vf->links)return(OV_EINVAL);
if(i<0){
ogg_int64_t acc=0;
int i;
for(i=0;i<vf->links;i++)
acc+=ov_pcm_total(vf,i);
return(acc);
}else{
return(vf->pcmlengths[i*2+1]);
}
}
ogg_int64_t ov_time_total(OggVorbis_File *vf,int i){
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(!vf->seekable || i>=vf->links)return(OV_EINVAL);
if(i<0){
ogg_int64_t acc=0;
int i;
for(i=0;i<vf->links;i++)
acc+=ov_time_total(vf,i);
return(acc);
}else{
return(((ogg_int64_t)vf->pcmlengths[i*2+1])*1000/vf->vi[i].rate);
}
}
int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos){
ogg_stream_state work_os;
int ret;
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(!vf->seekable)
return(OV_ENOSEEK);
if(pos<0 || pos>vf->end)return(OV_EINVAL);
if(vf->ready_state>=STREAMSET){
if(pos<vf->offsets[vf->current_link] || pos>=vf->offsets[vf->current_link+1])
_decode_clear(vf);
}
vf->pcm_offset=-1;
ogg_stream_reset_serialno(&vf->os,
vf->current_serialno);
vorbis_synthesis_restart(&vf->vd);
ret=_seek_helper(vf,pos);
if(ret)goto seek_error;
{
ogg_page og;
ogg_packet op;
int lastblock=0;
int accblock=0;
int thisblock=0;
int lastflag=0;
int firstflag=0;
ogg_int64_t pagepos=-1;
ogg_stream_init(&work_os,vf->current_serialno);
ogg_stream_reset(&work_os);
while(1){
if(vf->ready_state>=STREAMSET){
int result=ogg_stream_packetout(&work_os,&op);
if(result>0){
if(vf->vi[vf->current_link].codec_setup){
thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op);
if(thisblock<0){
ogg_stream_packetout(&vf->os,NULL);
thisblock=0;
}else{
if(lastflag && !firstflag)
ogg_stream_packetout(&vf->os,NULL);
else
if(lastblock)accblock+=(lastblock+thisblock)>>2;
}
if(op.granulepos!=-1){
int i,link=vf->current_link;
ogg_int64_t granulepos=op.granulepos-vf->pcmlengths[link*2];
if(granulepos<0)granulepos=0;
for(i=0;i<link;i++)
granulepos+=vf->pcmlengths[i*2+1];
vf->pcm_offset=granulepos-accblock;
if(vf->pcm_offset<0)vf->pcm_offset=0;
break;
}
lastblock=thisblock;
continue;
}else
ogg_stream_packetout(&vf->os,NULL);
}
}
if(!lastblock){
pagepos=_get_next_page(vf,&og,-1);
if(pagepos<0){
vf->pcm_offset=ov_pcm_total(vf,-1);
break;
}
}else{
vf->pcm_offset=-1;
break;
}
if(vf->ready_state>=STREAMSET){
if(vf->current_serialno!=ogg_page_serialno(&og)){
if(ogg_page_bos(&og)){
_decode_clear(vf);
ogg_stream_clear(&work_os);
}
}
}
if(vf->ready_state<STREAMSET){
int link;
ogg_uint32_t serialno = ogg_page_serialno(&og);
for(link=0;link<vf->links;link++)
if(vf->serialnos[link]==serialno)break;
if(link==vf->links) continue;
vf->current_link=link;
vf->current_serialno=serialno;
ogg_stream_reset_serialno(&vf->os,serialno);
ogg_stream_reset_serialno(&work_os,serialno);
vf->ready_state=STREAMSET;
firstflag=(pagepos<=vf->dataoffsets[link]);
}
ogg_stream_pagein(&vf->os,&og);
ogg_stream_pagein(&work_os,&og);
lastflag=ogg_page_eos(&og);
}
}
ogg_stream_clear(&work_os);
vf->bittrack=0;
vf->samptrack=0;
return(0);
seek_error:
vf->pcm_offset=-1;
ogg_stream_clear(&work_os);
_decode_clear(vf);
return OV_EBADLINK;
}
ogg_int64_t rescale64(ogg_int64_t x, ogg_int64_t from, ogg_int64_t to){
ogg_int64_t frac=0;
ogg_int64_t ret=0;
int i;
if(x >= from) return to;
if(x <= 0) return 0;
for(i=0;i<64;i++){
if(x>=from){
frac|=1;
x-=from;
}
x<<=1;
frac<<=1;
}
for(i=0;i<64;i++){
if(frac & 1){
ret+=to;
}
frac>>=1;
ret>>=1;
}
return ret;
}
int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){
int link=-1;
ogg_int64_t result=0;
ogg_int64_t total=ov_pcm_total(vf,-1);
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(!vf->seekable)return(OV_ENOSEEK);
if(pos<0 || pos>total)return(OV_EINVAL);
for(link=vf->links-1;link>=0;link--){
total-=vf->pcmlengths[link*2+1];
if(pos>=total)break;
}
{
ogg_int64_t end=vf->offsets[link+1];
ogg_int64_t begin=vf->dataoffsets[link];
ogg_int64_t begintime = vf->pcmlengths[link*2];
ogg_int64_t endtime = vf->pcmlengths[link*2+1]+begintime;
ogg_int64_t target=pos-total+begintime;
ogg_int64_t best=-1;
int got_page=0;
ogg_page og;
if(begin==end){
result=_seek_helper(vf,begin);
if(result) goto seek_error;
result=_get_next_page(vf,&og,1);
if(result<0) goto seek_error;
got_page=1;
}
while(begin<end){
ogg_int64_t bisect;
if(end-begin<CHUNKSIZE){
bisect=begin;
}else{
bisect=begin + rescale64(target-begintime,
endtime-begintime,
end-begin) - CHUNKSIZE;
if(bisect<begin+CHUNKSIZE)
bisect=begin;
}
result=_seek_helper(vf,bisect);
if(result) goto seek_error;
while(begin<end){
result=_get_next_page(vf,&og,end-vf->offset);
if(result==OV_EREAD) goto seek_error;
if(result<0){
if(bisect<=begin+1)
end=begin;
else{
if(bisect==0) goto seek_error;
bisect-=CHUNKSIZE;
if(bisect<=begin)bisect=begin+1;
result=_seek_helper(vf,bisect);
if(result) goto seek_error;
}
}else{
ogg_int64_t granulepos;
got_page=1;
if(ogg_page_serialno(&og)!=vf->serialnos[link])
continue;
granulepos=ogg_page_granulepos(&og);
if(granulepos==-1)continue;
if(granulepos<target){
best=result;
begin=vf->offset;
begintime=granulepos;
if(target-begintime>44100)break;
bisect=begin;
}else{
if(bisect<=begin+1){
end=begin;
}else{
if(end==vf->offset){
end=result;
bisect-=CHUNKSIZE;
if(bisect<=begin)bisect=begin+1;
result=_seek_helper(vf,bisect);
if(result) goto seek_error;
}else{
end=bisect;
endtime=granulepos;
break;
}
}
}
}
}
}
if(best == -1){
if(got_page &&
begin == vf->dataoffsets[link] &&
ogg_page_serialno(&og)==vf->serialnos[link]){
vf->pcm_offset=total;
if(link!=vf->current_link){
_decode_clear(vf);
vf->current_link=link;
vf->current_serialno=vf->serialnos[link];
vf->ready_state=STREAMSET;
}else{
vorbis_synthesis_restart(&vf->vd);
}
ogg_stream_reset_serialno(&vf->os,vf->current_serialno);
ogg_stream_pagein(&vf->os,&og);
}else
goto seek_error;
}else{
ogg_page og;
ogg_packet op;
result=_seek_helper(vf,best);
vf->pcm_offset=-1;
if(result) goto seek_error;
result=_get_next_page(vf,&og,-1);
if(result<0) goto seek_error;
if(link!=vf->current_link){
_decode_clear(vf);
vf->current_link=link;
vf->current_serialno=vf->serialnos[link];
vf->ready_state=STREAMSET;
}else{
vorbis_synthesis_restart(&vf->vd);
}
ogg_stream_reset_serialno(&vf->os,vf->current_serialno);
ogg_stream_pagein(&vf->os,&og);
while(1){
result=ogg_stream_packetpeek(&vf->os,&op);
if(result==0){
result=best;
while(result>vf->dataoffsets[link]){
result=_get_prev_page(vf,result,&og);
if(result<0) goto seek_error;
if(ogg_page_serialno(&og)==vf->current_serialno &&
(ogg_page_granulepos(&og)>-1 ||
!ogg_page_continued(&og))){
return ov_raw_seek(vf,result);
}
}
}
if(result<0){
result = OV_EBADPACKET;
goto seek_error;
}
if(op.granulepos!=-1){
vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2];
if(vf->pcm_offset<0)vf->pcm_offset=0;
vf->pcm_offset+=total;
break;
}else
result=ogg_stream_packetout(&vf->os,NULL);
}
}
}
if(vf->pcm_offset>pos || pos>ov_pcm_total(vf,-1)){
result=OV_EFAULT;
goto seek_error;
}
vf->bittrack=0;
vf->samptrack=0;
return(0);
seek_error:
vf->pcm_offset=-1;
_decode_clear(vf);
return (int)result;
}
int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos){
int thisblock,lastblock=0;
int ret=ov_pcm_seek_page(vf,pos);
if(ret<0)return(ret);
if((ret=_make_decode_ready(vf)))return ret;
while(1){
ogg_packet op;
ogg_page og;
int ret=ogg_stream_packetpeek(&vf->os,&op);
if(ret>0){
thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op);
if(thisblock<0){
ogg_stream_packetout(&vf->os,NULL);
continue;
}
if(lastblock)vf->pcm_offset+=(lastblock+thisblock)>>2;
if(vf->pcm_offset+((thisblock+
vorbis_info_blocksize(vf->vi,1))>>2)>=pos)break;
ogg_stream_packetout(&vf->os,NULL);
vorbis_synthesis_trackonly(&vf->vb,&op);
vorbis_synthesis_blockin(&vf->vd,&vf->vb);
if(op.granulepos>-1){
int i;
vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2];
if(vf->pcm_offset<0)vf->pcm_offset=0;
for(i=0;i<vf->current_link;i++)
vf->pcm_offset+=vf->pcmlengths[i*2+1];
}
lastblock=thisblock;
}else{
if(ret<0 && ret!=OV_HOLE)break;
if(_get_next_page(vf,&og,-1)<0)break;
if(ogg_page_bos(&og))_decode_clear(vf);
if(vf->ready_state<STREAMSET){
ogg_uint32_t serialno=ogg_page_serialno(&og);
int link;
for(link=0;link<vf->links;link++)
if(vf->serialnos[link]==serialno)break;
if(link==vf->links) continue;
vf->current_link=link;
vf->ready_state=STREAMSET;
vf->current_serialno=ogg_page_serialno(&og);
ogg_stream_reset_serialno(&vf->os,serialno);
ret=_make_decode_ready(vf);
if(ret)return ret;
lastblock=0;
}
ogg_stream_pagein(&vf->os,&og);
}
}
vf->bittrack=0;
vf->samptrack=0;
while(vf->pcm_offset<pos){
ogg_int64_t target=pos-vf->pcm_offset;
long samples=vorbis_synthesis_pcmout(&vf->vd,NULL);
if(samples>target)samples=target;
vorbis_synthesis_read(&vf->vd,samples);
vf->pcm_offset+=samples;
if(samples<target)
if(_fetch_and_process_packet(vf,NULL,1,1)<=0)
vf->pcm_offset=ov_pcm_total(vf,-1);
}
return 0;
}
int ov_time_seek(OggVorbis_File *vf,ogg_int64_t milliseconds){
int link=-1;
ogg_int64_t pcm_total=0;
ogg_int64_t time_total=0;
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(!vf->seekable)return(OV_ENOSEEK);
if(milliseconds<0)return(OV_EINVAL);
for(link=0;link<vf->links;link++){
ogg_int64_t addsec = ov_time_total(vf,link);
if(milliseconds<time_total+addsec)break;
time_total+=addsec;
pcm_total+=vf->pcmlengths[link*2+1];
}
if(link==vf->links)return(OV_EINVAL);
{
ogg_int64_t target=pcm_total+(milliseconds-time_total)*vf->vi[link].rate/1000;
return(ov_pcm_seek(vf,target));
}
}
int ov_time_seek_page(OggVorbis_File *vf,ogg_int64_t milliseconds){
int link=-1;
ogg_int64_t pcm_total=0;
ogg_int64_t time_total=0;
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(!vf->seekable)return(OV_ENOSEEK);
if(milliseconds<0)return(OV_EINVAL);
for(link=0;link<vf->links;link++){
ogg_int64_t addsec = ov_time_total(vf,link);
if(milliseconds<time_total+addsec)break;
time_total+=addsec;
pcm_total+=vf->pcmlengths[link*2+1];
}
if(link==vf->links)return(OV_EINVAL);
{
ogg_int64_t target=pcm_total+(milliseconds-time_total)*vf->vi[link].rate/1000;
return(ov_pcm_seek_page(vf,target));
}
}
ogg_int64_t ov_raw_tell(OggVorbis_File *vf){
if(vf->ready_state<OPENED)return(OV_EINVAL);
return(vf->offset);
}
ogg_int64_t ov_pcm_tell(OggVorbis_File *vf){
if(vf->ready_state<OPENED)return(OV_EINVAL);
return(vf->pcm_offset);
}
ogg_int64_t ov_time_tell(OggVorbis_File *vf){
int link=0;
ogg_int64_t pcm_total=0;
ogg_int64_t time_total=0;
if(vf->ready_state<OPENED)return(OV_EINVAL);
if(vf->seekable){
pcm_total=ov_pcm_total(vf,-1);
time_total=ov_time_total(vf,-1);
for(link=vf->links-1;link>=0;link--){
pcm_total-=vf->pcmlengths[link*2+1];
time_total-=ov_time_total(vf,link);
if(vf->pcm_offset>=pcm_total)break;
}
}
return(time_total+(1000*vf->pcm_offset-pcm_total)/vf->vi[link].rate);
}
vorbis_info *ov_info(OggVorbis_File *vf,int link){
if(vf->seekable){
if(link<0)
if(vf->ready_state>=STREAMSET)
return vf->vi+vf->current_link;
else
return vf->vi;
else
if(link>=vf->links)
return NULL;
else
return vf->vi+link;
}else{
return vf->vi;
}
}
vorbis_comment *ov_comment(OggVorbis_File *vf,int link){
if(vf->seekable){
if(link<0)
if(vf->ready_state>=STREAMSET)
return vf->vc+vf->current_link;
else
return vf->vc;
else
if(link>=vf->links)
return NULL;
else
return vf->vc+link;
}else{
return vf->vc;
}
}
long ov_read(OggVorbis_File *vf,char *buffer,int bytes_req,int *bitstream){
int i,j;
ogg_int32_t **pcm;
long samples;
if(vf->ready_state<OPENED)return(OV_EINVAL);
while(1){
if(vf->ready_state==INITSET){
samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
if(samples)break;
}
{
int ret=_fetch_and_process_packet(vf,NULL,1,1);
if(ret==OV_EOF)
return(0);
if(ret<=0)
return(ret);
}
}
if(samples>0){
long channels=ov_info(vf,-1)->channels;
if(samples>(bytes_req/(2*channels)))
samples=bytes_req/(2*channels);
for(i=0;i<channels;i++) {
ogg_int32_t *src=pcm[i];
short *dest=((short *)buffer)+i;
for(j=0;j<samples;j++) {
*dest=CLIP_TO_15(src[j]>>9);
dest+=channels;
}
}
vorbis_synthesis_read(&vf->vd,samples);
vf->pcm_offset+=samples;
if(bitstream)*bitstream=vf->current_link;
return(samples*2*channels);
}else{
return(samples);
}
}