Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linux-pipewire: Use list-based format selector for video capture #11741

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 117 additions & 141 deletions plugins/linux-pipewire/camera-portal.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ struct camera_portal_source {
obs_pipewire_stream *obs_pw_stream;
char *device_id;

bool restart_stream;

enum spa_media_subtype subtype;
struct obs_pw_video_format format;

struct {
struct spa_rectangle rect;
bool set;
Expand Down Expand Up @@ -242,7 +247,7 @@ static bool update_device_id(struct camera_portal_source *camera_source, const c

static void stream_camera(struct camera_portal_source *camera_source)
{
struct obs_pipwire_connect_stream_info connect_info;
struct obs_pipewire_connect_stream_info connect_info;
const struct spa_rectangle *resolution = NULL;
const struct spa_fraction *framerate = NULL;
struct camera_device *device;
Expand All @@ -261,11 +266,13 @@ static void stream_camera(struct camera_portal_source *camera_source)
if (camera_source->framerate.set)
framerate = &camera_source->framerate.fraction;

connect_info = (struct obs_pipwire_connect_stream_info){
connect_info = (struct obs_pipewire_connect_stream_info){
.stream_name = "OBS PipeWire Camera",
.stream_properties = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE, "Camera", NULL),
.video = {
.subtype = &camera_source->subtype,
.format = &camera_source->format,
.resolution = resolution,
.framerate = framerate,
}};
Expand All @@ -277,14 +284,17 @@ static void stream_camera(struct camera_portal_source *camera_source)
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_data_t *data = NULL;

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;
struct dstr str = {};
uint32_t media_type, media_subtype;
uint32_t format = 0;
const char *format_name;
struct spa_rectangle resolution;

if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
continue;
Expand All @@ -293,24 +303,42 @@ static void camera_format_list(struct camera_device *dev, obs_property_t *prop)
continue;
if (media_type != SPA_MEDIA_TYPE_video)
continue;

g_clear_pointer(&data, obs_data_release);

data = obs_data_create();

if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
struct obs_pw_video_format obs_pw_video_format;

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))
if (!obs_pw_video_format_from_spa_format(format, &obs_pw_video_format))
continue;

obs_data_set_bool(data, "encoded", false);
obs_data_set_int(data, "video_format", format);

format_name = obs_pw_video_format.pretty_name;
} else {
continue;
}

if (obs_pw_video_format.video_format == last_format)
if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, format ? &format : NULL,
SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Rectangle(&resolution)) < 0)
continue;

last_format = obs_pw_video_format.video_format;
obs_data_set_int(data, "width", resolution.width);
obs_data_set_int(data, "height", resolution.height);

obs_property_list_add_int(prop, obs_pw_video_format.pretty_name, format);
dstr_printf(&str, "%ux%u - %s", resolution.width, resolution.height, format_name);
obs_property_list_add_string(prop, str.array, obs_data_get_json(data));
dstr_free(&str);
}

g_clear_pointer(&data, obs_data_release);
}

static bool control_changed(void *data, obs_properties_t *props, obs_property_t *prop, obs_data_t *settings)
Expand Down Expand Up @@ -479,12 +507,11 @@ static bool device_selected(void *data, obs_properties_t *props, obs_property_t
if (device == NULL)
return false;

if (update_device_id(camera_source, device_id))
stream_camera(camera_source);
camera_source->restart_stream = update_device_id(camera_source, device_id);

blog(LOG_INFO, "[camera-portal] Updating pixel formats");

property = obs_properties_get(props, "pixelformat");
property = obs_properties_get(props, "format");
new_control_properties = obs_properties_create();
obs_properties_remove_by_name(props, "controls");

Expand All @@ -499,108 +526,6 @@ static bool device_selected(void *data, obs_properties_t *props, obs_property_t
return true;
}

static int sort_resolutions(gconstpointer a, gconstpointer b)
{
const struct spa_rectangle *resolution_a = a;
const struct spa_rectangle *resolution_b = b;
int64_t area_a = resolution_a->width * resolution_a->height;
int64_t area_b = resolution_b->width * resolution_b->height;

return area_a - area_b;
}

static void resolution_list(struct camera_device *dev, uint32_t pixelformat, obs_property_t *prop)
{
struct spa_rectangle last_resolution = SPA_RECTANGLE(0, 0);
g_autoptr(GArray) resolutions = NULL;
struct param *p;
obs_data_t *data;

resolutions = g_array_new(FALSE, FALSE, sizeof(struct spa_rectangle));

spa_list_for_each(p, &dev->param_list, link)
{
struct obs_pw_video_format obs_pw_video_format;
struct spa_rectangle resolution;
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 != pixelformat)
continue;

if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_size,
SPA_POD_OPT_Rectangle(&resolution)) < 0)
continue;

if (resolution.width == last_resolution.width && resolution.height == last_resolution.height)
continue;

last_resolution = resolution;
g_array_append_val(resolutions, resolution);
}

g_array_sort(resolutions, sort_resolutions);

obs_property_list_clear(prop);

data = obs_data_create();
for (size_t i = 0; i < resolutions->len; i++) {
const struct spa_rectangle *resolution = &g_array_index(resolutions, struct spa_rectangle, i);
struct dstr str = {};

dstr_printf(&str, "%ux%u", resolution->width, resolution->height);

obs_data_set_int(data, "width", resolution->width);
obs_data_set_int(data, "height", resolution->height);

obs_property_list_add_string(prop, str.array, obs_data_get_json(data));

dstr_free(&str);
}
obs_data_release(data);
}

/*
* Format selected callback
*/
static bool format_selected(void *data, obs_properties_t *properties, obs_property_t *property, obs_data_t *settings)
{
UNUSED_PARAMETER(property);
UNUSED_PARAMETER(settings);

struct camera_portal_source *camera_source = data;
struct camera_device *device;
obs_property_t *resolution;

blog(LOG_INFO, "[camera-portal] Selected format for '%s'", camera_source->device_id);

device = g_hash_table_lookup(connection->devices, camera_source->device_id);
if (device == NULL)
return false;

resolution = obs_properties_get(properties, "resolution");
resolution_list(device, obs_data_get_int(settings, "pixelformat"), resolution);

return true;
}

static int compare_framerates(gconstpointer a, gconstpointer b)
{
const struct spa_fraction *framerate_a = a;
Expand Down Expand Up @@ -760,47 +685,68 @@ static bool framerate_selected(void *data, obs_properties_t *properties, obs_pro
}

/*
* Resolution selected callback
* Format selected callback
*/

static bool parse_resolution(struct spa_rectangle *dest, const char *json)
static bool parse_format(struct camera_portal_source *dest, const char *json)
{
obs_data_t *data = obs_data_create_from_json(json);
bool ret = false;

if (!data)
return false;

dest->width = obs_data_get_int(data, "width");
dest->height = obs_data_get_int(data, "height");
if (obs_data_has_user_value(data, "video_format") && obs_data_has_user_value(data, "encoded")) {
struct obs_pw_video_format format;

if (obs_data_get_bool(data, "encoded")) {
dest->subtype = obs_data_get_int(data, "video_format");
ret = true;
} else if (obs_pw_video_format_from_spa_format(obs_data_get_int(data, "video_format"), &format)) {
dest->subtype = SPA_MEDIA_SUBTYPE_raw;
dest->format = format;
ret = true;
}

if (obs_data_has_user_value(data, "width") && obs_data_has_user_value(data, "height")) {
dest->resolution.rect.width = obs_data_get_int(data, "width");
dest->resolution.rect.height = obs_data_get_int(data, "height");
dest->resolution.set = true;
}
}

obs_data_release(data);
return true;
return ret;
}

static bool resolution_selected(void *data, obs_properties_t *properties, obs_property_t *property,
obs_data_t *settings)
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;
struct spa_rectangle resolution;
struct camera_device *device;
enum spa_media_subtype last_subtype = camera_source->subtype;
enum spa_video_format last_format = camera_source->format.spa_format;

blog(LOG_INFO, "[camera-portal] Selected resolution for '%s'", camera_source->device_id);
blog(LOG_INFO, "[camera-portal] Selected format for '%s'", camera_source->device_id);

device = g_hash_table_lookup(connection->devices, camera_source->device_id);
if (device == NULL)
return false;

if (!parse_resolution(&resolution, obs_data_get_string(settings, "resolution")))
if (!parse_format(camera_source, obs_data_get_string(settings, "format")))
return false;

if (camera_source->obs_pw_stream)
obs_pipewire_stream_set_resolution(camera_source->obs_pw_stream, &resolution);
if (!camera_source->obs_pw_stream || camera_source->restart_stream || last_subtype != camera_source->subtype ||
last_format != camera_source->format.spa_format) {
camera_source->restart_stream = false;
stream_camera(camera_source);
} else if (camera_source->obs_pw_stream) {
obs_pipewire_stream_set_resolution(camera_source->obs_pw_stream, &camera_source->resolution.rect);
}

property = obs_properties_get(properties, "framerate");
framerate_list(device, obs_data_get_int(settings, "pixelformat"), &resolution, property);
framerate_list(device, camera_source->format.spa_format, &camera_source->resolution.rect, property);

return true;
}
Expand Down Expand Up @@ -1116,6 +1062,19 @@ static const char *pipewire_camera_get_name(void *data)
return obs_module_text("PipeWireCamera");
}

static bool parse_resolution(struct spa_rectangle *dest, const char *json)
{
obs_data_t *data = obs_data_create_from_json(json);

if (!data)
return false;

dest->width = obs_data_get_int(data, "width");
dest->height = obs_data_get_int(data, "height");
obs_data_release(data);
return true;
}

static void *pipewire_camera_create(obs_data_t *settings, obs_source_t *source)
{
struct camera_portal_source *camera_source;
Expand All @@ -1125,8 +1084,30 @@ static void *pipewire_camera_create(obs_data_t *settings, obs_source_t *source)
camera_source->device_id = bstrdup(obs_data_get_string(settings, "device_id"));
camera_source->framerate.set =
parse_framerate(&camera_source->framerate.fraction, obs_data_get_string(settings, "framerate"));
camera_source->resolution.set =
parse_resolution(&camera_source->resolution.rect, obs_data_get_string(settings, "resolution"));

if (obs_data_has_user_value(settings, "format")) {
parse_format(camera_source, obs_data_get_string(settings, "format"));
} else if (obs_pw_video_format_from_spa_format(obs_data_get_int(settings, "pixelformat"),
&camera_source->format)) {
camera_source->subtype = SPA_MEDIA_SUBTYPE_raw;

camera_source->resolution.set =
parse_resolution(&camera_source->resolution.rect, obs_data_get_string(settings, "resolution"));

/* NOTE: We can convert to the new format only if resolution is available */
if (camera_source->resolution.set) {
obs_data_t *format = obs_data_create();

obs_data_set_bool(format, "encoded", false);
obs_data_set_int(format, "video_format", camera_source->format.spa_format);
obs_data_set_int(format, "width", camera_source->resolution.rect.width);
obs_data_set_int(format, "height", camera_source->resolution.rect.height);

obs_data_set_string(settings, "format", obs_data_get_json(format));

obs_data_release(format);
}
}

access_camera(camera_source);

Expand Down Expand Up @@ -1156,7 +1137,6 @@ static obs_properties_t *pipewire_camera_get_properties(void *data)
struct camera_portal_source *camera_source = data;
obs_properties_t *controls_props;
obs_properties_t *props;
obs_property_t *resolution_list;
obs_property_t *framerate_list;
obs_property_t *device_list;
obs_property_t *format_list;
Expand All @@ -1166,11 +1146,8 @@ static obs_properties_t *pipewire_camera_get_properties(void *data)
device_list = obs_properties_add_list(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_STRING);
format_list = obs_properties_add_list(props, "format", obs_module_text("VideoFormat"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);

framerate_list = obs_properties_add_list(props, "framerate", obs_module_text("FrameRate"), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
Expand All @@ -1184,7 +1161,6 @@ static obs_properties_t *pipewire_camera_get_properties(void *data)

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);
obs_property_set_modified_callback2(framerate_list, framerate_selected, camera_source);

return props;
Expand Down
Loading
Loading