Skip to content

Commit c11253b

Browse files
tytan652RytoEX
authored andcommitted
linux-pipewire: Use list-based format selector for video capture
Negotiation is now made with the user selected format. Also fixes a typo in the name of a structure.
1 parent 8e2da12 commit c11253b

5 files changed

Lines changed: 144 additions & 156 deletions

File tree

plugins/linux-pipewire/camera-portal.c

Lines changed: 117 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ struct camera_portal_source {
4444
obs_pipewire_stream *obs_pw_stream;
4545
char *device_id;
4646

47+
bool restart_stream;
48+
49+
enum spa_media_subtype subtype;
50+
struct obs_pw_video_format format;
51+
4752
struct {
4853
struct spa_rectangle rect;
4954
bool set;
@@ -242,7 +247,7 @@ static bool update_device_id(struct camera_portal_source *camera_source, const c
242247

243248
static void stream_camera(struct camera_portal_source *camera_source)
244249
{
245-
struct obs_pipwire_connect_stream_info connect_info;
250+
struct obs_pipewire_connect_stream_info connect_info;
246251
const struct spa_rectangle *resolution = NULL;
247252
const struct spa_fraction *framerate = NULL;
248253
struct camera_device *device;
@@ -261,11 +266,13 @@ static void stream_camera(struct camera_portal_source *camera_source)
261266
if (camera_source->framerate.set)
262267
framerate = &camera_source->framerate.fraction;
263268

264-
connect_info = (struct obs_pipwire_connect_stream_info){
269+
connect_info = (struct obs_pipewire_connect_stream_info){
265270
.stream_name = "OBS PipeWire Camera",
266271
.stream_properties = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture",
267272
PW_KEY_MEDIA_ROLE, "Camera", NULL),
268273
.video = {
274+
.subtype = &camera_source->subtype,
275+
.format = &camera_source->format,
269276
.resolution = resolution,
270277
.framerate = framerate,
271278
}};
@@ -277,14 +284,17 @@ static void stream_camera(struct camera_portal_source *camera_source)
277284
static void camera_format_list(struct camera_device *dev, obs_property_t *prop)
278285
{
279286
struct param *p;
280-
enum video_format last_format = VIDEO_FORMAT_NONE;
287+
obs_data_t *data = NULL;
281288

282289
obs_property_list_clear(prop);
283290

284291
spa_list_for_each(p, &dev->param_list, link)
285292
{
286-
struct obs_pw_video_format obs_pw_video_format;
287-
uint32_t media_type, media_subtype, format;
293+
struct dstr str = {};
294+
uint32_t media_type, media_subtype;
295+
uint32_t format = 0;
296+
const char *format_name;
297+
struct spa_rectangle resolution;
288298

289299
if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
290300
continue;
@@ -293,24 +303,42 @@ static void camera_format_list(struct camera_device *dev, obs_property_t *prop)
293303
continue;
294304
if (media_type != SPA_MEDIA_TYPE_video)
295305
continue;
306+
307+
g_clear_pointer(&data, obs_data_release);
308+
309+
data = obs_data_create();
310+
296311
if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
312+
struct obs_pw_video_format obs_pw_video_format;
313+
297314
if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_format,
298315
SPA_POD_Id(&format)) < 0)
299316
continue;
300-
} else {
301-
format = SPA_VIDEO_FORMAT_ENCODED;
302-
}
303317

304-
if (!obs_pw_video_format_from_spa_format(format, &obs_pw_video_format))
318+
if (!obs_pw_video_format_from_spa_format(format, &obs_pw_video_format))
319+
continue;
320+
321+
obs_data_set_bool(data, "encoded", false);
322+
obs_data_set_int(data, "video_format", format);
323+
324+
format_name = obs_pw_video_format.pretty_name;
325+
} else {
305326
continue;
327+
}
306328

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

310-
last_format = obs_pw_video_format.video_format;
333+
obs_data_set_int(data, "width", resolution.width);
334+
obs_data_set_int(data, "height", resolution.height);
311335

312-
obs_property_list_add_int(prop, obs_pw_video_format.pretty_name, format);
336+
dstr_printf(&str, "%ux%u - %s", resolution.width, resolution.height, format_name);
337+
obs_property_list_add_string(prop, str.array, obs_data_get_json(data));
338+
dstr_free(&str);
313339
}
340+
341+
g_clear_pointer(&data, obs_data_release);
314342
}
315343

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

482-
if (update_device_id(camera_source, device_id))
483-
stream_camera(camera_source);
510+
camera_source->restart_stream = update_device_id(camera_source, device_id);
484511

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

487-
property = obs_properties_get(props, "pixelformat");
514+
property = obs_properties_get(props, "format");
488515
new_control_properties = obs_properties_create();
489516
obs_properties_remove_by_name(props, "controls");
490517

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

502-
static int sort_resolutions(gconstpointer a, gconstpointer b)
503-
{
504-
const struct spa_rectangle *resolution_a = a;
505-
const struct spa_rectangle *resolution_b = b;
506-
int64_t area_a = resolution_a->width * resolution_a->height;
507-
int64_t area_b = resolution_b->width * resolution_b->height;
508-
509-
return area_a - area_b;
510-
}
511-
512-
static void resolution_list(struct camera_device *dev, uint32_t pixelformat, obs_property_t *prop)
513-
{
514-
struct spa_rectangle last_resolution = SPA_RECTANGLE(0, 0);
515-
g_autoptr(GArray) resolutions = NULL;
516-
struct param *p;
517-
obs_data_t *data;
518-
519-
resolutions = g_array_new(FALSE, FALSE, sizeof(struct spa_rectangle));
520-
521-
spa_list_for_each(p, &dev->param_list, link)
522-
{
523-
struct obs_pw_video_format obs_pw_video_format;
524-
struct spa_rectangle resolution;
525-
uint32_t media_type, media_subtype, format;
526-
527-
if (p->id != SPA_PARAM_EnumFormat || p->param == NULL)
528-
continue;
529-
530-
if (spa_format_parse(p->param, &media_type, &media_subtype) < 0)
531-
continue;
532-
if (media_type != SPA_MEDIA_TYPE_video)
533-
continue;
534-
if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
535-
if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_format,
536-
SPA_POD_Id(&format)) < 0)
537-
continue;
538-
} else {
539-
format = SPA_VIDEO_FORMAT_ENCODED;
540-
}
541-
542-
if (!obs_pw_video_format_from_spa_format(format, &obs_pw_video_format))
543-
continue;
544-
545-
if (obs_pw_video_format.video_format != pixelformat)
546-
continue;
547-
548-
if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_size,
549-
SPA_POD_OPT_Rectangle(&resolution)) < 0)
550-
continue;
551-
552-
if (resolution.width == last_resolution.width && resolution.height == last_resolution.height)
553-
continue;
554-
555-
last_resolution = resolution;
556-
g_array_append_val(resolutions, resolution);
557-
}
558-
559-
g_array_sort(resolutions, sort_resolutions);
560-
561-
obs_property_list_clear(prop);
562-
563-
data = obs_data_create();
564-
for (size_t i = 0; i < resolutions->len; i++) {
565-
const struct spa_rectangle *resolution = &g_array_index(resolutions, struct spa_rectangle, i);
566-
struct dstr str = {};
567-
568-
dstr_printf(&str, "%ux%u", resolution->width, resolution->height);
569-
570-
obs_data_set_int(data, "width", resolution->width);
571-
obs_data_set_int(data, "height", resolution->height);
572-
573-
obs_property_list_add_string(prop, str.array, obs_data_get_json(data));
574-
575-
dstr_free(&str);
576-
}
577-
obs_data_release(data);
578-
}
579-
580-
/*
581-
* Format selected callback
582-
*/
583-
static bool format_selected(void *data, obs_properties_t *properties, obs_property_t *property, obs_data_t *settings)
584-
{
585-
UNUSED_PARAMETER(property);
586-
UNUSED_PARAMETER(settings);
587-
588-
struct camera_portal_source *camera_source = data;
589-
struct camera_device *device;
590-
obs_property_t *resolution;
591-
592-
blog(LOG_INFO, "[camera-portal] Selected format for '%s'", camera_source->device_id);
593-
594-
device = g_hash_table_lookup(connection->devices, camera_source->device_id);
595-
if (device == NULL)
596-
return false;
597-
598-
resolution = obs_properties_get(properties, "resolution");
599-
resolution_list(device, obs_data_get_int(settings, "pixelformat"), resolution);
600-
601-
return true;
602-
}
603-
604529
static int compare_framerates(gconstpointer a, gconstpointer b)
605530
{
606531
const struct spa_fraction *framerate_a = a;
@@ -760,47 +685,68 @@ static bool framerate_selected(void *data, obs_properties_t *properties, obs_pro
760685
}
761686

762687
/*
763-
* Resolution selected callback
688+
* Format selected callback
764689
*/
765-
766-
static bool parse_resolution(struct spa_rectangle *dest, const char *json)
690+
static bool parse_format(struct camera_portal_source *dest, const char *json)
767691
{
768692
obs_data_t *data = obs_data_create_from_json(json);
693+
bool ret = false;
769694

770695
if (!data)
771696
return false;
772697

773-
dest->width = obs_data_get_int(data, "width");
774-
dest->height = obs_data_get_int(data, "height");
698+
if (obs_data_has_user_value(data, "video_format") && obs_data_has_user_value(data, "encoded")) {
699+
struct obs_pw_video_format format;
700+
701+
if (obs_data_get_bool(data, "encoded")) {
702+
dest->subtype = obs_data_get_int(data, "video_format");
703+
ret = true;
704+
} else if (obs_pw_video_format_from_spa_format(obs_data_get_int(data, "video_format"), &format)) {
705+
dest->subtype = SPA_MEDIA_SUBTYPE_raw;
706+
dest->format = format;
707+
ret = true;
708+
}
709+
710+
if (obs_data_has_user_value(data, "width") && obs_data_has_user_value(data, "height")) {
711+
dest->resolution.rect.width = obs_data_get_int(data, "width");
712+
dest->resolution.rect.height = obs_data_get_int(data, "height");
713+
dest->resolution.set = true;
714+
}
715+
}
716+
775717
obs_data_release(data);
776-
return true;
718+
return ret;
777719
}
778720

779-
static bool resolution_selected(void *data, obs_properties_t *properties, obs_property_t *property,
780-
obs_data_t *settings)
721+
static bool format_selected(void *data, obs_properties_t *properties, obs_property_t *property, obs_data_t *settings)
781722
{
782-
UNUSED_PARAMETER(properties);
783723
UNUSED_PARAMETER(property);
784724
UNUSED_PARAMETER(settings);
785725

786726
struct camera_portal_source *camera_source = data;
787-
struct spa_rectangle resolution;
788727
struct camera_device *device;
728+
enum spa_media_subtype last_subtype = camera_source->subtype;
729+
enum spa_video_format last_format = camera_source->format.spa_format;
789730

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

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

796-
if (!parse_resolution(&resolution, obs_data_get_string(settings, "resolution")))
737+
if (!parse_format(camera_source, obs_data_get_string(settings, "format")))
797738
return false;
798739

799-
if (camera_source->obs_pw_stream)
800-
obs_pipewire_stream_set_resolution(camera_source->obs_pw_stream, &resolution);
740+
if (!camera_source->obs_pw_stream || camera_source->restart_stream || last_subtype != camera_source->subtype ||
741+
last_format != camera_source->format.spa_format) {
742+
camera_source->restart_stream = false;
743+
stream_camera(camera_source);
744+
} else if (camera_source->obs_pw_stream) {
745+
obs_pipewire_stream_set_resolution(camera_source->obs_pw_stream, &camera_source->resolution.rect);
746+
}
801747

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

805751
return true;
806752
}
@@ -1116,6 +1062,19 @@ static const char *pipewire_camera_get_name(void *data)
11161062
return obs_module_text("PipeWireCamera");
11171063
}
11181064

1065+
static bool parse_resolution(struct spa_rectangle *dest, const char *json)
1066+
{
1067+
obs_data_t *data = obs_data_create_from_json(json);
1068+
1069+
if (!data)
1070+
return false;
1071+
1072+
dest->width = obs_data_get_int(data, "width");
1073+
dest->height = obs_data_get_int(data, "height");
1074+
obs_data_release(data);
1075+
return true;
1076+
}
1077+
11191078
static void *pipewire_camera_create(obs_data_t *settings, obs_source_t *source)
11201079
{
11211080
struct camera_portal_source *camera_source;
@@ -1125,8 +1084,30 @@ static void *pipewire_camera_create(obs_data_t *settings, obs_source_t *source)
11251084
camera_source->device_id = bstrdup(obs_data_get_string(settings, "device_id"));
11261085
camera_source->framerate.set =
11271086
parse_framerate(&camera_source->framerate.fraction, obs_data_get_string(settings, "framerate"));
1128-
camera_source->resolution.set =
1129-
parse_resolution(&camera_source->resolution.rect, obs_data_get_string(settings, "resolution"));
1087+
1088+
if (obs_data_has_user_value(settings, "format")) {
1089+
parse_format(camera_source, obs_data_get_string(settings, "format"));
1090+
} else if (obs_pw_video_format_from_spa_format(obs_data_get_int(settings, "pixelformat"),
1091+
&camera_source->format)) {
1092+
camera_source->subtype = SPA_MEDIA_SUBTYPE_raw;
1093+
1094+
camera_source->resolution.set =
1095+
parse_resolution(&camera_source->resolution.rect, obs_data_get_string(settings, "resolution"));
1096+
1097+
/* NOTE: We can convert to the new format only if resolution is available */
1098+
if (camera_source->resolution.set) {
1099+
obs_data_t *format = obs_data_create();
1100+
1101+
obs_data_set_bool(format, "encoded", false);
1102+
obs_data_set_int(format, "video_format", camera_source->format.spa_format);
1103+
obs_data_set_int(format, "width", camera_source->resolution.rect.width);
1104+
obs_data_set_int(format, "height", camera_source->resolution.rect.height);
1105+
1106+
obs_data_set_string(settings, "format", obs_data_get_json(format));
1107+
1108+
obs_data_release(format);
1109+
}
1110+
}
11301111

11311112
access_camera(camera_source);
11321113

@@ -1156,7 +1137,6 @@ static obs_properties_t *pipewire_camera_get_properties(void *data)
11561137
struct camera_portal_source *camera_source = data;
11571138
obs_properties_t *controls_props;
11581139
obs_properties_t *props;
1159-
obs_property_t *resolution_list;
11601140
obs_property_t *framerate_list;
11611141
obs_property_t *device_list;
11621142
obs_property_t *format_list;
@@ -1166,11 +1146,8 @@ static obs_properties_t *pipewire_camera_get_properties(void *data)
11661146
device_list = obs_properties_add_list(props, "device_id", obs_module_text("PipeWireCameraDevice"),
11671147
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
11681148

1169-
format_list = obs_properties_add_list(props, "pixelformat", obs_module_text("VideoFormat"), OBS_COMBO_TYPE_LIST,
1170-
OBS_COMBO_FORMAT_INT);
1171-
1172-
resolution_list = obs_properties_add_list(props, "resolution", obs_module_text("Resolution"),
1173-
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
1149+
format_list = obs_properties_add_list(props, "format", obs_module_text("VideoFormat"), OBS_COMBO_TYPE_LIST,
1150+
OBS_COMBO_FORMAT_STRING);
11741151

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

11851162
obs_property_set_modified_callback2(device_list, device_selected, camera_source);
11861163
obs_property_set_modified_callback2(format_list, format_selected, camera_source);
1187-
obs_property_set_modified_callback2(resolution_list, resolution_selected, camera_source);
11881164
obs_property_set_modified_callback2(framerate_list, framerate_selected, camera_source);
11891165

11901166
return props;

0 commit comments

Comments
 (0)