From 1cea8f6bf1e3ffec1a408b3ead5e7ca2febf7419 Mon Sep 17 00:00:00 2001 From: "Timothy B. Terriberry" <tterribe@xiph.org> Date: Wed, 9 Jan 2013 18:38:55 -0800 Subject: [PATCH] Make opusfile_example output WAV. --- examples/opusfile_example.c | 274 ++++++++++++++++++++++-------------- 1 file changed, 171 insertions(+), 103 deletions(-) diff --git a/examples/opusfile_example.c b/examples/opusfile_example.c index 8669ce2..bb15f14 100644 --- a/examples/opusfile_example.c +++ b/examples/opusfile_example.c @@ -24,14 +24,6 @@ #endif #include <opusfile.h> -#if defined(OP_FIXED_POINT) -typedef opus_int16 op_sample; -# define op_read_native_stereo op_read_stereo -#else -typedef float op_sample; -# define op_read_native_stereo op_read_float_stereo -#endif - static void print_duration(FILE *_fp,ogg_int64_t _nsamples,int _frac){ ogg_int64_t seconds; ogg_int64_t minutes; @@ -97,15 +89,50 @@ static void print_size(FILE *_fp,opus_int64 _nbytes,int _metric, 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 pcm_offset; - ogg_int64_t pcm_print_offset; - ogg_int64_t nsamples; - opus_int32 bitrate; - int ret; - int prev_li; - int is_ssl; + OggOpusFile *of; + ogg_int64_t duration; + unsigned char wav_header[44]; + int ret; + int is_ssl; + int output_seekable; #if defined(_WIN32) # undef fileno # define fileno _fileno @@ -150,8 +177,9 @@ int main(int _argc,const char **_argv){ 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)){ - ogg_int64_t duration; opus_int64 size; fprintf(stderr,"Total number of links: %i\n",op_link_count(of)); duration=op_pcm_total(of,-1); @@ -163,104 +191,144 @@ int main(int _argc,const char **_argv){ print_size(stderr,size,0,""); fprintf(stderr,"\n"); } - 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); + 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; } - pcm_print_offset=pcm_offset-48000; - bitrate=0; - for(;;){ - ogg_int64_t next_pcm_offset; - op_sample pcm[120*48*2]; - int li; - ret=op_read_native_stereo(of,pcm,sizeof(pcm)/sizeof(*pcm)); - 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; + 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); } - li=op_current_link(of); - if(li!=prev_li){ - const OpusHead *head; - const OpusTags *tags; - 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,""); + 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<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 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++){ + fprintf(stderr," %s\n",tags->user_comments[ci]); + } 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(head->input_sample_rate){ - fprintf(stderr," Original sampling rate: %lu Hz\n", - (unsigned long)head->input_sample_rate); + 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; } - tags=op_tags(of,li); - fprintf(stderr," Encoded by: %s\n",tags->vendor); - for(ci=0;ci<tags->comments;ci++){ - fprintf(stderr," %s\n",tags->user_comments[ci]); + 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); } - 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); - } + 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,stdout)){ + fprintf(stderr,"\nError writing decoded audio data: %s\n", + strerror(errno)); + ret=EXIT_FAILURE; + break; + } + nsamples+=ret; + prev_li=li; } - 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; - } - 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); + if(ret==EXIT_SUCCESS){ + fprintf(stderr,"\nDone: played "); + print_duration(stderr,nsamples,3); + fprintf(stderr," (%li samples @ 48 kHz).\n",(long)nsamples); } - pcm_offset=next_pcm_offset; - if(ret<=0){ - ret=EXIT_SUCCESS; - break; + 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(!fwrite(pcm,sizeof(*pcm)*2,ret,stdout)){ - fprintf(stderr,"\nError writing decoded audio data: %s\n", - strerror(errno)); - ret=EXIT_FAILURE; - break; + 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; + } } - nsamples+=ret; - prev_li=li; } op_free(of); - if(ret==EXIT_SUCCESS){ - fprintf(stderr,"\nDone: played "); - print_duration(stderr,nsamples,3); - fprintf(stderr," (%li samples @ 48 kHz).\n",(long)nsamples); - } return ret; } -- GitLab