summaryrefslogtreecommitdiff
path: root/capture.c
diff options
context:
space:
mode:
authorMikhail Burakov <mburakov@mailbox.org>2023-03-14 08:40:53 +0100
committerMikhail Burakov <mburakov@mailbox.org>2023-03-14 08:40:53 +0100
commit1c79dfed2c973b159b8f4a39a8087f772b70ceb0 (patch)
treec7ea2fc1d0d15557c16c16372761e3c1bf1d1903 /capture.c
Initial import of streamer source code
Diffstat (limited to 'capture.c')
-rw-r--r--capture.c166
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;
+}