linux-pipewire: Collect device controls

Collect the controls on the node. Enumerate the controls as
properties.

Co-authored-by: Georges Basile Stavracas Neto
<georges.stavracas@gmail.com>
This commit is contained in:
Wim Taymans 2022-10-28 20:55:45 +02:00 committed by Georges Basile Stavracas Neto
parent e6d98ebfbb
commit 07cbbe9bec
2 changed files with 400 additions and 6 deletions

View file

@ -19,6 +19,8 @@
*/
#include "pipewire.h"
#include "formats.h"
#include "portal.h"
#include <util/dstr.h>
@ -30,8 +32,11 @@
#include <spa/debug/dict.h>
#include <spa/node/keys.h>
#include <spa/pod/iter.h>
#include <spa/pod/parser.h>
#include <spa/param/props.h>
#include <spa/utils/defs.h>
#include <spa/utils/keys.h>
#include <spa/utils/result.h>
struct camera_portal_source {
obs_source_t *source;
@ -118,8 +123,102 @@ struct camera_device {
struct pw_properties *properties;
struct pw_proxy *proxy;
struct spa_hook proxy_listener;
struct pw_node *node;
struct spa_hook node_listener;
struct pw_node_info *info;
uint32_t changed;
struct spa_list pending_list;
struct spa_list param_list;
int pending_sync;
};
struct param {
uint32_t id;
int32_t seq;
struct spa_list link;
struct spa_pod *param;
};
static uint32_t clear_params(struct spa_list *param_list, uint32_t id)
{
struct param *p, *t;
uint32_t count = 0;
spa_list_for_each_safe(p, t, param_list, link)
{
if (id == SPA_ID_INVALID || p->id == id) {
spa_list_remove(&p->link);
free(p);
count++;
}
}
return count;
}
static struct param *add_param(struct spa_list *params, int seq, uint32_t id,
const struct spa_pod *param)
{
struct param *p;
if (id == SPA_ID_INVALID) {
if (param == NULL || !spa_pod_is_object(param)) {
errno = EINVAL;
return NULL;
}
id = SPA_POD_OBJECT_ID(param);
}
p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0));
if (p == NULL)
return NULL;
p->id = id;
p->seq = seq;
if (param != NULL) {
p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod);
memcpy(p->param, param, SPA_POD_SIZE(param));
} else {
clear_params(params, id);
p->param = NULL;
}
spa_list_append(params, &p->link);
return p;
}
static void object_update_params(struct spa_list *param_list,
struct spa_list *pending_list,
uint32_t n_params,
struct spa_param_info *params)
{
struct param *p, *t;
uint32_t i;
for (i = 0; i < n_params; i++) {
spa_list_for_each_safe(p, t, pending_list, link)
{
if (p->id == params[i].id && p->seq != params[i].seq &&
p->param != NULL) {
spa_list_remove(&p->link);
free(p);
}
}
}
spa_list_consume(p, pending_list, link)
{
spa_list_remove(&p->link);
if (p->param == NULL) {
clear_params(param_list, p->id);
free(p);
} else {
spa_list_append(param_list, &p->link);
}
}
}
static struct camera_device *
camera_device_new(uint32_t id, const struct spa_dict *properties)
{
@ -127,6 +226,8 @@ camera_device_new(uint32_t id, const struct spa_dict *properties)
device->id = id;
device->properties = properties ? pw_properties_new_dict(properties)
: NULL;
spa_list_init(&device->pending_list);
spa_list_init(&device->param_list);
return device;
}
@ -135,6 +236,8 @@ static void camera_device_free(struct camera_device *device)
if (!device)
return;
clear_params(&device->pending_list, SPA_ID_INVALID);
clear_params(&device->param_list, SPA_ID_INVALID);
g_clear_pointer(&device->proxy, pw_proxy_destroy);
g_clear_pointer(&device->properties, pw_properties_free);
bfree(device);
@ -183,6 +286,140 @@ static void stream_camera(struct camera_portal_source *camera_source)
&connect_info);
}
static void camera_format_list(struct camera_device *dev, obs_property_t *prop)
{
struct param *p;
enum video_format last_format = VIDEO_FORMAT_NONE;
obs_property_list_clear(prop);
spa_list_for_each(p, &dev->param_list, link)
{
struct obs_pw_video_format obs_pw_video_format;
uint32_t media_type, media_subtype, format;
if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
continue;
if (spa_format_parse(p->param, &media_type, &media_subtype) < 0)
continue;
if (media_type != SPA_MEDIA_TYPE_video)
continue;
if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
if (spa_pod_parse_object(p->param,
SPA_TYPE_OBJECT_Format, NULL,
SPA_FORMAT_VIDEO_format,
SPA_POD_Id(&format)) < 0)
continue;
} else {
format = SPA_VIDEO_FORMAT_ENCODED;
}
if (!obs_pw_video_format_from_spa_format(format,
&obs_pw_video_format))
continue;
if (obs_pw_video_format.video_format == last_format)
continue;
last_format = obs_pw_video_format.video_format;
obs_property_list_add_int(prop, obs_pw_video_format.pretty_name,
format);
}
}
static inline void add_control_property(obs_properties_t *props,
obs_data_t *settings,
struct camera_device *dev,
struct param *p)
{
UNUSED_PARAMETER(dev);
const struct spa_pod *type, *pod, *labels = NULL;
uint32_t n_vals, choice, container = SPA_ID_INVALID;
obs_property_t *prop = NULL;
const char *name;
if (spa_pod_parse_object(
p->param, SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_description, SPA_POD_OPT_String(&name),
SPA_PROP_INFO_type, SPA_POD_PodChoice(&type),
SPA_PROP_INFO_container, SPA_POD_OPT_Id(&container),
SPA_PROP_INFO_labels, SPA_POD_OPT_PodStruct(&labels)) < 0)
return;
pod = spa_pod_get_values(type, &n_vals, &choice);
container = container != SPA_ID_INVALID ? container : SPA_POD_TYPE(pod);
switch (SPA_POD_TYPE(pod)) {
case SPA_TYPE_Int: {
int32_t *vals = SPA_POD_BODY(pod);
if (n_vals < 1)
return;
if (choice == SPA_CHOICE_Enum) {
struct spa_pod_parser prs;
struct spa_pod_frame f;
if (labels == NULL)
return;
prop = obs_properties_add_list(props, (char *)name,
(char *)name,
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
spa_pod_parser_pod(&prs, (struct spa_pod *)labels);
if (spa_pod_parser_push_struct(&prs, &f) < 0)
return;
while (1) {
int32_t id;
const char *desc;
if (spa_pod_parser_get_int(&prs, &id) < 0 ||
spa_pod_parser_get_string(&prs, &desc) < 0)
break;
obs_property_list_add_int(prop, (char *)desc,
id);
}
} else {
prop = obs_properties_add_int_slider(
props, (char *)name, (char *)name,
n_vals > 1 ? vals[1] : vals[0],
n_vals > 2 ? vals[2] : vals[0],
n_vals > 3 ? vals[3] : 1);
}
obs_data_set_default_int(settings, (char *)name, vals[0]);
break;
}
case SPA_TYPE_Bool: {
int32_t *vals = SPA_POD_BODY(pod);
if (n_vals < 1)
return;
prop = obs_properties_add_bool(props, (char *)name,
(char *)name);
obs_data_set_default_bool(settings, (char *)name, vals[0]);
break;
}
default:
break;
}
}
static void camera_update_controls(struct camera_device *dev,
obs_properties_t *props,
obs_data_t *settings)
{
struct param *p;
spa_list_for_each(p, &dev->param_list, link)
{
if (p->id != SPA_PARAM_PropInfo || p->param == NULL)
continue;
add_control_property(props, settings, dev, p);
}
}
static bool device_selected(void *data, obs_properties_t *props,
obs_property_t *property, obs_data_t *settings)
{
@ -191,12 +428,70 @@ static bool device_selected(void *data, obs_properties_t *props,
struct camera_portal_source *camera_source = data;
const char *device_id;
struct camera_device *device;
obs_properties_t *new_control_properties;
device_id = obs_data_get_string(settings, "device_id");
blog(LOG_INFO, "[camera-portal] selected camera '%s'", device_id);
device = g_hash_table_lookup(connection->devices, device_id);
if (device == NULL)
return false;
if (update_device_id(camera_source, device_id))
stream_camera(camera_source);
blog(LOG_INFO, "[camera-portal] Updating pixel formats");
property = obs_properties_get(props, "pixelformat");
new_control_properties = obs_properties_create();
obs_properties_remove_by_name(props, "controls");
camera_format_list(device, property);
camera_update_controls(device, new_control_properties, settings);
obs_properties_add_group(props, "controls",
obs_module_text("CameraControls"),
OBS_GROUP_NORMAL, new_control_properties);
obs_property_modified(property, settings);
return true;
}
/*
* Format selected callback
*/
static bool format_selected(void *data, obs_properties_t *properties,
obs_property_t *property, obs_data_t *settings)
{
UNUSED_PARAMETER(properties);
UNUSED_PARAMETER(property);
UNUSED_PARAMETER(settings);
struct camera_portal_source *camera_source = data;
blog(LOG_INFO, "[camera-portal] Selected format for '%s'",
camera_source->device_id);
return true;
}
/*
* Resolution selected callback
*/
static bool resolution_selected(void *data, obs_properties_t *properties,
obs_property_t *property, obs_data_t *settings)
{
UNUSED_PARAMETER(properties);
UNUSED_PARAMETER(property);
UNUSED_PARAMETER(settings);
struct camera_portal_source *camera_source = data;
blog(LOG_INFO, "[camera-portal] Selected resolution for '%s'",
camera_source->device_id);
return true;
}
@ -240,6 +535,60 @@ static void populate_cameras_list(struct camera_portal_source *camera_source,
/* ------------------------------------------------- */
static void node_info(void *data, const struct pw_node_info *info)
{
struct camera_device *device = data;
uint32_t i, changed = 0;
int res;
info = device->info = pw_node_info_update(device->info, info);
if (info == NULL)
return;
if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
for (i = 0; i < info->n_params; i++) {
uint32_t id = info->params[i].id;
if (info->params[i].user == 0)
continue;
info->params[i].user = 0;
changed++;
add_param(&device->pending_list, 0, id, NULL);
if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
continue;
res = pw_node_enum_params(
(struct pw_node *)device->proxy,
++info->params[i].seq, id, 0, -1, NULL);
if (SPA_RESULT_IS_ASYNC(res))
info->params[i].seq = res;
}
}
if (changed) {
device->changed += changed;
device->pending_sync =
pw_proxy_sync(device->proxy, device->pending_sync);
}
}
static void node_param(void *data, int seq, uint32_t id, uint32_t index,
uint32_t next, const struct spa_pod *param)
{
UNUSED_PARAMETER(index);
UNUSED_PARAMETER(next);
struct camera_device *device = data;
add_param(&device->pending_list, seq, id, param);
}
static const struct pw_node_events node_events = {
PW_VERSION_NODE_EVENTS,
.info = node_info,
.param = node_param,
};
static void on_proxy_removed_cb(void *data)
{
struct camera_device *device = data;
@ -254,11 +603,21 @@ static void on_destroy_proxy_cb(void *data)
device->proxy = NULL;
}
static void on_done_proxy_cb(void *data, int seq)
{
struct camera_device *device = data;
if (device->info != NULL && device->pending_sync == seq) {
object_update_params(&device->param_list, &device->pending_list,
device->info->n_params,
device->info->params);
}
}
static const struct pw_proxy_events proxy_events = {
PW_VERSION_PROXY_EVENTS,
.removed = on_proxy_removed_cb,
.destroy = on_destroy_proxy_cb,
.done = on_done_proxy_cb,
};
static void on_registry_global_cb(void *user_data, uint32_t id,
@ -292,6 +651,9 @@ static void on_registry_global_cb(void *user_data, uint32_t id,
}
pw_proxy_add_listener(device->proxy, &device->proxy_listener,
&proxy_events, device);
device->node = (struct pw_node *)device->proxy;
pw_node_add_listener(device->node, &device->node_listener, &node_events,
device);
g_hash_table_insert(connection->devices, bstrdup(device_id), device);
@ -511,22 +873,48 @@ static void pipewire_camera_get_defaults(obs_data_t *settings)
static obs_properties_t *pipewire_camera_get_properties(void *data)
{
struct camera_portal_source *camera_source = data;
obs_properties_t *properties;
obs_properties_t *controls_props;
obs_properties_t *props;
obs_property_t *resolution_list;
obs_property_t *device_list;
obs_property_t *format_list;
properties = obs_properties_create();
props = obs_properties_create();
device_list = obs_properties_add_list(
properties, "device_id",
obs_module_text("PipeWireCameraDevice"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
props, "device_id", obs_module_text("PipeWireCameraDevice"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
format_list = obs_properties_add_list(props, "pixelformat",
obs_module_text("VideoFormat"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
resolution_list = obs_properties_add_list(props, "resolution",
obs_module_text("Resolution"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_properties_add_list(props, "framerate",
obs_module_text("FrameRate"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
// a group to contain the camera control
controls_props = obs_properties_create();
obs_properties_add_group(props, "controls",
obs_module_text("CameraControls"),
OBS_GROUP_NORMAL, controls_props);
populate_cameras_list(camera_source, device_list);
obs_property_set_modified_callback2(device_list, device_selected,
camera_source);
obs_property_set_modified_callback2(format_list, format_selected,
camera_source);
obs_property_set_modified_callback2(resolution_list,
resolution_selected, camera_source);
return properties;
return props;
}
static void pipewire_camera_update(void *data, obs_data_t *settings)
@ -536,6 +924,8 @@ static void pipewire_camera_update(void *data, obs_data_t *settings)
device_id = obs_data_get_string(settings, "device_id");
blog(LOG_INFO, "[camera-portal] Updating device %s", device_id);
if (update_device_id(camera_source, device_id))
stream_camera(camera_source);
}

View file

@ -1,7 +1,11 @@
CameraControls="Camera Controls"
FrameRate="Frame Rate"
PipeWireCamera="Video Capture Device (PipeWire) (BETA)"
PipeWireCameraDevice="Device"
PipeWireDesktopCapture="Screen Capture (PipeWire)"
PipeWireSelectMonitor="Select Monitor"
PipeWireSelectWindow="Select Window"
PipeWireWindowCapture="Window Capture (PipeWire)"
Resolution="Resolution"
ShowCursor="Show Cursor"
VideoFormat="Video Format"