[PATCH 00/20] Matroska muxer patches
Hello, this patchset is about the Matroska muxer. It consists partially of patches from my earlier patchset [1] that is currently blocked by the webm_chunk muxer [2]. One focus of this patchset is about improving writing Cues; in particular, an assert that is triggered if the reserved space for writing Cues in front exceeds the needed space by one byte is fixed. Furthermore, the checks involved in writing SeekHeads were fixed. This goes beyond what was done in the earlier patchset. - Andreas [1]: https://ffmpeg.org/pipermail/ffmpeg-devel/2019-November/252578.html [2]: https://ffmpeg.org/pipermail/ffmpeg-devel/2019-December/254783.html Andreas Rheinhardt (20): avformat/matroskaenc: Fix ReferenceBlock timestamp avformat/matroskaenc: Improve writing Projection avformat/matroskaenc: Remove useless AVIOContext avformat/matroskaenc: Adapt documentation of put_ebml_num avformat/matroskaenc: Check return value of ff_isom_write_hvcc() avformat/matroskaenc: Check functions that can fail avformat/matroskaenc: Avoid allocations for SeekHead avformat/matroskaenc: Improve calculating EBML ID size avformat/matroskaenc: Check for failure when writing SeekHead avformat/matroskaenc: Avoid unnecessary seek avformat/matroskaenc: Cosmetics avformat/matroskaenc: Avoid allocation for Cues avformat/aviobuf: Add function to reset dynamic buffer avformat/matroskadec: Simplify writing Cues avformat/matroskaenc: Write level 1 elements in one go avformat/matroskaenc: Remove unused function parameter avformat/matroskaenc: Check that Cluster has been opened avformat/matroskaenc: Don't fail if reserved Cues space doesn't suffice avformat/matroskaenc: Fix edge case of writing Cues at the beginning avformat/matroskaenc: Avoid seek when writing Cues at the front doc/muxers.texi | 4 +- libavformat/avio_internal.h | 7 + libavformat/aviobuf.c | 11 + libavformat/matroskaenc.c | 516 ++++++++++++--------------- tests/fate/matroska.mak | 2 +- tests/fate/wavpack.mak | 4 +- tests/ref/fate/aac-autobsf-adtstoasc | 4 +- tests/ref/fate/binsub-mksenc | 2 +- tests/ref/fate/rgb24-mkv | 4 +- tests/ref/lavf/mka | 4 +- tests/ref/lavf/mkv | 4 +- tests/ref/lavf/mkv_attachment | 4 +- tests/ref/seek/lavf-mkv | 44 +-- 13 files changed, 286 insertions(+), 324 deletions(-) -- 2.20.1
In order to indicate that the frames in a BlockGroup are not keyframes, one has to add a ReferenceBlock element containing the timestamp of a referenced Block that has already been written. The timestamp ought to be relative to the timestamp of the Block it is attached to. Yet the Matroska muxer used the relative timestamp of the preceding Block of the track, i.e. the timestamp of the preceding block relative to the timestamp of the Cluster containing said block (that need not be the Cluster containing the current Block). This has been fixed. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 469b604de6..9cf840c9be 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2167,9 +2167,9 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, av_free(data); if (blockid == MATROSKA_ID_BLOCK && !keyframe) { - put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp); + put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp - ts); } - track->last_timestamp = ts - mkv->cluster_pts; + track->last_timestamp = ts; if (discard_padding) { put_ebml_sint(pb, MATROSKA_ID_DISCARDPADDING, discard_padding); -- 2.20.1
On Wed, Jan 1, 2020 at 1:59 AM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
In order to indicate that the frames in a BlockGroup are not keyframes, one has to add a ReferenceBlock element containing the timestamp of a referenced Block that has already been written. The timestamp ought to be relative to the timestamp of the Block it is attached to. Yet the Matroska muxer used the relative timestamp of the preceding Block of the track, i.e. the timestamp of the preceding block relative to the timestamp of the Cluster containing said block (that need not be the Cluster containing the current Block). This has been fixed.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 469b604de6..9cf840c9be 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2167,9 +2167,9 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, av_free(data);
if (blockid == MATROSKA_ID_BLOCK && !keyframe) { - put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp); + put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp - ts); } - track->last_timestamp = ts - mkv->cluster_pts; + track->last_timestamp = ts;
if (discard_padding) { put_ebml_sint(pb, MATROSKA_ID_DISCARDPADDING, discard_padding); -- 2.20.1
Ping for the rest of this patchset. - Andreas
On Tue, Jan 7, 2020 at 3:12 PM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
On Wed, Jan 1, 2020 at 1:59 AM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
In order to indicate that the frames in a BlockGroup are not keyframes, one has to add a ReferenceBlock element containing the timestamp of a referenced Block that has already been written. The timestamp ought to be relative to the timestamp of the Block it is attached to. Yet the Matroska muxer used the relative timestamp of the preceding Block of the track, i.e. the timestamp of the preceding block relative to the timestamp of the Cluster containing said block (that need not be the Cluster containing the current Block). This has been fixed.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 469b604de6..9cf840c9be 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2167,9 +2167,9 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, av_free(data);
if (blockid == MATROSKA_ID_BLOCK && !keyframe) { - put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp); + put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp - ts); } - track->last_timestamp = ts - mkv->cluster_pts; + track->last_timestamp = ts;
if (discard_padding) { put_ebml_sint(pb, MATROSKA_ID_DISCARDPADDING, discard_padding); -- 2.20.1
Ping for the rest of this patchset.
- Andreas
Ping. - Andreas
probably ok On 1/1/20, Andreas Rheinhardt <andreas.rheinhardt@gmail.com> wrote:
In order to indicate that the frames in a BlockGroup are not keyframes, one has to add a ReferenceBlock element containing the timestamp of a referenced Block that has already been written. The timestamp ought to be relative to the timestamp of the Block it is attached to. Yet the Matroska muxer used the relative timestamp of the preceding Block of the track, i.e. the timestamp of the preceding block relative to the timestamp of the Cluster containing said block (that need not be the Cluster containing the current Block). This has been fixed.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 469b604de6..9cf840c9be 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2167,9 +2167,9 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, av_free(data);
if (blockid == MATROSKA_ID_BLOCK && !keyframe) { - put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp); + put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp - ts); } - track->last_timestamp = ts - mkv->cluster_pts; + track->last_timestamp = ts;
if (discard_padding) { put_ebml_sint(pb, MATROSKA_ID_DISCARDPADDING, discard_padding); -- 2.20.1
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
On 1/11/2020 11:12 AM, Paul B Mahol wrote:
probably ok
Applied.
On 1/1/20, Andreas Rheinhardt <andreas.rheinhardt@gmail.com> wrote:
In order to indicate that the frames in a BlockGroup are not keyframes, one has to add a ReferenceBlock element containing the timestamp of a referenced Block that has already been written. The timestamp ought to be relative to the timestamp of the Block it is attached to. Yet the Matroska muxer used the relative timestamp of the preceding Block of the track, i.e. the timestamp of the preceding block relative to the timestamp of the Cluster containing said block (that need not be the Cluster containing the current Block). This has been fixed.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 469b604de6..9cf840c9be 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2167,9 +2167,9 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, av_free(data);
if (blockid == MATROSKA_ID_BLOCK && !keyframe) { - put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp); + put_ebml_sint(pb, MATROSKA_ID_BLOCKREFERENCE, track->last_timestamp - ts); } - track->last_timestamp = ts - mkv->cluster_pts; + track->last_timestamp = ts;
if (discard_padding) { put_ebml_sint(pb, MATROSKA_ID_DISCARDPADDING, discard_padding); -- 2.20.1
The Matroska Projection master element has such a small maximum length that it can always be written with a length field of length one. So it is unnecessary to first write the element into a dynamic buffer to get the accurate length in order not to waste bytes on the length field. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 9cf840c9be..bbf9b55e78 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -947,10 +947,8 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, AVStream *st) { AVIOContext b; - AVIOContext *dyn_cp; + ebml_master projection; int side_data_size = 0; - int ret, projection_size; - uint8_t *projection_ptr; uint8_t private[20]; const AVSphericalMapping *spherical = @@ -960,62 +958,58 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, if (!side_data_size) return 0; - ret = avio_open_dyn_buf(&dyn_cp); - if (ret < 0) - return ret; + if (spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR && + spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR_TILE && + spherical->projection != AV_SPHERICAL_CUBEMAP) { + av_log(s, AV_LOG_WARNING, "Unknown projection type\n"); + return 0; + } + + // Maximally 4 8-byte elements with id-length 2 + 1 byte length field + // and the private data of the AV_SPHERICAL_EQUIRECTANGULAR_TILE case + projection = start_ebml_master(pb, MATROSKA_ID_VIDEOPROJECTION, + 4 * (2 + 1 + 8) + (2 + 1 + 20)); switch (spherical->projection) { case AV_SPHERICAL_EQUIRECTANGULAR: - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); break; case AV_SPHERICAL_EQUIRECTANGULAR_TILE: ffio_init_context(&b, private, 20, 1, NULL, NULL, NULL, NULL); - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); avio_wb32(&b, 0); // version + flags avio_wb32(&b, spherical->bound_top); avio_wb32(&b, spherical->bound_bottom); avio_wb32(&b, spherical->bound_left); avio_wb32(&b, spherical->bound_right); - put_ebml_binary(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, + put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, private, avio_tell(&b)); break; case AV_SPHERICAL_CUBEMAP: ffio_init_context(&b, private, 12, 1, NULL, NULL, NULL, NULL); - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_CUBEMAP); avio_wb32(&b, 0); // version + flags avio_wb32(&b, 0); // layout avio_wb32(&b, spherical->padding); - put_ebml_binary(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, + put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, private, avio_tell(&b)); break; - default: - av_log(s, AV_LOG_WARNING, "Unknown projection type\n"); - goto end; } if (spherical->yaw) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW, (double) spherical->yaw / (1 << 16)); if (spherical->pitch) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH, (double) spherical->pitch / (1 << 16)); if (spherical->roll) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL, (double) spherical->roll / (1 << 16)); -end: - projection_size = avio_close_dyn_buf(dyn_cp, &projection_ptr); - if (projection_size) { - ebml_master projection = start_ebml_master(pb, - MATROSKA_ID_VIDEOPROJECTION, - projection_size); - avio_write(pb, projection_ptr, projection_size); - end_ebml_master(pb, projection); - } - av_freep(&projection_ptr); + end_ebml_master(pb, projection); return 0; } -- 2.20.1
Am Mi., 1. Jan. 2020 um 01:59 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
The Matroska Projection master element has such a small maximum length that it can always be written with a length field of length one. So it is unnecessary to first write the element into a dynamic buffer to get the accurate length in order not to waste bytes on the length field.
"Improve" or "Simplify"?
On Wed, Jan 1, 2020 at 4:53 AM Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote:
Am Mi., 1. Jan. 2020 um 01:59 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
The Matroska Projection master element has such a small maximum length that it can always be written with a length field of length one. So it is unnecessary to first write the element into a dynamic buffer to get the accurate length in order not to waste bytes on the length field.
"Improve" or "Simplify"?
Both are applicable. - Andreas
On Wed, Jan 1, 2020 at 1:59 AM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
The Matroska Projection master element has such a small maximum length that it can always be written with a length field of length one. So it is unnecessary to first write the element into a dynamic buffer to get the accurate length in order not to waste bytes on the length field.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 9cf840c9be..bbf9b55e78 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -947,10 +947,8 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, AVStream *st) { AVIOContext b; - AVIOContext *dyn_cp; + ebml_master projection; int side_data_size = 0; - int ret, projection_size; - uint8_t *projection_ptr; uint8_t private[20];
const AVSphericalMapping *spherical = @@ -960,62 +958,58 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, if (!side_data_size) return 0;
- ret = avio_open_dyn_buf(&dyn_cp); - if (ret < 0) - return ret; + if (spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR && + spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR_TILE && + spherical->projection != AV_SPHERICAL_CUBEMAP) { + av_log(s, AV_LOG_WARNING, "Unknown projection type\n"); + return 0; + } + + // Maximally 4 8-byte elements with id-length 2 + 1 byte length field + // and the private data of the AV_SPHERICAL_EQUIRECTANGULAR_TILE case + projection = start_ebml_master(pb, MATROSKA_ID_VIDEOPROJECTION, + 4 * (2 + 1 + 8) + (2 + 1 + 20));
switch (spherical->projection) { case AV_SPHERICAL_EQUIRECTANGULAR: - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); break; case AV_SPHERICAL_EQUIRECTANGULAR_TILE: ffio_init_context(&b, private, 20, 1, NULL, NULL, NULL, NULL); - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); avio_wb32(&b, 0); // version + flags avio_wb32(&b, spherical->bound_top); avio_wb32(&b, spherical->bound_bottom); avio_wb32(&b, spherical->bound_left); avio_wb32(&b, spherical->bound_right); - put_ebml_binary(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, + put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, private, avio_tell(&b)); break; case AV_SPHERICAL_CUBEMAP: ffio_init_context(&b, private, 12, 1, NULL, NULL, NULL, NULL); - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_CUBEMAP); avio_wb32(&b, 0); // version + flags avio_wb32(&b, 0); // layout avio_wb32(&b, spherical->padding); - put_ebml_binary(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, + put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, private, avio_tell(&b)); break; - default: - av_log(s, AV_LOG_WARNING, "Unknown projection type\n"); - goto end; }
if (spherical->yaw) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW, (double) spherical->yaw / (1 << 16)); if (spherical->pitch) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH, (double) spherical->pitch / (1 << 16)); if (spherical->roll) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL, (double) spherical->roll / (1 << 16));
-end: - projection_size = avio_close_dyn_buf(dyn_cp, &projection_ptr); - if (projection_size) { - ebml_master projection = start_ebml_master(pb, - MATROSKA_ID_VIDEOPROJECTION, - projection_size); - avio_write(pb, projection_ptr, projection_size); - end_ebml_master(pb, projection); - } - av_freep(&projection_ptr); + end_ebml_master(pb, projection);
return 0; } -- 2.20.1
Ping. - Andreas
On 12/31/2019 9:58 PM, Andreas Rheinhardt wrote:
The Matroska Projection master element has such a small maximum length that it can always be written with a length field of length one. So it is unnecessary to first write the element into a dynamic buffer to get the accurate length in order not to waste bytes on the length field.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 9cf840c9be..bbf9b55e78 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -947,10 +947,8 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, AVStream *st) { AVIOContext b; - AVIOContext *dyn_cp; + ebml_master projection; int side_data_size = 0; - int ret, projection_size; - uint8_t *projection_ptr; uint8_t private[20];
const AVSphericalMapping *spherical = @@ -960,62 +958,58 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, if (!side_data_size) return 0;
- ret = avio_open_dyn_buf(&dyn_cp); - if (ret < 0) - return ret; + if (spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR && + spherical->projection != AV_SPHERICAL_EQUIRECTANGULAR_TILE && + spherical->projection != AV_SPHERICAL_CUBEMAP) { + av_log(s, AV_LOG_WARNING, "Unknown projection type\n"); + return 0; + } + + // Maximally 4 8-byte elements with id-length 2 + 1 byte length field + // and the private data of the AV_SPHERICAL_EQUIRECTANGULAR_TILE case + projection = start_ebml_master(pb, MATROSKA_ID_VIDEOPROJECTION, + 4 * (2 + 1 + 8) + (2 + 1 + 20));
switch (spherical->projection) { case AV_SPHERICAL_EQUIRECTANGULAR: - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); break; case AV_SPHERICAL_EQUIRECTANGULAR_TILE: ffio_init_context(&b, private, 20, 1, NULL, NULL, NULL, NULL); - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); avio_wb32(&b, 0); // version + flags avio_wb32(&b, spherical->bound_top); avio_wb32(&b, spherical->bound_bottom); avio_wb32(&b, spherical->bound_left); avio_wb32(&b, spherical->bound_right); - put_ebml_binary(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, + put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, private, avio_tell(&b)); break; case AV_SPHERICAL_CUBEMAP: ffio_init_context(&b, private, 12, 1, NULL, NULL, NULL, NULL); - put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONTYPE, + put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_CUBEMAP); avio_wb32(&b, 0); // version + flags avio_wb32(&b, 0); // layout avio_wb32(&b, spherical->padding); - put_ebml_binary(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, + put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, private, avio_tell(&b)); break; - default: - av_log(s, AV_LOG_WARNING, "Unknown projection type\n"); - goto end;
Changed this to av_assert0(0) since the check you added above should ensure a "default" path is never taken here, and pushed. Thanks.
}
if (spherical->yaw) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEYAW, (double) spherical->yaw / (1 << 16)); if (spherical->pitch) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEPITCH, (double) spherical->pitch / (1 << 16)); if (spherical->roll) - put_ebml_float(dyn_cp, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL, + put_ebml_float(pb, MATROSKA_ID_VIDEOPROJECTIONPOSEROLL, (double) spherical->roll / (1 << 16));
-end: - projection_size = avio_close_dyn_buf(dyn_cp, &projection_ptr); - if (projection_size) { - ebml_master projection = start_ebml_master(pb, - MATROSKA_ID_VIDEOPROJECTION, - projection_size); - avio_write(pb, projection_ptr, projection_size); - end_ebml_master(pb, projection); - } - av_freep(&projection_ptr); + end_ebml_master(pb, projection);
return 0; }
Write a few numbers directly via AV_WB32 instead of using an AVIOContext (that is initialized only for this very purpose) to write these numbers at known offsets into a fixed buffer. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index bbf9b55e78..d51c8cbebf 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -946,7 +946,6 @@ static int mkv_write_video_color(AVIOContext *pb, AVCodecParameters *par, AVStre static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, AVStream *st) { - AVIOContext b; ebml_master projection; int side_data_size = 0; uint8_t private[20]; @@ -976,26 +975,24 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); break; case AV_SPHERICAL_EQUIRECTANGULAR_TILE: - ffio_init_context(&b, private, 20, 1, NULL, NULL, NULL, NULL); put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); - avio_wb32(&b, 0); // version + flags - avio_wb32(&b, spherical->bound_top); - avio_wb32(&b, spherical->bound_bottom); - avio_wb32(&b, spherical->bound_left); - avio_wb32(&b, spherical->bound_right); + AV_WB32(private , 0); // version + flags + AV_WB32(private + 4, spherical->bound_top); + AV_WB32(private + 8, spherical->bound_bottom); + AV_WB32(private + 12, spherical->bound_left); + AV_WB32(private + 16, spherical->bound_right); put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, - private, avio_tell(&b)); + private, 20); break; case AV_SPHERICAL_CUBEMAP: - ffio_init_context(&b, private, 12, 1, NULL, NULL, NULL, NULL); put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_CUBEMAP); - avio_wb32(&b, 0); // version + flags - avio_wb32(&b, 0); // layout - avio_wb32(&b, spherical->padding); + AV_WB32(private , 0); // version + flags + AV_WB32(private + 4, 0); // layout + AV_WB32(private + 8, spherical->padding); put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, - private, avio_tell(&b)); + private, 12); break; } -- 2.20.1
On 12/31/2019 9:58 PM, Andreas Rheinhardt wrote:
Write a few numbers directly via AV_WB32 instead of using an AVIOContext (that is initialized only for this very purpose) to write these numbers at known offsets into a fixed buffer.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index bbf9b55e78..d51c8cbebf 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -946,7 +946,6 @@ static int mkv_write_video_color(AVIOContext *pb, AVCodecParameters *par, AVStre static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, AVStream *st) { - AVIOContext b; ebml_master projection; int side_data_size = 0; uint8_t private[20]; @@ -976,26 +975,24 @@ static int mkv_write_video_projection(AVFormatContext *s, AVIOContext *pb, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); break; case AV_SPHERICAL_EQUIRECTANGULAR_TILE: - ffio_init_context(&b, private, 20, 1, NULL, NULL, NULL, NULL); put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_EQUIRECTANGULAR); - avio_wb32(&b, 0); // version + flags - avio_wb32(&b, spherical->bound_top); - avio_wb32(&b, spherical->bound_bottom); - avio_wb32(&b, spherical->bound_left); - avio_wb32(&b, spherical->bound_right); + AV_WB32(private , 0); // version + flags + AV_WB32(private + 4, spherical->bound_top); + AV_WB32(private + 8, spherical->bound_bottom); + AV_WB32(private + 12, spherical->bound_left); + AV_WB32(private + 16, spherical->bound_right); put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, - private, avio_tell(&b)); + private, 20); break; case AV_SPHERICAL_CUBEMAP: - ffio_init_context(&b, private, 12, 1, NULL, NULL, NULL, NULL); put_ebml_uint(pb, MATROSKA_ID_VIDEOPROJECTIONTYPE, MATROSKA_VIDEO_PROJECTION_TYPE_CUBEMAP); - avio_wb32(&b, 0); // version + flags - avio_wb32(&b, 0); // layout - avio_wb32(&b, spherical->padding); + AV_WB32(private , 0); // version + flags + AV_WB32(private + 4, 0); // layout + AV_WB32(private + 8, spherical->padding); put_ebml_binary(pb, MATROSKA_ID_VIDEOPROJECTIONPRIVATE, - private, avio_tell(&b)); + private, 12); break; }
Pushed.
to its actual behaviour: That it uses the least amount of bytes unless overridden. The current documentation leaves it undefined how many bytes will be used when no number to use has been given explicitly. But several estimates (used to write EBML Master elements with a small length field) require this number to be the least amount of bytes to work. Therefore change the documentation; and remove a comment about writing length fields indicating "unkown length". It has been outdated since 0580a122. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index d51c8cbebf..76c38b8d09 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -218,7 +218,7 @@ static int ebml_num_size(uint64_t num) * Write a number in EBML variable length format. * * @param bytes The number of bytes that need to be used to write the number. - * If zero, any number of bytes can be used. + * If zero, the minimal number of bytes will be used. */ static void put_ebml_num(AVIOContext *pb, uint64_t num, int bytes) { @@ -228,10 +228,9 @@ static void put_ebml_num(AVIOContext *pb, uint64_t num, int bytes) av_assert0(num < (1ULL << 56) - 1); if (bytes == 0) - // don't care how many bytes are used, so use the min bytes = needed_bytes; - // the bytes needed to write the given size would exceed the bytes - // that we need to use, so write unknown size. This shouldn't happen. + // The bytes needed to write the given size must not exceed + // the bytes that we ought to use. av_assert0(bytes >= needed_bytes); num |= 1ULL << bytes * 7; -- 2.20.1
On Wed, Jan 01, 2020 at 01:58:21AM +0100, Andreas Rheinhardt wrote:
to its actual behaviour: That it uses the least amount of bytes unless overridden.
The current documentation leaves it undefined how many bytes will be used when no number to use has been given explicitly. But several estimates (used to write EBML Master elements with a small length field) require this number to be the least amount of bytes to work. Therefore change the documentation; and remove a comment about writing length fields indicating "unkown length". It has been outdated since 0580a122.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
will apply thx [...] -- Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB In a rich man's house there is no place to spit but his face. -- Diogenes of Sinope
The Matroska muxer currently does not check the return value of ff_isom_write_hvcc(), the function used to write mp4-style HEVC-extradata as Matroska also uses it. This was intentionally done in 7a5356c72 to allow remuxing from mpeg-ts. But if ff_isom_write_hvcc() fails, it has not output anything and the file ends up without CodecPrivate and, if the input was Annex B, with Annex B data, which is against the spec. So check the return value again. The underlying issue of not having extradata seems to have been fixed by the introduction of the extract_extradata bitstream filter. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 76c38b8d09..8cea829b31 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -748,9 +748,8 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, return ff_isom_write_avcc(dyn_cp, par->extradata, par->extradata_size); case AV_CODEC_ID_HEVC: - ff_isom_write_hvcc(dyn_cp, par->extradata, - par->extradata_size, 0); - return 0; + return ff_isom_write_hvcc(dyn_cp, par->extradata, + par->extradata_size, 0); case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, -- 2.20.1
On Wed, Jan 1, 2020 at 8:59 AM Andreas Rheinhardt <andreas.rheinhardt@gmail.com> wrote:
The Matroska muxer currently does not check the return value of ff_isom_write_hvcc(), the function used to write mp4-style HEVC-extradata as Matroska also uses it. This was intentionally done in 7a5356c72 to allow remuxing from mpeg-ts.
But if ff_isom_write_hvcc() fails, it has not output anything and the file ends up without CodecPrivate and, if the input was Annex B, with Annex B data, which is against the spec. So check the return value again.
The underlying issue of not having extradata seems to have been fixed by the introduction of the extract_extradata bitstream filter.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 76c38b8d09..8cea829b31 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -748,9 +748,8 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, return ff_isom_write_avcc(dyn_cp, par->extradata, par->extradata_size); case AV_CODEC_ID_HEVC: - ff_isom_write_hvcc(dyn_cp, par->extradata, - par->extradata_size, 0); - return 0; + return ff_isom_write_hvcc(dyn_cp, par->extradata, + par->extradata_size, 0); case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, -- 2.20.1
LGTM
On Wed, Jan 01, 2020 at 12:42:51PM +0800, mypopy@gmail.com wrote:
On Wed, Jan 1, 2020 at 8:59 AM Andreas Rheinhardt <andreas.rheinhardt@gmail.com> wrote:
The Matroska muxer currently does not check the return value of ff_isom_write_hvcc(), the function used to write mp4-style HEVC-extradata as Matroska also uses it. This was intentionally done in 7a5356c72 to allow remuxing from mpeg-ts.
But if ff_isom_write_hvcc() fails, it has not output anything and the file ends up without CodecPrivate and, if the input was Annex B, with Annex B data, which is against the spec. So check the return value again.
The underlying issue of not having extradata seems to have been fixed by the introduction of the extract_extradata bitstream filter.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 76c38b8d09..8cea829b31 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -748,9 +748,8 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, return ff_isom_write_avcc(dyn_cp, par->extradata, par->extradata_size); case AV_CODEC_ID_HEVC: - ff_isom_write_hvcc(dyn_cp, par->extradata, - par->extradata_size, 0); - return 0; + return ff_isom_write_hvcc(dyn_cp, par->extradata, + par->extradata_size, 0); case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, -- 2.20.1
LGTM
will apply thx [...] -- Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB Breaking DRM is a little like attempting to break through a door even though the window is wide open and the only thing in the house is a bunch of things you dont want and which you would get tomorrow for free anyway
Sometimes it has not been checked whether opening the dynamic buffer for writing Tags fails; this might have led to segfaults. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 8cea829b31..7857656309 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1557,7 +1557,9 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TAGS, avio_tell(s->pb)); if (ret < 0) return ret; - start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + if (ret < 0) + return ret; } pb = mkv->tags_bc; @@ -1653,7 +1655,10 @@ static int mkv_write_tags(AVFormatContext *s) if (st->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT) continue; - mkv_write_tag_targets(s, MATROSKA_ID_TAGTARGETS_TRACKUID, i + 1, &tag_target); + ret = mkv_write_tag_targets(s, MATROSKA_ID_TAGTARGETS_TRACKUID, + i + 1, &tag_target); + if (ret < 0) + return ret; pb = mkv->tags_bc; tag = start_ebml_master(pb, MATROSKA_ID_SIMPLETAG, 0); -- 2.20.1
Andreas Rheinhardt:
Sometimes it has not been checked whether opening the dynamic buffer for writing Tags fails; this might have led to segfaults.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 8cea829b31..7857656309 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1557,7 +1557,9 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TAGS, avio_tell(s->pb)); if (ret < 0) return ret;
- start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + if (ret < 0) + return ret; } pb = mkv->tags_bc;
@@ -1653,7 +1655,10 @@ static int mkv_write_tags(AVFormatContext *s) if (st->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT) continue;
- mkv_write_tag_targets(s, MATROSKA_ID_TAGTARGETS_TRACKUID, i + 1, &tag_target); + ret = mkv_write_tag_targets(s, MATROSKA_ID_TAGTARGETS_TRACKUID, + i + 1, &tag_target); + if (ret < 0) + return ret; pb = mkv->tags_bc;
tag = start_ebml_master(pb, MATROSKA_ID_SIMPLETAG, 0);
Ping. - Andreas
On Wed, Jan 01, 2020 at 01:58:23AM +0100, Andreas Rheinhardt wrote:
Sometimes it has not been checked whether opening the dynamic buffer for writing Tags fails; this might have led to segfaults.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
will apply thx [...] -- Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB I know you won't believe me, but the highest form of Human Excellence is to question oneself and others. -- Socrates
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately. The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- Slightly changed since last time: The five bytes mentioned at the end of the commit message have been stripped away, too. libavformat/matroskaenc.c | 119 ++++++++------------------- tests/fate/matroska.mak | 2 +- tests/fate/wavpack.mak | 4 +- tests/ref/fate/aac-autobsf-adtstoasc | 4 +- tests/ref/fate/binsub-mksenc | 2 +- tests/ref/fate/rgb24-mkv | 4 +- tests/ref/lavf/mka | 4 +- tests/ref/lavf/mkv | 4 +- tests/ref/lavf/mkv_attachment | 4 +- tests/ref/seek/lavf-mkv | 44 +++++----- 10 files changed, 71 insertions(+), 120 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 7857656309..25ad337844 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -57,6 +57,10 @@ #include "libavcodec/mpeg4audio.h" #include "libavcodec/internal.h" +/* Level 1 elements we create a SeekHead entry for: + * Info, Tracks, Chapters, Attachments, Tags and Cues */ +#define MAX_SEEKHEAD_ENTRIES 6 + typedef struct ebml_master { int64_t pos; ///< absolute offset in the containing AVIOContext where the master's elements start int sizebytes; ///< how many bytes were reserved for the size @@ -69,11 +73,9 @@ typedef struct mkv_seekhead_entry { typedef struct mkv_seekhead { int64_t filepos; - int64_t segment_offset; ///< the file offset to the beginning of the segment - int reserved_size; ///< -1 if appending to file - int max_entries; - mkv_seekhead_entry *entries; + mkv_seekhead_entry entries[MAX_SEEKHEAD_ENTRIES]; int num_entries; + int reserved_size; } mkv_seekhead; typedef struct mkv_cuepoint { @@ -136,7 +138,7 @@ typedef struct MatroskaMuxContext { int64_t cluster_pts; int64_t duration_offset; int64_t duration; - mkv_seekhead *seekhead; + mkv_seekhead seekhead; mkv_cues *cues; mkv_track *tracks; mkv_attachments *attachments; @@ -396,10 +398,6 @@ static void mkv_deinit(AVFormatContext *s) ffio_free_dyn_buf(&mkv->tracks_bc); ffio_free_dyn_buf(&mkv->tags_bc); - if (mkv->seekhead) { - av_freep(&mkv->seekhead->entries); - av_freep(&mkv->seekhead); - } if (mkv->cues) { av_freep(&mkv->cues->entries); av_freep(&mkv->cues); @@ -412,61 +410,33 @@ static void mkv_deinit(AVFormatContext *s) } /** - * Initialize a mkv_seekhead element to be ready to index level 1 Matroska - * elements. If a maximum number of elements is specified, enough space - * will be reserved at the current file location to write a seek head of - * that size. - * - * @param segment_offset The absolute offset to the position in the file - * where the segment begins. - * @param numelements The maximum number of elements that will be indexed - * by this seek head, 0 if unlimited. + * Initialize the SeekHead element to be ready to index level 1 Matroska + * elements. Enough space to write MAX_SEEKHEAD_ENTRIES SeekHead entries + * will be reserved at the current file location. */ -static mkv_seekhead *mkv_start_seekhead(AVIOContext *pb, int64_t segment_offset, - int numelements) +static void mkv_start_seekhead(MatroskaMuxContext *mkv, AVIOContext *pb) { - mkv_seekhead *new_seekhead = av_mallocz(sizeof(mkv_seekhead)); - if (!new_seekhead) - return NULL; - - new_seekhead->segment_offset = segment_offset; - - if (numelements > 0) { - new_seekhead->filepos = avio_tell(pb); - // 21 bytes max for a seek entry, 10 bytes max for the SeekHead ID - // and size, 6 bytes for a CRC32 element, and 3 bytes to guarantee - // that an EBML void element will fit afterwards - new_seekhead->reserved_size = numelements * MAX_SEEKENTRY_SIZE + 19; - new_seekhead->max_entries = numelements; - put_ebml_void(pb, new_seekhead->reserved_size); - } - return new_seekhead; + mkv->seekhead.filepos = avio_tell(pb); + // 21 bytes max for a Seek entry, 6 bytes max for the SeekHead ID + // and size, 6 bytes for a CRC32 element, and 2 bytes to guarantee + // that an EBML void element will fit afterwards + mkv->seekhead.reserved_size = MAX_SEEKHEAD_ENTRIES * MAX_SEEKENTRY_SIZE + 14; + put_ebml_void(pb, mkv->seekhead.reserved_size); } -static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, uint32_t elementid, uint64_t filepos) +static void mkv_add_seekhead_entry(MatroskaMuxContext *mkv, uint32_t elementid, + uint64_t filepos) { - mkv_seekhead_entry *entries = seekhead->entries; + mkv_seekhead *seekhead = &mkv->seekhead; - // don't store more elements than we reserved space for - if (seekhead->max_entries > 0 && seekhead->max_entries <= seekhead->num_entries) - return -1; - - entries = av_realloc_array(entries, seekhead->num_entries + 1, sizeof(mkv_seekhead_entry)); - if (!entries) - return AVERROR(ENOMEM); - seekhead->entries = entries; + av_assert1(seekhead->num_entries < MAX_SEEKHEAD_ENTRIES); seekhead->entries[seekhead->num_entries].elementid = elementid; - seekhead->entries[seekhead->num_entries++].segmentpos = filepos - seekhead->segment_offset; - - return 0; + seekhead->entries[seekhead->num_entries++].segmentpos = filepos - mkv->segment_offset; } /** - * Write the seek head to the file and free it. If a maximum number of - * elements was specified to mkv_start_seekhead(), the seek head will - * be written at the location reserved for it. Otherwise, it is written - * at the current location in the file. + * Write the SeekHead to the file at the location reserved for it. * * @return The file offset where the seekhead was written, * -1 if an error occurred. @@ -474,19 +444,17 @@ static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, uint32_t elementid, ui static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) { AVIOContext *dyn_cp; - mkv_seekhead *seekhead = mkv->seekhead; + mkv_seekhead *seekhead = &mkv->seekhead; ebml_master seekentry; - int64_t currentpos; + int64_t currentpos, remaining; int i; currentpos = avio_tell(pb); - if (seekhead->reserved_size > 0) { if (avio_seek(pb, seekhead->filepos, SEEK_SET) < 0) { currentpos = -1; goto fail; } - } if (start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD) < 0) { currentpos = -1; @@ -507,16 +475,13 @@ static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) } end_ebml_master_crc32(pb, &dyn_cp, mkv); - if (seekhead->reserved_size > 0) { - uint64_t remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); + remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); avio_seek(pb, currentpos, SEEK_SET); currentpos = seekhead->filepos; - } + fail: - av_freep(&mkv->seekhead->entries); - av_freep(&mkv->seekhead); return currentpos; } @@ -1427,9 +1392,7 @@ static int mkv_write_tracks(AVFormatContext *s) AVIOContext *pb = s->pb; int i, ret, default_stream_exists = 0; - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TRACKS, avio_tell(pb)); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_TRACKS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); if (ret < 0) @@ -1464,8 +1427,7 @@ static int mkv_write_chapters(AVFormatContext *s) if (!s->nb_chapters || mkv->wrote_chapters) return 0; - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_CHAPTERS, avio_tell(pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CHAPTERS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); if (ret < 0) return ret; @@ -1554,8 +1516,7 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, int ret; if (!mkv->tags_bc) { - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TAGS, avio_tell(s->pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_TAGS, avio_tell(s->pb)); ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); if (ret < 0) @@ -1725,8 +1686,7 @@ static int mkv_write_attachments(AVFormatContext *s) av_lfg_init(&c, av_get_random_seed()); - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); if (ret < 0) return ret; @@ -1875,16 +1835,10 @@ static int mkv_write_header(AVFormatContext *s) mkv->segment_offset = avio_tell(pb); // we write a seek head at the beginning to point to all other level - // one elements, which aren't more than 10 elements as we write only one - // of every other currently defined level 1 element - mkv->seekhead = mkv_start_seekhead(pb, mkv->segment_offset, 10); - if (!mkv->seekhead) { - return AVERROR(ENOMEM); - } + // one elements (except Clusters). + mkv_start_seekhead(mkv, pb); - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_INFO, avio_tell(pb)); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_INFO, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); if (ret < 0) @@ -2570,10 +2524,7 @@ static int mkv_write_trailer(AVFormatContext *s) cuespos = mkv_write_cues(s, mkv->cues, mkv->tracks, s->nb_streams); } - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_CUES, - cuespos); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); } mkv_write_seekhead(pb, mkv); diff --git a/tests/fate/matroska.mak b/tests/fate/matroska.mak index 4aca4dc8eb..b9ed7322fd 100644 --- a/tests/fate/matroska.mak +++ b/tests/fate/matroska.mak @@ -12,7 +12,7 @@ fate-matroska-prores-header-insertion-bz2: CMD = framecrc -i $(TARGET_SAMPLES)/m FATE_MATROSKA-$(call DEMMUX, MATROSKA, MATROSKA) += fate-matroska-remux fate-matroska-remux: CMD = md5pipe -i $(TARGET_SAMPLES)/vp9-test-vectors/vp90-2-2pass-akiyo.webm -color_trc 4 -c:v copy -fflags +bitexact -strict -2 -f matroska fate-matroska-remux: CMP = oneline -fate-matroska-remux: REF = e5457e5fa606d564a54914bd12f426c8 +fate-matroska-remux: REF = 49a60ef91cf7302ab7276f9373f8a429 FATE_MATROSKA-$(call ALLYES, MATROSKA_DEMUXER VORBIS_PARSER) += fate-matroska-xiph-lacing fate-matroska-xiph-lacing: CMD = framecrc -i $(TARGET_SAMPLES)/mkv/xiph_lacing.mka -c:a copy diff --git a/tests/fate/wavpack.mak b/tests/fate/wavpack.mak index 286e17f2ae..a89cc1293d 100644 --- a/tests/fate/wavpack.mak +++ b/tests/fate/wavpack.mak @@ -91,12 +91,12 @@ fate-wavpack-matroskamode: CMD = md5 -i $(TARGET_SAMPLES)/wavpack/special/matros FATE_WAVPACK-$(call DEMMUX, WV, MATROSKA) += fate-wavpack-matroska_mux-mono fate-wavpack-matroska_mux-mono: CMD = md5pipe -i $(TARGET_SAMPLES)/wavpack/num_channels/mono_16bit_int.wv -c copy -fflags +bitexact -f matroska fate-wavpack-matroska_mux-mono: CMP = oneline -fate-wavpack-matroska_mux-mono: REF = c5a2b46d8b31c9c2e7bfb436f9d56f02 +fate-wavpack-matroska_mux-mono: REF = 0a1a7c6d3b413925f8261c92d1d53363 FATE_WAVPACK-$(call DEMMUX, WV, MATROSKA) += fate-wavpack-matroska_mux-61 fate-wavpack-matroska_mux-61: CMD = md5pipe -i $(TARGET_SAMPLES)/wavpack/num_channels/eva_2.22_6.1_16bit-partial.wv -c copy -fflags +bitexact -f matroska fate-wavpack-matroska_mux-61: CMP = oneline -fate-wavpack-matroska_mux-61: REF = 22fc2a591be22657d19cab89ad6eef55 +fate-wavpack-matroska_mux-61: REF = 1f012f7c237f46f2f63089ba84c589c5 FATE_SAMPLES_AVCONV += $(FATE_WAVPACK-yes) fate-wavpack: $(FATE_WAVPACK-yes) diff --git a/tests/ref/fate/aac-autobsf-adtstoasc b/tests/ref/fate/aac-autobsf-adtstoasc index 9bf9dfe78f..45b68b62bd 100644 --- a/tests/ref/fate/aac-autobsf-adtstoasc +++ b/tests/ref/fate/aac-autobsf-adtstoasc @@ -1,5 +1,5 @@ -b09fc2f554712adbf84fe7899eb679d4 *tests/data/fate/aac-autobsf-adtstoasc.matroska -6695 tests/data/fate/aac-autobsf-adtstoasc.matroska +d10b73732b32f6b2f08885b40c41c77f *tests/data/fate/aac-autobsf-adtstoasc.matroska +6606 tests/data/fate/aac-autobsf-adtstoasc.matroska #extradata 0: 2, 0x0030001c #tb 0: 1/1000 #media_type 0: audio diff --git a/tests/ref/fate/binsub-mksenc b/tests/ref/fate/binsub-mksenc index 4b13ef217a..3266501de6 100644 --- a/tests/ref/fate/binsub-mksenc +++ b/tests/ref/fate/binsub-mksenc @@ -1 +1 @@ -a5811caa0caba3a3f9a449b91569745c +03d376cc2e8622e7af540d4d9809dbf7 diff --git a/tests/ref/fate/rgb24-mkv b/tests/ref/fate/rgb24-mkv index b9db53f592..43b4ee2e19 100644 --- a/tests/ref/fate/rgb24-mkv +++ b/tests/ref/fate/rgb24-mkv @@ -1,5 +1,5 @@ -ffe2c21083d56764aaae7945fd146326 *tests/data/fate/rgb24-mkv.matroska -58305 tests/data/fate/rgb24-mkv.matroska +92861b36a8220a3dae10d91cba614892 *tests/data/fate/rgb24-mkv.matroska +58216 tests/data/fate/rgb24-mkv.matroska #tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo diff --git a/tests/ref/lavf/mka b/tests/ref/lavf/mka index a244893cf2..e8239442e9 100644 --- a/tests/ref/lavf/mka +++ b/tests/ref/lavf/mka @@ -1,3 +1,3 @@ -a91bfdb08ff5a1e62b039748d16b07f7 *tests/data/lavf/lavf.mka -43652 tests/data/lavf/lavf.mka +58936d3ad4eb1372bd2e5845963fe17c *tests/data/lavf/lavf.mka +43563 tests/data/lavf/lavf.mka tests/data/lavf/lavf.mka CRC=0x3a1da17e diff --git a/tests/ref/lavf/mkv b/tests/ref/lavf/mkv index 05999b79fb..4d73fbf39d 100644 --- a/tests/ref/lavf/mkv +++ b/tests/ref/lavf/mkv @@ -1,3 +1,3 @@ -3cddd26ed41f34925d3f34acabab174e *tests/data/lavf/lavf.mkv -320555 tests/data/lavf/lavf.mkv +6ec6d6a058a85bf616e0b981bb8e383f *tests/data/lavf/lavf.mkv +320466 tests/data/lavf/lavf.mkv tests/data/lavf/lavf.mkv CRC=0xec6c3c68 diff --git a/tests/ref/lavf/mkv_attachment b/tests/ref/lavf/mkv_attachment index 230dff369a..2e9711483f 100644 --- a/tests/ref/lavf/mkv_attachment +++ b/tests/ref/lavf/mkv_attachment @@ -1,3 +1,3 @@ -4a25c53150c09537cd4fcbff8f8f14ff *tests/data/lavf/lavf.mkv_attachment -472706 tests/data/lavf/lavf.mkv_attachment +41b4f132ada6e0faedf92e909526a899 *tests/data/lavf/lavf.mkv_attachment +472617 tests/data/lavf/lavf.mkv_attachment tests/data/lavf/lavf.mkv_attachment CRC=0xec6c3c68 diff --git a/tests/ref/seek/lavf-mkv b/tests/ref/seek/lavf-mkv index 6ed1f3a763..223e098829 100644 --- a/tests/ref/seek/lavf-mkv +++ b/tests/ref/seek/lavf-mkv @@ -1,48 +1,48 @@ -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st:-1 flags:0 ts:-1.000000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st:-1 flags:1 ts: 1.894167 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:0 ts: 0.788000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:1 ts:-0.317000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 1 flags:0 ts: 2.577000 ret: 0 st: 1 flags:1 ts: 1.471000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret: 0 st:-1 flags:0 ts: 0.365002 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st:-1 flags:1 ts:-0.740831 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 0 flags:0 ts: 2.153000 ret: 0 st: 0 flags:1 ts: 1.048000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 1 flags:0 ts:-0.058000 -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st: 1 flags:1 ts: 2.836000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret:-1 st:-1 flags:0 ts: 1.730004 ret: 0 st:-1 flags:1 ts: 0.624171 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st: 0 flags:0 ts:-0.482000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st: 0 flags:1 ts: 2.413000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret:-1 st: 1 flags:0 ts: 1.307000 ret: 0 st: 1 flags:1 ts: 0.201000 -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st:-1 flags:0 ts:-0.904994 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st:-1 flags:1 ts: 1.989173 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:0 ts: 0.883000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:1 ts:-0.222000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 1 flags:0 ts: 2.672000 ret: 0 st: 1 flags:1 ts: 1.566000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret: 0 st:-1 flags:0 ts: 0.460008 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st:-1 flags:1 ts:-0.645825 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 -- 2.20.1
When the Matroska muxer writes an EBML ID, it calculates the length of said ID before; and it does this as if this were a number that needs to be encoded as EBML number: The formula used is (av_log2(id + 1) - 1) / 7 + 1. But the constants used already contain the VINT_MARKER (the leading bit indicating the length of the EBML number) and therefore the algorithm used makes no sense. Instead the position of the most significant byte set gives the desired length. The algorithm used until now worked because EBML numbers are subject to restrictions: If the EBML number takes up k bytes, then the bit 1 << (7 * k) is set and av_log2(id) is 7 * k. So the current algorithm produces the correct result unless the EBML ID is of the form 7 * k - 1 because of the "id + 1". But contrary to encoding lengths as EBML number (where the + 1 exists to avoid the encodings reserved for unknown length), such EBML numbers are simply forbidden as EBML IDs and as such none of them were ever written. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 25ad337844..cecf7589e5 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -183,7 +183,7 @@ typedef struct MatroskaMuxContext { static int ebml_id_size(uint32_t id) { - return (av_log2(id + 1) - 1) / 7 + 1; + return (av_log2(id) + 7U) / 8; } static void put_ebml_id(AVIOContext *pb, uint32_t id) -- 2.20.1
mkv_write_seekhead() would up until now try to seek to the position where the SeekHead ought to be written, write the SeekHead and seek back. The first of these seeks was checked as was writing, yet the seek back was unchecked. Moreover the return value of mkv_write_seekhead() was unchecked (the ordinary return value was the position where the SeekHead was written). This commit changes this: Everything is checked. In the unseekable case (where the first seek may nevertheless work when it happens in the buffer) a failure at the first seek is not considered an error. In any case, failure to seek back is an error. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 52 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index cecf7589e5..c7948fb643 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -436,31 +436,35 @@ static void mkv_add_seekhead_entry(MatroskaMuxContext *mkv, uint32_t elementid, } /** - * Write the SeekHead to the file at the location reserved for it. + * Write the SeekHead to the file at the location reserved for it + * and seek back to the initial position. When error_on_seek_failure + * is not set, failure to seek to the position designated for the + * SeekHead is not considered an error; failure to seek back afterwards + * is always an error. * - * @return The file offset where the seekhead was written, - * -1 if an error occurred. + * @return 0 on success, < 0 on error. */ -static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) +static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, + int error_on_seek_failure) { AVIOContext *dyn_cp; mkv_seekhead *seekhead = &mkv->seekhead; ebml_master seekentry; - int64_t currentpos, remaining; - int i; + int64_t currentpos, remaining, ret64; + int i, ret; currentpos = avio_tell(pb); - if (avio_seek(pb, seekhead->filepos, SEEK_SET) < 0) { - currentpos = -1; - goto fail; - } - - if (start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD) < 0) { - currentpos = -1; - goto fail; + if ((ret64 = avio_seek(pb, seekhead->filepos, SEEK_SET)) < 0) { + if (error_on_seek_failure) + return ret64; + goto seek_back; } + ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); + if (ret < 0) + return ret; + for (i = 0; i < seekhead->num_entries; i++) { mkv_seekhead_entry *entry = &seekhead->entries[i]; @@ -477,13 +481,12 @@ static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); - avio_seek(pb, currentpos, SEEK_SET); - currentpos = seekhead->filepos; +seek_back: + if ((ret64 = avio_seek(pb, currentpos, SEEK_SET)) < 0) + return ret64; -fail: - - return currentpos; + return 0; } static mkv_cues *mkv_start_cues(int64_t segment_offset) @@ -1925,8 +1928,11 @@ static int mkv_write_header(AVFormatContext *s) if (ret < 0) return ret; - if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - mkv_write_seekhead(pb, mkv); + if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) { + ret = mkv_write_seekhead(pb, mkv, 0); + if (ret < 0) + return ret; + } mkv->cues = mkv_start_cues(mkv->segment_offset); if (!mkv->cues) { @@ -2527,7 +2533,9 @@ static int mkv_write_trailer(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); } - mkv_write_seekhead(pb, mkv); + ret = mkv_write_seekhead(pb, mkv, 1); + if (ret < 0) + return ret; // update the duration av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); -- 2.20.1
When writing the SeekHead (a form of index) at the end of the muxing process, mkv_write_seekhead() would first seek to the position where the SeekHead ought to be written, then write it there and seek back to the original position afterwards. Which means: To the end of the file. Afterwards, a seek to the beginning of the file is performed to update further values. This of course means that the second seek in mkv_write_seekhead() was unnecessary. This has been changed: A new parameter was added to mkv_write_seekhead() containing the destination for the second seek, effectively eliminating the seek to the end of the file after writing the SeekHead. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index c7948fb643..8040c8311c 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -437,28 +437,26 @@ static void mkv_add_seekhead_entry(MatroskaMuxContext *mkv, uint32_t elementid, /** * Write the SeekHead to the file at the location reserved for it - * and seek back to the initial position. When error_on_seek_failure + * and seek to destpos afterwards. When error_on_seek_failure * is not set, failure to seek to the position designated for the - * SeekHead is not considered an error; failure to seek back afterwards - * is always an error. + * SeekHead is not considered an error; failure to seek to destpos + * afterwards is always an error. * * @return 0 on success, < 0 on error. */ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, - int error_on_seek_failure) + int error_on_seek_failure, int64_t destpos) { AVIOContext *dyn_cp; mkv_seekhead *seekhead = &mkv->seekhead; ebml_master seekentry; - int64_t currentpos, remaining, ret64; + int64_t remaining, ret64; int i, ret; - currentpos = avio_tell(pb); - if ((ret64 = avio_seek(pb, seekhead->filepos, SEEK_SET)) < 0) { if (error_on_seek_failure) return ret64; - goto seek_back; + goto seek; } ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); @@ -482,8 +480,8 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); -seek_back: - if ((ret64 = avio_seek(pb, currentpos, SEEK_SET)) < 0) +seek: + if ((ret64 = avio_seek(pb, destpos, SEEK_SET)) < 0) return ret64; return 0; @@ -1929,7 +1927,7 @@ static int mkv_write_header(AVFormatContext *s) return ret; if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) { - ret = mkv_write_seekhead(pb, mkv, 0); + ret = mkv_write_seekhead(pb, mkv, 0, avio_tell(pb)); if (ret < 0) return ret; } @@ -2533,16 +2531,16 @@ static int mkv_write_trailer(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); } - ret = mkv_write_seekhead(pb, mkv, 1); + currentpos = avio_tell(pb); + + ret = mkv_write_seekhead(pb, mkv, 1, mkv->info_pos); if (ret < 0) return ret; // update the duration av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); - currentpos = avio_tell(pb); avio_seek(mkv->info_bc, mkv->duration_offset, SEEK_SET); put_ebml_float(mkv->info_bc, MATROSKA_ID_DURATION, mkv->duration); - avio_seek(pb, mkv->info_pos, SEEK_SET); end_ebml_master_crc32(pb, &mkv->info_bc, mkv); // write tracks master -- 2.20.1
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 8040c8311c..6dd731eb90 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -449,7 +449,6 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, { AVIOContext *dyn_cp; mkv_seekhead *seekhead = &mkv->seekhead; - ebml_master seekentry; int64_t remaining, ret64; int i, ret; @@ -465,8 +464,8 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, for (i = 0; i < seekhead->num_entries; i++) { mkv_seekhead_entry *entry = &seekhead->entries[i]; - - seekentry = start_ebml_master(dyn_cp, MATROSKA_ID_SEEKENTRY, MAX_SEEKENTRY_SIZE); + ebml_master seekentry = start_ebml_master(dyn_cp, MATROSKA_ID_SEEKENTRY, + MAX_SEEKENTRY_SIZE); put_ebml_id(dyn_cp, MATROSKA_ID_SEEKID); put_ebml_num(dyn_cp, ebml_id_size(entry->elementid), 0); @@ -478,7 +477,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, end_ebml_master_crc32(pb, &dyn_cp, mkv); remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); - put_ebml_void(pb, remaining); + put_ebml_void(pb, remaining); seek: if ((ret64 = avio_seek(pb, destpos, SEEK_SET)) < 0) -- 2.20.1
Up until now, the Matroska muxer would allocate a structure containing three members: The segment offset, a pointer to an array containing Cue (index) entries and a counter for said array. It is unnecessary to allocate it separately and it is unnecessary to contain the segment offset in said structure, as it duplicates another field contained in the MatroskaMuxContext. This commit implements the corresponding changes. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 6dd731eb90..2c4d3c338f 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -88,7 +88,6 @@ typedef struct mkv_cuepoint { } mkv_cuepoint; typedef struct mkv_cues { - int64_t segment_offset; mkv_cuepoint *entries; int num_entries; } mkv_cues; @@ -139,7 +138,7 @@ typedef struct MatroskaMuxContext { int64_t duration_offset; int64_t duration; mkv_seekhead seekhead; - mkv_cues *cues; + mkv_cues cues; mkv_track *tracks; mkv_attachments *attachments; @@ -398,10 +397,7 @@ static void mkv_deinit(AVFormatContext *s) ffio_free_dyn_buf(&mkv->tracks_bc); ffio_free_dyn_buf(&mkv->tags_bc); - if (mkv->cues) { - av_freep(&mkv->cues->entries); - av_freep(&mkv->cues); - } + av_freep(&mkv->cues.entries); if (mkv->attachments) { av_freep(&mkv->attachments->entries); av_freep(&mkv->attachments); @@ -486,19 +482,10 @@ seek: return 0; } -static mkv_cues *mkv_start_cues(int64_t segment_offset) -{ - mkv_cues *cues = av_mallocz(sizeof(mkv_cues)); - if (!cues) - return NULL; - - cues->segment_offset = segment_offset; - return cues; -} - -static int mkv_add_cuepoint(mkv_cues *cues, int stream, int tracknum, int64_t ts, +static int mkv_add_cuepoint(MatroskaMuxContext *mkv, int stream, int tracknum, int64_t ts, int64_t cluster_pos, int64_t relative_pos, int64_t duration) { + mkv_cues *cues = &mkv->cues; mkv_cuepoint *entries = cues->entries; if (ts < 0) @@ -512,7 +499,7 @@ static int mkv_add_cuepoint(mkv_cues *cues, int stream, int tracknum, int64_t ts cues->entries[cues->num_entries].pts = ts; cues->entries[cues->num_entries].stream_idx = stream; cues->entries[cues->num_entries].tracknum = tracknum; - cues->entries[cues->num_entries].cluster_pos = cluster_pos - cues->segment_offset; + cues->entries[cues->num_entries].cluster_pos = cluster_pos - mkv->segment_offset; cues->entries[cues->num_entries].relative_pos = relative_pos; cues->entries[cues->num_entries++].duration = duration; @@ -1931,11 +1918,6 @@ static int mkv_write_header(AVFormatContext *s) return ret; } - mkv->cues = mkv_start_cues(mkv->segment_offset); - if (!mkv->cues) { - return AVERROR(ENOMEM); - } - if (s->metadata_header_padding > 0) { if (s->metadata_header_padding == 1) s->metadata_header_padding++; @@ -2346,7 +2328,8 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_ if (par->codec_type != AVMEDIA_TYPE_SUBTITLE) { mkv_write_block(s, pb, MATROSKA_ID_SIMPLEBLOCK, pkt, keyframe); if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && (par->codec_type == AVMEDIA_TYPE_VIDEO && keyframe || add_cue)) { - ret = mkv_add_cuepoint(mkv->cues, pkt->stream_index, tracknum, ts, mkv->cluster_pos, relative_packet_pos, -1); + ret = mkv_add_cuepoint(mkv, pkt->stream_index, tracknum, ts, + mkv->cluster_pos, relative_packet_pos, -1); if (ret < 0) return ret; } } else { @@ -2371,7 +2354,7 @@ FF_ENABLE_DEPRECATION_WARNINGS } if (s->pb->seekable & AVIO_SEEKABLE_NORMAL) { - ret = mkv_add_cuepoint(mkv->cues, pkt->stream_index, tracknum, ts, + ret = mkv_add_cuepoint(mkv, pkt->stream_index, tracknum, ts, mkv->cluster_pos, relative_packet_pos, duration); if (ret < 0) return ret; @@ -2501,14 +2484,14 @@ static int mkv_write_trailer(AVFormatContext *s) if ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) { - if (mkv->cues->num_entries) { + if (mkv->cues.num_entries) { if (mkv->reserve_cues_space) { int64_t cues_end; currentpos = avio_tell(pb); avio_seek(pb, mkv->cues_pos, SEEK_SET); - cuespos = mkv_write_cues(s, mkv->cues, mkv->tracks, s->nb_streams); + cuespos = mkv_write_cues(s, &mkv->cues, mkv->tracks, s->nb_streams); cues_end = avio_tell(pb); if (cues_end > cuespos + mkv->reserve_cues_space) { av_log(s, AV_LOG_ERROR, @@ -2524,7 +2507,7 @@ static int mkv_write_trailer(AVFormatContext *s) avio_seek(pb, currentpos, SEEK_SET); } else { - cuespos = mkv_write_cues(s, mkv->cues, mkv->tracks, s->nb_streams); + cuespos = mkv_write_cues(s, &mkv->cues, mkv->tracks, s->nb_streams); } mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); -- 2.20.1
Resetting a dynamic buffer means to keep the AVIOContext and the internal buffer used by the dynamic buffer. This is done in order to save (re)allocations when one has a workflow where one opens and closes dynamic buffers in sequence. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- This function might also be beneficial in the mpegts muxer when converting AAC to ADTS (currently a new dynamic buffer is opened for every packet) or in the nut muxer. Besides the application in this very patchset, there is another way that this can be used in the Matroska muxer: By not reallocating the buffer used for writing packets one can improve speed on platforms where realloc is slow (namely Windows). A patch for this comes soon. libavformat/avio_internal.h | 7 +++++++ libavformat/aviobuf.c | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/libavformat/avio_internal.h b/libavformat/avio_internal.h index eb628ac493..c575df8035 100644 --- a/libavformat/avio_internal.h +++ b/libavformat/avio_internal.h @@ -171,6 +171,13 @@ int ffio_open_whitelist(AVIOContext **s, const char *url, int flags, */ int ffio_close_null_buf(AVIOContext *s); +/** + * Reset a dynamic buffer. + * + * Resets everything, but keeps the allocated buffer for later use. + */ +void ffio_reset_dyn_buf(AVIOContext *s); + /** * Free a dynamic buffer. * diff --git a/libavformat/aviobuf.c b/libavformat/aviobuf.c index 70e1d2ca10..d44ee0fa35 100644 --- a/libavformat/aviobuf.c +++ b/libavformat/aviobuf.c @@ -1461,6 +1461,17 @@ int avio_get_dyn_buf(AVIOContext *s, uint8_t **pbuffer) return d->size; } +void ffio_reset_dyn_buf(AVIOContext *s) +{ + DynBuffer *d = s->opaque; + int max_packet_size = s->max_packet_size; + + ffio_init_context(s, d->io_buffer, d->io_buffer_size, 1, d, NULL, + s->write_packet, s->seek); + s->max_packet_size = max_packet_size; + d->pos = d->size = 0; +} + int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer) { DynBuffer *d; -- 2.20.1
When the Matroska muxer writes the Cues (the index), it groups index entries with the same timestamp into the same CuePoint to save space. But given Matroska's variable-length length fields, it either needs to have an upper bound of the final size of the CuePoint before writing it or the CuePoint has to be assembled in a different buffer, so that after having assembled the CuePoint (when the real size is known), the CuePoint's header can be written and its data copied after it. The first of these approaches is the currently used one. This entails finding out the number of entries in a CuePoint before starting the CuePoint and therefore means that the list is read at least twice. Furthermore, a worst-case upper-bound for the length of a single entry was used, so that sometimes bytes are wasted on length fields. This commit switches to the second approach. This is no longer more expensive than the current approach if one only resets the dynamic buffer used to write the CuePoint's content instead of opening a new buffer for every CuePoint: Writing the trailer of a file with 540.000 CuePoints improved actually from 219054414 decicycles to 2164379394 decicycles (based upon 50 iterations). Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 74 +++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 2c4d3c338f..6f0760fa69 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -170,12 +170,8 @@ typedef struct MatroskaMuxContext { * offset, 4 bytes for target EBML ID */ #define MAX_SEEKENTRY_SIZE 21 -/** per-cuepoint-track - 5 1-byte EBML IDs, 5 1-byte EBML sizes, 3 8-byte uint max - * and one 1-byte uint for the track number (this assumes MAX_TRACKS to be <= 255) */ -#define MAX_CUETRACKPOS_SIZE 35 - -/** per-cuepoint - 1 1-byte EBML ID, 1 1-byte EBML size, 8-byte uint max */ -#define MAX_CUEPOINT_CONTENT_SIZE(num_tracks) 10 + MAX_CUETRACKPOS_SIZE * num_tracks +/** 4 * (1-byte EBML ID, 1-byte EBML size, 8-byte uint max) */ +#define MAX_CUETRACKPOS_SIZE 40 /** Seek preroll value for opus */ #define OPUS_SEEK_PREROLL 80000000 @@ -509,58 +505,54 @@ static int mkv_add_cuepoint(MatroskaMuxContext *mkv, int stream, int tracknum, i static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tracks, int num_tracks) { MatroskaMuxContext *mkv = s->priv_data; - AVIOContext *dyn_cp, *pb = s->pb; + AVIOContext *dyn_cp, *pb = s->pb, *cuepoint; int64_t currentpos; - int i, j, ret; + int ret; currentpos = avio_tell(pb); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); if (ret < 0) return ret; - for (i = 0; i < cues->num_entries; i++) { - ebml_master cuepoint, track_positions; - mkv_cuepoint *entry = &cues->entries[i]; - uint64_t pts = entry->pts; - int ctp_nb = 0; - - // Calculate the number of entries, so we know the element size - for (j = 0; j < num_tracks; j++) - tracks[j].has_cue = 0; - for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) { - int idx = entry[j].stream_idx; + ret = avio_open_dyn_buf(&cuepoint); + if (ret < 0) { + ffio_free_dyn_buf(&dyn_cp); + return ret; + } - av_assert0(idx >= 0 && idx < num_tracks); - if (tracks[idx].has_cue && s->streams[idx]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) - continue; - tracks[idx].has_cue = 1; - ctp_nb ++; - } + for (mkv_cuepoint *entry = cues->entries, *end = entry + cues->num_entries; + entry < end;) { + uint64_t pts = entry->pts; + uint8_t *buf; + int size; - cuepoint = start_ebml_master(dyn_cp, MATROSKA_ID_POINTENTRY, MAX_CUEPOINT_CONTENT_SIZE(ctp_nb)); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUETIME, pts); + put_ebml_uint(cuepoint, MATROSKA_ID_CUETIME, pts); // put all the entries from different tracks that have the exact same // timestamp into the same CuePoint - for (j = 0; j < num_tracks; j++) + for (int j = 0; j < num_tracks; j++) tracks[j].has_cue = 0; - for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) { - int idx = entry[j].stream_idx; + do { + ebml_master track_positions; + int idx = entry->stream_idx; + av_assert0(idx >= 0 && idx < num_tracks); if (tracks[idx].has_cue && s->streams[idx]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) continue; tracks[idx].has_cue = 1; - track_positions = start_ebml_master(dyn_cp, MATROSKA_ID_CUETRACKPOSITION, MAX_CUETRACKPOS_SIZE); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUETRACK , entry[j].tracknum ); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUECLUSTERPOSITION , entry[j].cluster_pos); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUERELATIVEPOSITION, entry[j].relative_pos); - if (entry[j].duration != -1) - put_ebml_uint(dyn_cp, MATROSKA_ID_CUEDURATION , entry[j].duration); - end_ebml_master(dyn_cp, track_positions); - } - i += j - 1; - end_ebml_master(dyn_cp, cuepoint); - } + track_positions = start_ebml_master(cuepoint, MATROSKA_ID_CUETRACKPOSITION, MAX_CUETRACKPOS_SIZE); + put_ebml_uint(cuepoint, MATROSKA_ID_CUETRACK , entry->tracknum ); + put_ebml_uint(cuepoint, MATROSKA_ID_CUECLUSTERPOSITION , entry->cluster_pos); + put_ebml_uint(cuepoint, MATROSKA_ID_CUERELATIVEPOSITION, entry->relative_pos); + if (entry->duration != -1) + put_ebml_uint(cuepoint, MATROSKA_ID_CUEDURATION , entry->duration); + end_ebml_master(cuepoint, track_positions); + } while (++entry < end && entry->pts == pts); + size = avio_get_dyn_buf(cuepoint, &buf); + put_ebml_binary(dyn_cp, MATROSKA_ID_POINTENTRY, buf, size); + ffio_reset_dyn_buf(cuepoint); + } + ffio_free_dyn_buf(&cuepoint); end_ebml_master_crc32(pb, &dyn_cp, mkv); return currentpos; -- 2.20.1
When the Matroska muxer writes the Cues (the index), it groups index entries with the same timestamp into the same CuePoint to save space. But given Matroska's variable-length length fields, it either needs to have an upper bound of the final size of the CuePoint before writing it or the CuePoint has to be assembled in a different buffer, so that after having assembled the CuePoint (when the real size is known), the CuePoint's header can be written and its data copied after it. The first of these approaches is the currently used one. This entails finding out the number of entries in a CuePoint before starting the CuePoint and therefore means that the list is read at least twice. Furthermore, a worst-case upper-bound for the length of a single entry was used, so that sometimes bytes are wasted on length fields. This commit switches to the second approach. This is no longer more expensive than the current approach if one only resets the dynamic buffer used to write the CuePoint's content instead of opening a new buffer for every CuePoint: Writing the trailer of a file with 540.000 CuePoints improved actually from 219054414 decicycles to 2164379394 decicycles (based upon 50 iterations). Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- The earlier commit message falsely stated to be about matroskadec. This fixes this. Nothing else changed. libavformat/matroskaenc.c | 74 +++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index bbf3a3de7f..a5fe509fdc 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -170,12 +170,8 @@ typedef struct MatroskaMuxContext { * offset, 4 bytes for target EBML ID */ #define MAX_SEEKENTRY_SIZE 21 -/** per-cuepoint-track - 5 1-byte EBML IDs, 5 1-byte EBML sizes, 3 8-byte uint max - * and one 1-byte uint for the track number (this assumes MAX_TRACKS to be <= 255) */ -#define MAX_CUETRACKPOS_SIZE 35 - -/** per-cuepoint - 1 1-byte EBML ID, 1 1-byte EBML size, 8-byte uint max */ -#define MAX_CUEPOINT_CONTENT_SIZE(num_tracks) 10 + MAX_CUETRACKPOS_SIZE * num_tracks +/** 4 * (1-byte EBML ID, 1-byte EBML size, 8-byte uint max) */ +#define MAX_CUETRACKPOS_SIZE 40 /** Seek preroll value for opus */ #define OPUS_SEEK_PREROLL 80000000 @@ -509,58 +505,54 @@ static int mkv_add_cuepoint(MatroskaMuxContext *mkv, int stream, int tracknum, i static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tracks, int num_tracks) { MatroskaMuxContext *mkv = s->priv_data; - AVIOContext *dyn_cp, *pb = s->pb; + AVIOContext *dyn_cp, *pb = s->pb, *cuepoint; int64_t currentpos; - int i, j, ret; + int ret; currentpos = avio_tell(pb); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); if (ret < 0) return ret; - for (i = 0; i < cues->num_entries; i++) { - ebml_master cuepoint, track_positions; - mkv_cuepoint *entry = &cues->entries[i]; - uint64_t pts = entry->pts; - int ctp_nb = 0; - - // Calculate the number of entries, so we know the element size - for (j = 0; j < num_tracks; j++) - tracks[j].has_cue = 0; - for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) { - int idx = entry[j].stream_idx; + ret = avio_open_dyn_buf(&cuepoint); + if (ret < 0) { + ffio_free_dyn_buf(&dyn_cp); + return ret; + } - av_assert0(idx >= 0 && idx < num_tracks); - if (tracks[idx].has_cue && s->streams[idx]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) - continue; - tracks[idx].has_cue = 1; - ctp_nb ++; - } + for (mkv_cuepoint *entry = cues->entries, *end = entry + cues->num_entries; + entry < end;) { + uint64_t pts = entry->pts; + uint8_t *buf; + int size; - cuepoint = start_ebml_master(dyn_cp, MATROSKA_ID_POINTENTRY, MAX_CUEPOINT_CONTENT_SIZE(ctp_nb)); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUETIME, pts); + put_ebml_uint(cuepoint, MATROSKA_ID_CUETIME, pts); // put all the entries from different tracks that have the exact same // timestamp into the same CuePoint - for (j = 0; j < num_tracks; j++) + for (int j = 0; j < num_tracks; j++) tracks[j].has_cue = 0; - for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) { - int idx = entry[j].stream_idx; + do { + ebml_master track_positions; + int idx = entry->stream_idx; + av_assert0(idx >= 0 && idx < num_tracks); if (tracks[idx].has_cue && s->streams[idx]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) continue; tracks[idx].has_cue = 1; - track_positions = start_ebml_master(dyn_cp, MATROSKA_ID_CUETRACKPOSITION, MAX_CUETRACKPOS_SIZE); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUETRACK , entry[j].tracknum ); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUECLUSTERPOSITION , entry[j].cluster_pos); - put_ebml_uint(dyn_cp, MATROSKA_ID_CUERELATIVEPOSITION, entry[j].relative_pos); - if (entry[j].duration != -1) - put_ebml_uint(dyn_cp, MATROSKA_ID_CUEDURATION , entry[j].duration); - end_ebml_master(dyn_cp, track_positions); - } - i += j - 1; - end_ebml_master(dyn_cp, cuepoint); - } + track_positions = start_ebml_master(cuepoint, MATROSKA_ID_CUETRACKPOSITION, MAX_CUETRACKPOS_SIZE); + put_ebml_uint(cuepoint, MATROSKA_ID_CUETRACK , entry->tracknum ); + put_ebml_uint(cuepoint, MATROSKA_ID_CUECLUSTERPOSITION , entry->cluster_pos); + put_ebml_uint(cuepoint, MATROSKA_ID_CUERELATIVEPOSITION, entry->relative_pos); + if (entry->duration != -1) + put_ebml_uint(cuepoint, MATROSKA_ID_CUEDURATION , entry->duration); + end_ebml_master(cuepoint, track_positions); + } while (++entry < end && entry->pts == pts); + size = avio_get_dyn_buf(cuepoint, &buf); + put_ebml_binary(dyn_cp, MATROSKA_ID_POINTENTRY, buf, size); + ffio_reset_dyn_buf(cuepoint); + } + ffio_free_dyn_buf(&cuepoint); end_ebml_master_crc32(pb, &dyn_cp, mkv); return currentpos; -- 2.20.1
Up until now, writing level 1 elements proceeded as follows: First, the element id was written to the ordinary output AVIOContext and a dynamic buffer was opened for the content of the level 1 element in start_ebml_master_crc32(). Then this buffer was actually used and after it was closed (in end_ebml_master_crc32()), the size field corresponding to the buffer's size was written, after which the actual data was written. This commit changes this: Nothing is written to the main AVIOContext any more in start_ebml_master_crc32(). end_ebml_master_crc32() now writes both the id, the length field as well as the data. This implies that one can start a level 1 element in memory without outputting anything. This is done to enable to test whether enough space has been reserved for the Cues (if space has been reserved for them) before writing them. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 60 +++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 6f0760fa69..f9509129c8 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -325,26 +325,26 @@ static void end_ebml_master(AVIOContext *pb, ebml_master master) avio_seek(pb, pos, SEEK_SET); } -static int start_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, - uint32_t elementid) +static int start_ebml_master_crc32(AVIOContext **dyn_cp, MatroskaMuxContext *mkv) { int ret; if ((ret = avio_open_dyn_buf(dyn_cp)) < 0) return ret; - put_ebml_id(pb, elementid); if (mkv->write_crc) put_ebml_void(*dyn_cp, 6); /* Reserve space for CRC32 so position/size calculations using avio_tell() take it into account */ return 0; } -static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv) +static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, + MatroskaMuxContext *mkv, uint32_t id) { uint8_t *buf, crc[4]; int size, skip = 0; + put_ebml_id(pb, id); size = avio_close_dyn_buf(*dyn_cp, &buf); put_ebml_num(pb, size, 0); if (mkv->write_crc) { @@ -362,13 +362,14 @@ static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, Matrosk * Complete ebml master without destroying the buffer, allowing for later updates */ static void end_ebml_master_crc32_preliminary(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, - int64_t *pos) + uint32_t id, int64_t *pos) { uint8_t *buf; int size = avio_get_dyn_buf(*dyn_cp, &buf); *pos = avio_tell(pb); + put_ebml_id(pb, id); put_ebml_num(pb, size, 0); avio_write(pb, buf, size); } @@ -450,7 +451,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, goto seek; } - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; @@ -466,7 +467,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, put_ebml_uint(dyn_cp, MATROSKA_ID_SEEKPOSITION, entry->segmentpos); end_ebml_master(dyn_cp, seekentry); } - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); @@ -510,7 +511,7 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra int ret; currentpos = avio_tell(pb); - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; @@ -553,7 +554,7 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra ffio_reset_dyn_buf(cuepoint); } ffio_free_dyn_buf(&cuepoint); - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); return currentpos; } @@ -1373,7 +1374,7 @@ static int mkv_write_tracks(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_TRACKS, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); + ret = start_ebml_master_crc32(&mkv->tracks_bc, mkv); if (ret < 0) return ret; @@ -1388,9 +1389,10 @@ static int mkv_write_tracks(AVFormatContext *s) } if ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(pb, &mkv->tracks_bc, mkv, &mkv->tracks_pos); + end_ebml_master_crc32_preliminary(pb, &mkv->tracks_bc, mkv, + MATROSKA_ID_TRACKS, &mkv->tracks_pos); else - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); return 0; } @@ -1408,7 +1410,7 @@ static int mkv_write_chapters(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_CHAPTERS, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; editionentry = start_ebml_master(dyn_cp, MATROSKA_ID_EDITIONENTRY, 0); @@ -1446,7 +1448,7 @@ static int mkv_write_chapters(AVFormatContext *s) end_ebml_master(dyn_cp, chapteratom); } end_ebml_master(dyn_cp, editionentry); - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); mkv->wrote_chapters = 1; return 0; @@ -1497,7 +1499,7 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, if (!mkv->tags_bc) { mkv_add_seekhead_entry(mkv, MATROSKA_ID_TAGS, avio_tell(s->pb)); - ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + ret = start_ebml_master_crc32(&mkv->tags_bc, mkv); if (ret < 0) return ret; } @@ -1642,9 +1644,10 @@ static int mkv_write_tags(AVFormatContext *s) if (mkv->tags_bc) { if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->tags_bc, mkv, &mkv->tags_pos); + end_ebml_master_crc32_preliminary(s->pb, &mkv->tags_bc, mkv, + MATROSKA_ID_TAGS, &mkv->tags_pos); else - end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); } return 0; } @@ -1667,7 +1670,7 @@ static int mkv_write_attachments(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; for (i = 0; i < s->nb_streams; i++) { @@ -1740,7 +1743,7 @@ static int mkv_write_attachments(AVFormatContext *s) mkv->attachments->entries[mkv->attachments->num_entries].stream_idx = i; mkv->attachments->entries[mkv->attachments->num_entries++].fileuid = fileuid; } - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); return 0; } @@ -1819,7 +1822,7 @@ static int mkv_write_header(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_INFO, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); + ret = start_ebml_master_crc32(&mkv->info_bc, mkv); if (ret < 0) return ret; pb = mkv->info_bc; @@ -1878,9 +1881,10 @@ static int mkv_write_header(AVFormatContext *s) } } if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->info_bc, mkv, &mkv->info_pos); + end_ebml_master_crc32_preliminary(s->pb, &mkv->info_bc, mkv, + MATROSKA_ID_INFO, &mkv->info_pos); else - end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); pb = s->pb; ret = mkv_write_tracks(s); @@ -2162,7 +2166,7 @@ static void mkv_end_cluster(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; - end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); mkv->cluster_pos = -1; avio_flush(s->pb); } @@ -2303,7 +2307,7 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_ if (mkv->cluster_pos == -1) { mkv->cluster_pos = avio_tell(s->pb); - ret = start_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); + ret = start_ebml_master_crc32(&mkv->cluster_bc, mkv); if (ret < 0) return ret; put_ebml_uint(mkv->cluster_bc, MATROSKA_ID_CLUSTERTIMECODE, FFMAX(0, ts)); @@ -2467,7 +2471,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->cluster_bc) { - end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv); + end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); } ret = mkv_write_chapters(s); @@ -2515,11 +2519,11 @@ static int mkv_write_trailer(AVFormatContext *s) av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); avio_seek(mkv->info_bc, mkv->duration_offset, SEEK_SET); put_ebml_float(mkv->info_bc, MATROSKA_ID_DURATION, mkv->duration); - end_ebml_master_crc32(pb, &mkv->info_bc, mkv); + end_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); // write tracks master avio_seek(pb, mkv->tracks_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); // update stream durations if (!mkv->is_live) { @@ -2549,7 +2553,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->tags_bc && !mkv->is_live) { avio_seek(pb, mkv->tags_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tags_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); } avio_seek(pb, currentpos, SEEK_SET); -- 2.20.1
Up until now, writing level 1 elements proceeded as follows: First, the element id was written to the ordinary output AVIOContext and a dynamic buffer was opened for the content of the level 1 element in start_ebml_master_crc32(). Then this buffer was actually used and after it was closed (in end_ebml_master_crc32()), the size field corresponding to the buffer's size was written, after which the actual data was written. This commit changes this: Nothing is written to the main AVIOContext any more in start_ebml_master_crc32(). end_ebml_master_crc32() now writes both the id, the length field as well as the data. This implies that one can start a level 1 element in memory without outputting anything. This is done to enable to test whether enough space has been reserved for the Cues (if space has been reserved for them) before writing them. A large duration between outputting the header and outputting the rest could also break certain streaming usecases like the one from #8578 (which this commit fixes). Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- The commit message has been updated in light of ticket #8578; nothing else has changed. I'd like to apply this and everything until (but excluding) #18 [1] in the coming days to fix the aforementioned issue (I will also backport a fix to 4.2). [1] is the cutoff point because upon rereading I am not sure anymore whether it is good to write the cues at the end if the reserved space doesn't suffice and return normally afterwards, because the fact that the cues have not been written at the front won't be detectable from the return value of av_write_trailer() any more (one only gets a AV_LOG_WARNING logmessage). [1]: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-January/255157.html libavformat/matroskaenc.c | 60 +++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 66f1bc4255..501d68a8f7 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -325,26 +325,26 @@ static void end_ebml_master(AVIOContext *pb, ebml_master master) avio_seek(pb, pos, SEEK_SET); } -static int start_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, - uint32_t elementid) +static int start_ebml_master_crc32(AVIOContext **dyn_cp, MatroskaMuxContext *mkv) { int ret; if ((ret = avio_open_dyn_buf(dyn_cp)) < 0) return ret; - put_ebml_id(pb, elementid); if (mkv->write_crc) put_ebml_void(*dyn_cp, 6); /* Reserve space for CRC32 so position/size calculations using avio_tell() take it into account */ return 0; } -static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv) +static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, + MatroskaMuxContext *mkv, uint32_t id) { uint8_t *buf, crc[4]; int size, skip = 0; + put_ebml_id(pb, id); size = avio_close_dyn_buf(*dyn_cp, &buf); put_ebml_num(pb, size, 0); if (mkv->write_crc) { @@ -362,13 +362,14 @@ static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, Matrosk * Complete ebml master without destroying the buffer, allowing for later updates */ static void end_ebml_master_crc32_preliminary(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, - int64_t *pos) + uint32_t id, int64_t *pos) { uint8_t *buf; int size = avio_get_dyn_buf(*dyn_cp, &buf); *pos = avio_tell(pb); + put_ebml_id(pb, id); put_ebml_num(pb, size, 0); avio_write(pb, buf, size); } @@ -450,7 +451,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, goto seek; } - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; @@ -466,7 +467,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, put_ebml_uint(dyn_cp, MATROSKA_ID_SEEKPOSITION, entry->segmentpos); end_ebml_master(dyn_cp, seekentry); } - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); @@ -510,7 +511,7 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra int ret; currentpos = avio_tell(pb); - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; @@ -553,7 +554,7 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra ffio_reset_dyn_buf(cuepoint); } ffio_free_dyn_buf(&cuepoint); - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); return currentpos; } @@ -1375,7 +1376,7 @@ static int mkv_write_tracks(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_TRACKS, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); + ret = start_ebml_master_crc32(&mkv->tracks_bc, mkv); if (ret < 0) return ret; @@ -1390,9 +1391,10 @@ static int mkv_write_tracks(AVFormatContext *s) } if ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(pb, &mkv->tracks_bc, mkv, &mkv->tracks_pos); + end_ebml_master_crc32_preliminary(pb, &mkv->tracks_bc, mkv, + MATROSKA_ID_TRACKS, &mkv->tracks_pos); else - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); return 0; } @@ -1410,7 +1412,7 @@ static int mkv_write_chapters(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_CHAPTERS, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; editionentry = start_ebml_master(dyn_cp, MATROSKA_ID_EDITIONENTRY, 0); @@ -1448,7 +1450,7 @@ static int mkv_write_chapters(AVFormatContext *s) end_ebml_master(dyn_cp, chapteratom); } end_ebml_master(dyn_cp, editionentry); - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); mkv->wrote_chapters = 1; return 0; @@ -1499,7 +1501,7 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, if (!mkv->tags_bc) { mkv_add_seekhead_entry(mkv, MATROSKA_ID_TAGS, avio_tell(s->pb)); - ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + ret = start_ebml_master_crc32(&mkv->tags_bc, mkv); if (ret < 0) return ret; } @@ -1644,9 +1646,10 @@ static int mkv_write_tags(AVFormatContext *s) if (mkv->tags_bc) { if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->tags_bc, mkv, &mkv->tags_pos); + end_ebml_master_crc32_preliminary(s->pb, &mkv->tags_bc, mkv, + MATROSKA_ID_TAGS, &mkv->tags_pos); else - end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); } return 0; } @@ -1669,7 +1672,7 @@ static int mkv_write_attachments(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret; for (i = 0; i < s->nb_streams; i++) { @@ -1742,7 +1745,7 @@ static int mkv_write_attachments(AVFormatContext *s) mkv->attachments->entries[mkv->attachments->num_entries].stream_idx = i; mkv->attachments->entries[mkv->attachments->num_entries++].fileuid = fileuid; } - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); return 0; } @@ -1821,7 +1824,7 @@ static int mkv_write_header(AVFormatContext *s) mkv_add_seekhead_entry(mkv, MATROSKA_ID_INFO, avio_tell(pb)); - ret = start_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); + ret = start_ebml_master_crc32(&mkv->info_bc, mkv); if (ret < 0) return ret; pb = mkv->info_bc; @@ -1880,9 +1883,10 @@ static int mkv_write_header(AVFormatContext *s) } } if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->info_bc, mkv, &mkv->info_pos); + end_ebml_master_crc32_preliminary(s->pb, &mkv->info_bc, mkv, + MATROSKA_ID_INFO, &mkv->info_pos); else - end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); pb = s->pb; ret = mkv_write_tracks(s); @@ -2170,7 +2174,7 @@ static void mkv_end_cluster(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; - end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); mkv->cluster_pos = -1; avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_FLUSH_POINT); } @@ -2311,7 +2315,7 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_ if (mkv->cluster_pos == -1) { mkv->cluster_pos = avio_tell(s->pb); - ret = start_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); + ret = start_ebml_master_crc32(&mkv->cluster_bc, mkv); if (ret < 0) return ret; put_ebml_uint(mkv->cluster_bc, MATROSKA_ID_CLUSTERTIMECODE, FFMAX(0, ts)); @@ -2477,7 +2481,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->cluster_bc) { - end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv); + end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); } ret = mkv_write_chapters(s); @@ -2525,11 +2529,11 @@ static int mkv_write_trailer(AVFormatContext *s) av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); avio_seek(mkv->info_bc, mkv->duration_offset, SEEK_SET); put_ebml_float(mkv->info_bc, MATROSKA_ID_DURATION, mkv->duration); - end_ebml_master_crc32(pb, &mkv->info_bc, mkv); + end_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); // write tracks master avio_seek(pb, mkv->tracks_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); // update stream durations if (!mkv->is_live) { @@ -2559,7 +2563,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->tags_bc && !mkv->is_live) { avio_seek(pb, mkv->tags_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tags_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); } avio_seek(pb, currentpos, SEEK_SET); -- 2.20.1
Am Mi., 25. März 2020 um 05:45 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
Up until now, writing level 1 elements proceeded as follows: First, the element id was written to the ordinary output AVIOContext and a dynamic buffer was opened for the content of the level 1 element in start_ebml_master_crc32(). Then this buffer was actually used and after it was closed (in end_ebml_master_crc32()), the size field corresponding to the buffer's size was written, after which the actual data was written.
This commit changes this: Nothing is written to the main AVIOContext any more in start_ebml_master_crc32(). end_ebml_master_crc32() now writes both the id, the length field as well as the data. This implies that one can start a level 1 element in memory without outputting anything. This is done to enable to test whether enough space has been reserved for the Cues (if space has been reserved for them) before writing them. A large duration between outputting the header and outputting the rest could also break certain streaming usecases like the one from #8578 (which this commit fixes).
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- The commit message has been updated in light of ticket #8578; nothing else has changed.
Please mention the ticket in the commit message. Thank you, Carl Eugen
Carl Eugen Hoyos:
Am Mi., 25. März 2020 um 05:45 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
Up until now, writing level 1 elements proceeded as follows: First, the element id was written to the ordinary output AVIOContext and a dynamic buffer was opened for the content of the level 1 element in start_ebml_master_crc32(). Then this buffer was actually used and after it was closed (in end_ebml_master_crc32()), the size field corresponding to the buffer's size was written, after which the actual data was written.
This commit changes this: Nothing is written to the main AVIOContext any more in start_ebml_master_crc32(). end_ebml_master_crc32() now writes both the id, the length field as well as the data. This implies that one can start a level 1 element in memory without outputting anything. This is done to enable to test whether enough space has been reserved for the Cues (if space has been reserved for them) before writing them. A large duration between outputting the header and outputting the rest could also break certain streaming usecases like the one from #8578 (which this commit fixes).
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- The commit message has been updated in light of ticket #8578; nothing else has changed.
Please mention the ticket in the commit message.
It is already contained in the commit message. - Andreas
Am Mi., 25. März 2020 um 18:08 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
Carl Eugen Hoyos:
Am Mi., 25. März 2020 um 05:45 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
Up until now, writing level 1 elements proceeded as follows: First, the element id was written to the ordinary output AVIOContext and a dynamic buffer was opened for the content of the level 1 element in start_ebml_master_crc32(). Then this buffer was actually used and after it was closed (in end_ebml_master_crc32()), the size field corresponding to the buffer's size was written, after which the actual data was written.
This commit changes this: Nothing is written to the main AVIOContext any more in start_ebml_master_crc32(). end_ebml_master_crc32() now writes both the id, the length field as well as the data. This implies that one can start a level 1 element in memory without outputting anything. This is done to enable to test whether enough space has been reserved for the Cues (if space has been reserved for them) before writing them. A large duration between outputting the header and outputting the rest could also break certain streaming usecases like the one from #8578 (which this commit fixes).
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- The commit message has been updated in light of ticket #8578; nothing else has changed.
Please mention the ticket in the commit message.
It is already contained in the commit message.
Found it. Sorry, Carl Eugen
On 3/25/2020 1:44 AM, Andreas Rheinhardt wrote:
Up until now, writing level 1 elements proceeded as follows: First, the element id was written to the ordinary output AVIOContext and a dynamic buffer was opened for the content of the level 1 element in start_ebml_master_crc32(). Then this buffer was actually used and after it was closed (in end_ebml_master_crc32()), the size field corresponding to the buffer's size was written, after which the actual data was written.
This commit changes this: Nothing is written to the main AVIOContext any more in start_ebml_master_crc32(). end_ebml_master_crc32() now writes both the id, the length field as well as the data. This implies that one can start a level 1 element in memory without outputting anything. This is done to enable to test whether enough space has been reserved for the Cues (if space has been reserved for them) before writing them. A large duration between outputting the header and outputting the rest could also break certain streaming usecases like the one from #8578 (which this commit fixes).
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- The commit message has been updated in light of ticket #8578; nothing else has changed.
Should be ok. And please backport it to release/4.2 branch using git cherry-pick -x, so the commit hash from master is referenced.
I'd like to apply this and everything until (but excluding) #18 [1] in the coming days to fix the aforementioned issue (I will also backport a fix to 4.2).
[1] is the cutoff point because upon rereading I am not sure anymore whether it is good to write the cues at the end if the reserved space doesn't suffice and return normally afterwards, because the fact that the cues have not been written at the front won't be detectable from the return value of av_write_trailer() any more (one only gets a AV_LOG_WARNING logmessage).
[1]: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-January/255157.html
libavformat/matroskaenc.c | 60 +++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 28 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 66f1bc4255..501d68a8f7 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -325,26 +325,26 @@ static void end_ebml_master(AVIOContext *pb, ebml_master master) avio_seek(pb, pos, SEEK_SET); }
-static int start_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, - uint32_t elementid) +static int start_ebml_master_crc32(AVIOContext **dyn_cp, MatroskaMuxContext *mkv) { int ret;
if ((ret = avio_open_dyn_buf(dyn_cp)) < 0) return ret;
- put_ebml_id(pb, elementid); if (mkv->write_crc) put_ebml_void(*dyn_cp, 6); /* Reserve space for CRC32 so position/size calculations using avio_tell() take it into account */
return 0; }
-static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv) +static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, + MatroskaMuxContext *mkv, uint32_t id) { uint8_t *buf, crc[4]; int size, skip = 0;
+ put_ebml_id(pb, id); size = avio_close_dyn_buf(*dyn_cp, &buf); put_ebml_num(pb, size, 0); if (mkv->write_crc) { @@ -362,13 +362,14 @@ static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, Matrosk * Complete ebml master without destroying the buffer, allowing for later updates */ static void end_ebml_master_crc32_preliminary(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, - int64_t *pos) + uint32_t id, int64_t *pos) { uint8_t *buf; int size = avio_get_dyn_buf(*dyn_cp, &buf);
*pos = avio_tell(pb);
+ put_ebml_id(pb, id); put_ebml_num(pb, size, 0); avio_write(pb, buf, size); } @@ -450,7 +451,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, goto seek; }
- ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret;
@@ -466,7 +467,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, put_ebml_uint(dyn_cp, MATROSKA_ID_SEEKPOSITION, entry->segmentpos); end_ebml_master(dyn_cp, seekentry); } - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD);
remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); @@ -510,7 +511,7 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra int ret;
currentpos = avio_tell(pb); - ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret;
@@ -553,7 +554,7 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra ffio_reset_dyn_buf(cuepoint); } ffio_free_dyn_buf(&cuepoint); - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES);
return currentpos; } @@ -1375,7 +1376,7 @@ static int mkv_write_tracks(AVFormatContext *s)
mkv_add_seekhead_entry(mkv, MATROSKA_ID_TRACKS, avio_tell(pb));
- ret = start_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); + ret = start_ebml_master_crc32(&mkv->tracks_bc, mkv); if (ret < 0) return ret;
@@ -1390,9 +1391,10 @@ static int mkv_write_tracks(AVFormatContext *s) }
if ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(pb, &mkv->tracks_bc, mkv, &mkv->tracks_pos); + end_ebml_master_crc32_preliminary(pb, &mkv->tracks_bc, mkv, + MATROSKA_ID_TRACKS, &mkv->tracks_pos); else - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS);
return 0; } @@ -1410,7 +1412,7 @@ static int mkv_write_chapters(AVFormatContext *s)
mkv_add_seekhead_entry(mkv, MATROSKA_ID_CHAPTERS, avio_tell(pb));
- ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret;
editionentry = start_ebml_master(dyn_cp, MATROSKA_ID_EDITIONENTRY, 0); @@ -1448,7 +1450,7 @@ static int mkv_write_chapters(AVFormatContext *s) end_ebml_master(dyn_cp, chapteratom); } end_ebml_master(dyn_cp, editionentry); - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS);
mkv->wrote_chapters = 1; return 0; @@ -1499,7 +1501,7 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, if (!mkv->tags_bc) { mkv_add_seekhead_entry(mkv, MATROSKA_ID_TAGS, avio_tell(s->pb));
- ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + ret = start_ebml_master_crc32(&mkv->tags_bc, mkv); if (ret < 0) return ret; } @@ -1644,9 +1646,10 @@ static int mkv_write_tags(AVFormatContext *s)
if (mkv->tags_bc) { if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->tags_bc, mkv, &mkv->tags_pos); + end_ebml_master_crc32_preliminary(s->pb, &mkv->tags_bc, mkv, + MATROSKA_ID_TAGS, &mkv->tags_pos); else - end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); } return 0; } @@ -1669,7 +1672,7 @@ static int mkv_write_attachments(AVFormatContext *s)
mkv_add_seekhead_entry(mkv, MATROSKA_ID_ATTACHMENTS, avio_tell(pb));
- ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); + ret = start_ebml_master_crc32(&dyn_cp, mkv); if (ret < 0) return ret;
for (i = 0; i < s->nb_streams; i++) { @@ -1742,7 +1745,7 @@ static int mkv_write_attachments(AVFormatContext *s) mkv->attachments->entries[mkv->attachments->num_entries].stream_idx = i; mkv->attachments->entries[mkv->attachments->num_entries++].fileuid = fileuid; } - end_ebml_master_crc32(pb, &dyn_cp, mkv); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS);
return 0; } @@ -1821,7 +1824,7 @@ static int mkv_write_header(AVFormatContext *s)
mkv_add_seekhead_entry(mkv, MATROSKA_ID_INFO, avio_tell(pb));
- ret = start_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); + ret = start_ebml_master_crc32(&mkv->info_bc, mkv); if (ret < 0) return ret; pb = mkv->info_bc; @@ -1880,9 +1883,10 @@ static int mkv_write_header(AVFormatContext *s) } } if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->info_bc, mkv, &mkv->info_pos); + end_ebml_master_crc32_preliminary(s->pb, &mkv->info_bc, mkv, + MATROSKA_ID_INFO, &mkv->info_pos); else - end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); pb = s->pb;
ret = mkv_write_tracks(s); @@ -2170,7 +2174,7 @@ static void mkv_end_cluster(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data;
- end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv); + end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); mkv->cluster_pos = -1; avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_FLUSH_POINT); } @@ -2311,7 +2315,7 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_
if (mkv->cluster_pos == -1) { mkv->cluster_pos = avio_tell(s->pb); - ret = start_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); + ret = start_ebml_master_crc32(&mkv->cluster_bc, mkv); if (ret < 0) return ret; put_ebml_uint(mkv->cluster_bc, MATROSKA_ID_CLUSTERTIMECODE, FFMAX(0, ts)); @@ -2477,7 +2481,7 @@ static int mkv_write_trailer(AVFormatContext *s) }
if (mkv->cluster_bc) { - end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv); + end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); }
ret = mkv_write_chapters(s); @@ -2525,11 +2529,11 @@ static int mkv_write_trailer(AVFormatContext *s) av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); avio_seek(mkv->info_bc, mkv->duration_offset, SEEK_SET); put_ebml_float(mkv->info_bc, MATROSKA_ID_DURATION, mkv->duration); - end_ebml_master_crc32(pb, &mkv->info_bc, mkv); + end_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO);
// write tracks master avio_seek(pb, mkv->tracks_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS);
// update stream durations if (!mkv->is_live) { @@ -2559,7 +2563,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->tags_bc && !mkv->is_live) { avio_seek(pb, mkv->tags_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tags_bc, mkv); + end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); }
avio_seek(pb, currentpos, SEEK_SET);
end_ebml_master_crc32_preliminary() has a MatroskaMuxContext as parameter that isn't used at all. So remove it. Furthermore it doesn't close its dynamic buffer; it just uses the underlying buffer and therefore it only needs a pointer to the dynamic buffer, not a pointer to a pointer. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index f9509129c8..c0979feb88 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -361,11 +361,11 @@ static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, /** * Complete ebml master without destroying the buffer, allowing for later updates */ -static void end_ebml_master_crc32_preliminary(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, +static void end_ebml_master_crc32_preliminary(AVIOContext *pb, AVIOContext *dyn_cp, uint32_t id, int64_t *pos) { uint8_t *buf; - int size = avio_get_dyn_buf(*dyn_cp, &buf); + int size = avio_get_dyn_buf(dyn_cp, &buf); *pos = avio_tell(pb); @@ -1389,7 +1389,7 @@ static int mkv_write_tracks(AVFormatContext *s) } if ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(pb, &mkv->tracks_bc, mkv, + end_ebml_master_crc32_preliminary(pb, mkv->tracks_bc, MATROSKA_ID_TRACKS, &mkv->tracks_pos); else end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); @@ -1644,7 +1644,7 @@ static int mkv_write_tags(AVFormatContext *s) if (mkv->tags_bc) { if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->tags_bc, mkv, + end_ebml_master_crc32_preliminary(s->pb, mkv->tags_bc, MATROSKA_ID_TAGS, &mkv->tags_pos); else end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); @@ -1881,7 +1881,7 @@ static int mkv_write_header(AVFormatContext *s) } } if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) - end_ebml_master_crc32_preliminary(s->pb, &mkv->info_bc, mkv, + end_ebml_master_crc32_preliminary(s->pb, mkv->info_bc, MATROSKA_ID_INFO, &mkv->info_pos); else end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); -- 2.20.1
before setting the field indicating that a Cluster has been opened. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index c0979feb88..d7aa832eb3 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2306,10 +2306,10 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_ } if (mkv->cluster_pos == -1) { - mkv->cluster_pos = avio_tell(s->pb); ret = start_ebml_master_crc32(&mkv->cluster_bc, mkv); if (ret < 0) return ret; + mkv->cluster_pos = avio_tell(s->pb); put_ebml_uint(mkv->cluster_bc, MATROSKA_ID_CLUSTERTIMECODE, FFMAX(0, ts)); mkv->cluster_pts = FFMAX(0, ts); av_log(s, AV_LOG_DEBUG, -- 2.20.1
When the user opted to write the Cues at the beginning, the Cues were simply written without checking in advance whether enough space has been reserved for them. If it wasn't enough, the data following the space reserved for the Cues was simply overwritten, corrupting the file. This commit changes this by checking whether enough space has been reserved for the Cues before outputting anything. If it isn't enough, the Cues will be output at the end as usual. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- doc/muxers.texi | 4 +- libavformat/matroskaenc.c | 79 ++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/doc/muxers.texi b/doc/muxers.texi index 5d7ff1ab3b..9eca32c1ca 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -1314,8 +1314,8 @@ index at the beginning of the file. If this option is set to a non-zero value, the muxer will reserve a given amount of space in the file header and then try to write the cues there when the muxing -finishes. If the available space does not suffice, muxing will fail. A safe size -for most use cases should be about 50kB per hour of video. +finishes. If the reserved space does not suffice, the Cues will be written at +the end. A safe size for most use cases should be about 50kB per hour of video. Note that cues are only written if the output is seekable and this option will have no effect if it is not. diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index d7aa832eb3..4e739d245b 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -503,23 +503,15 @@ static int mkv_add_cuepoint(MatroskaMuxContext *mkv, int stream, int tracknum, i return 0; } -static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tracks, int num_tracks) +static int mkv_assemble_cues(AVStream **streams, AVIOContext *dyn_cp, + mkv_cues *cues, mkv_track *tracks, int num_tracks) { - MatroskaMuxContext *mkv = s->priv_data; - AVIOContext *dyn_cp, *pb = s->pb, *cuepoint; - int64_t currentpos; + AVIOContext *cuepoint; int ret; - currentpos = avio_tell(pb); - ret = start_ebml_master_crc32(&dyn_cp, mkv); - if (ret < 0) - return ret; - ret = avio_open_dyn_buf(&cuepoint); - if (ret < 0) { - ffio_free_dyn_buf(&dyn_cp); + if (ret < 0) return ret; - } for (mkv_cuepoint *entry = cues->entries, *end = entry + cues->num_entries; entry < end;) { @@ -538,7 +530,7 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra int idx = entry->stream_idx; av_assert0(idx >= 0 && idx < num_tracks); - if (tracks[idx].has_cue && s->streams[idx]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) + if (tracks[idx].has_cue && streams[idx]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) continue; tracks[idx].has_cue = 1; track_positions = start_ebml_master(cuepoint, MATROSKA_ID_CUETRACKPOSITION, MAX_CUETRACKPOS_SIZE); @@ -554,9 +546,8 @@ static int64_t mkv_write_cues(AVFormatContext *s, mkv_cues *cues, mkv_track *tra ffio_reset_dyn_buf(cuepoint); } ffio_free_dyn_buf(&cuepoint); - end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CUES); - return currentpos; + return 0; } static int put_xiph_codecpriv(AVFormatContext *s, AVIOContext *pb, AVCodecParameters *par) @@ -2457,7 +2448,7 @@ static int mkv_write_trailer(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; AVIOContext *pb = s->pb; - int64_t currentpos, cuespos; + int64_t currentpos; int ret; // check if we have an audio packet cached @@ -2480,33 +2471,45 @@ static int mkv_write_trailer(AVFormatContext *s) if ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) { + int64_t ret64; + if (mkv->cues.num_entries) { - if (mkv->reserve_cues_space) { - int64_t cues_end; - - currentpos = avio_tell(pb); - avio_seek(pb, mkv->cues_pos, SEEK_SET); - - cuespos = mkv_write_cues(s, &mkv->cues, mkv->tracks, s->nb_streams); - cues_end = avio_tell(pb); - if (cues_end > cuespos + mkv->reserve_cues_space) { - av_log(s, AV_LOG_ERROR, - "Insufficient space reserved for cues: %d " - "(needed: %" PRId64 ").\n", - mkv->reserve_cues_space, cues_end - cuespos); - return AVERROR(EINVAL); - } + AVIOContext *cues; + uint64_t size; - if (cues_end < cuespos + mkv->reserve_cues_space) - put_ebml_void(pb, mkv->reserve_cues_space - - (cues_end - cuespos)); + ret = start_ebml_master_crc32(&cues, mkv); + if (ret < 0) + return ret; + ret = mkv_assemble_cues(s->streams, cues, &mkv->cues, + mkv->tracks, s->nb_streams); + if (ret < 0) + return ret; + + if (mkv->reserve_cues_space) { + size = avio_tell(cues); + size += 4 + ebml_num_size(size); + if (mkv->reserve_cues_space < size) { + av_log(s, AV_LOG_WARNING, + "Insufficient space reserved for Cues: %d < " + "%"PRIu64". Writing Cues at the end of the file.\n", + mkv->reserve_cues_space, size); + mkv->reserve_cues_space = 0; + } else { + currentpos = avio_tell(pb); + if ((ret64 = avio_seek(pb, mkv->cues_pos, SEEK_SET)) < 0) { + ffio_free_dyn_buf(&cues); + return ret64; + } + } + } + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, avio_tell(pb)); + end_ebml_master_crc32(pb, &cues, mkv, MATROSKA_ID_CUES); + if (mkv->reserve_cues_space) { + if (size < mkv->reserve_cues_space) + put_ebml_void(pb, mkv->reserve_cues_space - size); avio_seek(pb, currentpos, SEEK_SET); - } else { - cuespos = mkv_write_cues(s, &mkv->cues, mkv->tracks, s->nb_streams); } - - mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); } currentpos = avio_tell(pb); -- 2.20.1
The Matroska muxer has the ability to write the Cues (the index) at the beginning of the file (in front of the Cluster): The user inputs the amount of space that should be reserved at the beginning of the file and if this is sufficient, the Cues will be written there and the part of the reserved space not used up by the Cues will be filled with a "Void" element. There is just one problem with this: One can not fill a single byte this way, because said Void element is minimally two bytes long (one byte ID, one byte length field). Up until now, if one reserved one byte more than needed, one would run into an assert when writing the Void element. There are two solutions for this: Error out if it happens. Or adjust the length field of the Cues in order to ensure that the above situation can't happen (i.e. write the length on one byte more than necessary). The first solution is very unsatisfactory, as enough space has been reserved. Therefore this commit implements the second solution. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 42 +++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 4e739d245b..24317d3819 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -339,14 +339,15 @@ static int start_ebml_master_crc32(AVIOContext **dyn_cp, MatroskaMuxContext *mkv } static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, - MatroskaMuxContext *mkv, uint32_t id) + MatroskaMuxContext *mkv, uint32_t id, + int length_size) { uint8_t *buf, crc[4]; int size, skip = 0; put_ebml_id(pb, id); size = avio_close_dyn_buf(*dyn_cp, &buf); - put_ebml_num(pb, size, 0); + put_ebml_num(pb, size, length_size); if (mkv->write_crc) { skip = 6; /* Skip reserved 6-byte long void element from the dynamic buffer. */ AV_WL32(crc, av_crc(av_crc_get_table(AV_CRC_32_IEEE_LE), UINT32_MAX, buf + skip, size - skip) ^ UINT32_MAX); @@ -467,7 +468,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, put_ebml_uint(dyn_cp, MATROSKA_ID_SEEKPOSITION, entry->segmentpos); end_ebml_master(dyn_cp, seekentry); } - end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD, 0); remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); @@ -1383,7 +1384,7 @@ static int mkv_write_tracks(AVFormatContext *s) end_ebml_master_crc32_preliminary(pb, mkv->tracks_bc, MATROSKA_ID_TRACKS, &mkv->tracks_pos); else - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS, 0); return 0; } @@ -1439,7 +1440,7 @@ static int mkv_write_chapters(AVFormatContext *s) end_ebml_master(dyn_cp, chapteratom); } end_ebml_master(dyn_cp, editionentry); - end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS, 0); mkv->wrote_chapters = 1; return 0; @@ -1638,7 +1639,7 @@ static int mkv_write_tags(AVFormatContext *s) end_ebml_master_crc32_preliminary(s->pb, mkv->tags_bc, MATROSKA_ID_TAGS, &mkv->tags_pos); else - end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0); } return 0; } @@ -1734,7 +1735,7 @@ static int mkv_write_attachments(AVFormatContext *s) mkv->attachments->entries[mkv->attachments->num_entries].stream_idx = i; mkv->attachments->entries[mkv->attachments->num_entries++].fileuid = fileuid; } - end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS, 0); return 0; } @@ -1875,7 +1876,7 @@ static int mkv_write_header(AVFormatContext *s) end_ebml_master_crc32_preliminary(s->pb, mkv->info_bc, MATROSKA_ID_INFO, &mkv->info_pos); else - end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); + end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO, 0); pb = s->pb; ret = mkv_write_tracks(s); @@ -2157,7 +2158,7 @@ static void mkv_end_cluster(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; - end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); + end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER, 0); mkv->cluster_pos = -1; avio_flush(s->pb); } @@ -2462,7 +2463,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->cluster_bc) { - end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER); + end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER, 0); } ret = mkv_write_chapters(s); @@ -2476,6 +2477,7 @@ static int mkv_write_trailer(AVFormatContext *s) if (mkv->cues.num_entries) { AVIOContext *cues; uint64_t size; + int length_size = 0; ret = start_ebml_master_crc32(&cues, mkv); if (ret < 0) @@ -2488,7 +2490,8 @@ static int mkv_write_trailer(AVFormatContext *s) if (mkv->reserve_cues_space) { size = avio_tell(cues); - size += 4 + ebml_num_size(size); + length_size = ebml_num_size(size); + size += 4 + length_size; if (mkv->reserve_cues_space < size) { av_log(s, AV_LOG_WARNING, "Insufficient space reserved for Cues: %d < " @@ -2501,10 +2504,19 @@ static int mkv_write_trailer(AVFormatContext *s) ffio_free_dyn_buf(&cues); return ret64; } + if (mkv->reserve_cues_space == size + 1) { + /* There is no way to reserve a single byte because + * the minimal size of an EBML Void element is 2 + * (1 byte ID, 1 byte length field). This problem + * is solved by writing the Cues' length field on + * one byte more than necessary. */ + length_size++; + size++; + } } } mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, avio_tell(pb)); - end_ebml_master_crc32(pb, &cues, mkv, MATROSKA_ID_CUES); + end_ebml_master_crc32(pb, &cues, mkv, MATROSKA_ID_CUES, length_size); if (mkv->reserve_cues_space) { if (size < mkv->reserve_cues_space) put_ebml_void(pb, mkv->reserve_cues_space - size); @@ -2522,11 +2534,11 @@ static int mkv_write_trailer(AVFormatContext *s) av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); avio_seek(mkv->info_bc, mkv->duration_offset, SEEK_SET); put_ebml_float(mkv->info_bc, MATROSKA_ID_DURATION, mkv->duration); - end_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); + end_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO, 0); // write tracks master avio_seek(pb, mkv->tracks_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS, 0); // update stream durations if (!mkv->is_live) { @@ -2556,7 +2568,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->tags_bc && !mkv->is_live) { avio_seek(pb, mkv->tags_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); + end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0); } avio_seek(pb, currentpos, SEEK_SET); -- 2.20.1
When the Cues are written in front of the Cluster, the muxer would seek to the beginning (to where the Cues ought to be written) and write the Cues; afterwards it would seek back to the end of the file only to seek to the beginning once again to update several elements there. This commit removes the seek to the end. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 24317d3819..95d97a10c5 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2449,7 +2449,6 @@ static int mkv_write_trailer(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; AVIOContext *pb = s->pb; - int64_t currentpos; int ret; // check if we have an audio packet cached @@ -2472,11 +2471,14 @@ static int mkv_write_trailer(AVFormatContext *s) if ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) { - int64_t ret64; + int64_t endpos, ret64; + + endpos = avio_tell(pb); if (mkv->cues.num_entries) { AVIOContext *cues; uint64_t size; + int64_t cuespos = endpos; int length_size = 0; ret = start_ebml_master_crc32(&cues, mkv); @@ -2499,7 +2501,7 @@ static int mkv_write_trailer(AVFormatContext *s) mkv->reserve_cues_space, size); mkv->reserve_cues_space = 0; } else { - currentpos = avio_tell(pb); + cuespos = mkv->cues_pos; if ((ret64 = avio_seek(pb, mkv->cues_pos, SEEK_SET)) < 0) { ffio_free_dyn_buf(&cues); return ret64; @@ -2515,17 +2517,15 @@ static int mkv_write_trailer(AVFormatContext *s) } } } - mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, avio_tell(pb)); + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); end_ebml_master_crc32(pb, &cues, mkv, MATROSKA_ID_CUES, length_size); if (mkv->reserve_cues_space) { if (size < mkv->reserve_cues_space) put_ebml_void(pb, mkv->reserve_cues_space - size); - avio_seek(pb, currentpos, SEEK_SET); - } + } else + endpos = avio_tell(pb); } - currentpos = avio_tell(pb); - ret = mkv_write_seekhead(pb, mkv, 1, mkv->info_pos); if (ret < 0) return ret; @@ -2571,7 +2571,7 @@ static int mkv_write_trailer(AVFormatContext *s) end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0); } - avio_seek(pb, currentpos, SEEK_SET); + avio_seek(pb, endpos, SEEK_SET); } if (!mkv->is_live) { -- 2.20.1
Reserving space in Matroska works by writing a Void element. And until now this worked as follows: The current position was recorded and the EBML ID as well as the length field written; then the new position was recorded to know how much more to write. Afterwards the actual writing has been performed via ffio_fill(). But it is unnecessary to explicitly use the positions (obtained via avio_tell()) to find out how much still needs to be written, because the length of the ID and the length field are known. So rewrite the function to no longer use them. Also, given that ffio_fill() uses an int parameter and given that no current caller (and no sane future caller) will want to reserve several GB of space, make the size parameter of put_ebml_void() itself an int. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 258638e459..e74d59d271 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -288,21 +288,22 @@ static void put_ebml_string(AVIOContext *pb, uint32_t elementid, * * @param size The number of bytes to reserve, which must be at least 2. */ -static void put_ebml_void(AVIOContext *pb, uint64_t size) +static void put_ebml_void(AVIOContext *pb, int size) { - int64_t currentpos = avio_tell(pb); - av_assert0(size >= 2); put_ebml_id(pb, EBML_ID_VOID); // we need to subtract the length needed to store the size from the // size we need to reserve so 2 cases, we use 8 bytes to store the // size if possible, 1 byte otherwise - if (size < 10) - put_ebml_num(pb, size - 2, 0); - else - put_ebml_num(pb, size - 9, 8); - ffio_fill(pb, 0, currentpos + size - avio_tell(pb)); + if (size < 10) { + size -= 2; + put_ebml_num(pb, size, 0); + } else { + size -= 9; + put_ebml_num(pb, size, 8); + } + ffio_fill(pb, 0, size); } static ebml_master start_ebml_master(AVIOContext *pb, uint32_t elementid, -- 2.20.1
Andreas Rheinhardt:
Reserving space in Matroska works by writing a Void element. And until now this worked as follows: The current position was recorded and the EBML ID as well as the length field written; then the new position was recorded to know how much more to write. Afterwards the actual writing has been performed via ffio_fill().
But it is unnecessary to explicitly use the positions (obtained via avio_tell()) to find out how much still needs to be written, because the length of the ID and the length field are known. So rewrite the function to no longer use them.
Also, given that ffio_fill() uses an int parameter and given that no current caller (and no sane future caller) will want to reserve several GB of space, make the size parameter of put_ebml_void() itself an int.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 258638e459..e74d59d271 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -288,21 +288,22 @@ static void put_ebml_string(AVIOContext *pb, uint32_t elementid, * * @param size The number of bytes to reserve, which must be at least 2. */ -static void put_ebml_void(AVIOContext *pb, uint64_t size) +static void put_ebml_void(AVIOContext *pb, int size) { - int64_t currentpos = avio_tell(pb); - av_assert0(size >= 2);
put_ebml_id(pb, EBML_ID_VOID); // we need to subtract the length needed to store the size from the // size we need to reserve so 2 cases, we use 8 bytes to store the // size if possible, 1 byte otherwise - if (size < 10) - put_ebml_num(pb, size - 2, 0); - else - put_ebml_num(pb, size - 9, 8); - ffio_fill(pb, 0, currentpos + size - avio_tell(pb)); + if (size < 10) { + size -= 2; + put_ebml_num(pb, size, 0); + } else { + size -= 9; + put_ebml_num(pb, size, 8); + } + ffio_fill(pb, 0, size); }
static ebml_master start_ebml_master(AVIOContext *pb, uint32_t elementid,
Any comments on this or the other Matroska muxer patches following this one? If not, I will probably apply this tomorrow. - Andreas
mkv_write_track() currently has three places where it checks for whether the current codec type is audio: One in a switch and two outside of it. These checks can be combined by moving the code after the other two checks inside the audio-related part of the switch. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 59 +++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index e74d59d271..93f01da930 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1092,7 +1092,7 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, ebml_master subinfo, track; int native_id = 0; int qt_id = 0; - int bit_depth = av_get_bits_per_sample(par->codec_id); + int bit_depth; int sample_rate = par->sample_rate; int output_sample_rate = 0; int display_width_div = 1; @@ -1105,17 +1105,6 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, return 0; } - if (par->codec_type == AVMEDIA_TYPE_AUDIO) { - if (!bit_depth && par->codec_id != AV_CODEC_ID_ADPCM_G726) { - if (par->bits_per_raw_sample) - bit_depth = par->bits_per_raw_sample; - else - bit_depth = av_get_bytes_per_sample(par->format) << 3; - } - if (!bit_depth) - bit_depth = par->bits_per_coded_sample; - } - if (par->codec_id == AV_CODEC_ID_AAC) { ret = get_aac_sample_rates(s, par->extradata, par->extradata_size, &sample_rate, &output_sample_rate); @@ -1202,24 +1191,6 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, } } - if (par->codec_type == AVMEDIA_TYPE_AUDIO && par->initial_padding && par->codec_id == AV_CODEC_ID_OPUS) { - int64_t codecdelay = av_rescale_q(par->initial_padding, - (AVRational){ 1, 48000 }, - (AVRational){ 1, 1000000000 }); - if (codecdelay < 0) { - av_log(s, AV_LOG_ERROR, "Initial padding is invalid\n"); - return AVERROR(EINVAL); - } -// mkv->tracks[i].ts_offset = av_rescale_q(par->initial_padding, -// (AVRational){ 1, par->sample_rate }, -// st->time_base); - - put_ebml_uint(pb, MATROSKA_ID_CODECDELAY, codecdelay); - } - if (par->codec_id == AV_CODEC_ID_OPUS) { - put_ebml_uint(pb, MATROSKA_ID_SEEKPREROLL, OPUS_SEEK_PREROLL); - } - switch (par->codec_type) { case AVMEDIA_TYPE_VIDEO: mkv->have_video = 1; @@ -1313,6 +1284,24 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, break; case AVMEDIA_TYPE_AUDIO: + if (par->initial_padding && par->codec_id == AV_CODEC_ID_OPUS) { + int64_t codecdelay = av_rescale_q(par->initial_padding, + (AVRational){ 1, 48000 }, + (AVRational){ 1, 1000000000 }); + if (codecdelay < 0) { + av_log(s, AV_LOG_ERROR, "Initial padding is invalid\n"); + return AVERROR(EINVAL); + } +// mkv->tracks[i].ts_offset = av_rescale_q(par->initial_padding, +// (AVRational){ 1, par->sample_rate }, +// st->time_base); + + put_ebml_uint(pb, MATROSKA_ID_CODECDELAY, codecdelay); + } + if (par->codec_id == AV_CODEC_ID_OPUS) { + put_ebml_uint(pb, MATROSKA_ID_SEEKPREROLL, OPUS_SEEK_PREROLL); + } + put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, MATROSKA_TRACK_TYPE_AUDIO); if (!native_id) @@ -1326,6 +1315,16 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, put_ebml_float (pb, MATROSKA_ID_AUDIOSAMPLINGFREQ, sample_rate); if (output_sample_rate) put_ebml_float(pb, MATROSKA_ID_AUDIOOUTSAMPLINGFREQ, output_sample_rate); + + bit_depth = av_get_bits_per_sample(par->codec_id); + if (!bit_depth && par->codec_id != AV_CODEC_ID_ADPCM_G726) { + if (par->bits_per_raw_sample) + bit_depth = par->bits_per_raw_sample; + else + bit_depth = av_get_bytes_per_sample(par->format) << 3; + } + if (!bit_depth) + bit_depth = par->bits_per_coded_sample; if (bit_depth) put_ebml_uint(pb, MATROSKA_ID_AUDIOBITDEPTH, bit_depth); end_ebml_master(pb, subinfo); -- 2.20.1
Also remove { } after an if if there is only one statement inside { }. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 93f01da930..83ed43ab77 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1285,22 +1285,21 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, case AVMEDIA_TYPE_AUDIO: if (par->initial_padding && par->codec_id == AV_CODEC_ID_OPUS) { - int64_t codecdelay = av_rescale_q(par->initial_padding, - (AVRational){ 1, 48000 }, - (AVRational){ 1, 1000000000 }); - if (codecdelay < 0) { - av_log(s, AV_LOG_ERROR, "Initial padding is invalid\n"); - return AVERROR(EINVAL); - } -// mkv->tracks[i].ts_offset = av_rescale_q(par->initial_padding, -// (AVRational){ 1, par->sample_rate }, -// st->time_base); + int64_t codecdelay = av_rescale_q(par->initial_padding, + (AVRational){ 1, 48000 }, + (AVRational){ 1, 1000000000 }); + if (codecdelay < 0) { + av_log(s, AV_LOG_ERROR, "Initial padding is invalid\n"); + return AVERROR(EINVAL); + } +// mkv->tracks[i].ts_offset = av_rescale_q(par->initial_padding, +// (AVRational){ 1, par->sample_rate }, +// st->time_base); - put_ebml_uint(pb, MATROSKA_ID_CODECDELAY, codecdelay); - } - if (par->codec_id == AV_CODEC_ID_OPUS) { - put_ebml_uint(pb, MATROSKA_ID_SEEKPREROLL, OPUS_SEEK_PREROLL); - } + put_ebml_uint(pb, MATROSKA_ID_CODECDELAY, codecdelay); + } + if (par->codec_id == AV_CODEC_ID_OPUS) + put_ebml_uint(pb, MATROSKA_ID_SEEKPREROLL, OPUS_SEEK_PREROLL); put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, MATROSKA_TRACK_TYPE_AUDIO); -- 2.20.1
Writing the language of WebVTT in WebM proceeded differently than the language of all other tracks: In case no language was given, it does not write anything instead of "und" (for undefined). Because the default value of the Language element in WebM (that inherited it from Matroska) is "eng" (for English), any such track will actually be flagged as English. Doing it this way goes back to commit 509642b4 (the commit adding support for WebVTT) and no reason for this has been given in the commit message or in the discussion about this patch on the mailing list; the best I can think of is this: the WebM wiki contains "The srclang attribute is stored as the Language sub-element." Someone unfamiliar with default values in Matroska/WebM could interpret this as meaning that no Language element should be written if there is no srclang attribute. But this is wrong and this commit changes it. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 83ed43ab77..2331e21ff8 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1122,11 +1122,8 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, if ((tag = av_dict_get(st->metadata, "title", NULL, 0))) put_ebml_string(pb, MATROSKA_ID_TRACKNAME, tag->value); tag = av_dict_get(st->metadata, "language", NULL, 0); - if (mkv->mode != MODE_WEBM || par->codec_id != AV_CODEC_ID_WEBVTT) { - put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag && tag->value ? tag->value:"und"); - } else if (tag && tag->value) { - put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag->value); - } + put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, + tag && tag->value ? tag->value : "und"); // The default value for TRACKFLAGDEFAULT is 1, so add element // if we need to clear it. -- 2.20.1
Up until now, the Matroska muxer would mark a track as default if it had the disposition AV_DISPOSITION_DEFAULT or if there was no track with AV_DISPOSITION_DEFAULT set; in the latter case even more than one track of a kind (audio, video, subtitles) was marked as default which is not sensible. This commit changes the logic used to mark tracks as default. There are now three modes for this: a) In the "infer" mode the first track of every type (audio, video, subtitles) with default disposition set will be marked as default; if there is no such track (for a given type), then the first track of this type (if existing) will be marked as default. This behaviour is inspired by mkvmerge. It ensures that the default flags will be set in a sensible way even if the input comes from containers that lack the concept of default flags. This mode is the default mode. b) The "infer_no_subs" mode is similar to the "infer" mode; the difference is that if no subtitle track with default disposition exists, no subtitle track will be marked as default at all. c) The "passthrough" mode: Here the track will be marked as default if and only the corresponding input stream had disposition default. This fixes ticket #8173 (the passthrough mode is ideal for this) as well as ticket #8416 (the "infer_no_subs" mode leads to the desired output). Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- Better naming suggestions than "infer_no_subs" welcome. Furthermore, I'd appreciate suggestions on how to shorten the AVOption documentation (without losing relevant information). doc/muxers.texi | 19 +++++++++++++ libavformat/matroskaenc.c | 56 +++++++++++++++++++++++++++++++++------ 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/doc/muxers.texi b/doc/muxers.texi index 521d99140c..48f3896146 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -1341,6 +1341,25 @@ the end. A safe size for most use cases should be about 50kB per hour of video. Note that cues are only written if the output is seekable and this option will have no effect if it is not. +@item default_mode +This option controls how the FlagDefault of the output tracks will be set. +It influences which tracks players should play by default. The default mode +is @samp{infer}. +@table @samp +@item infer +In this mode, for each type of track (audio, video or subtitle), if there is +a track with disposition default of this type, then the first such track +(i.e. the one with the lowest index) will be marked as default; if no such +track exists, the first track of this type will be marked as default instead +(if existing). This ensures that the default flag is set in a sensible way even +if the input originated from containers that lack the concept of default tracks. +@item infer_no_subs +This mode is the same as infer except that if no subtitle track with +disposition default exists, no subtitle track will be marked as default. +@item passthrough +In this mode the FlagDefault is set if and only if the AV_DISPOSITION_DEFAULT +flag is set in the disposition of the corresponding stream. +@end table @end table @anchor{md5} diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 2331e21ff8..5065b2adbd 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -61,6 +61,12 @@ * Info, Tracks, Chapters, Attachments, Tags and Cues */ #define MAX_SEEKHEAD_ENTRIES 6 +enum { + DEFAULT_MODE_INFER, + DEFAULT_MODE_INFER_NO_SUBS, + DEFAULT_MODE_PASSTHROUGH, +}; + typedef struct ebml_master { int64_t pos; ///< absolute offset in the containing AVIOContext where the master's elements start int sizebytes; ///< how many bytes were reserved for the size @@ -160,6 +166,7 @@ typedef struct MatroskaMuxContext { int wrote_chapters; int allow_raw_vfw; + int default_mode; } MatroskaMuxContext; /** 2 bytes * 7 for EBML IDs, 7 1-byte EBML lengths, 6 1-byte uint, @@ -1085,7 +1092,7 @@ static int mkv_write_stereo_mode(AVFormatContext *s, AVIOContext *pb, } static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, - int i, AVIOContext *pb, int default_stream_exists) + int i, AVIOContext *pb, int is_default) { AVStream *st = s->streams[i]; AVCodecParameters *par = st->codecpar; @@ -1127,8 +1134,8 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv, // The default value for TRACKFLAGDEFAULT is 1, so add element // if we need to clear it. - if (default_stream_exists && !(st->disposition & AV_DISPOSITION_DEFAULT)) - put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGDEFAULT, !!(st->disposition & AV_DISPOSITION_DEFAULT)); + if (!is_default) + put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGDEFAULT, 0); if (st->disposition & AV_DISPOSITION_FORCED) put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGFORCED, 1); @@ -1358,7 +1365,7 @@ static int mkv_write_tracks(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; AVIOContext *pb = s->pb; - int i, ret, default_stream_exists = 0; + int i, ret, video_default_idx, audio_default_idx, subtitle_default_idx; mkv_add_seekhead_entry(mkv, MATROSKA_ID_TRACKS, avio_tell(pb)); @@ -1366,12 +1373,41 @@ static int mkv_write_tracks(AVFormatContext *s) if (ret < 0) return ret; - for (i = 0; i < s->nb_streams; i++) { - AVStream *st = s->streams[i]; - default_stream_exists |= st->disposition & AV_DISPOSITION_DEFAULT; + if (mkv->default_mode != DEFAULT_MODE_PASSTHROUGH) { + int video_idx, audio_idx, subtitle_idx; + + video_idx = video_default_idx = + audio_idx = audio_default_idx = + subtitle_idx = subtitle_default_idx = -1; + + for (i = s->nb_streams - 1; i >= 0; i--) { + AVStream *st = s->streams[i]; + + switch (st->codecpar->codec_type) { +#define CASE(type, variable) \ + case AVMEDIA_TYPE_ ## type: \ + variable ## _idx = i; \ + if (st->disposition & AV_DISPOSITION_DEFAULT) \ + variable ## _default_idx = i; \ + break; + CASE(VIDEO, video) + CASE(AUDIO, audio) + CASE(SUBTITLE, subtitle) +#undef CASE + } + } + + video_default_idx = FFMAX(video_default_idx, video_idx); + audio_default_idx = FFMAX(audio_default_idx, audio_idx); + if (mkv->default_mode != DEFAULT_MODE_INFER_NO_SUBS) + subtitle_default_idx = FFMAX(subtitle_default_idx, subtitle_idx); } for (i = 0; i < s->nb_streams; i++) { - ret = mkv_write_track(s, mkv, i, mkv->tracks_bc, default_stream_exists); + int is_default = mkv->default_mode == DEFAULT_MODE_PASSTHROUGH ? + s->streams[i]->disposition & AV_DISPOSITION_DEFAULT : + i == video_default_idx || i == audio_default_idx || + i == subtitle_default_idx; + ret = mkv_write_track(s, mkv, i, mkv->tracks_bc, is_default); if (ret < 0) return ret; } @@ -2696,6 +2732,10 @@ static const AVOption options[] = { { "live", "Write files assuming it is a live stream.", OFFSET(is_live), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, { "allow_raw_vfw", "allow RAW VFW mode", OFFSET(allow_raw_vfw), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, { "write_crc32", "write a CRC32 element inside every Level 1 element", OFFSET(write_crc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS }, + { "default_mode", "Controls how a track's FlagDefault is inferred", OFFSET(default_mode), AV_OPT_TYPE_INT, { .i64 = DEFAULT_MODE_INFER }, DEFAULT_MODE_INFER, DEFAULT_MODE_PASSTHROUGH, FLAGS, "default_mode" }, + { "infer", "For each track type, mark the first track of disposition default as default; if none exists, mark the first track as default.", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER }, 0, 0, FLAGS, "default_mode" }, + { "infer_no_subs", "For each track type, mark the first track of disposition default as default; for audio and video: if none exists, mark the first track as default.", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER_NO_SUBS }, 0, 0, FLAGS, "default_mode" }, + { "passthrough", "Use the disposition flag as-is", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_PASSTHROUGH }, 0, 0, FLAGS, "default_mode" }, { NULL }, }; -- 2.20.1
Am Sa., 18. Jan. 2020 um 09:46 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
Up until now, the Matroska muxer would mark a track as default if it had the disposition AV_DISPOSITION_DEFAULT or if there was no track with AV_DISPOSITION_DEFAULT set; in the latter case even more than one track of a kind (audio, video, subtitles) was marked as default which is not sensible.
It would not help only to change this strange behaviour?
This commit changes the logic used to mark tracks as default. There are now three modes for this: a) In the "infer" mode the first track of every type (audio, video, subtitles) with default disposition set will be marked as default; if there is no such track (for a given type), then the first track of this type (if existing) will be marked as default. This behaviour is inspired by mkvmerge. It ensures that the default flags will be set in a sensible way even if the input comes from containers that lack the concept of default flags. This mode is the default mode. b) The "infer_no_subs" mode is similar to the "infer" mode; the difference is that if no subtitle track with default disposition exists, no subtitle track will be marked as default at all. c) The "passthrough" mode: Here the track will be marked as default if and only the corresponding input stream had disposition default.
This fixes ticket #8173 (the passthrough mode is ideal for this) as well as ticket #8416 (the "infer_no_subs" mode leads to the desired output).
Doesn't this duplicate the cli option for disposition? (Just asking, I know this is a can of worms) I am wondering if a new option is necessary and useful. Carl Eugen
Am Sa., 18. Jan. 2020 um 09:46 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
Up until now, the Matroska muxer would mark a track as default if it had the disposition AV_DISPOSITION_DEFAULT or if there was no track with AV_DISPOSITION_DEFAULT set; in the latter case even more than one track of a kind (audio, video, subtitles) was marked as default which is not sensible.
It would not help only to change this strange behaviour?
So you mean: If no stream with disposition default exist at all, then only mark the first track of each kind as default; otherwise pass the disposition through unchanged? That would be possible, of course; and it would be better than the current behaviour. But it would not allow to create files with no track marked as default at all (requested in #8173) and if one muxed several tracks from different Matroska files, each of which is marked as default in its source, one would still have multiple
On Tue, Jan 21, 2020 at 12:11 AM Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote: tracks of the same kind marked as default in the output. This should not be the default behaviour.
This commit changes the logic used to mark tracks as default. There are now three modes for this: a) In the "infer" mode the first track of every type (audio, video, subtitles) with default disposition set will be marked as default; if there is no such track (for a given type), then the first track of this type (if existing) will be marked as default. This behaviour is inspired by mkvmerge. It ensures that the default flags will be set in a sensible way even if the input comes from containers that lack the concept of default flags. This mode is the default mode. b) The "infer_no_subs" mode is similar to the "infer" mode; the difference is that if no subtitle track with default disposition exists, no subtitle track will be marked as default at all. c) The "passthrough" mode: Here the track will be marked as default if and only the corresponding input stream had disposition default.
This fixes ticket #8173 (the passthrough mode is ideal for this) as well as ticket #8416 (the "infer_no_subs" mode leads to the desired output).
Doesn't this duplicate the cli option for disposition? (Just asking, I know this is a can of worms)
I am wondering if a new option is necessary and useful.
Without a new option, one could not distinguish the case where the user explicitly wants no dispositions at all and the case where one remuxes from a source container that does not have the concept of default streams. Furthermore, having an option allows to distinguish the infer and "infer_no_subs" cases (the latter makes (some) players not show the subtitles unless one explicitly enables them which is what (some) users (including myself and apparently the guy behind #8416) want; I am actually unsure whether infer_no_subs would make a better default value).
- Andreas
Am Di., 21. Jan. 2020 um 00:43 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
On Tue, Jan 21, 2020 at 12:11 AM Carl Eugen Hoyos <ceffmpeg@gmail.com> wrote:
Am Sa., 18. Jan. 2020 um 09:46 Uhr schrieb Andreas Rheinhardt <andreas.rheinhardt@gmail.com>:
Up until now, the Matroska muxer would mark a track as default if it had the disposition AV_DISPOSITION_DEFAULT or if there was no track with AV_DISPOSITION_DEFAULT set; in the latter case even more than one track of a kind (audio, video, subtitles) was marked as default which is not sensible.
It would not help only to change this strange behaviour?
So you mean: If no stream with disposition default exist at all, then only mark the first track of each kind as default; otherwise pass the disposition through unchanged? That would be possible, of course; and it would be better than the current behaviour. But it would not allow to create files with no track marked as default at all (requested in #8173) and if one muxed several tracks from different Matroska files, each of which is marked as default in its source, one would still have multiple tracks of the same kind marked as default in the output. This should not be the default behaviour.
(The behaviour could be to only mark one stream as default, I always considered that as the only sane approach and never understood what the intention was with the code we have now...)
This commit changes the logic used to mark tracks as default. There are now three modes for this: a) In the "infer" mode the first track of every type (audio, video, subtitles) with default disposition set will be marked as default; if there is no such track (for a given type), then the first track of this type (if existing) will be marked as default. This behaviour is inspired by mkvmerge. It ensures that the default flags will be set in a sensible way even if the input comes from containers that lack the concept of default flags. This mode is the default mode. b) The "infer_no_subs" mode is similar to the "infer" mode; the difference is that if no subtitle track with default disposition exists, no subtitle track will be marked as default at all. c) The "passthrough" mode: Here the track will be marked as default if and only the corresponding input stream had disposition default.
This fixes ticket #8173 (the passthrough mode is ideal for this) as well as ticket #8416 (the "infer_no_subs" mode leads to the desired output).
Doesn't this duplicate the cli option for disposition? (Just asking, I know this is a can of worms)
I am wondering if a new option is necessary and useful.
Without a new option, one could not distinguish the case where the user explicitly wants no dispositions at all and the case where one remuxes from a source container that does not have the concept of default streams. Furthermore, having an option allows to distinguish the infer and "infer_no_subs" cases (the latter makes (some) players not show the subtitles unless one explicitly enables them which is what (some) users (including myself and apparently the guy behind #8416) want; I am actually unsure whether infer_no_subs would make a better default value).
Thank you for the explanation, I do not immediately see a better solution. Carl Eugen
Andreas Rheinhardt (12020-01-21):
Without a new option, one could not distinguish the case where the user explicitly wants no dispositions at all and the case where one remuxes from a source container that does not have the concept of default streams. Furthermore, having an option allows to distinguish the infer and "infer_no_subs" cases (the latter makes (some) players not show the subtitles unless one explicitly enables them which is what (some) users (including myself and apparently the guy behind #8416) want; I am actually unsure whether infer_no_subs would make a better default value).
Hi. I am replying to this old discussion because I just lost a little time on an encode: I had "-disposition:2 -default" in my command line, and the track was still marked default, I was missing "-default_mode passthrough". Silently ignoring or overriding user-given options is really not a good idea. How can we enhance that? The obvious solution would be to make passthrough the default. That would make matroskaenc similar to other muxers. But I suppose you had a good reason to make infer the default? We could try to find an heuristic to detect if the disposition was set, and print a warning if infer is used and it gets overridden. I do not like that much, because warnings are not a satisfactory solution. We can try to find a real solution to detect if the disposition is set or not reliably, and use it (and then deprecate -default_mode). For example: /** * Set by demuxers when they know the disposition is reliable. * Used by muxers to detect if they use the disposition or guess it. */ #define AV_DISPOSITION_SET 0x10000000 If none of these solution work, then we need to write "for Matroska, -default_mode passthrough is needed" in the documentation for -disposition. In blinking lights. What do you think? Regards, -- Nicolas George
Nicolas George:
Andreas Rheinhardt (12020-01-21):
Without a new option, one could not distinguish the case where the user explicitly wants no dispositions at all and the case where one remuxes from a source container that does not have the concept of default streams. Furthermore, having an option allows to distinguish the infer and "infer_no_subs" cases (the latter makes (some) players not show the subtitles unless one explicitly enables them which is what (some) users (including myself and apparently the guy behind #8416) want; I am actually unsure whether infer_no_subs would make a better default value).
Hi. I am replying to this old discussion because I just lost a little time on an encode: I had "-disposition:2 -default" in my command line, and the track was still marked default, I was missing "-default_mode passthrough".
Silently ignoring or overriding user-given options is really not a good idea. How can we enhance that?
The obvious solution would be to make passthrough the default. That would make matroskaenc similar to other muxers. But I suppose you had a good reason to make infer the default?
At the time of said patch, the definition of the default flag was: "Set if that track (audio, video or subs) **SHOULD** be active if no language found matches the user preference." This implies that if no language found matches the user preference, then all default tracks should be played simultaneously, so that it makes very little sense to mark more than one track of a given type as default (there were also notes to the spec which contradicted the main definition and said that in this case the *first* default track should be used). For this reason mkvmerge disallowed setting multiple default tracks of the same type. And given that mkvmerge is the de-facto reference implementation, this meant that simply passing the disposition through would have created files of dubious validity. So I imitated what mkvmerge was doing. The whole situation changed now; the meaning of the default flag has been revised in a way that leaves no doubt that it is perfectly valid to set the default flag for as many tracks as one wishes. Therefore I have sent a patchset implementing this: https://ffmpeg.org/pipermail/ffmpeg-devel/2021-August/284066.html
We could try to find an heuristic to detect if the disposition was set, and print a warning if infer is used and it gets overridden. I do not like that much, because warnings are not a satisfactory solution.
We can try to find a real solution to detect if the disposition is set or not reliably, and use it (and then deprecate -default_mode). For example:
/** * Set by demuxers when they know the disposition is reliable. * Used by muxers to detect if they use the disposition or guess it. */ #define AV_DISPOSITION_SET 0x10000000
I pondered doing such a thing then and now; I didn't do it last year because a system as you propose would not have solved the main "problem" (as I considered it at the time) of creating files where multiple tracks of the same type are marked as default. I didn't do it now, because the default course of action should always be just to preserve what the user indicated. But if there are complaints, then I will probably implement something like this. (mkvmerge's strategy is btw: If the user set it explicitly on the commandline, use this; else if the input file provides a default flag, use this; else treat it as default track.) - Andreas PS: Sorry for taking so long to answer.
The Matroska muxer uses a dynamic buffer to buffer the content of Clusters before eventually writing them. Up until now, each time a Cluster was written, the dynamic buffer was closed, i.e. freed; now it is only reset, saving allocations of the AVIOContext itself, its opaque as well as most of the reallocations of the buffer. This is advantageous performance-wise, in particular on systems where reallocations are slow (namely Windows). The following table shows the decicyles for writing a frame on Linux (Ubuntu 19.10) and Windows (7) on an x64 Haswell (to /dev/null on Linux, to stdout which is discarded on Windows (the default values of the size and duration of clusters for seekable output have been explicitly set in this case); in all tests, writing CRC-32 values has been disabled in all tests; calls to the muxer's write_packet function in write_packet() in libavformat/mux.c have been timed; each of the following tests has been repeated 50 times): | Windows before | Windows after | Linux before | Linux after _________________________________________________________________ A | 979437 | 192304 | 259500 | 183320 B | 715936 | 155648 | 152786 | 130879 C | 265115 | 56034 | 78496 | 53243 D | 386224 | 80307 | 128894 | 75354 E | 21732 | 10695 | 11320 | 9801 (A is a 10.2 mb/s file with a GOP length of 2s, amounting to an average Cluster size of about 2.5 MiB; the average Cluster size of B is 1.1 MiB; for C it is 2.35 MiB, for D it is 0.46 MiB; for E - a file with just a single audio track of 158kb/s resulting in a Cluster size of about 100 kB, the relative gains were the smallest, probably because of the small Cluster size.) Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 47 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 5065b2adbd..8bbacf5cd3 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -337,7 +337,7 @@ static int start_ebml_master_crc32(AVIOContext **dyn_cp, MatroskaMuxContext *mkv { int ret; - if ((ret = avio_open_dyn_buf(dyn_cp)) < 0) + if (!*dyn_cp && (ret = avio_open_dyn_buf(dyn_cp)) < 0) return ret; if (mkv->write_crc) @@ -348,13 +348,13 @@ static int start_ebml_master_crc32(AVIOContext **dyn_cp, MatroskaMuxContext *mkv static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv, uint32_t id, - int length_size) + int length_size, int keep_buffer) { uint8_t *buf, crc[4]; int size, skip = 0; put_ebml_id(pb, id); - size = avio_close_dyn_buf(*dyn_cp, &buf); + size = avio_get_dyn_buf(*dyn_cp, &buf); put_ebml_num(pb, size, length_size); if (mkv->write_crc) { skip = 6; /* Skip reserved 6-byte long void element from the dynamic buffer. */ @@ -363,8 +363,11 @@ static void end_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, } avio_write(pb, buf + skip, size - skip); - av_free(buf); - *dyn_cp = NULL; + if (keep_buffer) { + ffio_reset_dyn_buf(*dyn_cp); + } else { + ffio_free_dyn_buf(dyn_cp); + } } /** @@ -449,7 +452,7 @@ static void mkv_add_seekhead_entry(MatroskaMuxContext *mkv, uint32_t elementid, static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, int error_on_seek_failure, int64_t destpos) { - AVIOContext *dyn_cp; + AVIOContext *dyn_cp = NULL; mkv_seekhead *seekhead = &mkv->seekhead; int64_t remaining, ret64; int i, ret; @@ -476,7 +479,7 @@ static int mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv, put_ebml_uint(dyn_cp, MATROSKA_ID_SEEKPOSITION, entry->segmentpos); end_ebml_master(dyn_cp, seekentry); } - end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD, 0); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD, 0, 0); remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); @@ -1416,7 +1419,7 @@ static int mkv_write_tracks(AVFormatContext *s) end_ebml_master_crc32_preliminary(pb, mkv->tracks_bc, MATROSKA_ID_TRACKS, &mkv->tracks_pos); else - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS, 0); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS, 0, 0); return 0; } @@ -1424,7 +1427,7 @@ static int mkv_write_tracks(AVFormatContext *s) static int mkv_write_chapters(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; - AVIOContext *dyn_cp, *pb = s->pb; + AVIOContext *dyn_cp = NULL, *pb = s->pb; ebml_master editionentry; AVRational scale = {1, 1E9}; int i, ret; @@ -1472,7 +1475,7 @@ static int mkv_write_chapters(AVFormatContext *s) end_ebml_master(dyn_cp, chapteratom); } end_ebml_master(dyn_cp, editionentry); - end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS, 0); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS, 0, 0); mkv->wrote_chapters = 1; return 0; @@ -1671,7 +1674,7 @@ static int mkv_write_tags(AVFormatContext *s) end_ebml_master_crc32_preliminary(s->pb, mkv->tags_bc, MATROSKA_ID_TAGS, &mkv->tags_pos); else - end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0); + end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0, 0); } return 0; } @@ -1679,7 +1682,7 @@ static int mkv_write_tags(AVFormatContext *s) static int mkv_write_attachments(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; - AVIOContext *dyn_cp, *pb = s->pb; + AVIOContext *dyn_cp = NULL, *pb = s->pb; AVLFG c; int i, ret; @@ -1767,7 +1770,7 @@ static int mkv_write_attachments(AVFormatContext *s) mkv->attachments->entries[mkv->attachments->num_entries].stream_idx = i; mkv->attachments->entries[mkv->attachments->num_entries++].fileuid = fileuid; } - end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS, 0); + end_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS, 0, 0); return 0; } @@ -1908,7 +1911,7 @@ static int mkv_write_header(AVFormatContext *s) end_ebml_master_crc32_preliminary(s->pb, mkv->info_bc, MATROSKA_ID_INFO, &mkv->info_pos); else - end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO, 0); + end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO, 0, 0); pb = s->pb; ret = mkv_write_tracks(s); @@ -2188,7 +2191,7 @@ static void mkv_end_cluster(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; - end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER, 0); + end_ebml_master_crc32(s->pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER, 0, 1); mkv->cluster_pos = -1; avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_FLUSH_POINT); } @@ -2492,7 +2495,8 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->cluster_bc) { - end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv, MATROSKA_ID_CLUSTER, 0); + end_ebml_master_crc32(pb, &mkv->cluster_bc, mkv, + MATROSKA_ID_CLUSTER, 0, 0); } ret = mkv_write_chapters(s); @@ -2506,7 +2510,7 @@ static int mkv_write_trailer(AVFormatContext *s) endpos = avio_tell(pb); if (mkv->cues.num_entries) { - AVIOContext *cues; + AVIOContext *cues = NULL; uint64_t size; int64_t cuespos = endpos; int length_size = 0; @@ -2548,7 +2552,8 @@ static int mkv_write_trailer(AVFormatContext *s) } } mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); - end_ebml_master_crc32(pb, &cues, mkv, MATROSKA_ID_CUES, length_size); + end_ebml_master_crc32(pb, &cues, mkv, MATROSKA_ID_CUES, + length_size, 0); if (mkv->reserve_cues_space) { if (size < mkv->reserve_cues_space) put_ebml_void(pb, mkv->reserve_cues_space - size); @@ -2564,11 +2569,11 @@ static int mkv_write_trailer(AVFormatContext *s) av_log(s, AV_LOG_DEBUG, "end duration = %" PRIu64 "\n", mkv->duration); avio_seek(mkv->info_bc, mkv->duration_offset, SEEK_SET); put_ebml_float(mkv->info_bc, MATROSKA_ID_DURATION, mkv->duration); - end_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO, 0); + end_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO, 0, 0); // write tracks master avio_seek(pb, mkv->tracks_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS, 0); + end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS, 0, 0); // update stream durations if (!mkv->is_live) { @@ -2598,7 +2603,7 @@ static int mkv_write_trailer(AVFormatContext *s) } if (mkv->tags_bc && !mkv->is_live) { avio_seek(pb, mkv->tags_pos, SEEK_SET); - end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0); + end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0, 0); } avio_seek(pb, endpos, SEEK_SET); -- 2.20.1
avio_close_dyn_buf() has a bug: When the write pointer does not point to the end of the written data when calling it (i.e. when one has performed a seek back to update already written data), it would not add padding to the end of the buffer, but to the current position, overwriting other data; furthermore the reported size would be wrong (off by the amount of data it has overwritten with padding). In order not to run into this when updating already written elements or elements for which size has only been reserved, the Matroska muxer would first record the current position of the dynamic buffer, then seek to the desired position, perform the update and seek back to the earlier position. But now that end_ebml_master_crc32() does not make use of avio_close_dyn_buf() any more, this is no longer necessary. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- avio_close_dyn_buf() even has more bugs: Besides the design flaw of freeing a resource without setting the pointer to it to NULL, it returns a size of -AV_INPUT_BUFFER_PADDING_SIZE if a memory allocation failure happened (but not if the arbitrary limit of INT_MAX/2 has been surpassed); and this despite its documentation not allowing returning negative values at all. This will definitely need to be fixed (and this muxer will need to check for whether the allocations failed). libavformat/matroskaenc.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 8bbacf5cd3..354096266d 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2211,7 +2211,6 @@ static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt) case AV_CODEC_ID_AAC: if (side_data_size && (s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) { int filler, output_sample_rate = 0; - int64_t curpos; ret = get_aac_sample_rates(s, side_data, side_data_size, &track->sample_rate, &output_sample_rate); if (ret < 0) @@ -2222,7 +2221,6 @@ static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt) if (ret < 0) return ret; memcpy(par->extradata, side_data, side_data_size); - curpos = avio_tell(mkv->tracks_bc); avio_seek(mkv->tracks_bc, track->codecpriv_offset, SEEK_SET); mkv_write_codecprivate(s, mkv->tracks_bc, par, 1, 0); filler = MAX_PCE_SIZE + 2 + 4 - (avio_tell(mkv->tracks_bc) - track->codecpriv_offset); @@ -2231,7 +2229,6 @@ static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt) avio_seek(mkv->tracks_bc, track->sample_rate_offset, SEEK_SET); put_ebml_float(mkv->tracks_bc, MATROSKA_ID_AUDIOSAMPLINGFREQ, track->sample_rate); put_ebml_float(mkv->tracks_bc, MATROSKA_ID_AUDIOOUTSAMPLINGFREQ, output_sample_rate); - avio_seek(mkv->tracks_bc, curpos, SEEK_SET); } else if (!par->extradata_size && !track->sample_rate) { // No extradata (codecpar or packet side data). av_log(s, AV_LOG_ERROR, "Error parsing AAC extradata, unable to determine samplerate.\n"); @@ -2241,7 +2238,6 @@ static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt) case AV_CODEC_ID_FLAC: if (side_data_size && (s->pb->seekable & AVIO_SEEKABLE_NORMAL) && !mkv->is_live) { AVCodecParameters *codecpriv_par; - int64_t curpos; if (side_data_size != par->extradata_size) { av_log(s, AV_LOG_ERROR, "Invalid FLAC STREAMINFO metadata for output stream %d\n", pkt->stream_index); @@ -2256,10 +2252,8 @@ static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt) return ret; } memcpy(codecpriv_par->extradata, side_data, side_data_size); - curpos = avio_tell(mkv->tracks_bc); avio_seek(mkv->tracks_bc, track->codecpriv_offset, SEEK_SET); mkv_write_codecprivate(s, mkv->tracks_bc, codecpriv_par, 1, 0); - avio_seek(mkv->tracks_bc, curpos, SEEK_SET); avcodec_parameters_free(&codecpriv_par); } break; @@ -2271,7 +2265,6 @@ static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt) AVIOContext *dyn_cp; uint8_t *codecpriv; int codecpriv_size; - int64_t curpos; ret = avio_open_dyn_buf(&dyn_cp); if (ret < 0) return ret; @@ -2281,12 +2274,10 @@ static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt) av_free(codecpriv); return AVERROR_INVALIDDATA; } - curpos = avio_tell(mkv->tracks_bc); avio_seek(mkv->tracks_bc, track->codecpriv_offset, SEEK_SET); // Do not write the OBUs as we don't have space saved for them put_ebml_binary(mkv->tracks_bc, MATROSKA_ID_CODECPRIVATE, codecpriv, 4); av_free(codecpriv); - avio_seek(mkv->tracks_bc, curpos, SEEK_SET); ret = ff_alloc_extradata(par, side_data_size); if (ret < 0) return ret; @@ -2578,7 +2569,6 @@ static int mkv_write_trailer(AVFormatContext *s) // update stream durations if (!mkv->is_live) { int i; - int64_t curr = avio_tell(mkv->tags_bc); for (i = 0; i < s->nb_streams; ++i) { AVStream *st = s->streams[i]; mkv_track *track = &mkv->tracks[i]; @@ -2599,7 +2589,6 @@ static int mkv_write_trailer(AVFormatContext *s) put_ebml_binary(mkv->tags_bc, MATROSKA_ID_TAGSTRING, duration_string, 20); } } - avio_seek(mkv->tags_bc, curr, SEEK_SET); } if (mkv->tags_bc && !mkv->is_live) { avio_seek(pb, mkv->tags_pos, SEEK_SET); -- 2.20.1
When updating the Tags at the end, the Matroska muxer would twice check for whether (!mkv->is_live) is true, despite this code being only executed if it is. Furthermore, a loop iterates over all the streams even when there is no Tags element to update at all, because the check for whether there are Tags is only performed later. This commit fixes this. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 354096266d..444032b431 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2567,7 +2567,7 @@ static int mkv_write_trailer(AVFormatContext *s) end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS, 0, 0); // update stream durations - if (!mkv->is_live) { + if (mkv->tags_bc) { int i; for (i = 0; i < s->nb_streams; ++i) { AVStream *st = s->streams[i]; @@ -2589,8 +2589,7 @@ static int mkv_write_trailer(AVFormatContext *s) put_ebml_binary(mkv->tags_bc, MATROSKA_ID_TAGSTRING, duration_string, 20); } } - } - if (mkv->tags_bc && !mkv->is_live) { + avio_seek(pb, mkv->tags_pos, SEEK_SET); end_ebml_master_crc32(pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS, 0, 0); } -- 2.20.1
Don't read a 64bit number before having checked that the data is at least 8 bytes long. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 444032b431..8fc672a31f 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2100,9 +2100,13 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, &side_data_size); if (side_data) { - additional_id = AV_RB64(side_data); - side_data += 8; - side_data_size -= 8; + if (side_data_size < 8) { + side_data_size = 0; + } else { + additional_id = AV_RB64(side_data); + side_data += 8; + side_data_size -= 8; + } } if ((side_data_size && additional_id == 1) || discard_padding) { -- 2.20.1
On Sun, Jan 26, 2020 at 06:10:27AM +0100, Andreas Rheinhardt wrote:
Don't read a 64bit number before having checked that the data is at least 8 bytes long.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- libavformat/matroskaenc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
will apply thx [...] -- Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB Old school: Use the lowest level language in which you can solve the problem conveniently. New school: Use the highest level language in which the latest supercomputer can solve the problem without the user falling asleep waiting.
8ffcc826 added support for muxing BlockAdditions with BlockAddID equal to one. The restriction to BlockAddID == 1 probably resulted from a limitation to what was needed; yet over time this led to three occurences of "(side_data_size && additional_id == 1)". This commit changes this by setting side_data_size to 0 if additional_id != 1. It also stops hardcoding 1 for the value of BlockAddID to write; but it still upholds the requirement that it is 1. See below. Despite BlockAddID actually having a default value of 1, it is still written, because until very recently (namely dbc50f8a) our demuxer used a wrong default value of 0. Furthermore, use put_ebml_binary() to write the BlockAdditional element. (The Matroska specifications have evolved and now the BlockAddID 1 is reserved for the codec (as described in the codec's codec mapping), BlockMore elements with BlockAddID > 1 are now of a more codec-independent nature and require a BlockAdditionalMapping in the track's TrackEntry. Given that this muxer does not support writing said BlockAdditionalMapping yet (actually, none have been defined yet), we have to uphold the requirement that BlockAddID == 1.) Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- Btw: When writing BlockAdditions in Matroska mode, one also has to write the MaxBlockAdditionID in the corresponding TrackEntry. Yet we don't (and we don't have the information to do that reliably when writing the header). libavformat/matroskaenc.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 8fc672a31f..de3fff2043 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -2046,7 +2046,7 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, uint8_t *data = NULL, *side_data = NULL; int offset = 0, size = pkt->size, side_data_size = 0; int64_t ts = track->write_dts ? pkt->dts : pkt->pts; - uint64_t additional_id = 0; + uint64_t additional_id; int64_t discard_padding = 0; uint8_t track_number = (mkv->is_dash ? mkv->dash_track_number : (pkt->stream_index + 1)); ebml_master block_group, block_additions, block_more; @@ -2100,16 +2100,16 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, &side_data_size); if (side_data) { - if (side_data_size < 8) { + // Only the Codec-specific BlockMore (id == 1) is currently supported. + if (side_data_size < 8 || (additional_id = AV_RB64(side_data)) != 1) { side_data_size = 0; } else { - additional_id = AV_RB64(side_data); side_data += 8; side_data_size -= 8; } } - if ((side_data_size && additional_id == 1) || discard_padding) { + if (side_data_size || discard_padding) { block_group = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, 0); blockid = MATROSKA_ID_BLOCK; } @@ -2133,17 +2133,18 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, put_ebml_sint(pb, MATROSKA_ID_DISCARDPADDING, discard_padding); } - if (side_data_size && additional_id == 1) { + if (side_data_size) { block_additions = start_ebml_master(pb, MATROSKA_ID_BLOCKADDITIONS, 0); block_more = start_ebml_master(pb, MATROSKA_ID_BLOCKMORE, 0); - put_ebml_uint(pb, MATROSKA_ID_BLOCKADDID, 1); - put_ebml_id(pb, MATROSKA_ID_BLOCKADDITIONAL); - put_ebml_num(pb, side_data_size, 0); - avio_write(pb, side_data, side_data_size); + /* Until dbc50f8a our demuxer used a wrong default value + * of BlockAddID, so we write it unconditionally. */ + put_ebml_uint (pb, MATROSKA_ID_BLOCKADDID, additional_id); + put_ebml_binary(pb, MATROSKA_ID_BLOCKADDITIONAL, + side_data, side_data_size); end_ebml_master(pb, block_more); end_ebml_master(pb, block_additions); } - if ((side_data_size && additional_id == 1) || discard_padding) { + if (side_data_size || discard_padding) { end_ebml_master(pb, block_group); } } -- 2.20.1
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately. The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- Resending (and pinging) this because the lavf-fate-av1.mkv-FATE-test (which didn't exist at the time the first version has been sent) also needs to be updated. The rest of the patchset is already compatible with the new test. libavformat/matroskaenc.c | 119 ++++++++------------------- tests/fate/matroska.mak | 2 +- tests/fate/wavpack.mak | 4 +- tests/ref/fate/aac-autobsf-adtstoasc | 4 +- tests/ref/fate/binsub-mksenc | 2 +- tests/ref/fate/rgb24-mkv | 4 +- tests/ref/lavf-fate/av1.mkv | 4 +- tests/ref/lavf/mka | 4 +- tests/ref/lavf/mkv | 4 +- tests/ref/lavf/mkv_attachment | 4 +- tests/ref/seek/lavf-mkv | 44 +++++----- 11 files changed, 73 insertions(+), 122 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 42f21eae8b..99ea3c6bf1 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -57,6 +57,10 @@ #include "libavcodec/mpeg4audio.h" #include "libavcodec/internal.h" +/* Level 1 elements we create a SeekHead entry for: + * Info, Tracks, Chapters, Attachments, Tags and Cues */ +#define MAX_SEEKHEAD_ENTRIES 6 + typedef struct ebml_master { int64_t pos; ///< absolute offset in the containing AVIOContext where the master's elements start int sizebytes; ///< how many bytes were reserved for the size @@ -69,11 +73,9 @@ typedef struct mkv_seekhead_entry { typedef struct mkv_seekhead { int64_t filepos; - int64_t segment_offset; ///< the file offset to the beginning of the segment - int reserved_size; ///< -1 if appending to file - int max_entries; - mkv_seekhead_entry *entries; + mkv_seekhead_entry entries[MAX_SEEKHEAD_ENTRIES]; int num_entries; + int reserved_size; } mkv_seekhead; typedef struct mkv_cuepoint { @@ -136,7 +138,7 @@ typedef struct MatroskaMuxContext { int64_t cluster_pts; int64_t duration_offset; int64_t duration; - mkv_seekhead *seekhead; + mkv_seekhead seekhead; mkv_cues *cues; mkv_track *tracks; mkv_attachments *attachments; @@ -396,10 +398,6 @@ static void mkv_deinit(AVFormatContext *s) ffio_free_dyn_buf(&mkv->tracks_bc); ffio_free_dyn_buf(&mkv->tags_bc); - if (mkv->seekhead) { - av_freep(&mkv->seekhead->entries); - av_freep(&mkv->seekhead); - } if (mkv->cues) { av_freep(&mkv->cues->entries); av_freep(&mkv->cues); @@ -412,61 +410,33 @@ static void mkv_deinit(AVFormatContext *s) } /** - * Initialize a mkv_seekhead element to be ready to index level 1 Matroska - * elements. If a maximum number of elements is specified, enough space - * will be reserved at the current file location to write a seek head of - * that size. - * - * @param segment_offset The absolute offset to the position in the file - * where the segment begins. - * @param numelements The maximum number of elements that will be indexed - * by this seek head, 0 if unlimited. + * Initialize the SeekHead element to be ready to index level 1 Matroska + * elements. Enough space to write MAX_SEEKHEAD_ENTRIES SeekHead entries + * will be reserved at the current file location. */ -static mkv_seekhead *mkv_start_seekhead(AVIOContext *pb, int64_t segment_offset, - int numelements) +static void mkv_start_seekhead(MatroskaMuxContext *mkv, AVIOContext *pb) { - mkv_seekhead *new_seekhead = av_mallocz(sizeof(mkv_seekhead)); - if (!new_seekhead) - return NULL; - - new_seekhead->segment_offset = segment_offset; - - if (numelements > 0) { - new_seekhead->filepos = avio_tell(pb); - // 21 bytes max for a seek entry, 10 bytes max for the SeekHead ID - // and size, 6 bytes for a CRC32 element, and 3 bytes to guarantee - // that an EBML void element will fit afterwards - new_seekhead->reserved_size = numelements * MAX_SEEKENTRY_SIZE + 19; - new_seekhead->max_entries = numelements; - put_ebml_void(pb, new_seekhead->reserved_size); - } - return new_seekhead; + mkv->seekhead.filepos = avio_tell(pb); + // 21 bytes max for a Seek entry, 6 bytes max for the SeekHead ID + // and size, 6 bytes for a CRC32 element, and 2 bytes to guarantee + // that an EBML void element will fit afterwards + mkv->seekhead.reserved_size = MAX_SEEKHEAD_ENTRIES * MAX_SEEKENTRY_SIZE + 14; + put_ebml_void(pb, mkv->seekhead.reserved_size); } -static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, uint32_t elementid, uint64_t filepos) +static void mkv_add_seekhead_entry(MatroskaMuxContext *mkv, uint32_t elementid, + uint64_t filepos) { - mkv_seekhead_entry *entries = seekhead->entries; + mkv_seekhead *seekhead = &mkv->seekhead; - // don't store more elements than we reserved space for - if (seekhead->max_entries > 0 && seekhead->max_entries <= seekhead->num_entries) - return -1; - - entries = av_realloc_array(entries, seekhead->num_entries + 1, sizeof(mkv_seekhead_entry)); - if (!entries) - return AVERROR(ENOMEM); - seekhead->entries = entries; + av_assert1(seekhead->num_entries < MAX_SEEKHEAD_ENTRIES); seekhead->entries[seekhead->num_entries].elementid = elementid; - seekhead->entries[seekhead->num_entries++].segmentpos = filepos - seekhead->segment_offset; - - return 0; + seekhead->entries[seekhead->num_entries++].segmentpos = filepos - mkv->segment_offset; } /** - * Write the seek head to the file and free it. If a maximum number of - * elements was specified to mkv_start_seekhead(), the seek head will - * be written at the location reserved for it. Otherwise, it is written - * at the current location in the file. + * Write the SeekHead to the file at the location reserved for it. * * @return The file offset where the seekhead was written, * -1 if an error occurred. @@ -474,19 +444,17 @@ static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, uint32_t elementid, ui static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) { AVIOContext *dyn_cp; - mkv_seekhead *seekhead = mkv->seekhead; + mkv_seekhead *seekhead = &mkv->seekhead; ebml_master seekentry; - int64_t currentpos; + int64_t currentpos, remaining; int i; currentpos = avio_tell(pb); - if (seekhead->reserved_size > 0) { if (avio_seek(pb, seekhead->filepos, SEEK_SET) < 0) { currentpos = -1; goto fail; } - } if (start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD) < 0) { currentpos = -1; @@ -507,16 +475,13 @@ static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) } end_ebml_master_crc32(pb, &dyn_cp, mkv); - if (seekhead->reserved_size > 0) { - uint64_t remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); + remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); avio_seek(pb, currentpos, SEEK_SET); currentpos = seekhead->filepos; - } + fail: - av_freep(&mkv->seekhead->entries); - av_freep(&mkv->seekhead); return currentpos; } @@ -1429,9 +1394,7 @@ static int mkv_write_tracks(AVFormatContext *s) AVIOContext *pb = s->pb; int i, ret, default_stream_exists = 0; - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TRACKS, avio_tell(pb)); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_TRACKS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); if (ret < 0) @@ -1466,8 +1429,7 @@ static int mkv_write_chapters(AVFormatContext *s) if (!s->nb_chapters || mkv->wrote_chapters) return 0; - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_CHAPTERS, avio_tell(pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CHAPTERS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); if (ret < 0) return ret; @@ -1556,8 +1518,7 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, int ret; if (!mkv->tags_bc) { - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TAGS, avio_tell(s->pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_TAGS, avio_tell(s->pb)); ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); if (ret < 0) @@ -1727,8 +1688,7 @@ static int mkv_write_attachments(AVFormatContext *s) av_lfg_init(&c, av_get_random_seed()); - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); if (ret < 0) return ret; @@ -1877,16 +1837,10 @@ static int mkv_write_header(AVFormatContext *s) mkv->segment_offset = avio_tell(pb); // we write a seek head at the beginning to point to all other level - // one elements, which aren't more than 10 elements as we write only one - // of every other currently defined level 1 element - mkv->seekhead = mkv_start_seekhead(pb, mkv->segment_offset, 10); - if (!mkv->seekhead) { - return AVERROR(ENOMEM); - } + // one elements (except Clusters). + mkv_start_seekhead(mkv, pb); - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_INFO, avio_tell(pb)); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_INFO, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); if (ret < 0) @@ -2576,10 +2530,7 @@ static int mkv_write_trailer(AVFormatContext *s) cuespos = mkv_write_cues(s, mkv->cues, mkv->tracks, s->nb_streams); } - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_CUES, - cuespos); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); } mkv_write_seekhead(pb, mkv); diff --git a/tests/fate/matroska.mak b/tests/fate/matroska.mak index 4aca4dc8eb..b9ed7322fd 100644 --- a/tests/fate/matroska.mak +++ b/tests/fate/matroska.mak @@ -12,7 +12,7 @@ fate-matroska-prores-header-insertion-bz2: CMD = framecrc -i $(TARGET_SAMPLES)/m FATE_MATROSKA-$(call DEMMUX, MATROSKA, MATROSKA) += fate-matroska-remux fate-matroska-remux: CMD = md5pipe -i $(TARGET_SAMPLES)/vp9-test-vectors/vp90-2-2pass-akiyo.webm -color_trc 4 -c:v copy -fflags +bitexact -strict -2 -f matroska fate-matroska-remux: CMP = oneline -fate-matroska-remux: REF = e5457e5fa606d564a54914bd12f426c8 +fate-matroska-remux: REF = 49a60ef91cf7302ab7276f9373f8a429 FATE_MATROSKA-$(call ALLYES, MATROSKA_DEMUXER VORBIS_PARSER) += fate-matroska-xiph-lacing fate-matroska-xiph-lacing: CMD = framecrc -i $(TARGET_SAMPLES)/mkv/xiph_lacing.mka -c:a copy diff --git a/tests/fate/wavpack.mak b/tests/fate/wavpack.mak index 286e17f2ae..a89cc1293d 100644 --- a/tests/fate/wavpack.mak +++ b/tests/fate/wavpack.mak @@ -91,12 +91,12 @@ fate-wavpack-matroskamode: CMD = md5 -i $(TARGET_SAMPLES)/wavpack/special/matros FATE_WAVPACK-$(call DEMMUX, WV, MATROSKA) += fate-wavpack-matroska_mux-mono fate-wavpack-matroska_mux-mono: CMD = md5pipe -i $(TARGET_SAMPLES)/wavpack/num_channels/mono_16bit_int.wv -c copy -fflags +bitexact -f matroska fate-wavpack-matroska_mux-mono: CMP = oneline -fate-wavpack-matroska_mux-mono: REF = c5a2b46d8b31c9c2e7bfb436f9d56f02 +fate-wavpack-matroska_mux-mono: REF = 0a1a7c6d3b413925f8261c92d1d53363 FATE_WAVPACK-$(call DEMMUX, WV, MATROSKA) += fate-wavpack-matroska_mux-61 fate-wavpack-matroska_mux-61: CMD = md5pipe -i $(TARGET_SAMPLES)/wavpack/num_channels/eva_2.22_6.1_16bit-partial.wv -c copy -fflags +bitexact -f matroska fate-wavpack-matroska_mux-61: CMP = oneline -fate-wavpack-matroska_mux-61: REF = 22fc2a591be22657d19cab89ad6eef55 +fate-wavpack-matroska_mux-61: REF = 1f012f7c237f46f2f63089ba84c589c5 FATE_SAMPLES_AVCONV += $(FATE_WAVPACK-yes) fate-wavpack: $(FATE_WAVPACK-yes) diff --git a/tests/ref/fate/aac-autobsf-adtstoasc b/tests/ref/fate/aac-autobsf-adtstoasc index 9bf9dfe78f..45b68b62bd 100644 --- a/tests/ref/fate/aac-autobsf-adtstoasc +++ b/tests/ref/fate/aac-autobsf-adtstoasc @@ -1,5 +1,5 @@ -b09fc2f554712adbf84fe7899eb679d4 *tests/data/fate/aac-autobsf-adtstoasc.matroska -6695 tests/data/fate/aac-autobsf-adtstoasc.matroska +d10b73732b32f6b2f08885b40c41c77f *tests/data/fate/aac-autobsf-adtstoasc.matroska +6606 tests/data/fate/aac-autobsf-adtstoasc.matroska #extradata 0: 2, 0x0030001c #tb 0: 1/1000 #media_type 0: audio diff --git a/tests/ref/fate/binsub-mksenc b/tests/ref/fate/binsub-mksenc index 4b13ef217a..3266501de6 100644 --- a/tests/ref/fate/binsub-mksenc +++ b/tests/ref/fate/binsub-mksenc @@ -1 +1 @@ -a5811caa0caba3a3f9a449b91569745c +03d376cc2e8622e7af540d4d9809dbf7 diff --git a/tests/ref/fate/rgb24-mkv b/tests/ref/fate/rgb24-mkv index b9db53f592..43b4ee2e19 100644 --- a/tests/ref/fate/rgb24-mkv +++ b/tests/ref/fate/rgb24-mkv @@ -1,5 +1,5 @@ -ffe2c21083d56764aaae7945fd146326 *tests/data/fate/rgb24-mkv.matroska -58305 tests/data/fate/rgb24-mkv.matroska +92861b36a8220a3dae10d91cba614892 *tests/data/fate/rgb24-mkv.matroska +58216 tests/data/fate/rgb24-mkv.matroska #tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo diff --git a/tests/ref/lavf-fate/av1.mkv b/tests/ref/lavf-fate/av1.mkv index 242c63a541..bd8ae6eefa 100644 --- a/tests/ref/lavf-fate/av1.mkv +++ b/tests/ref/lavf-fate/av1.mkv @@ -1,3 +1,3 @@ -6604d339d44864be58b8580fe2fb52cc *tests/data/lavf-fate/lavf.av1.mkv -55725 tests/data/lavf-fate/lavf.av1.mkv +be4903a6a6710043383c8f38a535a8d3 *tests/data/lavf-fate/lavf.av1.mkv +55636 tests/data/lavf-fate/lavf.av1.mkv tests/data/lavf-fate/lavf.av1.mkv CRC=0x7c27cc15 diff --git a/tests/ref/lavf/mka b/tests/ref/lavf/mka index a244893cf2..e8239442e9 100644 --- a/tests/ref/lavf/mka +++ b/tests/ref/lavf/mka @@ -1,3 +1,3 @@ -a91bfdb08ff5a1e62b039748d16b07f7 *tests/data/lavf/lavf.mka -43652 tests/data/lavf/lavf.mka +58936d3ad4eb1372bd2e5845963fe17c *tests/data/lavf/lavf.mka +43563 tests/data/lavf/lavf.mka tests/data/lavf/lavf.mka CRC=0x3a1da17e diff --git a/tests/ref/lavf/mkv b/tests/ref/lavf/mkv index 05999b79fb..4d73fbf39d 100644 --- a/tests/ref/lavf/mkv +++ b/tests/ref/lavf/mkv @@ -1,3 +1,3 @@ -3cddd26ed41f34925d3f34acabab174e *tests/data/lavf/lavf.mkv -320555 tests/data/lavf/lavf.mkv +6ec6d6a058a85bf616e0b981bb8e383f *tests/data/lavf/lavf.mkv +320466 tests/data/lavf/lavf.mkv tests/data/lavf/lavf.mkv CRC=0xec6c3c68 diff --git a/tests/ref/lavf/mkv_attachment b/tests/ref/lavf/mkv_attachment index 230dff369a..2e9711483f 100644 --- a/tests/ref/lavf/mkv_attachment +++ b/tests/ref/lavf/mkv_attachment @@ -1,3 +1,3 @@ -4a25c53150c09537cd4fcbff8f8f14ff *tests/data/lavf/lavf.mkv_attachment -472706 tests/data/lavf/lavf.mkv_attachment +41b4f132ada6e0faedf92e909526a899 *tests/data/lavf/lavf.mkv_attachment +472617 tests/data/lavf/lavf.mkv_attachment tests/data/lavf/lavf.mkv_attachment CRC=0xec6c3c68 diff --git a/tests/ref/seek/lavf-mkv b/tests/ref/seek/lavf-mkv index 6ed1f3a763..223e098829 100644 --- a/tests/ref/seek/lavf-mkv +++ b/tests/ref/seek/lavf-mkv @@ -1,48 +1,48 @@ -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st:-1 flags:0 ts:-1.000000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st:-1 flags:1 ts: 1.894167 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:0 ts: 0.788000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:1 ts:-0.317000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 1 flags:0 ts: 2.577000 ret: 0 st: 1 flags:1 ts: 1.471000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret: 0 st:-1 flags:0 ts: 0.365002 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st:-1 flags:1 ts:-0.740831 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 0 flags:0 ts: 2.153000 ret: 0 st: 0 flags:1 ts: 1.048000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 1 flags:0 ts:-0.058000 -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st: 1 flags:1 ts: 2.836000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret:-1 st:-1 flags:0 ts: 1.730004 ret: 0 st:-1 flags:1 ts: 0.624171 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st: 0 flags:0 ts:-0.482000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st: 0 flags:1 ts: 2.413000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret:-1 st: 1 flags:0 ts: 1.307000 ret: 0 st: 1 flags:1 ts: 0.201000 -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st:-1 flags:0 ts:-0.904994 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st:-1 flags:1 ts: 1.989173 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:0 ts: 0.883000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:1 ts:-0.222000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 1 flags:0 ts: 2.672000 ret: 0 st: 1 flags:1 ts: 1.566000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret: 0 st:-1 flags:0 ts: 0.460008 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st:-1 flags:1 ts:-0.645825 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 -- 2.20.1
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> ---
Ping. - Andreas
Andreas Rheinhardt:
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> ---
Ping.
- Andreas
Ping. - Andreas
Andreas Rheinhardt:
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> ---
Ping.
- Andreas
Ping (the actual patch (which has been omitted for brevity) is here: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-February/256997.html). - Andreas
Andreas Rheinhardt:
Andreas Rheinhardt:
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> ---
Ping.
- Andreas
Ping (the actual patch (which has been omitted for brevity) is here: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-February/256997.html).
- Andreas
Another ping. - Andreas
On Tue, Feb 25, 2020 at 2:24 PM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
Andreas Rheinhardt:
Andreas Rheinhardt:
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> ---
Ping.
- Andreas
Ping (the actual patch (which has been omitted for brevity) is here: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-February/256997.html).
- Andreas
Another ping.
- Andreas
Ping. - Andreas
On Fri, Feb 28, 2020 at 6:10 AM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
On Tue, Feb 25, 2020 at 2:24 PM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
Andreas Rheinhardt:
Andreas Rheinhardt:
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> ---
Ping.
- Andreas
Ping (the actual patch (which has been omitted for brevity) is here: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-February/256997.html).
- Andreas
Another ping.
- Andreas
Ping.
- Andreas
Ping. - Andreas
Andreas Rheinhardt:
On Fri, Feb 28, 2020 at 6:10 AM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
On Tue, Feb 25, 2020 at 2:24 PM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
Andreas Rheinhardt:
Andreas Rheinhardt:
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> ---
Ping.
- Andreas
Ping (the actual patch (which has been omitted for brevity) is here: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-February/256997.html).
- Andreas
Another ping.
- Andreas
Ping.
- Andreas
Ping.
- Andreas
Ping. - Andreas
Andreas Rheinhardt:
Andreas Rheinhardt:
On Fri, Feb 28, 2020 at 6:10 AM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
On Tue, Feb 25, 2020 at 2:24 PM Andreas Rheinhardt < andreas.rheinhardt@gmail.com> wrote:
Andreas Rheinhardt:
Andreas Rheinhardt:
Andreas Rheinhardt: > Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the > beginning referencing the main level 1 elements (i.e. not the Clusters) > and one at the end, referencing the Clusters. This second SeekHead was > useless and has therefore been removed. Yet the SeekHead-related > functions and structures are still geared towards this usecase: They > are built around an allocated array of variable size that gets > reallocated every time an element is added to it although the maximum > number of Seek entries is a small compile-time constant, so that one should > rather include the array in the SeekHead structure itself; and said > structure should be contained in the MatroskaMuxContext instead of being > allocated separately. > > The earlier code reserved space for a SeekHead with 10 entries, although > we currently write at most 6. Reducing said number implied that every > Matroska/Webm file will be 84 bytes smaller and required to adapt > several FATE tests; furthermore, the reserved amount overestimated the > amount needed for for the SeekHead's length field and how many bytes > need to be reserved to write a EBML Void element, bringing the total > reduction to 89 bytes. > > Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> > ---
Ping.
- Andreas
Ping (the actual patch (which has been omitted for brevity) is here: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-February/256997.html).
- Andreas
Another ping.
- Andreas
Ping.
- Andreas
Ping.
- Andreas
Ping.
- Andreas
Ping. - Andreas
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately. The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes. This also fixes a potential segfault: If !mkv->is_live and if the AVIOContext is initially unseekable when writing the header, the SeekHead is already written when writing the header and this used to free the SeekHead-related structures that have been allocated. But if the AVIOContext happens to be seekable when writing the trailer, it will be attempted to write the SeekHead again which will lead to segfaults because the corresponding structures have already been freed. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- 1. Changing seekability doesn't seem to happen in real life: Before add68dcc (merged last April) the writing process for all level 1 elements was different depending upon whether the output was seekable or not; if it switched between seekable and unseekable, the output would be corrupt, yet no one reported this. 2. Only the commit message has been modified; the content of this patch is unchanged since last time. 3. Changing seekability would still not go well. I'll work on it. libavformat/matroskaenc.c | 119 ++++++++------------------- tests/fate/matroska.mak | 2 +- tests/fate/wavpack.mak | 4 +- tests/ref/fate/aac-autobsf-adtstoasc | 4 +- tests/ref/fate/binsub-mksenc | 2 +- tests/ref/fate/rgb24-mkv | 4 +- tests/ref/lavf-fate/av1.mkv | 4 +- tests/ref/lavf/mka | 4 +- tests/ref/lavf/mkv | 4 +- tests/ref/lavf/mkv_attachment | 4 +- tests/ref/seek/lavf-mkv | 44 +++++----- 11 files changed, 73 insertions(+), 122 deletions(-) diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 42f21eae8b..99ea3c6bf1 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -57,6 +57,10 @@ #include "libavcodec/mpeg4audio.h" #include "libavcodec/internal.h" +/* Level 1 elements we create a SeekHead entry for: + * Info, Tracks, Chapters, Attachments, Tags and Cues */ +#define MAX_SEEKHEAD_ENTRIES 6 + typedef struct ebml_master { int64_t pos; ///< absolute offset in the containing AVIOContext where the master's elements start int sizebytes; ///< how many bytes were reserved for the size @@ -69,11 +73,9 @@ typedef struct mkv_seekhead_entry { typedef struct mkv_seekhead { int64_t filepos; - int64_t segment_offset; ///< the file offset to the beginning of the segment - int reserved_size; ///< -1 if appending to file - int max_entries; - mkv_seekhead_entry *entries; + mkv_seekhead_entry entries[MAX_SEEKHEAD_ENTRIES]; int num_entries; + int reserved_size; } mkv_seekhead; typedef struct mkv_cuepoint { @@ -136,7 +138,7 @@ typedef struct MatroskaMuxContext { int64_t cluster_pts; int64_t duration_offset; int64_t duration; - mkv_seekhead *seekhead; + mkv_seekhead seekhead; mkv_cues *cues; mkv_track *tracks; mkv_attachments *attachments; @@ -396,10 +398,6 @@ static void mkv_deinit(AVFormatContext *s) ffio_free_dyn_buf(&mkv->tracks_bc); ffio_free_dyn_buf(&mkv->tags_bc); - if (mkv->seekhead) { - av_freep(&mkv->seekhead->entries); - av_freep(&mkv->seekhead); - } if (mkv->cues) { av_freep(&mkv->cues->entries); av_freep(&mkv->cues); @@ -412,61 +410,33 @@ static void mkv_deinit(AVFormatContext *s) } /** - * Initialize a mkv_seekhead element to be ready to index level 1 Matroska - * elements. If a maximum number of elements is specified, enough space - * will be reserved at the current file location to write a seek head of - * that size. - * - * @param segment_offset The absolute offset to the position in the file - * where the segment begins. - * @param numelements The maximum number of elements that will be indexed - * by this seek head, 0 if unlimited. + * Initialize the SeekHead element to be ready to index level 1 Matroska + * elements. Enough space to write MAX_SEEKHEAD_ENTRIES SeekHead entries + * will be reserved at the current file location. */ -static mkv_seekhead *mkv_start_seekhead(AVIOContext *pb, int64_t segment_offset, - int numelements) +static void mkv_start_seekhead(MatroskaMuxContext *mkv, AVIOContext *pb) { - mkv_seekhead *new_seekhead = av_mallocz(sizeof(mkv_seekhead)); - if (!new_seekhead) - return NULL; - - new_seekhead->segment_offset = segment_offset; - - if (numelements > 0) { - new_seekhead->filepos = avio_tell(pb); - // 21 bytes max for a seek entry, 10 bytes max for the SeekHead ID - // and size, 6 bytes for a CRC32 element, and 3 bytes to guarantee - // that an EBML void element will fit afterwards - new_seekhead->reserved_size = numelements * MAX_SEEKENTRY_SIZE + 19; - new_seekhead->max_entries = numelements; - put_ebml_void(pb, new_seekhead->reserved_size); - } - return new_seekhead; + mkv->seekhead.filepos = avio_tell(pb); + // 21 bytes max for a Seek entry, 6 bytes max for the SeekHead ID + // and size, 6 bytes for a CRC32 element, and 2 bytes to guarantee + // that an EBML void element will fit afterwards + mkv->seekhead.reserved_size = MAX_SEEKHEAD_ENTRIES * MAX_SEEKENTRY_SIZE + 14; + put_ebml_void(pb, mkv->seekhead.reserved_size); } -static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, uint32_t elementid, uint64_t filepos) +static void mkv_add_seekhead_entry(MatroskaMuxContext *mkv, uint32_t elementid, + uint64_t filepos) { - mkv_seekhead_entry *entries = seekhead->entries; + mkv_seekhead *seekhead = &mkv->seekhead; - // don't store more elements than we reserved space for - if (seekhead->max_entries > 0 && seekhead->max_entries <= seekhead->num_entries) - return -1; - - entries = av_realloc_array(entries, seekhead->num_entries + 1, sizeof(mkv_seekhead_entry)); - if (!entries) - return AVERROR(ENOMEM); - seekhead->entries = entries; + av_assert1(seekhead->num_entries < MAX_SEEKHEAD_ENTRIES); seekhead->entries[seekhead->num_entries].elementid = elementid; - seekhead->entries[seekhead->num_entries++].segmentpos = filepos - seekhead->segment_offset; - - return 0; + seekhead->entries[seekhead->num_entries++].segmentpos = filepos - mkv->segment_offset; } /** - * Write the seek head to the file and free it. If a maximum number of - * elements was specified to mkv_start_seekhead(), the seek head will - * be written at the location reserved for it. Otherwise, it is written - * at the current location in the file. + * Write the SeekHead to the file at the location reserved for it. * * @return The file offset where the seekhead was written, * -1 if an error occurred. @@ -474,19 +444,17 @@ static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, uint32_t elementid, ui static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) { AVIOContext *dyn_cp; - mkv_seekhead *seekhead = mkv->seekhead; + mkv_seekhead *seekhead = &mkv->seekhead; ebml_master seekentry; - int64_t currentpos; + int64_t currentpos, remaining; int i; currentpos = avio_tell(pb); - if (seekhead->reserved_size > 0) { if (avio_seek(pb, seekhead->filepos, SEEK_SET) < 0) { currentpos = -1; goto fail; } - } if (start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_SEEKHEAD) < 0) { currentpos = -1; @@ -507,16 +475,13 @@ static int64_t mkv_write_seekhead(AVIOContext *pb, MatroskaMuxContext *mkv) } end_ebml_master_crc32(pb, &dyn_cp, mkv); - if (seekhead->reserved_size > 0) { - uint64_t remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); + remaining = seekhead->filepos + seekhead->reserved_size - avio_tell(pb); put_ebml_void(pb, remaining); avio_seek(pb, currentpos, SEEK_SET); currentpos = seekhead->filepos; - } + fail: - av_freep(&mkv->seekhead->entries); - av_freep(&mkv->seekhead); return currentpos; } @@ -1429,9 +1394,7 @@ static int mkv_write_tracks(AVFormatContext *s) AVIOContext *pb = s->pb; int i, ret, default_stream_exists = 0; - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TRACKS, avio_tell(pb)); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_TRACKS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, MATROSKA_ID_TRACKS); if (ret < 0) @@ -1466,8 +1429,7 @@ static int mkv_write_chapters(AVFormatContext *s) if (!s->nb_chapters || mkv->wrote_chapters) return 0; - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_CHAPTERS, avio_tell(pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CHAPTERS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_CHAPTERS); if (ret < 0) return ret; @@ -1556,8 +1518,7 @@ static int mkv_write_tag_targets(AVFormatContext *s, uint32_t elementid, int ret; if (!mkv->tags_bc) { - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_TAGS, avio_tell(s->pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_TAGS, avio_tell(s->pb)); ret = start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, MATROSKA_ID_TAGS); if (ret < 0) @@ -1727,8 +1688,7 @@ static int mkv_write_attachments(AVFormatContext *s) av_lfg_init(&c, av_get_random_seed()); - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); - if (ret < 0) return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_ATTACHMENTS, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, MATROSKA_ID_ATTACHMENTS); if (ret < 0) return ret; @@ -1877,16 +1837,10 @@ static int mkv_write_header(AVFormatContext *s) mkv->segment_offset = avio_tell(pb); // we write a seek head at the beginning to point to all other level - // one elements, which aren't more than 10 elements as we write only one - // of every other currently defined level 1 element - mkv->seekhead = mkv_start_seekhead(pb, mkv->segment_offset, 10); - if (!mkv->seekhead) { - return AVERROR(ENOMEM); - } + // one elements (except Clusters). + mkv_start_seekhead(mkv, pb); - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_INFO, avio_tell(pb)); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_INFO, avio_tell(pb)); ret = start_ebml_master_crc32(pb, &mkv->info_bc, mkv, MATROSKA_ID_INFO); if (ret < 0) @@ -2576,10 +2530,7 @@ static int mkv_write_trailer(AVFormatContext *s) cuespos = mkv_write_cues(s, mkv->cues, mkv->tracks, s->nb_streams); } - ret = mkv_add_seekhead_entry(mkv->seekhead, MATROSKA_ID_CUES, - cuespos); - if (ret < 0) - return ret; + mkv_add_seekhead_entry(mkv, MATROSKA_ID_CUES, cuespos); } mkv_write_seekhead(pb, mkv); diff --git a/tests/fate/matroska.mak b/tests/fate/matroska.mak index 4aca4dc8eb..b9ed7322fd 100644 --- a/tests/fate/matroska.mak +++ b/tests/fate/matroska.mak @@ -12,7 +12,7 @@ fate-matroska-prores-header-insertion-bz2: CMD = framecrc -i $(TARGET_SAMPLES)/m FATE_MATROSKA-$(call DEMMUX, MATROSKA, MATROSKA) += fate-matroska-remux fate-matroska-remux: CMD = md5pipe -i $(TARGET_SAMPLES)/vp9-test-vectors/vp90-2-2pass-akiyo.webm -color_trc 4 -c:v copy -fflags +bitexact -strict -2 -f matroska fate-matroska-remux: CMP = oneline -fate-matroska-remux: REF = e5457e5fa606d564a54914bd12f426c8 +fate-matroska-remux: REF = 49a60ef91cf7302ab7276f9373f8a429 FATE_MATROSKA-$(call ALLYES, MATROSKA_DEMUXER VORBIS_PARSER) += fate-matroska-xiph-lacing fate-matroska-xiph-lacing: CMD = framecrc -i $(TARGET_SAMPLES)/mkv/xiph_lacing.mka -c:a copy diff --git a/tests/fate/wavpack.mak b/tests/fate/wavpack.mak index 286e17f2ae..a89cc1293d 100644 --- a/tests/fate/wavpack.mak +++ b/tests/fate/wavpack.mak @@ -91,12 +91,12 @@ fate-wavpack-matroskamode: CMD = md5 -i $(TARGET_SAMPLES)/wavpack/special/matros FATE_WAVPACK-$(call DEMMUX, WV, MATROSKA) += fate-wavpack-matroska_mux-mono fate-wavpack-matroska_mux-mono: CMD = md5pipe -i $(TARGET_SAMPLES)/wavpack/num_channels/mono_16bit_int.wv -c copy -fflags +bitexact -f matroska fate-wavpack-matroska_mux-mono: CMP = oneline -fate-wavpack-matroska_mux-mono: REF = c5a2b46d8b31c9c2e7bfb436f9d56f02 +fate-wavpack-matroska_mux-mono: REF = 0a1a7c6d3b413925f8261c92d1d53363 FATE_WAVPACK-$(call DEMMUX, WV, MATROSKA) += fate-wavpack-matroska_mux-61 fate-wavpack-matroska_mux-61: CMD = md5pipe -i $(TARGET_SAMPLES)/wavpack/num_channels/eva_2.22_6.1_16bit-partial.wv -c copy -fflags +bitexact -f matroska fate-wavpack-matroska_mux-61: CMP = oneline -fate-wavpack-matroska_mux-61: REF = 22fc2a591be22657d19cab89ad6eef55 +fate-wavpack-matroska_mux-61: REF = 1f012f7c237f46f2f63089ba84c589c5 FATE_SAMPLES_AVCONV += $(FATE_WAVPACK-yes) fate-wavpack: $(FATE_WAVPACK-yes) diff --git a/tests/ref/fate/aac-autobsf-adtstoasc b/tests/ref/fate/aac-autobsf-adtstoasc index 9bf9dfe78f..45b68b62bd 100644 --- a/tests/ref/fate/aac-autobsf-adtstoasc +++ b/tests/ref/fate/aac-autobsf-adtstoasc @@ -1,5 +1,5 @@ -b09fc2f554712adbf84fe7899eb679d4 *tests/data/fate/aac-autobsf-adtstoasc.matroska -6695 tests/data/fate/aac-autobsf-adtstoasc.matroska +d10b73732b32f6b2f08885b40c41c77f *tests/data/fate/aac-autobsf-adtstoasc.matroska +6606 tests/data/fate/aac-autobsf-adtstoasc.matroska #extradata 0: 2, 0x0030001c #tb 0: 1/1000 #media_type 0: audio diff --git a/tests/ref/fate/binsub-mksenc b/tests/ref/fate/binsub-mksenc index 4b13ef217a..3266501de6 100644 --- a/tests/ref/fate/binsub-mksenc +++ b/tests/ref/fate/binsub-mksenc @@ -1 +1 @@ -a5811caa0caba3a3f9a449b91569745c +03d376cc2e8622e7af540d4d9809dbf7 diff --git a/tests/ref/fate/rgb24-mkv b/tests/ref/fate/rgb24-mkv index b9db53f592..43b4ee2e19 100644 --- a/tests/ref/fate/rgb24-mkv +++ b/tests/ref/fate/rgb24-mkv @@ -1,5 +1,5 @@ -ffe2c21083d56764aaae7945fd146326 *tests/data/fate/rgb24-mkv.matroska -58305 tests/data/fate/rgb24-mkv.matroska +92861b36a8220a3dae10d91cba614892 *tests/data/fate/rgb24-mkv.matroska +58216 tests/data/fate/rgb24-mkv.matroska #tb 0: 1/10 #media_type 0: video #codec_id 0: rawvideo diff --git a/tests/ref/lavf-fate/av1.mkv b/tests/ref/lavf-fate/av1.mkv index 242c63a541..bd8ae6eefa 100644 --- a/tests/ref/lavf-fate/av1.mkv +++ b/tests/ref/lavf-fate/av1.mkv @@ -1,3 +1,3 @@ -6604d339d44864be58b8580fe2fb52cc *tests/data/lavf-fate/lavf.av1.mkv -55725 tests/data/lavf-fate/lavf.av1.mkv +be4903a6a6710043383c8f38a535a8d3 *tests/data/lavf-fate/lavf.av1.mkv +55636 tests/data/lavf-fate/lavf.av1.mkv tests/data/lavf-fate/lavf.av1.mkv CRC=0x7c27cc15 diff --git a/tests/ref/lavf/mka b/tests/ref/lavf/mka index a244893cf2..e8239442e9 100644 --- a/tests/ref/lavf/mka +++ b/tests/ref/lavf/mka @@ -1,3 +1,3 @@ -a91bfdb08ff5a1e62b039748d16b07f7 *tests/data/lavf/lavf.mka -43652 tests/data/lavf/lavf.mka +58936d3ad4eb1372bd2e5845963fe17c *tests/data/lavf/lavf.mka +43563 tests/data/lavf/lavf.mka tests/data/lavf/lavf.mka CRC=0x3a1da17e diff --git a/tests/ref/lavf/mkv b/tests/ref/lavf/mkv index 05999b79fb..4d73fbf39d 100644 --- a/tests/ref/lavf/mkv +++ b/tests/ref/lavf/mkv @@ -1,3 +1,3 @@ -3cddd26ed41f34925d3f34acabab174e *tests/data/lavf/lavf.mkv -320555 tests/data/lavf/lavf.mkv +6ec6d6a058a85bf616e0b981bb8e383f *tests/data/lavf/lavf.mkv +320466 tests/data/lavf/lavf.mkv tests/data/lavf/lavf.mkv CRC=0xec6c3c68 diff --git a/tests/ref/lavf/mkv_attachment b/tests/ref/lavf/mkv_attachment index 230dff369a..2e9711483f 100644 --- a/tests/ref/lavf/mkv_attachment +++ b/tests/ref/lavf/mkv_attachment @@ -1,3 +1,3 @@ -4a25c53150c09537cd4fcbff8f8f14ff *tests/data/lavf/lavf.mkv_attachment -472706 tests/data/lavf/lavf.mkv_attachment +41b4f132ada6e0faedf92e909526a899 *tests/data/lavf/lavf.mkv_attachment +472617 tests/data/lavf/lavf.mkv_attachment tests/data/lavf/lavf.mkv_attachment CRC=0xec6c3c68 diff --git a/tests/ref/seek/lavf-mkv b/tests/ref/seek/lavf-mkv index 6ed1f3a763..223e098829 100644 --- a/tests/ref/seek/lavf-mkv +++ b/tests/ref/seek/lavf-mkv @@ -1,48 +1,48 @@ -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st:-1 flags:0 ts:-1.000000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st:-1 flags:1 ts: 1.894167 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:0 ts: 0.788000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:1 ts:-0.317000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 1 flags:0 ts: 2.577000 ret: 0 st: 1 flags:1 ts: 1.471000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret: 0 st:-1 flags:0 ts: 0.365002 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st:-1 flags:1 ts:-0.740831 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 0 flags:0 ts: 2.153000 ret: 0 st: 0 flags:1 ts: 1.048000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 1 flags:0 ts:-0.058000 -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st: 1 flags:1 ts: 2.836000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret:-1 st:-1 flags:0 ts: 1.730004 ret: 0 st:-1 flags:1 ts: 0.624171 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st: 0 flags:0 ts:-0.482000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st: 0 flags:1 ts: 2.413000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret:-1 st: 1 flags:0 ts: 1.307000 ret: 0 st: 1 flags:1 ts: 0.201000 -ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 803 size: 208 +ret: 0 st: 1 flags:1 dts: 0.000000 pts: 0.000000 pos: 714 size: 208 ret: 0 st:-1 flags:0 ts:-0.904994 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret: 0 st:-1 flags:1 ts: 1.989173 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:0 ts: 0.883000 -ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292435 size: 27834 +ret: 0 st: 0 flags:1 dts: 0.971000 pts: 0.971000 pos: 292346 size: 27834 ret: 0 st: 0 flags:1 ts:-0.222000 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 ret:-1 st: 1 flags:0 ts: 2.672000 ret: 0 st: 1 flags:1 ts: 1.566000 -ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320276 size: 209 +ret: 0 st: 1 flags:1 dts: 0.993000 pts: 0.993000 pos: 320187 size: 209 ret: 0 st:-1 flags:0 ts: 0.460008 -ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146987 size: 27925 +ret: 0 st: 0 flags:1 dts: 0.491000 pts: 0.491000 pos: 146898 size: 27925 ret: 0 st:-1 flags:1 ts:-0.645825 -ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 1019 size: 27837 +ret: 0 st: 0 flags:1 dts: 0.011000 pts: 0.011000 pos: 930 size: 27837 -- 2.20.1
Andreas Rheinhardt:
Up until e7ddafd5, the Matroska muxer wrote two SeekHeads: One at the beginning referencing the main level 1 elements (i.e. not the Clusters) and one at the end, referencing the Clusters. This second SeekHead was useless and has therefore been removed. Yet the SeekHead-related functions and structures are still geared towards this usecase: They are built around an allocated array of variable size that gets reallocated every time an element is added to it although the maximum number of Seek entries is a small compile-time constant, so that one should rather include the array in the SeekHead structure itself; and said structure should be contained in the MatroskaMuxContext instead of being allocated separately.
The earlier code reserved space for a SeekHead with 10 entries, although we currently write at most 6. Reducing said number implied that every Matroska/Webm file will be 84 bytes smaller and required to adapt several FATE tests; furthermore, the reserved amount overestimated the amount needed for for the SeekHead's length field and how many bytes need to be reserved to write a EBML Void element, bringing the total reduction to 89 bytes.
This also fixes a potential segfault: If !mkv->is_live and if the AVIOContext is initially unseekable when writing the header, the SeekHead is already written when writing the header and this used to free the SeekHead-related structures that have been allocated. But if the AVIOContext happens to be seekable when writing the trailer, it will be attempted to write the SeekHead again which will lead to segfaults because the corresponding structures have already been freed.
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@gmail.com> --- 1. Changing seekability doesn't seem to happen in real life: Before add68dcc (merged last April) the writing process for all level 1 elements was different depending upon whether the output was seekable or not; if it switched between seekable and unseekable, the output would be corrupt, yet no one reported this. 2. Only the commit message has been modified; the content of this patch is unchanged since last time. 3. Changing seekability would still not go well. I'll work on it.
libavformat/matroskaenc.c | 119 ++++++++------------------- tests/fate/matroska.mak | 2 +- tests/fate/wavpack.mak | 4 +- tests/ref/fate/aac-autobsf-adtstoasc | 4 +- tests/ref/fate/binsub-mksenc | 2 +- tests/ref/fate/rgb24-mkv | 4 +- tests/ref/lavf-fate/av1.mkv | 4 +- tests/ref/lavf/mka | 4 +- tests/ref/lavf/mkv | 4 +- tests/ref/lavf/mkv_attachment | 4 +- tests/ref/seek/lavf-mkv | 44 +++++----- 11 files changed, 73 insertions(+), 122 deletions(-)
Ping. (The actual patch has been left out because of its length, but it can be found at [1].) - Andreas [1]: https://ffmpeg.org/pipermail/ffmpeg-devel/2020-March/258390.html
participants (9)
-
Andreas Rheinhardt -
Andreas Rheinhardt -
Carl Eugen Hoyos -
James Almer -
Michael Niedermayer -
Michael Niedermayer -
mypopy@gmail.com -
Nicolas George -
Paul B Mahol