linux-pipewire: Add support for async sources

Cameras and audio streams will use the async output routines, but
not screencasting. This needs to be handled differently in the
processing callback.

As it is, the code is starting to get a bit messy, as we're now
dealing with two different frame processing approaches. Later on,
this code will be cleaned up and split in more logical pieces.

Co-authored-by: Georges Basile Stavracas Neto
<georges.stavracas@gmail.com>
This commit is contained in:
columbarius 2022-07-12 03:12:26 +02:00 committed by Georges Basile Stavracas Neto
parent c7e355e89c
commit 67f01c7a44

View file

@ -328,6 +328,24 @@ static const struct format_data supported_formats[] = {
#define N_SUPPORTED_FORMATS \
(sizeof(supported_formats) / sizeof(supported_formats[0]))
static const uint32_t supported_formats_async[] = {
SPA_VIDEO_FORMAT_RGBA,
};
#define N_SUPPORTED_FORMATS_ASYNC \
(sizeof(supported_formats_async) / sizeof(supported_formats_async[0]))
static const uint32_t supported_formats_sync[] = {
SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx,
#if PW_CHECK_VERSION(0, 3, 41)
SPA_VIDEO_FORMAT_ABGR_210LE, SPA_VIDEO_FORMAT_xBGR_210LE,
#endif
};
#define N_SUPPORTED_FORMATS_SYNC \
(sizeof(supported_formats_sync) / sizeof(supported_formats_sync[0]))
static bool
lookup_format_info_from_spa_format(uint32_t spa_format,
struct format_data *out_format_data)
@ -468,7 +486,25 @@ static bool drm_format_available(uint32_t drm_format, uint32_t *drm_formats,
return false;
}
static void init_format_info(obs_pipewire_stream *obs_pw_stream)
static void init_format_info_async(obs_pipewire_stream *obs_pw_stream)
{
da_init(obs_pw_stream->format_info);
for (size_t i = 0; i < N_SUPPORTED_FORMATS_ASYNC; i++) {
struct format_data format_data;
struct format_info *info;
if (!lookup_format_info_from_spa_format(
supported_formats_async[i], &format_data))
continue;
info = da_push_back_new(obs_pw_stream->format_info);
da_init(info->modifiers);
info->spa_format = format_data.spa_format;
info->drm_format = format_data.drm_format;
}
}
static void init_format_info_sync(obs_pipewire_stream *obs_pw_stream)
{
da_init(obs_pw_stream->format_info);
@ -481,24 +517,28 @@ static void init_format_info(obs_pipewire_stream *obs_pw_stream)
bool capabilities_queried = gs_query_dmabuf_capabilities(
&dmabuf_flags, &drm_formats, &n_drm_formats);
for (size_t i = 0; i < N_SUPPORTED_FORMATS; i++) {
for (size_t i = 0; i < N_SUPPORTED_FORMATS_SYNC; i++) {
struct format_data format_data;
struct format_info *info;
if (!lookup_format_info_from_spa_format(
supported_formats_sync[i], &format_data) ||
format_data.gs_format == GS_UNKNOWN)
continue;
info = da_push_back_new(obs_pw_stream->format_info);
da_init(info->modifiers);
info->spa_format = supported_formats[i].spa_format;
info->drm_format = supported_formats[i].drm_format;
info->spa_format = format_data.spa_format;
info->drm_format = format_data.drm_format;
if (!capabilities_queried ||
!drm_format_available(supported_formats[i].drm_format,
drm_formats, n_drm_formats))
!drm_format_available(format_data.drm_format, drm_formats,
n_drm_formats))
continue;
size_t n_modifiers;
uint64_t *modifiers = NULL;
if (gs_query_dmabuf_modifiers_for_format(
supported_formats[i].drm_format, &modifiers,
&n_modifiers)) {
format_data.drm_format, &modifiers, &n_modifiers)) {
da_push_back_array(info->modifiers, modifiers,
n_modifiers);
}
@ -515,6 +555,20 @@ static void init_format_info(obs_pipewire_stream *obs_pw_stream)
bfree(drm_formats);
}
static void init_format_info(obs_pipewire_stream *obs_pw_stream)
{
uint32_t output_flags;
output_flags = obs_source_get_output_flags(obs_pw_stream->source);
if (output_flags & OBS_SOURCE_VIDEO) {
if (output_flags & OBS_SOURCE_ASYNC)
init_format_info_async(obs_pw_stream);
else
init_format_info_sync(obs_pw_stream);
}
}
static void clear_format_info(obs_pipewire_stream *obs_pw_stream)
{
for (size_t i = 0; i < obs_pw_stream->format_info.num; i++) {
@ -584,9 +638,131 @@ static void renegotiate_format(void *data, uint64_t expirations)
/* ------------------------------------------------- */
static void on_process_cb(void *user_data)
static inline struct pw_buffer *find_latest_buffer(struct pw_stream *stream)
{
struct pw_buffer *b;
/* Find the most recent buffer */
b = NULL;
while (true) {
struct pw_buffer *aux = pw_stream_dequeue_buffer(stream);
if (!aux)
break;
if (b)
pw_stream_queue_buffer(stream, b);
b = aux;
}
return b;
}
static enum video_colorspace
video_colorspace_from_spa_color_matrix(enum spa_video_color_matrix matrix)
{
switch (matrix) {
case SPA_VIDEO_COLOR_MATRIX_RGB:
return VIDEO_CS_DEFAULT;
case SPA_VIDEO_COLOR_MATRIX_BT601:
return VIDEO_CS_601;
case SPA_VIDEO_COLOR_MATRIX_BT709:
return VIDEO_CS_709;
default:
return VIDEO_CS_DEFAULT;
}
}
static enum video_range_type
video_color_range_from_spa_color_range(enum spa_video_color_range colorrange)
{
switch (colorrange) {
case SPA_VIDEO_COLOR_RANGE_0_255:
return VIDEO_RANGE_FULL;
case SPA_VIDEO_COLOR_RANGE_16_235:
return VIDEO_RANGE_PARTIAL;
default:
return VIDEO_RANGE_DEFAULT;
}
}
static bool prepare_obs_frame(obs_pipewire_stream *obs_pw_stream,
struct obs_source_frame *frame)
{
struct format_data format_data;
frame->width = obs_pw_stream->format.info.raw.size.width;
frame->height = obs_pw_stream->format.info.raw.size.height;
video_format_get_parameters(
video_colorspace_from_spa_color_matrix(
obs_pw_stream->format.info.raw.color_matrix),
video_color_range_from_spa_color_range(
obs_pw_stream->format.info.raw.color_range),
frame->color_matrix, frame->color_range_min,
frame->color_range_max);
if (!lookup_format_info_from_spa_format(
obs_pw_stream->format.info.raw.format, &format_data) ||
format_data.video_format == VIDEO_FORMAT_NONE)
return false;
frame->format = format_data.video_format;
frame->linesize[0] = SPA_ROUND_UP_N(frame->width * format_data.bpp, 4);
return true;
}
static void process_video_async(obs_pipewire_stream *obs_pw_stream)
{
struct spa_buffer *buffer;
struct pw_buffer *b;
bool has_buffer;
b = find_latest_buffer(obs_pw_stream->stream);
if (!b) {
blog(LOG_DEBUG, "[pipewire] Out of buffers!");
return;
}
buffer = b->buffer;
has_buffer = buffer->datas[0].chunk->size != 0;
if (!has_buffer)
goto done;
#ifdef DEBUG_PIPEWIRE
blog(LOG_DEBUG, "[pipewire] Buffer has memory texture");
#endif
struct obs_source_frame out = {0};
if (!prepare_obs_frame(obs_pw_stream, &out)) {
blog(LOG_ERROR, "[pipewire] Couldn't prepare frame");
goto done;
}
for (uint32_t i = 0; i < buffer->n_datas && i < MAX_AV_PLANES; i++) {
out.data[i] = buffer->datas[i].data;
if (out.data[i] == NULL) {
blog(LOG_ERROR, "[pipewire] Failed to access data");
goto done;
}
}
#ifdef DEBUG_PIPEWIRE
blog(LOG_DEBUG, "[pipewire] Frame info: Format: %s, Planes: %u",
get_video_format_name(out.format), buffer->n_datas);
for (uint32_t i = 0; i < buffer->n_datas && i < MAX_AV_PLANES; i++) {
blog(LOG_DEBUG, "[pipewire] Plane %u: Dataptr:%p, Linesize:%d",
i, out.data[i], out.linesize[i]);
}
#endif
obs_source_output_video(obs_pw_stream->source, &out);
done:
pw_stream_queue_buffer(obs_pw_stream->stream, b);
}
static void process_video_sync(obs_pipewire_stream *obs_pw_stream)
{
obs_pipewire_stream *obs_pw_stream = user_data;
obs_pipewire *obs_pw = obs_pw_stream->obs_pw;
struct spa_meta_cursor *cursor;
uint32_t drm_format;
@ -599,18 +775,7 @@ static void on_process_cb(void *user_data)
bool swap_red_blue = false;
bool has_buffer = true;
/* Find the most recent buffer */
b = NULL;
while (true) {
struct pw_buffer *aux =
pw_stream_dequeue_buffer(obs_pw_stream->stream);
if (!aux)
break;
if (b)
pw_stream_queue_buffer(obs_pw_stream->stream, b);
b = aux;
}
b = find_latest_buffer(obs_pw_stream->stream);
if (!b) {
blog(LOG_DEBUG, "[pipewire] Out of buffers!");
return;
@ -813,6 +978,21 @@ read_metadata:
obs_leave_graphics();
}
static void on_process_cb(void *user_data)
{
obs_pipewire_stream *obs_pw_stream = user_data;
uint32_t output_flags;
output_flags = obs_source_get_output_flags(obs_pw_stream->source);
if (output_flags & OBS_SOURCE_VIDEO) {
if (output_flags & OBS_SOURCE_ASYNC)
process_video_async(obs_pw_stream);
else
process_video_sync(obs_pw_stream);
}
}
static void on_param_changed_cb(void *user_data, uint32_t id,
const struct spa_pod *param)
{
@ -822,6 +1002,7 @@ static void on_param_changed_cb(void *user_data, uint32_t id,
const struct spa_pod *params[5];
uint32_t n_params = 0;
uint32_t buffer_types;
uint32_t output_flags;
uint8_t params_buffer[1024];
int result;
@ -839,11 +1020,15 @@ static void on_param_changed_cb(void *user_data, uint32_t id,
spa_format_video_raw_parse(param, &obs_pw_stream->format.info.raw);
output_flags = obs_source_get_output_flags(obs_pw_stream->source);
buffer_types = 1 << SPA_DATA_MemPtr;
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))
if ((has_modifier ||
check_pw_version(&obs_pw->server_version, 0, 3, 24)) &&
(output_flags & OBS_SOURCE_ASYNC_VIDEO) != OBS_SOURCE_ASYNC_VIDEO)
buffer_types |= 1 << SPA_DATA_DmaBuf;
blog(LOG_INFO, "[pipewire] Negotiated format:");
@ -1217,9 +1402,15 @@ void obs_pipewire_stream_set_cursor_visible(obs_pipewire_stream *obs_pw_stream,
void obs_pipewire_stream_destroy(obs_pipewire_stream *obs_pw_stream)
{
uint32_t output_flags;
if (!obs_pw_stream)
return;
output_flags = obs_source_get_output_flags(obs_pw_stream->source);
if (output_flags & OBS_SOURCE_ASYNC_VIDEO)
obs_source_output_video(obs_pw_stream->source, NULL);
g_ptr_array_remove(obs_pw_stream->obs_pw->streams, obs_pw_stream);
obs_enter_graphics();