cannam@154: /******************************************************************** cannam@154: * * cannam@154: * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * cannam@154: * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * cannam@154: * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * cannam@154: * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * cannam@154: * * cannam@154: * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012 * cannam@154: * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * cannam@154: * * cannam@154: ********************************************************************/ cannam@154: #ifdef HAVE_CONFIG_H cannam@154: #include "config.h" cannam@154: #endif cannam@154: cannam@154: /*For fileno()*/ cannam@154: #if !defined(_POSIX_SOURCE) cannam@154: # define _POSIX_SOURCE 1 cannam@154: #endif cannam@154: #include cannam@154: #include cannam@154: #include cannam@154: #include cannam@154: #include cannam@154: #if defined(_WIN32) cannam@154: # include "win32utf8.h" cannam@154: # undef fileno cannam@154: # define fileno _fileno cannam@154: #endif cannam@154: cannam@154: static void print_duration(FILE *_fp,ogg_int64_t _nsamples,int _frac){ cannam@154: ogg_int64_t seconds; cannam@154: ogg_int64_t minutes; cannam@154: ogg_int64_t hours; cannam@154: ogg_int64_t days; cannam@154: ogg_int64_t weeks; cannam@154: _nsamples+=_frac?24:24000; cannam@154: seconds=_nsamples/48000; cannam@154: _nsamples-=seconds*48000; cannam@154: minutes=seconds/60; cannam@154: seconds-=minutes*60; cannam@154: hours=minutes/60; cannam@154: minutes-=hours*60; cannam@154: days=hours/24; cannam@154: hours-=days*24; cannam@154: weeks=days/7; cannam@154: days-=weeks*7; cannam@154: if(weeks)fprintf(_fp,"%liw",(long)weeks); cannam@154: if(weeks||days)fprintf(_fp,"%id",(int)days); cannam@154: if(weeks||days||hours){ cannam@154: if(weeks||days)fprintf(_fp,"%02ih",(int)hours); cannam@154: else fprintf(_fp,"%ih",(int)hours); cannam@154: } cannam@154: if(weeks||days||hours||minutes){ cannam@154: if(weeks||days||hours)fprintf(_fp,"%02im",(int)minutes); cannam@154: else fprintf(_fp,"%im",(int)minutes); cannam@154: fprintf(_fp,"%02i",(int)seconds); cannam@154: } cannam@154: else fprintf(_fp,"%i",(int)seconds); cannam@154: if(_frac)fprintf(_fp,".%03i",(int)(_nsamples/48)); cannam@154: fprintf(_fp,"s"); cannam@154: } cannam@154: cannam@154: static void print_size(FILE *_fp,opus_int64 _nbytes,int _metric, cannam@154: const char *_spacer){ cannam@154: static const char SUFFIXES[7]={' ','k','M','G','T','P','E'}; cannam@154: opus_int64 val; cannam@154: opus_int64 den; cannam@154: opus_int64 round; cannam@154: int base; cannam@154: int shift; cannam@154: base=_metric?1000:1024; cannam@154: round=0; cannam@154: den=1; cannam@154: for(shift=0;shift<6;shift++){ cannam@154: if(_nbytes>1; cannam@154: } cannam@154: val=(_nbytes+round)/den; cannam@154: if(den>1&&val<10){ cannam@154: if(den>=1000000000)val=(_nbytes+(round/100))/(den/100); cannam@154: else val=(_nbytes*100+round)/den; cannam@154: fprintf(_fp,"%li.%02i%s%c",(long)(val/100),(int)(val%100), cannam@154: _spacer,SUFFIXES[shift]); cannam@154: } cannam@154: else if(den>1&&val<100){ cannam@154: if(den>=1000000000)val=(_nbytes+(round/10))/(den/10); cannam@154: else val=(_nbytes*10+round)/den; cannam@154: fprintf(_fp,"%li.%i%s%c",(long)(val/10),(int)(val%10), cannam@154: _spacer,SUFFIXES[shift]); cannam@154: } cannam@154: else fprintf(_fp,"%li%s%c",(long)val,_spacer,SUFFIXES[shift]); cannam@154: } cannam@154: cannam@154: static void put_le32(unsigned char *_dst,opus_uint32 _x){ cannam@154: _dst[0]=(unsigned char)(_x&0xFF); cannam@154: _dst[1]=(unsigned char)(_x>>8&0xFF); cannam@154: _dst[2]=(unsigned char)(_x>>16&0xFF); cannam@154: _dst[3]=(unsigned char)(_x>>24&0xFF); cannam@154: } cannam@154: cannam@154: /*Make a header for a 48 kHz, stereo, signed, 16-bit little-endian PCM WAV.*/ cannam@154: static void make_wav_header(unsigned char _dst[44],ogg_int64_t _duration){ cannam@154: /*The chunk sizes are set to 0x7FFFFFFF by default. cannam@154: Many, though not all, programs will interpret this to mean the duration is cannam@154: "undefined", and continue to read from the file so long as there is actual cannam@154: data.*/ cannam@154: static const unsigned char WAV_HEADER_TEMPLATE[44]={ cannam@154: 'R','I','F','F',0xFF,0xFF,0xFF,0x7F, cannam@154: 'W','A','V','E','f','m','t',' ', cannam@154: 0x10,0x00,0x00,0x00,0x01,0x00,0x02,0x00, cannam@154: 0x80,0xBB,0x00,0x00,0x00,0xEE,0x02,0x00, cannam@154: 0x04,0x00,0x10,0x00,'d','a','t','a', cannam@154: 0xFF,0xFF,0xFF,0x7F cannam@154: }; cannam@154: memcpy(_dst,WAV_HEADER_TEMPLATE,sizeof(WAV_HEADER_TEMPLATE)); cannam@154: if(_duration>0){ cannam@154: if(_duration>0x1FFFFFF6){ cannam@154: fprintf(stderr,"WARNING: WAV output would be larger than 2 GB.\n"); cannam@154: fprintf(stderr, cannam@154: "Writing non-standard WAV header with invalid chunk sizes.\n"); cannam@154: } cannam@154: else{ cannam@154: opus_uint32 audio_size; cannam@154: audio_size=(opus_uint32)(_duration*4); cannam@154: put_le32(_dst+4,audio_size+36); cannam@154: put_le32(_dst+40,audio_size); cannam@154: } cannam@154: } cannam@154: } cannam@154: cannam@154: int main(int _argc,const char **_argv){ cannam@154: OggOpusFile *of; cannam@154: ogg_int64_t duration; cannam@154: unsigned char wav_header[44]; cannam@154: int ret; cannam@154: int is_ssl; cannam@154: int output_seekable; cannam@154: #if defined(_WIN32) cannam@154: win32_utf8_setup(&_argc,&_argv); cannam@154: #endif cannam@154: if(_argc!=2){ cannam@154: fprintf(stderr,"Usage: %s \n",_argv[0]); cannam@154: return EXIT_FAILURE; cannam@154: } cannam@154: is_ssl=0; cannam@154: if(strcmp(_argv[1],"-")==0){ cannam@154: OpusFileCallbacks cb={NULL,NULL,NULL,NULL}; cannam@154: of=op_open_callbacks(op_fdopen(&cb,fileno(stdin),"rb"),&cb,NULL,0,&ret); cannam@154: } cannam@154: else{ cannam@154: OpusServerInfo info; cannam@154: /*Try to treat the argument as a URL.*/ cannam@154: of=op_open_url(_argv[1],&ret,OP_GET_SERVER_INFO(&info),NULL); cannam@154: #if 0 cannam@154: if(of==NULL){ cannam@154: OpusFileCallbacks cb={NULL,NULL,NULL,NULL}; cannam@154: void *fp; cannam@154: /*For debugging: force a file to not be seekable.*/ cannam@154: fp=op_fopen(&cb,_argv[1],"rb"); cannam@154: cb.seek=NULL; cannam@154: cb.tell=NULL; cannam@154: of=op_open_callbacks(fp,&cb,NULL,0,NULL); cannam@154: } cannam@154: #else cannam@154: if(of==NULL)of=op_open_file(_argv[1],&ret); cannam@154: #endif cannam@154: else{ cannam@154: if(info.name!=NULL){ cannam@154: fprintf(stderr,"Station name: %s\n",info.name); cannam@154: } cannam@154: if(info.description!=NULL){ cannam@154: fprintf(stderr,"Station description: %s\n",info.description); cannam@154: } cannam@154: if(info.genre!=NULL){ cannam@154: fprintf(stderr,"Station genre: %s\n",info.genre); cannam@154: } cannam@154: if(info.url!=NULL){ cannam@154: fprintf(stderr,"Station homepage: %s\n",info.url); cannam@154: } cannam@154: if(info.bitrate_kbps>=0){ cannam@154: fprintf(stderr,"Station bitrate: %u kbps\n", cannam@154: (unsigned)info.bitrate_kbps); cannam@154: } cannam@154: if(info.is_public>=0){ cannam@154: fprintf(stderr,"%s\n", cannam@154: info.is_public?"Station is public.":"Station is private."); cannam@154: } cannam@154: if(info.server!=NULL){ cannam@154: fprintf(stderr,"Server software: %s\n",info.server); cannam@154: } cannam@154: if(info.content_type!=NULL){ cannam@154: fprintf(stderr,"Content-Type: %s\n",info.content_type); cannam@154: } cannam@154: is_ssl=info.is_ssl; cannam@154: opus_server_info_clear(&info); cannam@154: } cannam@154: } cannam@154: if(of==NULL){ cannam@154: fprintf(stderr,"Failed to open file '%s': %i\n",_argv[1],ret); cannam@154: return EXIT_FAILURE; cannam@154: } cannam@154: duration=0; cannam@154: output_seekable=fseek(stdout,0,SEEK_CUR)!=-1; cannam@154: if(op_seekable(of)){ cannam@154: opus_int64 size; cannam@154: fprintf(stderr,"Total number of links: %i\n",op_link_count(of)); cannam@154: duration=op_pcm_total(of,-1); cannam@154: fprintf(stderr,"Total duration: "); cannam@154: print_duration(stderr,duration,3); cannam@154: fprintf(stderr," (%li samples @ 48 kHz)\n",(long)duration); cannam@154: size=op_raw_total(of,-1); cannam@154: fprintf(stderr,"Total size: "); cannam@154: print_size(stderr,size,0,""); cannam@154: fprintf(stderr,"\n"); cannam@154: } cannam@154: else if(!output_seekable){ cannam@154: fprintf(stderr,"WARNING: Neither input nor output are seekable.\n"); cannam@154: fprintf(stderr, cannam@154: "Writing non-standard WAV header with invalid chunk sizes.\n"); cannam@154: } cannam@154: make_wav_header(wav_header,duration); cannam@154: if(!fwrite(wav_header,sizeof(wav_header),1,stdout)){ cannam@154: fprintf(stderr,"Error writing WAV header: %s\n",strerror(errno)); cannam@154: ret=EXIT_FAILURE; cannam@154: } cannam@154: else{ cannam@154: ogg_int64_t pcm_offset; cannam@154: ogg_int64_t pcm_print_offset; cannam@154: ogg_int64_t nsamples; cannam@154: opus_int32 bitrate; cannam@154: int prev_li; cannam@154: prev_li=-1; cannam@154: nsamples=0; cannam@154: pcm_offset=op_pcm_tell(of); cannam@154: if(pcm_offset!=0){ cannam@154: fprintf(stderr,"Non-zero starting PCM offset: %li\n",(long)pcm_offset); cannam@154: } cannam@154: pcm_print_offset=pcm_offset-48000; cannam@154: bitrate=0; cannam@154: for(;;){ cannam@154: ogg_int64_t next_pcm_offset; cannam@154: opus_int16 pcm[120*48*2]; cannam@154: unsigned char out[120*48*2*2]; cannam@154: int li; cannam@154: int si; cannam@154: /*Although we would generally prefer to use the float interface, WAV cannam@154: files with signed, 16-bit little-endian samples are far more cannam@154: universally supported, so that's what we output.*/ cannam@154: ret=op_read_stereo(of,pcm,sizeof(pcm)/sizeof(*pcm)); cannam@154: if(ret==OP_HOLE){ cannam@154: fprintf(stderr,"\nHole detected! Corrupt file segment?\n"); cannam@154: continue; cannam@154: } cannam@154: else if(ret<0){ cannam@154: fprintf(stderr,"\nError decoding '%s': %i\n",_argv[1],ret); cannam@154: if(is_ssl)fprintf(stderr,"Possible truncation attack?\n"); cannam@154: ret=EXIT_FAILURE; cannam@154: break; cannam@154: } cannam@154: li=op_current_link(of); cannam@154: if(li!=prev_li){ cannam@154: const OpusHead *head; cannam@154: const OpusTags *tags; cannam@154: int binary_suffix_len; cannam@154: int ci; cannam@154: /*We found a new link. cannam@154: Print out some information.*/ cannam@154: fprintf(stderr,"Decoding link %i: \n",li); cannam@154: head=op_head(of,li); cannam@154: fprintf(stderr," Channels: %i\n",head->channel_count); cannam@154: if(op_seekable(of)){ cannam@154: ogg_int64_t duration; cannam@154: opus_int64 size; cannam@154: duration=op_pcm_total(of,li); cannam@154: fprintf(stderr," Duration: "); cannam@154: print_duration(stderr,duration,3); cannam@154: fprintf(stderr," (%li samples @ 48 kHz)\n",(long)duration); cannam@154: size=op_raw_total(of,li); cannam@154: fprintf(stderr," Size: "); cannam@154: print_size(stderr,size,0,""); cannam@154: fprintf(stderr,"\n"); cannam@154: } cannam@154: if(head->input_sample_rate){ cannam@154: fprintf(stderr," Original sampling rate: %lu Hz\n", cannam@154: (unsigned long)head->input_sample_rate); cannam@154: } cannam@154: tags=op_tags(of,li); cannam@154: fprintf(stderr," Encoded by: %s\n",tags->vendor); cannam@154: for(ci=0;cicomments;ci++){ cannam@154: const char *comment; cannam@154: comment=tags->user_comments[ci]; cannam@154: if(opus_tagncompare("METADATA_BLOCK_PICTURE",22,comment)==0){ cannam@154: OpusPictureTag pic; cannam@154: int err; cannam@154: err=opus_picture_tag_parse(&pic,comment); cannam@154: fprintf(stderr," %.23s",comment); cannam@154: if(err>=0){ cannam@154: fprintf(stderr,"%u|%s|%s|%ux%ux%u",pic.type,pic.mime_type, cannam@154: pic.description,pic.width,pic.height,pic.depth); cannam@154: if(pic.colors!=0)fprintf(stderr,"/%u",pic.colors); cannam@154: if(pic.format==OP_PIC_FORMAT_URL){ cannam@154: fprintf(stderr,"|%s\n",pic.data); cannam@154: } cannam@154: else{ cannam@154: fprintf(stderr,"|<%u bytes of image data>\n",pic.data_length); cannam@154: } cannam@154: opus_picture_tag_clear(&pic); cannam@154: } cannam@154: else fprintf(stderr,"\n"); cannam@154: } cannam@154: else fprintf(stderr," %s\n",tags->user_comments[ci]); cannam@154: } cannam@154: if(opus_tags_get_binary_suffix(tags,&binary_suffix_len)!=NULL){ cannam@154: fprintf(stderr,"<%u bytes of unknown binary metadata>\n", cannam@154: binary_suffix_len); cannam@154: } cannam@154: fprintf(stderr,"\n"); cannam@154: if(!op_seekable(of)){ cannam@154: pcm_offset=op_pcm_tell(of)-ret; cannam@154: if(pcm_offset!=0){ cannam@154: fprintf(stderr,"Non-zero starting PCM offset in link %i: %li\n", cannam@154: li,(long)pcm_offset); cannam@154: } cannam@154: } cannam@154: } cannam@154: if(li!=prev_li||pcm_offset>=pcm_print_offset+48000){ cannam@154: opus_int32 next_bitrate; cannam@154: opus_int64 raw_offset; cannam@154: next_bitrate=op_bitrate_instant(of); cannam@154: if(next_bitrate>=0)bitrate=next_bitrate; cannam@154: raw_offset=op_raw_tell(of); cannam@154: fprintf(stderr,"\r "); cannam@154: print_size(stderr,raw_offset,0,""); cannam@154: fprintf(stderr," "); cannam@154: print_duration(stderr,pcm_offset,0); cannam@154: fprintf(stderr," ("); cannam@154: print_size(stderr,bitrate,1," "); cannam@154: fprintf(stderr,"bps) \r"); cannam@154: pcm_print_offset=pcm_offset; cannam@154: fflush(stderr); cannam@154: } cannam@154: next_pcm_offset=op_pcm_tell(of); cannam@154: if(pcm_offset+ret!=next_pcm_offset){ cannam@154: fprintf(stderr,"\nPCM offset gap! %li+%i!=%li\n", cannam@154: (long)pcm_offset,ret,(long)next_pcm_offset); cannam@154: } cannam@154: pcm_offset=next_pcm_offset; cannam@154: if(ret<=0){ cannam@154: ret=EXIT_SUCCESS; cannam@154: break; cannam@154: } cannam@154: /*Ensure the data is little-endian before writing it out.*/ cannam@154: for(si=0;si<2*ret;si++){ cannam@154: out[2*si+0]=(unsigned char)(pcm[si]&0xFF); cannam@154: out[2*si+1]=(unsigned char)(pcm[si]>>8&0xFF); cannam@154: } cannam@154: if(!fwrite(out,sizeof(*out)*4*ret,1,stdout)){ cannam@154: fprintf(stderr,"\nError writing decoded audio data: %s\n", cannam@154: strerror(errno)); cannam@154: ret=EXIT_FAILURE; cannam@154: break; cannam@154: } cannam@154: nsamples+=ret; cannam@154: prev_li=li; cannam@154: } cannam@154: if(ret==EXIT_SUCCESS){ cannam@154: fprintf(stderr,"\nDone: played "); cannam@154: print_duration(stderr,nsamples,3); cannam@154: fprintf(stderr," (%li samples @ 48 kHz).\n",(long)nsamples); cannam@154: } cannam@154: if(op_seekable(of)&&nsamples!=duration){ cannam@154: fprintf(stderr,"\nWARNING: " cannam@154: "Number of output samples does not match declared file duration.\n"); cannam@154: if(!output_seekable)fprintf(stderr,"Output WAV file will be corrupt.\n"); cannam@154: } cannam@154: if(output_seekable&&nsamples!=duration){ cannam@154: make_wav_header(wav_header,nsamples); cannam@154: if(fseek(stdout,0,SEEK_SET)|| cannam@154: !fwrite(wav_header,sizeof(wav_header),1,stdout)){ cannam@154: fprintf(stderr,"Error rewriting WAV header: %s\n",strerror(errno)); cannam@154: ret=EXIT_FAILURE; cannam@154: } cannam@154: } cannam@154: } cannam@154: op_free(of); cannam@154: return ret; cannam@154: }