From fa30504ec2bb0615b6537fc65387f7cf7809c4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Mon, 19 Jun 2023 15:11:30 +0200 Subject: [PATCH] qtdemux: parse Opus and dOps as qtdemux nodes and add size checks 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: --- .../gst-plugins-good/gst/isomp4/qtdemux.c | 103 ++++++++++++++---- .../gst/isomp4/qtdemux_dump.c | 82 ++++++++++++++ .../gst/isomp4/qtdemux_dump.h | 4 + .../gst/isomp4/qtdemux_types.c | 2 + 4 files changed, 172 insertions(+), 19 deletions(-) diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c index d671264312..7879d0cb70 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux.c @@ -8862,6 +8862,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; @@ -8873,6 +8874,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; @@ -13444,34 +13447,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; + GNode *opus; + GNode *dops; - 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); + 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; } - entry->caps = gst_codec_utils_opus_create_caps (rate, channels, + 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, n_channels, channel_mapping_family, stream_count, coupled_count, channel_mapping); g_free (channel_mapping); @@ -13827,6 +13891,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } break; } + case FOURCC_opus: case FOURCC_lpcm: case FOURCC_in24: case FOURCC_in32: diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c index 45296c266e..22da35e9e7 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.c @@ -1053,6 +1053,88 @@ qtdemux_dump_fLaC (GstQTDemux * qtdemux, GstByteReader * data, int depth) return TRUE; } +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) { diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.h b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.h index 45dcd3f081..de58fd48fb 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.h +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_dump.h @@ -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); diff --git a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_types.c b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_types.c index cf8502f4f9..3222524f80 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/qtdemux_types.c +++ b/subprojects/gst-plugins-good/gst/isomp4/qtdemux_types.c @@ -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},