190bbc985cd99cf6076c1b69e30bbcc815aae219
[opusfile.git] / examples / opusfile_example.c
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 <string.h>
24 #include <opusfile.h>
25 #if defined(_WIN32)
26 # include "win32utf8.h"
27 # undef fileno
28 # define fileno _fileno
29 #endif
30
31 static void print_duration(FILE *_fp,ogg_int64_t _nsamples,int _frac){
32   ogg_int64_t seconds;
33   ogg_int64_t minutes;
34   ogg_int64_t hours;
35   ogg_int64_t days;
36   ogg_int64_t weeks;
37   _nsamples+=_frac?24:24000;
38   seconds=_nsamples/48000;
39   _nsamples-=seconds*48000;
40   minutes=seconds/60;
41   seconds-=minutes*60;
42   hours=minutes/60;
43   minutes-=hours*60;
44   days=hours/24;
45   hours-=days*24;
46   weeks=days/7;
47   days-=weeks*7;
48   if(weeks)fprintf(_fp,"%liw",(long)weeks);
49   if(weeks||days)fprintf(_fp,"%id",(int)days);
50   if(weeks||days||hours){
51     if(weeks||days)fprintf(_fp,"%02ih",(int)hours);
52     else fprintf(_fp,"%ih",(int)hours);
53   }
54   if(weeks||days||hours||minutes){
55     if(weeks||days||hours)fprintf(_fp,"%02im",(int)minutes);
56     else fprintf(_fp,"%im",(int)minutes);
57     fprintf(_fp,"%02i",(int)seconds);
58   }
59   else fprintf(_fp,"%i",(int)seconds);
60   if(_frac)fprintf(_fp,".%03i",(int)(_nsamples/48));
61   fprintf(_fp,"s");
62 }
63
64 static void print_size(FILE *_fp,opus_int64 _nbytes,int _metric,
65  const char *_spacer){
66   static const char SUFFIXES[7]={' ','k','M','G','T','P','E'};
67   opus_int64 val;
68   opus_int64 den;
69   opus_int64 round;
70   int        base;
71   int        shift;
72   base=_metric?1000:1024;
73   round=0;
74   den=1;
75   for(shift=0;shift<6;shift++){
76     if(_nbytes<den*base-round)break;
77     den*=base;
78     round=den>>1;
79   }
80   val=(_nbytes+round)/den;
81   if(den>1&&val<10){
82     if(den>=1000000000)val=(_nbytes+(round/100))/(den/100);
83     else val=(_nbytes*100+round)/den;
84     fprintf(_fp,"%li.%02i%s%c",(long)(val/100),(int)(val%100),
85      _spacer,SUFFIXES[shift]);
86   }
87   else if(den>1&&val<100){
88     if(den>=1000000000)val=(_nbytes+(round/10))/(den/10);
89     else val=(_nbytes*10+round)/den;
90     fprintf(_fp,"%li.%i%s%c",(long)(val/10),(int)(val%10),
91      _spacer,SUFFIXES[shift]);
92   }
93   else fprintf(_fp,"%li%s%c",(long)val,_spacer,SUFFIXES[shift]);
94 }
95
96 static void put_le32(unsigned char *_dst,opus_uint32 _x){
97   _dst[0]=(unsigned char)(_x&0xFF);
98   _dst[1]=(unsigned char)(_x>>8&0xFF);
99   _dst[2]=(unsigned char)(_x>>16&0xFF);
100   _dst[3]=(unsigned char)(_x>>24&0xFF);
101 }
102
103 /*Make a header for a 48 kHz, stereo, signed, 16-bit little-endian PCM WAV.*/
104 static void make_wav_header(unsigned char _dst[44],ogg_int64_t _duration){
105   /*The chunk sizes are set to 0x7FFFFFFF by default.
106     Many, though not all, programs will interpret this to mean the duration is
107      "undefined", and continue to read from the file so long as there is actual
108      data.*/
109   static const unsigned char WAV_HEADER_TEMPLATE[44]={
110     'R','I','F','F',0xFF,0xFF,0xFF,0x7F,
111     'W','A','V','E','f','m','t',' ',
112     0x10,0x00,0x00,0x00,0x01,0x00,0x02,0x00,
113     0x80,0xBB,0x00,0x00,0x00,0xEE,0x02,0x00,
114     0x04,0x00,0x10,0x00,'d','a','t','a',
115     0xFF,0xFF,0xFF,0x7F
116   };
117   memcpy(_dst,WAV_HEADER_TEMPLATE,sizeof(WAV_HEADER_TEMPLATE));
118   if(_duration>0){
119     if(_duration>0x1FFFFFF6){
120       fprintf(stderr,"WARNING: WAV output would be larger than 2 GB.\n");
121       fprintf(stderr,
122        "Writing non-standard WAV header with invalid chunk sizes.\n");
123     }
124     else{
125       opus_uint32 audio_size;
126       audio_size=(opus_uint32)(_duration*4);
127       put_le32(_dst+4,audio_size+36);
128       put_le32(_dst+40,audio_size);
129     }
130   }
131 }
132
133 int main(int _argc,const char **_argv){
134   OggOpusFile  *of;
135   ogg_int64_t   duration;
136   unsigned char wav_header[44];
137   int           ret;
138   int           is_ssl;
139   int           output_seekable;
140 #if defined(_WIN32)
141   win32_utf8_setup(&_argc,&_argv);
142 #endif
143   if(_argc!=2){
144     fprintf(stderr,"Usage: %s <file.opus>\n",_argv[0]);
145     return EXIT_FAILURE;
146   }
147   is_ssl=0;
148   if(strcmp(_argv[1],"-")==0){
149     OpusFileCallbacks cb={NULL,NULL,NULL,NULL};
150     of=op_open_callbacks(op_fdopen(&cb,fileno(stdin),"rb"),&cb,NULL,0,&ret);
151   }
152   else{
153     OpusServerInfo info;
154     /*Try to treat the argument as a URL.*/
155     of=op_open_url(_argv[1],&ret,OP_GET_SERVER_INFO(&info),NULL);
156 #if 0
157     if(of==NULL){
158       OpusFileCallbacks  cb={NULL,NULL,NULL,NULL};
159       void              *fp;
160       /*For debugging: force a file to not be seekable.*/
161       fp=op_fopen(&cb,_argv[1],"rb");
162       cb.seek=NULL;
163       cb.tell=NULL;
164       of=op_open_callbacks(fp,&cb,NULL,0,NULL);
165     }
166 #else
167     if(of==NULL)of=op_open_file(_argv[1],&ret);
168 #endif
169     else{
170       if(info.name!=NULL){
171         fprintf(stderr,"Station name: %s\n",info.name);
172       }
173       if(info.description!=NULL){
174         fprintf(stderr,"Station description: %s\n",info.description);
175       }
176       if(info.genre!=NULL){
177         fprintf(stderr,"Station genre: %s\n",info.genre);
178       }
179       if(info.url!=NULL){
180         fprintf(stderr,"Station homepage: %s\n",info.url);
181       }
182       if(info.bitrate_kbps>=0){
183         fprintf(stderr,"Station bitrate: %u kbps\n",
184          (unsigned)info.bitrate_kbps);
185       }
186       if(info.is_public>=0){
187         fprintf(stderr,"%s\n",
188          info.is_public?"Station is public.":"Station is private.");
189       }
190       if(info.server!=NULL){
191         fprintf(stderr,"Server software: %s\n",info.server);
192       }
193       if(info.content_type!=NULL){
194         fprintf(stderr,"Content-Type: %s\n",info.content_type);
195       }
196       is_ssl=info.is_ssl;
197       opus_server_info_clear(&info);
198     }
199   }
200   if(of==NULL){
201     fprintf(stderr,"Failed to open file '%s': %i\n",_argv[1],ret);
202     return EXIT_FAILURE;
203   }
204   duration=0;
205   output_seekable=fseek(stdout,0,SEEK_CUR)!=-1;
206   if(op_seekable(of)){
207     opus_int64  size;
208     fprintf(stderr,"Total number of links: %i\n",op_link_count(of));
209     duration=op_pcm_total(of,-1);
210     fprintf(stderr,"Total duration: ");
211     print_duration(stderr,duration,3);
212     fprintf(stderr," (%li samples @ 48 kHz)\n",(long)duration);
213     size=op_raw_total(of,-1);
214     fprintf(stderr,"Total size: ");
215     print_size(stderr,size,0,"");
216     fprintf(stderr,"\n");
217   }
218   else if(!output_seekable){
219     fprintf(stderr,"WARNING: Neither input nor output are seekable.\n");
220     fprintf(stderr,
221      "Writing non-standard WAV header with invalid chunk sizes.\n");
222   }
223   make_wav_header(wav_header,duration);
224   if(!fwrite(wav_header,sizeof(wav_header),1,stdout)){
225     fprintf(stderr,"Error writing WAV header: %s\n",strerror(errno));
226     ret=EXIT_FAILURE;
227   }
228   else{
229     ogg_int64_t pcm_offset;
230     ogg_int64_t pcm_print_offset;
231     ogg_int64_t nsamples;
232     opus_int32  bitrate;
233     int         prev_li;
234     prev_li=-1;
235     nsamples=0;
236     pcm_offset=op_pcm_tell(of);
237     if(pcm_offset!=0){
238       fprintf(stderr,"Non-zero starting PCM offset: %li\n",(long)pcm_offset);
239     }
240     pcm_print_offset=pcm_offset-48000;
241     bitrate=0;
242     for(;;){
243       ogg_int64_t   next_pcm_offset;
244       opus_int16    pcm[120*48*2];
245       unsigned char out[120*48*2*2];
246       int           li;
247       int           si;
248       /*Although we would generally prefer to use the float interface, WAV
249          files with signed, 16-bit little-endian samples are far more
250          universally supported, so that's what we output.*/
251       ret=op_read_stereo(of,pcm,sizeof(pcm)/sizeof(*pcm));
252       if(ret==OP_HOLE){
253         fprintf(stderr,"\nHole detected! Corrupt file segment?\n");
254         continue;
255       }
256       else if(ret<0){
257         fprintf(stderr,"\nError decoding '%s': %i\n",_argv[1],ret);
258         if(is_ssl)fprintf(stderr,"Possible truncation attack?\n");
259         ret=EXIT_FAILURE;
260         break;
261       }
262       li=op_current_link(of);
263       if(li!=prev_li){
264         const OpusHead *head;
265         const OpusTags *tags;
266         int             binary_suffix_len;
267         int             ci;
268         /*We found a new link.
269           Print out some information.*/
270         fprintf(stderr,"Decoding link %i:                          \n",li);
271         head=op_head(of,li);
272         fprintf(stderr,"  Channels: %i\n",head->channel_count);
273         if(op_seekable(of)){
274           ogg_int64_t duration;
275           opus_int64  size;
276           duration=op_pcm_total(of,li);
277           fprintf(stderr,"  Duration: ");
278           print_duration(stderr,duration,3);
279           fprintf(stderr," (%li samples @ 48 kHz)\n",(long)duration);
280           size=op_raw_total(of,li);
281           fprintf(stderr,"  Size: ");
282           print_size(stderr,size,0,"");
283           fprintf(stderr,"\n");
284         }
285         if(head->input_sample_rate){
286           fprintf(stderr,"  Original sampling rate: %lu Hz\n",
287            (unsigned long)head->input_sample_rate);
288         }
289         tags=op_tags(of,li);
290         fprintf(stderr,"  Encoded by: %s\n",tags->vendor);
291         for(ci=0;ci<tags->comments;ci++){
292           const char *comment;
293           comment=tags->user_comments[ci];
294           if(opus_tagncompare("METADATA_BLOCK_PICTURE",22,comment)==0){
295             OpusPictureTag pic;
296             int            err;
297             err=opus_picture_tag_parse(&pic,comment);
298             fprintf(stderr,"  %.23s",comment);
299             if(err>=0){
300               fprintf(stderr,"%u|%s|%s|%ux%ux%u",pic.type,pic.mime_type,
301                pic.description,pic.width,pic.height,pic.depth);
302               if(pic.colors!=0)fprintf(stderr,"/%u",pic.colors);
303               if(pic.format==OP_PIC_FORMAT_URL){
304                 fprintf(stderr,"|%s\n",pic.data);
305               }
306               else{
307                 fprintf(stderr,"|<%u bytes of image data>\n",pic.data_length);
308               }
309               opus_picture_tag_clear(&pic);
310             }
311             else fprintf(stderr,"<error parsing picture tag>\n");
312           }
313           else fprintf(stderr,"  %s\n",tags->user_comments[ci]);
314         }
315         if(opus_tags_get_binary_suffix(tags,&binary_suffix_len)!=NULL){
316           fprintf(stderr,"<%u bytes of unknown binary metadata>\n",
317            binary_suffix_len);
318         }
319         fprintf(stderr,"\n");
320         if(!op_seekable(of)){
321           pcm_offset=op_pcm_tell(of)-ret;
322           if(pcm_offset!=0){
323             fprintf(stderr,"Non-zero starting PCM offset in link %i: %li\n",
324              li,(long)pcm_offset);
325           }
326         }
327       }
328       if(li!=prev_li||pcm_offset>=pcm_print_offset+48000){
329         opus_int32 next_bitrate;
330         opus_int64 raw_offset;
331         next_bitrate=op_bitrate_instant(of);
332         if(next_bitrate>=0)bitrate=next_bitrate;
333         raw_offset=op_raw_tell(of);
334         fprintf(stderr,"\r ");
335         print_size(stderr,raw_offset,0,"");
336         fprintf(stderr,"  ");
337         print_duration(stderr,pcm_offset,0);
338         fprintf(stderr,"  (");
339         print_size(stderr,bitrate,1," ");
340         fprintf(stderr,"bps)                    \r");
341         pcm_print_offset=pcm_offset;
342         fflush(stderr);
343       }
344       next_pcm_offset=op_pcm_tell(of);
345       if(pcm_offset+ret!=next_pcm_offset){
346         fprintf(stderr,"\nPCM offset gap! %li+%i!=%li\n",
347          (long)pcm_offset,ret,(long)next_pcm_offset);
348       }
349       pcm_offset=next_pcm_offset;
350       if(ret<=0){
351         ret=EXIT_SUCCESS;
352         break;
353       }
354       /*Ensure the data is little-endian before writing it out.*/
355       for(si=0;si<2*ret;si++){
356         out[2*si+0]=(unsigned char)(pcm[si]&0xFF);
357         out[2*si+1]=(unsigned char)(pcm[si]>>8&0xFF);
358       }
359       if(!fwrite(out,sizeof(*out)*4*ret,1,stdout)){
360         fprintf(stderr,"\nError writing decoded audio data: %s\n",
361          strerror(errno));
362         ret=EXIT_FAILURE;
363         break;
364       }
365       nsamples+=ret;
366       prev_li=li;
367     }
368     if(ret==EXIT_SUCCESS){
369       fprintf(stderr,"\nDone: played ");
370       print_duration(stderr,nsamples,3);
371       fprintf(stderr," (%li samples @ 48 kHz).\n",(long)nsamples);
372     }
373     if(op_seekable(of)&&nsamples!=duration){
374       fprintf(stderr,"\nWARNING: "
375        "Number of output samples does not match declared file duration.\n");
376       if(!output_seekable)fprintf(stderr,"Output WAV file will be corrupt.\n");
377     }
378     if(output_seekable&&nsamples!=duration){
379       make_wav_header(wav_header,nsamples);
380       if(fseek(stdout,0,SEEK_SET)||
381        !fwrite(wav_header,sizeof(wav_header),1,stdout)){
382         fprintf(stderr,"Error rewriting WAV header: %s\n",strerror(errno));
383         ret=EXIT_FAILURE;
384       }
385     }
386   }
387   op_free(of);
388   return ret;
389 }