This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 17c109e8d94d19976dc2f5db9c3577b3f831df43 Author: marcos ashton <[email protected]> AuthorDate: Sat Apr 18 14:30:39 2026 +0100 Commit: michaelni <[email protected]> CommitDate: Thu Aug 27 10:54:38 2026 +0000 tests/fate/libavutil: add FATE test for side_data Test av_frame_side_data_desc and av_frame_side_data_name on valid and invalid types, av_frame_side_data_get and its _c variant for present and absent types, av_frame_side_data_remove (including MULTI types with duplicates), av_frame_side_data_remove_by_props filtering by AV_SIDE_DATA_PROP_*, av_frame_side_data_clone with and without AV_FRAME_SIDE_DATA_FLAG_REPLACE (including the AVERROR(EEXIST) and AVERROR(EINVAL) paths) and with AV_FRAME_SIDE_DATA_FLAG_UNIQUE, av_frame_side_data_add in move mode, with AV_FRAME_SIDE_DATA_FLAG_NEW_REF and with AV_FRAME_SIDE_DATA_FLAG_REPLACE over an existing entry, and av_frame_side_data_free. Coverage for libavutil/side_data.c: 72.14% -> 86.43% Numbers measured against a full FATE run. Remaining uncovered lines are internal allocation-failure error paths requiring injected failures mid-operation. Signed-off-by: marcos ashton <[email protected]> --- .forgejo/CODEOWNERS | 2 + libavutil/Makefile | 1 + libavutil/tests/.gitignore | 1 + libavutil/tests/side_data.c | 328 ++++++++++++++++++++++++++++++++++++++++++++ tests/fate/libavutil.mak | 4 + tests/ref/fate/side_data | 60 ++++++++ 6 files changed, 396 insertions(+) diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS index 3e55f000d7..325cd43aac 100644 --- a/.forgejo/CODEOWNERS +++ b/.forgejo/CODEOWNERS @@ -242,6 +242,7 @@ libavutil/tests/hdr_dynamic_metadata.* @MarcosAsh libavutil/tests/hdr_dynamic_vivid_metadata.* @MarcosAsh libavutil/tests/mastering_display_metadata.* @MarcosAsh libavutil/tests/pixdesc.* @MarcosAsh +libavutil/tests/side_data.* @MarcosAsh libavutil/tests/tdrdi.* @MarcosAsh libavutil/tests/timestamp.* @MarcosAsh libavutil/tests/video_hint.* @MarcosAsh @@ -255,6 +256,7 @@ tests/ref/fate/hdr_dynamic_metadata @MarcosAsh tests/ref/fate/hdr_dynamic_vivid_metadata @MarcosAsh tests/ref/fate/mastering_display_metadata @MarcosAsh tests/ref/fate/pixdesc @MarcosAsh +tests/ref/fate/side_data @MarcosAsh tests/ref/fate/sub-mcc.* @programmerjake tests/ref/fate/tdrdi @MarcosAsh tests/ref/fate/timestamp @MarcosAsh diff --git a/libavutil/Makefile b/libavutil/Makefile index a1170af6eb..92174fa29e 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -311,6 +311,7 @@ TESTPROGS = adler32 \ sha \ sha512 \ samplefmt \ + side_data \ side_data_array \ softfloat \ spherical \ diff --git a/libavutil/tests/.gitignore b/libavutil/tests/.gitignore index 6e69431bca..ac30059249 100644 --- a/libavutil/tests/.gitignore +++ b/libavutil/tests/.gitignore @@ -58,6 +58,7 @@ /samplefmt /sha /sha512 +/side_data /side_data_array /softfloat /spherical diff --git a/libavutil/tests/side_data.c b/libavutil/tests/side_data.c new file mode 100644 index 0000000000..3900cb9587 --- /dev/null +++ b/libavutil/tests/side_data.c @@ -0,0 +1,328 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <string.h> + +#include "libavutil/avassert.h" +#include "libavutil/buffer.h" +#include "libavutil/dict.h" +#include "libavutil/display.h" +#include "libavutil/downmix_info.h" +#include "libavutil/error.h" +#include "libavutil/frame.h" +#include "libavutil/macros.h" +#include "libavutil/mem.h" +#include "libavutil/motion_vector.h" +#include "libavutil/spherical.h" +#include "libavutil/stereo3d.h" + +typedef struct Set { + AVFrameSideData **sd; + int nb_sd; +} Set; + +/* Every entry below carries a payload laid out as AVFrameSideDataType + * documents for its type: a producer owes that layout even when no consumer + * reads the buffer back. The small number that identifies an entry in the + * reference output therefore lives in the metadata dictionary, which + * av_frame_side_data_clone() copies along with the buffer. */ +static void set_marker(AVFrameSideData *sd, int marker) +{ + char value[16]; + + snprintf(value, sizeof(value), "%d", marker); + av_assert0(av_dict_set(&sd->metadata, "marker", value, 0) >= 0); +} + +static AVFrameSideData *new_entry(Set *s, enum AVFrameSideDataType type, + const void *payload, size_t size, int marker) +{ + AVFrameSideData *sd = av_frame_side_data_new(&s->sd, &s->nb_sd, type, + size, 0); + + av_assert0(sd && sd->size == size); + memcpy(sd->data, payload, size); + set_marker(sd, marker); + return sd; +} + +static AVBufferRef *payload_buffer(const void *payload, size_t size) +{ + AVBufferRef *buf = av_buffer_alloc(size); + + av_assert0(buf); + memcpy(buf->data, payload, size); + return buf; +} + +static const char *entry_marker(const AVFrameSideData *sd) +{ + const AVDictionaryEntry *e = av_dict_get(sd->metadata, "marker", NULL, 0); + + return e ? e->value : "none"; +} + +static void print_set(const char *label, const Set *s) +{ + printf("%s: n=%d\n", label, s->nb_sd); + for (int i = 0; i < s->nb_sd; i++) + printf(" [%d] %s marker=%s\n", i, + av_frame_side_data_name(s->sd[i]->type), + entry_marker(s->sd[i])); +} + +/* Dumps a set by looking each type up instead of walking the array. Removal is + * documented to drop the matching entries, not to keep the survivors where + * they were, so printing by index would tie the reference to the current habit + * of moving the last entry into the freed slot. */ +static void print_lookup(const char *label, const Set *s, + const enum AVFrameSideDataType *types, size_t nb_types) +{ + printf("%s: n=%d\n", label, s->nb_sd); + for (size_t i = 0; i < nb_types; i++) { + const AVFrameSideData *sd = + av_frame_side_data_get(s->sd, s->nb_sd, types[i]); + const char *name = av_frame_side_data_name(types[i]); + + if (sd) + printf(" %s: marker=%s\n", name, entry_marker(sd)); + else + printf(" %s: absent\n", name); + } +} + +int main(void) +{ + /* desc / name accessors. */ + printf("Testing av_frame_side_data_desc() / av_frame_side_data_name()\n"); + static const enum AVFrameSideDataType known[] = { + AV_FRAME_DATA_STEREO3D, /* PROP_GLOBAL */ + AV_FRAME_DATA_DYNAMIC_HDR_PLUS, /* PROP_COLOR_DEPENDENT */ + AV_FRAME_DATA_SPHERICAL, /* PROP_GLOBAL|SIZE_DEPENDENT */ + AV_FRAME_DATA_DOWNMIX_INFO, /* PROP_CHANNEL_DEPENDENT */ + AV_FRAME_DATA_SEI_UNREGISTERED, /* PROP_MULTI */ + }; + for (size_t i = 0; i < FF_ARRAY_ELEMS(known); i++) { + const AVSideDataDescriptor *d = av_frame_side_data_desc(known[i]); + const char *n = av_frame_side_data_name(known[i]); + /* Compare content: the API only promises a string identifying the + * type, not the descriptor's own pointer. */ + printf(" type=%d name_match=%s props=0x%x\n", known[i], + d && n && !strcmp(d->name, n) ? "yes" : "no", + d ? d->props : 0); + } + + /* Invalid / unmapped types must return NULL (sd_props gap). */ + { + enum AVFrameSideDataType bad = (enum AVFrameSideDataType)-1; + printf(" invalid type desc=%s name=%s\n", + av_frame_side_data_desc(bad) ? "non-null" : "null", + av_frame_side_data_name(bad) ? "non-null" : "null"); + } + + /* Payloads. The two structs are allocated through their public + * allocators because their sizes are not part of the public ABI. */ + size_t stereo3d_size, spherical_size; + AVStereo3D *stereo3d = av_stereo3d_alloc_size(&stereo3d_size); + AVSphericalMapping *spherical = av_spherical_alloc(&spherical_size); + /* 16 bytes of uuid_iso_iec_11578 followed by user_data_payload_byte. */ + uint8_t udu[16 + 8] = { + 0x9a, 0x21, 0xf3, 0x4d, 0x6b, 0x1c, 0x47, 0x8e, + 0xa5, 0x30, 0xc2, 0x7f, 0x11, 0x8d, 0x46, 0x02, + 'p', 'a', 'y', 'l', 'o', 'a', 'd', '1', + }; + int32_t displaymatrix[9]; + /* src_x = dst_x + motion_x / motion_scale, likewise for y. */ + AVMotionVector mvs[2] = { + { .source = -1, .w = 16, .h = 16, .src_x = 32, .src_y = 48, + .dst_x = 40, .dst_y = 48, .motion_x = -8, .motion_y = 0, + .motion_scale = 1 }, + { .source = 1, .w = 8, .h = 8, .src_x = 64, .src_y = 64, + .dst_x = 64, .dst_y = 72, .motion_x = 0, .motion_y = -8, + .motion_scale = 1 }, + }; + /* The mix levels are absolute linear scale factors, not dB. */ + AVDownmixInfo downmix = { + .preferred_downmix_type = AV_DOWNMIX_TYPE_LTRT, + .center_mix_level = 0.5, + .center_mix_level_ltrt = 0.625, + .surround_mix_level = 0.25, + .surround_mix_level_ltrt = 0.375, + .lfe_mix_level = 0.125, + }; + + av_assert0(stereo3d && spherical); + + stereo3d->type = AV_STEREO3D_SIDEBYSIDE; + stereo3d->view = AV_STEREO3D_VIEW_PACKED; + stereo3d->primary_eye = AV_PRIMARY_EYE_LEFT; + stereo3d->baseline = 65000; + + spherical->projection = AV_SPHERICAL_EQUIRECTANGULAR; + spherical->yaw = 90 << 16; + + av_display_rotation_set(displaymatrix, 90.0); + + /* Populate a set with several types, one of them MULTI. */ + Set set = { 0 }; + new_entry(&set, AV_FRAME_DATA_STEREO3D, stereo3d, stereo3d_size, 100); + new_entry(&set, AV_FRAME_DATA_DOWNMIX_INFO, &downmix, sizeof(downmix), + 200); + new_entry(&set, AV_FRAME_DATA_SEI_UNREGISTERED, udu, sizeof(udu), 1); + udu[sizeof(udu) - 1] = '2'; + new_entry(&set, AV_FRAME_DATA_SEI_UNREGISTERED, udu, sizeof(udu), 2); + new_entry(&set, AV_FRAME_DATA_SPHERICAL, spherical, spherical_size, 300); + /* Entries are appended, so this dump is the insertion order by + * construction and shows both MULTI entries. */ + print_set("\nInitial set", &set); + + /* Types probed by every dump below. */ + static const enum AVFrameSideDataType probed[] = { + AV_FRAME_DATA_STEREO3D, + AV_FRAME_DATA_DOWNMIX_INFO, + AV_FRAME_DATA_SEI_UNREGISTERED, + AV_FRAME_DATA_SPHERICAL, + AV_FRAME_DATA_DISPLAYMATRIX, + AV_FRAME_DATA_MOTION_VECTORS, + }; + + /* get / get_c: present and missing types. */ + printf("\nTesting av_frame_side_data_get()\n"); + { + const AVFrameSideData *got = + av_frame_side_data_get(set.sd, set.nb_sd, AV_FRAME_DATA_STEREO3D); + printf(" stereo3d: %s marker=%s payload_match=%s\n", + got ? "found" : "missing", got ? entry_marker(got) : "none", + got && got->size == stereo3d_size && + !memcmp(got->data, stereo3d, stereo3d_size) ? "yes" : "no"); + got = av_frame_side_data_get(set.sd, set.nb_sd, + AV_FRAME_DATA_MASTERING_DISPLAY_METADATA); + printf(" mastering (absent): %s\n", got ? "FAIL" : "null"); + } + + /* remove by type clears all matching entries (including duplicates). */ + printf("\nTesting av_frame_side_data_remove()\n"); + av_frame_side_data_remove(&set.sd, &set.nb_sd, + AV_FRAME_DATA_SEI_UNREGISTERED); + print_lookup(" after remove SEI_UNREGISTERED", &set, + probed, FF_ARRAY_ELEMS(probed)); + + /* remove_by_props: PROP_GLOBAL drops STEREO3D and SPHERICAL. */ + printf("\nTesting av_frame_side_data_remove_by_props()\n"); + av_frame_side_data_remove_by_props(&set.sd, &set.nb_sd, + AV_SIDE_DATA_PROP_GLOBAL); + print_lookup(" after remove_by_props(GLOBAL)", &set, + probed, FF_ARRAY_ELEMS(probed)); + + /* Clone: copy remaining entry into a second set. */ + printf("\nTesting av_frame_side_data_clone()\n"); + Set dst = { 0 }; + if (set.nb_sd > 0) { + int ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd, + set.sd[0], 0); + printf(" clone into empty: ret=%d nb=%d marker=%s\n", ret, dst.nb_sd, + dst.nb_sd ? entry_marker(dst.sd[0]) : "none"); + + /* Same type again without REPLACE must fail with EEXIST. */ + ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd, set.sd[0], 0); + printf(" clone duplicate no REPLACE: ret==AVERROR(EEXIST)=%s\n", + ret == AVERROR(EEXIST) ? "yes" : "no"); + + /* With REPLACE, clone replaces the existing entry in place. */ + ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd, set.sd[0], + AV_FRAME_SIDE_DATA_FLAG_REPLACE); + printf(" clone REPLACE: ret=%d nb=%d\n", ret, dst.nb_sd); + + /* Invalid args. */ + ret = av_frame_side_data_clone(NULL, &dst.nb_sd, set.sd[0], 0); + printf(" clone NULL sd: ret==AVERROR(EINVAL)=%s\n", + ret == AVERROR(EINVAL) ? "yes" : "no"); + } + + /* add: buffer ownership transfer and NEW_REF. */ + printf("\nTesting av_frame_side_data_add()\n"); + { + AVBufferRef *buf = payload_buffer(displaymatrix, sizeof(displaymatrix)); + + AVFrameSideData *sd_added = + av_frame_side_data_add(&dst.sd, &dst.nb_sd, + AV_FRAME_DATA_DISPLAYMATRIX, + &buf, AV_FRAME_SIDE_DATA_FLAG_UNIQUE); + printf(" add (move): added=%s pbuf_nulled=%s\n", + sd_added ? "yes" : "no", buf == NULL ? "yes" : "no"); + if (sd_added) + set_marker(sd_added, 999); + + /* NEW_REF: the caller retains its reference. */ + AVBufferRef *keep = payload_buffer(mvs, sizeof(mvs)); + sd_added = av_frame_side_data_add(&dst.sd, &dst.nb_sd, + AV_FRAME_DATA_MOTION_VECTORS, &keep, + AV_FRAME_SIDE_DATA_FLAG_NEW_REF); + printf(" add (NEW_REF): added=%s caller_ref_kept=%s nb_mvs=%d\n", + sd_added ? "yes" : "no", keep ? "yes" : "no", + sd_added ? (int)(sd_added->size / sizeof(AVMotionVector)) : -1); + if (sd_added) + set_marker(sd_added, 42); + av_buffer_unref(&keep); + + /* REPLACE on existing entry: same type, payload swaps in place. */ + int32_t replacement[9]; + av_display_rotation_set(replacement, 180.0); + AVBufferRef *repl = payload_buffer(replacement, sizeof(replacement)); + sd_added = av_frame_side_data_add(&dst.sd, &dst.nb_sd, + AV_FRAME_DATA_DISPLAYMATRIX, &repl, + AV_FRAME_SIDE_DATA_FLAG_REPLACE); + if (sd_added) + set_marker(sd_added, 1234); + { + const AVFrameSideData *after = + av_frame_side_data_get(dst.sd, dst.nb_sd, + AV_FRAME_DATA_DISPLAYMATRIX); + printf(" add (REPLACE existing): added=%s marker=%s" + " payload_match=%s\n", + sd_added ? "yes" : "no", + after ? entry_marker(after) : "none", + after && after->size == sizeof(replacement) && + !memcmp(after->data, replacement, sizeof(replacement)) ? + "yes" : "no"); + } + } + + /* clone with UNIQUE: existing same-type entry is removed first. dst + * already has a set.sd[0]->type entry from the REPLACE clone above. */ + printf("\nTesting av_frame_side_data_clone() UNIQUE\n"); + if (set.nb_sd > 0) { + int before = dst.nb_sd; + int ret = av_frame_side_data_clone(&dst.sd, &dst.nb_sd, set.sd[0], + AV_FRAME_SIDE_DATA_FLAG_UNIQUE); + printf(" clone UNIQUE: ret=%d before=%d after=%d\n", + ret, before, dst.nb_sd); + } + + print_lookup("\nFinal dst", &dst, probed, FF_ARRAY_ELEMS(probed)); + + av_frame_side_data_free(&dst.sd, &dst.nb_sd); + av_frame_side_data_free(&set.sd, &set.nb_sd); + printf("\nfree: set_nb=%d dst_nb=%d\n", set.nb_sd, dst.nb_sd); + + av_free(stereo3d); + av_free(spherical); + + return 0; +} diff --git a/tests/fate/libavutil.mak b/tests/fate/libavutil.mak index 7796c21b7f..11c495d361 100644 --- a/tests/fate/libavutil.mak +++ b/tests/fate/libavutil.mak @@ -204,6 +204,10 @@ FATE_LIBAVUTIL += fate-samplefmt fate-samplefmt: libavutil/tests/samplefmt$(EXESUF) fate-samplefmt: CMD = run libavutil/tests/samplefmt$(EXESUF) +FATE_LIBAVUTIL += fate-side_data +fate-side_data: libavutil/tests/side_data$(EXESUF) +fate-side_data: CMD = run libavutil/tests/side_data$(EXESUF) + FATE_LIBAVUTIL += fate-side_data_array fate-side_data_array: libavutil/tests/side_data_array$(EXESUF) fate-side_data_array: CMD = run libavutil/tests/side_data_array$(EXESUF) diff --git a/tests/ref/fate/side_data b/tests/ref/fate/side_data new file mode 100644 index 0000000000..5d0b230765 --- /dev/null +++ b/tests/ref/fate/side_data @@ -0,0 +1,60 @@ +Testing av_frame_side_data_desc() / av_frame_side_data_name() + type=2 name_match=yes props=0x1 + type=17 name_match=yes props=0x8 + type=13 name_match=yes props=0x5 + type=4 name_match=yes props=0x10 + type=20 name_match=yes props=0x2 + invalid type desc=null name=null + +Initial set: n=5 + [0] Stereo 3D marker=100 + [1] Metadata relevant to a downmix procedure marker=200 + [2] H.26[45] User Data Unregistered SEI message marker=1 + [3] H.26[45] User Data Unregistered SEI message marker=2 + [4] Spherical Mapping marker=300 + +Testing av_frame_side_data_get() + stereo3d: found marker=100 payload_match=yes + mastering (absent): null + +Testing av_frame_side_data_remove() + after remove SEI_UNREGISTERED: n=3 + Stereo 3D: marker=100 + Metadata relevant to a downmix procedure: marker=200 + H.26[45] User Data Unregistered SEI message: absent + Spherical Mapping: marker=300 + 3x3 displaymatrix: absent + Motion vectors: absent + +Testing av_frame_side_data_remove_by_props() + after remove_by_props(GLOBAL): n=1 + Stereo 3D: absent + Metadata relevant to a downmix procedure: marker=200 + H.26[45] User Data Unregistered SEI message: absent + Spherical Mapping: absent + 3x3 displaymatrix: absent + Motion vectors: absent + +Testing av_frame_side_data_clone() + clone into empty: ret=0 nb=1 marker=200 + clone duplicate no REPLACE: ret==AVERROR(EEXIST)=yes + clone REPLACE: ret=0 nb=1 + clone NULL sd: ret==AVERROR(EINVAL)=yes + +Testing av_frame_side_data_add() + add (move): added=yes pbuf_nulled=yes + add (NEW_REF): added=yes caller_ref_kept=yes nb_mvs=2 + add (REPLACE existing): added=yes marker=1234 payload_match=yes + +Testing av_frame_side_data_clone() UNIQUE + clone UNIQUE: ret=0 before=3 after=3 + +Final dst: n=3 + Stereo 3D: absent + Metadata relevant to a downmix procedure: marker=200 + H.26[45] User Data Unregistered SEI message: absent + Spherical Mapping: absent + 3x3 displaymatrix: marker=1234 + Motion vectors: marker=42 + +free: set_nb=0 dst_nb=0 -- To stop receiving notification emails like this one, please contact [email protected]. _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
