ipa: Switch to the plain C API

Switch IPA communication to the plain C API. As the IPAInterface class
is easier to use for pipeline handlers than a plain C API, retain it and
add an IPAContextWrapper that translate between the C++ and the C APIs.

On the IPA module side usage of IPAInterface may be desired for IPAs
implemented in C++ that want to link to libcamera. For those IPAs, a new
IPAInterfaceWrapper helper class is introduced to wrap the IPAInterface
implemented internally by the IPA module into an ipa_context,
ipa_context_ops and ipa_callback_ops.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
This commit is contained in:
Jacopo Mondi
2019-09-15 17:30:26 +03:00
committed by Laurent Pinchart
parent bc9527de45
commit 132d99bc8f
17 changed files with 676 additions and 23 deletions
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ipa_context_wrapper.h - Image Processing Algorithm context wrapper
*/
#ifndef __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__
#define __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__
#include <ipa/ipa_interface.h>
#include "control_serializer.h"
namespace libcamera {
class IPAContextWrapper final : public IPAInterface
{
public:
IPAContextWrapper(struct ipa_context *context);
~IPAContextWrapper();
int init() override;
void configure(const std::map<unsigned int, IPAStream> &streamConfig,
const std::map<unsigned int, const ControlInfoMap &> &entityControls) override;
void mapBuffers(const std::vector<IPABuffer> &buffers) override;
void unmapBuffers(const std::vector<unsigned int> &ids) override;
virtual void processEvent(const IPAOperationData &data) override;
private:
static void queue_frame_action(void *ctx, unsigned int frame,
struct ipa_operation_data &data);
static const struct ipa_callback_ops callbacks_;
struct ipa_context *ctx_;
ControlSerializer serializer_;
};
} /* namespace libcamera */
#endif /* __LIBCAMERA_IPA_CONTEXT_WRAPPER_H__ */
+2 -3
View File
@@ -7,7 +7,6 @@
#ifndef __LIBCAMERA_IPA_MODULE_H__
#define __LIBCAMERA_IPA_MODULE_H__
#include <memory>
#include <string>
#include <ipa/ipa_interface.h>
@@ -30,7 +29,7 @@ public:
bool load();
std::unique_ptr<IPAInterface> createInstance();
struct ipa_context *createContext();
bool match(PipelineHandler *pipe,
uint32_t minVersion, uint32_t maxVersion) const;
@@ -45,7 +44,7 @@ private:
bool loaded_;
void *dlHandle_;
typedef IPAInterface *(*IPAIntfFactory)(void);
typedef struct ipa_context *(*IPAIntfFactory)(void);
IPAIntfFactory ipaCreate_;
int loadIPAModuleInfo();
+1
View File
@@ -9,6 +9,7 @@ libcamera_headers = files([
'device_enumerator_udev.h',
'event_dispatcher_poll.h',
'formats.h',
'ipa_context_wrapper.h',
'ipa_manager.h',
'ipa_module.h',
'ipa_proxy.h',
+221
View File
@@ -0,0 +1,221 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ipa_context_wrapper.cpp - Image Processing Algorithm context wrapper
*/
#include "ipa_context_wrapper.h"
#include <vector>
#include <libcamera/controls.h>
#include "byte_stream_buffer.h"
/**
* \file ipa_context_wrapper.h
* \brief Image Processing Algorithm context wrapper
*/
namespace libcamera {
/**
* \class IPAContextWrapper
* \brief Wrap an ipa_context and expose it as an IPAInterface
*
* The IPAContextWrapper class wraps an ipa_context, provided by an IPA module, and
* exposes an IPAInterface. This mechanism is used for IPAs that are not
* isolated in a separate process to allow direct calls from pipeline handler
* using the IPAInterface API instead of the lower-level ipa_context API.
*
* The IPAInterface methods are converted to the ipa_context API by translating
* all C++ arguments into plain C structures or byte arrays that contain no
* pointer, as required by the ipa_context API.
*/
/**
* \brief Construct an IPAContextWrapper instance that wraps the \a context
* \param[in] context The IPA module context
*
* Ownership of the \a context is passed to the IPAContextWrapper. The context remains
* valid for the whole lifetime of the wrapper and is destroyed automatically
* with it.
*/
IPAContextWrapper::IPAContextWrapper(struct ipa_context *context)
: ctx_(context)
{
if (!ctx_)
return;
ctx_->ops->register_callbacks(ctx_, &IPAContextWrapper::callbacks_,
this);
}
IPAContextWrapper::~IPAContextWrapper()
{
if (!ctx_)
return;
ctx_->ops->destroy(ctx_);
}
int IPAContextWrapper::init()
{
if (!ctx_)
return 0;
ctx_->ops->init(ctx_);
return 0;
}
void IPAContextWrapper::configure(const std::map<unsigned int, IPAStream> &streamConfig,
const std::map<unsigned int, const ControlInfoMap &> &entityControls)
{
if (!ctx_)
return;
serializer_.reset();
/* Translate the IPA stream configurations map. */
struct ipa_stream c_streams[streamConfig.size()];
unsigned int i = 0;
for (const auto &stream : streamConfig) {
struct ipa_stream *c_stream = &c_streams[i];
unsigned int id = stream.first;
const IPAStream &ipaStream = stream.second;
c_stream->id = id;
c_stream->pixel_format = ipaStream.pixelFormat;
c_stream->width = ipaStream.size.width;
c_stream->height = ipaStream.size.height;
++i;
}
/* Translate the IPA entity controls map. */
struct ipa_control_info_map c_info_maps[entityControls.size()];
std::vector<std::vector<uint8_t>> data(entityControls.size());
i = 0;
for (const auto &info : entityControls) {
struct ipa_control_info_map &c_info_map = c_info_maps[i];
unsigned int id = info.first;
const ControlInfoMap &infoMap = info.second;
size_t infoMapSize = serializer_.binarySize(infoMap);
data[i].resize(infoMapSize);
ByteStreamBuffer byteStream(data[i].data(), data[i].size());
serializer_.serialize(infoMap, byteStream);
c_info_map.id = id;
c_info_map.data = byteStream.base();
c_info_map.size = byteStream.size();
++i;
}
ctx_->ops->configure(ctx_, c_streams, streamConfig.size(),
c_info_maps, entityControls.size());
}
void IPAContextWrapper::mapBuffers(const std::vector<IPABuffer> &buffers)
{
if (!ctx_)
return;
struct ipa_buffer c_buffers[buffers.size()];
for (unsigned int i = 0; i < buffers.size(); ++i) {
struct ipa_buffer &c_buffer = c_buffers[i];
const IPABuffer &buffer = buffers[i];
const std::vector<Plane> &planes = buffer.memory.planes();
c_buffer.id = buffer.id;
c_buffer.num_planes = planes.size();
for (unsigned int j = 0; j < planes.size(); ++j) {
const Plane &plane = planes[j];
c_buffer.planes[j].dmabuf = plane.dmabuf();
c_buffer.planes[j].length = plane.length();
}
}
ctx_->ops->map_buffers(ctx_, c_buffers, buffers.size());
}
void IPAContextWrapper::unmapBuffers(const std::vector<unsigned int> &ids)
{
if (!ctx_)
return;
ctx_->ops->unmap_buffers(ctx_, ids.data(), ids.size());
}
void IPAContextWrapper::processEvent(const IPAOperationData &data)
{
if (!ctx_)
return;
struct ipa_operation_data c_data;
c_data.operation = data.operation;
c_data.data = data.data.data();
c_data.num_data = data.data.size();
struct ipa_control_list control_lists[data.controls.size()];
c_data.lists = control_lists;
c_data.num_lists = data.controls.size();
std::size_t listsSize = 0;
for (const auto &list : data.controls)
listsSize += serializer_.binarySize(list);
std::vector<uint8_t> binaryData(listsSize);
ByteStreamBuffer byteStreamBuffer(binaryData.data(), listsSize);
unsigned int i = 0;
for (const auto &list : data.controls) {
struct ipa_control_list &c_list = control_lists[i];
c_list.size = serializer_.binarySize(list);
ByteStreamBuffer b = byteStreamBuffer.carveOut(c_list.size);
serializer_.serialize(list, b);
c_list.data = b.base();
}
ctx_->ops->process_event(ctx_, &c_data);
}
void IPAContextWrapper::queue_frame_action(void *ctx, unsigned int frame,
struct ipa_operation_data &data)
{
IPAContextWrapper *_this = static_cast<IPAContextWrapper *>(ctx);
IPAOperationData opData;
opData.operation = data.operation;
for (unsigned int i = 0; i < data.num_data; ++i)
opData.data.push_back(data.data[i]);
for (unsigned int i = 0; i < data.num_lists; ++i) {
const struct ipa_control_list &c_list = data.lists[i];
ByteStreamBuffer b(c_list.data, c_list.size);
opData.controls.push_back(_this->serializer_.deserialize<ControlList>(b));
}
_this->queueFrameAction.emit(frame, opData);
}
#ifndef __DOXYGEN__
/*
* This construct confuses Doygen and makes it believe that all members of the
* operations is a member of IPAInterfaceWrapper. It must thus be hidden.
*/
const struct ipa_callback_ops IPAContextWrapper::callbacks_ = {
.queue_frame_action = &IPAContextWrapper::queue_frame_action,
};
#endif
} /* namespace libcamera */
+66 -1
View File
@@ -12,6 +12,7 @@
#include <string.h>
#include <sys/types.h>
#include "ipa_context_wrapper.h"
#include "ipa_module.h"
#include "ipa_proxy.h"
#include "log.h"
@@ -30,6 +31,66 @@ LOG_DEFINE_CATEGORY(IPAManager)
/**
* \class IPAManager
* \brief Manager for IPA modules
*
* The IPA module manager discovers IPA modules from disk, queries and loads
* them, and creates IPA contexts. It supports isolation of the modules in a
* separate process with IPC communication and offers a unified IPAInterface
* view of the IPA contexts to pipeline handlers regardless of whether the
* modules are isolated or loaded in the same process.
*
* Module isolation is based on the module licence. Open-source modules are
* loaded without isolation, while closed-source module are forcefully isolated.
* The isolation mechanism ensures that no code from a closed-source module is
* ever run in the libcamera process.
*
* To create an IPA context, pipeline handlers call the IPAManager::ipaCreate()
* method. For a directly loaded module, the manager calls the module's
* ipaCreate() function directly and wraps the returned context in an
* IPAContextWrapper that exposes an IPAInterface.
*
* ~~~~
* +---------------+
* | Pipeline |
* | Handler |
* +---------------+
* |
* v
* +---------------+ +---------------+
* | IPA | | Open Source |
* | Interface | | IPA Module |
* | - - - - - - - | | - - - - - - - |
* | IPA Context | ipa_context_ops | ipa_context |
* | Wrapper | ----------------> | |
* +---------------+ +---------------+
* ~~~~
*
* For an isolated module, the manager instantiates an IPAProxy which spawns a
* new process for an IPA proxy worker. The worker loads the IPA module and
* creates the IPA context. The IPAProxy alse exposes an IPAInterface.
*
* ~~~~
* +---------------+ +---------------+
* | Pipeline | | Closed Source |
* | Handler | | IPA Module |
* +---------------+ | - - - - - - - |
* | | ipa_context |
* v | |
* +---------------+ +---------------+
* | IPA | ipa_context_ops ^
* | Interface | |
* | - - - - - - - | +---------------+
* | IPA Proxy | operations | IPA Proxy |
* | | ----------------> | Worker |
* +---------------+ over IPC +---------------+
* ~~~~
*
* The IPAInterface implemented by the IPAContextWrapper or IPAProxy is
* returned to the pipeline handler, and all interactions with the IPA context
* go the same interface regardless of process isolation.
*
* In all cases the data passed to the IPAInterface methods is serialized to
* Plain Old Data, either for the purpose of passing it to the IPA context
* plain C API, or to transmit the data to the isolated process through IPC.
*/
IPAManager::IPAManager()
@@ -199,7 +260,11 @@ std::unique_ptr<IPAInterface> IPAManager::createIPA(PipelineHandler *pipe,
if (!m->load())
return nullptr;
return m->createInstance();
struct ipa_context *ctx = m->createContext();
if (!ctx)
return nullptr;
return utils::make_unique<IPAContextWrapper>(ctx);
}
} /* namespace libcamera */
+12 -11
View File
@@ -385,13 +385,13 @@ const std::string &IPAModule::path() const
/**
* \brief Load the IPA implementation factory from the shared object
*
* The IPA module shared object implements an IPAInterface class to be used
* The IPA module shared object implements an ipa_context object to be used
* by pipeline handlers. This method loads the factory function from the
* shared object. Later, createInstance() can be called to instantiate the
* IPAInterface.
* shared object. Later, createContext() can be called to instantiate the
* ipa_context.
*
* This method only needs to be called successfully once, after which
* createInstance() can be called as many times as IPAInterface instances are
* createContext() can be called as many times as ipa_context instances are
* needed.
*
* Calling this function on an invalid module (as returned by isValid()) is
@@ -433,24 +433,25 @@ bool IPAModule::load()
}
/**
* \brief Instantiate an IPAInterface
* \brief Instantiate an IPA context
*
* After loading the IPA module with load(), this method creates an
* instance of the IPA module interface.
* After loading the IPA module with load(), this method creates an instance of
* the IPA module context. Ownership of the context is passed to the caller, and
* the context shall be destroyed by calling the \ref ipa_context_ops::destroy
* "ipa_context::ops::destroy()" function.
*
* Calling this function on a module that has not yet been loaded, or an
* invalid module (as returned by load() and isValid(), respectively) is
* an error.
*
* \return The IPA implementation as a new IPAInterface instance on success,
* or nullptr on error
* \return The IPA context on success, or nullptr on error
*/
std::unique_ptr<IPAInterface> IPAModule::createInstance()
struct ipa_context *IPAModule::createContext()
{
if (!valid_ || !loaded_)
return nullptr;
return std::unique_ptr<IPAInterface>(ipaCreate_());
return ipaCreate_();
}
/**
+1
View File
@@ -16,6 +16,7 @@ libcamera_sources = files([
'event_notifier.cpp',
'formats.cpp',
'geometry.cpp',
'ipa_context_wrapper.cpp',
'ipa_controls.cpp',
'ipa_interface.cpp',
'ipa_manager.cpp',
@@ -72,9 +72,9 @@ int main(int argc, char **argv)
}
socket.readyRead.connect(&readyRead);
std::unique_ptr<IPAInterface> ipa = ipam->createInstance();
if (!ipa) {
LOG(IPAProxyLinuxWorker, Error) << "Failed to create IPA interface";
struct ipa_context *ipac = ipam->createContext();
if (!ipac) {
LOG(IPAProxyLinuxWorker, Error) << "Failed to create IPA context";
return EXIT_FAILURE;
}
@@ -85,5 +85,7 @@ int main(int argc, char **argv)
while (1)
dispatcher->processEvents();
ipac->ops->destroy(ipac);
return 0;
}