~emersion/vaapi-decoder

67ad63acddee50d89c013c6205456290edf23f91 — Simon Ser 9 months ago e328c2b wayland
Open a Wayland window
3 files changed, 191 insertions(+), 30 deletions(-)

M .gitignore
M Makefile
M main.c
M .gitignore => .gitignore +1 -0
@@ 1,1 1,2 @@
/vaapi-decoder
*-protocol.[ch]

M Makefile => Makefile +19 -3
@@ 1,12 1,28 @@
CFLAGS = -g -Wall -Wextra -Wno-unused-parameter
WAYLAND_PROTOCOLS_DIR = $(shell pkg-config wayland-protocols --variable=pkgdatadir)
WAYLAND_SCANNER = $(shell pkg-config --variable=wayland_scanner wayland-scanner)

deps = libavcodec libavformat libavutil libdrm
deps = libavcodec libavformat libavutil libdrm wayland-client
depflags = $(shell pkg-config $(deps) --cflags --libs)

xdg_shell_protocol = $(WAYLAND_PROTOCOLS_DIR)/stable/xdg-shell/xdg-shell.xml
linux_dmabuf_unstable_v1_protocol = $(WAYLAND_PROTOCOLS_DIR)/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml
protocol_files = xdg-shell-client-protocol.h xdg-shell-protocol.c \
	linux-dmabuf-unstable-v1-client-protocol.h linux-dmabuf-unstable-v1-protocol.c

all: vaapi-decoder

vaapi-decoder: main.c
vaapi-decoder: main.c $(protocol_files)
	$(CC) $(CFLAGS) $(depflags) -o $@ $^

xdg-shell-client-protocol.h: $(xdg_shell_protocol)
	$(WAYLAND_SCANNER) client-header $< $@
xdg-shell-protocol.c: $(xdg_shell_protocol)
	$(WAYLAND_SCANNER) private-code $< $@
linux-dmabuf-unstable-v1-client-protocol.h: $(linux_dmabuf_unstable_v1_protocol)
	$(WAYLAND_SCANNER) client-header $< $@
linux-dmabuf-unstable-v1-protocol.c: $(linux_dmabuf_unstable_v1_protocol)
	$(WAYLAND_SCANNER) private-code $< $@

clean:
	$(RM) vaapi-decoder
	$(RM) vaapi-decoder $(protocol_files)

M main.c => main.c +171 -27
@@ 8,9 8,22 @@
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_drm.h>
#include <libavutil/pixdesc.h>
#include <wayland-client.h>

#include "xdg-shell-client-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"

static AVFormatContext *input_ctx = NULL;
static int video_stream = -1;
static AVCodecContext *decoder_ctx = NULL;
static enum AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;

static struct wl_compositor *compositor = NULL;
static struct xdg_wm_base *xdg_wm_base = NULL;
static struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1 = NULL;

static struct wl_surface *surface = NULL;

static const struct {
	uint32_t format;
	int nb_layers;


@@ 120,7 133,7 @@ static void print_drm_frame_desc(const AVDRMFrameDescriptor *drm_frame_desc) {
	fprintf(stderr, "DRM frame:\n");
	for (int i = 0; i < drm_frame_desc->nb_objects; i++) {
		const AVDRMObjectDescriptor *drm_object = &drm_frame_desc->objects[i];
		fprintf(stderr, "  object #%i: fd = %d, size = %zu, "
		fprintf(stderr, "  objectdecoder_ctx, #%i: fd = %d, size = %zu, "
			"format_modifier = 0x%lX\n", i, drm_object->fd,
			drm_object->size, drm_object->format_modifier);
	}


@@ 137,6 150,137 @@ static void print_drm_frame_desc(const AVDRMFrameDescriptor *drm_frame_desc) {
	}
}

static void xdg_surface_handle_configure(void *data,
		struct xdg_surface *xdg_surface, uint32_t serial) {
	xdg_surface_ack_configure(xdg_surface, serial);
}

static const struct xdg_surface_listener xdg_surface_listener = {
	.configure = xdg_surface_handle_configure,
};

static void handle_global(void *data, struct wl_registry *registry,
		uint32_t name, const char *interface, uint32_t version) {
	if (strcmp(interface, wl_compositor_interface.name) == 0) {
		compositor =
			wl_registry_bind(registry, name, &wl_compositor_interface, 1);
	} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
		xdg_wm_base =
			wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
	} else if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) {
		zwp_linux_dmabuf_v1 =
			wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, 2);
	}
}

static void handle_global_remove(void *data, struct wl_registry *registry,
		uint32_t name) {
	// This space is intentionally left blank
}

static const struct wl_registry_listener registry_listener = {
	.global = handle_global,
	.global_remove = handle_global_remove,
};

static struct wl_buffer *import_drm_frame_desc(
		const AVDRMFrameDescriptor *drm_frame_desc, int width, int height) {
	// VA-API drivers may use separate layers with one plane each, or a single
	// layer with multiple planes. We need to handle both.
	uint32_t drm_format = get_drm_frame_format(drm_frame_desc);
	if (drm_format == DRM_FORMAT_INVALID) {
		fprintf(stderr, "Failed to get DRM frame format\n");
		return NULL;
	}
	fprintf(stderr, "DRM format: 0x%X\n", drm_format);

	struct zwp_linux_buffer_params_v1 *dmabuf_params =
		zwp_linux_dmabuf_v1_create_params(zwp_linux_dmabuf_v1);
	int k = 0;
	for (int i = 0; i < drm_frame_desc->nb_layers; i++) {
		const AVDRMLayerDescriptor *drm_layer = &drm_frame_desc->layers[i];

		for (int j = 0; j < drm_layer->nb_planes; j++) {
			const AVDRMPlaneDescriptor *drm_plane = &drm_layer->planes[j];
			const AVDRMObjectDescriptor *drm_object =
				&drm_frame_desc->objects[drm_plane->object_index];

			uint32_t modifier_hi = drm_object->format_modifier >> 32;
			uint32_t modifier_lo = drm_object->format_modifier & 0xFFFFFFFF;

			zwp_linux_buffer_params_v1_add(dmabuf_params, drm_object->fd, k,
				drm_plane->offset, drm_plane->pitch, modifier_hi, modifier_lo);
			k++;
		}
	}

	return zwp_linux_buffer_params_v1_create_immed(dmabuf_params,
		width, height, drm_format, 0);
}

static void handle_buffer_release(void *data, struct wl_buffer *buffer) {
	AVFrame *frame = data;
	av_frame_free(&frame);
	wl_buffer_destroy(buffer);
}

static const struct wl_buffer_listener buffer_listener = {
	.release = handle_buffer_release,
};

static void push_frame(void);

static void handle_frame_callback_done(void *data, struct wl_callback *callback,
		uint32_t time) {
	push_frame();
}

static const struct wl_callback_listener frame_callback_listener = {
	.done = handle_frame_callback_done,
};

static void push_frame(void) {
	AVFrame *frame = NULL;
	int ret = decode_frame(input_ctx, video_stream, decoder_ctx, &frame);
	if (ret == AVERROR_EOF) {
		exit(0);
	} else if (ret < 0) {
		exit(1);
	}

	fprintf(stderr, "Got %dx%d frame!\n", frame->width, frame->height);

	AVFrame *drm_frame = av_frame_alloc();
	drm_frame->format = AV_PIX_FMT_DRM_PRIME;
	drm_frame->hw_frames_ctx = av_buffer_ref(frame->hw_frames_ctx);

	// Convert the VA-API frame into a DMA-BUF frame
	ret = av_hwframe_map(drm_frame, frame, 0);
	if (ret < 0) {
		fprintf(stderr, "Failed to map frame: %s\n", av_err2str(ret));
		exit(1);
	}

	AVDRMFrameDescriptor *drm_frame_desc = (void *)drm_frame->data[0];
	print_drm_frame_desc(drm_frame_desc);

	struct wl_buffer *buffer = import_drm_frame_desc(drm_frame_desc,
		drm_frame->width, drm_frame->height);
	if (!buffer) {
		exit(1);
	}
	wl_buffer_add_listener(buffer, &buffer_listener, frame);

	struct wl_callback *callback = wl_surface_frame(surface);
	wl_callback_add_listener(callback, &frame_callback_listener, NULL);

	wl_surface_damage(surface, 0, 0, INT32_MAX, INT32_MAX);
	wl_surface_attach(surface, buffer, 0, 0);
	wl_surface_commit(surface);

	av_frame_free(&drm_frame);
}

int main(int argc, char *argv[]) {
	if (argc < 2) {
		fprintf(stderr, "usage: %s <file>\n", argv[0]);


@@ 150,7 294,6 @@ int main(int argc, char *argv[]) {
		return 1;
	}

	AVFormatContext *input_ctx = NULL;
	if (avformat_open_input(&input_ctx, argv[1], NULL, NULL) != 0) {
		fprintf(stderr, "Failed to open input file\n");
		return 1;


@@ 162,7 305,7 @@ int main(int argc, char *argv[]) {
	}

	AVCodec *decoder = NULL;
	int video_stream = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO,
	video_stream = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO,
		-1, -1, &decoder, 0);
	if (video_stream < 0) {
		fprintf(stderr, "Failed to find video stream in input file\n");


@@ 189,7 332,7 @@ int main(int argc, char *argv[]) {
	}
	fprintf(stderr, "Selected pixel format %s\n", av_get_pix_fmt_name(hw_pix_fmt));

	AVCodecContext *decoder_ctx = avcodec_alloc_context3(decoder);
	decoder_ctx = avcodec_alloc_context3(decoder);
	if (!decoder_ctx) {
		fprintf(stderr, "Failed to allocate decoder context\n");
		return 1;


@@ 219,39 362,40 @@ int main(int argc, char *argv[]) {
		return 1;
	}

	AVFrame *frame = NULL;
	ret = decode_frame(input_ctx, video_stream, decoder_ctx, &frame);
	if (ret < 0) {
	struct wl_display *display = wl_display_connect(NULL);
	if (!display) {
		fprintf(stderr, "Failed to connect to Wayland display\n");
		return 1;
	}

	fprintf(stderr, "Got %dx%d frame!\n", frame->width, frame->height);

	AVFrame *drm_frame = av_frame_alloc();
	drm_frame->format = AV_PIX_FMT_DRM_PRIME;
	drm_frame->hw_frames_ctx = av_buffer_ref(frame->hw_frames_ctx);
	struct wl_registry *registry = wl_display_get_registry(display);
	wl_registry_add_listener(registry, &registry_listener, NULL);
	wl_display_roundtrip(display);

	// Convert the VA-API frame into a DMA-BUF frame
	ret = av_hwframe_map(drm_frame, frame, 0);
	if (ret < 0) {
		fprintf(stderr, "Failed to map frame: %s\n", av_err2str(ret));
	if (compositor == NULL || xdg_wm_base == NULL || zwp_linux_dmabuf_v1 == NULL) {
		fprintf(stderr, "Missing wl_compositor, xdg_wm_base or zwp_linux_dmabuf_v1 support\n");
		return 1;
	}

	AVDRMFrameDescriptor *drm_frame_desc = (void *)drm_frame->data[0];
	print_drm_frame_desc(drm_frame_desc);
	surface = wl_compositor_create_surface(compositor);
	struct xdg_surface *xdg_surface =
		xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
	xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
	struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
	wl_surface_commit(surface);

	// VA-API drivers may use separate layers with one plane each, or a single
	// layer with multiple planes. We need to handle both.
	uint32_t drm_format = get_drm_frame_format(drm_frame_desc);
	if (drm_format == DRM_FORMAT_INVALID) {
		fprintf(stderr, "Failed to get DRM frame format\n");
		return 1;
	// TODO: wait for configure event instead
	wl_display_roundtrip(display);

	push_frame();

	while (wl_display_dispatch(display) != -1) {
		// This space intentionally left blank
	}
	fprintf(stderr, "DRM format: 0x%X\n", drm_format);

	av_frame_free(&drm_frame);
	av_frame_free(&frame);
	xdg_toplevel_destroy(xdg_toplevel);
	xdg_surface_destroy(xdg_surface);
	wl_surface_destroy(surface);

	avcodec_free_context(&decoder_ctx);
	avformat_close_input(&input_ctx);