Mercurial > hg > sv-dependency-builds
comparison src/opusfile-0.9/examples/seeking_example.c @ 154:4664ac0c1032
Add Opus sources and macOS builds
| author | Chris Cannam <cannam@all-day-breakfast.com> | 
|---|---|
| date | Wed, 23 Jan 2019 13:48:08 +0000 | 
| parents | |
| children | 
   comparison
  equal
  deleted
  inserted
  replaced
| 153:84bc3a5ec321 | 154:4664ac0c1032 | 
|---|---|
| 1 /******************************************************************** | |
| 2 * * | |
| 3 * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * | |
| 4 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * | |
| 5 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * | |
| 6 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * | |
| 7 * * | |
| 8 * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012 * | |
| 9 * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * | |
| 10 * * | |
| 11 ********************************************************************/ | |
| 12 #ifdef HAVE_CONFIG_H | |
| 13 #include "config.h" | |
| 14 #endif | |
| 15 | |
| 16 /*For fileno()*/ | |
| 17 #if !defined(_POSIX_SOURCE) | |
| 18 # define _POSIX_SOURCE 1 | |
| 19 #endif | |
| 20 #include <stdio.h> | |
| 21 #include <stdlib.h> | |
| 22 #include <errno.h> | |
| 23 #include <math.h> | |
| 24 #include <string.h> | |
| 25 #include <opusfile.h> | |
| 26 #if defined(_WIN32) | |
| 27 # include "win32utf8.h" | |
| 28 # undef fileno | |
| 29 # define fileno _fileno | |
| 30 #endif | |
| 31 | |
| 32 /*Use shorts, they're smaller.*/ | |
| 33 #if !defined(OP_FIXED_POINT) | |
| 34 # define OP_FIXED_POINT (1) | |
| 35 #endif | |
| 36 | |
| 37 #if defined(OP_FIXED_POINT) | |
| 38 | |
| 39 typedef opus_int16 op_sample; | |
| 40 | |
| 41 # define op_read_native op_read | |
| 42 | |
| 43 /*TODO: The convergence after 80 ms of preroll is far from exact. | |
| 44 Our comparison is very rough. | |
| 45 Need to find some way to do this better.*/ | |
| 46 # define MATCH_TOL (16384) | |
| 47 | |
| 48 # define ABS(_x) ((_x)<0?-(_x):(_x)) | |
| 49 | |
| 50 # define MATCH(_a,_b) (ABS((_a)-(_b))<MATCH_TOL) | |
| 51 | |
| 52 /*Don't have fixed-point downmixing code.*/ | |
| 53 # undef OP_WRITE_SEEK_SAMPLES | |
| 54 | |
| 55 #else | |
| 56 | |
| 57 typedef float op_sample; | |
| 58 | |
| 59 # define op_read_native op_read_float | |
| 60 | |
| 61 /*TODO: The convergence after 80 ms of preroll is far from exact. | |
| 62 Our comparison is very rough. | |
| 63 Need to find some way to do this better.*/ | |
| 64 # define MATCH_TOL (16384.0/32768) | |
| 65 | |
| 66 # define FABS(_x) ((_x)<0?-(_x):(_x)) | |
| 67 | |
| 68 # define MATCH(_a,_b) (FABS((_a)-(_b))<MATCH_TOL) | |
| 69 | |
| 70 # if defined(OP_WRITE_SEEK_SAMPLES) | |
| 71 /*Matrices for downmixing from the supported channel counts to stereo.*/ | |
| 72 static const float DOWNMIX_MATRIX[8][8][2]={ | |
| 73 /*mono*/ | |
| 74 { | |
| 75 {1.F,1.F} | |
| 76 }, | |
| 77 /*stereo*/ | |
| 78 { | |
| 79 {1.F,0.F},{0.F,1.F} | |
| 80 }, | |
| 81 /*3.0*/ | |
| 82 { | |
| 83 {0.5858F,0.F},{0.4142F,0.4142F},{0,0.5858F} | |
| 84 }, | |
| 85 /*quadrophonic*/ | |
| 86 { | |
| 87 {0.4226F,0.F},{0,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F} | |
| 88 }, | |
| 89 /*5.0*/ | |
| 90 { | |
| 91 {0.651F,0.F},{0.46F,0.46F},{0,0.651F},{0.5636F,0.3254F},{0.3254F,0.5636F} | |
| 92 }, | |
| 93 /*5.1*/ | |
| 94 { | |
| 95 {0.529F,0.F},{0.3741F,0.3741F},{0.F,0.529F},{0.4582F,0.2645F}, | |
| 96 {0.2645F,0.4582F},{0.3741F,0.3741F} | |
| 97 }, | |
| 98 /*6.1*/ | |
| 99 { | |
| 100 {0.4553F,0.F},{0.322F,0.322F},{0.F,0.4553F},{0.3943F,0.2277F}, | |
| 101 {0.2277F,0.3943F},{0.2788F,0.2788F},{0.322F,0.322F} | |
| 102 }, | |
| 103 /*7.1*/ | |
| 104 { | |
| 105 {0.3886F,0.F},{0.2748F,0.2748F},{0.F,0.3886F},{0.3366F,0.1943F}, | |
| 106 {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F},{0.2748F,0.2748F} | |
| 107 } | |
| 108 }; | |
| 109 | |
| 110 static void write_samples(float *_samples,int _nsamples,int _nchannels){ | |
| 111 float stereo_pcm[120*48*2]; | |
| 112 int i; | |
| 113 for(i=0;i<_nsamples;i++){ | |
| 114 float l; | |
| 115 float r; | |
| 116 int ci; | |
| 117 l=r=0.F; | |
| 118 for(ci=0;ci<_nchannels;ci++){ | |
| 119 l+=DOWNMIX_MATRIX[_nchannels-1][ci][0]*_samples[i*_nchannels+ci]; | |
| 120 r+=DOWNMIX_MATRIX[_nchannels-1][ci][1]*_samples[i*_nchannels+ci]; | |
| 121 } | |
| 122 stereo_pcm[2*i+0]=l; | |
| 123 stereo_pcm[2*i+1]=r; | |
| 124 } | |
| 125 fwrite(stereo_pcm,sizeof(*stereo_pcm)*2,_nsamples,stdout); | |
| 126 } | |
| 127 # endif | |
| 128 | |
| 129 #endif | |
| 130 | |
| 131 static long nfailures; | |
| 132 | |
| 133 static void verify_seek(OggOpusFile *_of,opus_int64 _byte_offset, | |
| 134 ogg_int64_t _pcm_offset,ogg_int64_t _pcm_length,op_sample *_bigassbuffer){ | |
| 135 opus_int64 byte_offset; | |
| 136 ogg_int64_t pcm_offset; | |
| 137 ogg_int64_t duration; | |
| 138 op_sample buffer[120*48*8]; | |
| 139 int nchannels; | |
| 140 int nsamples; | |
| 141 int li; | |
| 142 int lj; | |
| 143 int i; | |
| 144 byte_offset=op_raw_tell(_of); | |
| 145 if(_byte_offset!=-1&&byte_offset<_byte_offset){ | |
| 146 fprintf(stderr,"\nRaw position out of tolerance: requested %li, " | |
| 147 "got %li.\n",(long)_byte_offset,(long)byte_offset); | |
| 148 nfailures++; | |
| 149 } | |
| 150 pcm_offset=op_pcm_tell(_of); | |
| 151 if(_pcm_offset!=-1&&pcm_offset>_pcm_offset){ | |
| 152 fprintf(stderr,"\nPCM position out of tolerance: requested %li, " | |
| 153 "got %li.\n",(long)_pcm_offset,(long)pcm_offset); | |
| 154 nfailures++; | |
| 155 } | |
| 156 if(pcm_offset<0||pcm_offset>_pcm_length){ | |
| 157 fprintf(stderr,"\nPCM position out of bounds: got %li.\n", | |
| 158 (long)pcm_offset); | |
| 159 nfailures++; | |
| 160 } | |
| 161 nsamples=op_read_native(_of,buffer,sizeof(buffer)/sizeof(*buffer),&li); | |
| 162 if(nsamples<0){ | |
| 163 fprintf(stderr,"\nFailed to read PCM data after seek: %i\n",nsamples); | |
| 164 nfailures++; | |
| 165 li=op_current_link(_of); | |
| 166 } | |
| 167 for(lj=0;lj<li;lj++){ | |
| 168 duration=op_pcm_total(_of,lj); | |
| 169 if(0<=pcm_offset&&pcm_offset<duration){ | |
| 170 fprintf(stderr,"\nPCM data after seek came from the wrong link: " | |
| 171 "expected %i, got %i.\n",lj,li); | |
| 172 nfailures++; | |
| 173 } | |
| 174 pcm_offset-=duration; | |
| 175 if(_bigassbuffer!=NULL)_bigassbuffer+=op_channel_count(_of,lj)*duration; | |
| 176 } | |
| 177 duration=op_pcm_total(_of,li); | |
| 178 if(pcm_offset+nsamples>duration){ | |
| 179 fprintf(stderr,"\nPCM data after seek exceeded link duration: " | |
| 180 "limit %li, got %li.\n",(long)duration,(long)(pcm_offset+nsamples)); | |
| 181 nfailures++; | |
| 182 } | |
| 183 nchannels=op_channel_count(_of,li); | |
| 184 if(_bigassbuffer!=NULL){ | |
| 185 for(i=0;i<nsamples*nchannels;i++){ | |
| 186 if(!MATCH(buffer[i],_bigassbuffer[pcm_offset*nchannels+i])){ | |
| 187 ogg_int64_t j; | |
| 188 fprintf(stderr,"\nData after seek doesn't match declared PCM " | |
| 189 "position: mismatch %G\n", | |
| 190 (double)buffer[i]-_bigassbuffer[pcm_offset*nchannels+i]); | |
| 191 for(j=0;j<duration-nsamples;j++){ | |
| 192 for(i=0;i<nsamples*nchannels;i++){ | |
| 193 if(!MATCH(buffer[i],_bigassbuffer[j*nchannels+i]))break; | |
| 194 } | |
| 195 if(i==nsamples*nchannels){ | |
| 196 fprintf(stderr,"\nData after seek appears to match position %li.\n", | |
| 197 (long)i); | |
| 198 } | |
| 199 } | |
| 200 nfailures++; | |
| 201 break; | |
| 202 } | |
| 203 } | |
| 204 } | |
| 205 #if defined(OP_WRITE_SEEK_SAMPLES) | |
| 206 write_samples(buffer,nsamples,nchannels); | |
| 207 #endif | |
| 208 } | |
| 209 | |
| 210 #define OP_MIN(_a,_b) ((_a)<(_b)?(_a):(_b)) | |
| 211 | |
| 212 /*A simple wrapper that lets us count the number of underlying seek calls.*/ | |
| 213 | |
| 214 static op_seek_func real_seek; | |
| 215 | |
| 216 static long nreal_seeks; | |
| 217 | |
| 218 static int seek_stat_counter(void *_stream,opus_int64 _offset,int _whence){ | |
| 219 if(_whence==SEEK_SET)nreal_seeks++; | |
| 220 /*SEEK_CUR with an offset of 0 is free, as is SEEK_END with an offset of 0 | |
| 221 (assuming we know the file size), so don't count them.*/ | |
| 222 else if(_offset!=0)nreal_seeks++; | |
| 223 return (*real_seek)(_stream,_offset,_whence); | |
| 224 } | |
| 225 | |
| 226 #define NSEEK_TESTS (1000) | |
| 227 | |
| 228 static void print_duration(FILE *_fp,ogg_int64_t _nsamples){ | |
| 229 ogg_int64_t seconds; | |
| 230 ogg_int64_t minutes; | |
| 231 ogg_int64_t hours; | |
| 232 ogg_int64_t days; | |
| 233 ogg_int64_t weeks; | |
| 234 seconds=_nsamples/48000; | |
| 235 _nsamples-=seconds*48000; | |
| 236 minutes=seconds/60; | |
| 237 seconds-=minutes*60; | |
| 238 hours=minutes/60; | |
| 239 minutes-=hours*60; | |
| 240 days=hours/24; | |
| 241 hours-=days*24; | |
| 242 weeks=days/7; | |
| 243 days-=weeks*7; | |
| 244 if(weeks)fprintf(_fp,"%liw",(long)weeks); | |
| 245 if(weeks||days)fprintf(_fp,"%id",(int)days); | |
| 246 if(weeks||days||hours){ | |
| 247 if(weeks||days)fprintf(_fp,"%02ih",(int)hours); | |
| 248 else fprintf(_fp,"%ih",(int)hours); | |
| 249 } | |
| 250 if(weeks||days||hours||minutes){ | |
| 251 if(weeks||days||hours)fprintf(_fp,"%02im",(int)minutes); | |
| 252 else fprintf(_fp,"%im",(int)minutes); | |
| 253 fprintf(_fp,"%02i",(int)seconds); | |
| 254 } | |
| 255 else fprintf(_fp,"%i",(int)seconds); | |
| 256 fprintf(_fp,".%03is",(int)(_nsamples+24)/48); | |
| 257 } | |
| 258 | |
| 259 int main(int _argc,const char **_argv){ | |
| 260 OpusFileCallbacks cb; | |
| 261 OggOpusFile *of; | |
| 262 void *fp; | |
| 263 #if defined(_WIN32) | |
| 264 win32_utf8_setup(&_argc,&_argv); | |
| 265 #endif | |
| 266 if(_argc!=2){ | |
| 267 fprintf(stderr,"Usage: %s <file.opus>\n",_argv[0]); | |
| 268 return EXIT_FAILURE; | |
| 269 } | |
| 270 memset(&cb,0,sizeof(cb)); | |
| 271 if(strcmp(_argv[1],"-")==0)fp=op_fdopen(&cb,fileno(stdin),"rb"); | |
| 272 else{ | |
| 273 /*Try to treat the argument as a URL.*/ | |
| 274 fp=op_url_stream_create(&cb,_argv[1], | |
| 275 OP_SSL_SKIP_CERTIFICATE_CHECK(1),NULL); | |
| 276 /*Fall back assuming it's a regular file name.*/ | |
| 277 if(fp==NULL)fp=op_fopen(&cb,_argv[1],"rb"); | |
| 278 } | |
| 279 if(cb.seek!=NULL){ | |
| 280 real_seek=cb.seek; | |
| 281 cb.seek=seek_stat_counter; | |
| 282 } | |
| 283 of=op_open_callbacks(fp,&cb,NULL,0,NULL); | |
| 284 if(of==NULL){ | |
| 285 fprintf(stderr,"Failed to open file '%s'.\n",_argv[1]); | |
| 286 return EXIT_FAILURE; | |
| 287 } | |
| 288 if(op_seekable(of)){ | |
| 289 op_sample *bigassbuffer; | |
| 290 ogg_int64_t size; | |
| 291 ogg_int64_t pcm_offset; | |
| 292 ogg_int64_t pcm_length; | |
| 293 ogg_int64_t nsamples; | |
| 294 long max_seeks; | |
| 295 int nlinks; | |
| 296 int ret; | |
| 297 int li; | |
| 298 int i; | |
| 299 /*Because we want to do sample-level verification that the seek does what | |
| 300 it claimed, decode the entire file into memory.*/ | |
| 301 nlinks=op_link_count(of); | |
| 302 fprintf(stderr,"Opened file containing %i links with %li seeks " | |
| 303 "(%0.3f per link).\n",nlinks,nreal_seeks,nreal_seeks/(double)nlinks); | |
| 304 /*Reset the seek counter.*/ | |
| 305 nreal_seeks=0; | |
| 306 nsamples=0; | |
| 307 for(li=0;li<nlinks;li++){ | |
| 308 nsamples+=op_pcm_total(of,li)*op_channel_count(of,li); | |
| 309 } | |
| 310 /*Until we find another way to do the comparisons that solves the MATCH_TOL | |
| 311 problem, disable this.*/ | |
| 312 #if 0 | |
| 313 bigassbuffer=_ogg_malloc(sizeof(*bigassbuffer)*nsamples); | |
| 314 if(bigassbuffer==NULL){ | |
| 315 fprintf(stderr, | |
| 316 "Buffer allocation failed. Seek offset detection disabled.\n"); | |
| 317 } | |
| 318 #else | |
| 319 bigassbuffer=NULL; | |
| 320 #endif | |
| 321 pcm_offset=op_pcm_tell(of); | |
| 322 if(pcm_offset!=0){ | |
| 323 fprintf(stderr,"Initial PCM offset was not 0, got %li instead.!\n", | |
| 324 (long)pcm_offset); | |
| 325 nfailures++; | |
| 326 } | |
| 327 /*Disabling the linear scan for now. | |
| 328 Only test on non-borken files!*/ | |
| 329 #if 0 | |
| 330 { | |
| 331 op_sample smallerbuffer[120*48*8]; | |
| 332 ogg_int64_t pcm_print_offset; | |
| 333 ogg_int64_t si; | |
| 334 opus_int32 bitrate; | |
| 335 int saw_hole; | |
| 336 pcm_print_offset=pcm_offset-48000; | |
| 337 bitrate=0; | |
| 338 saw_hole=0; | |
| 339 for(si=0;si<nsamples;){ | |
| 340 ogg_int64_t next_pcm_offset; | |
| 341 opus_int32 next_bitrate; | |
| 342 op_sample *buf; | |
| 343 int buf_size; | |
| 344 buf=bigassbuffer==NULL?smallerbuffer:bigassbuffer+si; | |
| 345 buf_size=(int)OP_MIN(nsamples-si, | |
| 346 (int)(sizeof(smallerbuffer)/sizeof(*smallerbuffer))), | |
| 347 ret=op_read_native(of,buf,buf_size,&li); | |
| 348 if(ret==OP_HOLE){ | |
| 349 /*Only warn once in a row.*/ | |
| 350 if(saw_hole)continue; | |
| 351 saw_hole=1; | |
| 352 /*This is just a warning. | |
| 353 As long as the timestamps are still contiguous we're okay.*/ | |
| 354 fprintf(stderr,"\nHole in PCM data at sample %li\n", | |
| 355 (long)pcm_offset); | |
| 356 continue; | |
| 357 } | |
| 358 else if(ret<=0){ | |
| 359 fprintf(stderr,"\nFailed to read PCM data: %i\n",ret); | |
| 360 exit(EXIT_FAILURE); | |
| 361 } | |
| 362 saw_hole=0; | |
| 363 /*If we have gaps in the PCM positions, seeking is not likely to work | |
| 364 near them.*/ | |
| 365 next_pcm_offset=op_pcm_tell(of); | |
| 366 if(pcm_offset+ret!=next_pcm_offset){ | |
| 367 fprintf(stderr,"\nGap in PCM offset: expecting %li, got %li\n", | |
| 368 (long)(pcm_offset+ret),(long)next_pcm_offset); | |
| 369 nfailures++; | |
| 370 } | |
| 371 pcm_offset=next_pcm_offset; | |
| 372 si+=ret*op_channel_count(of,li); | |
| 373 if(pcm_offset>=pcm_print_offset+48000){ | |
| 374 next_bitrate=op_bitrate_instant(of); | |
| 375 if(next_bitrate>=0)bitrate=next_bitrate; | |
| 376 fprintf(stderr,"\r%s... [%li left] (%0.3f kbps) ", | |
| 377 bigassbuffer==NULL?"Scanning":"Loading",nsamples-si,bitrate/1000.0); | |
| 378 pcm_print_offset=pcm_offset; | |
| 379 } | |
| 380 } | |
| 381 ret=op_read_native(of,smallerbuffer,8,&li); | |
| 382 if(ret<0){ | |
| 383 fprintf(stderr,"Failed to read PCM data: %i\n",ret); | |
| 384 nfailures++; | |
| 385 } | |
| 386 if(ret>0){ | |
| 387 fprintf(stderr,"Read too much PCM data!\n"); | |
| 388 nfailures++; | |
| 389 } | |
| 390 } | |
| 391 #endif | |
| 392 pcm_length=op_pcm_total(of,-1); | |
| 393 size=op_raw_total(of,-1); | |
| 394 fprintf(stderr,"\rLoaded (%0.3f kbps average). \n", | |
| 395 op_bitrate(of,-1)/1000.0); | |
| 396 fprintf(stderr,"Testing raw seeking to random places in %li bytes...\n", | |
| 397 (long)size); | |
| 398 max_seeks=0; | |
| 399 for(i=0;i<NSEEK_TESTS;i++){ | |
| 400 long nseeks_tmp; | |
| 401 opus_int64 byte_offset; | |
| 402 nseeks_tmp=nreal_seeks; | |
| 403 byte_offset=(opus_int64)(rand()/(double)RAND_MAX*size); | |
| 404 fprintf(stderr,"\r\t%3i [raw position %li]... ", | |
| 405 i,(long)byte_offset); | |
| 406 ret=op_raw_seek(of,byte_offset); | |
| 407 if(ret<0){ | |
| 408 fprintf(stderr,"\nSeek failed: %i.\n",ret); | |
| 409 nfailures++; | |
| 410 } | |
| 411 if(i==28){ | |
| 412 i=28; | |
| 413 } | |
| 414 verify_seek(of,byte_offset,-1,pcm_length,bigassbuffer); | |
| 415 nseeks_tmp=nreal_seeks-nseeks_tmp; | |
| 416 max_seeks=nseeks_tmp>max_seeks?nseeks_tmp:max_seeks; | |
| 417 } | |
| 418 fprintf(stderr,"\rTotal seek operations: %li (%.3f per raw seek, %li maximum).\n", | |
| 419 nreal_seeks,nreal_seeks/(double)NSEEK_TESTS,max_seeks); | |
| 420 nreal_seeks=0; | |
| 421 fprintf(stderr,"Testing exact PCM seeking to random places in %li " | |
| 422 "samples (",(long)pcm_length); | |
| 423 print_duration(stderr,pcm_length); | |
| 424 fprintf(stderr,")...\n"); | |
| 425 max_seeks=0; | |
| 426 for(i=0;i<NSEEK_TESTS;i++){ | |
| 427 ogg_int64_t pcm_offset2; | |
| 428 long nseeks_tmp; | |
| 429 nseeks_tmp=nreal_seeks; | |
| 430 pcm_offset=(ogg_int64_t)(rand()/(double)RAND_MAX*pcm_length); | |
| 431 fprintf(stderr,"\r\t%3i [PCM position %li]... ", | |
| 432 i,(long)pcm_offset); | |
| 433 ret=op_pcm_seek(of,pcm_offset); | |
| 434 if(ret<0){ | |
| 435 fprintf(stderr,"\nSeek failed: %i.\n",ret); | |
| 436 nfailures++; | |
| 437 } | |
| 438 pcm_offset2=op_pcm_tell(of); | |
| 439 if(pcm_offset!=pcm_offset2){ | |
| 440 fprintf(stderr,"\nDeclared PCM position did not perfectly match " | |
| 441 "request: requested %li, got %li.\n", | |
| 442 (long)pcm_offset,(long)pcm_offset2); | |
| 443 nfailures++; | |
| 444 } | |
| 445 verify_seek(of,-1,pcm_offset,pcm_length,bigassbuffer); | |
| 446 nseeks_tmp=nreal_seeks-nseeks_tmp; | |
| 447 max_seeks=nseeks_tmp>max_seeks?nseeks_tmp:max_seeks; | |
| 448 } | |
| 449 fprintf(stderr,"\rTotal seek operations: %li (%.3f per exact seek, %li maximum).\n", | |
| 450 nreal_seeks,nreal_seeks/(double)NSEEK_TESTS,max_seeks); | |
| 451 nreal_seeks=0; | |
| 452 fprintf(stderr,"OK.\n"); | |
| 453 _ogg_free(bigassbuffer); | |
| 454 } | |
| 455 else{ | |
| 456 fprintf(stderr,"Input was not seekable.\n"); | |
| 457 exit(EXIT_FAILURE); | |
| 458 } | |
| 459 op_free(of); | |
| 460 if(nfailures>0){ | |
| 461 fprintf(stderr,"FAILED: %li failure conditions encountered.\n",nfailures); | |
| 462 } | |
| 463 return nfailures!=0?EXIT_FAILURE:EXIT_SUCCESS; | |
| 464 } | 
