seeking_example.c 14 KB

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