2023-02-08 18:12:00 +01:00
|
|
|
/* Spa libcamera manager */
|
|
|
|
|
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
|
|
|
|
|
/* SPDX-License-Identifier: MIT */
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2025-07-12 17:45:50 +02:00
|
|
|
#include <cstddef>
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
#include <cinttypes>
|
2022-09-09 17:06:09 +02:00
|
|
|
#include <utility>
|
2022-09-09 19:00:29 +02:00
|
|
|
#include <mutex>
|
|
|
|
|
#include <optional>
|
|
|
|
|
#include <queue>
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
#include <libcamera/camera.h>
|
|
|
|
|
#include <libcamera/camera_manager.h>
|
|
|
|
|
|
|
|
|
|
using namespace libcamera;
|
|
|
|
|
|
|
|
|
|
#include <spa/support/log.h>
|
|
|
|
|
#include <spa/utils/type.h>
|
|
|
|
|
#include <spa/utils/keys.h>
|
|
|
|
|
#include <spa/utils/names.h>
|
|
|
|
|
#include <spa/utils/result.h>
|
|
|
|
|
#include <spa/utils/string.h>
|
|
|
|
|
#include <spa/support/loop.h>
|
|
|
|
|
#include <spa/support/plugin.h>
|
|
|
|
|
#include <spa/monitor/device.h>
|
|
|
|
|
#include <spa/monitor/utils.h>
|
|
|
|
|
|
|
|
|
|
#include "libcamera.h"
|
|
|
|
|
#include "libcamera-manager.hpp"
|
|
|
|
|
|
2022-09-03 20:27:10 +02:00
|
|
|
namespace {
|
|
|
|
|
|
2021-11-02 17:24:19 +01:00
|
|
|
struct device {
|
|
|
|
|
std::shared_ptr<Camera> camera;
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-09 18:50:58 +02:00
|
|
|
struct impl {
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
static constexpr std::size_t max_devices = 64;
|
|
|
|
|
|
2021-11-02 17:24:19 +01:00
|
|
|
struct spa_handle handle;
|
2022-09-03 20:27:10 +02:00
|
|
|
struct spa_device device = {};
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
struct spa_log *log;
|
2022-09-09 19:00:29 +02:00
|
|
|
struct spa_loop_utils *loop_utils;
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
struct spa_hook_list hooks;
|
|
|
|
|
|
2022-09-03 20:27:10 +02:00
|
|
|
static constexpr uint64_t info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS;
|
|
|
|
|
struct spa_device_info info = SPA_DEVICE_INFO_INIT();
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2022-09-03 22:10:50 +02:00
|
|
|
std::shared_ptr<CameraManager> manager;
|
2021-11-02 17:24:19 +01:00
|
|
|
void addCamera(std::shared_ptr<libcamera::Camera> camera);
|
2022-09-03 19:03:11 +02:00
|
|
|
void removeCamera(std::shared_ptr<libcamera::Camera> camera);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
struct device devices[max_devices];
|
2022-09-03 20:27:10 +02:00
|
|
|
|
2022-09-09 19:00:29 +02:00
|
|
|
struct hotplug_event {
|
|
|
|
|
enum class type { add, remove } type;
|
|
|
|
|
std::shared_ptr<Camera> camera;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::mutex hotplug_events_lock;
|
|
|
|
|
std::queue<hotplug_event> hotplug_events;
|
|
|
|
|
struct spa_source *hotplug_event_source;
|
|
|
|
|
|
|
|
|
|
impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source);
|
|
|
|
|
|
|
|
|
|
~impl()
|
|
|
|
|
{
|
|
|
|
|
spa_loop_utils_destroy_source(loop_utils, hotplug_event_source);
|
|
|
|
|
}
|
2021-11-02 17:24:19 +01:00
|
|
|
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
std::uint32_t id_of(const struct device& d) const
|
|
|
|
|
{
|
|
|
|
|
spa_assert(std::begin(devices) <= &d && &d < std::end(devices));
|
|
|
|
|
return &d - std::begin(devices);
|
|
|
|
|
}
|
2025-07-29 17:26:44 +02:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void queue_hotplug_event(enum hotplug_event::type type, std::shared_ptr<libcamera::Camera>&& camera)
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard guard(hotplug_events_lock);
|
|
|
|
|
hotplug_events.push({ type, std::move(camera) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spa_loop_utils_signal_event(loop_utils, hotplug_event_source);
|
|
|
|
|
}
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
};
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
struct device *add_device(struct impl *impl, std::shared_ptr<Camera> camera)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
for (auto& d : impl->devices) {
|
|
|
|
|
if (!d.camera) {
|
|
|
|
|
d.camera = std::move(camera);
|
|
|
|
|
return &d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
struct device *find_device(struct impl *impl, const Camera *camera)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
for (auto& d : impl->devices) {
|
|
|
|
|
if (d.camera.get() == camera)
|
|
|
|
|
return &d;
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
|
2025-07-12 19:29:04 +02:00
|
|
|
return nullptr;
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void remove_device(struct impl *impl, struct device *device)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
*device = {};
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void clear_devices(struct impl *impl)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
for (auto& d : impl->devices)
|
|
|
|
|
d = {};
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
int emit_object_info(struct impl *impl, const struct device *device)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
|
|
|
|
struct spa_device_object_info info;
|
|
|
|
|
struct spa_dict_item items[20];
|
|
|
|
|
struct spa_dict dict;
|
|
|
|
|
uint32_t n_items = 0;
|
|
|
|
|
|
|
|
|
|
info = SPA_DEVICE_OBJECT_INFO_INIT();
|
|
|
|
|
|
|
|
|
|
info.type = SPA_TYPE_INTERFACE_Device;
|
|
|
|
|
info.factory_name = SPA_NAME_API_LIBCAMERA_DEVICE;
|
|
|
|
|
info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS |
|
|
|
|
|
SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
|
|
|
|
|
info.flags = 0;
|
|
|
|
|
|
|
|
|
|
#define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value)
|
|
|
|
|
ADD_ITEM(SPA_KEY_DEVICE_ENUM_API,"libcamera.manager");
|
|
|
|
|
ADD_ITEM(SPA_KEY_DEVICE_API, "libcamera");
|
|
|
|
|
ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device");
|
2024-03-08 22:48:42 +01:00
|
|
|
ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, device->camera->id().c_str());
|
2021-11-02 17:24:19 +01:00
|
|
|
#undef ADD_ITEM
|
|
|
|
|
|
2022-09-03 19:03:11 +02:00
|
|
|
dict = SPA_DICT_INIT(items, n_items);
|
|
|
|
|
info.props = &dict;
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
spa_device_emit_object_info(&impl->hooks, impl->id_of(*device), &info);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void try_add_camera(struct impl *impl, std::shared_ptr<Camera> camera)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
|
|
|
|
struct device *device;
|
|
|
|
|
|
2025-07-12 19:29:04 +02:00
|
|
|
if ((device = find_device(impl, camera.get())) != nullptr)
|
2021-11-02 17:24:19 +01:00
|
|
|
return;
|
|
|
|
|
|
2025-07-12 19:29:04 +02:00
|
|
|
if ((device = add_device(impl, std::move(camera))) == nullptr)
|
2021-11-02 17:24:19 +01:00
|
|
|
return;
|
|
|
|
|
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
spa_log_info(impl->log, "camera added: id:%" PRIu32 " %s",
|
|
|
|
|
impl->id_of(*device), device->camera->id().c_str());
|
2021-11-02 17:24:19 +01:00
|
|
|
emit_object_info(impl, device);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void try_remove_camera(struct impl *impl, const Camera *camera)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
|
|
|
|
struct device *device;
|
|
|
|
|
|
2025-07-12 19:29:04 +02:00
|
|
|
if ((device = find_device(impl, camera)) == nullptr)
|
2021-11-02 17:24:19 +01:00
|
|
|
return;
|
|
|
|
|
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
auto id = impl->id_of(*device);
|
|
|
|
|
|
|
|
|
|
spa_log_info(impl->log, "camera removed: id:%" PRIu32 " %s",
|
|
|
|
|
id, device->camera->id().c_str());
|
|
|
|
|
spa_device_emit_object_info(&impl->hooks, id, nullptr);
|
2021-11-02 17:24:19 +01:00
|
|
|
remove_device(impl, device);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void consume_hotplug_event(struct impl *impl, impl::hotplug_event& event)
|
2022-09-09 19:00:29 +02:00
|
|
|
{
|
|
|
|
|
auto& [ type, camera ] = event;
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case impl::hotplug_event::type::add:
|
|
|
|
|
spa_log_info(impl->log, "camera appeared: %s", camera->id().c_str());
|
|
|
|
|
try_add_camera(impl, std::move(camera));
|
|
|
|
|
break;
|
|
|
|
|
case impl::hotplug_event::type::remove:
|
|
|
|
|
spa_log_info(impl->log, "camera disappeared: %s", camera->id().c_str());
|
|
|
|
|
try_remove_camera(impl, camera.get());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void on_hotplug_event(void *data, std::uint64_t)
|
2022-09-09 19:00:29 +02:00
|
|
|
{
|
|
|
|
|
auto impl = static_cast<struct impl *>(data);
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
std::optional<impl::hotplug_event> event;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
std::unique_lock guard(impl->hotplug_events_lock);
|
|
|
|
|
|
|
|
|
|
if (!impl->hotplug_events.empty()) {
|
|
|
|
|
event = std::move(impl->hotplug_events.front());
|
|
|
|
|
impl->hotplug_events.pop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!event)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
consume_hotplug_event(impl, *event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void impl::addCamera(std::shared_ptr<Camera> camera)
|
|
|
|
|
{
|
2025-07-29 17:26:44 +02:00
|
|
|
queue_hotplug_event(hotplug_event::type::add, std::move(camera));
|
2022-09-09 19:00:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void impl::removeCamera(std::shared_ptr<Camera> camera)
|
|
|
|
|
{
|
2025-07-29 17:26:44 +02:00
|
|
|
queue_hotplug_event(hotplug_event::type::remove, std::move(camera));
|
2022-09-09 19:00:29 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void start_monitor(struct impl *impl)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
2022-09-09 18:50:58 +02:00
|
|
|
impl->manager->cameraAdded.connect(impl, &impl::addCamera);
|
|
|
|
|
impl->manager->cameraRemoved.connect(impl, &impl::removeCamera);
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
int stop_monitor(struct impl *impl)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
2022-09-03 22:10:50 +02:00
|
|
|
if (impl->manager) {
|
2022-09-09 18:50:58 +02:00
|
|
|
impl->manager->cameraAdded.disconnect(impl, &impl::addCamera);
|
|
|
|
|
impl->manager->cameraRemoved.disconnect(impl, &impl::removeCamera);
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
clear_devices (impl);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void collect_existing_devices(struct impl *impl)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
|
|
|
|
auto cameras = impl->manager->cameras();
|
|
|
|
|
|
2022-09-09 19:00:29 +02:00
|
|
|
for (std::shared_ptr<Camera>& camera : cameras)
|
|
|
|
|
try_add_camera(impl, std::move(camera));
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
const struct spa_dict_item device_info_items[] = {
|
2021-11-02 17:24:19 +01:00
|
|
|
{ SPA_KEY_DEVICE_API, "libcamera" },
|
|
|
|
|
{ SPA_KEY_DEVICE_NICK, "libcamera-manager" },
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void emit_device_info(struct impl *impl, bool full)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
|
|
|
|
uint64_t old = full ? impl->info.change_mask : 0;
|
|
|
|
|
if (full)
|
|
|
|
|
impl->info.change_mask = impl->info_all;
|
|
|
|
|
if (impl->info.change_mask) {
|
|
|
|
|
struct spa_dict dict;
|
|
|
|
|
dict = SPA_DICT_INIT_ARRAY(device_info_items);
|
|
|
|
|
impl->info.props = &dict;
|
|
|
|
|
spa_device_emit_info(&impl->hooks, &impl->info);
|
|
|
|
|
impl->info.change_mask = old;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
void impl_hook_removed(struct spa_hook *hook)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
|
|
|
|
struct impl *impl = (struct impl*)hook->priv;
|
|
|
|
|
if (spa_hook_list_is_empty(&impl->hooks)) {
|
|
|
|
|
stop_monitor(impl);
|
2022-09-03 22:10:50 +02:00
|
|
|
impl->manager.reset();
|
2021-11-02 17:24:19 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
int
|
2021-11-02 17:24:19 +01:00
|
|
|
impl_device_add_listener(void *object, struct spa_hook *listener,
|
|
|
|
|
const struct spa_device_events *events, void *data)
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
struct impl *impl = (struct impl*) object;
|
2022-09-03 19:03:11 +02:00
|
|
|
struct spa_hook_list save;
|
2022-09-09 19:54:43 +02:00
|
|
|
bool had_manager = !!impl->manager;
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2025-07-12 19:29:04 +02:00
|
|
|
spa_return_val_if_fail(impl != nullptr, -EINVAL);
|
|
|
|
|
spa_return_val_if_fail(events != nullptr, -EINVAL);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2022-09-09 19:42:28 +02:00
|
|
|
if (!impl->manager && !(impl->manager = libcamera_manager_acquire(res)))
|
2022-09-03 22:10:50 +02:00
|
|
|
return res;
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2022-09-03 19:03:11 +02:00
|
|
|
spa_hook_list_isolate(&impl->hooks, &save, listener, events, data);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
emit_device_info(impl, true);
|
|
|
|
|
|
2022-09-09 19:54:43 +02:00
|
|
|
if (had_manager) {
|
spa: libcamera: manager: fix id allocation
There is an issue in the id allocation mechanism which can result
in the different devices having the same id. Specifically, consider
the scenario where there are only two cameras, which have just been
added. In this case `impl::devices` looks like this:
(0, camA) | (1, camB) | (?, nullptr) | ...
Now assume that `camA` is removed, after which the array appears
as follows:
(1, camB) | (1, nullptr) | (?, nullptr) | ...
Then assume that a new camera appears. When `get_free_id()` runs,
when `i == 1`, it will observe that `devices[i].camera == nullptr`,
so it selects `1` as the id. Leading to the following:
(1, camB) | (1, camC) | (?, nullptr) | ...
This is of course incorrect. The set of ids must be unique. When
wireplumber is faced with this situation it destroys the device
object for `camB` when `camC` is emitted.
Fix this by simply not moving elements in the `devices` array,
leaving everything where it is. In which case the array looks
like this:
(nullptr) | (camB) | (nullptr) | ... // after `camA` removal
(camC) | (camB) | (nullptr) | ... // after `camC` appearance
Note that `device::id` is removed, and the id is now derived from
the position in `impl::devices`.
2025-07-12 19:38:24 +02:00
|
|
|
for (const auto& d : impl->devices) {
|
|
|
|
|
if (d.camera)
|
|
|
|
|
emit_object_info(impl, &d);
|
|
|
|
|
}
|
2022-09-09 19:54:43 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
collect_existing_devices(impl);
|
|
|
|
|
start_monitor(impl);
|
|
|
|
|
}
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2022-09-03 19:03:11 +02:00
|
|
|
spa_hook_list_join(&impl->hooks, &save);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
listener->removed = impl_hook_removed;
|
|
|
|
|
listener->priv = impl;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
const struct spa_device_methods impl_device = {
|
2024-05-02 03:24:35 +02:00
|
|
|
.version = SPA_VERSION_DEVICE_METHODS,
|
2021-11-02 17:24:19 +01:00
|
|
|
.add_listener = impl_device_add_listener,
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
2025-07-12 19:25:19 +02:00
|
|
|
auto *impl = reinterpret_cast<struct impl *>(handle);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2025-07-12 19:29:04 +02:00
|
|
|
spa_return_val_if_fail(handle != nullptr, -EINVAL);
|
|
|
|
|
spa_return_val_if_fail(interface != nullptr, -EINVAL);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
if (spa_streq(type, SPA_TYPE_INTERFACE_Device))
|
|
|
|
|
*interface = &impl->device;
|
|
|
|
|
else
|
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
int impl_clear(struct spa_handle *handle)
|
2021-11-02 17:24:19 +01:00
|
|
|
{
|
2022-09-03 20:27:10 +02:00
|
|
|
auto impl = reinterpret_cast<struct impl *>(handle);
|
|
|
|
|
|
2021-11-02 17:24:19 +01:00
|
|
|
stop_monitor(impl);
|
2022-09-03 20:27:10 +02:00
|
|
|
std::destroy_at(impl);
|
|
|
|
|
|
2021-11-02 17:24:19 +01:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-09 19:00:29 +02:00
|
|
|
impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source)
|
2022-09-03 20:27:10 +02:00
|
|
|
: handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }),
|
2022-09-09 19:00:29 +02:00
|
|
|
log(log),
|
|
|
|
|
loop_utils(loop_utils),
|
|
|
|
|
hotplug_event_source(hotplug_event_source)
|
2022-09-03 20:27:10 +02:00
|
|
|
{
|
|
|
|
|
libcamera_log_topic_init(log);
|
|
|
|
|
|
|
|
|
|
spa_hook_list_init(&hooks);
|
|
|
|
|
|
|
|
|
|
device.iface = SPA_INTERFACE_INIT(
|
|
|
|
|
SPA_TYPE_INTERFACE_Device,
|
|
|
|
|
SPA_VERSION_DEVICE,
|
|
|
|
|
&impl_device, this);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
size_t
|
2021-11-02 17:24:19 +01:00
|
|
|
impl_get_size(const struct spa_handle_factory *factory,
|
|
|
|
|
const struct spa_dict *params)
|
|
|
|
|
{
|
|
|
|
|
return sizeof(struct impl);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
int
|
2021-11-02 17:24:19 +01:00
|
|
|
impl_init(const struct spa_handle_factory *factory,
|
|
|
|
|
struct spa_handle *handle,
|
|
|
|
|
const struct spa_dict *info,
|
|
|
|
|
const struct spa_support *support,
|
|
|
|
|
uint32_t n_support)
|
|
|
|
|
{
|
2025-07-12 19:29:04 +02:00
|
|
|
spa_return_val_if_fail(factory != nullptr, -EINVAL);
|
|
|
|
|
spa_return_val_if_fail(handle != nullptr, -EINVAL);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2022-09-03 20:27:10 +02:00
|
|
|
auto log = static_cast<spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log));
|
2021-11-02 17:24:19 +01:00
|
|
|
|
2022-09-09 19:00:29 +02:00
|
|
|
auto loop_utils = static_cast<spa_loop_utils *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils));
|
|
|
|
|
if (!loop_utils) {
|
|
|
|
|
spa_log_error(log, "a " SPA_TYPE_INTERFACE_LoopUtils " is needed");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto hotplug_event_source = spa_loop_utils_add_event(loop_utils, on_hotplug_event, handle);
|
|
|
|
|
if (!hotplug_event_source) {
|
|
|
|
|
int res = -errno;
|
|
|
|
|
spa_log_error(log, "failed to create hotplug event: %m");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new (handle) impl(log, loop_utils, hotplug_event_source);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
const struct spa_interface_info impl_interfaces[] = {
|
2021-11-02 17:24:19 +01:00
|
|
|
{SPA_TYPE_INTERFACE_Device,},
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
int
|
2021-11-02 17:24:19 +01:00
|
|
|
impl_enum_interface_info(const struct spa_handle_factory *factory,
|
|
|
|
|
const struct spa_interface_info **info,
|
|
|
|
|
uint32_t *index)
|
|
|
|
|
{
|
2025-07-12 19:29:04 +02:00
|
|
|
spa_return_val_if_fail(factory != nullptr, -EINVAL);
|
|
|
|
|
spa_return_val_if_fail(info != nullptr, -EINVAL);
|
|
|
|
|
spa_return_val_if_fail(index != nullptr, -EINVAL);
|
2021-11-02 17:24:19 +01:00
|
|
|
|
|
|
|
|
if (*index >= SPA_N_ELEMENTS(impl_interfaces))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
*info = &impl_interfaces[(*index)++];
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:18:24 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-02 17:24:19 +01:00
|
|
|
extern "C" {
|
|
|
|
|
const struct spa_handle_factory spa_libcamera_manager_factory = {
|
|
|
|
|
SPA_VERSION_HANDLE_FACTORY,
|
|
|
|
|
SPA_NAME_API_LIBCAMERA_ENUM_MANAGER,
|
2025-07-12 19:29:04 +02:00
|
|
|
nullptr,
|
2021-11-02 17:24:19 +01:00
|
|
|
impl_get_size,
|
|
|
|
|
impl_init,
|
|
|
|
|
impl_enum_interface_info,
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-07-12 19:18:24 +02:00
|
|
|
|
|
|
|
|
std::shared_ptr<CameraManager> libcamera_manager_acquire(int& res)
|
|
|
|
|
{
|
|
|
|
|
static std::weak_ptr<CameraManager> global_manager;
|
|
|
|
|
static std::mutex lock;
|
|
|
|
|
|
|
|
|
|
std::lock_guard guard(lock);
|
|
|
|
|
|
|
|
|
|
if (auto manager = global_manager.lock())
|
|
|
|
|
return manager;
|
|
|
|
|
|
|
|
|
|
auto manager = std::make_shared<CameraManager>();
|
|
|
|
|
if ((res = manager->start()) < 0)
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
global_manager = manager;
|
|
|
|
|
|
|
|
|
|
return manager;
|
|
|
|
|
}
|