mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-07-19 09:39:15 +00:00
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:
parent
e6d98ebfbb
commit
07cbbe9bec
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue