[FFmpeg-devel] [PATCH 4/4] hwcontext: Add test for frames context creation and transfer
Mark Thompson
sw at jkqxz.net
Tue May 22 01:35:11 EEST 2018
Creates a frames context of every available type and attempts to
upload/download frame data to each, checking that the output is the same
as the input in each case.
---
This test passes with VDPAU and OpenCL, but may fail for others.
For VAAPI the main issue is that there isn't really a consistent way to determine what formats are supported as surfaces, as images and for upload/download.
With the i965 driver:
* YUV 4:2:2 planar works as surfaces and images but can't be downloaded.
* Greyscale images can't be created.
* Uploading RGB and then downloading it again gives different results. It looks like it might have gone via 4:2:0 somewhere?
* With older hardware (Haswell), P010 appears to be supported but always fails.
With the Mesa driver:
* Uploading UYVY and then downloading it doesn't match halfway through the image. Halfway suggests another consequence of the separated fields madness, I didn't look very carefully.
* It aborts when asked to make a YUYV (YUY2) surface. This should be easily fixable in the driver.
DXVA2 can fail because format support is not checked at init time (see patch 1/4 here for OpenCL). Should that be changed?
D3D11 and libmfx require fixed-size pools to create, which I don't attempt to deal with here.
Any thoughts invited.
- Mark
libavutil/Makefile | 1 +
libavutil/tests/hwframes.c | 404 +++++++++++++++++++++++++++++++++++++++++++++
tests/fate/hw.mak | 5 +
3 files changed, 410 insertions(+)
create mode 100644 libavutil/tests/hwframes.c
diff --git a/libavutil/Makefile b/libavutil/Makefile
index d0632f16a6..59b8d52e04 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -207,6 +207,7 @@ TESTPROGS = adler32 \
hash \
hmac \
hwdevice \
+ hwframes \
integer \
imgutils \
lfg \
diff --git a/libavutil/tests/hwframes.c b/libavutil/tests/hwframes.c
new file mode 100644
index 0000000000..32e3cdbdf9
--- /dev/null
+++ b/libavutil/tests/hwframes.c
@@ -0,0 +1,404 @@
+/*
+ * 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 "libavutil/hwcontext.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/pixdesc.h"
+
+static int test_frames_transfer(AVBufferRef *frames_ref)
+{
+ AVHWFramesContext *frames = (AVHWFramesContext*)frames_ref->data;
+ const AVPixFmtDescriptor *desc;
+ AVFrame *src, *hw, *dst;
+ int planes, line_width[4], plane_height[4];
+ int c, p, x, y, err;
+ uint8_t val;
+
+ desc = av_pix_fmt_desc_get(frames->sw_format);
+ if (!desc)
+ return -1;
+
+ err = av_image_fill_linesizes(line_width,
+ frames->sw_format, frames->width);
+ if (err < 0) {
+ fprintf(stderr, "Failed to determine line widths for format %s.\n",
+ desc->name);
+ return -1;
+ }
+ planes = 0;
+ for (c = 0; c < desc->nb_components; c++)
+ planes = FFMAX(planes, desc->comp[c].plane + 1);
+ for (p = 0; p < planes; p++) {
+ plane_height[p] = AV_CEIL_RSHIFT(frames->height,
+ desc->log2_chroma_h);
+ }
+
+ src = av_frame_alloc();
+ hw = av_frame_alloc();
+ dst = av_frame_alloc();
+ if (!src || !hw || !dst) {
+ fprintf(stderr, "Failed to allocate frames.\n");
+ err = -1;
+ goto fail;
+ }
+
+ src->format = dst->format = frames->sw_format;
+ src->width = dst->width = frames->width;
+ src->height = dst->height = frames->height;
+
+ err = av_frame_get_buffer(src, 0);
+ if (err < 0) {
+ fprintf(stderr, "Failed to allocate source frame data.\n");
+ err = -1;
+ goto fail;
+ }
+ err = av_hwframe_get_buffer(frames_ref, hw, 0);
+ if (err < 0) {
+ fprintf(stderr, "Failed to allocate hardware frame.\n");
+ err = -1;
+ goto fail;
+ }
+ err = av_frame_get_buffer(dst, 0);
+ if (err < 0) {
+ fprintf(stderr, "Failed to allocate destination frame data.\n");
+ err = -1;
+ goto fail;
+ }
+
+ val = 0;
+ for (p = 0; p < planes; p++) {
+ for (y = 0; y < plane_height[p]; y++) {
+ for (x = 0; x < line_width[p]; x++) {
+ src->data[p][y * src->linesize[p] + x] = val;
+ val += 127;
+ }
+ }
+ }
+
+ err = av_hwframe_transfer_data(hw, src, 0);
+ if (err < 0) {
+ fprintf(stderr, "Failed to transfer data to hardware frame: %d.\n",
+ err);
+ err = -1;
+ goto fail;
+ }
+
+ err = av_hwframe_transfer_data(dst, hw, 0);
+ if (err < 0) {
+ fprintf(stderr, "Failed to transfer data to hardware frame: %d.\n",
+ err);
+ err = -1;
+ goto fail;
+ }
+
+ for (p = 0; p < planes; p++) {
+ for (y = 0; y < plane_height[p]; y++) {
+ for (x = 0; x < line_width[p]; x++) {
+ if (src->data[p][y * src->linesize[p] + x] !=
+ dst->data[p][y * dst->linesize[p] + x]) {
+ fprintf(stderr, "Downloaded frame does not match "
+ "uploaded frame: differs at plane %d line %d "
+ "byte %d: wrote %d read %d.\n", p, y, x,
+ src->data[p][y * src->linesize[p] + x],
+ dst->data[p][y * dst->linesize[p] + x]);
+ err = -1;
+ goto fail;
+ }
+ }
+ }
+ }
+
+ fprintf(stderr, "Transfer successful for %s %dx%d.\n",
+ desc->name, frames->width, frames->height);
+
+fail:
+ av_frame_free(&src);
+ av_frame_free(&hw);
+ av_frame_free(&dst);
+ return err;
+}
+
+static void test_free_function(AVHWFramesContext *frames)
+{
+ int *free_check = frames->user_opaque;
+
+ *free_check = 1;
+}
+
+static int test_format(AVBufferRef *device_ref, enum AVPixelFormat hw_format,
+ enum AVPixelFormat sw_format, int expect_failure)
+{
+ AVBufferRef *frames_ref;
+ AVHWFramesContext *frames;
+ const AVPixFmtDescriptor *hw_desc, *sw_desc;
+ int err, free_check;
+
+ hw_desc = av_pix_fmt_desc_get(hw_format);
+ sw_desc = av_pix_fmt_desc_get(sw_format);
+ if (!hw_desc || !sw_desc) {
+ fprintf(stderr, "Frame format %d / %d is invalid.\n",
+ hw_format, sw_format);
+ return -1;
+ }
+
+ frames_ref = av_hwframe_ctx_alloc(device_ref);
+ if (!frames_ref) {
+ fprintf(stderr, "Failed to allocate frames context.\n");
+ return -1;
+ }
+
+ frames = (AVHWFramesContext*)frames_ref->data;
+
+ frames->format = hw_format;
+ frames->sw_format = sw_format;
+
+ frames->width = 640;
+ frames->height = 480;
+
+ free_check = 0;
+ frames->free = &test_free_function;
+ frames->user_opaque = &free_check;
+
+ err = av_hwframe_ctx_init(frames_ref);
+ if (err < 0) {
+ av_buffer_unref(&frames_ref);
+ if (!expect_failure) {
+ fprintf(stderr, "Failed to create frames with format %s / %s.\n",
+ hw_desc->name, sw_desc->name);
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ if (expect_failure) {
+ fprintf(stderr, "Successfully created frames with format %s / %s, "
+ "but expected to fail.\n", hw_desc->name, sw_desc->name);
+ // Continue anyway.
+ }
+
+ fprintf(stderr, "Frames created with format %s / %s.\n",
+ hw_desc->name, sw_desc->name);
+
+ err = test_frames_transfer(frames_ref);
+
+ if (free_check != 0) {
+ fprintf(stderr, "Free check value incorrect before freeing: %d.\n",
+ free_check);
+ err = -1;
+ }
+ av_buffer_unref(&frames_ref);
+ if (free_check != 1) {
+ fprintf(stderr, "Free check value incorrect after freeing: %d.\n",
+ free_check);
+ err = -1;
+ }
+
+ return err;
+}
+
+static enum AVPixelFormat test_format_list[] = {
+ // YUV 4:2:0 formats;
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_NV12,
+ AV_PIX_FMT_YUV420P10,
+ AV_PIX_FMT_P010,
+ AV_PIX_FMT_YUV420P16,
+ AV_PIX_FMT_P016,
+ // YUV 4:2:2 formats.
+ AV_PIX_FMT_YUYV422,
+ AV_PIX_FMT_UYVY422,
+ AV_PIX_FMT_YUV422P,
+ AV_PIX_FMT_YUV422P10,
+ AV_PIX_FMT_YUV422P16,
+ // YUV 4:4:4 formats.
+ AV_PIX_FMT_YUV444P,
+ AV_PIX_FMT_YUV444P10,
+ AV_PIX_FMT_YUV444P16,
+ // Packed RGB / RGBA.
+ AV_PIX_FMT_RGBA,
+ AV_PIX_FMT_BGRA,
+ AV_PIX_FMT_RGB0,
+ AV_PIX_FMT_BGR0,
+ // Planar RGB,
+ AV_PIX_FMT_GBRP,
+ AV_PIX_FMT_GBRP10,
+ AV_PIX_FMT_GBRP16,
+ // A valid format which won't be supported in hardware.
+ AV_PIX_FMT_PAL8,
+};
+
+static int test_device(enum AVHWDeviceType type, const char *name,
+ const char *device, AVDictionary *opts, int flags)
+{
+ AVBufferRef *ref;
+ AVHWFramesConstraints *constraints = NULL;
+ enum AVPixelFormat hw_format, sw_format;
+ int i, j, k, failures, err;
+
+ err = av_hwdevice_ctx_create(&ref, type, device, opts, flags);
+ if (err < 0) {
+ fprintf(stderr, "Failed to create %s device: %d.\n", name, err);
+ return 1;
+ }
+
+ constraints = av_hwdevice_get_hwframe_constraints(ref, NULL);
+ if (!constraints || !constraints->valid_hw_formats) {
+ fprintf(stderr, "Device does not implement constraints.\n");
+ err = 1;
+ goto done;
+ }
+
+ failures = 0;
+ for (i = 0; constraints->valid_hw_formats[i] != AV_PIX_FMT_NONE; i++) {
+ hw_format = constraints->valid_hw_formats[i];
+
+ if (constraints->valid_sw_formats) {
+ for (j = 0; constraints->valid_sw_formats[j] !=
+ AV_PIX_FMT_NONE; j++) {
+ sw_format = constraints->valid_sw_formats[j];
+
+ err = test_format(ref, hw_format, sw_format, 0);
+ if (err < 0)
+ ++failures;
+ }
+ }
+ for (j = 0; j < FF_ARRAY_ELEMS(test_format_list); j++) {
+ sw_format = test_format_list[j];
+ for (k = 0; constraints->valid_sw_formats[k] !=
+ AV_PIX_FMT_NONE; k++) {
+ if (constraints->valid_sw_formats[k] == sw_format)
+ sw_format = AV_PIX_FMT_NONE;
+ }
+ if (sw_format == AV_PIX_FMT_NONE)
+ continue;
+
+ err = test_format(ref, hw_format, sw_format, 1);
+ if (err < 0)
+ ++failures;
+ }
+ }
+
+ err = failures ? -1 : 0;
+done:
+ av_hwframe_constraints_free(&constraints);
+ av_buffer_unref(&ref);
+ return err;
+}
+
+static const struct {
+ enum AVHWDeviceType type;
+ const char *possible_devices[5];
+} test_devices[] = {
+ { AV_HWDEVICE_TYPE_CUDA,
+ { "0", "1", "2" } },
+ { AV_HWDEVICE_TYPE_DRM,
+ { "/dev/dri/card0", "/dev/dri/card1",
+ "/dev/dri/renderD128", "/dev/dri/renderD129" } },
+ { AV_HWDEVICE_TYPE_DXVA2,
+ { "0", "1", "2" } },
+ { AV_HWDEVICE_TYPE_D3D11VA,
+ { "0", "1", "2" } },
+ { AV_HWDEVICE_TYPE_OPENCL,
+ { "0.0", "0.1", "1.0", "1.1" } },
+ { AV_HWDEVICE_TYPE_VAAPI,
+ { "/dev/dri/renderD128", "/dev/dri/renderD129", ":0" } },
+};
+
+static int test_device_type(enum AVHWDeviceType type)
+{
+ enum AVHWDeviceType check;
+ const char *name;
+ int i, j, found, err;
+
+ name = av_hwdevice_get_type_name(type);
+ if (!name) {
+ fprintf(stderr, "No name available for device type %d.\n", type);
+ return -1;
+ }
+
+ check = av_hwdevice_find_type_by_name(name);
+ if (check != type) {
+ fprintf(stderr, "Type %d maps to name %s maps to type %d.\n",
+ type, name, check);
+ return -1;
+ }
+
+ found = 0;
+
+ err = test_device(type, name, NULL, NULL, 0);
+ if (err < 0) {
+ fprintf(stderr, "Test failed for %s with default options.\n", name);
+ return -1;
+ }
+ if (err == 0) {
+ fprintf(stderr, "Test passed for %s with default options.\n", name);
+ ++found;
+ }
+
+ for (i = 0; i < FF_ARRAY_ELEMS(test_devices); i++) {
+ if (test_devices[i].type != type)
+ continue;
+
+ for (j = 0; test_devices[i].possible_devices[j]; j++) {
+ err = test_device(type, name,
+ test_devices[i].possible_devices[j],
+ NULL, 0);
+ if (err < 0) {
+ fprintf(stderr, "Test failed for %s with device %s.\n",
+ name, test_devices[i].possible_devices[j]);
+ return -1;
+ }
+ if (err == 0) {
+ fprintf(stderr, "Test passed for %s with device %s.\n",
+ name, test_devices[i].possible_devices[j]);
+ ++found;
+ }
+ }
+ }
+
+ return !found;
+}
+
+int main(void)
+{
+ enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
+ int pass, fail, skip, err;
+
+ pass = fail = skip = 0;
+ while (1) {
+ type = av_hwdevice_iterate_types(type);
+ if (type == AV_HWDEVICE_TYPE_NONE)
+ break;
+
+ err = test_device_type(type);
+ if (err == 0)
+ ++pass;
+ else if (err < 0)
+ ++fail;
+ else
+ ++skip;
+ }
+
+ fprintf(stderr, "Attempted to test %d device types: "
+ "%d passed, %d failed, %d skipped.\n",
+ pass + fail + skip, pass, fail, skip);
+
+ return fail > 0;
+}
diff --git a/tests/fate/hw.mak b/tests/fate/hw.mak
index d606cdeab6..a46b289b67 100644
--- a/tests/fate/hw.mak
+++ b/tests/fate/hw.mak
@@ -3,4 +3,9 @@ fate-hwdevice: libavutil/tests/hwdevice$(EXESUF)
fate-hwdevice: CMD = run libavutil/tests/hwdevice
fate-hwdevice: CMP = null
+FATE_HWCONTEXT += fate-hwframes
+fate-hwframes: libavutil/tests/hwframes$(EXESUF)
+fate-hwframes: CMD = run libavutil/tests/hwframes
+fate-hwframes: CMP = null
+
FATE_HW-$(CONFIG_AVUTIL) += $(FATE_HWCONTEXT)
--
2.16.3
More information about the ffmpeg-devel
mailing list