mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-04-05 11:06:40 +09:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cafcda1022 | ||
|
|
450bc0ac4a | ||
|
|
8e267bb3b0 | ||
|
|
6e545c7ca0 | ||
|
|
802067d1df | ||
|
|
b55096544b | ||
|
|
e87ade6ee3 | ||
|
|
832052a012 |
5
TODO
5
TODO
@@ -2,7 +2,6 @@ Check for reparent.
|
||||
Quickly changing workspace and back while recording under i3 breaks the screen recorder. i3 probably unmaps windows in other workspaces.
|
||||
See https://trac.ffmpeg.org/wiki/EncodingForStreamingSites for optimizing streaming.
|
||||
Look at VK_EXT_external_memory_dma_buf.
|
||||
Allow setting a different output resolution than the input resolution.
|
||||
Use mov+faststart.
|
||||
Allow recording all monitors/selected monitor without nvfbc by recording the compositor proxy window and only recording the part that matches the monitor(s).
|
||||
Allow recording a region by recording the compositor proxy window / nvfbc window and copying part of it.
|
||||
@@ -10,7 +9,6 @@ Support amf and qsv.
|
||||
Disable flipping on nvidia? this might fix some stuttering issues on some setups. See NvCtrlGetAttribute/NvCtrlSetAttributeAndGetStatus NV_CTRL_SYNC_TO_VBLANK https://github.com/NVIDIA/nvidia-settings/blob/d5f022976368cbceb2f20b838ddb0bf992f0cfb9/src/gtk%2B-2.x/ctkopengl.c.
|
||||
Replays seem to have some issues with audio/video. Why?
|
||||
Cleanup unused gl/egl functions, macro, etc.
|
||||
Add option to disable overlapping of replays (the old behavior kinda. Remove the whole replay buffer data after saving when doing this).
|
||||
Set audio track name to audio device name (if not merge of multiple audio devices).
|
||||
Add support for webcam, but only really for amd/intel because amd/intel can get drm fd access to webcam, nvidia cant. This allows us to create an opengl texture directly from the webcam fd for optimal performance.
|
||||
Reverse engineer nvapi so we can disable "force p2 state" on linux too (nvapi profile api with the settings id 0x50166c5e).
|
||||
@@ -206,6 +204,7 @@ Ffmpeg fixed black bars in videos on amd when using hevc and when recording at s
|
||||
https://github.com/FFmpeg/FFmpeg/commit/bcfbf2bac8f9eeeedc407b40596f5c7aaa0d5b47
|
||||
https://github.com/FFmpeg/FFmpeg/commit/d0facac679faf45d3356dff2e2cb382580d7a521
|
||||
Disable gpu screen recorder black bar handling when using hevc on amd when the libavcodec version is the one that comes after those commits.
|
||||
Also consider the mesa version, to see if the gpu supports this.
|
||||
|
||||
Use opengl compute shader instead of graphics shader. This might allow for better performance when games are using 100% of graphics unit which might fix issue with 100% gpu usage causing gpu screen recorder to run slow when not using vaapi to convert rgb to nv12(?).
|
||||
|
||||
@@ -219,3 +218,5 @@ Always disable prime run/dri prime and list all monitors to record from from all
|
||||
|
||||
Allow flv av1 if recent ffmpeg version and streaming to youtube (and twitch?) and for custom services.
|
||||
Use explicit sync in pipewire video code: https://docs.pipewire.org/page_dma_buf.html.
|
||||
|
||||
Support vaapi rotation. Support for it is added in mesa here: https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32919.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.0.1', default_options : ['warning_level=2'])
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.1.0', default_options : ['warning_level=2'])
|
||||
|
||||
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
|
||||
if get_option('buildtype') == 'debug'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gpu-screen-recorder"
|
||||
type = "executable"
|
||||
version = "5.0.1"
|
||||
version = "5.1.0"
|
||||
platforms = ["posix"]
|
||||
|
||||
[config]
|
||||
|
||||
84
src/main.cpp
84
src/main.cpp
@@ -1069,7 +1069,7 @@ static void open_video_hardware(AVCodecContext *codec_context, VideoQuality vide
|
||||
static void usage_header() {
|
||||
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
|
||||
const char *program_name = inside_flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder" : "gpu-screen-recorder";
|
||||
printf("usage: %s -w <window_id|monitor|focused|portal> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-k h264|hevc|av1|vp8|vp9|hevc_hdr|av1_hdr|hevc_10bit|av1_10bit] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-bm auto|qp|vbr|cbr] [-cr limited|full] [-df yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] [-o <output_file>] [--list-capture-options [card_path] [vendor]] [--list-audio-devices] [--list-application-audio] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
|
||||
printf("usage: %s -w <window_id|monitor|focused|portal> [-c <container_format>] [-s WxH] -f <fps> [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-restart-replay-on-save yes|no] [-k h264|hevc|av1|vp8|vp9|hevc_hdr|av1_hdr|hevc_10bit|av1_10bit] [-ac aac|opus|flac] [-ab <bitrate>] [-oc yes|no] [-fm cfr|vfr|content] [-bm auto|qp|vbr|cbr] [-cr limited|full] [-df yes|no] [-sc <script_path>] [-cursor yes|no] [-keyint <value>] [-restore-portal-session yes|no] [-portal-session-token-filepath filepath] [-encoder gpu|cpu] [-o <output_file>] [--list-capture-options [card_path] [vendor]] [--list-audio-devices] [--list-application-audio] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -1130,6 +1130,12 @@ static void usage_full() {
|
||||
printf(" Note that the video data is stored in RAM, so don't use too long replay buffer time and use constant bitrate option (-bm cbr) to prevent RAM usage from going too high in busy scenes.\n");
|
||||
printf(" Optional, disabled by default.\n");
|
||||
printf("\n");
|
||||
printf(" -restart-replay-on-save\n");
|
||||
printf(" Restart replay on save. For example if this is set to 'no' and replay time (-r) is set to 60 seconds and a replay is saved once then the first replay video is 60 seconds long\n");
|
||||
printf(" and if a replay is saved 10 seconds later then the second replay video will also be 60 seconds long and contain 50 seconds of the previous video as well.\n");
|
||||
printf(" If this is set to 'yes' then after a replay is saved the replay buffer data is cleared and the second replay will start from that point onward.\n");
|
||||
printf(" Optional, set to 'no' by default.\n");
|
||||
printf("\n");
|
||||
printf(" -k Video codec to use. Should be either 'auto', 'h264', 'hevc', 'av1', 'vp8', 'vp9', 'hevc_hdr', 'av1_hdr', 'hevc_10bit' or 'av1_10bit'.\n");
|
||||
printf(" Optional, set to 'auto' by default which defaults to 'h264'. Forcefully set to 'h264' if the file container type is 'flv'.\n");
|
||||
printf(" 'hevc_hdr' and 'av1_hdr' option is not available on X11 nor when using the portal capture option.\n");
|
||||
@@ -1309,7 +1315,7 @@ static std::string get_date_str() {
|
||||
time_t now = time(NULL);
|
||||
struct tm *t = localtime(&now);
|
||||
strftime(str, sizeof(str)-1, "%Y-%m-%d_%H-%M-%S", t);
|
||||
return str;
|
||||
return str;
|
||||
}
|
||||
|
||||
static std::string get_date_only_str() {
|
||||
@@ -1481,7 +1487,7 @@ static std::string save_replay_output_filepath;
|
||||
static void save_replay_async(AVCodecContext *video_codec_context, int video_stream_index, std::vector<AudioTrack> &audio_tracks, std::deque<std::shared_ptr<PacketData>> &frame_data_queue, bool frames_erased, std::string output_dir, const char *container_format, const std::string &file_extension, std::mutex &write_output_mutex, bool date_folders, bool hdr, gsr_capture *capture) {
|
||||
if(save_replay_thread.valid())
|
||||
return;
|
||||
|
||||
|
||||
size_t start_index = (size_t)-1;
|
||||
int64_t video_pts_offset = 0;
|
||||
int64_t audio_pts_offset = 0;
|
||||
@@ -1502,7 +1508,7 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
|
||||
|
||||
if(frames_erased) {
|
||||
video_pts_offset = frame_data_queue[start_index]->data.pts;
|
||||
|
||||
|
||||
// Find the next audio packet to use as audio pts offset
|
||||
for(size_t i = start_index; i < frame_data_queue.size(); ++i) {
|
||||
const AVPacket &av_packet = frame_data_queue[i]->data;
|
||||
@@ -1715,26 +1721,26 @@ static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph
|
||||
char ch_layout[64];
|
||||
int err = 0;
|
||||
ch_layout[0] = '\0';
|
||||
|
||||
|
||||
AVFilterGraph *filter_graph = avfilter_graph_alloc();
|
||||
if (!filter_graph) {
|
||||
fprintf(stderr, "Unable to create filter graph.\n");
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
|
||||
for(size_t i = 0; i < num_sources; ++i) {
|
||||
const AVFilter *abuffer = avfilter_get_by_name("abuffer");
|
||||
if (!abuffer) {
|
||||
fprintf(stderr, "Could not find the abuffer filter.\n");
|
||||
return AVERROR_FILTER_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
AVFilterContext *abuffer_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, NULL);
|
||||
if (!abuffer_ctx) {
|
||||
fprintf(stderr, "Could not allocate the abuffer instance.\n");
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 60
|
||||
av_get_channel_layout_string(ch_layout, sizeof(ch_layout), 0, AV_CH_LAYOUT_STEREO);
|
||||
#else
|
||||
@@ -1745,7 +1751,7 @@ static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph
|
||||
av_opt_set_q (abuffer_ctx, "time_base", audio_codec_context->time_base, AV_OPT_SEARCH_CHILDREN);
|
||||
av_opt_set_int(abuffer_ctx, "sample_rate", audio_codec_context->sample_rate, AV_OPT_SEARCH_CHILDREN);
|
||||
av_opt_set_int(abuffer_ctx, "bit_rate", audio_codec_context->bit_rate, AV_OPT_SEARCH_CHILDREN);
|
||||
|
||||
|
||||
err = avfilter_init_str(abuffer_ctx, NULL);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "Could not initialize the abuffer filter.\n");
|
||||
@@ -1760,35 +1766,42 @@ static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph
|
||||
av_log(NULL, AV_LOG_ERROR, "Could not find the mix filter.\n");
|
||||
return AVERROR_FILTER_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
#if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(7, 107, 100)
|
||||
bool normalize = false;
|
||||
char args[512];
|
||||
snprintf(args, sizeof(args), "inputs=%d:normalize=%s", (int)num_sources, normalize ? "true" : "false");
|
||||
#else
|
||||
char args[512];
|
||||
snprintf(args, sizeof(args), "inputs=%d", (int)num_sources);
|
||||
|
||||
fprintf(stderr, "Warning: your ffmpeg version doesn't support disabling normalizing of mixed audio. Volume might be lower than expected\n");
|
||||
#endif
|
||||
|
||||
AVFilterContext *mix_ctx;
|
||||
err = avfilter_graph_create_filter(&mix_ctx, mix_filter, "amix", args, NULL, filter_graph);
|
||||
if (err < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Cannot create audio amix filter\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
const AVFilter *abuffersink = avfilter_get_by_name("abuffersink");
|
||||
if (!abuffersink) {
|
||||
fprintf(stderr, "Could not find the abuffersink filter.\n");
|
||||
return AVERROR_FILTER_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
AVFilterContext *abuffersink_ctx = avfilter_graph_alloc_filter(filter_graph, abuffersink, "sink");
|
||||
if (!abuffersink_ctx) {
|
||||
fprintf(stderr, "Could not allocate the abuffersink instance.\n");
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
|
||||
err = avfilter_init_str(abuffersink_ctx, NULL);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "Could not initialize the abuffersink instance.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
err = 0;
|
||||
for(size_t i = 0; i < src_filter_ctx.size(); ++i) {
|
||||
AVFilterContext *src_ctx = src_filter_ctx[i];
|
||||
@@ -1801,16 +1814,16 @@ static int init_filter_graph(AVCodecContext *audio_codec_context, AVFilterGraph
|
||||
av_log(NULL, AV_LOG_ERROR, "Error connecting filters\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
err = avfilter_graph_config(filter_graph, NULL);
|
||||
if (err < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Error configuring the filter graph\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
*graph = filter_graph;
|
||||
*sink = abuffersink_ctx;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2148,6 +2161,7 @@ static void info_command() {
|
||||
puts("is_steam_deck|yes");
|
||||
else
|
||||
puts("is_steam_deck|no");
|
||||
printf("gsr_version|%s\n", GSR_VERSION);
|
||||
puts("section=gpu_info");
|
||||
list_gpu_info(&egl);
|
||||
puts("section=video_codecs");
|
||||
@@ -2626,7 +2640,7 @@ static AudioCodec select_audio_codec_with_fallback(AudioCodec audio_codec, const
|
||||
}
|
||||
|
||||
static const char* video_codec_to_string(VideoCodec video_codec) {
|
||||
switch(video_codec) {
|
||||
switch(video_codec) {
|
||||
case VideoCodec::H264: return "h264";
|
||||
case VideoCodec::HEVC: return "hevc";
|
||||
case VideoCodec::HEVC_HDR: return "hevc_hdr";
|
||||
@@ -2643,7 +2657,7 @@ static const char* video_codec_to_string(VideoCodec video_codec) {
|
||||
}
|
||||
|
||||
static bool video_codec_only_supports_low_power_mode(const gsr_supported_video_codecs &supported_video_codecs, VideoCodec video_codec) {
|
||||
switch(video_codec) {
|
||||
switch(video_codec) {
|
||||
case VideoCodec::H264: return supported_video_codecs.h264.low_power;
|
||||
case VideoCodec::HEVC: return supported_video_codecs.hevc.low_power;
|
||||
case VideoCodec::HEVC_HDR: return supported_video_codecs.hevc_hdr.low_power;
|
||||
@@ -3055,6 +3069,7 @@ int main(int argc, char **argv) {
|
||||
{ "-q", Arg { {}, true, false } },
|
||||
{ "-o", Arg { {}, true, false } },
|
||||
{ "-r", Arg { {}, true, false } },
|
||||
{ "-restart-replay-on-save", Arg { {}, true, false } },
|
||||
{ "-k", Arg { {}, true, false } },
|
||||
{ "-ac", Arg { {}, true, false } },
|
||||
{ "-ab", Arg { {}, true, false } },
|
||||
@@ -3381,6 +3396,20 @@ int main(int argc, char **argv) {
|
||||
replay_buffer_size_secs += std::ceil(keyint); // Add a few seconds to account of lost packets because of non-keyframe packets skipped
|
||||
}
|
||||
|
||||
bool restart_replay_on_save = false;
|
||||
const char *restart_replay_on_save_str = args["-restart-replay-on-save"].value();
|
||||
if(!restart_replay_on_save_str)
|
||||
restart_replay_on_save_str = "no";
|
||||
|
||||
if(strcmp(restart_replay_on_save_str, "yes") == 0) {
|
||||
restart_replay_on_save = true;
|
||||
} else if(strcmp(restart_replay_on_save_str, "no") == 0) {
|
||||
restart_replay_on_save = false;
|
||||
} else {
|
||||
fprintf(stderr, "Error: -restart-replay-on-save should either be either 'yes' or 'no', got: '%s'\n", restart_replay_on_save_str);
|
||||
usage();
|
||||
}
|
||||
|
||||
std::string window_str = args["-w"].value();
|
||||
const bool is_portal_capture = strcmp(window_str.c_str(), "portal") == 0;
|
||||
|
||||
@@ -3866,6 +3895,7 @@ int main(int argc, char **argv) {
|
||||
std::mutex audio_filter_mutex;
|
||||
|
||||
const double record_start_time = clock_get_monotonic_seconds();
|
||||
std::atomic<double> replay_start_time(record_start_time);
|
||||
std::deque<std::shared_ptr<PacketData>> frame_data_queue;
|
||||
bool frames_erased = false;
|
||||
|
||||
@@ -3984,7 +4014,7 @@ int main(int argc, char **argv) {
|
||||
ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
|
||||
if(ret >= 0) {
|
||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||
receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_device.frame->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_device.frame->pts, av_format_context, replay_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to encode audio!\n");
|
||||
}
|
||||
@@ -4016,7 +4046,7 @@ int main(int argc, char **argv) {
|
||||
ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
|
||||
if(ret >= 0) {
|
||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||
receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_device.frame->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, audio_device.frame->pts, av_format_context, replay_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to encode audio!\n");
|
||||
}
|
||||
@@ -4050,7 +4080,7 @@ int main(int argc, char **argv) {
|
||||
err = avcodec_send_frame(audio_track.codec_context, aframe);
|
||||
if(err >= 0){
|
||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||
receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
receive_frames(audio_track.codec_context, audio_track.stream_index, audio_track.stream, aframe->pts, av_format_context, replay_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to encode audio!\n");
|
||||
}
|
||||
@@ -4196,7 +4226,7 @@ int main(int argc, char **argv) {
|
||||
if(ret == 0) {
|
||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||
receive_frames(video_codec_context, VIDEO_STREAM_INDEX, video_stream, video_frame->pts, av_format_context,
|
||||
record_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
replay_start_time, frame_data_queue, replay_buffer_size_secs, frames_erased, write_output_mutex, paused_time_offset);
|
||||
} else {
|
||||
fprintf(stderr, "Error: avcodec_send_frame failed, error: %s\n", av_error_to_string(ret));
|
||||
}
|
||||
@@ -4225,8 +4255,14 @@ int main(int argc, char **argv) {
|
||||
fflush(stdout);
|
||||
if(recording_saved_script)
|
||||
run_recording_saved_script_async(recording_saved_script, save_replay_output_filepath.c_str(), "replay");
|
||||
|
||||
std::lock_guard<std::mutex> lock(write_output_mutex);
|
||||
save_replay_packets.clear();
|
||||
if(restart_replay_on_save) {
|
||||
frame_data_queue.clear();
|
||||
frames_erased = true;
|
||||
replay_start_time = clock_get_monotonic_seconds() - paused_time_offset;
|
||||
}
|
||||
}
|
||||
|
||||
if(save_replay == 1 && !save_replay_thread.valid() && replay_buffer_size_secs != -1) {
|
||||
|
||||
Reference in New Issue
Block a user