/* * Copyright (C) 2023 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 "capture.h" #include #include #include #include #include #include #include #include #include "gpu.h" #include "util.h" struct CaptureContext { int drm_fd; struct GpuFrame* gpu_frame; }; static int OpenAnyModule(void) { static const char* const modules[] = { "i915", "amdgpu", "radeon", "nouveau", "vmwgfx", "omapdrm", "exynos", "tilcdc", "msm", "sti", "tegra", "imx-drm", "rockchip", "atmel-hlcdc", "fsl-dcu-drm", "vc4", "virtio_gpu", "mediatek", "meson", "pl111", "stm", "sun4i-drm", "armada-drm", "komeda", "imx-dcss", "mxsfb-drm", "simpledrm", "imx-lcdif", "vkms", }; for (size_t i = 0; i < LENGTH(modules); i++) { int drm_fd = drmOpen(modules[i], NULL); if (drm_fd >= 0) return drm_fd; LOG("Failed to open %s (%s)", modules[i], strerror(errno)); } return -1; } static void drmModeResPtrDestroy(drmModeResPtr* res) { if (res && *res) drmModeFreeResources(*res); } static void drmModeCrtcPtrDestroy(drmModeCrtcPtr* crtc) { if (crtc && *crtc) drmModeFreeCrtc(*crtc); } static void drmModeFB2PtrDestroy(drmModeFB2Ptr* fb2) { if (fb2 && *fb2) drmModeFreeFB2(*fb2); } static struct GpuFrame* WrapFramebuffer(int drm_fd, drmModeFB2Ptr fb2, struct GpuContext* gpu_context) { size_t nplanes = 0; struct GpuFrame* result = NULL; struct GpuFramePlane planes[LENGTH(fb2->handles)]; for (; nplanes < LENGTH(planes) && fb2->handles[nplanes]; nplanes++) { int status = drmPrimeHandleToFD(drm_fd, fb2->handles[nplanes], 0, &planes[nplanes].dmabuf_fd); if (status) { LOG("Failed to get dmabuf fd (%d)", status); goto release_planes; } planes[nplanes].offset = fb2->offsets[nplanes]; planes[nplanes].pitch = fb2->pitches[nplanes]; // TODO(mburakov): Structure of drmModeFB2 implies that all the planes have // the same modifier. At the same time, surrounding code supports per-plane // modifiers. So right now a drmModeFB2-wide modifier is just copypasted // into each plane descriptor. planes[nplanes].modifier = fb2->modifier; } result = GpuFrameCreate(gpu_context, fb2->width, fb2->height, fb2->pixel_format, nplanes, planes); if (!result) LOG("Failed to create gpu frame"); release_planes: for (; nplanes; nplanes--) close(planes[nplanes - 1].dmabuf_fd); return result; } static struct GpuFrame* GrabCrtc(int drm_fd, uint32_t crtc_id, struct GpuContext* gpu_context) { AUTO(drmModeCrtcPtr) crtc = drmModeGetCrtc(drm_fd, crtc_id); if (!crtc) { LOG("Failed to get crtc %u", crtc_id); return NULL; } if (!crtc->buffer_id) { LOG("Crtc %u has no framebuffer", crtc_id); return NULL; } AUTO(drmModeFB2Ptr) fb2 = drmModeGetFB2(drm_fd, crtc->buffer_id); if (!fb2) { LOG("Failed to get framebuffer %u", crtc->buffer_id); return NULL; } if (!fb2->handles[0]) { LOG("Framebuffer %u has no handles", crtc->buffer_id); return NULL; } LOG("Capturing framebuffer %u on crtc %u", crtc->buffer_id, crtc_id); return WrapFramebuffer(drm_fd, fb2, gpu_context); } struct CaptureContext* CaptureContextCreate(struct GpuContext* gpu_context) { struct AUTO(CaptureContext)* capture_context = malloc(sizeof(struct CaptureContext)); if (!capture_context) { LOG("Failed to allocate capture context (%s)", strerror(errno)); return NULL; } *capture_context = (struct CaptureContext){ .drm_fd = -1, .gpu_frame = NULL, }; capture_context->drm_fd = OpenAnyModule(); if (capture_context->drm_fd == -1) return NULL; AUTO(drmModeResPtr) res = drmModeGetResources(capture_context->drm_fd); if (!res) { LOG("Failed to get drm mode resources (%s)", strerror(errno)); return NULL; } for (int i = 0; i < res->count_crtcs; i++) { capture_context->gpu_frame = GrabCrtc(capture_context->drm_fd, res->crtcs[i], gpu_context); if (capture_context->gpu_frame) return RELEASE(capture_context); } LOG("Nothing to capture"); return NULL; } const struct GpuFrame* CaptureContextGetFrame( struct CaptureContext* capture_context) { // TODO(mburakov): Verify nothing changed since last frame return capture_context->gpu_frame; } void CaptureContextDestroy(struct CaptureContext** capture_context) { if (!capture_context || !*capture_context) return; if ((*capture_context)->gpu_frame) GpuFrameDestroy(&(*capture_context)->gpu_frame); if ((*capture_context)->drm_fd != -1) drmClose((*capture_context)->drm_fd); free(*capture_context); capture_context = NULL; }