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