mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 09:10:36 +00:00
qtdemux: move stco, stts, stss and stps atom parsing over to GstByteReader
Make sure we don't read beyond the atom boundary. Note that the code behaves slightly differently in the corner case where there is not enough atom data for the specified number of samples (n_samples_time) in the atom, but still enough data to fill the pre-allocated index of n_samples entries: before we would just stop parsing the stts data and continue, whereas now we will likely error out. This should not be a problem in practice though. We could maintain the old behaviour by doing reads with a size check inside the loop if needed.
This commit is contained in:
parent
4be46b1586
commit
c8c9b0f35d
1 changed files with 155 additions and 121 deletions
|
@ -3497,24 +3497,20 @@ static gboolean
|
||||||
qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
GNode * stbl)
|
GNode * stbl)
|
||||||
{
|
{
|
||||||
|
QtAtomParser co_reader;
|
||||||
QtAtomParser stsz;
|
QtAtomParser stsz;
|
||||||
QtAtomParser stsc;
|
QtAtomParser stsc;
|
||||||
GNode *stco;
|
QtAtomParser stts;
|
||||||
GNode *co64;
|
|
||||||
GNode *stts;
|
|
||||||
GNode *stss;
|
|
||||||
GNode *stps;
|
|
||||||
GNode *ctts;
|
GNode *ctts;
|
||||||
const guint8 *stco_data, *co64_data, *stts_data;
|
|
||||||
guint32 sample_size;
|
guint32 sample_size;
|
||||||
guint32 n_samples;
|
guint32 n_samples;
|
||||||
guint32 n_samples_per_chunk;
|
guint32 n_samples_per_chunk;
|
||||||
int sample_index;
|
int sample_index;
|
||||||
int n_sample_times;
|
|
||||||
QtDemuxSample *samples;
|
QtDemuxSample *samples;
|
||||||
gint i, j, k;
|
gint i, j, k;
|
||||||
int index;
|
int index;
|
||||||
guint64 timestamp, time;
|
guint64 timestamp, time;
|
||||||
|
guint co_size;
|
||||||
|
|
||||||
/* sample to chunk */
|
/* sample to chunk */
|
||||||
if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stsc, &stsc))
|
if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stsc, &stsc))
|
||||||
|
@ -3525,27 +3521,26 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
goto corrupt_file;
|
goto corrupt_file;
|
||||||
|
|
||||||
/* chunk offsets */
|
/* chunk offsets */
|
||||||
stco = qtdemux_tree_get_child_by_type (stbl, FOURCC_stco);
|
if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stco, &co_reader))
|
||||||
co64 = qtdemux_tree_get_child_by_type (stbl, FOURCC_co64);
|
co_size = sizeof (guint32);
|
||||||
if (stco) {
|
else if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_co64, &co_reader))
|
||||||
stco_data = (const guint8 *) stco->data;
|
co_size = sizeof (guint64);
|
||||||
co64_data = NULL;
|
else
|
||||||
} else {
|
goto corrupt_file;
|
||||||
stco_data = NULL;
|
|
||||||
if (co64 == NULL)
|
/* sample time */
|
||||||
goto corrupt_file;
|
if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stts, &stts))
|
||||||
co64_data = (const guint8 *) co64->data;
|
|
||||||
}
|
|
||||||
/* sample time */
|
|
||||||
if (!(stts = qtdemux_tree_get_child_by_type (stbl, FOURCC_stts)))
|
|
||||||
goto corrupt_file;
|
goto corrupt_file;
|
||||||
stts_data = (const guint8 *) stts->data;
|
|
||||||
|
|
||||||
if (!qt_atom_parser_skip (&stsz, 1 + 3) ||
|
if (!qt_atom_parser_skip (&stsz, 1 + 3) ||
|
||||||
!qt_atom_parser_get_uint32 (&stsz, &sample_size))
|
!qt_atom_parser_get_uint32 (&stsz, &sample_size))
|
||||||
goto corrupt_file;
|
goto corrupt_file;
|
||||||
|
|
||||||
if (sample_size == 0 || stream->sampled) {
|
if (sample_size == 0 || stream->sampled) {
|
||||||
|
/* skip version, flags, number of entries */
|
||||||
|
if (!gst_byte_reader_skip (&co_reader, 1 + 3 + 4))
|
||||||
|
goto corrupt_file;
|
||||||
|
|
||||||
if (!qt_atom_parser_get_uint32 (&stsz, &n_samples))
|
if (!qt_atom_parser_get_uint32 (&stsz, &n_samples))
|
||||||
goto corrupt_file;
|
goto corrupt_file;
|
||||||
|
|
||||||
|
@ -3589,6 +3584,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
|
|
||||||
index = 0;
|
index = 0;
|
||||||
for (i = 0; i < n_samples_per_chunk; i++) {
|
for (i = 0; i < n_samples_per_chunk; i++) {
|
||||||
|
QtAtomParser co_chunk;
|
||||||
guint32 first_chunk, last_chunk;
|
guint32 first_chunk, last_chunk;
|
||||||
guint32 samples_per_chunk;
|
guint32 samples_per_chunk;
|
||||||
|
|
||||||
|
@ -3615,17 +3611,28 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
--last_chunk;
|
--last_chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (G_UNLIKELY (last_chunk < first_chunk))
|
||||||
|
goto corrupt_file;
|
||||||
|
|
||||||
|
if (last_chunk != G_MAXUINT32) {
|
||||||
|
if (!qt_atom_parser_peek_sub (&co_reader, first_chunk * co_size,
|
||||||
|
(last_chunk - first_chunk) * co_size, &co_chunk))
|
||||||
|
goto corrupt_file;
|
||||||
|
} else {
|
||||||
|
co_chunk = co_reader;
|
||||||
|
if (!qt_atom_parser_skip (&co_chunk, first_chunk * co_size))
|
||||||
|
goto corrupt_file;
|
||||||
|
}
|
||||||
|
|
||||||
for (j = first_chunk; j < last_chunk; j++) {
|
for (j = first_chunk; j < last_chunk; j++) {
|
||||||
guint64 chunk_offset;
|
guint64 chunk_offset;
|
||||||
|
|
||||||
if (stco) {
|
if (!qt_atom_parser_get_offset (&co_chunk, co_size, &chunk_offset))
|
||||||
chunk_offset = QT_UINT32 (stco_data + 16 + j * 4);
|
goto corrupt_file;
|
||||||
} else {
|
|
||||||
chunk_offset = QT_UINT64 (co64_data + 16 + j * 8);
|
|
||||||
}
|
|
||||||
for (k = 0; k < samples_per_chunk; k++) {
|
for (k = 0; k < samples_per_chunk; k++) {
|
||||||
GST_LOG_OBJECT (qtdemux, "Creating entry %d with offset %lld",
|
GST_LOG_OBJECT (qtdemux, "Creating entry %d with offset %"
|
||||||
index, chunk_offset);
|
G_GUINT64_FORMAT, index, chunk_offset);
|
||||||
samples[index].offset = chunk_offset;
|
samples[index].offset = chunk_offset;
|
||||||
chunk_offset += samples[index].size;
|
chunk_offset += samples[index].size;
|
||||||
index++;
|
index++;
|
||||||
|
@ -3635,112 +3642,132 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done2:
|
done2:
|
||||||
|
{
|
||||||
|
guint32 n_sample_times;
|
||||||
|
|
||||||
n_sample_times = QT_UINT32 (stts_data + 12);
|
if (!qt_atom_parser_skip (&stts, 4))
|
||||||
GST_LOG_OBJECT (qtdemux, "%u timestamp blocks", n_sample_times);
|
goto corrupt_file;
|
||||||
timestamp = 0;
|
if (!qt_atom_parser_get_uint32 (&stts, &n_sample_times))
|
||||||
stream->min_duration = 0;
|
goto corrupt_file;
|
||||||
time = 0;
|
GST_LOG_OBJECT (qtdemux, "%u timestamp blocks", n_sample_times);
|
||||||
index = 0;
|
|
||||||
stts_data += 16;
|
|
||||||
for (i = 0; i < n_sample_times; i++) {
|
|
||||||
guint32 n;
|
|
||||||
guint32 duration;
|
|
||||||
|
|
||||||
n = QT_UINT32 (stts_data);
|
/* make sure there's enough data */
|
||||||
stts_data += 4;
|
if (qt_atom_parser_get_remaining (&stts) < (n_sample_times * (2 * 4)))
|
||||||
duration = QT_UINT32 (stts_data);
|
goto corrupt_file;
|
||||||
stts_data += 4;
|
|
||||||
GST_LOG_OBJECT (qtdemux, "block %d, %u timestamps, duration %u ", i, n,
|
|
||||||
duration);
|
|
||||||
|
|
||||||
/* take first duration for fps */
|
timestamp = 0;
|
||||||
if (G_UNLIKELY (stream->min_duration == 0))
|
stream->min_duration = 0;
|
||||||
stream->min_duration = duration;
|
time = 0;
|
||||||
|
index = 0;
|
||||||
|
for (i = 0; i < n_sample_times; i++) {
|
||||||
|
guint32 n;
|
||||||
|
guint32 duration;
|
||||||
|
|
||||||
for (j = 0; j < n; j++) {
|
n = qt_atom_parser_get_uint32_unchecked (&stts);
|
||||||
GST_DEBUG_OBJECT (qtdemux,
|
duration = qt_atom_parser_get_uint32_unchecked (&stts);
|
||||||
"sample %d: index %d, timestamp %" GST_TIME_FORMAT, index, j,
|
GST_LOG_OBJECT (qtdemux, "block %d, %u timestamps, duration %u ", i, n,
|
||||||
GST_TIME_ARGS (timestamp));
|
duration);
|
||||||
|
|
||||||
samples[index].timestamp = timestamp;
|
/* take first duration for fps */
|
||||||
/* add non-scaled values to avoid rounding errors */
|
if (G_UNLIKELY (stream->min_duration == 0))
|
||||||
time += duration;
|
stream->min_duration = duration;
|
||||||
timestamp = gst_util_uint64_scale (time, GST_SECOND, stream->timescale);
|
|
||||||
samples[index].duration = timestamp - samples[index].timestamp;
|
|
||||||
|
|
||||||
index++;
|
for (j = 0; j < n; j++) {
|
||||||
if (G_UNLIKELY (index >= n_samples))
|
GST_DEBUG_OBJECT (qtdemux,
|
||||||
goto done3;
|
"sample %d: index %d, timestamp %" GST_TIME_FORMAT, index, j,
|
||||||
}
|
GST_TIME_ARGS (timestamp));
|
||||||
}
|
|
||||||
/* fill up empty timestamps with the last timestamp, this can happen when
|
|
||||||
* the last samples do not decode and so we don't have timestamps for them.
|
|
||||||
* We however look at the last timestamp to estimate the track length so we
|
|
||||||
* need something in here. */
|
|
||||||
for (; index < n_samples; index++) {
|
|
||||||
GST_DEBUG_OBJECT (qtdemux, "fill sample %d: timestamp %" GST_TIME_FORMAT,
|
|
||||||
index, GST_TIME_ARGS (timestamp));
|
|
||||||
samples[index].timestamp = timestamp;
|
|
||||||
samples[index].duration = -1;
|
|
||||||
}
|
|
||||||
done3:
|
|
||||||
|
|
||||||
/* sample sync, can be NULL */
|
samples[index].timestamp = timestamp;
|
||||||
stss = qtdemux_tree_get_child_by_type (stbl, FOURCC_stss);
|
/* add non-scaled values to avoid rounding errors */
|
||||||
|
time += duration;
|
||||||
|
timestamp =
|
||||||
|
gst_util_uint64_scale (time, GST_SECOND, stream->timescale);
|
||||||
|
samples[index].duration = timestamp - samples[index].timestamp;
|
||||||
|
|
||||||
if (stss) {
|
index++;
|
||||||
/* mark keyframes */
|
if (G_UNLIKELY (index >= n_samples))
|
||||||
guint32 n_sample_syncs;
|
goto done3;
|
||||||
const guint8 *stss_p = (guint8 *) stss->data;
|
|
||||||
|
|
||||||
stss_p += 12;
|
|
||||||
n_sample_syncs = QT_UINT32 (stss_p);
|
|
||||||
if (n_sample_syncs == 0) {
|
|
||||||
stream->all_keyframe = TRUE;
|
|
||||||
} else {
|
|
||||||
for (i = 0; i < n_sample_syncs; i++) {
|
|
||||||
stss_p += 4;
|
|
||||||
/* note that the first sample is index 1, not 0 */
|
|
||||||
index = QT_UINT32 (stss_p);
|
|
||||||
if (G_LIKELY (index > 0 && index <= n_samples))
|
|
||||||
samples[index - 1].keyframe = TRUE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stps = qtdemux_tree_get_child_by_type (stbl, FOURCC_stps);
|
/* fill up empty timestamps with the last timestamp, this can happen when
|
||||||
if (stps) {
|
* the last samples do not decode and so we don't have timestamps for them.
|
||||||
/* stps marks partial sync frames like open GOP I-Frames */
|
* We however look at the last timestamp to estimate the track length so we
|
||||||
guint32 n_sample_syncs;
|
* need something in here. */
|
||||||
const guint8 *stps_p = (guint8 *) stps->data;
|
for (; index < n_samples; index++) {
|
||||||
|
GST_DEBUG_OBJECT (qtdemux, "fill sample %d: timestamp %" GST_TIME_FORMAT,
|
||||||
|
index, GST_TIME_ARGS (timestamp));
|
||||||
|
samples[index].timestamp = timestamp;
|
||||||
|
samples[index].duration = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done3:
|
||||||
|
{
|
||||||
|
/* FIXME: split this block out into a separate function */
|
||||||
|
QtAtomParser stss, stps;
|
||||||
|
|
||||||
stps_p += 12;
|
/* sample sync, can be NULL */
|
||||||
n_sample_syncs = QT_UINT32 (stps_p);
|
if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stss, &stss)) {
|
||||||
if (n_sample_syncs != 0) {
|
guint32 n_sample_syncs;
|
||||||
/* no entries, the stss table contains the real sync
|
|
||||||
* samples */
|
/* mark keyframes */
|
||||||
|
if (!qt_atom_parser_skip (&stss, 4))
|
||||||
|
goto corrupt_file;
|
||||||
|
if (!qt_atom_parser_get_uint32 (&stss, &n_sample_syncs))
|
||||||
|
goto corrupt_file;
|
||||||
|
|
||||||
|
if (n_sample_syncs == 0) {
|
||||||
|
stream->all_keyframe = TRUE;
|
||||||
} else {
|
} else {
|
||||||
|
/* make sure there's enough data */
|
||||||
|
if (qt_atom_parser_get_remaining (&stss) < (n_sample_syncs * 4))
|
||||||
|
goto corrupt_file;
|
||||||
for (i = 0; i < n_sample_syncs; i++) {
|
for (i = 0; i < n_sample_syncs; i++) {
|
||||||
stps_p += 4;
|
|
||||||
/* note that the first sample is index 1, not 0 */
|
/* note that the first sample is index 1, not 0 */
|
||||||
index = QT_UINT32 (stps_p);
|
index = qt_atom_parser_get_uint32_unchecked (&stss);
|
||||||
if (G_LIKELY (index > 0 && index <= n_samples))
|
if (G_LIKELY (index > 0 && index <= n_samples))
|
||||||
samples[index - 1].keyframe = TRUE;
|
samples[index - 1].keyframe = TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* stps marks partial sync frames like open GOP I-Frames */
|
||||||
|
if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stps, &stps)) {
|
||||||
|
guint32 n_sample_syncs;
|
||||||
|
|
||||||
|
if (!qt_atom_parser_skip (&stps, 4))
|
||||||
|
goto corrupt_file;
|
||||||
|
if (!qt_atom_parser_get_uint32 (&stps, &n_sample_syncs))
|
||||||
|
goto corrupt_file;
|
||||||
|
|
||||||
|
if (n_sample_syncs != 0) {
|
||||||
|
/* no entries, the stss table contains the real sync
|
||||||
|
* samples */
|
||||||
|
} else {
|
||||||
|
/* make sure there's enough data */
|
||||||
|
if (qt_atom_parser_get_remaining (&stps) < (n_sample_syncs * 4))
|
||||||
|
goto corrupt_file;
|
||||||
|
for (i = 0; i < n_sample_syncs; i++) {
|
||||||
|
/* note that the first sample is index 1, not 0 */
|
||||||
|
index = qt_atom_parser_get_uint32_unchecked (&stps);
|
||||||
|
if (G_LIKELY (index > 0 && index <= n_samples))
|
||||||
|
samples[index - 1].keyframe = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* no stss, all samples are keyframes */
|
||||||
|
stream->all_keyframe = TRUE;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
/* no stss, all samples are keyframes */
|
|
||||||
stream->all_keyframe = TRUE;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GST_DEBUG_OBJECT (qtdemux,
|
GST_DEBUG_OBJECT (qtdemux,
|
||||||
"stsz sample_size %d != 0, treating chunks as samples", sample_size);
|
"stsz sample_size %d != 0, treating chunks as samples", sample_size);
|
||||||
|
|
||||||
|
/* skip version + flags */
|
||||||
|
if (!gst_byte_reader_skip (&co_reader, 1 + 3))
|
||||||
|
goto corrupt_file;
|
||||||
|
|
||||||
/* treat chunks as samples */
|
/* treat chunks as samples */
|
||||||
if (stco) {
|
if (!gst_byte_reader_get_uint32_be (&co_reader, &n_samples))
|
||||||
n_samples = QT_UINT32 (stco_data + 12);
|
goto corrupt_file;
|
||||||
} else {
|
|
||||||
n_samples = QT_UINT32 (co64_data + 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n_samples == 0)
|
if (n_samples == 0)
|
||||||
goto no_samples;
|
goto no_samples;
|
||||||
|
@ -3766,6 +3793,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
goto corrupt_file;
|
goto corrupt_file;
|
||||||
|
|
||||||
for (i = 0; i < n_samples_per_chunk; i++) {
|
for (i = 0; i < n_samples_per_chunk; i++) {
|
||||||
|
QtAtomParser co_chunk;
|
||||||
guint32 first_chunk, last_chunk;
|
guint32 first_chunk, last_chunk;
|
||||||
guint32 samples_per_chunk;
|
guint32 samples_per_chunk;
|
||||||
|
|
||||||
|
@ -3796,22 +3824,28 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
|
||||||
"entry %d has first_chunk %d, last_chunk %d, samples_per_chunk %d", i,
|
"entry %d has first_chunk %d, last_chunk %d, samples_per_chunk %d", i,
|
||||||
first_chunk, last_chunk, samples_per_chunk);
|
first_chunk, last_chunk, samples_per_chunk);
|
||||||
|
|
||||||
for (j = first_chunk; j < last_chunk; j++) {
|
if (G_UNLIKELY (last_chunk < first_chunk))
|
||||||
guint64 chunk_offset;
|
goto corrupt_file;
|
||||||
|
|
||||||
|
if (last_chunk != G_MAXUINT32) {
|
||||||
|
if (!qt_atom_parser_peek_sub (&co_reader, first_chunk * co_size,
|
||||||
|
(last_chunk - first_chunk) * co_size, &co_chunk))
|
||||||
|
goto corrupt_file;
|
||||||
|
} else {
|
||||||
|
co_chunk = co_reader;
|
||||||
|
if (!qt_atom_parser_skip (&co_chunk, first_chunk * co_size))
|
||||||
|
goto corrupt_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = first_chunk; j < last_chunk; j++) {
|
||||||
if (j >= n_samples)
|
if (j >= n_samples)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
if (stco) {
|
samples[j].offset =
|
||||||
chunk_offset = QT_UINT32 (stco_data + 16 + j * 4);
|
qt_atom_parser_get_offset_unchecked (&co_chunk, co_size);
|
||||||
} else {
|
|
||||||
chunk_offset = QT_UINT64 (co64_data + 16 + j * 8);
|
|
||||||
}
|
|
||||||
GST_LOG_OBJECT (qtdemux,
|
|
||||||
"Creating entry %d with offset %" G_GUINT64_FORMAT, j,
|
|
||||||
chunk_offset);
|
|
||||||
|
|
||||||
samples[j].offset = chunk_offset;
|
GST_LOG_OBJECT (qtdemux, "Created entry %d with offset "
|
||||||
|
"%" G_GUINT64_FORMAT, j, samples[j].offset);
|
||||||
|
|
||||||
if (stream->samples_per_frame * stream->bytes_per_frame) {
|
if (stream->samples_per_frame * stream->bytes_per_frame) {
|
||||||
samples[j].size = (samples_per_chunk * stream->n_channels) /
|
samples[j].size = (samples_per_chunk * stream->n_channels) /
|
||||||
|
|
Loading…
Reference in a new issue