Make opusfile_example output WAV.
authorTimothy B. Terriberry <tterribe@xiph.org>
Thu, 10 Jan 2013 02:38:55 +0000 (18:38 -0800)
committerTimothy B. Terriberry <tterribe@xiph.org>
Thu, 10 Jan 2013 02:45:11 +0000 (18:45 -0800)
examples/opusfile_example.c

index 8669ce2..bb15f14 100644 (file)
 #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;
 }