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
{