diff options
Diffstat (limited to 'lib/opusfile/examples')
| -rw-r--r-- | lib/opusfile/examples/opusfile_example.c | 390 | ||||
| -rw-r--r-- | lib/opusfile/examples/seeking_example.c | 465 | ||||
| -rw-r--r-- | lib/opusfile/examples/win32utf8.c | 123 | ||||
| -rw-r--r-- | lib/opusfile/examples/win32utf8.h | 21 |
4 files changed, 999 insertions, 0 deletions
diff --git a/lib/opusfile/examples/opusfile_example.c b/lib/opusfile/examples/opusfile_example.c new file mode 100644 index 00000000..88ba6aad --- /dev/null +++ b/lib/opusfile/examples/opusfile_example.c @@ -0,0 +1,390 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2020 * + * by the Xiph.Org Foundation and contributors https://xiph.org/ * + * * + ********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/*For fileno()*/ +#if !defined(_POSIX_SOURCE) +# define _POSIX_SOURCE 1 +#endif +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <opusfile.h> +#if defined(_WIN32) +# include "win32utf8.h" +# undef fileno +# define fileno _fileno +#endif + +static void print_duration(FILE *_fp,ogg_int64_t _nsamples,int _frac){ + ogg_int64_t seconds; + ogg_int64_t minutes; + ogg_int64_t hours; + ogg_int64_t days; + ogg_int64_t weeks; + _nsamples+=_frac?24:24000; + seconds=_nsamples/48000; + _nsamples-=seconds*48000; + minutes=seconds/60; + seconds-=minutes*60; + hours=minutes/60; + minutes-=hours*60; + days=hours/24; + hours-=days*24; + weeks=days/7; + days-=weeks*7; + if(weeks)fprintf(_fp,"%liw",(long)weeks); + if(weeks||days)fprintf(_fp,"%id",(int)days); + if(weeks||days||hours){ + if(weeks||days)fprintf(_fp,"%02ih",(int)hours); + else fprintf(_fp,"%ih",(int)hours); + } + if(weeks||days||hours||minutes){ + if(weeks||days||hours)fprintf(_fp,"%02im",(int)minutes); + else fprintf(_fp,"%im",(int)minutes); + fprintf(_fp,"%02i",(int)seconds); + } + else fprintf(_fp,"%i",(int)seconds); + if(_frac)fprintf(_fp,".%03i",(int)(_nsamples/48)); + fprintf(_fp,"s"); +} + +static void print_size(FILE *_fp,opus_int64 _nbytes,int _metric, + const char *_spacer){ + static const char SUFFIXES[7]={' ','k','M','G','T','P','E'}; + opus_int64 val; + opus_int64 den; + opus_int64 round; + int base; + int shift; + base=_metric?1000:1024; + round=0; + den=1; + for(shift=0;shift<6;shift++){ + if(_nbytes<den*base-round)break; + den*=base; + round=den>>1; + } + val=(_nbytes+round)/den; + if(den>1&&val<10){ + if(den>=1000000000)val=(_nbytes+(round/100))/(den/100); + else val=(_nbytes*100+round)/den; + fprintf(_fp,"%li.%02i%s%c",(long)(val/100),(int)(val%100), + _spacer,SUFFIXES[shift]); + } + else if(den>1&&val<100){ + if(den>=1000000000)val=(_nbytes+(round/10))/(den/10); + else val=(_nbytes*10+round)/den; + fprintf(_fp,"%li.%i%s%c",(long)(val/10),(int)(val%10), + _spacer,SUFFIXES[shift]); + } + else fprintf(_fp,"%li%s%c",(long)val,_spacer,SUFFIXES[shift]); +} + +static void put_le32(unsigned char *_dst,opus_uint32 _x){ + _dst[0]=(unsigned char)(_x&0xFF); + _dst[1]=(unsigned char)(_x>>8&0xFF); + _dst[2]=(unsigned char)(_x>>16&0xFF); + _dst[3]=(unsigned char)(_x>>24&0xFF); +} + +/*Make a header for a 48 kHz, stereo, signed, 16-bit little-endian PCM WAV.*/ +static void make_wav_header(unsigned char _dst[44],ogg_int64_t _duration){ + /*The chunk sizes are set to 0x7FFFFFFF by default. + Many, though not all, programs will interpret this to mean the duration is + "undefined", and continue to read from the file so long as there is actual + data.*/ + static const unsigned char WAV_HEADER_TEMPLATE[44]={ + 'R','I','F','F',0xFF,0xFF,0xFF,0x7F, + 'W','A','V','E','f','m','t',' ', + 0x10,0x00,0x00,0x00,0x01,0x00,0x02,0x00, + 0x80,0xBB,0x00,0x00,0x00,0xEE,0x02,0x00, + 0x04,0x00,0x10,0x00,'d','a','t','a', + 0xFF,0xFF,0xFF,0x7F + }; + memcpy(_dst,WAV_HEADER_TEMPLATE,sizeof(WAV_HEADER_TEMPLATE)); + if(_duration>0){ + if(_duration>0x1FFFFFF6){ + fprintf(stderr,"WARNING: WAV output would be larger than 2 GB.\n"); + fprintf(stderr, + "Writing non-standard WAV header with invalid chunk sizes.\n"); + } + else{ + opus_uint32 audio_size; + audio_size=(opus_uint32)(_duration*4); + put_le32(_dst+4,audio_size+36); + put_le32(_dst+40,audio_size); + } + } +} + +int main(int _argc,const char **_argv){ + OggOpusFile *of; + ogg_int64_t duration; + unsigned char wav_header[44]; + int ret; + int is_ssl; + int output_seekable; +#if defined(_WIN32) + win32_utf8_setup(&_argc,&_argv); +#endif + if(_argc!=2){ + fprintf(stderr,"Usage: %s <file.opus>\n",_argv[0]); + return EXIT_FAILURE; + } + is_ssl=0; + if(strcmp(_argv[1],"-")==0){ + OpusFileCallbacks cb={NULL,NULL,NULL,NULL}; + of=op_open_callbacks(op_fdopen(&cb,fileno(stdin),"rb"),&cb,NULL,0,&ret); + } + else{ + OpusServerInfo info; + /*Try to treat the argument as a URL.*/ + of=op_open_url(_argv[1],&ret,OP_GET_SERVER_INFO(&info),NULL); +#if 0 + if(of==NULL){ + OpusFileCallbacks cb={NULL,NULL,NULL,NULL}; + void *fp; + /*For debugging: force a file to not be seekable.*/ + fp=op_fopen(&cb,_argv[1],"rb"); + cb.seek=NULL; + cb.tell=NULL; + of=op_open_callbacks(fp,&cb,NULL,0,NULL); + } +#else + if(of==NULL)of=op_open_file(_argv[1],&ret); +#endif + else{ + if(info.name!=NULL){ + fprintf(stderr,"Station name: %s\n",info.name); + } + if(info.description!=NULL){ + fprintf(stderr,"Station description: %s\n",info.description); + } + if(info.genre!=NULL){ + fprintf(stderr,"Station genre: %s\n",info.genre); + } + if(info.url!=NULL){ + fprintf(stderr,"Station homepage: %s\n",info.url); + } + if(info.bitrate_kbps>=0){ + fprintf(stderr,"Station bitrate: %u kbps\n", + (unsigned)info.bitrate_kbps); + } + if(info.is_public>=0){ + fprintf(stderr,"%s\n", + info.is_public?"Station is public.":"Station is private."); + } + if(info.server!=NULL){ + fprintf(stderr,"Server software: %s\n",info.server); + } + if(info.content_type!=NULL){ + fprintf(stderr,"Content-Type: %s\n",info.content_type); + } + is_ssl=info.is_ssl; + opus_server_info_clear(&info); + } + } + if(of==NULL){ + fprintf(stderr,"Failed to open file '%s': %i\n",_argv[1],ret); + return EXIT_FAILURE; + } + duration=0; + output_seekable=fseek(stdout,0,SEEK_CUR)!=-1; + if(op_seekable(of)){ + opus_int64 size; + fprintf(stderr,"Total number of links: %i\n",op_link_count(of)); + duration=op_pcm_total(of,-1); + fprintf(stderr,"Total duration: "); + print_duration(stderr,duration,3); + fprintf(stderr," (%li samples @ 48 kHz)\n",(long)duration); + size=op_raw_total(of,-1); + fprintf(stderr,"Total size: "); + print_size(stderr,size,0,""); + fprintf(stderr,"\n"); + } + else if(!output_seekable){ + fprintf(stderr,"WARNING: Neither input nor output are seekable.\n"); + fprintf(stderr, + "Writing non-standard WAV header with invalid chunk sizes.\n"); + } + make_wav_header(wav_header,duration); + if(!fwrite(wav_header,sizeof(wav_header),1,stdout)){ + fprintf(stderr,"Error writing WAV header: %s\n",strerror(errno)); + ret=EXIT_FAILURE; + } + else{ + ogg_int64_t pcm_offset; + ogg_int64_t pcm_print_offset; + ogg_int64_t nsamples; + opus_int32 bitrate; + int prev_li; + prev_li=-1; + nsamples=0; + pcm_offset=op_pcm_tell(of); + if(pcm_offset!=0){ + fprintf(stderr,"Non-zero starting PCM offset: %li\n",(long)pcm_offset); + } + pcm_print_offset=pcm_offset-48000; + bitrate=0; + for(;;){ + ogg_int64_t next_pcm_offset; + opus_int16 pcm[120*48*2]; + unsigned char out[120*48*2*2]; + int li; + int si; + /*Although we would generally prefer to use the float interface, WAV + files with signed, 16-bit little-endian samples are far more + universally supported, so that's what we output.*/ + ret=op_read_stereo(of,pcm,sizeof(pcm)/sizeof(*pcm)); + if(ret==OP_HOLE){ + fprintf(stderr,"\nHole detected! Corrupt file segment?\n"); + continue; + } + else if(ret<0){ + fprintf(stderr,"\nError decoding '%s': %i\n",_argv[1],ret); + if(is_ssl)fprintf(stderr,"Possible truncation attack?\n"); + ret=EXIT_FAILURE; + break; + } + li=op_current_link(of); + if(li!=prev_li){ + const OpusHead *head; + const OpusTags *tags; + int binary_suffix_len; + int ci; + /*We found a new link. + Print out some information.*/ + fprintf(stderr,"Decoding link %i: \n",li); + head=op_head(of,li); + fprintf(stderr," Channels: %i\n",head->channel_count); + if(op_seekable(of)){ + ogg_int64_t duration; + opus_int64 size; + duration=op_pcm_total(of,li); + fprintf(stderr," Duration: "); + print_duration(stderr,duration,3); + fprintf(stderr," (%li samples @ 48 kHz)\n",(long)duration); + size=op_raw_total(of,li); + fprintf(stderr," Size: "); + print_size(stderr,size,0,""); + fprintf(stderr,"\n"); + } + if(head->input_sample_rate){ + fprintf(stderr," Original sampling rate: %lu Hz\n", + (unsigned long)head->input_sample_rate); + } + tags=op_tags(of,li); + fprintf(stderr," Encoded by: %s\n",tags->vendor); + for(ci=0;ci<tags->comments;ci++){ + const char *comment; + comment=tags->user_comments[ci]; + if(opus_tagncompare("METADATA_BLOCK_PICTURE",22,comment)==0){ + OpusPictureTag pic; + int err; + err=opus_picture_tag_parse(&pic,comment); + fprintf(stderr," %.23s",comment); + if(err>=0){ + fprintf(stderr,"%u|%s|%s|%ux%ux%u",pic.type,pic.mime_type, + pic.description,pic.width,pic.height,pic.depth); + if(pic.colors!=0)fprintf(stderr,"/%u",pic.colors); + if(pic.format==OP_PIC_FORMAT_URL){ + fprintf(stderr,"|%s\n",pic.data); + } + else{ + fprintf(stderr,"|<%u bytes of image data>\n",pic.data_length); + } + opus_picture_tag_clear(&pic); + } + else fprintf(stderr,"<error parsing picture tag>\n"); + } + else fprintf(stderr," %s\n",tags->user_comments[ci]); + } + if(opus_tags_get_binary_suffix(tags,&binary_suffix_len)!=NULL){ + fprintf(stderr,"<%u bytes of unknown binary metadata>\n", + binary_suffix_len); + } + fprintf(stderr,"\n"); + if(!op_seekable(of)){ + pcm_offset=op_pcm_tell(of)-ret; + if(pcm_offset!=0){ + fprintf(stderr,"Non-zero starting PCM offset in link %i: %li\n", + li,(long)pcm_offset); + } + } + } + if(li!=prev_li||pcm_offset>=pcm_print_offset+48000){ + opus_int32 next_bitrate; + opus_int64 raw_offset; + next_bitrate=op_bitrate_instant(of); + if(next_bitrate>=0)bitrate=next_bitrate; + raw_offset=op_raw_tell(of); + fprintf(stderr,"\r "); + print_size(stderr,raw_offset,0,""); + fprintf(stderr," "); + print_duration(stderr,pcm_offset,0); + fprintf(stderr," ("); + print_size(stderr,bitrate,1," "); + fprintf(stderr,"bps) \r"); + pcm_print_offset=pcm_offset; + fflush(stderr); + } + next_pcm_offset=op_pcm_tell(of); + if(pcm_offset+ret!=next_pcm_offset){ + fprintf(stderr,"\nPCM offset gap! %li+%i!=%li\n", + (long)pcm_offset,ret,(long)next_pcm_offset); + } + pcm_offset=next_pcm_offset; + if(ret<=0){ + ret=EXIT_SUCCESS; + break; + } + /*Ensure the data is little-endian before writing it out.*/ + for(si=0;si<2*ret;si++){ + out[2*si+0]=(unsigned char)(pcm[si]&0xFF); + out[2*si+1]=(unsigned char)(pcm[si]>>8&0xFF); + } + if(!fwrite(out,sizeof(*out)*4*ret,1,stdout)){ + fprintf(stderr,"\nError writing decoded audio data: %s\n", + strerror(errno)); + ret=EXIT_FAILURE; + break; + } + nsamples+=ret; + prev_li=li; + } + if(ret==EXIT_SUCCESS){ + fprintf(stderr,"\nDone: played "); + print_duration(stderr,nsamples,3); + fprintf(stderr," (%li samples @ 48 kHz).\n",(long)nsamples); + } + if(op_seekable(of)&&nsamples!=duration){ + fprintf(stderr,"\nWARNING: " + "Number of output samples does not match declared file duration.\n"); + if(!output_seekable)fprintf(stderr,"Output WAV file will be corrupt.\n"); + } + if(output_seekable&&nsamples!=duration){ + make_wav_header(wav_header,nsamples); + if(fseek(stdout,0,SEEK_SET)|| + !fwrite(wav_header,sizeof(wav_header),1,stdout)){ + fprintf(stderr,"Error rewriting WAV header: %s\n",strerror(errno)); + ret=EXIT_FAILURE; + } + } + } + op_free(of); + return ret; +} diff --git a/lib/opusfile/examples/seeking_example.c b/lib/opusfile/examples/seeking_example.c new file mode 100644 index 00000000..a72d2e74 --- /dev/null +++ b/lib/opusfile/examples/seeking_example.c @@ -0,0 +1,465 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2020 * + * by the Xiph.Org Foundation and contributors https://xiph.org/ * + * * + ********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/*For fileno()*/ +#if !defined(_POSIX_SOURCE) +# define _POSIX_SOURCE 1 +#endif +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <math.h> +#include <string.h> +#include <opusfile.h> +#if defined(_WIN32) +# include "win32utf8.h" +# undef fileno +# define fileno _fileno +#endif + +/*Use shorts, they're smaller.*/ +#if !defined(OP_FIXED_POINT) +# define OP_FIXED_POINT (1) +#endif + +#if defined(OP_FIXED_POINT) + +typedef opus_int16 op_sample; + +# define op_read_native op_read + +/*TODO: The convergence after 80 ms of preroll is far from exact. + Our comparison is very rough. + Need to find some way to do this better.*/ +# define MATCH_TOL (16384) + +# define ABS(_x) ((_x)<0?-(_x):(_x)) + +# define MATCH(_a,_b) (ABS((_a)-(_b))<MATCH_TOL) + +/*Don't have fixed-point downmixing code.*/ +# undef OP_WRITE_SEEK_SAMPLES + +#else + +typedef float op_sample; + +# define op_read_native op_read_float + +/*TODO: The convergence after 80 ms of preroll is far from exact. + Our comparison is very rough. + Need to find some way to do this better.*/ +# define MATCH_TOL (16384.0/32768) + +# define FABS(_x) ((_x)<0?-(_x):(_x)) + +# define MATCH(_a,_b) (FABS((_a)-(_b))<MATCH_TOL) + +# if defined(OP_WRITE_SEEK_SAMPLES) +/*Matrices for downmixing from the supported channel counts to stereo.*/ +static const float DOWNMIX_MATRIX[8][8][2]={ + /*mono*/ + { + {1.F,1.F} + }, + /*stereo*/ + { + {1.F,0.F},{0.F,1.F} + }, + /*3.0*/ + { + {0.5858F,0.F},{0.4142F,0.4142F},{0,0.5858F} + }, + /*quadrophonic*/ + { + {0.4226F,0.F},{0,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F} + }, + /*5.0*/ + { + {0.651F,0.F},{0.46F,0.46F},{0,0.651F},{0.5636F,0.3254F},{0.3254F,0.5636F} + }, + /*5.1*/ + { + {0.529F,0.F},{0.3741F,0.3741F},{0.F,0.529F},{0.4582F,0.2645F}, + {0.2645F,0.4582F},{0.3741F,0.3741F} + }, + /*6.1*/ + { + {0.4553F,0.F},{0.322F,0.322F},{0.F,0.4553F},{0.3943F,0.2277F}, + {0.2277F,0.3943F},{0.2788F,0.2788F},{0.322F,0.322F} + }, + /*7.1*/ + { + {0.3886F,0.F},{0.2748F,0.2748F},{0.F,0.3886F},{0.3366F,0.1943F}, + {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F},{0.2748F,0.2748F} + } +}; + +static void write_samples(float *_samples,int _nsamples,int _nchannels){ + float stereo_pcm[120*48*2]; + int i; + for(i=0;i<_nsamples;i++){ + float l; + float r; + int ci; + l=r=0.F; + for(ci=0;ci<_nchannels;ci++){ + l+=DOWNMIX_MATRIX[_nchannels-1][ci][0]*_samples[i*_nchannels+ci]; + r+=DOWNMIX_MATRIX[_nchannels-1][ci][1]*_samples[i*_nchannels+ci]; + } + stereo_pcm[2*i+0]=l; + stereo_pcm[2*i+1]=r; + } + fwrite(stereo_pcm,sizeof(*stereo_pcm)*2,_nsamples,stdout); +} +# endif + +#endif + +static long nfailures; + +static void verify_seek(OggOpusFile *_of,opus_int64 _byte_offset, + ogg_int64_t _pcm_offset,ogg_int64_t _pcm_length,op_sample *_bigassbuffer){ + opus_int64 byte_offset; + ogg_int64_t pcm_offset; + ogg_int64_t duration; + op_sample buffer[120*48*8]; + int nchannels; + int nsamples; + int li; + int lj; + int i; + byte_offset=op_raw_tell(_of); + if(_byte_offset!=-1&&byte_offset<_byte_offset){ + fprintf(stderr,"\nRaw position out of tolerance: requested %li, " + "got %li.\n",(long)_byte_offset,(long)byte_offset); + nfailures++; + } + pcm_offset=op_pcm_tell(_of); + if(_pcm_offset!=-1&&pcm_offset>_pcm_offset){ + fprintf(stderr,"\nPCM position out of tolerance: requested %li, " + "got %li.\n",(long)_pcm_offset,(long)pcm_offset); + nfailures++; + } + if(pcm_offset<0||pcm_offset>_pcm_length){ + fprintf(stderr,"\nPCM position out of bounds: got %li.\n", + (long)pcm_offset); + nfailures++; + } + nsamples=op_read_native(_of,buffer,sizeof(buffer)/sizeof(*buffer),&li); + if(nsamples<0){ + fprintf(stderr,"\nFailed to read PCM data after seek: %i\n",nsamples); + nfailures++; + li=op_current_link(_of); + } + for(lj=0;lj<li;lj++){ + duration=op_pcm_total(_of,lj); + if(0<=pcm_offset&&pcm_offset<duration){ + fprintf(stderr,"\nPCM data after seek came from the wrong link: " + "expected %i, got %i.\n",lj,li); + nfailures++; + } + pcm_offset-=duration; + if(_bigassbuffer!=NULL)_bigassbuffer+=op_channel_count(_of,lj)*duration; + } + duration=op_pcm_total(_of,li); + if(pcm_offset+nsamples>duration){ + fprintf(stderr,"\nPCM data after seek exceeded link duration: " + "limit %li, got %li.\n",(long)duration,(long)(pcm_offset+nsamples)); + nfailures++; + } + nchannels=op_channel_count(_of,li); + if(_bigassbuffer!=NULL){ + for(i=0;i<nsamples*nchannels;i++){ + if(!MATCH(buffer[i],_bigassbuffer[pcm_offset*nchannels+i])){ + ogg_int64_t j; + fprintf(stderr,"\nData after seek doesn't match declared PCM " + "position: mismatch %G\n", + (double)buffer[i]-_bigassbuffer[pcm_offset*nchannels+i]); + for(j=0;j<duration-nsamples;j++){ + for(i=0;i<nsamples*nchannels;i++){ + if(!MATCH(buffer[i],_bigassbuffer[j*nchannels+i]))break; + } + if(i==nsamples*nchannels){ + fprintf(stderr,"\nData after seek appears to match position %li.\n", + (long)i); + } + } + nfailures++; + break; + } + } + } +#if defined(OP_WRITE_SEEK_SAMPLES) + write_samples(buffer,nsamples,nchannels); +#endif +} + +#define OP_MIN(_a,_b) ((_a)<(_b)?(_a):(_b)) + +/*A simple wrapper that lets us count the number of underlying seek calls.*/ + +static op_seek_func real_seek; + +static long nreal_seeks; + +static int seek_stat_counter(void *_stream,opus_int64 _offset,int _whence){ + if(_whence==SEEK_SET)nreal_seeks++; + /*SEEK_CUR with an offset of 0 is free, as is SEEK_END with an offset of 0 + (assuming we know the file size), so don't count them.*/ + else if(_offset!=0)nreal_seeks++; + return (*real_seek)(_stream,_offset,_whence); +} + +#define NSEEK_TESTS (1000) + +static void print_duration(FILE *_fp,ogg_int64_t _nsamples){ + ogg_int64_t seconds; + ogg_int64_t minutes; + ogg_int64_t hours; + ogg_int64_t days; + ogg_int64_t weeks; + seconds=_nsamples/48000; + _nsamples-=seconds*48000; + minutes=seconds/60; + seconds-=minutes*60; + hours=minutes/60; + minutes-=hours*60; + days=hours/24; + hours-=days*24; + weeks=days/7; + days-=weeks*7; + if(weeks)fprintf(_fp,"%liw",(long)weeks); + if(weeks||days)fprintf(_fp,"%id",(int)days); + if(weeks||days||hours){ + if(weeks||days)fprintf(_fp,"%02ih",(int)hours); + else fprintf(_fp,"%ih",(int)hours); + } + if(weeks||days||hours||minutes){ + if(weeks||days||hours)fprintf(_fp,"%02im",(int)minutes); + else fprintf(_fp,"%im",(int)minutes); + fprintf(_fp,"%02i",(int)seconds); + } + else fprintf(_fp,"%i",(int)seconds); + fprintf(_fp,".%03is",(int)(_nsamples+24)/48); +} + +int main(int _argc,const char **_argv){ + OpusFileCallbacks cb; + OggOpusFile *of; + void *fp; +#if defined(_WIN32) + win32_utf8_setup(&_argc,&_argv); +#endif + if(_argc!=2){ + fprintf(stderr,"Usage: %s <file.opus>\n",_argv[0]); + return EXIT_FAILURE; + } + memset(&cb,0,sizeof(cb)); + if(strcmp(_argv[1],"-")==0)fp=op_fdopen(&cb,fileno(stdin),"rb"); + else{ + /*Try to treat the argument as a URL.*/ + fp=op_url_stream_create(&cb,_argv[1], + OP_SSL_SKIP_CERTIFICATE_CHECK(1),NULL); + /*Fall back assuming it's a regular file name.*/ + if(fp==NULL)fp=op_fopen(&cb,_argv[1],"rb"); + } + if(cb.seek!=NULL){ + real_seek=cb.seek; + cb.seek=seek_stat_counter; + } + of=op_open_callbacks(fp,&cb,NULL,0,NULL); + if(of==NULL){ + fprintf(stderr,"Failed to open file '%s'.\n",_argv[1]); + return EXIT_FAILURE; + } + if(op_seekable(of)){ + op_sample *bigassbuffer; + ogg_int64_t size; + ogg_int64_t pcm_offset; + ogg_int64_t pcm_length; + ogg_int64_t nsamples; + long max_seeks; + int nlinks; + int ret; + int li; + int i; + /*Because we want to do sample-level verification that the seek does what + it claimed, decode the entire file into memory.*/ + nlinks=op_link_count(of); + fprintf(stderr,"Opened file containing %i links with %li seeks " + "(%0.3f per link).\n",nlinks,nreal_seeks,nreal_seeks/(double)nlinks); + /*Reset the seek counter.*/ + nreal_seeks=0; + nsamples=0; + for(li=0;li<nlinks;li++){ + nsamples+=op_pcm_total(of,li)*op_channel_count(of,li); + } +/*Until we find another way to do the comparisons that solves the MATCH_TOL + problem, disable this.*/ +#if 0 + bigassbuffer=_ogg_malloc(sizeof(*bigassbuffer)*nsamples); + if(bigassbuffer==NULL){ + fprintf(stderr, + "Buffer allocation failed. Seek offset detection disabled.\n"); + } +#else + bigassbuffer=NULL; +#endif + pcm_offset=op_pcm_tell(of); + if(pcm_offset!=0){ + fprintf(stderr,"Initial PCM offset was not 0, got %li instead.!\n", + (long)pcm_offset); + nfailures++; + } +/*Disabling the linear scan for now. + Only test on non-borken files!*/ +#if 0 + { + op_sample smallerbuffer[120*48*8]; + ogg_int64_t pcm_print_offset; + ogg_int64_t si; + opus_int32 bitrate; + int saw_hole; + pcm_print_offset=pcm_offset-48000; + bitrate=0; + saw_hole=0; + for(si=0;si<nsamples;){ + ogg_int64_t next_pcm_offset; + opus_int32 next_bitrate; + op_sample *buf; + int buf_size; + buf=bigassbuffer==NULL?smallerbuffer:bigassbuffer+si; + buf_size=(int)OP_MIN(nsamples-si, + (int)(sizeof(smallerbuffer)/sizeof(*smallerbuffer))), + ret=op_read_native(of,buf,buf_size,&li); + if(ret==OP_HOLE){ + /*Only warn once in a row.*/ + if(saw_hole)continue; + saw_hole=1; + /*This is just a warning. + As long as the timestamps are still contiguous we're okay.*/ + fprintf(stderr,"\nHole in PCM data at sample %li\n", + (long)pcm_offset); + continue; + } + else if(ret<=0){ + fprintf(stderr,"\nFailed to read PCM data: %i\n",ret); + exit(EXIT_FAILURE); + } + saw_hole=0; + /*If we have gaps in the PCM positions, seeking is not likely to work + near them.*/ + next_pcm_offset=op_pcm_tell(of); + if(pcm_offset+ret!=next_pcm_offset){ + fprintf(stderr,"\nGap in PCM offset: expecting %li, got %li\n", + (long)(pcm_offset+ret),(long)next_pcm_offset); + nfailures++; + } + pcm_offset=next_pcm_offset; + si+=ret*op_channel_count(of,li); + if(pcm_offset>=pcm_print_offset+48000){ + next_bitrate=op_bitrate_instant(of); + if(next_bitrate>=0)bitrate=next_bitrate; + fprintf(stderr,"\r%s... [%li left] (%0.3f kbps) ", + bigassbuffer==NULL?"Scanning":"Loading",nsamples-si,bitrate/1000.0); + pcm_print_offset=pcm_offset; + } + } + ret=op_read_native(of,smallerbuffer,8,&li); + if(ret<0){ + fprintf(stderr,"Failed to read PCM data: %i\n",ret); + nfailures++; + } + if(ret>0){ + fprintf(stderr,"Read too much PCM data!\n"); + nfailures++; + } + } +#endif + pcm_length=op_pcm_total(of,-1); + size=op_raw_total(of,-1); + fprintf(stderr,"\rLoaded (%0.3f kbps average). \n", + op_bitrate(of,-1)/1000.0); + fprintf(stderr,"Testing raw seeking to random places in %li bytes...\n", + (long)size); + max_seeks=0; + for(i=0;i<NSEEK_TESTS;i++){ + long nseeks_tmp; + opus_int64 byte_offset; + nseeks_tmp=nreal_seeks; + byte_offset=(opus_int64)(rand()/(double)RAND_MAX*size); + fprintf(stderr,"\r\t%3i [raw position %li]... ", + i,(long)byte_offset); + ret=op_raw_seek(of,byte_offset); + if(ret<0){ + fprintf(stderr,"\nSeek failed: %i.\n",ret); + nfailures++; + } + if(i==28){ + i=28; + } + verify_seek(of,byte_offset,-1,pcm_length,bigassbuffer); + nseeks_tmp=nreal_seeks-nseeks_tmp; + max_seeks=nseeks_tmp>max_seeks?nseeks_tmp:max_seeks; + } + fprintf(stderr,"\rTotal seek operations: %li (%.3f per raw seek, %li maximum).\n", + nreal_seeks,nreal_seeks/(double)NSEEK_TESTS,max_seeks); + nreal_seeks=0; + fprintf(stderr,"Testing exact PCM seeking to random places in %li " + "samples (",(long)pcm_length); + print_duration(stderr,pcm_length); + fprintf(stderr,")...\n"); + max_seeks=0; + for(i=0;i<NSEEK_TESTS;i++){ + ogg_int64_t pcm_offset2; + long nseeks_tmp; + nseeks_tmp=nreal_seeks; + pcm_offset=(ogg_int64_t)(rand()/(double)RAND_MAX*pcm_length); + fprintf(stderr,"\r\t%3i [PCM position %li]... ", + i,(long)pcm_offset); + ret=op_pcm_seek(of,pcm_offset); + if(ret<0){ + fprintf(stderr,"\nSeek failed: %i.\n",ret); + nfailures++; + } + pcm_offset2=op_pcm_tell(of); + if(pcm_offset!=pcm_offset2){ + fprintf(stderr,"\nDeclared PCM position did not perfectly match " + "request: requested %li, got %li.\n", + (long)pcm_offset,(long)pcm_offset2); + nfailures++; + } + verify_seek(of,-1,pcm_offset,pcm_length,bigassbuffer); + nseeks_tmp=nreal_seeks-nseeks_tmp; + max_seeks=nseeks_tmp>max_seeks?nseeks_tmp:max_seeks; + } + fprintf(stderr,"\rTotal seek operations: %li (%.3f per exact seek, %li maximum).\n", + nreal_seeks,nreal_seeks/(double)NSEEK_TESTS,max_seeks); + nreal_seeks=0; + fprintf(stderr,"OK.\n"); + _ogg_free(bigassbuffer); + } + else{ + fprintf(stderr,"Input was not seekable.\n"); + exit(EXIT_FAILURE); + } + op_free(of); + if(nfailures>0){ + fprintf(stderr,"FAILED: %li failure conditions encountered.\n",nfailures); + } + return nfailures!=0?EXIT_FAILURE:EXIT_SUCCESS; +} diff --git a/lib/opusfile/examples/win32utf8.c b/lib/opusfile/examples/win32utf8.c new file mode 100644 index 00000000..1246b21b --- /dev/null +++ b/lib/opusfile/examples/win32utf8.c @@ -0,0 +1,123 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2020 * + * by the Xiph.Org Foundation and contributors https://xiph.org/ * + * * + ********************************************************************/ + +#if defined(_WIN32) +# include <stdio.h> +# include <stdlib.h> +# include <wchar.h> +/*We need the following two to set stdin/stdout to binary.*/ +# include <io.h> +# include <fcntl.h> +# define WIN32_LEAN_AND_MEAN +# define WIN32_EXTRA_LEAN +# include <windows.h> +# include "win32utf8.h" + +static char *utf16_to_utf8(const wchar_t *_src){ + char *dst; + size_t len; + size_t si; + size_t di; + len=wcslen(_src); + dst=(char *)malloc(sizeof(*dst)*(3*len+1)); + if(dst==NULL)return dst; + for(di=si=0;si<len;si++){ + unsigned c0; + c0=_src[si]; + if(c0<0x80){ + /*Can be represented by a 1-byte sequence.*/ + dst[di++]=(char)c0; + continue; + } + else if(c0<0x800){ + /*Can be represented by a 2-byte sequence.*/ + dst[di++]=(char)(0xC0|c0>>6); + dst[di++]=(char)(0x80|c0&0x3F); + continue; + } + else if(c0>=0xD800&&c0<0xDC00){ + unsigned c1; + /*This is safe, because c0 was not 0 and _src is NUL-terminated.*/ + c1=_src[si+1]; + if(c1>=0xDC00&&c1<0xE000){ + unsigned w; + /*Surrogate pair.*/ + w=((c0&0x3FF)<<10|c1&0x3FF)+0x10000; + /*Can be represented by a 4-byte sequence.*/ + dst[di++]=(char)(0xF0|w>>18); + dst[di++]=(char)(0x80|w>>12&0x3F); + dst[di++]=(char)(0x80|w>>6&0x3F); + dst[di++]=(char)(0x80|w&0x3F); + si++; + continue; + } + } + /*Anything else is either a valid 3-byte sequence, an invalid surrogate + pair, or 'not a character'. + In the latter two cases, we just encode the value as a 3-byte + sequence anyway (producing technically invalid UTF-8). + Later error handling will detect the problem, with a better + chance of giving a useful error message.*/ + dst[di++]=(char)(0xE0|c0>>12); + dst[di++]=(char)(0x80|c0>>6&0x3F); + dst[di++]=(char)(0x80|c0&0x3F); + } + dst[di++]='\0'; + return dst; +} + +typedef LPWSTR *(APIENTRY *command_line_to_argv_w_func)(LPCWSTR cmd_line, + int *num_args); + +/*Make a best-effort attempt to support UTF-8 on Windows.*/ +void win32_utf8_setup(int *_argc,const char ***_argv){ + HMODULE hlib; + /*We need to set stdin/stdout to binary mode. + This is unrelated to UTF-8 support, but it's platform specific and we need + to do it in the same places.*/ + _setmode(_fileno(stdin),_O_BINARY); + _setmode(_fileno(stdout),_O_BINARY); + hlib=LoadLibraryA("shell32.dll"); + if(hlib!=NULL){ + command_line_to_argv_w_func command_line_to_argv_w; + /*This function is only available on Windows 2000 or later.*/ + command_line_to_argv_w=(command_line_to_argv_w_func)GetProcAddress(hlib, + "CommandLineToArgvW"); + if(command_line_to_argv_w!=NULL){ + wchar_t **argvw; + int argc; + argvw=(*command_line_to_argv_w)(GetCommandLineW(),&argc); + if(argvw!=NULL){ + int ai; + /*Really, I don't see why argc would ever differ from *_argc, but let's + be paranoid.*/ + if(argc>*_argc)argc=*_argc; + for(ai=0;ai<argc;ai++){ + char *argv; + argv=utf16_to_utf8(argvw[ai]); + if(argv!=NULL)(*_argv)[ai]=argv; + } + *_argc=argc; + LocalFree(argvw); + } + } + FreeLibrary(hlib); + } +# if defined(CP_UTF8) + /*This does not work correctly in all environments (it breaks output in + mingw32 for me), and requires a Unicode font (e.g., when using the default + Raster font, even characters that are available in the font's codepage + won't display properly).*/ + /*SetConsoleOutputCP(CP_UTF8);*/ +# endif +} +#endif diff --git a/lib/opusfile/examples/win32utf8.h b/lib/opusfile/examples/win32utf8.h new file mode 100644 index 00000000..02598237 --- /dev/null +++ b/lib/opusfile/examples/win32utf8.h @@ -0,0 +1,21 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2020 * + * by the Xiph.Org Foundation and contributors https://xiph.org/ * + * * + ********************************************************************/ + +#if !defined(_win32utf8_H) +# define _win32utf8_H (1) +# if defined(_WIN32) + +/*Make a best-effort attempt to support UTF-8 on Windows.*/ +void win32_utf8_setup(int *_argc,const char ***_argv); + +# endif +#endif |
