Commit fd65b94f authored by Timothy B. Terriberry's avatar Timothy B. Terriberry

Improve handling of holes (corrupt pages).

Previously, when we encountered a hole (a gap in the page sequence
 numbers), we would save off all of the packets from the first page
 after the hole, but not timestamp them.
That meant when they were actually decoded, op_pcm_tell() would
 report a timestamp of 0 until reaching the last packet on that
 page.

Instead, handle holes just like a raw seek.
We reset the granule position tracking, and attempt to timestamp
 packets backwards from the end of the page.
If the first page after the hole is an EOS page, we just throw it
 away (rather than risk playing invalid samples due to incorrect
 end-trimming).
We also throw away the first 80 ms of audio after a hole, to allow
 the decoder state to reconverge.

This patch also updates the example to report the hole and
 continue decoding, rather than immediately stopping when a hole is
 encountered, in order to test the above features.
parent f83675eb
......@@ -249,7 +249,11 @@ int main(int _argc,const char **_argv){
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){
if(ret==OP_HOLE){
fprintf(stderr,"\nHole detected! Corrupt file segment?\n");
continue;
}
else 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;
......
......@@ -1980,13 +1980,32 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
opus_int32 total_duration;
int durations[255];
int op_count;
int report_hole;
report_hole=0;
total_duration=op_collect_audio_packets(_of,durations);
if(OP_UNLIKELY(total_duration<0)){
/*Drain the packets from the page anyway.*/
/*libogg reported a hole (a gap in the page sequence numbers).
Drain the packets from the page anyway.
If we don't, they'll still be there when we fetch the next page.
Then, when we go to pull out packets, we might get more than 255,
which would overrun our packet buffer.*/
total_duration=op_collect_audio_packets(_of,durations);
OP_ASSERT(total_duration>=0);
/*Report holes to the caller.*/
if(!_ignore_holes)return OP_HOLE;
if(!_ignore_holes){
/*Report the hole to the caller after we finish timestamping the
packets.*/
report_hole=1;
/*We had lost or damaged pages, so reset our granule position
tracking.
This makes holes behave the same as a small raw seek.
If the next page is the EOS page, we'll discard it (because we
can't perform end trimming properly), and we'll always discard at
least 80 ms of audio (to allow decoder state to re-converge).
We could try to fill in the gap with PLC by looking at timestamps
in the non-EOS case, but that's complicated and error prone and we
can't rely on the timestamps being valid.*/
_of->prev_packet_gp=-1;
}
}
op_count=_of->op_count;
/*If we found at least one audio data packet, compute per-packet granule
......@@ -2013,6 +2032,7 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
Proceed to the next link, rather than risk playing back some
samples that shouldn't have been played.*/
_of->op_count=0;
if(report_hole)return OP_HOLE;
continue;
}
/*By default discard 80 ms of data after a seek, unless we seek
......@@ -2114,10 +2134,11 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
}
_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 0;
_of->op_count=op_count=pi;
}
if(report_hole)return OP_HOLE;
/*If end-trimming didn't trim all the packets, we're done.*/
if(op_count>0)return 0;
}
}
}
......
Markdown is supported
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