summaryrefslogtreecommitdiff
path: root/drivers/opus/opusfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/opus/opusfile.c')
-rw-r--r--drivers/opus/opusfile.c201
1 files changed, 159 insertions, 42 deletions
diff --git a/drivers/opus/opusfile.c b/drivers/opus/opusfile.c
index a38e8cd5ad..a9d6cc4d63 100644
--- a/drivers/opus/opusfile.c
+++ b/drivers/opus/opusfile.c
@@ -14,9 +14,7 @@
last mod: $Id: vorbisfile.c 17573 2010-10-27 14:53:59Z xiphmont $
********************************************************************/
-#ifdef OPUS_ENABLED
#include "opus/opus_config.h"
-#endif
#include "opus/internal.h"
#include <stdio.h>
@@ -237,7 +235,9 @@ static int op_add_serialno(const ogg_page *_og,
nserialnos=*_nserialnos;
cserialnos=*_cserialnos;
if(OP_UNLIKELY(nserialnos>=cserialnos)){
- if(OP_UNLIKELY(cserialnos>INT_MAX-1>>1))return OP_EFAULT;
+ if(OP_UNLIKELY(cserialnos>INT_MAX/(int)sizeof(*serialnos)-1>>1)){
+ return OP_EFAULT;
+ }
cserialnos=2*cserialnos+1;
OP_ASSERT(nserialnos<cserialnos);
serialnos=(ogg_uint32_t *)_ogg_realloc(serialnos,
@@ -825,6 +825,7 @@ static opus_int32 op_collect_audio_packets(OggOpusFile *_of,
static int op_find_initial_pcm_offset(OggOpusFile *_of,
OggOpusLink *_link,ogg_page *_og){
ogg_page og;
+ opus_int64 page_offset;
ogg_int64_t pcm_start;
ogg_int64_t prev_packet_gp;
ogg_int64_t cur_page_gp;
@@ -842,13 +843,12 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
least once.*/
total_duration=0;
do{
- opus_int64 llret;
- llret=op_get_next_page(_of,_og,_of->end);
+ page_offset=op_get_next_page(_of,_og,_of->end);
/*We should get a page unless the file is truncated or mangled.
Otherwise there are no audio data packets in the whole logical stream.*/
- if(OP_UNLIKELY(llret<0)){
+ if(OP_UNLIKELY(page_offset<0)){
/*Fail if there was a read error.*/
- if(llret<OP_FALSE)return (int)llret;
+ if(page_offset<OP_FALSE)return (int)page_offset;
/*Fail if the pre-skip is non-zero, since it's asking us to skip more
samples than exist.*/
if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP;
@@ -949,6 +949,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
_of->op_count=pi;
_of->cur_discard_count=_link->head.pre_skip;
_of->prev_packet_gp=_link->pcm_start=pcm_start;
+ _of->prev_page_offset=page_offset;
return 0;
}
@@ -1304,13 +1305,20 @@ static void op_update_gain(OggOpusFile *_of){
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_ALBUM_GAIN:{
+ int album_gain_q8;
+ album_gain_q8=0;
+ opus_tags_get_album_gain(&_of->links[li].tags,&album_gain_q8);
+ gain_q8+=album_gain_q8;
+ gain_q8+=head->output_gain;
+ }break;
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.*/
+ gain_q8+=head->output_gain;
+ }break;
case OP_HEADER_GAIN:gain_q8+=head->output_gain;break;
case OP_ABSOLUTE_GAIN:break;
default:OP_ASSERT(0);
@@ -1361,7 +1369,7 @@ static int op_make_decode_ready(OggOpusFile *_of){
_of->ready_state=OP_INITSET;
_of->bytes_tracked=0;
_of->samples_tracked=0;
-#if !defined(OPUS_FIXED_POINT)
+#if !defined(OP_FIXED_POINT)
_of->state_channel_count=0;
/*Use the serial number for the PRNG seed to get repeatable output for
straight play-throughs.*/
@@ -1402,6 +1410,7 @@ static int op_open_seekable2(OggOpusFile *_of){
ogg_sync_state oy_start;
ogg_stream_state os_start;
ogg_packet *op_start;
+ opus_int64 prev_page_offset;
opus_int64 start_offset;
int start_op_count;
int ret;
@@ -1421,6 +1430,7 @@ static int op_open_seekable2(OggOpusFile *_of){
if(op_start==NULL)return OP_EFAULT;
*&oy_start=_of->oy;
*&os_start=_of->os;
+ prev_page_offset=_of->prev_page_offset;
start_offset=_of->offset;
memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count);
OP_ASSERT((*_of->callbacks.tell)(_of->source)==op_position(_of));
@@ -1437,6 +1447,7 @@ static int op_open_seekable2(OggOpusFile *_of){
memcpy(_of->op,op_start,sizeof(*_of->op)*start_op_count);
_ogg_free(op_start);
_of->prev_packet_gp=_of->links[0].pcm_start;
+ _of->prev_page_offset=prev_page_offset;
_of->cur_discard_count=_of->links[0].head.pre_skip;
if(OP_UNLIKELY(ret<0))return ret;
/*And restore the position indicator.*/
@@ -1451,6 +1462,7 @@ static void op_decode_clear(OggOpusFile *_of){
_of->op_count=0;
_of->od_buffer_size=0;
_of->prev_packet_gp=-1;
+ _of->prev_page_offset=-1;
if(!_of->seekable){
OP_ASSERT(_of->ready_state>=OP_INITSET);
opus_tags_clear(&_of->links[0].tags);
@@ -1815,7 +1827,8 @@ opus_int32 op_bitrate_instant(OggOpusFile *_of){
0) Need more data (only if _readp==0).
1) Got at least one audio data packet.*/
static int op_fetch_and_process_page(OggOpusFile *_of,
- ogg_page *_og,opus_int64 _page_pos,int _readp,int _spanp,int _ignore_holes){
+ ogg_page *_og,opus_int64 _page_offset,
+ int _readp,int _spanp,int _ignore_holes){
OggOpusLink *links;
ogg_uint32_t cur_serialno;
int seekable;
@@ -1843,9 +1856,9 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
_og=NULL;
}
/*Keep reading until we get a page with the correct serialno.*/
- else _page_pos=op_get_next_page(_of,&og,_of->end);
+ else _page_offset=op_get_next_page(_of,&og,_of->end);
/*EOF: Leave uninitialized.*/
- if(_page_pos<0)return _page_pos<OP_FALSE?(int)_page_pos:OP_EOF;
+ if(_page_offset<0)return _page_offset<OP_FALSE?(int)_page_offset:OP_EOF;
if(OP_LIKELY(_of->ready_state>=OP_STREAMSET)){
if(cur_serialno!=(ogg_uint32_t)ogg_page_serialno(&og)){
/*Two possibilities:
@@ -1890,8 +1903,9 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
_of->ready_state=OP_STREAMSET;
/*If we're at the start of this link, initialize the granule position
and pre-skip tracking.*/
- if(_page_pos<=links[cur_link].data_offset){
+ if(_page_offset<=links[cur_link].data_offset){
_of->prev_packet_gp=links[cur_link].pcm_start;
+ _of->prev_page_offset=-1;
_of->cur_discard_count=links[cur_link].head.pre_skip;
/*Ignore a hole at the start of a new link (this is common for
streams joined in the middle) or after seeking.*/
@@ -2039,7 +2053,8 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
op_granpos_add(&prev_packet_gp,prev_packet_gp,total_duration)
should succeed and give prev_packet_gp==cur_page_gp.
But we don't bother to check that, as there isn't much we can do
- if it's not true.
+ if it's not true, and it actually will not be true on the first
+ page after a seek, if there was a continued packet.
The only thing we guarantee is that the start and end granule
positions of the packets are valid, and that they are monotonic
within a page.
@@ -2068,6 +2083,7 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
OP_ASSERT(total_duration==0);
}
_of->prev_packet_gp=prev_packet_gp;
+ _of->prev_page_offset=_page_offset;
_of->op_count=pi;
/*If end-trimming didn't trim all the packets, we're done.*/
if(OP_LIKELY(pi>0))return 1;
@@ -2141,6 +2157,27 @@ static ogg_int64_t op_get_granulepos(const OggOpusFile *_of,
return -1;
}
+/*A small helper to determine if an Ogg page contains data that continues onto
+ a subsequent page.*/
+static int op_page_continues(const ogg_page *_og){
+ int nlacing;
+ OP_ASSERT(_og->header_len>=27);
+ nlacing=_og->header[26];
+ OP_ASSERT(_og->header_len>=27+nlacing);
+ /*This also correctly handles the (unlikely) case of nlacing==0, because
+ 0!=255.*/
+ return _og->header[27+nlacing-1]==255;
+}
+
+/*A small helper to buffer the continued packet data from a page.*/
+static void op_buffer_continued_data(OggOpusFile *_of,ogg_page *_og){
+ ogg_packet op;
+ ogg_stream_pagein(&_of->os,_og);
+ /*Drain any packets that did end on this page (and ignore holes).
+ We only care about the continued packet data.*/
+ while(ogg_stream_packetout(&_of->os,&op));
+}
+
/*This controls how close the target has to be to use the current stream
position to subdivide the initial range.
Two minutes seems to be a good default.*/
@@ -2172,11 +2209,13 @@ static int op_pcm_seek_page(OggOpusFile *_of,
opus_int64 end;
opus_int64 boundary;
opus_int64 best;
+ opus_int64 best_start;
opus_int64 page_offset;
opus_int64 d0;
opus_int64 d1;
opus_int64 d2;
int force_bisect;
+ int buffering;
int ret;
_of->bytes_tracked=0;
_of->samples_tracked=0;
@@ -2184,8 +2223,9 @@ static int op_pcm_seek_page(OggOpusFile *_of,
best_gp=pcm_start=link->pcm_start;
pcm_end=link->pcm_end;
serialno=link->serialno;
- best=begin=link->data_offset;
+ best=best_start=begin=link->data_offset;
page_offset=-1;
+ buffering=0;
/*We discard the first 80 ms of data after a seek, so seek back that much
farther.
If we can't, simply seek to the beginning of the link.*/
@@ -2231,6 +2271,18 @@ static int op_pcm_seek_page(OggOpusFile *_of,
if(offset-begin>=end-begin>>1||diff>-OP_CUR_TIME_THRESH){
best=begin=offset;
best_gp=pcm_start=gp;
+ /*If we have buffered data from a continued packet, remember the
+ offset of the previous page's start, so that if we do wind up
+ having to seek back here later, we can prime the stream with
+ the continued packet data.
+ With no continued packet, we remember the end of the page.*/
+ best_start=_of->os.body_returned<_of->os.body_fill?
+ _of->prev_page_offset:best;
+ /*If there's completed packets and data in the stream state,
+ prev_page_offset should always be set.*/
+ OP_ASSERT(best_start>=0);
+ /*Buffer any continued packet data starting from here.*/
+ buffering=1;
}
}
else{
@@ -2242,13 +2294,14 @@ static int op_pcm_seek_page(OggOpusFile *_of,
generally 1 second or less), we can loop them continuously
without seeking at all.*/
OP_ALWAYS_TRUE(!op_granpos_add(&prev_page_gp,_of->op[0].granulepos,
- op_get_packet_duration(_of->op[0].packet,_of->op[0].bytes)));
+ -op_get_packet_duration(_of->op[0].packet,_of->op[0].bytes)));
if(op_granpos_cmp(prev_page_gp,_target_gp)<=0){
/*Don't call op_decode_clear(), because it will dump our
packets.*/
_of->op_pos=0;
_of->od_buffer_size=0;
_of->prev_packet_gp=prev_page_gp;
+ /*_of->prev_page_offset already points to the right place.*/
_of->ready_state=OP_STREAMSET;
return op_make_decode_ready(_of);
}
@@ -2269,6 +2322,9 @@ static int op_pcm_seek_page(OggOpusFile *_of,
Vinen)" from libvorbisfile.
It has been modified substantially since.*/
op_decode_clear(_of);
+ if(!buffering)ogg_stream_reset_serialno(&_of->os,serialno);
+ _of->cur_link=_li;
+ _of->ready_state=OP_STREAMSET;
/*Initialize the interval size history.*/
d2=d1=d0=end-begin;
force_bisect=0;
@@ -2294,12 +2350,23 @@ static int op_pcm_seek_page(OggOpusFile *_of,
force_bisect=0;
}
if(bisect!=_of->offset){
+ /*Discard any buffered continued packet data.*/
+ if(buffering)ogg_stream_reset(&_of->os);
+ buffering=0;
page_offset=-1;
ret=op_seek_helper(_of,bisect);
if(OP_UNLIKELY(ret<0))return ret;
}
chunk_size=OP_CHUNK_SIZE;
next_boundary=boundary;
+ /*Now scan forward and figure out where we landed.
+ In the ideal case, we will see a page with a granule position at or
+ before our target, followed by a page with a granule position after our
+ target (or the end of the search interval).
+ Then we can just drop out and will have all of the data we need with no
+ additional seeking.
+ If we landed too far before, or after, we'll break out and do another
+ bisection.*/
while(begin<end){
page_offset=op_get_next_page(_of,&og,boundary);
if(page_offset<0){
@@ -2309,7 +2376,10 @@ static int op_pcm_seek_page(OggOpusFile *_of,
/*If we scanned the whole interval, we're done.*/
if(bisect<=begin+1)end=begin;
else{
- /*Otherwise, back up one chunk.*/
+ /*Otherwise, back up one chunk.
+ First, discard any data from a continued packet.*/
+ if(buffering)ogg_stream_reset(&_of->os);
+ buffering=0;
bisect=OP_MAX(bisect-chunk_size,begin);
ret=op_seek_helper(_of,bisect);
if(OP_UNLIKELY(ret<0))return ret;
@@ -2322,12 +2392,32 @@ static int op_pcm_seek_page(OggOpusFile *_of,
}
else{
ogg_int64_t gp;
+ int has_packets;
/*Save the offset of the first page we found after the seek, regardless
of the stream it came from or whether or not it has a timestamp.*/
next_boundary=OP_MIN(page_offset,next_boundary);
if(serialno!=(ogg_uint32_t)ogg_page_serialno(&og))continue;
- gp=ogg_page_granulepos(&og);
- if(gp==-1)continue;
+ has_packets=ogg_page_packets(&og)>0;
+ /*Force the gp to -1 (as it should be per spec) if no packets end on
+ this page.
+ Otherwise we might get confused when we try to pull out a packet
+ with that timestamp and can't find it.*/
+ gp=has_packets?ogg_page_granulepos(&og):-1;
+ if(gp==-1){
+ if(buffering){
+ if(OP_LIKELY(!has_packets))ogg_stream_pagein(&_of->os,&og);
+ else{
+ /*If packets did end on this page, but we still didn't have a
+ valid granule position (in violation of the spec!), stop
+ buffering continued packet data.
+ Otherwise we might continue past the packet we actually
+ wanted.*/
+ ogg_stream_reset(&_of->os);
+ buffering=0;
+ }
+ }
+ continue;
+ }
if(op_granpos_cmp(gp,_target_gp)<0){
/*We found a page that ends before our target.
Advance to the raw offset of the next page.*/
@@ -2340,7 +2430,22 @@ static int op_pcm_seek_page(OggOpusFile *_of,
}
/*Save the byte offset of the end of the page with this granule
position.*/
- best=begin;
+ best=best_start=begin;
+ /*Buffer any data from a continued packet, if necessary.
+ This avoids the need to seek back here if the next timestamp we
+ encounter while scanning forward lies after our target.*/
+ if(buffering)ogg_stream_reset(&_of->os);
+ if(op_page_continues(&og)){
+ op_buffer_continued_data(_of,&og);
+ /*If we have a continued packet, remember the offset of this
+ page's start, so that if we do wind up having to seek back here
+ later, we can prime the stream with the continued packet data.
+ With no continued packet, we remember the end of the page.*/
+ best_start=page_offset;
+ }
+ /*Then force buffering on, so that if a packet starts (but does not
+ end) on the next page, we still avoid the extra seek back.*/
+ buffering=1;
best_gp=pcm_start=gp;
OP_ALWAYS_TRUE(!op_granpos_diff(&diff,_target_gp,pcm_start));
/*If we're more than a second away from our target, break out and
@@ -2372,28 +2477,40 @@ static int op_pcm_seek_page(OggOpusFile *_of,
}
}
}
- /*Found our page.
- Seek to the end of it and update prev_packet_gp.
- Our caller will set cur_discard_count.
- This is an easier case than op_raw_seek(), as we don't need to keep any
- packets from the page we found.*/
- /*Seek, if necessary.*/
- if(best!=page_offset){
- page_offset=-1;
- ret=op_seek_helper(_of,best);
- if(OP_UNLIKELY(ret<0))return ret;
- }
+ /*Found our page.*/
OP_ASSERT(op_granpos_cmp(best_gp,pcm_start)>=0);
- _of->cur_link=_li;
- _of->ready_state=OP_STREAMSET;
+ /*Seek, if necessary.
+ If we were buffering data from a continued packet, we should be able to
+ continue to scan forward to get the rest of the data (even if
+ page_offset==-1).
+ Otherwise, we need to seek back to best_start.*/
+ if(!buffering){
+ if(best_start!=page_offset){
+ page_offset=-1;
+ ret=op_seek_helper(_of,best_start);
+ if(OP_UNLIKELY(ret<0))return ret;
+ }
+ if(best_start<best){
+ /*Retrieve the page at best_start, if we do not already have it.*/
+ if(page_offset<0){
+ page_offset=op_get_next_page(_of,&og,link->end_offset);
+ if(OP_UNLIKELY(page_offset<OP_FALSE))return (int)page_offset;
+ if(OP_UNLIKELY(page_offset!=best_start))return OP_EBADLINK;
+ }
+ op_buffer_continued_data(_of,&og);
+ page_offset=-1;
+ }
+ }
+ /*Update prev_packet_gp to allow per-packet granule position assignment.*/
_of->prev_packet_gp=best_gp;
- ogg_stream_reset_serialno(&_of->os,serialno);
+ _of->prev_page_offset=best_start;
ret=op_fetch_and_process_page(_of,page_offset<0?NULL:&og,page_offset,1,0,1);
if(OP_UNLIKELY(ret<=0))return OP_EBADLINK;
/*Verify result.*/
if(OP_UNLIKELY(op_granpos_cmp(_of->prev_packet_gp,_target_gp)>0)){
return OP_EBADLINK;
}
+ /*Our caller will set cur_discard_count to handle pre-roll.*/
return 0;
}
@@ -2554,8 +2671,8 @@ void op_set_decode_callback(OggOpusFile *_of,
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){
+ if(_gain_type!=OP_HEADER_GAIN&&_gain_type!=OP_ALBUM_GAIN
+ &&_gain_type!=OP_TRACK_GAIN&&_gain_type!=OP_ABSOLUTE_GAIN){
return OP_EINVAL;
}
_of->gain_type=_gain_type;
@@ -2568,7 +2685,7 @@ int op_set_gain_offset(OggOpusFile *_of,
}
void op_set_dither_enabled(OggOpusFile *_of,int _enabled){
-#if !defined(OPUS_FIXED_POINT)
+#if !defined(OP_FIXED_POINT)
_of->dither_disabled=!_enabled;
if(!_enabled)_of->dither_mute=65;
#endif
@@ -2603,7 +2720,7 @@ static int op_decode(OggOpusFile *_of,op_sample *_pcm,
int ret;
/*First we try using the application-provided decode callback.*/
if(_of->decode_cb!=NULL){
-#if defined(OPUS_FIXED_POINT)
+#if defined(OP_FIXED_POINT)
ret=(*_of->decode_cb)(_of->decode_cb_ctx,_of->od,_pcm,_op,
_nsamples,_nchannels,OP_DEC_FORMAT_SHORT,_of->cur_link);
#else
@@ -2614,7 +2731,7 @@ static int op_decode(OggOpusFile *_of,op_sample *_pcm,
else ret=OP_DEC_USE_DEFAULT;
/*If the application didn't want to handle decoding, do it ourselves.*/
if(ret==OP_DEC_USE_DEFAULT){
-#if defined(OPUS_FIXED_POINT)
+#if defined(OP_FIXED_POINT)
ret=opus_multistream_decode(_of->od,
_op->packet,_op->bytes,_pcm,_nsamples,0);
#else
@@ -2776,7 +2893,7 @@ static int op_filter_read_native(OggOpusFile *_of,void *_dst,int _dst_sz,
return ret;
}
-#if !defined(OPUS_FIXED_POINT)||!defined(OP_DISABLE_FLOAT_API)
+#if !defined(OP_FIXED_POINT)||!defined(OP_DISABLE_FLOAT_API)
/*Matrices for downmixing from the supported channel counts to stereo.
The matrices with 5 or more channels are normalized to a total volume of 2.0,
@@ -2815,7 +2932,7 @@ static const float OP_STEREO_DOWNMIX[OP_NCHANNELS_MAX-2][OP_NCHANNELS_MAX][2]={
#endif
-#if defined(OPUS_FIXED_POINT)
+#if defined(OP_FIXED_POINT)
/*Matrices for downmixing from the supported channel counts to stereo.
The matrices with 5 or more channels are normalized to a total volume of 2.0,