Merge pull request #992 from emersion/screencontent
Implement wlr_export_dmabuf_unstable_v1 protocolmaster
commit
e459fe0ec7
@ -0,0 +1,743 @@
|
|||||||
|
#define _XOPEN_SOURCE 700
|
||||||
|
#define _POSIX_C_SOURCE 199309L
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavutil/display.h>
|
||||||
|
#include <libavutil/hwcontext_drm.h>
|
||||||
|
#include <libavutil/pixdesc.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <libdrm/drm_fourcc.h>
|
||||||
|
#include "wlr-export-dmabuf-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
struct wayland_output {
|
||||||
|
struct wl_list link;
|
||||||
|
uint32_t id;
|
||||||
|
struct wl_output *output;
|
||||||
|
char *make;
|
||||||
|
char *model;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
AVRational framerate;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct capture_context {
|
||||||
|
AVClass *class; /* For pretty logging */
|
||||||
|
struct wl_display *display;
|
||||||
|
struct wl_registry *registry;
|
||||||
|
struct zwlr_export_dmabuf_manager_v1 *export_manager;
|
||||||
|
|
||||||
|
struct wl_list output_list;
|
||||||
|
|
||||||
|
/* Target */
|
||||||
|
struct wl_output *target_output;
|
||||||
|
|
||||||
|
/* Main frame callback */
|
||||||
|
struct zwlr_export_dmabuf_frame_v1 *frame_callback;
|
||||||
|
|
||||||
|
/* If something happens during capture */
|
||||||
|
int err;
|
||||||
|
int quit;
|
||||||
|
|
||||||
|
/* FFmpeg specific parts */
|
||||||
|
AVFrame *current_frame;
|
||||||
|
AVBufferRef *drm_device_ref;
|
||||||
|
AVBufferRef *drm_frames_ref;
|
||||||
|
|
||||||
|
AVBufferRef *mapped_device_ref;
|
||||||
|
AVBufferRef *mapped_frames_ref;
|
||||||
|
|
||||||
|
AVFormatContext *avf;
|
||||||
|
AVCodecContext *avctx;
|
||||||
|
|
||||||
|
int64_t start_pts;
|
||||||
|
|
||||||
|
/* Config */
|
||||||
|
enum AVPixelFormat software_format;
|
||||||
|
enum AVHWDeviceType hw_device_type;
|
||||||
|
AVDictionary *encoder_opts;
|
||||||
|
int is_software_encoder;
|
||||||
|
char *hardware_device;
|
||||||
|
char *out_filename;
|
||||||
|
char *encoder_name;
|
||||||
|
float out_bitrate;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void output_handle_geometry(void *data, struct wl_output *wl_output,
|
||||||
|
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
|
||||||
|
int32_t subpixel, const char *make, const char *model,
|
||||||
|
int32_t transform) {
|
||||||
|
struct wayland_output *output = data;
|
||||||
|
output->make = av_strdup(make);
|
||||||
|
output->model = av_strdup(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_handle_mode(void *data, struct wl_output *wl_output,
|
||||||
|
uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
||||||
|
if (flags & WL_OUTPUT_MODE_CURRENT) {
|
||||||
|
struct wayland_output *output = data;
|
||||||
|
output->width = width;
|
||||||
|
output->height = height;
|
||||||
|
output->framerate = (AVRational){ refresh, 1000 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_handle_done(void* data, struct wl_output *wl_output) {
|
||||||
|
/* Nothing to do */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_handle_scale(void* data, struct wl_output *wl_output,
|
||||||
|
int32_t factor) {
|
||||||
|
/* Nothing to do */
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_output_listener output_listener = {
|
||||||
|
.geometry = output_handle_geometry,
|
||||||
|
.mode = output_handle_mode,
|
||||||
|
.done = output_handle_done,
|
||||||
|
.scale = output_handle_scale,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void registry_handle_add(void *data, struct wl_registry *reg,
|
||||||
|
uint32_t id, const char *interface, uint32_t ver) {
|
||||||
|
struct capture_context *ctx = data;
|
||||||
|
|
||||||
|
if (!strcmp(interface, wl_output_interface.name)) {
|
||||||
|
struct wayland_output *output = av_mallocz(sizeof(*output));
|
||||||
|
|
||||||
|
output->id = id;
|
||||||
|
output->output = wl_registry_bind(reg, id, &wl_output_interface, 1);
|
||||||
|
|
||||||
|
wl_output_add_listener(output->output, &output_listener, output);
|
||||||
|
wl_list_insert(&ctx->output_list, &output->link);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
|
||||||
|
ctx->export_manager = wl_registry_bind(reg, id,
|
||||||
|
&zwlr_export_dmabuf_manager_v1_interface, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_output(struct wayland_output *out) {
|
||||||
|
wl_list_remove(&out->link);
|
||||||
|
av_free(out->make);
|
||||||
|
av_free(out->model);
|
||||||
|
av_free(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct wayland_output *find_output(struct capture_context *ctx,
|
||||||
|
struct wl_output *out, uint32_t id) {
|
||||||
|
struct wayland_output *output, *tmp;
|
||||||
|
wl_list_for_each_safe(output, tmp, &ctx->output_list, link) {
|
||||||
|
if ((output->output == out) || (output->id == id)) {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registry_handle_remove(void *data, struct wl_registry *reg,
|
||||||
|
uint32_t id) {
|
||||||
|
remove_output(find_output((struct capture_context *)data, NULL, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wl_registry_listener registry_listener = {
|
||||||
|
.global = registry_handle_add,
|
||||||
|
.global_remove = registry_handle_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void frame_free(void *opaque, uint8_t *data) {
|
||||||
|
AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)data;
|
||||||
|
|
||||||
|
for (int i = 0; i < desc->nb_objects; ++i) {
|
||||||
|
close(desc->objects[i].fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
zwlr_export_dmabuf_frame_v1_destroy(opaque);
|
||||||
|
|
||||||
|
av_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
|
||||||
|
uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y,
|
||||||
|
uint32_t buffer_flags, uint32_t flags, uint32_t format,
|
||||||
|
uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) {
|
||||||
|
struct capture_context *ctx = data;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
/* Allocate DRM specific struct */
|
||||||
|
AVDRMFrameDescriptor *desc = av_mallocz(sizeof(*desc));
|
||||||
|
if (!desc) {
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
desc->nb_objects = num_objects;
|
||||||
|
desc->objects[0].format_modifier = ((uint64_t)mod_high << 32) | mod_low;
|
||||||
|
|
||||||
|
desc->nb_layers = 1;
|
||||||
|
desc->layers[0].format = format;
|
||||||
|
|
||||||
|
/* Allocate a frame */
|
||||||
|
AVFrame *f = av_frame_alloc();
|
||||||
|
if (!f) {
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set base frame properties */
|
||||||
|
ctx->current_frame = f;
|
||||||
|
f->width = width;
|
||||||
|
f->height = height;
|
||||||
|
f->format = AV_PIX_FMT_DRM_PRIME;
|
||||||
|
|
||||||
|
/* Set the frame data to the DRM specific struct */
|
||||||
|
f->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc),
|
||||||
|
&frame_free, frame, 0);
|
||||||
|
if (!f->buf[0]) {
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
f->data[0] = (uint8_t*)desc;
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
ctx->err = err;
|
||||||
|
frame_free(frame, (uint8_t *)desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
|
||||||
|
uint32_t index, int32_t fd, uint32_t size, uint32_t offset,
|
||||||
|
uint32_t stride, uint32_t plane_index) {
|
||||||
|
struct capture_context *ctx = data;
|
||||||
|
AVFrame *f = ctx->current_frame;
|
||||||
|
AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0];
|
||||||
|
|
||||||
|
desc->objects[index].fd = fd;
|
||||||
|
desc->objects[index].size = size;
|
||||||
|
|
||||||
|
desc->layers[0].planes[plane_index].object_index = index;
|
||||||
|
desc->layers[0].planes[plane_index].offset = offset;
|
||||||
|
desc->layers[0].planes[plane_index].pitch = stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum AVPixelFormat drm_fmt_to_pixfmt(uint32_t fmt) {
|
||||||
|
switch (fmt) {
|
||||||
|
case DRM_FORMAT_NV12: return AV_PIX_FMT_NV12;
|
||||||
|
case DRM_FORMAT_ARGB8888: return AV_PIX_FMT_BGRA;
|
||||||
|
case DRM_FORMAT_XRGB8888: return AV_PIX_FMT_BGR0;
|
||||||
|
case DRM_FORMAT_ABGR8888: return AV_PIX_FMT_RGBA;
|
||||||
|
case DRM_FORMAT_XBGR8888: return AV_PIX_FMT_RGB0;
|
||||||
|
case DRM_FORMAT_RGBA8888: return AV_PIX_FMT_ABGR;
|
||||||
|
case DRM_FORMAT_RGBX8888: return AV_PIX_FMT_0BGR;
|
||||||
|
case DRM_FORMAT_BGRA8888: return AV_PIX_FMT_ARGB;
|
||||||
|
case DRM_FORMAT_BGRX8888: return AV_PIX_FMT_0RGB;
|
||||||
|
default: return AV_PIX_FMT_NONE;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static int attach_drm_frames_ref(struct capture_context *ctx, AVFrame *f,
|
||||||
|
enum AVPixelFormat sw_format) {
|
||||||
|
int err = 0;
|
||||||
|
AVHWFramesContext *hwfc;
|
||||||
|
|
||||||
|
if (ctx->drm_frames_ref) {
|
||||||
|
hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data;
|
||||||
|
if (hwfc->width == f->width && hwfc->height == f->height &&
|
||||||
|
hwfc->sw_format == sw_format) {
|
||||||
|
goto attach;
|
||||||
|
}
|
||||||
|
av_buffer_unref(&ctx->drm_frames_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->drm_frames_ref = av_hwframe_ctx_alloc(ctx->drm_device_ref);
|
||||||
|
if (!ctx->drm_frames_ref) {
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data;
|
||||||
|
|
||||||
|
hwfc->format = f->format;
|
||||||
|
hwfc->sw_format = sw_format;
|
||||||
|
hwfc->width = f->width;
|
||||||
|
hwfc->height = f->height;
|
||||||
|
|
||||||
|
err = av_hwframe_ctx_init(ctx->drm_frames_ref);
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "AVHWFramesContext init failed: %s!\n",
|
||||||
|
av_err2str(err));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
attach:
|
||||||
|
/* Set frame hardware context referencce */
|
||||||
|
f->hw_frames_ctx = av_buffer_ref(ctx->drm_frames_ref);
|
||||||
|
if (!f->hw_frames_ctx) {
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
av_buffer_unref(&ctx->drm_frames_ref);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register_cb(struct capture_context *ctx);
|
||||||
|
|
||||||
|
static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
|
||||||
|
uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
|
||||||
|
struct capture_context *ctx = data;
|
||||||
|
AVFrame *f = ctx->current_frame;
|
||||||
|
AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0];
|
||||||
|
enum AVPixelFormat pix_fmt = drm_fmt_to_pixfmt(desc->layers[0].format);
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
/* Attach the hardware frame context to the frame */
|
||||||
|
err = attach_drm_frames_ref(ctx, f, pix_fmt);
|
||||||
|
if (err) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: support multiplane stuff */
|
||||||
|
desc->layers[0].nb_planes = av_pix_fmt_count_planes(pix_fmt);
|
||||||
|
|
||||||
|
AVFrame *mapped_frame = av_frame_alloc();
|
||||||
|
if (!mapped_frame) {
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVHWFramesContext *mapped_hwfc;
|
||||||
|
mapped_hwfc = (AVHWFramesContext *)ctx->mapped_frames_ref->data;
|
||||||
|
mapped_frame->format = mapped_hwfc->format;
|
||||||
|
|
||||||
|
/* Set frame hardware context referencce */
|
||||||
|
mapped_frame->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref);
|
||||||
|
if (!mapped_frame->hw_frames_ctx) {
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = av_hwframe_map(mapped_frame, f, 0);
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Error mapping: %s!\n", av_err2str(err));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVFrame *enc_input = mapped_frame;
|
||||||
|
|
||||||
|
if (ctx->is_software_encoder) {
|
||||||
|
AVFrame *soft_frame = av_frame_alloc();
|
||||||
|
av_hwframe_transfer_data(soft_frame, mapped_frame, 0);
|
||||||
|
av_frame_free(&mapped_frame);
|
||||||
|
enc_input = soft_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nanoseconds */
|
||||||
|
enc_input->pts = (((uint64_t)tv_sec_hi) << 32) | tv_sec_lo;
|
||||||
|
enc_input->pts *= 1000000000;
|
||||||
|
enc_input->pts += tv_nsec;
|
||||||
|
|
||||||
|
if (!ctx->start_pts) {
|
||||||
|
ctx->start_pts = enc_input->pts;
|
||||||
|
}
|
||||||
|
|
||||||
|
enc_input->pts -= ctx->start_pts;
|
||||||
|
|
||||||
|
enc_input->pts = av_rescale_q(enc_input->pts, (AVRational){ 1, 1000000000 },
|
||||||
|
ctx->avctx->time_base);
|
||||||
|
|
||||||
|
do {
|
||||||
|
err = avcodec_send_frame(ctx->avctx, enc_input);
|
||||||
|
|
||||||
|
av_frame_free(&enc_input);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n", av_err2str(err));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
AVPacket pkt;
|
||||||
|
av_init_packet(&pkt);
|
||||||
|
|
||||||
|
int ret = avcodec_receive_packet(ctx->avctx, &pkt);
|
||||||
|
if (ret == AVERROR(EAGAIN)) {
|
||||||
|
break;
|
||||||
|
} else if (ret == AVERROR_EOF) {
|
||||||
|
av_log(ctx, AV_LOG_INFO, "Encoder flushed!\n");
|
||||||
|
ctx->quit = 2;
|
||||||
|
goto end;
|
||||||
|
} else if (ret) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n",
|
||||||
|
av_err2str(ret));
|
||||||
|
err = ret;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt.stream_index = 0;
|
||||||
|
err = av_interleaved_write_frame(ctx->avf, &pkt);
|
||||||
|
|
||||||
|
av_packet_unref(&pkt);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Writing packet fail: %s!\n",
|
||||||
|
av_err2str(err));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} while (ctx->quit);
|
||||||
|
|
||||||
|
av_log(NULL, AV_LOG_INFO, "Encoded frame %i!\n", ctx->avctx->frame_number);
|
||||||
|
|
||||||
|
register_cb(ctx);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ctx->err = err;
|
||||||
|
av_frame_free(&ctx->current_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
|
||||||
|
uint32_t reason) {
|
||||||
|
struct capture_context *ctx = data;
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Frame cancelled!\n");
|
||||||
|
av_frame_free(&ctx->current_frame);
|
||||||
|
if (reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Permanent failure, exiting\n");
|
||||||
|
ctx->err = 1;
|
||||||
|
} else {
|
||||||
|
register_cb(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = {
|
||||||
|
.frame = frame_start,
|
||||||
|
.object = frame_object,
|
||||||
|
.ready = frame_ready,
|
||||||
|
.cancel = frame_cancel,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void register_cb(struct capture_context *ctx) {
|
||||||
|
ctx->frame_callback = zwlr_export_dmabuf_manager_v1_capture_output(
|
||||||
|
ctx->export_manager, 0, ctx->target_output);
|
||||||
|
|
||||||
|
zwlr_export_dmabuf_frame_v1_add_listener(ctx->frame_callback,
|
||||||
|
&frame_listener, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init_lavu_hwcontext(struct capture_context *ctx) {
|
||||||
|
/* DRM hwcontext */
|
||||||
|
ctx->drm_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DRM);
|
||||||
|
if (!ctx->drm_device_ref)
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
|
||||||
|
AVHWDeviceContext *ref_data = (AVHWDeviceContext*)ctx->drm_device_ref->data;
|
||||||
|
AVDRMDeviceContext *hwctx = ref_data->hwctx;
|
||||||
|
|
||||||
|
/* We don't need a device (we don't even know it and can't open it) */
|
||||||
|
hwctx->fd = -1;
|
||||||
|
|
||||||
|
av_hwdevice_ctx_init(ctx->drm_device_ref);
|
||||||
|
|
||||||
|
/* Mapped hwcontext */
|
||||||
|
int err = av_hwdevice_ctx_create(&ctx->mapped_device_ref,
|
||||||
|
ctx->hw_device_type, ctx->hardware_device, NULL, 0);
|
||||||
|
if (err < 0) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Failed to create a hardware device: %s\n",
|
||||||
|
av_err2str(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int set_hwframe_ctx(struct capture_context *ctx,
|
||||||
|
AVBufferRef *hw_device_ctx) {
|
||||||
|
AVHWFramesContext *frames_ctx = NULL;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (!(ctx->mapped_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) {
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
AVHWFramesConstraints *cst =
|
||||||
|
av_hwdevice_get_hwframe_constraints(ctx->mapped_device_ref, NULL);
|
||||||
|
if (!cst) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Failed to get hw device constraints!\n");
|
||||||
|
av_buffer_unref(&ctx->mapped_frames_ref);
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
frames_ctx = (AVHWFramesContext *)(ctx->mapped_frames_ref->data);
|
||||||
|
frames_ctx->format = cst->valid_hw_formats[0];
|
||||||
|
frames_ctx->sw_format = ctx->avctx->pix_fmt;
|
||||||
|
frames_ctx->width = ctx->avctx->width;
|
||||||
|
frames_ctx->height = ctx->avctx->height;
|
||||||
|
|
||||||
|
av_hwframe_constraints_free(&cst);
|
||||||
|
|
||||||
|
if ((err = av_hwframe_ctx_init(ctx->mapped_frames_ref))) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Failed to initialize hw frame context: %s!\n",
|
||||||
|
av_err2str(err));
|
||||||
|
av_buffer_unref(&ctx->mapped_frames_ref);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx->is_software_encoder) {
|
||||||
|
ctx->avctx->pix_fmt = frames_ctx->format;
|
||||||
|
ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref);
|
||||||
|
if (!ctx->avctx->hw_frames_ctx) {
|
||||||
|
av_buffer_unref(&ctx->mapped_frames_ref);
|
||||||
|
err = AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init_encoding(struct capture_context *ctx) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* lavf init */
|
||||||
|
err = avformat_alloc_output_context2(&ctx->avf, NULL,
|
||||||
|
NULL, ctx->out_filename);
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Unable to init lavf context!\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVStream *st = avformat_new_stream(ctx->avf, NULL);
|
||||||
|
if (!st) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Unable to alloc stream!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find encoder */
|
||||||
|
AVCodec *out_codec = avcodec_find_encoder_by_name(ctx->encoder_name);
|
||||||
|
if (!out_codec) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Codec not found (not compiled in lavc?)!\n");
|
||||||
|
return AVERROR(EINVAL);
|
||||||
|
}
|
||||||
|
ctx->avf->oformat->video_codec = out_codec->id;
|
||||||
|
ctx->is_software_encoder = !(out_codec->capabilities & AV_CODEC_CAP_HARDWARE);
|
||||||
|
|
||||||
|
ctx->avctx = avcodec_alloc_context3(out_codec);
|
||||||
|
if (!ctx->avctx)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
ctx->avctx->opaque = ctx;
|
||||||
|
ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f;
|
||||||
|
ctx->avctx->pix_fmt = ctx->software_format;
|
||||||
|
ctx->avctx->time_base = (AVRational){ 1, 1000 };
|
||||||
|
ctx->avctx->compression_level = 7;
|
||||||
|
ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width;
|
||||||
|
ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height;
|
||||||
|
|
||||||
|
if (ctx->avf->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||||
|
ctx->avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
st->id = 0;
|
||||||
|
st->time_base = ctx->avctx->time_base;
|
||||||
|
st->avg_frame_rate = find_output(ctx, ctx->target_output, 0)->framerate;
|
||||||
|
|
||||||
|
/* Init hw frames context */
|
||||||
|
err = set_hwframe_ctx(ctx, ctx->mapped_device_ref);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = avcodec_open2(ctx->avctx, out_codec, &ctx->encoder_opts);
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Cannot open encoder: %s!\n",
|
||||||
|
av_err2str(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avcodec_parameters_from_context(st->codecpar, ctx->avctx) < 0) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Couldn't copy codec params: %s!\n",
|
||||||
|
av_err2str(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug print */
|
||||||
|
av_dump_format(ctx->avf, 0, ctx->out_filename, 1);
|
||||||
|
|
||||||
|
/* Open for writing */
|
||||||
|
err = avio_open(&ctx->avf->pb, ctx->out_filename, AVIO_FLAG_WRITE);
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Couldn't open %s: %s!\n", ctx->out_filename,
|
||||||
|
av_err2str(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = avformat_write_header(ctx->avf, NULL);
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Couldn't write header: %s!\n", av_err2str(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct capture_context *q_ctx = NULL;
|
||||||
|
|
||||||
|
void on_quit_signal(int signo) {
|
||||||
|
printf("\r");
|
||||||
|
q_ctx->quit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int main_loop(struct capture_context *ctx) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
q_ctx = ctx;
|
||||||
|
|
||||||
|
if (signal(SIGINT, on_quit_signal) == SIG_ERR) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Unable to install signal handler!\n");
|
||||||
|
return AVERROR(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = init_lavu_hwcontext(ctx);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = init_encoding(ctx);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start the frame callback */
|
||||||
|
register_cb(ctx);
|
||||||
|
|
||||||
|
while (wl_display_dispatch(ctx->display) != -1 && !ctx->err &&
|
||||||
|
ctx->quit < 2) {
|
||||||
|
// This space intentionally left blank
|
||||||
|
}
|
||||||
|
|
||||||
|
err = av_write_trailer(ctx->avf);
|
||||||
|
if (err) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Error writing trailer: %s!\n",
|
||||||
|
av_err2str(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
av_log(ctx, AV_LOG_INFO, "Wrote trailer!\n");
|
||||||
|
|
||||||
|
return ctx->err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init(struct capture_context *ctx) {
|
||||||
|
ctx->display = wl_display_connect(NULL);
|
||||||
|
if (!ctx->display) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Failed to connect to display!\n");
|
||||||
|
return AVERROR(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_list_init(&ctx->output_list);
|
||||||
|
|
||||||
|
ctx->registry = wl_display_get_registry(ctx->display);
|
||||||
|
wl_registry_add_listener(ctx->registry, ®istry_listener, ctx);
|
||||||
|
|
||||||
|
wl_display_roundtrip(ctx->display);
|
||||||
|
wl_display_dispatch(ctx->display);
|
||||||
|
|
||||||
|
if (!ctx->export_manager) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Compositor doesn't support %s!\n",
|
||||||
|
zwlr_export_dmabuf_manager_v1_interface.name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void uninit(struct capture_context *ctx);
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
int err;
|
||||||
|
struct capture_context ctx = { 0 };
|
||||||
|
ctx.class = &((AVClass) {
|
||||||
|
.class_name = "dmabuf-capture",
|
||||||
|
.item_name = av_default_item_name,
|
||||||
|
.version = LIBAVUTIL_VERSION_INT,
|
||||||
|
});
|
||||||
|
|
||||||
|
err = init(&ctx);
|
||||||
|
if (err) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wayland_output *o, *tmp_o;
|
||||||
|
wl_list_for_each_reverse_safe(o, tmp_o, &ctx.output_list, link) {
|
||||||
|
printf("Capturable output: %s Model: %s: ID: %i\n",
|
||||||
|
o->make, o->model, o->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc != 8) {
|
||||||
|
printf("Invalid number of arguments! Usage and example:\n"
|
||||||
|
"./dmabuf-capture <source id> <hardware device type> <device> "
|
||||||
|
"<encoder name> <pixel format> <bitrate in Mbps> <file path>\n"
|
||||||
|
"./dmabuf-capture 0 vaapi /dev/dri/renderD129 libx264 nv12 12 "
|
||||||
|
"dmabuf_recording_01.mkv\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int o_id = strtol(argv[1], NULL, 10);
|
||||||
|
o = find_output(&ctx, NULL, o_id);
|
||||||
|
if (!o) {
|
||||||
|
printf("Unable to find output with ID %i!\n", o_id);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.target_output = o->output;
|
||||||
|
ctx.hw_device_type = av_hwdevice_find_type_by_name(argv[2]);
|
||||||
|
ctx.hardware_device = argv[3];
|
||||||
|
|
||||||
|
ctx.encoder_name = argv[4];
|
||||||
|
ctx.software_format = av_get_pix_fmt(argv[5]);
|
||||||
|
ctx.out_bitrate = strtof(argv[6], NULL);
|
||||||
|
ctx.out_filename = argv[7];
|
||||||
|
|
||||||
|
av_dict_set(&ctx.encoder_opts, "preset", "veryfast", 0);
|
||||||
|
|
||||||
|
err = main_loop(&ctx);
|
||||||
|
if (err) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
uninit(&ctx);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void uninit(struct capture_context *ctx) {
|
||||||
|
struct wayland_output *output, *tmp_o;
|
||||||
|
wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) {
|
||||||
|
remove_output(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->export_manager) {
|
||||||
|
zwlr_export_dmabuf_manager_v1_destroy(ctx->export_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
av_buffer_unref(&ctx->drm_frames_ref);
|
||||||
|
av_buffer_unref(&ctx->drm_device_ref);
|
||||||
|
av_buffer_unref(&ctx->mapped_frames_ref);
|
||||||
|
av_buffer_unref(&ctx->mapped_device_ref);
|
||||||
|
|
||||||
|
av_dict_free(&ctx->encoder_opts);
|
||||||
|
|
||||||
|
avcodec_close(ctx->avctx);
|
||||||
|
if (ctx->avf) {
|
||||||
|
avio_closep(&ctx->avf->pb);
|
||||||
|
}
|
||||||
|
avformat_free_context(ctx->avf);
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
#ifndef WLR_TYPES_WLR_EXPORT_DMABUF_V1_H
|
||||||
|
#define WLR_TYPES_WLR_EXPORT_DMABUF_V1_H
|
||||||
|
|
||||||
|
#include <wayland-server.h>
|
||||||
|
#include <wlr/render/dmabuf.h>
|
||||||
|
|
||||||
|
struct wlr_export_dmabuf_manager_v1;
|
||||||
|
|
||||||
|
struct wlr_export_dmabuf_frame_v1 {
|
||||||
|
struct wl_resource *resource;
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *manager;
|
||||||
|
struct wl_list link;
|
||||||
|
|
||||||
|
struct wlr_dmabuf_attributes attribs;
|
||||||
|
struct wlr_output *output;
|
||||||
|
|
||||||
|
struct wl_listener output_swap_buffers;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wlr_export_dmabuf_manager_v1 {
|
||||||
|
struct wl_global *global;
|
||||||
|
struct wl_list resources;
|
||||||
|
struct wl_list frames;
|
||||||
|
|
||||||
|
struct wl_listener display_destroy;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct wl_signal destroy;
|
||||||
|
} events;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create(
|
||||||
|
struct wl_display *display);
|
||||||
|
void wlr_export_dmabuf_manager_v1_destroy(
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *manager);
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,203 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_export_dmabuf_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2018 Rostislav Pehlivanov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<description summary="a protocol for low overhead screen content capturing">
|
||||||
|
An interface to capture surfaces in an efficient way by exporting DMA-BUFs.
|
||||||
|
|
||||||
|
Warning! The protocol described in this file is experimental and
|
||||||
|
backward incompatible changes may be made. Backward compatible changes
|
||||||
|
may be added together with the corresponding interface version bump.
|
||||||
|
Backward incompatible changes are done by bumping the version number in
|
||||||
|
the protocol and interface names and resetting the interface version.
|
||||||
|
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||||
|
version number in the protocol and interface names are removed and the
|
||||||
|
interface version number is reset.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="zwlr_export_dmabuf_manager_v1" version="1">
|
||||||
|
<description summary="manager to inform clients and begin capturing">
|
||||||
|
This object is a manager with which to start capturing from sources.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="capture_output">
|
||||||
|
<description summary="capture a frame from an output">
|
||||||
|
Capture the next frame of a an entire output.
|
||||||
|
</description>
|
||||||
|
<arg name="frame" type="new_id" interface="zwlr_export_dmabuf_frame_v1"/>
|
||||||
|
<arg name="overlay_cursor" type="int"
|
||||||
|
summary="include custom client hardware cursor on top of the frame"/>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the manager">
|
||||||
|
All objects created by the manager will still remain valid, until their
|
||||||
|
appropriate destroy request has been called.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_export_dmabuf_frame_v1" version="1">
|
||||||
|
<description summary="a DMA-BUF frame">
|
||||||
|
This object represents a single DMA-BUF frame.
|
||||||
|
|
||||||
|
If the capture is successful, the compositor will first send a "frame"
|
||||||
|
event, followed by one or several "object". When the frame is available
|
||||||
|
for readout, the "ready" event is sent.
|
||||||
|
|
||||||
|
If the capture failed, the "cancel" event is sent. This can happen anytime
|
||||||
|
before the "ready" event.
|
||||||
|
|
||||||
|
Once either a "ready" or a "cancel" event is received, the client should
|
||||||
|
destroy the frame. Once an "object" event is received, the client is
|
||||||
|
responsible for closing the associated file descriptor.
|
||||||
|
|
||||||
|
All frames are read-only and may not be written into or altered.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="flags">
|
||||||
|
<description summary="frame flags">
|
||||||
|
Special flags that should be respected by the client.
|
||||||
|
</description>
|
||||||
|
<entry name="transient" value="0x1"
|
||||||
|
summary="clients should copy frame before processing"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="frame">
|
||||||
|
<description summary="a frame description">
|
||||||
|
Main event supplying the client with information about the frame. If the
|
||||||
|
capture didn't fail, this event is always emitted first before any other
|
||||||
|
events.
|
||||||
|
|
||||||
|
This event is followed by a number of "object" as specified by the
|
||||||
|
"num_objects" argument.
|
||||||
|
</description>
|
||||||
|
<arg name="width" type="uint"
|
||||||
|
summary="frame width in pixels"/>
|
||||||
|
<arg name="height" type="uint"
|
||||||
|
summary="frame height in pixels"/>
|
||||||
|
<arg name="offset_x" type="uint"
|
||||||
|
summary="crop offset for the x axis"/>
|
||||||
|
<arg name="offset_y" type="uint"
|
||||||
|
summary="crop offset for the y axis"/>
|
||||||
|
<arg name="buffer_flags" type="uint"
|
||||||
|
summary="flags which indicate properties (invert, interlacing),
|
||||||
|
has the same values as zwp_linux_buffer_params_v1:flags"/>
|
||||||
|
<arg name="flags" type="uint" enum="flags"
|
||||||
|
summary="indicates special frame features"/>
|
||||||
|
<arg name="format" type="uint"
|
||||||
|
summary="format of the frame (DRM_FORMAT_*)"/>
|
||||||
|
<arg name="mod_high" type="uint"
|
||||||
|
summary="drm format modifier, high"/>
|
||||||
|
<arg name="mod_low" type="uint"
|
||||||
|
summary="drm format modifier, low"/>
|
||||||
|
<arg name="num_objects" type="uint"
|
||||||
|
summary="indicates how many objects (FDs) the frame has (max 4)"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="object">
|
||||||
|
<description summary="an object description">
|
||||||
|
Event which serves to supply the client with the file descriptors
|
||||||
|
containing the data for each object.
|
||||||
|
|
||||||
|
After receiving this event, the client must always close the file
|
||||||
|
descriptor as soon as they're done with it and even if the frame fails.
|
||||||
|
</description>
|
||||||
|
<arg name="index" type="uint"
|
||||||
|
summary="index of the current object"/>
|
||||||
|
<arg name="fd" type="fd"
|
||||||
|
summary="fd of the current object"/>
|
||||||
|
<arg name="size" type="uint"
|
||||||
|
summary="size in bytes for the current object"/>
|
||||||
|
<arg name="offset" type="uint"
|
||||||
|
summary="starting point for the data in the object's fd"/>
|
||||||
|
<arg name="stride" type="uint"
|
||||||
|
summary="line size in bytes"/>
|
||||||
|
<arg name="plane_index" type="uint"
|
||||||
|
summary="index of the the plane the data in the object applies to"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="ready">
|
||||||
|
<description summary="indicates frame is available for reading">
|
||||||
|
This event is sent as soon as the frame is presented, indicating it is
|
||||||
|
available for reading. This event includes the time at which
|
||||||
|
presentation happened at.
|
||||||
|
|
||||||
|
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
|
||||||
|
each component being an unsigned 32-bit value. Whole seconds are in
|
||||||
|
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
|
||||||
|
and the additional fractional part in tv_nsec as nanoseconds. Hence,
|
||||||
|
for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
|
||||||
|
may have an arbitrary offset at start.
|
||||||
|
|
||||||
|
After receiving this event, the client should destroy this object.
|
||||||
|
</description>
|
||||||
|
<arg name="tv_sec_hi" type="uint"
|
||||||
|
summary="high 32 bits of the seconds part of the timestamp"/>
|
||||||
|
<arg name="tv_sec_lo" type="uint"
|
||||||
|
summary="low 32 bits of the seconds part of the timestamp"/>
|
||||||
|
<arg name="tv_nsec" type="uint"
|
||||||
|
summary="nanoseconds part of the timestamp"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<enum name="cancel_reason">
|
||||||
|
<description summary="cancel reason">
|
||||||
|
Indicates reason for cancelling the frame.
|
||||||
|
</description>
|
||||||
|
<entry name="temporary" value="0"
|
||||||
|
summary="temporary error, source will produce more frames"/>
|
||||||
|
<entry name="permanent" value="1"
|
||||||
|
summary="fatal error, source will not produce frames"/>
|
||||||
|
<entry name="resizing" value="2"
|
||||||
|
summary="temporary error, source will produce more frames"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="cancel">
|
||||||
|
<description summary="indicates the frame is no longer valid">
|
||||||
|
If the capture failed or if the frame is no longer valid after the
|
||||||
|
"frame" event has been emitted, this event will be used to inform the
|
||||||
|
client to scrap the frame.
|
||||||
|
|
||||||
|
If the failure is temporary, the client may capture again the same
|
||||||
|
source. If the failure is permanent, any further attempts to capture the
|
||||||
|
same source will fail again.
|
||||||
|
|
||||||
|
After receiving this event, the client should destroy this object.
|
||||||
|
</description>
|
||||||
|
<arg name="reason" type="uint" enum="cancel_reason"
|
||||||
|
summary="indicates a reason for cancelling this frame capture"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="delete this object, used or not">
|
||||||
|
Unreferences the frame. This request must be called as soon as its no
|
||||||
|
longer used.
|
||||||
|
|
||||||
|
It can be called at any time by the client. The client will still have
|
||||||
|
to close any FDs it has been given.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
@ -0,0 +1,10 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <wlr/render/dmabuf.h>
|
||||||
|
|
||||||
|
void wlr_dmabuf_attributes_finish( struct wlr_dmabuf_attributes *attribs) {
|
||||||
|
for (int i = 0; i < attribs->n_planes; ++i) {
|
||||||
|
close(attribs->fd[i]);
|
||||||
|
attribs->fd[i] = -1;
|
||||||
|
}
|
||||||
|
attribs->n_planes = 0;
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <wlr/interfaces/wlr_output.h>
|
||||||
|
#include <wlr/types/wlr_export_dmabuf_v1.h>
|
||||||
|
#include <wlr/types/wlr_linux_dmabuf.h>
|
||||||
|
#include <wlr/types/wlr_output.h>
|
||||||
|
#include "wlr-export-dmabuf-unstable-v1-protocol.h"
|
||||||
|
#include <wlr/util/log.h>
|
||||||
|
|
||||||
|
#define EXPORT_DMABUF_MANAGER_VERSION 1
|
||||||
|
|
||||||
|
|
||||||
|
static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl;
|
||||||
|
|
||||||
|
static struct wlr_export_dmabuf_frame_v1 *frame_from_resource(
|
||||||
|
struct wl_resource *resource) {
|
||||||
|
assert(wl_resource_instance_of(resource,
|
||||||
|
&zwlr_export_dmabuf_frame_v1_interface, &frame_impl));
|
||||||
|
return wl_resource_get_user_data(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void frame_handle_destroy(struct wl_client *client,
|
||||||
|
struct wl_resource *resource) {
|
||||||
|
wl_resource_destroy(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl = {
|
||||||
|
.destroy = frame_handle_destroy,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void frame_handle_resource_destroy(struct wl_resource *resource) {
|
||||||
|
struct wlr_export_dmabuf_frame_v1 *frame = frame_from_resource(resource);
|
||||||
|
wl_list_remove(&frame->link);
|
||||||
|
wl_list_remove(&frame->output_swap_buffers.link);
|
||||||
|
wlr_dmabuf_attributes_finish(&frame->attribs);
|
||||||
|
free(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void frame_output_handle_swap_buffers(struct wl_listener *listener,
|
||||||
|
void *data) {
|
||||||
|
struct wlr_export_dmabuf_frame_v1 *frame =
|
||||||
|
wl_container_of(listener, frame, output_swap_buffers);
|
||||||
|
struct wlr_output_event_swap_buffers *event = data;
|
||||||
|
|
||||||
|
wl_list_remove(&frame->output_swap_buffers.link);
|
||||||
|
wl_list_init(&frame->output_swap_buffers.link);
|
||||||
|
|
||||||
|
uint32_t tv_sec_hi = event->when->tv_sec >> 32;
|
||||||
|
uint32_t tv_sec_lo = event->when->tv_sec & 0xFFFFFFFF;
|
||||||
|
zwlr_export_dmabuf_frame_v1_send_ready(frame->resource,
|
||||||
|
tv_sec_hi, tv_sec_lo, event->when->tv_nsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl;
|
||||||
|
|
||||||
|
static struct wlr_export_dmabuf_manager_v1 *manager_from_resource(
|
||||||
|
struct wl_resource *resource) {
|
||||||
|
assert(wl_resource_instance_of(resource,
|
||||||
|
&zwlr_export_dmabuf_manager_v1_interface, &manager_impl));
|
||||||
|
return wl_resource_get_user_data(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void manager_handle_capture_output(struct wl_client *client,
|
||||||
|
struct wl_resource *manager_resource, uint32_t id,
|
||||||
|
int32_t overlay_cursor, struct wl_resource *output_resource) {
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *manager =
|
||||||
|
manager_from_resource(manager_resource);
|
||||||
|
struct wlr_output *output = wlr_output_from_resource(output_resource);
|
||||||
|
|
||||||
|
struct wlr_export_dmabuf_frame_v1 *frame =
|
||||||
|
calloc(1, sizeof(struct wlr_export_dmabuf_frame_v1));
|
||||||
|
if (frame == NULL) {
|
||||||
|
wl_resource_post_no_memory(manager_resource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame->manager = manager;
|
||||||
|
frame->output = output;
|
||||||
|
wl_list_init(&frame->output_swap_buffers.link);
|
||||||
|
|
||||||
|
uint32_t version = wl_resource_get_version(manager_resource);
|
||||||
|
frame->resource = wl_resource_create(client,
|
||||||
|
&zwlr_export_dmabuf_frame_v1_interface, version, id);
|
||||||
|
if (frame->resource == NULL) {
|
||||||
|
wl_client_post_no_memory(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wl_resource_set_implementation(frame->resource, &frame_impl, frame,
|
||||||
|
frame_handle_resource_destroy);
|
||||||
|
|
||||||
|
wl_list_insert(&manager->frames, &frame->link);
|
||||||
|
|
||||||
|
if (!output->impl->export_dmabuf) {
|
||||||
|
zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource,
|
||||||
|
ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_dmabuf_attributes *attribs = &frame->attribs;
|
||||||
|
if (!wlr_output_export_dmabuf(output, attribs)) {
|
||||||
|
zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource,
|
||||||
|
ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t frame_flags = ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT;
|
||||||
|
uint32_t mod_high = attribs->modifier >> 32;
|
||||||
|
uint32_t mod_low = attribs->modifier & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
zwlr_export_dmabuf_frame_v1_send_frame(frame->resource,
|
||||||
|
output->width, output->height, 0, 0, attribs->flags, frame_flags,
|
||||||
|
attribs->format, mod_high, mod_low, attribs->n_planes);
|
||||||
|
|
||||||
|
for (int i = 0; i < attribs->n_planes; ++i) {
|
||||||
|
off_t size = lseek(attribs->fd[i], 0, SEEK_END);
|
||||||
|
|
||||||
|
zwlr_export_dmabuf_frame_v1_send_object(frame->resource, i,
|
||||||
|
attribs->fd[i], size, attribs->offset[i], attribs->stride[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_list_remove(&frame->output_swap_buffers.link);
|
||||||
|
wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers);
|
||||||
|
frame->output_swap_buffers.notify = frame_output_handle_swap_buffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl = {
|
||||||
|
.capture_output = manager_handle_capture_output,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void manager_handle_resource_destroy(struct wl_resource *resource) {
|
||||||
|
wl_list_remove(wl_resource_get_link(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void manager_bind(struct wl_client *client, void *data, uint32_t version,
|
||||||
|
uint32_t id) {
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *manager = data;
|
||||||
|
|
||||||
|
struct wl_resource *resource = wl_resource_create(client,
|
||||||
|
&zwlr_export_dmabuf_manager_v1_interface, version, id);
|
||||||
|
if (resource == NULL) {
|
||||||
|
wl_client_post_no_memory(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wl_resource_set_implementation(resource, &manager_impl, manager,
|
||||||
|
manager_handle_resource_destroy);
|
||||||
|
|
||||||
|
wl_list_insert(&manager->resources, wl_resource_get_link(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_display_destroy(struct wl_listener *listener, void *data) {
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *manager =
|
||||||
|
wl_container_of(listener, manager, display_destroy);
|
||||||
|
wlr_export_dmabuf_manager_v1_destroy(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create(
|
||||||
|
struct wl_display *display) {
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *manager =
|
||||||
|
calloc(1, sizeof(struct wlr_export_dmabuf_manager_v1));
|
||||||
|
if (manager == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
wl_list_init(&manager->resources);
|
||||||
|
wl_list_init(&manager->frames);
|
||||||
|
|
||||||
|
manager->global = wl_global_create(display,
|
||||||
|
&zwlr_export_dmabuf_manager_v1_interface, EXPORT_DMABUF_MANAGER_VERSION,
|
||||||
|
manager, manager_bind);
|
||||||
|
if (manager->global == NULL) {
|
||||||
|
free(manager);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
manager->display_destroy.notify = handle_display_destroy;
|
||||||
|
wl_display_add_destroy_listener(display, &manager->display_destroy);
|
||||||
|
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wlr_export_dmabuf_manager_v1_destroy(
|
||||||
|
struct wlr_export_dmabuf_manager_v1 *manager) {
|
||||||
|
if (manager == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wl_list_remove(&manager->display_destroy.link);
|
||||||
|
wl_global_destroy(manager->global);
|
||||||
|
struct wl_resource *resource, *resource_tmp;
|
||||||
|
wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) {
|
||||||
|
wl_resource_destroy(resource);
|
||||||
|
}
|
||||||
|
struct wlr_export_dmabuf_frame_v1 *frame, *frame_tmp;
|
||||||
|
wl_list_for_each_safe(frame, frame_tmp, &manager->frames, link) {
|
||||||
|
wl_resource_destroy(frame->resource);
|
||||||
|
}
|
||||||
|
free(manager);
|
||||||
|
}
|
Loading…
Reference in new issue