qtdemux: parse Opus and dOps as qtdemux nodes and add size checks
authorFrançois Laignel <francois@centricular.com>
Mon, 19 Jun 2023 13:11:30 +0000 (15:11 +0200)
committerTim-Philipp Müller <tim@centricular.com>
Mon, 19 Jun 2023 15:09:48 +0000 (16:09 +0100)
This allows checking the nodes conformity and dumping parsed values.

Note: Audio Sample Entry version parsing and offset handling is handled as part
of `FOURCC_soun` common processing and in `qtdemux_parse_node`.

Also, only read `stream_count` and `coupled_count` when
`channel_mapping_family` != 0. See:

https://opus-codec.org/docs/opus_in_isobmff.html#4.3.2

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4891>

subprojects/gst-plugins-good/gst/isomp4/qtdemux.c
subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c
subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.h
subprojects/gst-plugins-good/gst/isomp4/qtdemux_types.c

index c7e43eb..5137e1e 100644 (file)
@@ -8301,6 +8301,7 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer,
       case FOURCC_alac:
       case FOURCC_fLaC:
       case FOURCC_aavd:
+      case FOURCC_opus:
       {
         guint32 version;
         guint32 offset;
@@ -8312,6 +8313,8 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer,
           min_size = 20;
         else if (fourcc == FOURCC_fLaC)
           min_size = 86;
+        else if (fourcc == FOURCC_opus)
+          min_size = 55;
         else
           min_size = 40;
 
@@ -12883,34 +12886,95 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
         }
         case FOURCC_opus:
         {
-          const guint8 *dops_data;
           guint8 *channel_mapping = NULL;
-          guint32 rate;
-          guint8 channels;
+          guint32 dops_len, rate;
+          guint8 n_channels;
           guint8 channel_mapping_family;
           guint8 stream_count;
           guint8 coupled_count;
           guint8 i;
 
-          version = GST_READ_UINT16_BE (stsd_entry_data + 16);
-          if (version == 1)
-            dops_data = stsd_entry_data + 51;
-          else
-            dops_data = stsd_entry_data + 35;
-
-          channels = GST_READ_UINT8 (dops_data + 10);
-          rate = GST_READ_UINT32_BE (dops_data + 13);
-          channel_mapping_family = GST_READ_UINT8 (dops_data + 19);
-          stream_count = GST_READ_UINT8 (dops_data + 20);
-          coupled_count = GST_READ_UINT8 (dops_data + 21);
-
-          if (channels > 0) {
-            channel_mapping = g_malloc (channels * sizeof (guint8));
-            for (i = 0; i < channels; i++)
-              channel_mapping[i] = GST_READ_UINT8 (dops_data + i + 22);
+          GNode *opus;
+          GNode *dops;
+
+          opus = qtdemux_tree_get_child_by_type (stsd, FOURCC_opus);
+          if (opus == NULL) {
+            GST_WARNING_OBJECT (qtdemux, "Opus Sample Entry not found");
+            goto corrupt_file;
+          }
+
+          dops = qtdemux_tree_get_child_by_type (opus, FOURCC_dops);
+          if (dops == NULL) {
+            GST_WARNING_OBJECT (qtdemux, "Opus Specific Box not found");
+            goto corrupt_file;
+          }
+
+          /* Opus Specific Box content:
+           * 4 bytes: length
+           * 4 bytes: "dOps"
+           * 1 byte: Version;
+           * 1 byte: OutputChannelCount;
+           * 2 bytes: PreSkip (big-endians);
+           * 4 bytes: InputSampleRate (big-endians);
+           * 2 bytes: OutputGain (big-endians);
+           * 1 byte: ChannelMappingFamily;
+           * if (ChannelMappingFamily != 0) {
+           *   1 byte: StreamCount;
+           *   1 byte: CoupledCount;
+           *   for (OutputChannel in 0..OutputChannelCount) {
+           *     1 byte: ChannelMapping;
+           *   }
+           * }
+           */
+
+          dops_len = QT_UINT32 ((guint8 *) dops->data);
+          if (len < offset + dops_len) {
+            GST_WARNING_OBJECT (qtdemux,
+                "Opus Sample Entry has bogus size %" G_GUINT32_FORMAT, len);
+            goto corrupt_file;
+          }
+          if (dops_len < 19) {
+            GST_WARNING_OBJECT (qtdemux,
+                "Opus Specific Box has bogus size %" G_GUINT32_FORMAT,
+                dops_len);
+            goto corrupt_file;
+          }
+
+          n_channels = GST_READ_UINT8 ((guint8 *) dops->data + 9);
+          rate = GST_READ_UINT32_BE ((guint8 *) dops->data + 12);
+          channel_mapping_family = GST_READ_UINT8 ((guint8 *) dops->data + 18);
+
+          if (channel_mapping_family != 0) {
+            if (dops_len < 21 + n_channels) {
+              GST_WARNING_OBJECT (qtdemux,
+                  "Opus Specific Box has bogus size %" G_GUINT32_FORMAT,
+                  dops_len);
+              goto corrupt_file;
+            }
+
+            stream_count = GST_READ_UINT8 ((guint8 *) dops->data + 19);
+            coupled_count = GST_READ_UINT8 ((guint8 *) dops->data + 20);
+
+            if (n_channels > 0) {
+              channel_mapping = g_malloc (n_channels * sizeof (guint8));
+              for (i = 0; i < n_channels; i++)
+                channel_mapping[i] =
+                    GST_READ_UINT8 ((guint8 *) dops->data + i + 21);
+            }
+          } else if (n_channels == 1) {
+            stream_count = 1;
+            coupled_count = 0;
+          } else if (n_channels == 2) {
+            stream_count = 1;
+            coupled_count = 1;
+          } else {
+            GST_WARNING_OBJECT (qtdemux,
+                "Opus unexpected nb of channels %d without channel mapping",
+                n_channels);
+            goto corrupt_file;
           }
 
-          entry->caps = gst_codec_utils_opus_create_caps (rate, channels,
+          entry->caps = gst_codec_utils_opus_create_caps (rate, n_channels,
               channel_mapping_family, stream_count, coupled_count,
               channel_mapping);
           g_free (channel_mapping);
@@ -13266,6 +13330,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
             }
             break;
           }
+          case FOURCC_opus:
           case FOURCC_lpcm:
           case FOURCC_in24:
           case FOURCC_in32:
index 45296c2..22da35e 100644 (file)
@@ -1054,6 +1054,88 @@ qtdemux_dump_fLaC (GstQTDemux * qtdemux, GstByteReader * data, int depth)
 }
 
 gboolean
+qtdemux_dump_opus (GstQTDemux * qtdemux, GstByteReader * data, int depth)
+{
+  guint16 version, data_ref_id, n_channels, sample_size;
+  guint32 sample_rate;
+
+  if (!gst_byte_reader_skip (data, 6) ||
+      !gst_byte_reader_get_uint16_be (data, &data_ref_id) ||
+      !gst_byte_reader_get_uint16_be (data, &version) ||
+      !gst_byte_reader_skip (data, 6) ||
+      !gst_byte_reader_get_uint16_be (data, &n_channels) ||
+      !gst_byte_reader_get_uint16_be (data, &sample_size) ||
+      !gst_byte_reader_skip (data, 4) ||
+      !gst_byte_reader_get_uint32_be (data, &sample_rate))
+    return FALSE;
+
+  GST_LOG ("%*s  data reference: %d", depth, "", data_ref_id);
+  GST_LOG ("%*s  version:        %d", depth, "", version);
+  GST_LOG ("%*s  channel count:  %d", depth, "", n_channels);
+  GST_LOG ("%*s  sample size:    %d", depth, "", sample_size);
+  GST_LOG ("%*s  sample rate:    %d", depth, "", sample_rate >> 16);
+
+  return TRUE;
+}
+
+gboolean
+qtdemux_dump_dops (GstQTDemux * qtdemux, GstByteReader * data, int depth)
+{
+  guint8 version, n_channels, channel_mapping_family;
+  guint8 stream_count = 1, coupled_count = 0, i = 0;
+  guint8 *channel_mapping = NULL;
+  guint16 pre_skip, output_gain;
+  guint32 sample_rate;
+
+  if (!gst_byte_reader_get_uint8 (data, &version) ||
+      !gst_byte_reader_get_uint8 (data, &n_channels) ||
+      !gst_byte_reader_get_uint16_be (data, &pre_skip) ||
+      !gst_byte_reader_get_uint32_be (data, &sample_rate) ||
+      !gst_byte_reader_get_uint16_be (data, &output_gain) ||
+      !gst_byte_reader_get_uint8 (data, &channel_mapping_family))
+    return FALSE;
+
+  if (channel_mapping_family != 0) {
+    if (!gst_byte_reader_get_uint8 (data, &stream_count) ||
+        !gst_byte_reader_get_uint8 (data, &coupled_count))
+      return FALSE;
+
+    if (n_channels > 0) {
+      channel_mapping = g_malloc (n_channels * sizeof (guint8));
+
+      for (i = 0; i < n_channels; i++)
+        if (!gst_byte_reader_get_uint8 (data, &channel_mapping[i])) {
+          g_free (channel_mapping);
+          return FALSE;
+        }
+    }
+  }
+
+  GST_LOG ("%*s  version:                %d", depth, "", version);
+  GST_LOG ("%*s  channel count:          %d", depth, "", n_channels);
+  GST_LOG ("%*s  pre skip:               %d", depth, "", pre_skip);
+  GST_LOG ("%*s  sample rate:            %d", depth, "", sample_rate);
+  GST_LOG ("%*s  output gain:            %d", depth, "", output_gain);
+  GST_LOG ("%*s  channel mapping family: %d", depth, "",
+      channel_mapping_family);
+
+  if (channel_mapping_family != 0) {
+    GST_LOG ("%*s  stream count:           %d", depth, "", stream_count);
+    GST_LOG ("%*s  coupled count:          %d", depth, "", coupled_count);
+
+    if (n_channels > 0) {
+      for (i = 0; i < n_channels; i++)
+        GST_LOG ("%*s  channel mapping: %d -> %d", depth, "", i,
+            channel_mapping[i]);
+
+      g_free (channel_mapping);
+    }
+  }
+
+  return TRUE;
+}
+
+gboolean
 qtdemux_dump_gmin (GstQTDemux * qtdemux, GstByteReader * data, int depth)
 {
   guint32 ver_flags;
index 45dcd3f..de58fd4 100644 (file)
@@ -89,6 +89,10 @@ gboolean qtdemux_dump_dfLa (GstQTDemux * qtdemux, GstByteReader * data,
     int depth);
 gboolean qtdemux_dump_fLaC (GstQTDemux * qtdemux, GstByteReader * data,
     int depth);
+gboolean qtdemux_dump_opus (GstQTDemux * qtdemux, GstByteReader * data,
+    int depth);
+gboolean qtdemux_dump_dops (GstQTDemux * qtdemux, GstByteReader * data,
+    int depth);
 gboolean qtdemux_dump_gmin (GstQTDemux * qtdemux, GstByteReader * data,
     int depth);
 
index 5e6d735..9c73224 100644 (file)
@@ -97,6 +97,8 @@ static const QtNodeType qt_node_types[] = {
   {FOURCC_alac, "alac", 0,},
   {FOURCC_fLaC, "fLaC", 0, qtdemux_dump_fLaC},
   {FOURCC_dfLa, "dfLa", 0, qtdemux_dump_dfLa},
+  {FOURCC_opus, "opus", 0, qtdemux_dump_opus},
+  {FOURCC_dops, "dOps", 0, qtdemux_dump_dops},
   {FOURCC_wave, "wave", QT_FLAG_CONTAINER},
   {FOURCC_appl, "appl", QT_FLAG_CONTAINER},
   {FOURCC_cfhd, "cfhd", QT_FLAG_CONTAINER},