[vlc-commits] demux: es: use MLLT tables for accurate MP3 seeking

Francois Cartegnie git at videolan.org
Tue Aug 23 05:11:47 CEST 2016


vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | Wed Aug 17 23:50:46 2016 +0200| [ea5c3ceabed8760964141b90de623045fda69634] | committer: Francois Cartegnie

demux: es: use MLLT tables for accurate MP3 seeking

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=ea5c3ceabed8760964141b90de623045fda69634
---

 modules/demux/mpeg/es.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 143 insertions(+)

diff --git a/modules/demux/mpeg/es.c b/modules/demux/mpeg/es.c
index 182dd7f..b82e610 100644
--- a/modules/demux/mpeg/es.c
+++ b/modules/demux/mpeg/es.c
@@ -99,6 +99,25 @@ typedef struct
     float pf_replay_peak[AUDIO_REPLAY_GAIN_MAX];
 } lame_extra_t;
 
+typedef struct
+{
+    mtime_t i_time;
+    uint64_t i_pos;
+    bs_t br;
+} sync_table_ctx_t;
+
+typedef struct
+{
+    uint16_t i_frames_btw_refs;
+    uint32_t i_bytes_btw_refs;
+    uint32_t i_ms_btw_refs;
+    uint8_t i_bits_per_bytes_dev;
+    uint8_t i_bits_per_ms_dev;
+    uint8_t *p_bits;
+    size_t i_bits;
+    sync_table_ctx_t current;
+} sync_table_t;
+
 struct demux_sys_t
 {
     codec_t codec;
@@ -135,6 +154,8 @@ struct demux_sys_t
         lame_extra_t lame;
         bool b_lame;
     } xing;
+
+    sync_table_t mllt;
 };
 
 static int MpgaProbe( demux_t *p_demux, int64_t *pi_offset );
@@ -155,6 +176,7 @@ static int ThdProbe( demux_t *p_demux, int64_t *pi_offset );
 static int MlpInit( demux_t *p_demux );
 
 static bool Parse( demux_t *p_demux, block_t **pp_output );
+static uint64_t SeekByMlltTable( demux_t *p_demux, mtime_t *pi_time );
 
 static const codec_t p_codecs[] = {
     { VLC_CODEC_MP4A, false, "mp4 audio",  AacProbe,  AacInit },
@@ -350,6 +372,8 @@ static void Close( vlc_object_t * p_this )
 
     if( p_sys->p_packetized_data )
         block_ChainRelease( p_sys->p_packetized_data );
+    if( p_sys->mllt.p_bits )
+        free( p_sys->mllt.p_bits );
     demux_PacketizerDestroy( p_sys->p_packetizer );
     free( p_sys );
 }
@@ -407,8 +431,24 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
         }
 
         case DEMUX_SET_TIME:
+        {
+            if( p_sys->mllt.p_bits )
+            {
+                int64_t i_time = va_arg(args, int64_t);
+                uint64_t i_pos = SeekByMlltTable( p_demux, &i_time );
+                int i_ret = vlc_stream_Seek( p_demux->s, p_sys->i_stream_offset + i_pos );
+                if( i_ret != VLC_SUCCESS )
+			return i_ret;
+                p_sys->i_time_offset = i_time - p_sys->i_pts;
+                /* And reset buffered data */
+                if( p_sys->p_packetized_data )
+                    block_ChainRelease( p_sys->p_packetized_data );
+                p_sys->p_packetized_data = NULL;
+                return VLC_SUCCESS;
+            }
             /* FIXME TODO: implement a high precision seek (with mp3 parsing)
              * needed for multi-input */
+        }
         default:
             i_ret = demux_vaControlHelper( p_demux->s, p_sys->i_stream_offset, -1,
                                             p_sys->i_bitrate_avg, 1, i_query,
@@ -814,6 +854,107 @@ static double MpgaXingLameConvertPeak( uint32_t x )
     return x / 8388608.0; /* pow(2, 23) */
 }
 
+static uint32_t ID3ReadSize( const uint8_t *p_buffer, bool b_syncsafe )
+{
+    if( !b_syncsafe )
+        return GetDWBE( p_buffer );
+    return ( (uint32_t)p_buffer[3] & 0x7F ) |
+            (( (uint32_t)p_buffer[2] & 0x7F ) << 7) |
+            (( (uint32_t)p_buffer[1] & 0x7F ) << 14) |
+            (( (uint32_t)p_buffer[0] & 0x7F ) << 21);
+}
+
+static bool ID3IsTag( const uint8_t *p_buffer )
+{
+    return( memcmp(p_buffer, "ID3", 3) == 0 &&
+            p_buffer[3] < 0xFF &&
+            p_buffer[4] < 0xFF &&
+           ((GetDWBE(&p_buffer[6]) & 0x80808080) == 0) );
+}
+
+static uint64_t SeekByMlltTable( demux_t *p_demux, mtime_t *pi_time )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    sync_table_ctx_t *p_cur = &p_sys->mllt.current;
+
+    /* reset or init context */
+    if( *pi_time < p_cur->i_time || !p_cur->br.p )
+    {
+        p_cur->i_time = 0;
+        p_cur->i_pos = 0;
+        bs_init(&p_cur->br, p_sys->mllt.p_bits, p_sys->mllt.i_bits);
+    }
+
+    while(bs_remain(&p_cur->br) >= p_sys->mllt.i_bits_per_bytes_dev + p_sys->mllt.i_bits_per_ms_dev)
+    {
+        const uint32_t i_bytesdev = bs_read(&p_cur->br, p_sys->mllt.i_bits_per_bytes_dev);
+        const uint32_t i_msdev = bs_read(&p_cur->br, p_sys->mllt.i_bits_per_ms_dev);
+        const mtime_t i_deltatime = (p_sys->mllt.i_ms_btw_refs + i_msdev) * INT64_C(1000);
+        if( p_cur->i_time + i_deltatime > *pi_time )
+            break;
+        p_cur->i_time += i_deltatime;
+        p_cur->i_pos += p_sys->mllt.i_bytes_btw_refs + i_bytesdev;
+    }
+    *pi_time = p_cur->i_time;
+    return p_cur->i_pos;
+}
+
+static int ID3Parse( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    const uint8_t *p_peek;
+
+    bool b_canseek;
+    if( p_sys->i_stream_offset < 10 ||
+        vlc_stream_Control( p_demux->s, STREAM_CAN_SEEK, &b_canseek ) != VLC_SUCCESS ||
+        !b_canseek ||
+        vlc_stream_Seek( p_demux->s, 0 ) != VLC_SUCCESS )
+        return VLC_EGENERIC;
+
+    if( vlc_stream_Peek( p_demux->s, &p_peek, p_sys->i_stream_offset ) == p_sys->i_stream_offset &&
+        ID3IsTag( p_peek ) )
+    {
+        const bool b_syncsafe = p_peek[5] & 0x80;
+        uint32_t i_peek = ID3ReadSize( &p_peek[6], true );
+        if( i_peek > p_sys->i_stream_offset - 10 )
+            return VLC_EGENERIC;
+        const uint8_t *p_frame = &p_peek[10];
+        while( i_peek > 10 )
+        {
+            uint32_t i_framesize = ID3ReadSize( &p_frame[4], b_syncsafe ) + 10;
+            if( i_framesize > i_peek )
+                return VLC_EGENERIC;
+            if( !memcmp(p_frame, "MLLT", 4) && i_framesize > 20 )
+            {
+                const uint8_t *p_payload = &p_frame[10];
+                p_sys->mllt.i_frames_btw_refs = GetWBE(p_payload);
+                p_sys->mllt.i_bytes_btw_refs = GetDWBE(&p_payload[1]) & 0x00FFFFFF;
+                p_sys->mllt.i_ms_btw_refs = GetDWBE(&p_payload[4]) & 0x00FFFFFF;
+                if( !p_sys->mllt.i_frames_btw_refs || !p_sys->mllt.i_bytes_btw_refs ||
+                    !p_sys->mllt.i_ms_btw_refs ||
+                    p_payload[8] > 31 || p_payload[9] > 31 || /* bits length sanity check */
+                    ((p_payload[8] + p_payload[9]) % 4) || p_payload[8] + p_payload[9] < 4 )
+                    return VLC_EGENERIC;
+                p_sys->mllt.i_bits_per_bytes_dev = p_payload[8];
+                p_sys->mllt.i_bits_per_ms_dev = p_payload[9];
+                p_sys->mllt.p_bits = malloc(i_framesize - 20);
+                if( likely(p_sys->mllt.p_bits) )
+                {
+                    p_sys->mllt.i_bits = i_framesize - 20;
+                    memcpy(p_sys->mllt.p_bits, &p_frame[20], p_sys->mllt.i_bits);
+                    msg_Dbg(p_demux, "read MLLT sync table with %zu entries",
+                            (p_sys->mllt.i_bits * 8) / (p_sys->mllt.i_bits_per_bytes_dev + p_sys->mllt.i_bits_per_ms_dev) );
+                }
+                break;
+            }
+            p_frame += i_framesize;
+            i_peek -= i_framesize;
+        }
+    }
+
+    return vlc_stream_Seek( p_demux->s, p_sys->i_stream_offset );
+}
+
 static int MpgaInit( demux_t *p_demux )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
@@ -824,6 +965,8 @@ static int MpgaInit( demux_t *p_demux )
     /* */
     p_sys->i_packet_size = 1024;
 
+    ID3Parse( p_demux );
+
     /* Load a potential xing header */
     i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 4 + 1024 );
     if( i_peek < 4 + 21 )



More information about the vlc-commits mailing list