shithub: opus-tools

Download patch

ref: 892fd9297717d2f60fd56856e96d45b202945f0a
parent: b2b46a031e21189bef1ef0d34cf49c93fd4c5ccd
author: Timothy B. Terriberry <[email protected]>
date: Sat Dec 8 12:10:42 EST 2012

Add FLAC input support.

This copies the tags from FLAC files by default.
It also properly strips ReplayGain tags and converts them into a
 header gain and an R128_TRACK_GAIN tag, according to the current
 OggOpus specification (draft-ietf-codec-oggopus-00).

--- a/Makefile.am
+++ b/Makefile.am
@@ -13,6 +13,7 @@
 
 noinst_HEADERS = src/arch.h \
                  src/diag_range.h \
+                 src/flac.h \
                  src/info_opus.h \
                  src/lpc.h \
                  src/opusenc.h \
@@ -28,7 +29,7 @@
                  src/wav_io.h
 
 EXTRA_DIST = Makefile.unix \
-	     man/opusrtp.1 \
+             man/opusrtp.1 \
              opus-tools.sln \
              src/opusdec.vcxproj.filters \
              src/opusinfo.vcxproj.filters \
@@ -47,7 +48,7 @@
 
 dist_man_MANS = man/opusenc.1 man/opusdec.1 man/opusinfo.1
 
-opusenc_SOURCES = src/opus_header.c src/opusenc.c src/resample.c src/audio-in.c src/diag_range.c src/lpc.c win32/unicode_support.c
+opusenc_SOURCES = src/opus_header.c src/opusenc.c src/resample.c src/audio-in.c src/diag_range.c src/flac.c src/lpc.c win32/unicode_support.c
 opusenc_LDADD = $(OGG_LIBS) $(Opus_LIBS) -lm
 opusenc_MANS = man/opusenc.1
 
--- a/configure.ac
+++ b/configure.ac
@@ -104,6 +104,12 @@
   AC_DEFINE([ENABLE_ASSERTIONS], , [Assertions])
 fi])
 
+ac_enable_flac="yes"
+AC_ARG_ENABLE(flac, [  --disable-flac          disable FLAC support],
+[if test "$enableval" != "yes"; then
+  ac_enable_flac="no"
+fi])
+
 if test "x$CFLAGS" = "x-g -O2"; then
 saved_CFLAGS="$CFLAGS"
 CFLAGS="-O3 -ffast-math"
@@ -182,6 +188,48 @@
   AC_MSG_WARN([Audio support not found -- no direct audio output in opusdec])
 fi
 
+dnl check for flac
+if test "x$ac_enable_flac" = "xyes" ; then
+  HAVE_FLAC=no
+  if test "x$HAVE_PKG_CONFIG" = "xyes"
+  then
+    PKG_CHECK_MODULES(FLAC, flac >= 1.1.3, HAVE_FLAC=yes, HAVE_FLAC=no)
+  fi
+  if test "x$HAVE_FLAC" = "xno"
+  then
+    dnl fall back to AC_CHECK_LIB
+    AC_CHECK_LIB(m,log,FLAC_LIBS="-lm")
+    AC_CHECK_LIB(FLAC, [FLAC__stream_decoder_process_single],
+      [
+        HAVE_FLAC="yes"
+        FLAC_LIBS="-lFLAC $FLAC_LIBS"
+      ],
+      [
+        AC_MSG_ERROR([
+          FLAC is required to build this package!
+          Please install it or configure with --disable-flac.
+        ])
+        HAVE_FLAC="no"
+      ],
+      [$FLAC_LIBS]
+    )
+    AC_CHECK_HEADER(FLAC/stream_decoder.h,,
+      [
+        AC_MSG_ERROR([
+          FLAC headers are required to build this package!
+          Please install the development version of FLAC
+          or configure with --disable-flac.
+        ])
+        HAVE_FLAC="no"
+      ],
+      []
+    )
+  fi
+  CFLAGS="$FLAC_CFLAGS $CFLAGS"
+  LIBS="$FLAC_LIBS $LIBS"
+  AC_DEFINE([HAVE_LIBFLAC], , [FLAC])
+fi
+
 dnl check for pcap
 AC_CHECK_LIB([pcap], [pcap_open_live], [
   AC_DEFINE([HAVE_PCAP], 1, [Define if building with libpcap support])
@@ -342,6 +390,7 @@
     General configuration:
 
       Assertion checking: ............ ${ac_enable_assertions}
+      FLAC input: .................... ${ac_enable_flac}
 
 ------------------------------------------------------------------------
 ])
--- a/src/audio-in.c
+++ b/src/audio-in.c
@@ -74,10 +74,7 @@
 #include "speex_resampler.h"
 #include "lpc.h"
 #include "opus_header.h"
-
-#ifdef HAVE_LIBFLAC
 #include "flac.h"
-#endif
 
 /* Macros to read header data */
 #define READ_U32_LE(buf) \
@@ -96,10 +93,8 @@
 input_format formats[] = {
     {wav_id, 12, wav_open, wav_close, "wav", N_("WAV file reader")},
     {aiff_id, 12, aiff_open, wav_close, "aiff", N_("AIFF/AIFC file reader")},
-#ifdef HAVE_LIBFLAC
     {flac_id,     4, flac_open, flac_close, "flac", N_("FLAC file reader")},
-    {oggflac_id, 32, flac_open, flac_close, "ogg", N_("Ogg FLAC file reader")},
-#endif
+    {oggflac_id, 33, flac_open, flac_close, "ogg", N_("Ogg FLAC file reader")},
     {NULL, 0, NULL, NULL, NULL, NULL}
 };
 
--- /dev/null
+++ b/src/flac.c
@@ -1,0 +1,374 @@
+#if defined(HAVE_CONFIG_H)
+# include <config.h>
+#endif
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include "flac.h"
+#include "opus_header.h"
+
+#if defined(HAVE_LIBFLAC)
+
+/*Callback to read more data for the FLAC decoder.*/
+static FLAC__StreamDecoderReadStatus read_callback(
+   const FLAC__StreamDecoder *decoder,FLAC__byte buffer[],size_t *bytes,
+   void *client_data){
+  flacfile *flac;
+  (void)decoder;
+  flac=(flacfile *)client_data;
+  if(*bytes>0){
+    int bufpos;
+    int buflen;
+    bufpos=flac->bufpos;
+    buflen=flac->buflen;
+    if(bufpos<buflen){
+      size_t bytes_to_copy;
+      /*If we haven't consumed all the data we used for file ID yet, consume
+        some more.*/
+      bytes_to_copy=buflen-bufpos;
+      bytes_to_copy=*bytes<bytes_to_copy?*bytes:bytes_to_copy;
+      memcpy(buffer,flac->oldbuf,bytes_to_copy);
+      flac->bufpos+=bytes_to_copy;
+      *bytes=bytes_to_copy;
+    }else{
+      /*Otherwise just read from the file.*/
+      *bytes=fread(buffer,sizeof(*buffer),*bytes,flac->f);
+    }
+    /*This pretty much comes from the FLAC documentation, except that we only
+      check ferror() if we didn't read any bytes at all.*/
+    return *bytes==0?ferror(flac->f)?
+       FLAC__STREAM_DECODER_READ_STATUS_ABORT:
+       FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM:
+       FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+  }
+  return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+}
+
+/*Callback to test the stream for EOF.*/
+static FLAC__bool eof_callback(const FLAC__StreamDecoder *decoder,
+   void *client_data){
+  flacfile *flac;
+  (void)decoder;
+  flac=(flacfile *)client_data;
+  return feof(flac->f)?true:false;
+}
+
+/*A version of strncasecmp() that is guaranteed to only ignore the case of
+  ASCII characters.*/
+int flac_strncasecmp(const char *_a,const char *_b,int _n){
+  int i;
+  for(i=0;i<_n;i++){
+    int a;
+    int b;
+    int d;
+    a=_a[i];
+    b=_b[i];
+    if(a>='a'&&a<='z')a-='a'-'A';
+    if(b>='a'&&b<='z')b-='a'-'A';
+    d=a-b;
+    if(d)return d;
+  }
+  return 0;
+}
+
+/*Callback to process a metadata packet.*/
+static void metadata_callback(const FLAC__StreamDecoder *decoder,
+   const FLAC__StreamMetadata *metadata,void *client_data){
+  flacfile *flac;
+  oe_enc_opt *inopt;
+  (void)decoder;
+  flac=(flacfile *)client_data;
+  inopt=flac->inopt;
+  switch(metadata->type){
+    case FLAC__METADATA_TYPE_STREAMINFO:
+      flac->max_blocksize=metadata->data.stream_info.max_blocksize;
+      inopt->rate=metadata->data.stream_info.sample_rate;
+      inopt->channels=flac->channels=metadata->data.stream_info.channels;
+      inopt->samplesize=metadata->data.stream_info.bits_per_sample;
+      inopt->total_samples_per_channel=
+         metadata->data.stream_info.total_samples;
+      flac->block_buf=malloc(
+         flac->max_blocksize*flac->channels*sizeof(*flac->block_buf));
+      flac->block_buf_pos=0;
+      flac->block_buf_len=0;
+      break;
+    case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+      {
+        FLAC__StreamMetadata_VorbisComment_Entry *comments;
+        FLAC__uint32 num_comments;
+        FLAC__uint32 i;
+        double reference_loudness;
+        double album_gain;
+        double track_gain;
+        double gain;
+        int saw_album_gain;
+        int saw_track_gain;
+        if(!inopt->copy_comments)break;
+        num_comments=metadata->data.vorbis_comment.num_comments;
+        comments=metadata->data.vorbis_comment.comments;
+        saw_album_gain=saw_track_gain=0;
+        album_gain=track_gain=0;
+        /*The default reference loudness for ReplayGain is 89.0 dB*/
+        reference_loudness=89;
+        for(i=0;i<num_comments;i++){
+          char *entry;
+          char *end;
+          entry=(char *)comments[i].entry;
+          /*Check for ReplayGain tags.
+            Parse the ones we have R128 equivalents for, and skip the others.*/
+          if(flac_strncasecmp(entry,"REPLAYGAIN_REFERENCE_LOUDNESS=",30)==0){
+            gain=strtod(entry+30,&end);
+            if(end<=entry+30){
+              fprintf(stderr,_("WARNING: Invalid ReplayGain tag: %s\n"),entry);
+            }
+            else reference_loudness=gain;
+            continue;
+          }
+          if(flac_strncasecmp(entry,"REPLAYGAIN_ALBUM_GAIN=",22)==0){
+            gain=strtod(entry+22,&end);
+            if(end<=entry+22){
+              fprintf(stderr,_("WARNING: Invalid ReplayGain tag: %s\n"),entry);
+            }
+            else{
+              album_gain=gain;
+              saw_album_gain=1;
+            }
+            continue;
+          }
+          if(flac_strncasecmp(entry,"REPLAYGAIN_TRACK_GAIN=",22)==0){
+            gain=strtod(entry+22,&end);
+            if(end<entry+22){
+              fprintf(stderr,_("WARNING: Invalid ReplayGain tag: %s\n"),entry);
+            }
+            else{
+              track_gain=gain;
+              saw_track_gain=1;
+            }
+            continue;
+          }
+          if(flac_strncasecmp(entry,"REPLAYGAIN_ALBUM_PEAK=",22)==0
+             ||flac_strncasecmp(entry,"REPLAYGAIN_TRACK_PEAK=",22)==0){
+            continue;
+          }
+          if(!strchr(entry,'=')){
+            fprintf(stderr,_("WARNING: Invalid comment: %s\n"),entry);
+            fprintf(stderr,
+               _("Discarding comment not in the form name=value\n"));
+            continue;
+          }
+          comment_add(&inopt->comments,&inopt->comments_length,NULL,entry);
+        }
+        /*Set the header gain to the album gain after converting to the R128
+          reference level.*/
+        if(saw_album_gain){
+          gain=256*(album_gain+(84-reference_loudness))+0.5;
+          inopt->gain=gain<-32768?-32768:gain<32767?(int)floor(gain):32767;
+        }
+        /*If there was a track gain, then add an equivalent R128 tag for that.*/
+        if(saw_track_gain){
+          char track_gain_buf[7];
+          int track_gain_val;
+          gain=256*(track_gain-album_gain)+0.5;
+          track_gain_val=gain<-32768?-32768:gain<32767?(int)floor(gain):32767;
+          sprintf(track_gain_buf,"%i",track_gain_val);
+          comment_add(&inopt->comments,&inopt->comments_length,
+             "R128_TRACK_GAIN=",track_gain_buf);
+        }
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+/*Callback to process an audio frame.*/
+static FLAC__StreamDecoderWriteStatus write_callback(
+   const FLAC__StreamDecoder *decoder,const FLAC__Frame *frame,
+   const FLAC__int32 *const buffer[],void *client_data){
+  flacfile *flac;
+  int channels;
+  opus_int32 blocksize;
+  int bits_per_sample;
+  float scale;
+  const int *channel_permute;
+  float *block_buf;
+  int ci;
+  opus_int32 si;
+  (void)decoder;
+  flac=(flacfile *)client_data;
+  /*We do not allow the number of channels to change.*/
+  channels=frame->header.channels;
+  if(channels!=flac->channels){
+    return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+  }
+  /*We do not allow block sizes larger than the declared maximum.*/
+  blocksize=frame->header.blocksize;
+  if(blocksize>flac->max_blocksize){
+    return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+  }
+  /*We do allow the bits per sample to change, though this will confound Opus's
+    silence detection.*/
+  bits_per_sample=frame->header.bits_per_sample;
+  speex_assert(bits_per_sample>0&&bits_per_sample<=32);
+  scale=(0x80000000U>>(bits_per_sample-1))*(1.0F/0x80000000U);
+  channel_permute=flac->channel_permute;
+  block_buf=flac->block_buf;
+  for(ci=0;ci<channels;ci++){
+    const FLAC__int32 *channel_buf;
+    channel_buf=buffer[channel_permute[ci]];
+    for(si=0;si<blocksize;si++){
+      /*There's a loss of precision here for 32-bit samples, but libFLAC
+        doesn't currently support more than 24.*/
+      block_buf[si*channels+ci]=scale*(float)channel_buf[si];
+    }
+  }
+  flac->block_buf_pos=0;
+  flac->block_buf_len=blocksize;
+  return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+/*Dummy error callback (required by libFLAC).*/
+void error_callback(const FLAC__StreamDecoder *decoder,
+   FLAC__StreamDecoderErrorStatus status,void *client_data){
+  (void)decoder;
+  (void)status;
+  (void)client_data;
+}
+
+int flac_id(unsigned char *buf,int len){
+  /*Something screwed up.*/
+  if(len<4)return 0;
+  /*Not FLAC.*/
+  if(memcmp(buf,"fLaC",4))return 0;
+  /*Looks like FLAC.*/
+  return 1;
+}
+
+int oggflac_id(unsigned char *buf,int len){
+  /*Something screwed up.*/
+  if(len<33)return 0;
+  /*Not Ogg.*/
+  if(memcmp(buf,"OggS",4))return 0;
+  /*Not FLAC.*/
+  if(memcmp(buf+28,"\177FLAC",5))return 0;
+  /*Looks like OggFLAC.*/
+  return 1;
+}
+
+/*Read more data for the encoder.*/
+long flac_read(void *client_data,float *buffer,int samples){
+  flacfile *flac;
+  int channels;
+  float *block_buf;
+  long ret;
+  flac=(flacfile *)client_data;
+  channels=flac->channels;
+  block_buf=flac->block_buf;
+  ret=0;
+  /*Keep reading until we get all the samples or hit an error/EOF.
+    Short reads are not allowed.*/
+  while(samples>0){
+    opus_int32 block_buf_pos;
+    opus_int32 block_buf_len;
+    size_t samples_to_copy;
+    block_buf_pos=flac->block_buf_pos;
+    block_buf_len=flac->block_buf_len;
+    if(block_buf_pos>=block_buf_len){
+      /*Read the next frame from the stream.*/
+      if(!FLAC__stream_decoder_process_single(flac->decoder))return ret;
+      block_buf_pos=flac->block_buf_pos;
+      block_buf_len=flac->block_buf_len;
+      /*If we didn't get another block, we hit EOF.
+        FLAC__stream_decoder_process_single still returns successfully in this
+        case.*/
+      if(block_buf_pos>=block_buf_len)return ret;
+    }
+    block_buf_len-=block_buf_pos;
+    samples_to_copy=samples<block_buf_len?samples:block_buf_len;
+    memcpy(buffer,block_buf+block_buf_pos*channels,
+       samples_to_copy*channels*sizeof(*buffer));
+    flac->block_buf_pos+=samples_to_copy;
+    ret+=samples_to_copy;
+    buffer+=samples_to_copy*channels;
+    samples-=samples_to_copy;
+  }
+  return ret;
+}
+
+int flac_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen){
+  flacfile *flac;
+  /*Ok. At this point, we know we have a FLAC or an OggFLAC file.
+    Set up the FLAC decoder.*/
+  flac=malloc(sizeof(*flac));
+  flac->decoder=FLAC__stream_decoder_new();
+  FLAC__stream_decoder_set_md5_checking(flac->decoder,false);
+  /*We get STREAMINFO packets by default, but not VORBIS_COMMENT.*/
+  FLAC__stream_decoder_set_metadata_respond(flac->decoder,
+     FLAC__METADATA_TYPE_VORBIS_COMMENT);
+  flac->inopt=opt;
+  flac->f=in;
+  flac->oldbuf=malloc(buflen*sizeof(*flac->oldbuf));
+  memcpy(flac->oldbuf,oldbuf,buflen*sizeof(*flac->oldbuf));
+  flac->bufpos=0;
+  flac->buflen=buflen;
+  flac->block_buf=NULL;
+  if((*(flac_id(oldbuf,buflen)?
+     FLAC__stream_decoder_init_stream:FLAC__stream_decoder_init_ogg_stream))(
+        flac->decoder,read_callback,NULL,NULL,NULL,eof_callback,
+        write_callback,metadata_callback,error_callback,flac)==
+     FLAC__STREAM_DECODER_INIT_STATUS_OK){
+    /*Decode until we get the file length, sample rate, the number of channels,
+      and the Vorbis comments (if any).*/
+    if(FLAC__stream_decoder_process_until_end_of_metadata(flac->decoder)){
+      opt->read_samples=flac_read;
+      opt->readdata=flac;
+      /*FLAC supports 1 to 8 channels only.*/
+      speex_assert(flac->channels>0&&flac->channels<=8);
+      /*It uses the same channel mappings as WAV.*/
+      flac->channel_permute=wav_permute_matrix[flac->channels-1];
+      return 1;
+    }
+  }
+  flac_close(flac);
+  fprintf(stderr,_("ERROR: Could not open FLAC stream.\n"));
+  return 0;
+}
+
+void flac_close(void *client_data){
+  flacfile *flac;
+  flac=(flacfile *)client_data;
+  free(flac->block_buf);
+  free(flac->oldbuf);
+  FLAC__stream_decoder_delete(flac->decoder);
+  free(flac);
+}
+
+#else
+
+/*FLAC support is disabled.*/
+
+int flac_id(unsigned char *buf,int len){
+  (void)buf;
+  (void)len;
+  return 0;
+}
+
+int oggflac_id(unsigned char *buf,int len){
+  (void)buf;
+  (void)len;
+  return 0;
+}
+
+int flac_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen){
+  (void)in;
+  (void)opt;
+  (void)oldbuf;
+  (void)buflen;
+  return 0;
+}
+
+void flac_close(void *client_data){
+  (void)client_data;
+}
+
+#endif
--- /dev/null
+++ b/src/flac.h
@@ -1,0 +1,34 @@
+#ifndef __FLAC_H
+# define __FLAC_H
+# include <stdio.h>
+# include "os_support.h"
+# include "opusenc.h"
+# if defined(HAVE_LIBFLAC)
+#  include <FLAC/stream_decoder.h>
+#  include <FLAC/metadata.h>
+
+typedef struct flacfile flacfile;
+
+struct flacfile{
+  FLAC__StreamDecoder *decoder;
+  oe_enc_opt *inopt;
+  short channels;
+  FILE *f;
+  const int *channel_permute;
+  unsigned char *oldbuf;
+  int bufpos;
+  int buflen;
+  float *block_buf;
+  opus_int32 block_buf_pos;
+  opus_int32 block_buf_len;
+  opus_int32 max_blocksize;
+};
+
+# endif
+
+int flac_id(unsigned char *buf,int len);
+int oggflac_id(unsigned char *buf,int len);
+int flac_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen);
+void flac_close(void *client_data);
+
+#endif
--- a/src/opusenc.c
+++ b/src/opusenc.c
@@ -79,7 +79,6 @@
 #endif
 
 static void comment_init(char **comments, int* length, const char *vendor_string);
-static void comment_add(char **comments, int* length, char *tag, char *val);
 
 /*Write an Ogg page to a file pointer*/
 static inline int oe_write_page(ogg_page *page, FILE *fp)
@@ -204,6 +203,7 @@
     {"comment", required_argument, NULL, 0},
     {"artist", required_argument, NULL, 0},
     {"title", required_argument, NULL, 0},
+    {"discard-comments", no_argument, NULL, 0},
     {0, 0, 0, 0}
   };
   int i, ret;
@@ -230,9 +230,7 @@
   int                last_segments=0;
   int                eos=0;
   OpusHeader         header;
-  int                comments_length;
   char               ENCODER_string[64];
-  char               *comments;
   /*Counters*/
   opus_int64         nb_encoded=0;
   opus_int64         bytes_written=0;
@@ -290,10 +288,13 @@
   in_format=NULL;
   inopt.channels=chan;
   inopt.rate=coding_rate=rate;
+  /* 0 dB gain is recommended unless you know what you're doing */
+  inopt.gain=0;
   inopt.samplesize=16;
   inopt.endianness=0;
   inopt.rawmode=0;
   inopt.ignorelength=0;
+  inopt.copy_comments=1;
 
   for(i=0;i<256;i++)mapping[i]=i;
 
@@ -300,9 +301,9 @@
   opus_version=opus_get_version_string();
   /*Vendor string should just be the encoder library,
     the ENCODER comment specifies the tool used.*/
-  comment_init(&comments, &comments_length, opus_version);
+  comment_init(&inopt.comments, &inopt.comments_length, opus_version);
   snprintf(ENCODER_string, sizeof(ENCODER_string), "opusenc from %s %s",PACKAGE,VERSION);
-  comment_add(&comments, &comments_length, "ENCODER=", ENCODER_string);
+  comment_add(&inopt.comments, &inopt.comments_length, "ENCODER=", ENCODER_string);
 
   /*Process command-line options*/
   while(1){
@@ -440,11 +441,13 @@
             fprintf(stderr, "Comments must be of the form name=value\n");
             exit(1);
           }
-          comment_add(&comments, &comments_length, NULL, optarg);
+          comment_add(&inopt.comments, &inopt.comments_length, NULL, optarg);
         }else if(strcmp(long_options[option_index].name,"artist")==0){
-          comment_add(&comments, &comments_length, "artist=", optarg);
+          comment_add(&inopt.comments, &inopt.comments_length, "artist=", optarg);
         } else if(strcmp(long_options[option_index].name,"title")==0){
-          comment_add(&comments, &comments_length, "title=", optarg);
+          comment_add(&inopt.comments, &inopt.comments_length, "title=", optarg);
+        } else if(strcmp(long_options[option_index].name,"discard-comments")==0){
+          inopt.copy_comments=0;
         }
         break;
       case 'h':
@@ -548,9 +551,8 @@
   }
   header.channel_mapping=header.channels>8?255:header.nb_streams>1;
   if(header.channel_mapping>0)for(i=0;i<header.channels;i++)header.stream_map[i]=mapping[i];
-  /* 0 dB gain is the recommended unless you know what you're doing */
-  header.gain=0;
   header.input_sample_rate=rate;
+  header.gain=inopt.gain;
 
   min_bytes=max_frame_bytes=(1275*3+7)*header.nb_streams;
   packet=malloc(sizeof(unsigned char)*max_frame_bytes);
@@ -740,8 +742,8 @@
       pages_out++;
     }
 
-    op.packet=(unsigned char *)comments;
-    op.bytes=comments_length;
+    op.packet=(unsigned char *)inopt.comments;
+    op.bytes=inopt.comments_length;
     op.b_o_s=0;
     op.e_o_s=0;
     op.granulepos=0;
@@ -761,7 +763,7 @@
     pages_out++;
   }
 
-  free(comments);
+  free(inopt.comments);
 
   input=malloc(sizeof(float)*frame_size*chan);
   if(input==NULL){
@@ -1021,7 +1023,7 @@
   *comments=p;
 }
 
-static void comment_add(char **comments, int* length, char *tag, char *val)
+void comment_add(char **comments, int* length, char *tag, char *val)
 {
   char* p=*comments;
   int vendor_length=readint(p, 8);
--- a/src/opusenc.h
+++ b/src/opusenc.h
@@ -2,6 +2,7 @@
 #define __OPUSENC_H
 
 #include <opus_types.h>
+#include <ogg/ogg.h>
 
 #ifdef ENABLE_NLS
 #include <libintl.h>
@@ -27,6 +28,7 @@
     int rawmode;
     int channels;
     long rate;
+    int gain;
     int samplesize;
     int endianness;
     char *infilename;
@@ -33,6 +35,9 @@
     int ignorelength;
     int skip;
     int extraout;
+    char *comments;
+    int comments_length;
+    int copy_comments;
 } oe_enc_opt;
 
 void setup_scaler(oe_enc_opt *opt, float scale);
@@ -41,6 +46,7 @@
 void clear_padder(oe_enc_opt *opt);
 int setup_downmix(oe_enc_opt *opt, int out_channels);
 void clear_downmix(oe_enc_opt *opt);
+void comment_add(char **comments, int* length, char *tag, char *val);
 
 typedef struct
 {