From 59d16899ab4b4bee6e6a5cfc37f8a721648f892e Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 6 Sep 2025 00:18:12 +0200 Subject: [PATCH] Use pipewire audio routing to merge audio when possible (this fixes out of sync audio when using multiple audio inputs for some users) --- README.md | 5 +++++ TODO | 6 ++++++ src/main.cpp | 40 ++++++++++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 92538c9..327943c 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,11 @@ An example plugin can be found at `plugin/examples/hello_triangle`.\ Run `gpu-screen-recorder` with the `-p` option to specify a plugin to load, for example `gpu-screen-recorder -w screen -p ./triangle.so -o video.mp4`. `-p` can be specified multiple times to load multiple plugins.\ Build GPU Screen Recorder with the `-Dplugin_examples=true` meson option to build plugin examples. +## Smoother recording +If you record at your monitors refresh rate and enabled vsync in a game then there might be a desync between the game updating a frame and GPU Screen Recorder capturing a frame. +This is an issue in some games. +If you experience this issue then you might want to either disable vsync in the game or use the `-fm content` option to sync capture to the content on the screen. For example: `gpu-screen-recorder -w screen -fm content -o video.mp4`.\ +Note that this option is currently only available on X11 (it's not really possible to do on Wayland or it's a hit or miss). # Issues ## NVIDIA Nvidia drivers have an issue where CUDA breaks if CUDA is running when suspend/hibernation happens, and it remains broken until you reload the nvidia driver. `extra/gsr-nvidia.conf` will be installed by default when you install GPU Screen Recorder and that should fix this issue. If this doesn't fix the issue for you then your distro may use a different path for modprobe files. In that case you have to install that `extra/gsr-nvidia.conf` yourself into that location. diff --git a/TODO b/TODO index 33ee8d1..6ac52b5 100644 --- a/TODO +++ b/TODO @@ -104,6 +104,9 @@ Enable b-frames. Support vfr matching games exact fps all the time. On x11 use damage tracking, on wayland? maybe there is drm plane damage tracking. But that may not be accurate as the compositor may update it every monitor hz anyways. On wayland maybe only support it for desktop portal + pipewire capture. Another method to track damage that works regardless of the display server would be to do a diff between frames with a shader. + A 1x1 texture could be created and then write to the texture with imageStore in glsl. + Multiple textures aren't needed for diff, the diff between the color conversion output can be done by using it as an input + as well, which would diff it against the previous frame. Support selecting which gpu to use. This can be done in egl with eglQueryDevicesEXT and then eglGetPlatformDisplayEXT. This will automatically work on AMD and Intel as vaapi uses the same device. On nvidia we need to use eglQueryDeviceAttribEXT with EGL_CUDA_DEVICE_NV. Maybe on glx (nvidia x11 nvfbc) we need to use __NV_PRIME_RENDER_OFFLOAD, __NV_PRIME_RENDER_OFFLOAD_PROVIDER, __GLX_VENDOR_LIBRARY_NAME, __VK_LAYER_NV_optimus, VK_ICD_FILENAMES instead. Just look at prime-run /usr/bin/prime-run. @@ -329,3 +332,6 @@ KDE Plasma Wayland seems to use overlay planes now in non-fullscreen mode(limite If it is, then support it in kms capture. Support -fm content with pipewire (enable damage tracking in pipewire again and check if it actually works). + +Check if pipewire audio link-factory is available before attempting to use app audio or merging audio with pipewire. + Also do the same in supports_app_audio check in gpu-screen-recorder --info output. \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4a40144..e3bc3ce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,6 +76,12 @@ static const int VIDEO_STREAM_INDEX = 0; static thread_local char av_error_buffer[AV_ERROR_MAX_STRING_SIZE]; +enum class AudioMergeType { + NONE, + AMIX, + PIPEWIRE +}; + typedef struct { const gsr_window *window; } MonitorOutputCallbackUserdata; @@ -3119,12 +3125,24 @@ int main(int argc, char **argv) { std::vector requested_audio_inputs = parse_audio_inputs(audio_devices, audio_input_arg); const bool uses_app_audio = merged_audio_inputs_has_app_audio(requested_audio_inputs); + AudioMergeType audio_merge_type = AudioMergeType::NONE; std::vector app_audio_names; #ifdef GSR_APP_AUDIO + const bool audio_server_is_pipewire = audio_input_arg->num_values > 0 && pulseaudio_server_is_pipewire(); + if(merged_audio_inputs_should_use_amix(requested_audio_inputs)) { + if(audio_server_is_pipewire || uses_app_audio) + audio_merge_type = AudioMergeType::PIPEWIRE; + else + audio_merge_type = AudioMergeType::AMIX; + } + gsr_pipewire_audio pipewire_audio; memset(&pipewire_audio, 0, sizeof(pipewire_audio)); - if(uses_app_audio) { - if(!pulseaudio_server_is_pipewire()) { + // TODO: When recording multiple audio devices and merging them (for example desktop audio and microphone) then one (or more) of the audio sources + // can get desynced. I'm unable to reproduce this but some others are. Instead of merging audio with ffmpeg amix, merge audio with pipewire (if available). + // This fixes the issue for people that had the issue. + if(audio_merge_type == AudioMergeType::PIPEWIRE || uses_app_audio) { + if(!audio_server_is_pipewire) { fprintf(stderr, "gsr error: your sound server is not PipeWire. Application audio is only available when running PipeWire audio server\n"); _exit(2); } @@ -3140,6 +3158,14 @@ int main(int argc, char **argv) { return true; }, &app_audio_names); } +#else + if(merged_audio_inputs_should_use_amix(requested_audio_inputs)) + audio_merge_type = AudioMergeType::AMIX; + + if(uses_app_audio) { + fprintf(stderr, "gsr error: application audio can't be recorded because GPU Screen Recorder is built without application audio support (-Dapp_audio option)\n"); + _exit(2); + } #endif validate_merged_audio_inputs_app_audio(requested_audio_inputs, app_audio_names); @@ -3245,8 +3271,7 @@ int main(int argc, char **argv) { const bool force_no_audio_offset = arg_parser.is_livestream || arg_parser.is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm"); const double target_fps = 1.0 / (double)arg_parser.fps; - const bool uses_amix = merged_audio_inputs_should_use_amix(requested_audio_inputs); - arg_parser.audio_codec = select_audio_codec_with_fallback(arg_parser.audio_codec, file_extension, uses_amix); + arg_parser.audio_codec = select_audio_codec_with_fallback(arg_parser.audio_codec, file_extension, audio_merge_type == AudioMergeType::AMIX); gsr_capture *capture = create_capture_impl(arg_parser, &egl, false); @@ -3403,7 +3428,7 @@ int main(int argc, char **argv) { std::vector src_filter_ctx; AVFilterGraph *graph = nullptr; AVFilterContext *sink = nullptr; - if(use_amix) { + if(use_amix && audio_merge_type == AudioMergeType::AMIX) { int err = init_filter_graph(audio_codec_context, &graph, &sink, src_filter_ctx, merged_audio_inputs.audio_inputs.size()); if(err < 0) { fprintf(stderr, "gsr error: failed to create audio filter\n"); @@ -3420,8 +3445,7 @@ int main(int argc, char **argv) { const double num_audio_frames_shift = audio_startup_time_seconds / timeout_sec; std::vector audio_track_audio_devices; - if(audio_inputs_has_app_audio(merged_audio_inputs.audio_inputs)) { - assert(!use_amix); + if((use_amix && audio_merge_type == AudioMergeType::PIPEWIRE) || audio_inputs_has_app_audio(merged_audio_inputs.audio_inputs)) { #ifdef GSR_APP_AUDIO audio_track_audio_devices.push_back(create_application_audio_audio_input(merged_audio_inputs, audio_codec_context, num_channels, num_audio_frames_shift, &pipewire_audio)); #endif @@ -3636,7 +3660,7 @@ int main(int argc, char **argv) { } std::thread amix_thread; - if(uses_amix) { + if(audio_merge_type == AudioMergeType::AMIX) { amix_thread = std::thread([&]() { AVFrame *aframe = av_frame_alloc(); while(running) {