]> git.sesse.net Git - vlc/commitdiff
Opus demuxing fixes.
authorTimothy B. Terriberry <tterribe@xiph.org>
Sun, 2 Sep 2012 14:36:44 +0000 (07:36 -0700)
committerRafaël Carré <funman@videolan.org>
Mon, 3 Sep 2012 21:22:40 +0000 (23:22 +0200)
This properly handles pre-skip, seeking pre-roll, and end-trim. It
uses the i_nb_samples field of block_t to signal to the decoder how
many samples should be returned after (possibly) skipping some at
the start of the block (for both pre-skip and pre-roll). In
addition, it abuses the i_length field of block_t to signal to the
decoder how many samples to trim from the end of the packet (for
end-trimming).

This patch does not compute correct timestamps for streams which
start at a non-zero offset (e.g., live streams joined in the
middle), nor does it correctly compute the length of a stream. But
neither of those things work for Vorbis, either. I'm leaving them
for a follow-up patch.

Signed-off-by: Rafaël Carré <funman@videolan.org>
modules/demux/ogg.c
modules/demux/ogg.h
modules/demux/oggseek.c

index f0b603adeb18339188cb8bc05c44e6b81e6150f5..52b01af275590a1b08c74861218674d765b83f22 100644 (file)
@@ -123,6 +123,7 @@ static int  Control( demux_t *, int, va_list );
 static int  Ogg_ReadPage     ( demux_t *, ogg_page * );
 static void Ogg_UpdatePCR    ( logical_stream_t *, ogg_packet * );
 static void Ogg_DecodePacket ( demux_t *, logical_stream_t *, ogg_packet * );
+static int  Ogg_OpusPacketDuration( logical_stream_t *, ogg_packet * );
 
 static int Ogg_BeginningOfStream( demux_t *p_demux );
 static int Ogg_FindLogicalStreams( demux_t *p_demux );
@@ -289,6 +290,7 @@ static int Demux( demux_t * p_demux )
                 p_stream->b_reinit = true;
                 p_stream->i_pcr = VLC_TS_0;
                 p_stream->i_interpolated_pcr = VLC_TS_0;
+                p_stream->i_previous_granulepos = -1;
                 es_out_Control( p_demux->out, ES_OUT_SET_PCR, VLC_TS_0);
             }
 
@@ -342,10 +344,37 @@ static int Demux( demux_t * p_demux )
                 if( p_stream->i_pcr >= 0 )
                 {
                     p_stream->b_reinit = false;
+                    /* For Opus, trash the first 80 ms of decoded output as
+                       well, to avoid blowing out speakers if we get unlucky.
+                       Opus predicts content from prior frames, which can go
+                       badly if we seek right where the stream goes from very
+                       quiet to very loud. It will converge after a bit. */
+                    if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+                    {
+                        ogg_int64_t start_time;
+                        int duration;
+                        p_stream->i_skip_frames = 80*48;
+                        /* Make sure we never play audio from within the
+                           pre-skip at the beginning of the stream. */
+                        duration =
+                            Ogg_OpusPacketDuration( p_stream, &oggpacket );
+                        start_time = p_stream->i_previous_granulepos;
+                        if( duration > 0 )
+                        {
+                            start_time = start_time > duration ?
+                                start_time - duration : 0;
+                        }
+                        if( p_stream->i_pre_skip > start_time )
+                        {
+                            p_stream->i_skip_frames +=
+                                p_stream->i_pre_skip - start_time;
+                        }
+                    }
                 }
                 else
                 {
                     p_stream->i_interpolated_pcr = -1;
+                    p_stream->i_previous_granulepos = -1;
                     continue;
                 }
 
@@ -463,6 +492,7 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
                 p_stream->b_reinit = true;
                 p_stream->i_pcr = -1;
                 p_stream->i_interpolated_pcr = -1;
+                p_stream->i_previous_granulepos = -1;
                 ogg_stream_reset( &p_stream->os );
             }
             ogg_sync_reset( &p_sys->oy );
@@ -516,6 +546,7 @@ static int Ogg_ReadPage( demux_t *p_demux, ogg_page *p_oggpage )
 static void Ogg_UpdatePCR( logical_stream_t *p_stream,
                            ogg_packet *p_oggpacket )
 {
+    p_stream->i_end_trim = 0;
     /* Convert the granulepos into a pcr */
     if( p_oggpacket->granulepos >= 0 )
     {
@@ -538,15 +569,35 @@ static void Ogg_UpdatePCR( logical_stream_t *p_stream,
         }
         else
         {
-            p_stream->i_pcr = p_oggpacket->granulepos * INT64_C(1000000)
-                              / p_stream->f_rate;
+            ogg_int64_t sample;
+            sample = p_oggpacket->granulepos;
+            if( p_oggpacket->e_o_s &&
+                p_stream->fmt.i_codec == VLC_CODEC_OPUS &&
+                p_stream->i_previous_granulepos >= 0 )
+            {
+                int duration;
+                duration = Ogg_OpusPacketDuration( p_stream, p_oggpacket );
+                if( duration > 0 )
+                {
+                    ogg_int64_t end_sample;
+                    end_sample = p_stream->i_previous_granulepos + duration;
+                    if( end_sample > sample )
+                        p_stream->i_end_trim = (int)(end_sample - sample);
+                }
+            }
+            if (sample >= p_stream->i_pre_skip)
+                sample -= p_stream->i_pre_skip;
+            else
+                sample = 0;
+            p_stream->i_pcr = sample * INT64_C(1000000) / p_stream->f_rate;
         }
 
-        p_stream->i_pcr += 1;
+        p_stream->i_pcr += VLC_TS_0;
         p_stream->i_interpolated_pcr = p_stream->i_pcr;
     }
     else
     {
+        int duration;
         p_stream->i_pcr = -1;
 
         /* no granulepos available, try to interpolate the pcr.
@@ -554,11 +605,30 @@ static void Ogg_UpdatePCR( logical_stream_t *p_stream,
         if( p_stream->fmt.i_cat == VIDEO_ES )
             /* 1 frame per packet */
             p_stream->i_interpolated_pcr += (INT64_C(1000000) / p_stream->f_rate);
+        else if( p_stream->fmt.i_codec == VLC_CODEC_OPUS &&
+                 p_stream->i_previous_granulepos >= 0 &&
+                 ( duration =
+                     Ogg_OpusPacketDuration( p_stream, p_oggpacket ) ) > 0 )
+        {
+            ogg_int64_t sample;
+            p_oggpacket->granulepos =
+                p_stream->i_previous_granulepos + duration;
+            sample = p_oggpacket->granulepos;
+            if (sample >= p_stream->i_pre_skip)
+                sample -= p_stream->i_pre_skip;
+            else
+                sample = 0;
+            p_stream->i_interpolated_pcr =
+                VLC_TS_0 + sample * INT64_C(1000000) / p_stream->f_rate;
+        }
         else if( p_stream->fmt.i_bitrate )
+        {
             p_stream->i_interpolated_pcr +=
                 ( p_oggpacket->bytes * INT64_C(1000000) /
                   p_stream->fmt.i_bitrate / 8 );
+        }
     }
+    p_stream->i_previous_granulepos = p_oggpacket->granulepos;
 }
 
 /****************************************************************************
@@ -709,17 +779,6 @@ static void Ogg_DecodePacket( demux_t *p_demux,
         p_stream->fmt.i_codec == VLC_CODEC_SPEEX ||
         p_stream->fmt.i_codec == VLC_CODEC_OPUS ||
         p_stream->fmt.i_codec == VLC_CODEC_FLAC )
-        if (p_stream->i_pre_skip)
-        {
-            if( i_pts > p_stream->i_pre_skip )
-            {
-                i_pts -= - p_stream->i_pre_skip;
-            }
-            else
-            {
-                i_pts = 0;
-            }
-        }
     {
         if( p_stream->i_pcr >= 0 )
         {
@@ -738,17 +797,6 @@ static void Ogg_DecodePacket( demux_t *p_demux,
 
             /* The granulepos is the end date of the sample */
             i_pts = p_stream->i_pcr;
-            if (p_stream->i_pre_skip)
-            {
-                if( i_pts > p_stream->i_pre_skip )
-                {
-                    i_pts -= - p_stream->i_pre_skip;
-                }
-                else
-                {
-                    i_pts = 0;
-                }
-            }
         }
     }
 
@@ -798,12 +846,40 @@ static void Ogg_DecodePacket( demux_t *p_demux,
     if( !( p_block = block_New( p_demux, p_oggpacket->bytes ) ) ) return;
 
 
-    /* may need to preroll video frames after a seek */
+    /* may need to preroll after a seek */
     if ( p_stream->i_skip_frames > 0 )
     {
-        p_block->i_flags |= BLOCK_FLAG_PREROLL;
-        p_stream->i_skip_frames--;
+        if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+        {
+            int duration;
+            duration = Ogg_OpusPacketDuration( p_stream, p_oggpacket );
+            if( p_stream->i_skip_frames > duration )
+            {
+                p_block->i_flags |= BLOCK_FLAG_PREROLL;
+                p_block->i_nb_samples = 0;
+                p_stream->i_skip_frames -= duration;
+            }
+            else
+            {
+                p_block->i_nb_samples = duration - p_stream->i_skip_frames;
+                if( p_stream->i_previous_granulepos >=
+                    p_block->i_nb_samples + p_stream->i_pre_skip )
+                {
+                    i_pts = VLC_TS_0 + (p_stream->i_previous_granulepos
+                        - p_block->i_nb_samples - p_stream->i_pre_skip) *
+                        INT64_C(1000000) / p_stream->f_rate;
+                }
+                p_stream->i_skip_frames = 0;
+            }
+        }
+        else
+        {
+            p_block->i_flags |= BLOCK_FLAG_PREROLL;
+            p_stream->i_skip_frames--;
+        }
     }
+    else if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+        p_block->i_nb_samples = Ogg_OpusPacketDuration( p_stream, p_oggpacket );
 
 
     /* Normalize PTS */
@@ -812,7 +888,11 @@ static void Ogg_DecodePacket( demux_t *p_demux,
     else if( i_pts == -1 ) i_pts = VLC_TS_INVALID;
 
     if( p_stream->fmt.i_cat == AUDIO_ES )
+    {
         p_block->i_dts = p_block->i_pts = i_pts;
+        /* Blatant abuse of the i_length field. */
+        p_block->i_length = p_stream->i_end_trim;
+    }
     else if( p_stream->fmt.i_cat == SPU_ES )
     {
         p_block->i_dts = p_block->i_pts = i_pts;
@@ -905,6 +985,47 @@ static void Ogg_DecodePacket( demux_t *p_demux,
     es_out_Send( p_demux->out, p_stream->p_es, p_block );
 }
 
+/* Re-implemented to avoid linking against libopus from the demuxer. */
+static int Ogg_OpusPacketDuration( logical_stream_t *p_stream,
+                                   ogg_packet *p_oggpacket )
+{
+    static const int silk_fs_div[4] = { 6000, 3000, 1500, 1000 };
+    int toc;
+    int nframes;
+    int frame_size;
+    int nsamples;
+    int i_rate;
+    if( p_oggpacket->bytes < 1 )
+        return VLC_EGENERIC;
+    toc = p_oggpacket->packet[0];
+    switch( toc&3 )
+    {
+        case 0:
+            nframes = 1;
+            break;
+        case 1:
+        case 2:
+            nframes = 2;
+            break;
+        default:
+            if( p_oggpacket->bytes < 2 )
+                return VLC_EGENERIC;
+            nframes = p_oggpacket->packet[1]&0x3F;
+            break;
+    }
+    i_rate = (int)p_stream->fmt.audio.i_rate;
+    if( toc&0x80 )
+        frame_size = (i_rate << (toc >> 3 & 3)) / 400;
+    else if( ( toc&0x60 ) == 0x60 )
+        frame_size = i_rate/(100 >> (toc >> 3 & 1));
+    else
+        frame_size = i_rate*60 / silk_fs_div[toc >> 3 & 3];
+    nsamples = nframes*frame_size;
+    if( nsamples*25 > i_rate*3 )
+        return VLC_EGENERIC;
+    return nsamples;
+}
+
 /****************************************************************************
  * Ogg_FindLogicalStreams: Find the logical streams embedded in the physical
  *                         stream and fill p_ogg.
@@ -998,6 +1119,7 @@ static int Ogg_FindLogicalStreams( demux_t *p_demux )
                              "pre-skip: %i",
                              p_stream->fmt.audio.i_channels,
                              (int)p_stream->i_pre_skip);
+                    p_stream->i_skip_frames = p_stream->i_pre_skip;
                 }
                 /* Check for Flac header (< version 1.1.1) */
                 else if( oggpacket.bytes >= 4 &&
@@ -1546,15 +1668,102 @@ static bool Ogg_IsVorbisFormatCompatible( const es_format_t *p_new, const es_for
         free( pp_old_data[i] );
     return b_match;
 }
+
+static bool Ogg_IsOpusFormatCompatible( const es_format_t *p_new,
+                                        const es_format_t *p_old )
+{
+    unsigned pi_new_size[XIPH_MAX_HEADER_COUNT];
+    void     *pp_new_data[XIPH_MAX_HEADER_COUNT];
+    unsigned i_new_count;
+    if( xiph_SplitHeaders(pi_new_size, pp_new_data, &i_new_count, p_new->i_extra, p_new->p_extra ) )
+        i_new_count = 0;
+    unsigned pi_old_size[XIPH_MAX_HEADER_COUNT];
+    void     *pp_old_data[XIPH_MAX_HEADER_COUNT];
+    unsigned i_old_count;
+    if( xiph_SplitHeaders(pi_old_size, pp_old_data, &i_old_count, p_old->i_extra, p_old->p_extra ) )
+        i_old_count = 0;
+    bool b_match = false;
+    if( i_new_count == i_old_count && i_new_count > 0 )
+    {
+        static const unsigned char default_map[2] = { 0, 1 };
+        unsigned char *p_old_head;
+        unsigned char *p_new_head;
+        const unsigned char *p_old_map;
+        const unsigned char *p_new_map;
+        int i_old_channel_count;
+        int i_new_channel_count;
+        int i_old_stream_count;
+        int i_new_stream_count;
+        int i_old_coupled_count;
+        int i_new_coupled_count;
+        p_old_head = (unsigned char *)pp_old_data[0];
+        i_old_channel_count = i_old_stream_count = i_old_coupled_count = 0;
+        p_old_map = default_map;
+        if( pi_old_size[0] >= 19 && p_old_head[8] <= 15 )
+        {
+            i_old_channel_count = p_old_head[9];
+            switch( p_old_head[18] )
+            {
+                case 0:
+                    i_old_stream_count = 1;
+                    i_old_coupled_count = i_old_channel_count - 1;
+                    break;
+                case 1:
+                    if( pi_old_size[0] >= 21U + i_old_channel_count )
+                    {
+                        i_old_stream_count = p_old_head[19];
+                        i_old_coupled_count = p_old_head[20];
+                        p_old_map = p_old_head + 21;
+                    }
+                    break;
+            }
+        }
+        p_new_head = (unsigned char *)pp_new_data[0];
+        i_new_channel_count = i_new_stream_count = i_new_coupled_count = 0;
+        p_new_map = default_map;
+        if( pi_new_size[0] >= 19 && p_new_head[8] <= 15 )
+        {
+            i_new_channel_count = p_new_head[9];
+            switch( p_new_head[18] )
+            {
+                case 0:
+                    i_new_stream_count = 1;
+                    i_new_coupled_count = i_new_channel_count - 1;
+                    break;
+                case 1:
+                    if( pi_new_size[0] >= 21U + i_new_channel_count )
+                    {
+                        i_new_stream_count = p_new_head[19];
+                        i_new_coupled_count = p_new_head[20];
+                        p_new_map = p_new_head+21;
+                    }
+                    break;
+            }
+        }
+        b_match = i_old_channel_count == i_new_channel_count &&
+                  i_old_stream_count == i_new_stream_count &&
+                  i_old_coupled_count == i_new_coupled_count &&
+                  memcmp(p_old_map, p_new_map,
+                      i_new_channel_count*sizeof(*p_new_map)) == 0;
+    }
+    for( unsigned i = 0; i < i_new_count; i++ )
+        free( pp_new_data[i] );
+    for( unsigned i = 0; i < i_old_count; i++ )
+        free( pp_old_data[i] );
+    return b_match;
+}
+
 static bool Ogg_LogicalStreamResetEsFormat( demux_t *p_demux, logical_stream_t *p_stream )
 {
     bool b_compatible = false;
     if( !p_stream->fmt_old.i_cat || !p_stream->fmt_old.i_codec )
         return true;
 
-    /* Only vorbis is supported */
+    /* Only Vorbis and Opus are supported. */
     if( p_stream->fmt.i_codec == VLC_CODEC_VORBIS )
         b_compatible = Ogg_IsVorbisFormatCompatible( &p_stream->fmt, &p_stream->fmt_old );
+    else if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+        b_compatible = Ogg_IsOpusFormatCompatible( &p_stream->fmt, &p_stream->fmt_old );
 
     if( !b_compatible )
         msg_Warn( p_demux, "cannot reuse old stream, resetting the decoder" );
@@ -1590,8 +1799,10 @@ static void Ogg_ExtractMeta( demux_t *p_demux, vlc_fourcc_t i_codec, const uint8
     case VLC_CODEC_THEORA:
         Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 1+6 );
         break;
-    case VLC_CODEC_SPEEX:
     case VLC_CODEC_OPUS:
+        Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 8 );
+        break;
+    case VLC_CODEC_SPEEX:
         Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 0 );
         break;
 
@@ -1749,7 +1960,7 @@ static void Ogg_ReadSpeexHeader( logical_stream_t *p_stream,
 }
 
 static void Ogg_ReadOpusHeader( logical_stream_t *p_stream,
-                                 ogg_packet *p_oggpacket )
+                                ogg_packet *p_oggpacket )
 {
     oggpack_buffer opb;
 
@@ -1766,7 +1977,7 @@ static void Ogg_ReadOpusHeader( logical_stream_t *p_stream,
     p_stream->f_rate = p_stream->fmt.audio.i_rate = 48000;
     p_stream->fmt.i_bitrate = 0;
 
-    /* Cheat and get additionnal info ;) */
+    /* Cheat and get additional info ;) */
     oggpack_readinit( &opb, p_oggpacket->packet, p_oggpacket->bytes);
     oggpack_adv( &opb, 64 );
     oggpack_adv( &opb, 8 ); /* version_id */
index 8c1d72b159b9d92575478e831bb5a7fedba1f0ae..809f2af918736d12cf5d75a77fc109b51c6ec840 100644 (file)
@@ -55,6 +55,7 @@ typedef struct logical_stream_s
     int              i_packets_backup;
     void             *p_headers;
     int              i_headers;
+    ogg_int64_t      i_previous_granulepos;
 
     /* program clock reference (in units of 90kHz) derived from the previous
      * granulepos */
@@ -67,6 +68,8 @@ typedef struct logical_stream_s
     int i_granule_shift;
     /* Opus has a starting offset in the headers. */
     int i_pre_skip;
+    /* Vorbis and Opus can trim the end of a stream using granule positions. */
+    int i_end_trim;
 
     /* offset of first keyframe for theora; can be 0 or 1 depending on version number */
     int64_t i_keyframe_offset;
index 567d920df32cba2cf16447ae9804f3ae96cefc05..c67f8e0168f65deb3136847bd310b0231e5bda19 100644 (file)
@@ -776,14 +776,26 @@ int oggseek_find_frame ( demux_t *p_demux, logical_stream_t *p_stream, int64_t i
 
     i_tframe += p_stream->i_keyframe_offset;
 
+    i_cframe = i_tframe;
+    /* For Opus, seek back 80 ms before the target playback position. */
+    if ( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+    {
+        if ( i_tframe <= p_stream->i_pre_skip )
+            i_cframe = 0;
+        else if ( i_tframe < 80*48 )
+            i_cframe = 0;
+        else
+            i_cframe = i_tframe - 80*48;
+    }
+
     /* reduce the search domain */
-    fidx = get_bounds_for( p_stream, i_tframe, &i_pos_lower, &i_pos_upper );
+    fidx = get_bounds_for( p_stream, i_cframe, &i_pos_lower, &i_pos_upper );
 
     if ( fidx == NULL )
     {
-        /* no exact match found; search the domain for highest keyframe <= i_tframe */
+        /* no exact match found; search the domain for highest keyframe <= i_cframe */
 
-        i_granulepos = ogg_seek ( p_demux, p_stream, i_tframe, i_pos_lower, i_pos_upper,
+        i_granulepos = ogg_seek ( p_demux, p_stream, i_cframe, i_pos_lower, i_pos_upper,
                                   &i_pagepos, true );
         if ( i_granulepos == -1 )
         {