Merge branch 'spa_libcamera_expose_properties' into 'master'

Draft: spa: libcamera: device: expose libcamera camera properties

See merge request pipewire/pipewire!2890
This commit is contained in:
Barnabás Pőcze 2026-07-04 00:05:08 +02:00
commit 7d4e12b740
3 changed files with 172 additions and 15 deletions

View file

@ -665,14 +665,18 @@ SPA_API_JSON int spa_json_parse_string(const char *val, int len, char *result)
return spa_json_parse_stringn(val, len, result, len+1); return spa_json_parse_stringn(val, len, result, len+1);
} }
SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val) SPA_API_JSON size_t spa_json_encode_stringn(char *dst, size_t capacity, const char *src, size_t length)
{ {
int len = 0;
static const char hex[] = { "0123456789abcdef" }; static const char hex[] = { "0123456789abcdef" };
#define __PUT(c) { if (len < size) *str++ = c; len++; }
size_t len = 0;
#define __PUT(c) do { if (len < capacity) *dst++ = c; len++; } while (0)
__PUT('"'); __PUT('"');
while (*val) { for (; length > 0; length--, src++) {
switch (*val) { const char val = *src;
switch (val) {
case '\n': case '\n':
__PUT('\\'); __PUT('n'); __PUT('\\'); __PUT('n');
break; break;
@ -690,19 +694,18 @@ SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val)
break; break;
case '\\': case '\\':
case '"': case '"':
__PUT('\\'); __PUT(*val); __PUT('\\'); __PUT(val);
break; break;
default: default:
if (*val > 0 && *val < 0x20) { if ((unsigned char) val < 0x20) {
__PUT('\\'); __PUT('u'); __PUT('\\'); __PUT('u');
__PUT('0'); __PUT('0'); __PUT('0'); __PUT('0');
__PUT(hex[((*val)>>4)&0xf]); __PUT(hex[(*val)&0xf]); __PUT(hex[(val>>4)&0xf]); __PUT(hex[val&0xf]);
} else { } else {
__PUT(*val); __PUT(val);
} }
break; break;
} }
val++;
} }
__PUT('"'); __PUT('"');
__PUT('\0'); __PUT('\0');
@ -710,6 +713,11 @@ SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val)
return len-1; return len-1;
} }
SPA_API_JSON int spa_json_encode_string(char *dst, int capacity, const char *src)
{
return spa_json_encode_stringn(dst, capacity, src, strlen(src));
}
/** /**
* \} * \}
*/ */

View file

@ -5,12 +5,20 @@
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include <cstddef> #include <cstddef>
#include <ios>
#include <locale>
#include <optional>
#include <span> #include <span>
#include <sstream> #include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <spa/support/plugin.h> #include <spa/support/plugin.h>
#include <spa/support/log.h> #include <spa/support/log.h>
#include <spa/support/loop.h> #include <spa/support/loop.h>
#include <spa/utils/json.h>
#include <spa/utils/keys.h> #include <spa/utils/keys.h>
#include <spa/utils/result.h> #include <spa/utils/result.h>
#include <spa/utils/names.h> #include <spa/utils/names.h>
@ -43,6 +51,8 @@ struct impl {
std::shared_ptr<CameraManager> manager; std::shared_ptr<CameraManager> manager;
std::shared_ptr<Camera> camera; std::shared_ptr<Camera> camera;
std::vector<std::pair<std::string, std::string>> properties;
impl(spa_log *log, impl(spa_log *log,
std::shared_ptr<CameraManager> manager, std::shared_ptr<CameraManager> manager,
std::shared_ptr<Camera> camera, std::shared_ptr<Camera> camera,
@ -96,11 +106,144 @@ const char *cameraRot(const Camera& camera)
return nullptr; return nullptr;
} }
std::optional<std::string>
string_to_json(std::string_view str)
{
std::string result(str.size() + 2, '\0');
auto l = spa_json_encode_stringn(result.data(), result.size(), str.data(), str.size());
if (l > result.size()) {
result.resize(l);
l = spa_json_encode_stringn(result.data(), result.size(), str.data(), str.size());
if (l > result.size())
return {};
}
result.resize(l);
return result;
}
constexpr auto default_control_value_print = [](std::ostream& os, const auto& x) { os << x; };
template<typename T, typename Print = decltype(default_control_value_print)>
[[nodiscard]]
bool
control_value_to_string(std::ostream& os, const libcamera::ControlValue& v, Print print = {})
{
if (v.isArray()) {
os << "[ ";
bool first = true;
for (const auto& x : v.get<libcamera::Span<const T>>()) {
if (!first)
os << ", ";
print(os, x);
first = false;
}
os << " ]";
}
else {
print(os, v.get<T>());
}
return true;
}
[[nodiscard]]
bool
control_value_to_string(std::ostream& os, const libcamera::ControlValue& v,
const libcamera::ControlId *cid)
{
switch (v.type()) {
case libcamera::ControlTypeNone:
os << "null";
return true;
case libcamera::ControlTypeBool:
return control_value_to_string<bool>(os, v);
case libcamera::ControlTypeByte:
return control_value_to_string<uint8_t>(os, v);
case libcamera::ControlTypeUnsigned16:
return control_value_to_string<uint16_t>(os, v);
case libcamera::ControlTypeUnsigned32:
return control_value_to_string<uint32_t>(os, v);
case libcamera::ControlTypeInteger32:
return control_value_to_string<int32_t>(os, v, [&](std::ostream& os, int32_t x) {
if (cid) {
auto it = cid->enumerators().find(x);
if (it != cid->enumerators().end()) {
auto str = string_to_json(it->second);
if (str) {
os << *str;
return;
}
}
}
os << x;
});
case libcamera::ControlTypeInteger64:
return control_value_to_string<int64_t>(os, v);
case libcamera::ControlTypeFloat:
return control_value_to_string<float>(os, v);
case libcamera::ControlTypeString: {
auto str = string_to_json(v.get<std::string_view>());
if (!str)
return false;
os << *str;
return true;
}
}
return false;
}
[[nodiscard]]
std::vector<std::pair<std::string, std::string>>
control_list_to_dict(const libcamera::ControlList& list, std::string_view prefix)
{
static const std::locale c_locale("C");
std::vector<std::pair<std::string, std::string>> res;
const auto *id_map = list.idMap();
std::ostringstream value_s;
std::ostringstream key_s;
spa_assert(id_map);
key_s.imbue(c_locale);
value_s.imbue(c_locale);
value_s << std::boolalpha;
res.reserve(list.size());
for (const auto& [ id, value ] : list) {
auto it = id_map->find(id);
const auto *cid = it != id_map->end() ? it->second : nullptr;
if (!control_value_to_string(value_s, value, cid))
continue;
key_s << prefix << '.';
if (cid)
key_s << cid->vendor() << '.' << cid->name();
else
key_s << id;
res.emplace_back(std::move(key_s).str(), std::move(value_s).str());
}
return res;
}
int emit_info(struct impl *impl, bool full) int emit_info(struct impl *impl, bool full)
{ {
struct spa_dict_item items[12];
struct spa_dict dict; struct spa_dict dict;
uint32_t n_items = 0;
struct spa_device_info info; struct spa_device_info info;
Camera& camera = *impl->camera; Camera& camera = *impl->camera;
@ -108,7 +251,8 @@ int emit_info(struct impl *impl, bool full)
info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS;
#define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) std::vector<spa_dict_item> items;
#define ADD_ITEM(key, value) items.push_back({ key, value })
const auto path = "libcamera:" + impl->device_id; const auto path = "libcamera:" + impl->device_id;
ADD_ITEM(SPA_KEY_OBJECT_PATH, path.c_str()); ADD_ITEM(SPA_KEY_OBJECT_PATH, path.c_str());
@ -149,7 +293,10 @@ int emit_info(struct impl *impl, bool full)
#undef ADD_ITEM #undef ADD_ITEM
dict = SPA_DICT_INIT(items, n_items); for (const auto& [ k, v ] : impl->properties)
items.push_back({ k.c_str(), v.c_str() });
dict = SPA_DICT_INIT(items.data(), uint32_t(items.size()));
info.props = &dict; info.props = &dict;
spa_device_emit_info(&impl->hooks, &info); spa_device_emit_info(&impl->hooks, &info);
@ -252,7 +399,8 @@ impl::impl(spa_log *log,
log(log), log(log),
device_id(std::move(device_id)), device_id(std::move(device_id)),
manager(std::move(manager)), manager(std::move(manager)),
camera(std::move(camera)) camera(std::move(camera)),
properties(control_list_to_dict(this->camera->properties(), KEY_PROPERTY_PREFIX))
{ {
libcamera_log_topic_init(log); libcamera_log_topic_init(log);

View file

@ -14,6 +14,7 @@
#define KEY_VERSION_LIBRARY "api.libcamera.version.library" #define KEY_VERSION_LIBRARY "api.libcamera.version.library"
#define KEY_VERSION_HEADER "api.libcamera.version.header" #define KEY_VERSION_HEADER "api.libcamera.version.header"
#define KEY_PROPERTY_PREFIX "api.libcamera.property"
extern "C" { extern "C" {