From 36e8f1553fe063624efdd7cbe78aa13b42e1b9ac Mon Sep 17 00:00:00 2001 From: "van Veen, Stephan" Date: Wed, 7 May 2025 12:02:53 +0200 Subject: [PATCH] propagate libcamera control as pipewire properties So far the libcamera plugin only offers device and device name as properties. To be able to control camera parameters this patch maps libcamera controls onto properties. Upstream-Status: Pending --- spa/plugins/libcamera/libcamera-source.cpp | 109 +++++++++++++++++++-- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 08f92c5d7..fd21aff4d 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include @@ -121,6 +123,7 @@ struct impl { struct spa_node node = {}; struct spa_log *log; + struct spa_loop *main_loop; struct spa_loop *data_loop; struct spa_system *system; @@ -161,10 +164,12 @@ struct impl { ControlList ctrls; ControlList initial_controls; + ControlList request_metadata; + std::mutex request_metadata_mutex; bool active = false; bool acquired = false; - impl(spa_log *log, spa_loop *data_loop, spa_system *system, + impl(spa_log *log, spa_loop *main_loop, spa_loop *data_loop, spa_system *system, std::shared_ptr manager, std::shared_ptr camera, std::unique_ptr config); @@ -1037,6 +1042,23 @@ int spa_libcamera_apply_controls(struct impl *impl, libcamera::ControlList&& con ); } +void emit_node_info(struct impl *impl, bool full); + +static int do_emit_node_info(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *impl = (struct impl *)user_data; + + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + impl->params[NODE_Props].flags ^= SPA_PARAM_INFO_SERIAL; + emit_node_info(impl, true); + return 0; +} + void handle_completed_request(struct impl *impl, libcamera::Request *request) { const auto request_id = request->cookie(); @@ -1111,6 +1133,34 @@ void handle_completed_request(struct impl *impl, libcamera::Request *request) d->chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED; } + // update stored metadata from request metadata + { + const auto lock_guard = std::lock_guard{impl->request_metadata_mutex}; + + auto update = false; + for (const auto& [id, value] : request->metadata()) + { + const auto prop_id = control_to_prop_id(id); + if (prop_id >= SPA_PROP_START_CUSTOM) + { + continue; + } + const auto it = std::find_if(impl->request_metadata.begin(), impl->request_metadata.end(), [id](const auto& entry) + { + return id == entry.first; + }); + if ((it == impl->request_metadata.end()) || (value != it->second)) + { + impl->request_metadata.set(id, value); + update = true; + } + } + if (update) + { + spa_loop_invoke(impl->main_loop, do_emit_node_info, 0, nullptr, 0, false, impl); + } + } + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); spa_list_append(&port->queue, &b->link); @@ -1305,7 +1355,6 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, return 0; } - void impl::requestComplete(libcamera::Request *request) { struct impl *impl = this; @@ -1451,7 +1500,8 @@ int impl_node_enum_params(void *object, int seq, { struct impl *impl = (struct impl*)object; struct spa_pod *param; - struct spa_pod_builder b = { 0 }; + spa_auto(spa_pod_dynamic_builder) b = { 0 }; + struct spa_pod_builder_state state; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; @@ -1460,12 +1510,15 @@ int impl_node_enum_params(void *object, int seq, spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_builder_get_state(&b.b, &state); + result.id = id; result.next = start; next: result.index = result.next++; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_PropInfo: @@ -1481,6 +1534,39 @@ next: case SPA_PARAM_Props: { switch (result.index) { + case 0: { + const auto add_property = [&b, &impl](unsigned int controlId, const libcamera::ControlValue& value) { + const auto id = control_to_prop_id(controlId); + if (id >= SPA_PROP_START_CUSTOM) { + return; + } + if (value.isArray()) { + return; // not yet supported + } + spa_pod_builder_prop(&b.b, id, 0); + control_value_to_pod(b.b, value); + }; + + struct spa_pod_frame f; + spa_pod_builder_push_object(&b.b, &f, SPA_TYPE_OBJECT_Props, id); + const auto metadata = [&]() + { + const auto lock_guard = std::lock_guard{impl->request_metadata_mutex}; + auto metadata = impl->request_metadata; + return metadata; + }(); + for (const auto& [id, value] : metadata) + { + add_property(id, value); + } + for (const auto& [id, info] : impl->camera->controls()) { + if (!metadata.contains(id->id())) { + add_property(id->id(), info.def()); + } + } + param = (spa_pod*)spa_pod_builder_pop(&b.b, &f); + } + break; default: return 0; } @@ -1490,14 +1576,14 @@ next: return spa_libcamera_enum_format(impl, GET_OUT_PORT(impl, 0), seq, start, num, filter); case SPA_PARAM_Format: - if ((res = port_get_format(impl, GET_OUT_PORT(impl, 0), result.index, filter, ¶m, &b)) <= 0) + if ((res = port_get_format(impl, GET_OUT_PORT(impl, 0), result.index, filter, ¶m, &b.b)) <= 0) return res; break; default: return -ENOENT; } - if (spa_pod_filter(&b, &result.param, param, filter) < 0) + if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -2144,11 +2230,12 @@ int impl_clear(struct spa_handle *handle) return 0; } -impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, +impl::impl(spa_log *log, spa_loop *main_loop, spa_loop *data_loop, spa_system *system, std::shared_ptr manager, std::shared_ptr camera, std::unique_ptr config) : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), log(log), + main_loop(main_loop), data_loop(data_loop), system(system), out_ports{{this}}, @@ -2200,9 +2287,15 @@ impl_init(const struct spa_handle_factory *factory, spa_return_val_if_fail(handle != nullptr, -EINVAL); auto log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); + auto main_loop = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop)); auto data_loop = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop)); auto system = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System)); + if (!main_loop) { + spa_log_error(log, "a main_loop is needed"); + return -EINVAL; + } + if (!data_loop) { spa_log_error(log, "a data_loop is needed"); return -EINVAL; @@ -2235,7 +2328,7 @@ impl_init(const struct spa_handle_factory *factory, return -EINVAL; } - new (handle) impl(log, data_loop, system, + new (handle) impl(log, main_loop, data_loop, system, std::move(manager), std::move(camera), std::move(config));