seeking_example.c 14 KB

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