From af5cce996855f4d056e0975221e7db4c1752818b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= <sebastian@centricular.com>
Date: Sun, 4 May 2025 16:15:32 +0300
Subject: [PATCH] qtdemux: Parse uncompressed video uncC / cmpd boxes from
 already parsed stsd entry

Also simplifies code and error checking considerably.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8929>
---
 .../gst-plugins-good/gst/isomp4/qtdemux.c     | 140 ++++++------------
 1 file changed, 43 insertions(+), 97 deletions(-)

diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c
index f0bd18fff7..531e635356 100644
--- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c
+++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c
@@ -355,7 +355,7 @@ static void gst_qtdemux_handle_esds (GstQTDemux * qtdemux,
     GstTagList * list);
 static GstCaps *qtdemux_video_caps (GstQTDemux * qtdemux,
     QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc,
-    const guint8 * stsd_entry_data, gchar ** codec_name);
+    GNode * stsd_entry, gchar ** codec_name);
 static GstCaps *qtdemux_audio_caps (GstQTDemux * qtdemux,
     QtDemuxStream * stream, QtDemuxStreamStsdEntry * entry, guint32 fourcc,
     const guint8 * data, int len, gchar ** codec_name);
@@ -12059,7 +12059,7 @@ qtdemux_clear_cmpd (ComponentDefinitionBox * cmpd)
 
 static gboolean
 qtdemux_parse_cmpd (GstQTDemux * qtdemux, GstByteReader * reader,
-    ComponentDefinitionBox * cmpd, guint32 size)
+    ComponentDefinitionBox * cmpd)
 {
   /* There should be enough to parse the component_count (4) */
   if (gst_byte_reader_get_remaining (reader) < 4) {
@@ -12070,7 +12070,7 @@ qtdemux_parse_cmpd (GstQTDemux * qtdemux, GstByteReader * reader,
   cmpd->component_count = gst_byte_reader_get_uint32_be_unchecked (reader);
 
   guint32 minimum_size = cmpd->component_count * 2 + 4; // assuming type_uris are not used
-  if (size < minimum_size) {
+  if (gst_byte_reader_get_size (reader) < minimum_size) {
     GST_ERROR_OBJECT (qtdemux, "cmpd size is too short");
     goto error;
   }
@@ -12102,7 +12102,7 @@ error:
 
 static gboolean
 qtdemux_parse_uncC (GstQTDemux * qtdemux, GstByteReader * reader,
-    UncompressedFrameConfigBox * uncC, guint32 size)
+    UncompressedFrameConfigBox * uncC)
 {
   /* There should be enough to parse the version/flags (4) & profile (4) */
   if (gst_byte_reader_get_remaining (reader) < 8) {
@@ -12127,13 +12127,13 @@ qtdemux_parse_uncC (GstQTDemux * qtdemux, GstByteReader * reader,
     goto error;
   }
 
-  guint32 expected_size = uncC->component_count * 5 + 44;
-  if (size != expected_size) {
+  guint32 expected_size = uncC->component_count * 5 + 36;
+  if (gst_byte_reader_get_size (reader) != expected_size) {
     GST_ERROR_OBJECT (qtdemux, "uncC size is incorrect");
     goto error;
   }
 
-  guint32 expected_remaining = size - 20;       // 20 = box_size(4) + uncC(4) + version/flags(4) + profile(4) + component_count(4)
+  guint32 expected_remaining = uncC->component_count * 5 + 24;
   if (gst_byte_reader_get_remaining (reader) < expected_remaining) {
     GST_ERROR_OBJECT (qtdemux, "uncC is too short");
     goto error;
@@ -14884,7 +14884,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak, guint32 * mvhd_matrix)
         gst_caps_unref (entry->caps);
 
       entry->caps =
-          qtdemux_video_caps (qtdemux, stream, entry, fourcc, stsd_entry_data,
+          qtdemux_video_caps (qtdemux, stream, entry, fourcc, stsd_entry,
           &codec);
       if (G_UNLIKELY (!entry->caps)) {
         g_free (palette_data);
@@ -18347,7 +18347,7 @@ _get_unknown_codec_name (const gchar * type, guint32 fourcc)
 static GstCaps *
 qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
     QtDemuxStreamStsdEntry * entry, guint32 fourcc,
-    const guint8 * stsd_entry_data, gchar ** codec_name)
+    GNode * stsd_entry, gchar ** codec_name)
 {
   GstCaps *caps = NULL;
   GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
@@ -18403,7 +18403,8 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
     {
       guint16 bps;
 
-      bps = QT_UINT16 (stsd_entry_data + 82);
+      // Read VisualSampleEntry depth. Size is checked by the caller already.
+      bps = QT_UINT16 ((const guint8 *) stsd_entry->data + 82);
       switch (bps) {
         case 15:
           format = GST_VIDEO_FORMAT_RGB15;
@@ -18866,101 +18867,46 @@ qtdemux_video_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
     }
     case FOURCC_uncv:
     {
-      const guint8 ENTRY_MINIMUM_SIZE = 86;     // video sample description minimum size in bytes
-      const guint8 *first_child = stsd_entry_data + ENTRY_MINIMUM_SIZE;
-      const guint32 UNCV_SIZE = QT_UINT32 (stsd_entry_data);
-      const guint32 UNCV_CHILDREN_SIZE = UNCV_SIZE - ENTRY_MINIMUM_SIZE;
-      gboolean found_cmpd = FALSE;
-      gboolean found_uncC = FALSE;
-      gboolean success = TRUE;
+      GNode *uncC_node, *cmpd_node;
+
       GstByteReader reader;
       UncompressedFrameConfigBox uncC = { 0 };
       ComponentDefinitionBox cmpd = { 0 };
 
-      if (UNCV_SIZE < ENTRY_MINIMUM_SIZE) {
-        GST_WARNING_OBJECT (qtdemux, "visual sample entry 'uncv' is too small");
-        return NULL;
-      }
-
-      gst_byte_reader_init (&reader, first_child, UNCV_CHILDREN_SIZE);
-
-      // Parse each uncv child
-      while (gst_byte_reader_get_remaining (&reader)) {
-        guint32 child_size = 0; // Includes size (4), fourcc (4), & data
-        guint32 child_data_size = 0;    // The size in bytes of the data, not including the size (4) and fourcc (4)
-        guint32 child_fourcc = 0;
-
-        if (gst_byte_reader_get_remaining (&reader) < 8) {
-          GST_WARNING_OBJECT (qtdemux, "uncv child box is too small");
-          return NULL;
-        }
-
-        // Parse Child Header
-        child_size = gst_byte_reader_get_uint32_be_unchecked (&reader);
-        child_fourcc = gst_byte_reader_get_uint32_le_unchecked (&reader);
-        child_data_size = child_size - 8;
-
-        if (child_data_size > gst_byte_reader_get_remaining (&reader)) {
-          GST_WARNING_OBJECT (qtdemux, "uncv child box is too big");
-          return NULL;
-        }
-
-        switch (child_fourcc) {
-          case FOURCC_uncC:
-          {
-            if (found_uncC) {
-              success = FALSE;
-              GST_WARNING_OBJECT (qtdemux, "Found multiple uncC boxes in uncv");
-            } else {
-              found_uncC = TRUE;
-              success =
-                  qtdemux_parse_uncC (qtdemux, &reader, &uncC, child_size);
-            }
-            break;
-          }
-          case FOURCC_cmpd:
-          {
-            if (found_cmpd) {
-              success = FALSE;
-              GST_WARNING_OBJECT (qtdemux, "Found multiple cmpd boxes in uncv");
-            } else {
-              found_cmpd = TRUE;
-              success =
-                  qtdemux_parse_cmpd (qtdemux, &reader, &cmpd, child_size);
-            }
-            break;
-          }
-          default:
-          {
-            GST_LOG_OBJECT (qtdemux,
-                "Ignoring unknown uncv child: %" GST_FOURCC_FORMAT,
-                GST_FOURCC_ARGS (child_fourcc));
-            gst_byte_reader_skip_unchecked (&reader, child_data_size);  // Skip unknown child box
-          }
-        }
-        if (!success) {
-          GST_WARNING_OBJECT (qtdemux, "Failed to parse uncv");
-          break;
-        }
-      }
-
-      /* Successfully Parsed uncv? */
-      if (!found_uncC) {
+      uncC_node =
+          qtdemux_tree_get_child_by_type_full (stsd_entry, FOURCC_uncC,
+          &reader);
+      if (!uncC_node) {
         GST_WARNING_OBJECT (qtdemux,
             "Expected to find uncC box when parsing uncv");
-      } else if (uncC.version == 0 && !found_cmpd) {
-        GST_WARNING_OBJECT (qtdemux,
-            "Expected to find cmpd box when uncC version is 0");
-      } else if (!success) {
-        GST_WARNING_OBJECT (qtdemux, "Error when parsing uncv box");
-      } else {
-        format = qtdemux_get_format_from_uncv (qtdemux, &uncC, &cmpd);
-        gst_video_info_set_format (&stream->pre_info, format, entry->width,
-            entry->height);
-        qtdemux_set_info_from_uncv (qtdemux, entry, &uncC, &stream->pre_info);
-        stream->alignment = 32;
+        break;
       }
 
+      if (!qtdemux_parse_uncC (qtdemux, &reader, &uncC)) {
+        GST_WARNING_OBJECT (qtdemux, "Failed parsing uncC box");
+        break;
+      }
+
+      cmpd_node =
+          qtdemux_tree_get_child_by_type_full (stsd_entry, FOURCC_cmpd,
+          &reader);
+      if (!cmpd_node) {
+        GST_WARNING_OBJECT (qtdemux,
+            "Expected to find cmpd box when parsing uncv");
+        break;
+      }
+
+      if (!qtdemux_parse_cmpd (qtdemux, &reader, &cmpd)) {
+        GST_WARNING_OBJECT (qtdemux, "Failed parsing cmpd box");
+        break;
+      }
+
+      format = qtdemux_get_format_from_uncv (qtdemux, &uncC, &cmpd);
+      gst_video_info_set_format (&stream->pre_info, format, entry->width,
+          entry->height);
+      qtdemux_set_info_from_uncv (qtdemux, entry, &uncC, &stream->pre_info);
+      stream->alignment = 32;
+
       /* Free Memory */
       qtdemux_clear_uncC (&uncC);
       qtdemux_clear_cmpd (&cmpd);