Commit 7aea3cae authored by Timothy B. Terriberry's avatar Timothy B. Terriberry
Browse files

Add a gain control API.

A new op_set_gain_offset() allows the application to provide its own
 offset to the current decoder gain setting, as well as specify what
 offsets should be applied.
The header gain alone is still the default, but the application may
 also request that the track gain be applied, or that neither be
 applied.

In addition, an op_get_track_gain() function can parse the track
 gain out of a set of comment tags.
This is mainly provided as a convenience for applications that need
 this information, so they don't have to write their own parser.
parent e15e3621
......@@ -433,6 +433,24 @@ const char *opus_tags_query(const OpusTags *_tags,const char *_tag,int _count)
int opus_tags_query_count(const OpusTags *_tags,const char *_tag)
OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
/**Get the track gain from an R128_TRACK_GAIN tag, if one was specified.
This searches for the first R128_TRACK_GAIN tag with a valid signed,
16-bit decimal integer value and returns the value.
This routine is exposed merely for convenience for applications which wish
to do something special with the track gain (i.e., display it).
If you simply wish to apply the track gain instead of the header gain, you
can use op_set_gain_offset() with an #OP_TRACK_GAIN type and no offset.
\param _tags An initialized #OpusTags structure.
\param[out] _gain_q8 The track gain, in 1/256ths of a dB.
This will lie in the range [-32768,32767], and should
be applied in <em>addition</em> to the header gain.
On error, no value is returned, and the previous
contents remain unchanged.
\return 0 on success, or a negative value on error.
\retval OP_EFALSE There was no track gain available in the given tags.*/
int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8)
OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
/**Clears the #OpusTags structure.
This should be called on an #OpusTags structure after it is no longer
needed.
......@@ -1380,6 +1398,39 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
appropriately.*/
/*@{*/
/**Gain offset type that indicates that the provided offset is relative to the
header gain.
This is the default.*/
#define OP_HEADER_GAIN (0)
/**Gain offset type that indicates that the provided offset is relative to the
R128_TRACK_GAIN value (if any), in addition to the header gain.*/
#define OP_TRACK_GAIN (3008)
/**Gain offset type that indicates that the provided offset should be used as
the gain directly, without applying any the header or track gains.*/
#define OP_ABSOLUTE_GAIN (3009)
/**Sets the gain to be used for decoded output.
By default, the gain in the header is applied with no additional offset.
The total gain (including header gain and/or track gain, if applicable, and
this offset), will be clamped to [-32768,32767]/256 dB.
This is more than enough to saturate or underflow 16-bit PCM.
\note The new gain will not be applied to any already buffered, decoded
output.
This means you cannot change it sample-by-sample, as at best it will be
updated packet-by-packet.
It is meant for setting a target volume level, rather than applying smooth
fades, etc.
\param _of The \c OggOpusFile on which to set the gain offset.
\param _gain_type One of #OP_HEADER_GAIN, #OP_TRACK_GAIN, or
#OP_ABSOLUTE_GAIN.
\param _gain_offset_q8 The gain offset to apply, in 1/256ths of a dB.
\return 0 on success or a negative value on error.
\retval #OP_EINVAL The \a _gain_type was unrecognized.*/
int op_set_gain_offset(OggOpusFile *_of,
int _gain_type,opus_int32 _gain_offset_q8);
/**Reads more samples from the stream.
\note Although \a _buf_size must indicate the total number of values that
can be stored in \a _pcm, the return value is the number of samples
......
......@@ -288,3 +288,41 @@ int opus_tags_query_count(const OpusTags *_tags,const char *_tag){
}
return found;
}
int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){
char **comments;
int *comment_lengths;
int ncomments;
int ci;
comments=_tags->user_comments;
comment_lengths=_tags->comment_lengths;
ncomments=_tags->comments;
/*Look for the first valid R128_TRACK_GAIN tag and use that.*/
for(ci=0;ci<ncomments;ci++){
if(comment_lengths[ci]>16
&&op_strncasecmp(comments[ci],"R128_TRACK_GAIN=",16)==0){
char *p;
opus_int32 gain_q8;
int negative;
p=comments[ci]+16;
negative=0;
if(*p=='-'){
negative=-1;
p++;
}
else if(*p=='+')p++;
gain_q8=0;
while(*p>='0'&&*p<='9'){
gain_q8=10*gain_q8+*p-'0';
if(gain_q8>32767-negative)break;
p++;
}
/*This didn't look like a signed 16-bit decimal integer.
Not a valid R128_TRACK_GAIN tag.*/
if(*p!='\0')continue;
*_gain_q8=(int)(gain_q8+negative^negative);
return 0;
}
}
return OP_FALSE;
}
......@@ -215,6 +215,11 @@ struct OggOpusFile{
int od_buffer_pos;
/*The number of valid samples in the decoded buffer.*/
int od_buffer_size;
/*The type of gain offset to apply.
One of OP_HEADER_GAIN, OP_TRACK_GAIN, or OP_ABSOLUTE_GAIN.*/
int gain_type;
/*The offset to apply to the gain.*/
opus_int32 gain_offset_q8;
/*Internal state for soft clipping and dithering float->short output.*/
#if !defined(OP_FIXED_POINT)
# if defined(OP_SOFT_CLIP)
......
......@@ -1295,6 +1295,43 @@ static int op_bisect_forward_serialno(OggOpusFile *_of,
return 0;
}
static void op_update_gain(OggOpusFile *_of){
OpusHead *head;
opus_int32 gain_q8;
int li;
/*If decode isn't ready, then we'll apply the gain when we initialize the
decoder.*/
if(_of->ready_state<OP_INITSET)return;
gain_q8=_of->gain_offset_q8;
li=_of->seekable?_of->cur_link:0;
head=&_of->links[li].head;
/*We don't have to worry about overflow here because the header gain and
track gain must lie in the range [-32768,32767], and the user-supplied
offset has been pre-clamped to [-98302,98303].*/
switch(_of->gain_type){
case OP_TRACK_GAIN:{
int track_gain_q8;
track_gain_q8=0;
opus_tags_get_track_gain(&_of->links[li].tags,&track_gain_q8);
gain_q8+=track_gain_q8;
}
/*Fall through.*/
case OP_HEADER_GAIN:gain_q8+=head->output_gain;break;
case OP_ABSOLUTE_GAIN:break;
default:OP_ASSERT(0);
}
gain_q8=OP_CLAMP(-32768,gain_q8,32767);
OP_ASSERT(_of->od!=NULL);
#if defined(OPUS_SET_GAIN)
opus_multistream_decoder_ctl(_of->od,OPUS_SET_GAIN(gain_q8));
#else
/*A fallback that works with both float and fixed-point is a bunch of work,
so just force people to use a sufficiently new version.
This is deployed well enough at this point that this shouldn't be a burden.*/
# error "libopus 1.0.1 or later required"
#endif
}
static int op_make_decode_ready(OggOpusFile *_of){
OpusHead *head;
int li;
......@@ -1326,14 +1363,6 @@ static int op_make_decode_ready(OggOpusFile *_of){
_of->od_channel_count=channel_count;
memcpy(_of->od_mapping,head->mapping,sizeof(*head->mapping)*channel_count);
}
#if defined(OPUS_SET_GAIN)
opus_multistream_decoder_ctl(_of->od,OPUS_SET_GAIN(head->output_gain));
#else
/*A fallback that works with both float and fixed-point is a bunch of work,
so just force people to use a sufficiently new version.
This is deployed well enough at this point that this shouldn't be a burden.*/
# error "libopus 1.0.1 or later required"
#endif
_of->ready_state=OP_INITSET;
_of->bytes_tracked=0;
_of->samples_tracked=0;
......@@ -1343,6 +1372,7 @@ static int op_make_decode_ready(OggOpusFile *_of){
straight play-throughs.*/
_of->dither_seed=_of->links[li].serialno;
#endif
op_update_gain(_of);
return 0;
}
......@@ -2515,6 +2545,21 @@ ogg_int64_t op_pcm_tell(OggOpusFile *_of){
return op_get_pcm_offset(_of,gp,li);
}
int op_set_gain_offset(OggOpusFile *_of,
int _gain_type,opus_int32 _gain_offset_q8){
if(_gain_type!=OP_HEADER_GAIN&&_gain_type!=OP_TRACK_GAIN
&&_gain_type!=OP_ABSOLUTE_GAIN){
return OP_EINVAL;
}
_of->gain_type=_gain_type;
/*The sum of header gain and track gain lies in the range [-65536,65534].
These bounds allow the offset to set the final value to anywhere in the
range [-32768,32767], which is what we'll clamp it to before applying.*/
_of->gain_offset_q8=OP_CLAMP(-98302,_gain_offset_q8,98303);
op_update_gain(_of);
return 0;
}
/*Allocate the decoder scratch buffer.
This is done lazily, since if the user provides large enough buffers, we'll
never need it.*/
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment