123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- /********************************************************************
- * *
- * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. *
- * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
- * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
- * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
- * *
- * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012 *
- * by the Xiph.Org Foundation and contributors http://www.xiph.org/ *
- * *
- ********************************************************************/
- #ifdef HAVE_CONFIG_H
- #include "config.h"
- #endif
- /*For fileno()*/
- #if !defined(_POSIX_SOURCE)
- # define _POSIX_SOURCE 1
- #endif
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <math.h>
- #include <string.h>
- #include <opusfile.h>
- #if defined(_WIN32)
- # include "win32utf8.h"
- # undef fileno
- # define fileno _fileno
- #endif
- /*Use shorts, they're smaller.*/
- #if !defined(OP_FIXED_POINT)
- # define OP_FIXED_POINT (1)
- #endif
- #if defined(OP_FIXED_POINT)
- typedef opus_int16 op_sample;
- # define op_read_native op_read
- /*TODO: The convergence after 80 ms of preroll is far from exact.
- Our comparison is very rough.
- Need to find some way to do this better.*/
- # define MATCH_TOL (16384)
- # define ABS(_x) ((_x)<0?-(_x):(_x))
- # define MATCH(_a,_b) (ABS((_a)-(_b))<MATCH_TOL)
- /*Don't have fixed-point downmixing code.*/
- # undef OP_WRITE_SEEK_SAMPLES
- #else
- typedef float op_sample;
- # define op_read_native op_read_float
- /*TODO: The convergence after 80 ms of preroll is far from exact.
- Our comparison is very rough.
- Need to find some way to do this better.*/
- # define MATCH_TOL (16384.0/32768)
- # define FABS(_x) ((_x)<0?-(_x):(_x))
- # define MATCH(_a,_b) (FABS((_a)-(_b))<MATCH_TOL)
- # if defined(OP_WRITE_SEEK_SAMPLES)
- /*Matrices for downmixing from the supported channel counts to stereo.*/
- static const float DOWNMIX_MATRIX[8][8][2]={
- /*mono*/
- {
- {1.F,1.F}
- },
- /*stereo*/
- {
- {1.F,0.F},{0.F,1.F}
- },
- /*3.0*/
- {
- {0.5858F,0.F},{0.4142F,0.4142F},{0,0.5858F}
- },
- /*quadrophonic*/
- {
- {0.4226F,0.F},{0,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F}
- },
- /*5.0*/
- {
- {0.651F,0.F},{0.46F,0.46F},{0,0.651F},{0.5636F,0.3254F},{0.3254F,0.5636F}
- },
- /*5.1*/
- {
- {0.529F,0.F},{0.3741F,0.3741F},{0.F,0.529F},{0.4582F,0.2645F},
- {0.2645F,0.4582F},{0.3741F,0.3741F}
- },
- /*6.1*/
- {
- {0.4553F,0.F},{0.322F,0.322F},{0.F,0.4553F},{0.3943F,0.2277F},
- {0.2277F,0.3943F},{0.2788F,0.2788F},{0.322F,0.322F}
- },
- /*7.1*/
- {
- {0.3886F,0.F},{0.2748F,0.2748F},{0.F,0.3886F},{0.3366F,0.1943F},
- {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F},{0.2748F,0.2748F}
- }
- };
- static void write_samples(float *_samples,int _nsamples,int _nchannels){
- float stereo_pcm[120*48*2];
- int i;
- for(i=0;i<_nsamples;i++){
- float l;
- float r;
- int ci;
- l=r=0.F;
- for(ci=0;ci<_nchannels;ci++){
- l+=DOWNMIX_MATRIX[_nchannels-1][ci][0]*_samples[i*_nchannels+ci];
- r+=DOWNMIX_MATRIX[_nchannels-1][ci][1]*_samples[i*_nchannels+ci];
- }
- stereo_pcm[2*i+0]=l;
- stereo_pcm[2*i+1]=r;
- }
- fwrite(stereo_pcm,sizeof(*stereo_pcm)*2,_nsamples,stdout);
- }
- # endif
- #endif
- static long nfailures;
- static void verify_seek(OggOpusFile *_of,opus_int64 _byte_offset,
- ogg_int64_t _pcm_offset,ogg_int64_t _pcm_length,op_sample *_bigassbuffer){
- opus_int64 byte_offset;
- ogg_int64_t pcm_offset;
- ogg_int64_t duration;
- op_sample buffer[120*48*8];
- int nchannels;
- int nsamples;
- int li;
- int lj;
- int i;
- byte_offset=op_raw_tell(_of);
- if(_byte_offset!=-1&&byte_offset<_byte_offset){
- fprintf(stderr,"\nRaw position out of tolerance: requested %li, "
- "got %li.\n",(long)_byte_offset,(long)byte_offset);
- nfailures++;
- }
- pcm_offset=op_pcm_tell(_of);
- if(_pcm_offset!=-1&&pcm_offset>_pcm_offset){
- fprintf(stderr,"\nPCM position out of tolerance: requested %li, "
- "got %li.\n",(long)_pcm_offset,(long)pcm_offset);
- nfailures++;
- }
- if(pcm_offset<0||pcm_offset>_pcm_length){
- fprintf(stderr,"\nPCM position out of bounds: got %li.\n",
- (long)pcm_offset);
- nfailures++;
- }
- nsamples=op_read_native(_of,buffer,sizeof(buffer)/sizeof(*buffer),&li);
- if(nsamples<0){
- fprintf(stderr,"\nFailed to read PCM data after seek: %i\n",nsamples);
- nfailures++;
- li=op_current_link(_of);
- }
- for(lj=0;lj<li;lj++){
- duration=op_pcm_total(_of,lj);
- if(0<=pcm_offset&&pcm_offset<duration){
- fprintf(stderr,"\nPCM data after seek came from the wrong link: "
- "expected %i, got %i.\n",lj,li);
- nfailures++;
- }
- pcm_offset-=duration;
- if(_bigassbuffer!=NULL)_bigassbuffer+=op_channel_count(_of,lj)*duration;
- }
- duration=op_pcm_total(_of,li);
- if(pcm_offset+nsamples>duration){
- fprintf(stderr,"\nPCM data after seek exceeded link duration: "
- "limit %li, got %li.\n",(long)duration,(long)(pcm_offset+nsamples));
- nfailures++;
- }
- nchannels=op_channel_count(_of,li);
- if(_bigassbuffer!=NULL){
- for(i=0;i<nsamples*nchannels;i++){
- if(!MATCH(buffer[i],_bigassbuffer[pcm_offset*nchannels+i])){
- ogg_int64_t j;
- fprintf(stderr,"\nData after seek doesn't match declared PCM "
- "position: mismatch %G\n",
- (double)buffer[i]-_bigassbuffer[pcm_offset*nchannels+i]);
- for(j=0;j<duration-nsamples;j++){
- for(i=0;i<nsamples*nchannels;i++){
- if(!MATCH(buffer[i],_bigassbuffer[j*nchannels+i]))break;
- }
- if(i==nsamples*nchannels){
- fprintf(stderr,"\nData after seek appears to match position %li.\n",
- (long)i);
- }
- }
- nfailures++;
- break;
- }
- }
- }
- #if defined(OP_WRITE_SEEK_SAMPLES)
- write_samples(buffer,nsamples,nchannels);
- #endif
- }
- #define OP_MIN(_a,_b) ((_a)<(_b)?(_a):(_b))
- /*A simple wrapper that lets us count the number of underlying seek calls.*/
- static op_seek_func real_seek;
- static long nreal_seeks;
- static int seek_stat_counter(void *_stream,opus_int64 _offset,int _whence){
- if(_whence==SEEK_SET)nreal_seeks++;
- /*SEEK_CUR with an offset of 0 is free, as is SEEK_END with an offset of 0
- (assuming we know the file size), so don't count them.*/
- else if(_offset!=0)nreal_seeks++;
- return (*real_seek)(_stream,_offset,_whence);
- }
- #define NSEEK_TESTS (1000)
- static void print_duration(FILE *_fp,ogg_int64_t _nsamples){
- ogg_int64_t seconds;
- ogg_int64_t minutes;
- ogg_int64_t hours;
- ogg_int64_t days;
- ogg_int64_t weeks;
- seconds=_nsamples/48000;
- _nsamples-=seconds*48000;
- minutes=seconds/60;
- seconds-=minutes*60;
- hours=minutes/60;
- minutes-=hours*60;
- days=hours/24;
- hours-=days*24;
- weeks=days/7;
- days-=weeks*7;
- if(weeks)fprintf(_fp,"%liw",(long)weeks);
- if(weeks||days)fprintf(_fp,"%id",(int)days);
- if(weeks||days||hours){
- if(weeks||days)fprintf(_fp,"%02ih",(int)hours);
- else fprintf(_fp,"%ih",(int)hours);
- }
- if(weeks||days||hours||minutes){
- if(weeks||days||hours)fprintf(_fp,"%02im",(int)minutes);
- else fprintf(_fp,"%im",(int)minutes);
- fprintf(_fp,"%02i",(int)seconds);
- }
- else fprintf(_fp,"%i",(int)seconds);
- fprintf(_fp,".%03is",(int)(_nsamples+24)/48);
- }
- int main(int _argc,const char **_argv){
- OpusFileCallbacks cb;
- OggOpusFile *of;
- void *fp;
- #if defined(_WIN32)
- win32_utf8_setup(&_argc,&_argv);
- #endif
- if(_argc!=2){
- fprintf(stderr,"Usage: %s <file.opus>\n",_argv[0]);
- return EXIT_FAILURE;
- }
- memset(&cb,0,sizeof(cb));
- if(strcmp(_argv[1],"-")==0)fp=op_fdopen(&cb,fileno(stdin),"rb");
- else{
- /*Try to treat the argument as a URL.*/
- fp=op_url_stream_create(&cb,_argv[1],
- OP_SSL_SKIP_CERTIFICATE_CHECK(1),NULL);
- /*Fall back assuming it's a regular file name.*/
- if(fp==NULL)fp=op_fopen(&cb,_argv[1],"rb");
- }
- if(cb.seek!=NULL){
- real_seek=cb.seek;
- cb.seek=seek_stat_counter;
- }
- of=op_open_callbacks(fp,&cb,NULL,0,NULL);
- if(of==NULL){
- fprintf(stderr,"Failed to open file '%s'.\n",_argv[1]);
- return EXIT_FAILURE;
- }
- if(op_seekable(of)){
- op_sample *bigassbuffer;
- ogg_int64_t size;
- ogg_int64_t pcm_offset;
- ogg_int64_t pcm_length;
- ogg_int64_t nsamples;
- long max_seeks;
- int nlinks;
- int ret;
- int li;
- int i;
- /*Because we want to do sample-level verification that the seek does what
- it claimed, decode the entire file into memory.*/
- nlinks=op_link_count(of);
- fprintf(stderr,"Opened file containing %i links with %li seeks "
- "(%0.3f per link).\n",nlinks,nreal_seeks,nreal_seeks/(double)nlinks);
- /*Reset the seek counter.*/
- nreal_seeks=0;
- nsamples=0;
- for(li=0;li<nlinks;li++){
- nsamples+=op_pcm_total(of,li)*op_channel_count(of,li);
- }
- /*Until we find another way to do the comparisons that solves the MATCH_TOL
- problem, disable this.*/
- #if 0
- bigassbuffer=_ogg_malloc(sizeof(*bigassbuffer)*nsamples);
- if(bigassbuffer==NULL){
- fprintf(stderr,
- "Buffer allocation failed. Seek offset detection disabled.\n");
- }
- #else
- bigassbuffer=NULL;
- #endif
- pcm_offset=op_pcm_tell(of);
- if(pcm_offset!=0){
- fprintf(stderr,"Initial PCM offset was not 0, got %li instead.!\n",
- (long)pcm_offset);
- nfailures++;
- }
- /*Disabling the linear scan for now.
- Only test on non-borken files!*/
- #if 0
- {
- op_sample smallerbuffer[120*48*8];
- ogg_int64_t pcm_print_offset;
- ogg_int64_t si;
- opus_int32 bitrate;
- int saw_hole;
- pcm_print_offset=pcm_offset-48000;
- bitrate=0;
- saw_hole=0;
- for(si=0;si<nsamples;){
- ogg_int64_t next_pcm_offset;
- opus_int32 next_bitrate;
- op_sample *buf;
- int buf_size;
- buf=bigassbuffer==NULL?smallerbuffer:bigassbuffer+si;
- buf_size=(int)OP_MIN(nsamples-si,
- (int)(sizeof(smallerbuffer)/sizeof(*smallerbuffer))),
- ret=op_read_native(of,buf,buf_size,&li);
- if(ret==OP_HOLE){
- /*Only warn once in a row.*/
- if(saw_hole)continue;
- saw_hole=1;
- /*This is just a warning.
- As long as the timestamps are still contiguous we're okay.*/
- fprintf(stderr,"\nHole in PCM data at sample %li\n",
- (long)pcm_offset);
- continue;
- }
- else if(ret<=0){
- fprintf(stderr,"\nFailed to read PCM data: %i\n",ret);
- exit(EXIT_FAILURE);
- }
- saw_hole=0;
- /*If we have gaps in the PCM positions, seeking is not likely to work
- near them.*/
- next_pcm_offset=op_pcm_tell(of);
- if(pcm_offset+ret!=next_pcm_offset){
- fprintf(stderr,"\nGap in PCM offset: expecting %li, got %li\n",
- (long)(pcm_offset+ret),(long)next_pcm_offset);
- nfailures++;
- }
- pcm_offset=next_pcm_offset;
- si+=ret*op_channel_count(of,li);
- if(pcm_offset>=pcm_print_offset+48000){
- next_bitrate=op_bitrate_instant(of);
- if(next_bitrate>=0)bitrate=next_bitrate;
- fprintf(stderr,"\r%s... [%li left] (%0.3f kbps) ",
- bigassbuffer==NULL?"Scanning":"Loading",nsamples-si,bitrate/1000.0);
- pcm_print_offset=pcm_offset;
- }
- }
- ret=op_read_native(of,smallerbuffer,8,&li);
- if(ret<0){
- fprintf(stderr,"Failed to read PCM data: %i\n",ret);
- nfailures++;
- }
- if(ret>0){
- fprintf(stderr,"Read too much PCM data!\n");
- nfailures++;
- }
- }
- #endif
- pcm_length=op_pcm_total(of,-1);
- size=op_raw_total(of,-1);
- fprintf(stderr,"\rLoaded (%0.3f kbps average). \n",
- op_bitrate(of,-1)/1000.0);
- fprintf(stderr,"Testing raw seeking to random places in %li bytes...\n",
- (long)size);
- max_seeks=0;
- for(i=0;i<NSEEK_TESTS;i++){
- long nseeks_tmp;
- opus_int64 byte_offset;
- nseeks_tmp=nreal_seeks;
- byte_offset=(opus_int64)(rand()/(double)RAND_MAX*size);
- fprintf(stderr,"\r\t%3i [raw position %li]... ",
- i,(long)byte_offset);
- ret=op_raw_seek(of,byte_offset);
- if(ret<0){
- fprintf(stderr,"\nSeek failed: %i.\n",ret);
- nfailures++;
- }
- if(i==28){
- i=28;
- }
- verify_seek(of,byte_offset,-1,pcm_length,bigassbuffer);
- nseeks_tmp=nreal_seeks-nseeks_tmp;
- max_seeks=nseeks_tmp>max_seeks?nseeks_tmp:max_seeks;
- }
- fprintf(stderr,"\rTotal seek operations: %li (%.3f per raw seek, %li maximum).\n",
- nreal_seeks,nreal_seeks/(double)NSEEK_TESTS,max_seeks);
- nreal_seeks=0;
- fprintf(stderr,"Testing exact PCM seeking to random places in %li "
- "samples (",(long)pcm_length);
- print_duration(stderr,pcm_length);
- fprintf(stderr,")...\n");
- max_seeks=0;
- for(i=0;i<NSEEK_TESTS;i++){
- ogg_int64_t pcm_offset2;
- long nseeks_tmp;
- nseeks_tmp=nreal_seeks;
- pcm_offset=(ogg_int64_t)(rand()/(double)RAND_MAX*pcm_length);
- fprintf(stderr,"\r\t%3i [PCM position %li]... ",
- i,(long)pcm_offset);
- ret=op_pcm_seek(of,pcm_offset);
- if(ret<0){
- fprintf(stderr,"\nSeek failed: %i.\n",ret);
- nfailures++;
- }
- pcm_offset2=op_pcm_tell(of);
- if(pcm_offset!=pcm_offset2){
- fprintf(stderr,"\nDeclared PCM position did not perfectly match "
- "request: requested %li, got %li.\n",
- (long)pcm_offset,(long)pcm_offset2);
- nfailures++;
- }
- verify_seek(of,-1,pcm_offset,pcm_length,bigassbuffer);
- nseeks_tmp=nreal_seeks-nseeks_tmp;
- max_seeks=nseeks_tmp>max_seeks?nseeks_tmp:max_seeks;
- }
- fprintf(stderr,"\rTotal seek operations: %li (%.3f per exact seek, %li maximum).\n",
- nreal_seeks,nreal_seeks/(double)NSEEK_TESTS,max_seeks);
- nreal_seeks=0;
- fprintf(stderr,"OK.\n");
- _ogg_free(bigassbuffer);
- }
- else{
- fprintf(stderr,"Input was not seekable.\n");
- exit(EXIT_FAILURE);
- }
- op_free(of);
- if(nfailures>0){
- fprintf(stderr,"FAILED: %li failure conditions encountered.\n",nfailures);
- }
- return nfailures!=0?EXIT_FAILURE:EXIT_SUCCESS;
- }
|