mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2026-10-28 18:15:24 +08:00
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 <marcosashiglesias@gmail.com>
329 lines
13 KiB
C
329 lines
13 KiB
C
/*
|
|
* 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;
|
|
}
|