shithub: opusfile

Download patch

ref: fd65b94fa24d3e326fe978a9bf422fe8ce029882
parent: f83675ebbd79134b5eece88b792d6a4e953b78d3
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Fri May 12 13:49:53 EDT 2017

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.

--- a/examples/opusfile_example.c
+++ b/examples/opusfile_example.c
@@ -249,7 +249,11 @@
          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;
--- a/src/opusfile.c
+++ b/src/opusfile.c
@@ -1980,13 +1980,32 @@
       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 @@
               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 @@
         }
         _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;
     }
   }
 }