Mercurial > hg > sv-dependency-builds
comparison src/opusfile-0.9/examples/seeking_example.c @ 69:7aeed7906520
Add Opus sources and macOS builds
author | Chris Cannam |
---|---|
date | Wed, 23 Jan 2019 13:48:08 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
68:85d5306e114e | 69:7aeed7906520 |
---|---|
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 } |