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