diff --git a/plugins/linux-capture/pipewire.c b/plugins/linux-capture/pipewire.c index a9d715cec..3f13a383e 100644 --- a/plugins/linux-capture/pipewire.c +++ b/plugins/linux-capture/pipewire.c @@ -22,6 +22,7 @@ #include "portal.h" +#include #include #include @@ -50,6 +51,12 @@ struct obs_pw_version { int micro; }; +struct format_info { + uint32_t spa_format; + uint32_t drm_format; + DARRAY(uint64_t) modifiers; +}; + struct _obs_pipewire_data { GCancellable *cancellable; @@ -98,6 +105,8 @@ struct _obs_pipewire_data { enum obs_pw_capture_type capture_type; struct obs_video_info video_info; bool negotiated; + + DARRAY(struct format_info) format_info; }; struct dbus_call_data { @@ -366,6 +375,176 @@ static void swap_texture_red_blue(gs_texture_t *texture) glBindTexture(GL_TEXTURE_2D, 0); } +static inline struct spa_pod *build_format(struct spa_pod_builder *b, + struct obs_video_info *ovi, + uint32_t format, uint64_t *modifiers, + size_t modifier_count) +{ + struct spa_pod_frame f[2]; + + /* Make an object of type SPA_TYPE_OBJECT_Format and id SPA_PARAM_EnumFormat. + * The object type is important because it defines the properties that are + * acceptable. The id gives more context about what the object is meant to + * contain. In this case we enumerate supported formats. */ + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, + SPA_PARAM_EnumFormat); + /* add media type and media subtype properties */ + spa_pod_builder_add(b, SPA_FORMAT_mediaType, + SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, + SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + + /* formats */ + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + + /* modifier */ + if (modifier_count > 0) { + /* build an enumeration of modifiers */ + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, + SPA_POD_PROP_FLAG_MANDATORY | + SPA_POD_PROP_FLAG_DONT_FIXATE); + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + /* modifiers from an array */ + for (uint32_t i = 0; i < modifier_count; i++) { + uint64_t modifier = modifiers[i]; + spa_pod_builder_long(b, modifier); + if (i == 0) + spa_pod_builder_long(b, modifier); + } + spa_pod_builder_pop(b, &f[1]); + } + /* add size and framerate ranges */ + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, + SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(320, 240), // Arbitrary + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(8192, 4320)), + SPA_FORMAT_VIDEO_framerate, + SPA_POD_CHOICE_RANGE_Fraction( + &SPA_FRACTION(ovi->fps_num, ovi->fps_den), + &SPA_FRACTION(0, 1), &SPA_FRACTION(360, 1)), + 0); + return spa_pod_builder_pop(b, &f[0]); +} + +static bool build_format_params(obs_pipewire_data *obs_pw, + struct spa_pod_builder *pod_builder, + const struct spa_pod ***param_list, + uint32_t *n_params) +{ + uint32_t params_count = 0; + + const struct spa_pod **params; + params = + bzalloc(2 * obs_pw->format_info.num * sizeof(struct spa_pod *)); + + if (!params) { + blog(LOG_ERROR, + "[pipewire] Failed to allocate memory for param pointers"); + return false; + } + + for (size_t i = 0; i < obs_pw->format_info.num; i++) { + if (obs_pw->format_info.array[i].modifiers.num == 0 || + !check_pw_version(&obs_pw->server_version, 0, 3, 33)) { + continue; + } + params[params_count++] = build_format( + pod_builder, &obs_pw->video_info, + obs_pw->format_info.array[i].spa_format, + obs_pw->format_info.array[i].modifiers.array, + obs_pw->format_info.array[i].modifiers.num); + } + for (size_t i = 0; i < obs_pw->format_info.num; i++) { + params[params_count++] = build_format( + pod_builder, &obs_pw->video_info, + obs_pw->format_info.array[i].spa_format, NULL, 0); + } + *param_list = params; + *n_params = params_count; + return true; +} + +static bool drm_format_available(uint32_t drm_format, uint32_t *drm_formats, + size_t n_drm_formats) +{ + for (size_t j = 0; j < n_drm_formats; j++) { + if (drm_format == drm_formats[j]) { + return true; + } + } + return false; +} + +static void init_format_info(obs_pipewire_data *obs_pw) +{ + da_init(obs_pw->format_info); + + uint32_t formats[] = { + SPA_VIDEO_FORMAT_BGRA, + SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_BGRx, + SPA_VIDEO_FORMAT_RGBx, + }; + + size_t n_formats = sizeof(formats) / sizeof(formats[0]); + + obs_enter_graphics(); + + enum gs_dmabuf_flags dmabuf_flags; + uint32_t *drm_formats = NULL; + size_t n_drm_formats; + + bool capabilities_queried = gs_query_dmabuf_capabilities( + &dmabuf_flags, &drm_formats, &n_drm_formats); + + for (size_t i = 0; i < n_formats; i++) { + struct format_info *info; + uint32_t drm_format; + + if (!spa_pixel_format_to_drm_format(formats[i], &drm_format)) + continue; + + if (!drm_format_available(drm_format, drm_formats, + n_drm_formats)) + continue; + + info = da_push_back_new(obs_pw->format_info); + da_init(info->modifiers); + info->spa_format = formats[i]; + info->drm_format = drm_format; + + if (!capabilities_queried) + continue; + + size_t n_modifiers; + uint64_t *modifiers = NULL; + if (gs_query_dmabuf_modifiers_for_format(drm_format, &modifiers, + &n_modifiers)) { + da_push_back_array(info->modifiers, modifiers, + n_modifiers); + } + bfree(modifiers); + + if (dmabuf_flags & + GS_DMABUF_FLAG_IMPLICIT_MODIFIERS_SUPPORTED) { + uint64_t modifier_implicit = DRM_FORMAT_MOD_INVALID; + da_push_back(info->modifiers, &modifier_implicit); + } + } + obs_leave_graphics(); + + bfree(drm_formats); +} + +static void clear_format_info(obs_pipewire_data *obs_pw) +{ + for (size_t i = 0; i < obs_pw->format_info.num; i++) { + da_free(obs_pw->format_info.array[i].modifiers); + } + da_free(obs_pw->format_info); +} + /* ------------------------------------------------- */ static void on_process_cb(void *user_data) @@ -555,7 +734,10 @@ static void on_param_changed_cb(void *user_data, uint32_t id, spa_format_video_raw_parse(param, &obs_pw->format.info.raw); buffer_types = 1 << SPA_DATA_MemPtr; - if (check_pw_version(&obs_pw->server_version, 0, 3, 24)) + bool has_modifier = + spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier) != + NULL; + if (has_modifier || check_pw_version(&obs_pw->server_version, 0, 3, 24)) buffer_types |= 1 << SPA_DATA_DmaBuf; blog(LOG_DEBUG, "[pipewire] Negotiated format:"); @@ -659,9 +841,9 @@ static const struct pw_core_events core_events = { static void play_pipewire_stream(obs_pipewire_data *obs_pw) { struct spa_pod_builder pod_builder; - const struct spa_pod *params[1]; - uint8_t params_buffer[1024]; - struct obs_video_info ovi; + const struct spa_pod **params = NULL; + uint32_t n_params; + uint8_t params_buffer[2048]; obs_pw->thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL); obs_pw->context = pw_context_new( @@ -706,33 +888,23 @@ static void play_pipewire_stream(obs_pipewire_data *obs_pw) pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); - obs_get_video_info(&ovi); - params[0] = spa_pod_builder_add_object( - &pod_builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, - SPA_POD_CHOICE_ENUM_Id( - 4, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, - SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx), - SPA_FORMAT_VIDEO_size, - SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(320, 240), // Arbitrary - &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(8192, 4320)), - SPA_FORMAT_VIDEO_framerate, - SPA_POD_CHOICE_RANGE_Fraction( - &SPA_FRACTION(ovi.fps_num, ovi.fps_den), - &SPA_FRACTION(0, 1), &SPA_FRACTION(360, 1))); - obs_pw->video_info = ovi; + obs_get_video_info(&obs_pw->video_info); + + if (!build_format_params(obs_pw, &pod_builder, ¶ms, &n_params)) { + pw_thread_loop_unlock(obs_pw->thread_loop); + teardown_pipewire(obs_pw); + return; + } pw_stream_connect( obs_pw->stream, PW_DIRECTION_INPUT, obs_pw->pipewire_node, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, - 1); + n_params); blog(LOG_INFO, "[pipewire] playing stream…"); pw_thread_loop_unlock(obs_pw->thread_loop); + bfree(params); } /* ------------------------------------------------- */ @@ -1193,6 +1365,8 @@ void *obs_pipewire_create(enum obs_pw_capture_type capture_type, if (!init_obs_pipewire(obs_pw)) g_clear_pointer(&obs_pw, bfree); + init_format_info(obs_pw); + return obs_pw; } @@ -1205,6 +1379,7 @@ void obs_pipewire_destroy(obs_pipewire_data *obs_pw) destroy_session(obs_pw); g_clear_pointer(&obs_pw->restore_token, bfree); + clear_format_info(obs_pw); bfree(obs_pw); }