mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-07-19 01:29:18 +00:00
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:
parent
c7e355e89c
commit
67f01c7a44
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue