[vlc-devel] [PATCH 09/11] demux: wav: more robust chunk parsing

Thomas Guillem thomas at gllm.fr
Thu Mar 12 14:56:31 CET 2020


>From the spec:
"There are no restrictions upon the order of the chunks within a WAVE file,
with the exception that the Format chunk must precede the Data chunk."

The Wav demuxer can now parse chunks in any order. I don't check that the fmt
chunk is before the data one in order to be more resilient to broken samples.

This commit will allow to add new chunks more easily, specially chunks that can
be after the 'data' one. I'm thinking about the ADM support, that need 2
chunks, 'chna' and 'axml' that is after the 'data' chunk.
---
 modules/demux/wav.c | 154 +++++++++++++++++++++++++++++++-------------
 1 file changed, 108 insertions(+), 46 deletions(-)

diff --git a/modules/demux/wav.c b/modules/demux/wav.c
index 1349baa2555..12c39688b39 100644
--- a/modules/demux/wav.c
+++ b/modules/demux/wav.c
@@ -59,6 +59,31 @@ typedef struct
     uint8_t pi_chan_table[AOUT_CHAN_MAX];
 } demux_sys_t;
 
+enum wav_chunk_id {
+    wav_chunk_id_data,
+    wav_chunk_id_ds64,
+    wav_chunk_id_fmt,
+};
+
+static const struct wav_chunk_id_key
+{
+    enum wav_chunk_id id;
+    char key[5];
+} wav_chunk_id_key_list[] =  {
+    /* Alphabetical order */
+    { wav_chunk_id_data, "data" },
+    { wav_chunk_id_ds64, "ds64" },
+    { wav_chunk_id_fmt,  "fmt " },
+};
+static const size_t wav_chunk_id_key_count = ARRAY_SIZE(wav_chunk_id_key_list);
+
+static int
+wav_chunk_CompareCb(const void *a, const void *b)
+{
+    const struct wav_chunk_id_key *id = b;
+    return memcmp(a, id->key, 4);
+}
+
 static int Demux( demux_t *p_demux )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
@@ -127,37 +152,42 @@ static int ChunkSkip( demux_t *p_demux, size_t i_size )
     return VLC_SUCCESS;
 }
 
-static int ChunkFind( demux_t *p_demux, const char *fcc, uint32_t *pi_size )
+static int ChunkGetNext( demux_t *p_demux, enum wav_chunk_id *p_id,
+                         uint32_t *pi_size )
 {
-    const uint8_t *p_peek;
+#ifndef NDEBUG
+    /* assert that keys are in alphabetical order */
+    for( size_t i = 0; i < wav_chunk_id_key_count - 1; ++i )
+        assert( strcmp( wav_chunk_id_key_list[i].key,
+                        wav_chunk_id_key_list[i + 1].key ) < 0 );
+#endif
 
     for( ;; )
     {
-        uint32_t i_size;
-
+        const uint8_t *p_peek;
         if( vlc_stream_Peek( p_demux->s, &p_peek, 8 ) < 8 )
-        {
-            msg_Err( p_demux, "cannot peek" );
             return VLC_EGENERIC;
-        }
 
-        i_size = GetDWLE( p_peek + 4 );
+        const struct wav_chunk_id_key *id =
+            bsearch( p_peek, wav_chunk_id_key_list, wav_chunk_id_key_count,
+                     sizeof(*wav_chunk_id_key_list), wav_chunk_CompareCb );
+        uint32_t i_size = GetDWLE( p_peek + 4 );
 
-        msg_Dbg( p_demux, "chunk: fcc=`%4.4s` size=%"PRIu32, p_peek, i_size );
-
-        if( !memcmp( p_peek, fcc, 4 ) )
+        if( id == NULL )
         {
-            if( pi_size )
-            {
-                *pi_size = i_size;
-            }
-            if( vlc_stream_Read( p_demux->s, NULL, 8 ) != 8 )
-                return VLC_EGENERIC;
-            return VLC_SUCCESS;
+            msg_Warn( p_demux, "unknown chunk '%4.4s' of size: %u",
+                      p_peek, i_size );
+            ChunkSkip( p_demux, i_size + 8 );
+            continue;
         }
 
-        if( ChunkSkip( p_demux, i_size + 8 ) != VLC_SUCCESS )
+        if( vlc_stream_Read( p_demux->s, NULL, 8 ) != 8 )
             return VLC_EGENERIC;
+
+        *p_id = id->id;
+        *pi_size = i_size;
+
+        return VLC_SUCCESS;
     }
 }
 
@@ -550,7 +580,7 @@ static int Open( vlc_object_t * p_this )
 
     es_format_Init( &p_sys->fmt, AUDIO_ES, 0 );
     p_sys->p_es           = NULL;
-    p_sys->i_data_size    = 0;
+    p_sys->i_data_pos = p_sys->i_data_size = 0;
     p_sys->i_chans_to_reorder = 0;
     p_sys->i_channel_mask = 0;
 
@@ -558,42 +588,75 @@ static int Open( vlc_object_t * p_this )
     if( vlc_stream_Read( p_demux->s, NULL, 12 ) != 12 )
         goto error;
 
-    if( b_is_rf64 )
+    bool eof = false;
+    enum wav_chunk_id id;
+    while( !eof && ( ChunkGetNext( p_demux, &id, &i_size ) ) == VLC_SUCCESS )
     {
-        /* search datasize64 chunk */
-        if( ChunkFind( p_demux, "ds64", &i_size ) )
+        if( i_size == 0 )
         {
-            msg_Err( p_demux, "cannot find 'ds64' chunk" );
+            msg_Err( p_demux, "invalid chunk with a size 0");
             goto error;
         }
-        if( ChunkParseDS64( p_demux, i_size ) != VLC_SUCCESS )
-            goto error;
-    }
 
-    /* search fmt chunk */
-    if( ChunkFind( p_demux, "fmt ", &i_size ) )
-    {
-        msg_Err( p_demux, "cannot find 'fmt ' chunk" );
-        goto error;
+        switch( id )
+        {
+            case wav_chunk_id_data:
+            {
+                int64_t i_stream_size = stream_Size( p_demux->s );
+                p_sys->i_data_pos = vlc_stream_Tell( p_demux->s );
+
+                if( !b_is_rf64 || i_size < UINT32_MAX )
+                {
+                    if( i_stream_size > 0
+                     && (uint64_t) i_stream_size >= i_size + p_sys->i_data_pos )
+                        p_sys->i_data_size = i_size;
+                }
+                if( likely( p_sys->i_data_pos + i_size == (uint64_t) i_stream_size ) )
+                {
+                    /* Bypass the final ChunkGetNext() to avoid a read+seek
+                     * since this chunk is the last one */
+                    eof = true;
+                }
+                else
+                {
+                    /* Unlikely case where there is a chunk after 'data' */
+                    ChunkSkip( p_demux, i_size );
+                }
+                break;
+            }
+            case wav_chunk_id_ds64:
+                if( b_is_rf64 )
+                {
+                    if( ChunkParseDS64( p_demux, i_size ) != VLC_SUCCESS )
+                        goto error;
+                }
+                else
+                {
+                    msg_Err( p_demux, "'ds64' chunk found but format not RF64" );
+                    goto error;
+                }
+                break;
+            case wav_chunk_id_fmt:
+                if( ChunkParseFmt( p_demux, i_size ) != VLC_SUCCESS )
+                    goto error;
+                break;
+        }
     }
-    if( ChunkParseFmt( p_demux, i_size ) != VLC_SUCCESS )
-        goto error;
 
-    if( ChunkFind( p_demux, "data", &i_size ) )
+    if( p_sys->i_data_pos == 0 || p_sys->i_data_size == 0
+     || p_sys->i_frame_samples <= 0 )
     {
-        msg_Err( p_demux, "cannot find 'data' chunk" );
+        msg_Err( p_demux, "'%s' chunk not found",
+                 p_sys->i_data_pos == 0 ? "data" :
+                 p_sys->i_frame_samples <= 0 ? "fmt " :
+                 b_is_rf64 ? "ds64" : "data" );
         goto error;
     }
 
-    p_sys->i_data_pos = vlc_stream_Tell( p_demux->s );
-
-    if( !b_is_rf64 || i_size < UINT32_MAX )
-    {
-        int64_t i_stream_size = stream_Size( p_demux->s );
-        if( i_stream_size > 0
-         && (uint64_t) i_stream_size >= i_size + p_sys->i_data_pos )
-            p_sys->i_data_size = i_size;
-    }
+    /* Seek back to data position if needed */
+    if( unlikely( vlc_stream_Tell( p_demux->s ) != p_sys->i_data_pos )
+     && vlc_stream_Seek( p_demux->s, p_sys->i_data_pos ) != VLC_SUCCESS )
+        goto error;
 
     if( p_sys->fmt.i_bitrate <= 0 )
     {
@@ -612,7 +675,6 @@ static int Open( vlc_object_t * p_this )
     return VLC_SUCCESS;
 
 error:
-    msg_Err( p_demux, "An error occurred during wav demuxing" );
     es_format_Clean( &p_sys->fmt );
     free( p_sys );
     return VLC_EGENERIC;
-- 
2.20.1



More information about the vlc-devel mailing list