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