mirror of
https://github.com/mpv-player/mpv
synced 2024-12-24 15:52:25 +00:00
1edb3d061b
This is fragile enough that it warrants getting "monitored". This takes the commented test program code from img_format.c, makes it output to a text file, and then compares it to a "ref" file stored in git. Originally, I wanted to do the comparison etc. in a shell or Python script. But why not do it in C. So mpv calls /usr/bin/diff as a sub-process now. This test will start producing different output if FFmpeg adds new pixel formats or pixel format flags, or if mpv adds new IMGFMT (either aliases to FFmpeg formats or own formats). That is unavoidable, and requires manual inspection of the results, and then updating the ref file. The changes in the non-test code are to guarantee that the format ID conversion functions only translate between valid IDs.
212 lines
7.9 KiB
C
212 lines
7.9 KiB
C
#include <libavutil/frame.h>
|
|
#include <libavutil/pixdesc.h>
|
|
|
|
#include "tests.h"
|
|
#include "video/fmt-conversion.h"
|
|
#include "video/img_format.h"
|
|
#include "video/mp_image.h"
|
|
#include "video/sws_utils.h"
|
|
|
|
static int imgfmts[IMGFMT_AVPIXFMT_END - IMGFMT_AVPIXFMT_START + 100];
|
|
static int num_imgfmts;
|
|
static enum AVPixelFormat pixfmt_unsup[100];
|
|
static int num_pixfmt_unsup;
|
|
static bool imgfmts_initialized;
|
|
|
|
static int cmp_imgfmt_name(const void *a, const void *b)
|
|
{
|
|
char *name_a = mp_imgfmt_to_name(*(int *)a);
|
|
char *name_b = mp_imgfmt_to_name(*(int *)b);
|
|
|
|
return strcmp(name_a, name_b);
|
|
}
|
|
|
|
static void find_all_imgfmts(void)
|
|
{
|
|
if (imgfmts_initialized)
|
|
return;
|
|
|
|
const AVPixFmtDescriptor *avd = av_pix_fmt_desc_next(NULL);
|
|
for (; avd; avd = av_pix_fmt_desc_next(avd)) {
|
|
enum AVPixelFormat fmt = av_pix_fmt_desc_get_id(avd);
|
|
int mpfmt = pixfmt2imgfmt(fmt);
|
|
if (!mpfmt) {
|
|
assert(num_pixfmt_unsup < MP_ARRAY_SIZE(pixfmt_unsup));
|
|
pixfmt_unsup[num_pixfmt_unsup++] = fmt;
|
|
}
|
|
}
|
|
|
|
for (int fmt = IMGFMT_START; fmt <= IMGFMT_END; fmt++) {
|
|
struct mp_imgfmt_desc d = mp_imgfmt_get_desc(fmt);
|
|
enum AVPixelFormat pixfmt = imgfmt2pixfmt(fmt);
|
|
if (d.id || pixfmt != AV_PIX_FMT_NONE) {
|
|
assert(num_imgfmts < MP_ARRAY_SIZE(imgfmts)); // enlarge that array
|
|
imgfmts[num_imgfmts++] = fmt;
|
|
}
|
|
}
|
|
|
|
qsort(imgfmts, num_imgfmts, sizeof(imgfmts[0]), cmp_imgfmt_name);
|
|
|
|
imgfmts_initialized = true;
|
|
}
|
|
|
|
static const char *comp_type(enum mp_component_type type)
|
|
{
|
|
switch (type) {
|
|
case MP_COMPONENT_TYPE_UINT: return "uint";
|
|
case MP_COMPONENT_TYPE_FLOAT: return "float";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
static void run(struct test_ctx *ctx)
|
|
{
|
|
find_all_imgfmts();
|
|
|
|
FILE *f = test_open_out(ctx, "img_formats.txt");
|
|
|
|
for (int z = 0; z < num_imgfmts; z++) {
|
|
int mpfmt = imgfmts[z];
|
|
enum AVPixelFormat pixfmt = imgfmt2pixfmt(mpfmt);
|
|
const AVPixFmtDescriptor *avd = av_pix_fmt_desc_get(pixfmt);
|
|
|
|
fprintf(f, "%s: ", mp_imgfmt_to_name(mpfmt));
|
|
if (mpfmt >= IMGFMT_AVPIXFMT_START && mpfmt < IMGFMT_AVPIXFMT_END)
|
|
fprintf(f, "[GENERIC] ");
|
|
|
|
int fcsp = mp_imgfmt_get_forced_csp(mpfmt);
|
|
if (fcsp)
|
|
fprintf(f, "fcsp=%s ", m_opt_choice_str(mp_csp_names, fcsp));
|
|
fprintf(f, "ctype=%s\n", comp_type(mp_imgfmt_get_component_type(mpfmt)));
|
|
|
|
struct mp_imgfmt_desc d = mp_imgfmt_get_desc(mpfmt);
|
|
if (d.id) {
|
|
fprintf(f, " Legacy desc: ");
|
|
#define FLAG(t, c) if (d.flags & (t)) fprintf(f, "[%s]", c);
|
|
FLAG(MP_IMGFLAG_BYTE_ALIGNED, "ba")
|
|
FLAG(MP_IMGFLAG_ALPHA, "a")
|
|
FLAG(MP_IMGFLAG_YUV_P, "yuvp")
|
|
FLAG(MP_IMGFLAG_YUV_NV, "nv")
|
|
FLAG(MP_IMGFLAG_YUV, "yuv")
|
|
FLAG(MP_IMGFLAG_RGB, "rgb")
|
|
FLAG(MP_IMGFLAG_LE, "le")
|
|
FLAG(MP_IMGFLAG_BE, "be")
|
|
FLAG(MP_IMGFLAG_PAL, "pal")
|
|
FLAG(MP_IMGFLAG_HWACCEL, "hw")
|
|
fprintf(f, "\n");
|
|
fprintf(f, " planes=%d, chroma=%d:%d align=%d:%d bits=%d cbits=%d\n",
|
|
d.num_planes, d.chroma_xs, d.chroma_ys, d.align_x, d.align_y,
|
|
d.plane_bits, d.component_bits);
|
|
fprintf(f, " {");
|
|
for (int n = 0; n < MP_MAX_PLANES; n++) {
|
|
fprintf(f, "%d/%d/[%d:%d] ", d.bytes[n], d.bpp[n],
|
|
d.xs[n], d.ys[n]);
|
|
}
|
|
fprintf(f, "}\n");
|
|
} else {
|
|
fprintf(f, " [NODESC]\n");
|
|
}
|
|
|
|
if (!(d.flags & MP_IMGFLAG_HWACCEL) && pixfmt != AV_PIX_FMT_NONE) {
|
|
AVFrame *fr = av_frame_alloc();
|
|
fr->format = pixfmt;
|
|
fr->width = 128;
|
|
fr->height = 128;
|
|
int err = av_frame_get_buffer(fr, MP_IMAGE_BYTE_ALIGN);
|
|
assert(err >= 0);
|
|
struct mp_image *mpi = mp_image_alloc(mpfmt, fr->width, fr->height);
|
|
if (mpi) {
|
|
// A rather fuzzy test, which might fail even if there's no bug.
|
|
for (int n = 0; n < 4; n++) {
|
|
if (!!mpi->planes[n] != !!fr->data[n]) {
|
|
#ifdef AV_PIX_FMT_FLAG_PSEUDOPAL
|
|
if (n == 1 && (avd->flags & AV_PIX_FMT_FLAG_PSEUDOPAL))
|
|
continue;
|
|
#endif
|
|
fprintf(f, " Warning: p%d: %p %p\n", n,
|
|
mpi->planes[n], fr->data[n]);
|
|
}
|
|
if (mpi->stride[n] != fr->linesize[n]) {
|
|
fprintf(f, " Warning: p%d: %d %d\n", n,
|
|
mpi->stride[n], fr->linesize[n]);
|
|
}
|
|
}
|
|
} else {
|
|
fprintf(f, " [NOALLOC]\n");
|
|
}
|
|
talloc_free(mpi);
|
|
av_frame_free(&fr);
|
|
}
|
|
|
|
struct mp_regular_imgfmt reg;
|
|
if (mp_get_regular_imgfmt(®, mpfmt)) {
|
|
fprintf(f, " Regular: planes=%d compbytes=%d bitpad=%d "
|
|
"chroma=%dx%d ctype=%s\n",
|
|
reg.num_planes, reg.component_size, reg.component_pad,
|
|
reg.chroma_w, reg.chroma_h, comp_type(reg.component_type));
|
|
for (int n = 0; n < reg.num_planes; n++) {
|
|
struct mp_regular_imgfmt_plane *plane = ®.planes[n];
|
|
fprintf(f, " %d: {", n);
|
|
for (int i = 0; i < plane->num_components; i++) {
|
|
if (i > 0)
|
|
fprintf(f, ", ");
|
|
fprintf(f, "%d", plane->components[i]);
|
|
}
|
|
fprintf(f, "}\n");
|
|
}
|
|
}
|
|
|
|
// This isn't ours, but changes likely affect us.
|
|
if (avd) {
|
|
fprintf(f, " AVD: name=%s chroma=%d:%d flags=0x%"PRIx64, avd->name,
|
|
avd->log2_chroma_w, avd->log2_chroma_h, avd->flags);
|
|
#define FLAGAV(t, c) if (avd->flags & (t)) \
|
|
{fprintf(f, "%s[%s]", pre, c); pre = ""; }
|
|
char *pre = " ";
|
|
FLAGAV(AV_PIX_FMT_FLAG_BE, "be")
|
|
FLAGAV(AV_PIX_FMT_FLAG_PAL, "pal")
|
|
FLAGAV(AV_PIX_FMT_FLAG_BITSTREAM, "bs")
|
|
FLAGAV(AV_PIX_FMT_FLAG_HWACCEL, "hw")
|
|
FLAGAV(AV_PIX_FMT_FLAG_PLANAR, "planar")
|
|
FLAGAV(AV_PIX_FMT_FLAG_RGB, "rgb")
|
|
FLAGAV(AV_PIX_FMT_FLAG_ALPHA, "alpha")
|
|
FLAGAV(AV_PIX_FMT_FLAG_BAYER, "bayer")
|
|
FLAGAV(AV_PIX_FMT_FLAG_FLOAT, "float")
|
|
fprintf(f, "\n");
|
|
for (int n = 0; n < avd->nb_components; n++) {
|
|
const AVComponentDescriptor *cd = &avd->comp[n];
|
|
fprintf(f, " %d: p=%-2d st=%-2d o=%-2d sh=%-2d d=%d\n",
|
|
n, cd->plane, cd->step, cd->offset, cd->shift, cd->depth);
|
|
}
|
|
for (int n = avd->nb_components; n < 4; n++) {
|
|
const AVComponentDescriptor *cd = &avd->comp[n];
|
|
assert(!cd->plane && !cd->step && !cd->offset && !cd->shift &&
|
|
!cd->depth);
|
|
}
|
|
}
|
|
|
|
const AVPixFmtDescriptor *avd2 = av_pix_fmt_desc_next(NULL);
|
|
for (; avd2; avd2 = av_pix_fmt_desc_next(avd2)) {
|
|
enum AVPixelFormat pixfmt2 = av_pix_fmt_desc_get_id(avd2);
|
|
int mpfmt2 = pixfmt2imgfmt(pixfmt2);
|
|
if (mpfmt2 == mpfmt && pixfmt2 != pixfmt)
|
|
fprintf(f, " Ambiguous alias: %s\n", avd2->name);
|
|
}
|
|
}
|
|
|
|
for (int z = 0; z < num_pixfmt_unsup; z++) {
|
|
const AVPixFmtDescriptor *avd = av_pix_fmt_desc_get(pixfmt_unsup[z]);
|
|
fprintf(f, "Unsupported: %s\n", avd->name);
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
assert_text_files_equal(ctx, "img_formats.txt", "img_formats.txt",
|
|
"This can fail if FFmpeg adds new formats or flags.");
|
|
}
|
|
|
|
const struct unittest test_img_format = {
|
|
.name = "img_format",
|
|
.run = run,
|
|
};
|