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: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4891>
This commit is contained in:
François Laignel 2023-06-19 15:11:30 +02:00 committed by Tim-Philipp Müller
parent 8d2dc95567
commit 670ce68f06
4 changed files with 172 additions and 19 deletions

View 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;
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);
@ -13266,6 +13330,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
}
break;
}
case FOURCC_opus:
case FOURCC_lpcm:
case FOURCC_in24:
case FOURCC_in32:

View file

@ -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)
{

View 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);

View 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},