aboutsummaryrefslogtreecommitdiff
path: root/src/pipewire_video.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pipewire_video.c')
-rw-r--r--src/pipewire_video.c170
1 files changed, 131 insertions, 39 deletions
diff --git a/src/pipewire_video.c b/src/pipewire_video.c
index 3c6965e..277004c 100644
--- a/src/pipewire_video.c
+++ b/src/pipewire_video.c
@@ -6,7 +6,7 @@
#include <spa/param/video/format-utils.h>
#include <spa/debug/types.h>
-#include <libdrm/drm_fourcc.h>
+#include <drm_fourcc.h>
#include <fcntl.h>
#include <unistd.h>
@@ -14,7 +14,7 @@
/* This code is partially based on xr-video-player pipewire implementation which is based on obs-studio's pipewire implementation */
/* TODO: Make gsr_pipewire_video_init asynchronous */
-/* TODO: Support 10-bit capture (hdr) when pipewire supports it */
+/* TODO: Support hdr when pipewire supports it */
/* TODO: Test all of the image formats */
#ifndef SPA_POD_PROP_FLAG_DONT_FIXATE
@@ -65,14 +65,20 @@ static void on_core_done_cb(void *user_data, uint32_t id, int seq) {
static bool is_cursor_format_supported(const enum spa_video_format format) {
switch(format) {
- case SPA_VIDEO_FORMAT_RGBx: return true;
- case SPA_VIDEO_FORMAT_BGRx: return true;
- case SPA_VIDEO_FORMAT_xRGB: return true;
- case SPA_VIDEO_FORMAT_xBGR: return true;
- case SPA_VIDEO_FORMAT_RGBA: return true;
- case SPA_VIDEO_FORMAT_BGRA: return true;
- case SPA_VIDEO_FORMAT_ARGB: return true;
- case SPA_VIDEO_FORMAT_ABGR: return true;
+ case SPA_VIDEO_FORMAT_RGBx: return true;
+ case SPA_VIDEO_FORMAT_BGRx: return true;
+ case SPA_VIDEO_FORMAT_RGBA: return true;
+ case SPA_VIDEO_FORMAT_BGRA: return true;
+ case SPA_VIDEO_FORMAT_RGB: return true;
+ case SPA_VIDEO_FORMAT_BGR: return true;
+ case SPA_VIDEO_FORMAT_ARGB: return true;
+ case SPA_VIDEO_FORMAT_ABGR: return true;
+#if PW_CHECK_VERSION(0, 3, 41)
+ case SPA_VIDEO_FORMAT_xRGB_210LE: return true;
+ case SPA_VIDEO_FORMAT_xBGR_210LE: return true;
+ case SPA_VIDEO_FORMAT_ARGB_210LE: return true;
+ case SPA_VIDEO_FORMAT_ABGR_210LE: return true;
+#endif
default: break;
}
return false;
@@ -274,13 +280,21 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
self->negotiated = true;
}
-static void on_state_changed_cb(void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) {
- (void)old;
+static void on_state_changed_cb(void *user_data, enum pw_stream_state prev_state, enum pw_stream_state new_state, const char *error) {
gsr_pipewire_video *self = user_data;
- fprintf(stderr, "gsr info: pipewire: stream %p state: \"%s\" (error: %s)\n",
- (void*)self->stream, pw_stream_state_as_string(state),
+ fprintf(stderr, "gsr info: pipewire: stream %p previous state: \"%s\", new state: \"%s\" (error: %s)\n",
+ (void*)self->stream, pw_stream_state_as_string(prev_state), pw_stream_state_as_string(new_state),
error ? error : "none");
+
+ pthread_mutex_lock(&self->mutex);
+ if(new_state == PW_STREAM_STATE_PAUSED) {
+ self->paused_start_secs = clock_get_monotonic_seconds();
+ self->paused = true;
+ } else {
+ self->paused = false;
+ }
+ pthread_mutex_unlock(&self->mutex);
}
static const struct pw_stream_events stream_events = {
@@ -338,24 +352,46 @@ static inline struct spa_pod *build_format(struct spa_pod_builder *b,
/* For some reason gstreamer formats are in opposite order to drm formats */
static int64_t spa_video_format_to_drm_format(const enum spa_video_format format) {
switch(format) {
- case SPA_VIDEO_FORMAT_RGBx: return DRM_FORMAT_XBGR8888;
- case SPA_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888;
- case SPA_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888;
- case SPA_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888;
- case SPA_VIDEO_FORMAT_RGB: return DRM_FORMAT_XBGR8888;
- case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888;
- default: break;
+ case SPA_VIDEO_FORMAT_RGBx: return DRM_FORMAT_XBGR8888;
+ case SPA_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888;
+ // case SPA_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888;
+ //case SPA_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888;
+ case SPA_VIDEO_FORMAT_RGB: return DRM_FORMAT_XBGR8888;
+ case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888;
+ //case SPA_VIDEO_FORMAT_ARGB: return DRM_FORMAT_XRGB8888;
+ //case SPA_VIDEO_FORMAT_ABGR: return DRM_FORMAT_XRGB8888;
+#if PW_CHECK_VERSION(0, 3, 41)
+ case SPA_VIDEO_FORMAT_xRGB_210LE: return DRM_FORMAT_XRGB2101010;
+ case SPA_VIDEO_FORMAT_xBGR_210LE: return DRM_FORMAT_XBGR2101010;
+ // case SPA_VIDEO_FORMAT_ARGB_210LE: return DRM_FORMAT_ARGB2101010;
+ // case SPA_VIDEO_FORMAT_ABGR_210LE: return DRM_FORMAT_ABGR2101010;
+#endif
+ default: break;
}
return DRM_FORMAT_INVALID;
}
-static const enum spa_video_format video_formats[] = {
- SPA_VIDEO_FORMAT_BGRA,
+#if PW_CHECK_VERSION(0, 3, 41)
+#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS
+#else
+#define GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS 4
+#endif
+
+static const enum spa_video_format video_formats[GSR_PIPEWIRE_VIDEO_MAX_VIDEO_FORMATS] = {
+ // SPA_VIDEO_FORMAT_BGRA,
SPA_VIDEO_FORMAT_BGRx,
SPA_VIDEO_FORMAT_BGR,
SPA_VIDEO_FORMAT_RGBx,
- SPA_VIDEO_FORMAT_RGBA,
+ // SPA_VIDEO_FORMAT_RGBA,
SPA_VIDEO_FORMAT_RGB,
+ // SPA_VIDEO_FORMAT_ARGB,
+ // SPA_VIDEO_FORMAT_ABGR,
+#if PW_CHECK_VERSION(0, 3, 41)
+ SPA_VIDEO_FORMAT_xRGB_210LE,
+ SPA_VIDEO_FORMAT_xBGR_210LE,
+ // SPA_VIDEO_FORMAT_ARGB_210LE,
+ // SPA_VIDEO_FORMAT_ABGR_210LE
+#endif
};
static bool gsr_pipewire_video_build_format_params(gsr_pipewire_video *self, struct spa_pod_builder *pod_builder, struct spa_pod **params, uint32_t *num_params) {
@@ -367,7 +403,7 @@ static bool gsr_pipewire_video_build_format_params(gsr_pipewire_video *self, str
for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) {
if(self->supported_video_formats[i].modifiers_size == 0)
continue;
- params[i] = build_format(pod_builder, &self->video_info, self->supported_video_formats[i].format, self->modifiers + self->supported_video_formats[i].modifiers_index, self->supported_video_formats[i].modifiers_size);
+ params[*num_params] = build_format(pod_builder, &self->video_info, self->supported_video_formats[i].format, self->modifiers + self->supported_video_formats[i].modifiers_index, self->supported_video_formats[i].modifiers_size);
++(*num_params);
}
@@ -382,9 +418,10 @@ static void renegotiate_format(void *data, uint64_t expirations) {
struct spa_pod *params[GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS];
uint32_t num_video_formats = 0;
- uint8_t params_buffer[2048];
+ uint8_t params_buffer[4096];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
if (!gsr_pipewire_video_build_format_params(self, &pod_builder, params, &num_video_formats)) {
+ fprintf(stderr, "gsr error: renegotiate_format: failed to build formats\n");
pw_thread_loop_unlock(self->thread_loop);
return;
}
@@ -413,6 +450,11 @@ static bool spa_video_format_get_modifiers(gsr_pipewire_video *self, const enum
}
const int64_t drm_format = spa_video_format_to_drm_format(format);
+ if(drm_format == DRM_FORMAT_INVALID) {
+ fprintf(stderr, "gsr error: spa_video_format_get_modifiers: unsupported format: %d\n", (int)format);
+ return false;
+ }
+
if(!self->egl->eglQueryDmaBufModifiersEXT(self->egl->egl_display, drm_format, max_modifiers, modifiers, NULL, num_modifiers)) {
fprintf(stderr, "gsr error: spa_video_format_get_modifiers: eglQueryDmaBufModifiersEXT failed with drm format %d, %" PRIi64 "\n", (int)format, drm_format);
//modifiers[0] = DRM_FORMAT_MOD_LINEAR;
@@ -437,13 +479,34 @@ static void gsr_pipewire_video_init_modifiers(gsr_pipewire_video *self) {
spa_video_format_get_modifiers(self, self->supported_video_formats[i].format, self->modifiers + self->num_modifiers, GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS - self->num_modifiers, &num_modifiers);
self->supported_video_formats[i].modifiers_index = self->num_modifiers;
self->supported_video_formats[i].modifiers_size = num_modifiers;
+ self->num_modifiers += num_modifiers;
+ }
+}
+
+static void gsr_pipewire_video_format_remove_modifier(gsr_pipewire_video *self, gsr_video_format *video_format, uint64_t modifier) {
+ for(size_t i = 0; i < video_format->modifiers_size; ++i) {
+ if(self->modifiers[video_format->modifiers_index + i] != modifier)
+ continue;
+
+ for(size_t j = i + 1; j < video_format->modifiers_size; ++j) {
+ self->modifiers[j - 1] = self->modifiers[j];
+ }
+ --video_format->modifiers_size;
+ return;
+ }
+}
+
+static void gsr_pipewire_video_remove_modifier(gsr_pipewire_video *self, uint64_t modifier) {
+ for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) {
+ gsr_video_format *video_format = &self->supported_video_formats[i];
+ gsr_pipewire_video_format_remove_modifier(self, video_format, modifier);
}
}
static bool gsr_pipewire_video_setup_stream(gsr_pipewire_video *self) {
struct spa_pod *params[GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS];
uint32_t num_video_formats = 0;
- uint8_t params_buffer[2048];
+ uint8_t params_buffer[4096];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
self->thread_loop = pw_thread_loop_new("gsr screen capture", NULL);
@@ -476,6 +539,9 @@ static bool gsr_pipewire_video_setup_stream(gsr_pipewire_video *self) {
// TODO: Error check
pw_core_add_listener(self->core, &self->core_listener, &core_events, self);
+ self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, 0);
+ pw_thread_loop_wait(self->thread_loop);
+
gsr_pipewire_video_init_modifiers(self);
// TODO: Cleanup?
@@ -486,9 +552,6 @@ static bool gsr_pipewire_video_setup_stream(gsr_pipewire_video *self) {
goto error;
}
- self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, 0);
- pw_thread_loop_wait(self->thread_loop);
-
self->stream = pw_stream_new(self->core, "com.dec05eba.gpu_screen_recorder",
pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
PW_KEY_MEDIA_CATEGORY, "Capture",
@@ -617,6 +680,7 @@ void gsr_pipewire_video_deinit(gsr_pipewire_video *self) {
self->dmabuf_num_planes = 0;
self->negotiated = false;
+ self->renegotiated = false;
if(self->mutex_initialized) {
pthread_mutex_destroy(&self->mutex);
@@ -668,9 +732,19 @@ static EGLImage gsr_pipewire_video_create_egl_image_with_fallback(gsr_pipewire_v
} else {
image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, true);
if(!image) {
- fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n");
- self->no_modifiers_fallback = true;
- image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false);
+ if(self->renegotiated) {
+ fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n");
+ self->no_modifiers_fallback = true;
+ image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false);
+ } else {
+ fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, renegotiating with a different modifier\n");
+ self->negotiated = false;
+ self->renegotiated = true;
+ gsr_pipewire_video_remove_modifier(self, self->format.info.raw.modifier);
+ pw_thread_loop_lock(self->thread_loop);
+ pw_loop_signal_event(pw_thread_loop_get_loop(self->thread_loop), self->reneg);
+ pw_thread_loop_unlock(self->thread_loop);
+ }
}
}
return image;
@@ -705,8 +779,6 @@ static void gsr_pipewire_video_update_cursor_texture(gsr_pipewire_video *self, g
self->egl->glBindTexture(GL_TEXTURE_2D, texture_map.cursor_texture_id);
// TODO: glTextureSubImage2D if same size
self->egl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, self->cursor.width, self->cursor.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, self->cursor.data);
- self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
self->egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
self->egl->glBindTexture(GL_TEXTURE_2D, 0);
@@ -731,12 +803,15 @@ bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map te
}
EGLImage image = gsr_pipewire_video_create_egl_image_with_fallback(self);
- if(image) {
- gsr_pipewire_video_bind_image_to_texture_with_fallback(self, texture_map, image);
- *using_external_image = self->external_texture_fallback;
- self->egl->eglDestroyImage(self->egl->egl_display, image);
+ if(!image) {
+ pthread_mutex_unlock(&self->mutex);
+ return false;
}
+ gsr_pipewire_video_bind_image_to_texture_with_fallback(self, texture_map, image);
+ *using_external_image = self->external_texture_fallback;
+ self->egl->eglDestroyImage(self->egl->egl_display, image);
+
gsr_pipewire_video_update_cursor_texture(self, texture_map);
region->x = 0;
@@ -774,6 +849,9 @@ bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map te
}
bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self) {
+ if(!self->mutex_initialized)
+ return false;
+
bool damaged = false;
pthread_mutex_lock(&self->mutex);
damaged = self->damaged;
@@ -782,7 +860,21 @@ bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self) {
}
void gsr_pipewire_video_clear_damage(gsr_pipewire_video *self) {
+ if(!self->mutex_initialized)
+ return;
+
pthread_mutex_lock(&self->mutex);
self->damaged = false;
pthread_mutex_unlock(&self->mutex);
}
+
+bool gsr_pipewire_video_should_restart(gsr_pipewire_video *self) {
+ if(!self->mutex_initialized)
+ return false;
+
+ bool should_restart = false;
+ pthread_mutex_lock(&self->mutex);
+ should_restart = self->paused && clock_get_monotonic_seconds() - self->paused_start_secs >= 3.0;
+ pthread_mutex_unlock(&self->mutex);
+ return should_restart;
+}