linux-capture: Announce supported modifiers via PipeWire

Sharing DMA-BUFs via PipeWire requires the client to announce all
formats together with their supported modifiers. [1]

[1] https://docs.pipewire.org/page_dma_buf.html
This commit is contained in:
columbarius 2021-09-16 22:03:01 +02:00 committed by Georges Basile Stavracas Neto
parent b57f7f0aed
commit c31aba9600

View File

@ -22,6 +22,7 @@
#include "portal.h"
#include <util/darray.h>
#include <util/dstr.h>
#include <gio/gio.h>
@ -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, &params, &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);
}