diff options
author | Mikhail Burakov <mburakov@mailbox.org> | 2023-03-14 08:40:53 +0100 |
---|---|---|
committer | Mikhail Burakov <mburakov@mailbox.org> | 2023-03-14 08:40:53 +0100 |
commit | 1c79dfed2c973b159b8f4a39a8087f772b70ceb0 (patch) | |
tree | c7ea2fc1d0d15557c16c16372761e3c1bf1d1903 /capture.c |
Initial import of streamer source code
Diffstat (limited to 'capture.c')
-rw-r--r-- | capture.c | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/capture.c b/capture.c new file mode 100644 index 0000000..6542c1d --- /dev/null +++ b/capture.c @@ -0,0 +1,166 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include "capture.h" + +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <xf86drm.h> +#include <xf86drmMode.h> + +#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; +} |