/* * Copyright (C) 2024 Mikhail Burakov. This file is part of streamer. * * streamer is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * streamer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with streamer. If not, see . */ #include "video_context.h" #include #include #include #include #include #include #include #include #include "util.h" #include "wlr-export-dmabuf-unstable-v1.h" struct VideoContext { struct IoContext* io_context; // Wayland globals struct wl_display* display; struct wl_registry* registry; struct wl_output* output; struct zwlr_export_dmabuf_manager_v1* export_dmabuf_manager; // Threading struct pw_thread_loop* thread_loop; struct spa_source* source; }; static void OnRegistryGlobal(void* data, struct wl_registry* wl_registry, uint32_t name, const char* interface, uint32_t version) { #define MAYBE_BIND(a, b) \ if (!strcmp(interface, a.name)) do { \ if (!b) b = wl_registry_bind(wl_registry, name, &a, version); \ if (!b) LOG("Failed to bind " #a " (%s)", strerror(errno)); \ return; \ } while (false) struct VideoContext* video_context = data; MAYBE_BIND(wl_output_interface, video_context->output); MAYBE_BIND(zwlr_export_dmabuf_manager_v1_interface, video_context->export_dmabuf_manager); #undef MAYBE_BIND } static void OnRegistryGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) { (void)data; (void)registry; (void)name; } static bool InitWaylandGlobals(struct VideoContext* video_context) { video_context->display = wl_display_connect(NULL); if (!video_context->display) { LOG("Failed to open display"); return false; } video_context->registry = wl_display_get_registry(video_context->display); if (!video_context->registry) { LOG("Failed to get registry"); goto rollback_display; } static const struct wl_registry_listener kRegistryListener = { .global = OnRegistryGlobal, .global_remove = OnRegistryGlobalRemove, }; if (wl_registry_add_listener(video_context->registry, &kRegistryListener, video_context)) { LOG("Failed to add registry listener"); goto rollback_registry; } if (wl_display_roundtrip(video_context->display) == -1) { LOG("Failed to roundtrip display"); goto rollback_registry; } if (!video_context->output || !video_context->export_dmabuf_manager) { LOG("Some required globals are missing"); goto rollback_globals; } return true; rollback_globals: if (video_context->export_dmabuf_manager) zwlr_export_dmabuf_manager_v1_destroy(video_context->export_dmabuf_manager); if (video_context->output) wl_output_destroy(video_context->output); rollback_registry: wl_registry_destroy(video_context->registry); rollback_display: wl_display_disconnect(video_context->display); return false; } static void OnDisplayData(void* arg, int fd, uint32_t mask) { (void)fd; (void)mask; struct VideoContext* video_context = arg; if (wl_display_dispatch(video_context->display) == -1) { LOG("Failed to dispatch display"); // TODO(mburakov): Now what?.. } } static void OnExportDmabufFrameFrame( void* data, struct zwlr_export_dmabuf_frame_v1* export_dmabuf_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) { (void)data; (void)export_dmabuf_frame; (void)width; (void)height; (void)offset_x; (void)offset_y; (void)buffer_flags; (void)flags; (void)format; (void)mod_high; (void)mod_low; (void)num_objects; LOG("%s(data=%p, export_dmabuf_frame=%p, width=%u, height=%u, " "offset_x=%u, offset_y=%u, buffer_flags=0x%x, flags=0x%x, " "format=0x%08x, mod_high=%08x, mod_low=%08x, num_objects=%u)", __FUNCTION__, data, (void*)export_dmabuf_frame, width, height, offset_x, offset_y, buffer_flags, flags, format, mod_high, mod_low, num_objects); } static void OnExportDmabufFrameObject( void* data, struct zwlr_export_dmabuf_frame_v1* export_dmabuf_frame, uint32_t index, int32_t fd, uint32_t size, uint32_t offset, uint32_t stride, uint32_t plane_index) { (void)data; (void)export_dmabuf_frame; (void)index; (void)fd; (void)size; (void)offset; (void)stride; (void)plane_index; LOG("%s(data=%p, export_dmabuf_frame=%p, index=%u, fd=%d, " "size=%u, offset=%u, stride=%u, plane_index=%u)", __FUNCTION__, data, (void*)export_dmabuf_frame, index, fd, size, offset, stride, plane_index); } static void OnExportDmabufFrameReady( void* data, struct zwlr_export_dmabuf_frame_v1* export_dmabuf_frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { (void)data; (void)export_dmabuf_frame; (void)tv_sec_hi; (void)tv_sec_lo; (void)tv_nsec; struct VideoContext* video_context = data; LOG("%s(data=%p, export_dmabuf_frame=%p, " "tv_sec_hi=%u, tv_sec_lo=%u, tv_nsec=%u)", __FUNCTION__, data, (void*)export_dmabuf_frame, tv_sec_hi, tv_sec_lo, tv_nsec); zwlr_export_dmabuf_frame_v1_destroy(export_dmabuf_frame); } static void OnExportDmabufFrameCancel( void* data, struct zwlr_export_dmabuf_frame_v1* export_dmabuf_frame, uint32_t reason) { (void)data; (void)export_dmabuf_frame; (void)reason; struct VideoContext* video_context = data; static const char* const kCancelReasons[] = { "temporary", "permanent", "resizing", }; LOG("%s(data=%p, export_dmabuf_frame=%p, reason=%s)", __FUNCTION__, data, (void*)export_dmabuf_frame, kCancelReasons[reason]); zwlr_export_dmabuf_frame_v1_destroy(export_dmabuf_frame); } static int RequestCapture(struct spa_loop* loop, bool async, uint32_t seq, const void* data, size_t size, void* user_data) { (void)loop; (void)async; (void)seq; (void)data; (void)size; struct VideoContext* video_context = user_data; struct zwlr_export_dmabuf_frame_v1* export_dmabuf_frame = zwlr_export_dmabuf_manager_v1_capture_output( video_context->export_dmabuf_manager, 1, video_context->output); if (!export_dmabuf_frame) { LOG("Failed to capture output"); // TODO(mburakov): Now what?.. return 0; } static const struct zwlr_export_dmabuf_frame_v1_listener kExportDmabufFrameListener = { .frame = OnExportDmabufFrameFrame, .object = OnExportDmabufFrameObject, .ready = OnExportDmabufFrameReady, .cancel = OnExportDmabufFrameCancel, }; if (zwlr_export_dmabuf_frame_v1_add_listener( export_dmabuf_frame, &kExportDmabufFrameListener, video_context)) { LOG("Failed to add frame listener"); goto rollback_export_dmabuf_frame; } if (wl_display_flush(video_context->display) == -1) { LOG("Failed to flush display"); goto rollback_export_dmabuf_frame; } return 0; rollback_export_dmabuf_frame: zwlr_export_dmabuf_frame_v1_destroy(export_dmabuf_frame); // TODO(mburakov): Now what?.. return 0; } struct VideoContext* VideoContextCreate(struct IoContext* io_context) { struct VideoContext* video_context = malloc(sizeof(struct VideoContext)); if (!video_context) { LOG("Failed to allocate video context (%s)", strerror(errno)); return NULL; } *video_context = (struct VideoContext){ .io_context = io_context, }; if (!InitWaylandGlobals(video_context)) { LOG("Failed to init wayland globals"); goto rollback_video_context; } video_context->thread_loop = pw_thread_loop_new("video-capture", NULL); if (!video_context->thread_loop) { LOG("Failed to create thread loop"); goto rollback_wayland_globals; } pw_thread_loop_lock(video_context->thread_loop); if (pw_thread_loop_start(video_context->thread_loop)) { LOG("Failed to start thread loop"); goto rollback_thread_loop; } struct pw_loop* loop = pw_thread_loop_get_loop(video_context->thread_loop); if (!loop) { LOG("Failed to get thread loop"); goto rollback_thread_loop; } int events_fd = wl_display_get_fd(video_context->display); if (events_fd == -1) { LOG("Failed to get display fd"); goto rollback_thread_loop; } video_context->source = pw_loop_add_io(loop, events_fd, SPA_IO_IN, false, OnDisplayData, video_context); if (!video_context->source) { LOG("Failed to add thread loop io"); goto rollback_thread_loop; } if (pw_loop_invoke(loop, RequestCapture, SPA_ID_INVALID, NULL, 0, false, video_context)) { LOG("Failed to request capture"); goto rollback_thread_loop; } pw_thread_loop_unlock(video_context->thread_loop); return video_context; rollback_thread_loop: pw_thread_loop_unlock(video_context->thread_loop); pw_thread_loop_destroy(video_context->thread_loop); rollback_wayland_globals: zwlr_export_dmabuf_manager_v1_destroy(video_context->export_dmabuf_manager); wl_output_destroy(video_context->output); wl_registry_destroy(video_context->registry); wl_display_disconnect(video_context->display); rollback_video_context: free(video_context); return NULL; } void VideoContextDestroy(struct VideoContext* video_context) { pw_thread_loop_lock(video_context->thread_loop); assert(pw_loop_destroy_source( pw_thread_loop_get_loop(video_context->thread_loop), video_context->source)); pw_thread_loop_unlock(video_context->thread_loop); pw_thread_loop_destroy(video_context->thread_loop); zwlr_export_dmabuf_manager_v1_destroy(video_context->export_dmabuf_manager); wl_output_release(video_context->output); wl_registry_destroy(video_context->registry); wl_display_disconnect(video_context->display); free(video_context); }