aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2026-05-29 08:15:09 +0200
committerdec05eba <dec05eba@protonmail.com>2026-05-29 08:15:09 +0200
commit099b95a780805067aec84a62d3110760f0fcbb1a (patch)
treee5637e138eb5f90aee5b3a8a8f6be04f598cb545
parent60f0459f840f568159a320f58d56063ba02e52c4 (diff)
Fix -c whip not working (dont do avio_open for live streams)
-rw-r--r--README.md7
-rw-r--r--TODO5
-rw-r--r--include/args_parser.h1
-rw-r--r--src/args_parser.c8
-rw-r--r--src/main.cpp49
5 files changed, 38 insertions, 32 deletions
diff --git a/README.md b/README.md
index 289c5bf..4ce7221 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ The only official ways to install GPU Screen Recorder is either from source, arc
If you install GPU Screen Recorder from somewhere else and have an issue then try installing it from one of the official sources before reporting it as an issue.\
If you still prefer to install GPU Screen Recorder with a package manager instead of from source or as a flatpak then you may be able to find a package for your distro.\
Here are some known unofficial packages:
-* Ubuntu: [Pacstall](https://pacstall.dev/packages/gpu-screen-recorder)
+* Ubuntu: [gpu-screen-recorder-cli](https://launchpad.net/ubuntu/+source/gpu-screen-recorder)
* Debian: [gpu-screen-recorder-cli](https://tracker.debian.org/pkg/gpu-screen-recorder)
* Nix: [NixOS wiki](https://wiki.nixos.org/wiki/Gpu-screen-recorder)
* openSUSE: [openSUSE software repository](https://software.opensuse.org/package/gpu-screen-recorder)
@@ -122,6 +122,11 @@ By default videos are recorded with constant quality, but with replay mode you m
Streaming works the same way as recording, but the `-o` argument should be path to the live streaming service you want to use (including your live streaming key). Take a look at `scripts/twitch-stream.sh` to see an example of how to stream to twitch.\
GPU Screen Recorder uses Ffmpeg so GPU Screen Recorder supports all protocols that Ffmpeg supports.\
If you want to reduce latency one thing you can do is to use the `-keyint` option, for example `-keyint 0.5`. Lower value means lower latency at the cost of increased bitrate/decreased quality.
+## Very low latency streaming (WHIP)
+FFmpeg since version 7.1 supports the WHIP protocol for very low latency streaming (milliseconds latency). Here is a simple way to set it up:
+1. Download the latest [mediamtx](https://github.com/bluenviron/mediamtx/releases/) binary and run it (just run `./mediamtx`)
+2. Run `gpu-screen-recorder` with WHIP, for example: `gpu-screen-recorder -w screen -f 60 -k h264 -bm cbr -q 20000 -a default_output -ac opus -c whip -o "http://localhost:8889/mystream/whip"`.
+ If you have a high refresh rate monitor you can set `-f 60` framerate option to your monitors framerate (for example `-f 144`) to have even lower latency.
## Recording while using replay/streaming
You can record a regular video while using replay/streaming by launching GPU Screen Recorder with the `-ro` option to specify a directory where to save the recording (for example: `gpu-screen-recorder -w screen -c mp4 -r 60 -o "$HOME/Videos/replays" -ro "$HOME/Videos/recordings"`).\
To start/stop (and save) recording use the SIGRTMIN signal, for example `pkill -SIGRTMIN -f "^gpu-screen-recorder"`. The path to the video will be displayed in stdout when saving the video.\
diff --git a/TODO b/TODO
index 935388e..933df3a 100644
--- a/TODO
+++ b/TODO
@@ -422,3 +422,8 @@ Make -fm content the default, but first make it record at no lower than 2 fps if
Fix camera capture on nvidia x11. It doesn't work with mjpeg either now because external shader compilation fails.
Revert the revert of improving pipewire capture, for better cursor capture. It caused glitches for some, especially in kde plasma when hdr is enabled. Try marking test and moving the cursor and see.
+
+Add the option to use application audio with exectable name, or automatically check against both application name and executable name.
+ In the ui it should also list the application name beside the app audio.
+
+Region capture works incorrectly on gnome with hidpi, especially with 150% scaling. Same in gsr ui.
diff --git a/include/args_parser.h b/include/args_parser.h
index 75d8bfa..2208a9a 100644
--- a/include/args_parser.h
+++ b/include/args_parser.h
@@ -98,6 +98,7 @@ typedef struct {
bool restore_portal_session;
bool restart_replay_on_save;
bool write_first_frame_ts;
+ bool is_replaying;
bool is_livestream;
bool is_output_piped;
bool low_latency_recording;
diff --git a/src/args_parser.c b/src/args_parser.c
index 658e559..1957f32 100644
--- a/src/args_parser.c
+++ b/src/args_parser.c
@@ -398,18 +398,18 @@ static bool args_parser_set_values(args_parser *self) {
if(self->container_format && strcmp(self->container_format, "mkv") == 0)
self->container_format = "matroska";
- const bool is_replaying = self->replay_buffer_size_secs != -1;
+ self->is_replaying = self->replay_buffer_size_secs != -1;
self->is_livestream = false;
self->filename = args_get_value_by_key(self->args, NUM_ARGS, "-o");
if(self->filename) {
self->is_livestream = is_livestream_path(self->filename);
if(self->is_livestream) {
- if(is_replaying) {
+ if(self->is_replaying) {
fprintf(stderr, "gsr error: replay mode is not applicable to live streaming\n");
return false;
}
} else {
- if(!is_replaying) {
+ if(!self->is_replaying) {
char directory_buf[PATH_MAX];
snprintf(directory_buf, sizeof(directory_buf), "%s", self->filename);
char *directory = dirname(directory_buf);
@@ -435,7 +435,7 @@ static bool args_parser_set_values(args_parser *self) {
}
}
} else {
- if(!is_replaying) {
+ if(!self->is_replaying) {
self->filename = "/dev/stdout";
} else {
fprintf(stderr, "gsr error: Option -o is required when using option -r\n");
diff --git a/src/main.cpp b/src/main.cpp
index 147db4f..d109e9e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3026,7 +3026,7 @@ static gsr_audio_codec select_audio_codec_with_fallback(gsr_audio_codec audio_co
break;
}
case GSR_AUDIO_CODEC_OPUS: {
- if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm" && file_extension != "ts") {
+ if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm" && file_extension != "ts" && file_extension != "whip") {
//audio_codec_to_use = "aac";
audio_codec = GSR_AUDIO_CODEC_AAC;
fprintf(stderr, "gsr warning: opus audio codec is only supported by .mp4, .mkv, .webm and .ts files, falling back to aac instead\n");
@@ -3493,30 +3493,18 @@ static bool get_image_format_from_filename(const char *filename, gsr_image_forma
}
}
-// TODO: replace this with start_recording_create_steams
-static bool av_open_file_write_header(AVFormatContext *av_format_context, const char *filename, const char *ffmpeg_opts) {
- int ret = avio_open(&av_format_context->pb, filename, AVIO_FLAG_WRITE);
- if(ret < 0) {
- fprintf(stderr, "gsr error: Could not open '%s': %s\n", filename, av_error_to_string(ret));
- return false;
- }
-
+static void av_write_header(AVFormatContext *av_format_context, const char *ffmpeg_opts) {
AVDictionary *options = nullptr;
av_dict_set(&options, "strict", "experimental", 0);
if(ffmpeg_opts)
av_dict_parse_string(&options, ffmpeg_opts, "=", ";", 0);
- ret = avformat_write_header(av_format_context, &options);
+ const int ret = avformat_write_header(av_format_context, &options);
if(ret < 0)
fprintf(stderr, "Error occurred when writing header to output file: %s\n", av_error_to_string(ret));
- const bool success = ret >= 0;
- if(!success)
- avio_close(av_format_context->pb);
-
av_dict_free(&options);
- return success;
}
static int audio_codec_get_frame_size(gsr_audio_codec audio_codec) {
@@ -3780,8 +3768,6 @@ int main(int argc, char **argv) {
validate_merged_audio_inputs_app_audio(requested_audio_inputs, app_audio_names);
- const bool is_replaying = arg_parser.replay_buffer_size_secs != -1;
-
bool wayland = false;
Display *dpy = XOpenDisplay(nullptr);
if(dpy) {
@@ -3896,6 +3882,9 @@ int main(int argc, char **argv) {
file_extension = file_extension.substr(0, comma_index);
}
+ if(file_extension.empty())
+ file_extension = arg_parser.container_format ? arg_parser.container_format : "";
+
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;
@@ -3927,7 +3916,7 @@ int main(int argc, char **argv) {
const enum AVPixelFormat video_pix_fmt = get_pixel_format(arg_parser.video_codec, egl.gpu_info.vendor, arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU);
AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, video_size.x, video_size.y);
- if(!is_replaying)
+ if(!arg_parser.is_replaying)
video_stream = create_stream(av_format_context, video_codec_context);
AVFrame *video_frame = av_frame_alloc();
@@ -4014,7 +4003,7 @@ int main(int argc, char **argv) {
AVCodecContext *audio_codec_context = create_audio_codec_context(arg_parser.fps, arg_parser.audio_codec, use_amix, arg_parser.audio_bitrate);
AVStream *audio_stream = nullptr;
- if(!is_replaying) {
+ if(!arg_parser.is_replaying) {
audio_stream = create_stream(av_format_context, audio_codec_context);
if(gsr_encoder_add_recording_destination(&encoder, audio_codec_context, av_format_context, audio_stream, 0) == (size_t)-1)
fprintf(stderr, "gsr error: added too many audio sources\n");
@@ -4080,11 +4069,17 @@ int main(int argc, char **argv) {
//av_dump_format(av_format_context, 0, filename, 1);
- if(!is_replaying) {
- if(!av_open_file_write_header(av_format_context, arg_parser.filename, arg_parser.ffmpeg_opts))
+ if(!arg_parser.is_replaying && !(output_format->flags & AVFMT_NOFILE)) {
+ const int ret = avio_open(&av_format_context->pb, arg_parser.filename, AVIO_FLAG_WRITE);
+ if(ret < 0) {
+ fprintf(stderr, "gsr error: Could not open '%s': %s\n", arg_parser.filename, av_error_to_string(ret));
_exit(1);
+ }
}
+ if(!arg_parser.is_replaying)
+ av_write_header(av_format_context, arg_parser.ffmpeg_opts);
+
double fps_start_time = clock_get_monotonic_seconds();
//double frame_timer_start = fps_start_time;
int fps_counter = 0;
@@ -4478,7 +4473,7 @@ int main(int argc, char **argv) {
gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, output_color_conversion);
for(VideoSource &video_source : video_sources) {
- if(hdr && !hdr_metadata_set && !is_replaying && add_hdr_metadata_to_video_stream(video_source.capture, video_stream))
+ if(hdr && !hdr_metadata_set && !arg_parser.is_replaying && add_hdr_metadata_to_video_stream(video_source.capture, video_stream))
hdr_metadata_set = true;
}
@@ -4516,7 +4511,7 @@ int main(int argc, char **argv) {
video_pts_counter += num_missed_frames;
}
- if(toggle_pause == 1 && !is_replaying) {
+ if(toggle_pause == 1 && !arg_parser.is_replaying) {
const bool new_paused_state = !paused;
if(new_paused_state) {
paused_time_start = clock_get_monotonic_seconds();
@@ -4603,7 +4598,7 @@ int main(int argc, char **argv) {
}
}
- if(save_replay_seconds != 0 && !save_replay_thread.valid() && is_replaying) {
+ if(save_replay_seconds != 0 && !save_replay_thread.valid() && arg_parser.is_replaying) {
int current_save_replay_seconds = save_replay_seconds;
if(current_save_replay_seconds > 0)
current_save_replay_seconds += arg_parser.keyint;
@@ -4683,11 +4678,11 @@ int main(int argc, char **argv) {
amix_thread.join();
// TODO: Replace this with start_recording_create_steams
- if(!is_replaying && av_write_trailer(av_format_context) != 0) {
+ if(!arg_parser.is_replaying && av_write_trailer(av_format_context) != 0) {
//fprintf(stderr, "Failed to write trailer\n");
}
- if(!is_replaying) {
+ if(!arg_parser.is_replaying && !(output_format->flags & AVFMT_NOFILE)) {
avio_close(av_format_context->pb);
avformat_free_context(av_format_context);
}
@@ -4708,7 +4703,7 @@ int main(int argc, char **argv) {
gsr_kms_client_deinit(&kms_client);
}
- if(!is_replaying && arg_parser.recording_saved_script)
+ if(!arg_parser.is_replaying && arg_parser.recording_saved_script)
run_recording_saved_script_async(arg_parser.recording_saved_script, arg_parser.filename, "regular");
if(dpy) {