summaryrefslogtreecommitdiff
path: root/test/camera/camera_reconfigure.cpp
blob: f6076baa0a3118260fb86e28c421e7360dfd9c5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2019-2021, Google Inc.
 *
 * camera_request.h - libcamera Android Camera Request Descriptor
 */

#pragma once

#include <map>
#include <memory>
#include <vector>

#include <libcamera/base/class.h>
#include <libcamera/base/mutex.h>
#include <libcamera/base/unique_fd.h>

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

#include <hardware/camera3.h>

#include "camera_metadata.h"
#include "hal_framebuffer.h"

class CameraBuffer;
class CameraStream;

class Camera3RequestDescriptor
{
public:
	enum class Status {
		Success,
		Error,
	};

	struct StreamBuffer {
		StreamBuffer(CameraStream *stream,
			     const camera3_stream_buffer_t &buffer,
			     Camera3RequestDescriptor *request);
		~StreamBuffer();

		StreamBuffer(StreamBuffer &&);
		StreamBuffer &operator=(StreamBuffer &&);

		CameraStream *stream;
		buffer_handle_t *camera3Buffer;
		std::unique_ptr<HALFrameBuffer> frameBuffer;
		libcamera::UniqueFD fence;
		Status status = Status::Success;
		libcamera::FrameBuffer *internalBuffer = nullptr;
		const libcamera::FrameBuffer *srcBuffer = nullptr;
		std::unique_ptr<CameraBuffer> dstBuffer;
		Camera3RequestDescriptor *request;

	private:
		LIBCAMERA_DISABLE_COPY(StreamBuffer)
	};

	/* Keeps track of streams requiring post-processing. */
	std::map<CameraStream *, StreamBuffer *> pendingStreamsToProcess_
		LIBCAMERA_TSA_GUARDED_BY(streamsProcessMutex_);
	libcamera::Mutex streamsProcessMutex_;

	Camera3RequestDescriptor(libcamera::Camera *camera,
				 const camera3_capture_request_t *camera3Request);
	~Camera3RequestDescriptor();

	bool isPending() const { return !complete_; }

	uint32_t frameNumber_ = 0;

	std::vector<StreamBuffer> buffers_;

	CameraMetadata settings_;
	/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2021, Google Inc.
 *
 * Test:
 * - Multiple reconfigurations of the Camera without stopping the CameraManager
 * - Validate there are no file descriptor leaks when using IPC
 */

#include <dirent.h>
#include <fstream>
#include <iostream>

#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/file.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>

#include <libcamera/framebuffer_allocator.h>

#include "camera_test.h"
#include "test.h"

using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;

namespace {

class CameraReconfigure : public CameraTest, public Test
{
public:
	/* Initialize CameraTest with isolated IPA */
	CameraReconfigure()
		: CameraTest(kCamId_, true)
	{
	}

private:
	static constexpr const char *kCamId_ = "platform/vimc.0 Sensor B";
	static constexpr const char *kIpaProxyName_ = "vimc_ipa_proxy";
	static constexpr unsigned int kNumOfReconfigures_ = 10;

	void requestComplete(Request *request)
	{
		if (request->status() != Request::RequestComplete)
			return;

		const Request::BufferMap &buffers = request->buffers();

		const Stream *stream = buffers.begin()->first;
		FrameBuffer *buffer = buffers.begin()->second;

		/* Reuse the request and re-queue it with the same buffers. */
		request->reuse();
		request->addBuffer(stream, buffer);
		camera_->queueRequest(request);
	}

	int startAndStop()
	{
		StreamConfiguration &cfg = config_->at(0);

		if (camera_->acquire()) {
			cerr << "Failed to acquire the camera" << endl;
			return TestFail;
		}

		if (camera_->configure(config_.get())) {
			cerr << "Failed to set default configuration" << endl;
			return TestFail;
		}

		Stream *stream = cfg.stream();

		/*
		 * The configuration is consistent so we can re-use the
		 * same buffer allocation for each run.
		 */
		if (!allocated_) {
			int ret = allocator_->allocate(stream);
			if (ret < 0) {
				cerr << "Failed to allocate buffers" << endl;
				return TestFail;
			}
			allocated_ = true;
		}

		for (const unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
			unique_ptr<Request> request = camera_->createRequest();
			if (!request) {
				cerr << "Failed to create request" << endl;
				return TestFail;
			}

			if (request->addBuffer(stream, buffer.get())) {
				cerr << "Failed to associate buffer with request" << endl;
				return TestFail;
			}

			requests_.push_back(move(request));
		}

		camera_->requestCompleted.connect(this, &CameraReconfigure::requestComplete);

		if (camera_->start()) {
			cerr << "Failed to start camera" << endl;
			return TestFail;
		}

		for (unique_ptr<Request> &request : requests_) {
			if (camera_->queueRequest(request.get())) {
				cerr << "Failed to queue request" << endl;
				return TestFail;
			}
		}

		EventDispatcher *dispatcher = Thread::current()->eventDispatcher();

		Timer timer;
		timer.start(100ms);
		while (timer.isRunning())
			dispatcher->processEvents();

		if (camera_->stop()) {
			cerr << "Failed to stop camera" << endl;
			return TestFail;
		}

		if (camera_->release()) {
			cerr << "Failed to release camera" << endl;
			return TestFail;
		}

		camera_->requestCompleted.disconnect(this);

		requests_.clear();

		return 0;
	}

	int fdsOpen(pid_t pid)
	{
		string proxyFdPath = "/proc/" + to_string(pid) + "/fd";
		DIR *dir;
		struct dirent *ptr;
		unsigned int openFds = 0;

		dir = opendir(proxyFdPath.c_str());
		if (dir == nullptr) {
			int err = errno;
			cerr << "Error opening " << proxyFdPath << ": "
			     << strerror(-err) << endl;
			return 0;
		}

		while ((ptr = readdir(dir)) != nullptr) {
			if ((strcmp(ptr->d_name, ".") == 0) ||
			    (strcmp(ptr->d_name, "..") == 0))
				continue;

			openFds++;
		}
		closedir(dir);

		return openFds;
	}

	pid_t findProxyPid()
	{
		string proxyPid;
		string proxyName(kIpaProxyName_);
		DIR *dir;
		struct dirent *ptr;

		dir = opendir("/proc");
		while ((ptr = readdir(dir)) != nullptr) {
			if (ptr->d_type != DT_DIR)
				continue;

			string pname("/proc/" + string(ptr->d_name) + "/comm");
			if (File::exists(pname.c_str())) {
				ifstream pfile(pname.c_str());
				string comm;
				getline(pfile, comm);
				pfile.close();

				proxyPid = comm == proxyName ? string(ptr->d_name) : "";
			}

			if (!proxyPid.empty())
				break;
		}
		closedir(dir);

		if (!proxyPid.empty())
			return atoi(proxyPid.c_str());

		return -1;
	}

	int init() override
	{
		if (status_ != TestPass)
			return status_;

		config_ = camera_->generateConfiguration({ StreamRole::StillCapture });
		if (!config_ || config_->size() != 1) {
			cerr << "Failed to generate default configuration" << endl;
			return TestFail;
		}

		allocator_ = make_unique<FrameBufferAllocator>(camera_);
		allocated_ = false;

		return TestPass;
	}

	int run() override
	{
		unsigned int openFdsAtStart = 0;
		unsigned int openFds = 0;

		pid_t proxyPid = findProxyPid();
		if (proxyPid < 0) {
			cerr << "Cannot find " << kIpaProxyName_
			     << " pid, exiting" << endl;
			return TestFail;
		}

		openFdsAtStart = fdsOpen(proxyPid);
		for (unsigned int i = 0; i < kNumOfReconfigures_; i++) {
			startAndStop();
			openFds = fdsOpen(proxyPid);
			if (openFds == 0) {
				cerr << "No open fds found whereas "
				     << "open fds at start: " << openFdsAtStart
				     << endl;
				return TestFail;
			}

			if (openFds != openFdsAtStart) {
				cerr << "Leaking fds for " << kIpaProxyName_
				     << " - Open fds: " << openFds << " vs "
				     << "Open fds at start: " << openFdsAtStart
				     << endl;
				return TestFail;
			}
		}

		return TestPass;
	}

	bool allocated_;

	vector<unique_ptr<Request>> requests_;

	unique_ptr<CameraConfiguration> config_;
	unique_ptr<FrameBufferAllocator> allocator_;
};

} /* namespace */

TEST_REGISTER(CameraReconfigure)