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 }