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