/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne * * gstlibcameraallocator.cpp - GStreamer Custom Allocator */ #include "gstlibcameraallocator.h" #include #include #include #include "gstlibcamera-utils.h" using namespace libcamera; static gboolean gst_libcamera_allocator_release(GstMiniObject *mini_object); /** * \struct FrameWrap * \brief An internal wrapper to track the relation between FrameBuffer and * GstMemory(s) * * This wrapper maintains a count of the outstanding GstMemory (there may be * multiple GstMemory per FrameBuffer), and give back the FrameBuffer to the * allocator pool when all memory objects have returned. */ struct FrameWrap { FrameWrap(GstAllocator *allocator, FrameBuffer *buffer, gpointer stream); ~FrameWrap(); void acquirePlane() { ++outstandingPlanes_; } bool releasePlane() { return --outstandingPlanes_ == 0; } static GQuark getQuark(); gpointer stream_; FrameBuffer *buffer_; std::vector planes_; gint outstandingPlanes_; }; FrameWrap::FrameWrap(GstAllocator *allocator, FrameBuffer *buffer, gpointer stream) : stream_(stream), buffer_(buffer), outstandingPlanes_(0) { for (const FrameBuffer::Plane &plane : buffer->planes()) { GstMemory *mem = gst_fd_allocator_alloc(allocator, plane.fd.fd(), plane.length, GST_FD_MEMORY_FLAG_DONT_CLOSE); gst_mini_object_set_qdata(GST_MINI_OBJECT(mem), getQuark(), this, nullptr); GST_MINI_OBJECT(mem)->dispose = gst_libcamera_allocator_release; g_object_unref(mem->allocator); planes_.push_back(mem); } } FrameWrap::~FrameWrap() { for (GstMemory *mem : planes_) { GST_MINI_OBJECT(mem)->dispose = nullptr; g_object_ref(mem->allocator); gst_memory_unref(mem); } } GQuark FrameWrap::getQuark() { static gsize frame_quark = 0; if (g_once_init_enter(&frame_quark)) { GQuark quark = g_quark_from_string("GstLibcameraFrameWrap"); g_once_init_leave(&frame_quark, quark); } return frame_quark; } /** * \struct _GstLibcameraAllocator * \brief A pooling GstDmaBufAllocator for libcamera * * This is a pooling GstDmaBufAllocator implementation. This implementation override * the dispose function of memory object in order to keep them alive when they * are disposed by downstream elements. */ struct _GstLibcameraAllocator { GstDmaBufAllocator parent; FrameBufferAllocator *fb_allocator; /* * A hash table using Stream pointer as key and returning a GQueue of * FrameWrap. */ GHashTable *pools; }; G_DEFINE_TYPE(GstLibcameraAllocator, gst_libcamera_allocator, GST_TYPE_DMABUF_ALLOCATOR) static gboolean gst_libcamera_allocator_release(GstMiniObject *mini_object) { GstMemory *mem = GST_MEMORY_CAST(mini_object); GstLibcameraAllocator *self = GST_LIBCAMERA_ALLOCATOR(mem->allocator); GLibLocker lock(GST_OBJECT(self)); auto *frame = reinterpret_cast(gst_mini_object_get_qdata(mini_object, FrameWrap::getQuark())); gst_memory_ref(mem); if (frame->releasePlane()) { auto *pool = reinterpret_cast(g_hash_table_lookup(self->pools, frame->stream_)); g_return_val_if_fail(pool, TRUE); g_queue_push_tail(pool, frame); } /* Keep last in case we are holding on the last allocator ref. */ g_object_unref(mem->allocator); /* Return FALSE so that our mini object isn't freed. */ return FALSE; } static void gst_libcamera_allocator_free_pool(gpointer data) { GQueue *queue = reinterpret_cast(data); FrameWrap *frame; while ((frame = reinterpret_cast(g_queue_pop_head(queue)))) { g_warn_if_fail(frame->outstandingPlanes_ == 0); delete frame; } g_queue_free(queue); } static void gst_libcamera_allocator_init(GstLibcameraAllocator *self) { self->pools = g_hash_table_new_full(nullptr, nullptr, nullptr, gst_libcamera_allocator_free_pool); GST_OBJECT_FLAG_SET(self, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); } static void gst_libcamera_allocator_dispose(GObject *object) { GstLibcameraAllocator *self = GST_LIBCAMERA_ALLOCATOR(object); if (self->pools) { g_hash_table_unref(self->pools); self->pools = nullptr; } G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->dispose(object); } static void gst_libcamera_allocator_finalize(GObject *object) { GstLibcameraAllocator *self = GST_LIBCAMERA_ALLOCATOR(object); delete self->fb_allocator; G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->finalize(object); } static void gst_libcamera_allocator_class_init(GstLibcameraAllocatorClass *klass) { auto *allocator_class = GST_ALLOCATOR_CLASS(klass); auto *object_class = G_OBJECT_CLASS(klass); object_class->dispose = gst_libcamera_allocator_dispose; object_class->finalize = gst_libcamera_allocator_finalize; allocator_class->alloc = nullptr; } GstLibcameraAllocator * gst_libcamera_allocator_new(std::shared_ptr camera) { auto *self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR, nullptr)); self->fb_allocator = new FrameBufferAllocator(camera); for (Stream *stream : camera->streams()) { gint ret; ret = self->fb_allocator->allocate(stream); if (ret == 0) return nullptr; GQueue *pool = g_queue_new(); for (const std::unique_ptr &buffer : self->fb_allocator->buffers(stream)) { auto *fb = new FrameWrap(GST_ALLOCATOR(self), buffer.get(), stream); g_queue_push_tail(pool, fb); } g_hash_table_insert(self->pools, stream, pool); } return self; } bool gst_libcamera_allocator_prepare_buffer(GstLibcameraAllocator *self, Stream *stream, GstBuffer *buffer) { GLibLocker lock(GST_OBJECT(self)); auto *pool = reinterpret_cast(g_hash_table_lookup(self->pools, stream)); g_return_val_if_fail(pool, false); auto *frame = reinterpret_cast(g_queue_pop_head(pool)); if (!frame) return false; for (GstMemory *mem : frame->planes_) { frame->acquirePlane(); gst_buffer_append_memory(buffer, mem); g_object_ref(mem->allocator); } return true; } gsize gst_libcamera_allocator_get_pool_size(GstLibcameraAllocator *self, Stream *stream) { GLibLocker lock(GST_OBJECT(self)); auto *pool = reinterpret_cast(g_hash_table_lookup(self->pools, stream)); g_return_val_if_fail(pool, false); return pool->length; } FrameBuffer * gst_libcamera_memory_get_frame_buffer(GstMemory *mem) { auto *frame = reinterpret_cast(gst_mini_object_get_qdata(GST_MINI_OBJECT_CAST(mem), FrameWrap::getQuark())); return frame->buffer_; } f='#n72'>72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * v4l2_compat_manager.cpp - V4L2 compatibility manager
 */

#include "v4l2_compat_manager.h"

#include <dlfcn.h>
#include <fcntl.h>
#include <map>
#include <stdarg.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>

#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>

#include "libcamera/internal/log.h"

#include "v4l2_camera_file.h"

using namespace libcamera;

LOG_DEFINE_CATEGORY(V4L2Compat)

namespace {
template<typename T>
void get_symbol(T &func, const char *name)
{
	func = reinterpret_cast<T>(dlsym(RTLD_NEXT, name));
}
} /* namespace */

V4L2CompatManager::V4L2CompatManager()
	: cm_(nullptr)
{
	get_symbol(fops_.openat, "openat64");
	get_symbol(fops_.dup, "dup");
	get_symbol(fops_.close, "close");
	get_symbol(fops_.ioctl, "ioctl");
	get_symbol(fops_.mmap, "mmap64");
	get_symbol(fops_.munmap, "munmap");
}

V4L2CompatManager::~V4L2CompatManager()
{
	files_.clear();
	mmaps_.clear();

	if (cm_) {
		proxies_.clear();
		cm_->stop();
		delete cm_;
		cm_ = nullptr;
	}
}

int V4L2CompatManager::start()
{
	cm_ = new CameraManager();

	int ret = cm_->start();
	if (ret) {
		LOG(V4L2Compat, Error) << "Failed to start camera manager: "
				       << strerror(-ret);
		delete cm_;
		cm_ = nullptr;
		return ret;
	}

	LOG(V4L2Compat, Debug) << "Started camera manager";

	/*
	 * For each Camera registered in the system, a V4L2CameraProxy gets
	 * created here to wrap a camera device.
	 */
	unsigned int index = 0;
	for (auto &camera : cm_->cameras()) {
		V4L2CameraProxy *proxy = new V4L2CameraProxy(index, camera);
		proxies_.emplace_back(proxy);
		++index;
	}

	return 0;
}

V4L2CompatManager *V4L2CompatManager::instance()
{
	static V4L2CompatManager instance;
	return &instance;
}

std::shared_ptr<V4L2CameraFile> V4L2CompatManager::cameraFile(int fd)
{
	auto file = files_.find(fd);
	if (file == files_.end())
		return nullptr;

	return file->second;
}

int V4L2CompatManager::getCameraIndex(int fd)
{
	struct stat statbuf;
	int ret = fstat(fd, &statbuf);
	if (ret < 0)
		return -1;

	std::shared_ptr<Camera> target = cm_->get(statbuf.st_rdev);
	if (!target)
		return -1;

	unsigned int index = 0;
	for (auto &camera : cm_->cameras()) {
		if (camera == target)
			return index;
		++index;
	}

	return -1;
}

int V4L2CompatManager::openat(int dirfd, const char *path, int oflag, mode_t mode)
{
	int fd = fops_.openat(dirfd, path, oflag, mode);
	if (fd < 0)
		return fd;

	struct stat statbuf;
	int ret = fstat(fd, &statbuf);
	if (ret < 0 || (statbuf.st_mode & S_IFMT) != S_IFCHR ||
	    major(statbuf.st_rdev) != 81)
		return fd;

	if (!cm_)
		start();

	ret = getCameraIndex(fd);
	if (ret < 0) {
		LOG(V4L2Compat, Info) << "No camera found for " << path;
		return fd;
	}

	fops_.close(fd);

	int efd = eventfd(0, EFD_SEMAPHORE |
			     ((oflag & O_CLOEXEC) ? EFD_CLOEXEC : 0) |
			     ((oflag & O_NONBLOCK) ? EFD_NONBLOCK : 0));
	if (efd < 0)
		return efd;

	V4L2CameraProxy *proxy = proxies_[ret].get();
	files_.emplace(efd, std::make_shared<V4L2CameraFile>(efd, oflag & O_NONBLOCK, proxy));

	return efd;
}

int V4L2CompatManager::dup(int oldfd)
{
	int newfd = fops_.dup(oldfd);
	if (newfd < 0)
		return newfd;

	auto file = files_.find(oldfd);
	if (file != files_.end())
		files_[newfd] = file->second;

	return newfd;
}

int V4L2CompatManager::close(int fd)
{
	auto file = files_.find(fd);
	if (file != files_.end())
		files_.erase(file);

	/* We still need to close the eventfd. */
	return fops_.close(fd);
}

void *V4L2CompatManager::mmap(void *addr, size_t length, int prot, int flags,
			      int fd, off64_t offset)
{
	std::shared_ptr<V4L2CameraFile> file = cameraFile(fd);
	if (!file)