Compare commits

..

3 Commits
5.4.0 ... 5.4.1

Author SHA1 Message Date
dec05eba
06b559ecef 5.4.1 2025-04-23 19:48:42 +02:00
dec05eba
28bc8a0bd2 Update readme 2025-04-23 19:24:52 +02:00
dec05eba
15176579cb Fix replay saving freeze, unable to save replay if audio is not provided 2025-04-23 19:11:58 +02:00
8 changed files with 57 additions and 37 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ tests/compile_commands.json
.vscode/ .vscode/
build/ build/
debug-build/
*.o *.o
gpu-screen-recorder gpu-screen-recorder

View File

@@ -85,9 +85,7 @@ These are the dependencies needed to build GPU Screen Recorder:
* libva (and libva-drm) * libva (and libva-drm)
* libdrm * libdrm
* libcap * libcap
* wayland-client * wayland (wayland-client, wayland-egl, wayland-scanner)
* wayland-egl
* wayland-scanner
## Runtime dependencies ## Runtime dependencies
There are also additional dependencies needed at runtime depending on your GPU vendor: There are also additional dependencies needed at runtime depending on your GPU vendor:
@@ -137,7 +135,8 @@ This can be used for example to show a notification when a replay has been saved
The replay buffer is stored in ram (as encoded video), so don't use a too large replay time and/or video quality unless you have enough ram to store it. The replay buffer is stored in ram (as encoded video), so don't use a too large replay time and/or video quality unless you have enough ram to store it.
## Recording while using replay/streaming ## 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.\ 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.\
To start/stop (and save) recording use the SIGRTMIN signal, for example `pkill -SIGRTMIN -f gpu-screen-recorder`. The name of the video will be displayed in stdout when saving the video. To start/stop (and save) recording use the SIGRTMIN signal, for example `pkill -SIGRTMIN -f gpu-screen-recorder`. The name of the video will be displayed in stdout when saving the video.\
This way of recording while using replay/streaming is more efficient than running GPU Screen Recorder multiple times since this way it only records the screen and encodes the video once.
## Controlling GPU Screen Recorder remotely ## Controlling GPU Screen Recorder remotely
To save a video in replay mode, you need to send signal SIGUSR1 to gpu screen recorder. You can do this by running `pkill -SIGUSR1 -f gpu-screen-recorder`.\ To save a video in replay mode, you need to send signal SIGUSR1 to gpu screen recorder. You can do this by running `pkill -SIGUSR1 -f gpu-screen-recorder`.\
To stop recording send SIGINT to gpu screen recorder. You can do this by running `pkill -SIGINT -f gpu-screen-recorder` or pressing `Ctrl-C` in the terminal that runs gpu screen recorder. When recording a regular non-replay video this will also save the video.\ To stop recording send SIGINT to gpu screen recorder. You can do this by running `pkill -SIGINT -f gpu-screen-recorder` or pressing `Ctrl-C` in the terminal that runs gpu screen recorder. When recording a regular non-replay video this will also save the video.\

View File

@@ -5,6 +5,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <libavcodec/packet.h> #include <libavcodec/packet.h>
typedef struct gsr_replay_buffer gsr_replay_buffer;
typedef struct { typedef struct {
AVPacket packet; AVPacket packet;
int ref_counter; int ref_counter;
@@ -15,15 +17,15 @@ gsr_av_packet* gsr_av_packet_create(const AVPacket *av_packet, double timestamp)
gsr_av_packet* gsr_av_packet_ref(gsr_av_packet *self); gsr_av_packet* gsr_av_packet_ref(gsr_av_packet *self);
void gsr_av_packet_unref(gsr_av_packet *self); void gsr_av_packet_unref(gsr_av_packet *self);
typedef struct { struct gsr_replay_buffer {
gsr_av_packet **packets; gsr_av_packet **packets;
size_t capacity_num_packets; size_t capacity_num_packets;
size_t num_packets; size_t num_packets;
size_t index; size_t index;
pthread_mutex_t mutex; pthread_mutex_t mutex;
bool mutex_initialized; bool mutex_initialized;
bool owns_mutex; gsr_replay_buffer *original_replay_buffer;
} gsr_replay_buffer; };
bool gsr_replay_buffer_init(gsr_replay_buffer *self, size_t replay_buffer_num_packets); bool gsr_replay_buffer_init(gsr_replay_buffer *self, size_t replay_buffer_num_packets);
void gsr_replay_buffer_deinit(gsr_replay_buffer *self); void gsr_replay_buffer_deinit(gsr_replay_buffer *self);
@@ -32,7 +34,7 @@ bool gsr_replay_buffer_append(gsr_replay_buffer *self, const AVPacket *av_packet
void gsr_replay_buffer_clear(gsr_replay_buffer *self); void gsr_replay_buffer_clear(gsr_replay_buffer *self);
gsr_av_packet* gsr_replay_buffer_get_packet_at_index(gsr_replay_buffer *self, size_t index); gsr_av_packet* gsr_replay_buffer_get_packet_at_index(gsr_replay_buffer *self, size_t index);
/* The clone has to be deinitialized before the replay buffer it clones */ /* The clone has to be deinitialized before the replay buffer it clones */
bool gsr_replay_buffer_clone(const gsr_replay_buffer *self, gsr_replay_buffer *destination); bool gsr_replay_buffer_clone(gsr_replay_buffer *self, gsr_replay_buffer *destination);
/* Returns 0 if replay buffer is empty */ /* Returns 0 if replay buffer is empty */
size_t gsr_replay_buffer_find_packet_index_by_time_passed(gsr_replay_buffer *self, int seconds); size_t gsr_replay_buffer_find_packet_index_by_time_passed(gsr_replay_buffer *self, int seconds);
/* Returns -1 if not found */ /* Returns -1 if not found */

View File

@@ -1,4 +1,4 @@
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.4.0', default_options : ['warning_level=2']) project('gpu-screen-recorder', ['c', 'cpp'], version : '5.4.1', default_options : ['warning_level=2'])
add_project_arguments('-Wshadow', language : ['c', 'cpp']) add_project_arguments('-Wshadow', language : ['c', 'cpp'])
if get_option('buildtype') == 'debug' if get_option('buildtype') == 'debug'

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "gpu-screen-recorder" name = "gpu-screen-recorder"
type = "executable" type = "executable"
version = "5.4.0" version = "5.4.1"
platforms = ["posix"] platforms = ["posix"]
[config] [config]

View File

@@ -116,7 +116,7 @@ static bool get_supported_video_codecs(VADisplay va_dpy, gsr_supported_video_cod
int va_minor = 0; int va_minor = 0;
if(vaInitialize(va_dpy, &va_major, &va_minor) != VA_STATUS_SUCCESS) { if(vaInitialize(va_dpy, &va_major, &va_minor) != VA_STATUS_SUCCESS) {
fprintf(stderr, "gsr error: gsr_get_supported_video_codecs_vaapi: vaInitialize failed\n"); fprintf(stderr, "gsr error: gsr_get_supported_video_codecs_vaapi: vaInitialize failed\n");
goto fail; return false;
} }
int num_profiles = vaMaxNumProfiles(va_dpy); int num_profiles = vaMaxNumProfiles(va_dpy);

View File

@@ -1270,13 +1270,13 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
} }
const size_t audio_start_index = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_index, video_stream_index, true); const size_t audio_start_index = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_index, video_stream_index, true);
if(audio_start_index == (size_t)-1) { // if(audio_start_index == (size_t)-1) {
fprintf(stderr, "gsr error: failed to save replay: failed to find an audio keyframe. perhaps replay was saved too fast, before anything has been recorded\n"); // fprintf(stderr, "gsr error: failed to save replay: failed to find an audio keyframe. perhaps replay was saved too fast, before anything has been recorded\n");
return; // return;
} // }
const int64_t video_pts_offset = gsr_replay_buffer_get_packet_at_index(replay_buffer, video_start_index)->packet.pts; const int64_t video_pts_offset = gsr_replay_buffer_get_packet_at_index(replay_buffer, video_start_index)->packet.pts;
const int64_t audio_pts_offset = gsr_replay_buffer_get_packet_at_index(replay_buffer, audio_start_index)->packet.pts; const int64_t audio_pts_offset = audio_start_index == (size_t)-1 ? 0 : gsr_replay_buffer_get_packet_at_index(replay_buffer, audio_start_index)->packet.pts;
gsr_replay_buffer cloned_replay_buffer; gsr_replay_buffer cloned_replay_buffer;
if(!gsr_replay_buffer_clone(replay_buffer, &cloned_replay_buffer)) { if(!gsr_replay_buffer_clone(replay_buffer, &cloned_replay_buffer)) {

View File

@@ -54,7 +54,7 @@ bool gsr_replay_buffer_init(gsr_replay_buffer *self, size_t replay_buffer_num_pa
assert(replay_buffer_num_packets > 0); assert(replay_buffer_num_packets > 0);
memset(self, 0, sizeof(*self)); memset(self, 0, sizeof(*self));
self->mutex_initialized = false; self->mutex_initialized = false;
self->owns_mutex = true; self->original_replay_buffer = NULL;
if(pthread_mutex_init(&self->mutex, NULL) != 0) if(pthread_mutex_init(&self->mutex, NULL) != 0)
return false; return false;
@@ -70,10 +70,28 @@ bool gsr_replay_buffer_init(gsr_replay_buffer *self, size_t replay_buffer_num_pa
return true; return true;
} }
void gsr_replay_buffer_deinit(gsr_replay_buffer *self) { static void gsr_replay_buffer_lock(gsr_replay_buffer *self) {
if(self->original_replay_buffer) {
gsr_replay_buffer_lock(self->original_replay_buffer);
return;
}
if(self->mutex_initialized) if(self->mutex_initialized)
pthread_mutex_lock(&self->mutex); pthread_mutex_lock(&self->mutex);
}
static void gsr_replay_buffer_unlock(gsr_replay_buffer *self) {
if(self->original_replay_buffer) {
gsr_replay_buffer_unlock(self->original_replay_buffer);
return;
}
if(self->mutex_initialized)
pthread_mutex_unlock(&self->mutex);
}
void gsr_replay_buffer_deinit(gsr_replay_buffer *self) {
gsr_replay_buffer_lock(self);
for(size_t i = 0; i < self->num_packets; ++i) { for(size_t i = 0; i < self->num_packets; ++i) {
if(self->packets[i]) { if(self->packets[i]) {
gsr_av_packet_unref(self->packets[i]); gsr_av_packet_unref(self->packets[i]);
@@ -81,9 +99,7 @@ void gsr_replay_buffer_deinit(gsr_replay_buffer *self) {
} }
} }
self->num_packets = 0; self->num_packets = 0;
gsr_replay_buffer_unlock(self);
if(self->mutex_initialized)
pthread_mutex_unlock(&self->mutex);
if(self->packets) { if(self->packets) {
free(self->packets); free(self->packets);
@@ -93,17 +109,19 @@ void gsr_replay_buffer_deinit(gsr_replay_buffer *self) {
self->capacity_num_packets = 0; self->capacity_num_packets = 0;
self->index = 0; self->index = 0;
if(self->mutex_initialized && self->owns_mutex) { if(self->mutex_initialized && !self->original_replay_buffer) {
pthread_mutex_destroy(&self->mutex); pthread_mutex_destroy(&self->mutex);
self->mutex_initialized = false; self->mutex_initialized = false;
} }
self->original_replay_buffer = NULL;
} }
bool gsr_replay_buffer_append(gsr_replay_buffer *self, const AVPacket *av_packet, double timestamp) { bool gsr_replay_buffer_append(gsr_replay_buffer *self, const AVPacket *av_packet, double timestamp) {
pthread_mutex_lock(&self->mutex); gsr_replay_buffer_lock(self);
gsr_av_packet *packet = gsr_av_packet_create(av_packet, timestamp); gsr_av_packet *packet = gsr_av_packet_create(av_packet, timestamp);
if(!packet) { if(!packet) {
pthread_mutex_unlock(&self->mutex); gsr_replay_buffer_unlock(self);
return false; return false;
} }
@@ -118,12 +136,12 @@ bool gsr_replay_buffer_append(gsr_replay_buffer *self, const AVPacket *av_packet
if(self->num_packets > self->capacity_num_packets) if(self->num_packets > self->capacity_num_packets)
self->num_packets = self->capacity_num_packets; self->num_packets = self->capacity_num_packets;
pthread_mutex_unlock(&self->mutex); gsr_replay_buffer_unlock(self);
return true; return true;
} }
void gsr_replay_buffer_clear(gsr_replay_buffer *self) { void gsr_replay_buffer_clear(gsr_replay_buffer *self) {
pthread_mutex_lock(&self->mutex); gsr_replay_buffer_lock(self);
for(size_t i = 0; i < self->num_packets; ++i) { for(size_t i = 0; i < self->num_packets; ++i) {
if(self->packets[i]) { if(self->packets[i]) {
gsr_av_packet_unref(self->packets[i]); gsr_av_packet_unref(self->packets[i]);
@@ -132,7 +150,7 @@ void gsr_replay_buffer_clear(gsr_replay_buffer *self) {
} }
self->num_packets = 0; self->num_packets = 0;
self->index = 0; self->index = 0;
pthread_mutex_unlock(&self->mutex); gsr_replay_buffer_unlock(self);
} }
gsr_av_packet* gsr_replay_buffer_get_packet_at_index(gsr_replay_buffer *self, size_t index) { gsr_av_packet* gsr_replay_buffer_get_packet_at_index(gsr_replay_buffer *self, size_t index) {
@@ -147,17 +165,17 @@ gsr_av_packet* gsr_replay_buffer_get_packet_at_index(gsr_replay_buffer *self, si
return self->packets[offset]; return self->packets[offset];
} }
bool gsr_replay_buffer_clone(const gsr_replay_buffer *self, gsr_replay_buffer *destination) { bool gsr_replay_buffer_clone(gsr_replay_buffer *self, gsr_replay_buffer *destination) {
pthread_mutex_lock(&destination->mutex); gsr_replay_buffer_lock(self);
memset(destination, 0, sizeof(*destination)); memset(destination, 0, sizeof(*destination));
destination->owns_mutex = false; destination->original_replay_buffer = self;
destination->mutex = self->mutex; destination->mutex = self->mutex;
destination->capacity_num_packets = self->capacity_num_packets; destination->capacity_num_packets = self->capacity_num_packets;
destination->mutex_initialized = self->mutex_initialized; destination->mutex_initialized = self->mutex_initialized;
destination->index = self->index; destination->index = self->index;
destination->packets = calloc(destination->capacity_num_packets, sizeof(gsr_av_packet*)); destination->packets = calloc(destination->capacity_num_packets, sizeof(gsr_av_packet*));
if(!destination->packets) { if(!destination->packets) {
pthread_mutex_unlock(&destination->mutex); gsr_replay_buffer_unlock(self);
return false; return false;
} }
@@ -166,17 +184,17 @@ bool gsr_replay_buffer_clone(const gsr_replay_buffer *self, gsr_replay_buffer *d
destination->packets[i] = gsr_av_packet_ref(self->packets[i]); destination->packets[i] = gsr_av_packet_ref(self->packets[i]);
} }
pthread_mutex_unlock(&destination->mutex); gsr_replay_buffer_unlock(self);
return true; return true;
} }
/* Binary search */ /* Binary search */
size_t gsr_replay_buffer_find_packet_index_by_time_passed(gsr_replay_buffer *self, int seconds) { size_t gsr_replay_buffer_find_packet_index_by_time_passed(gsr_replay_buffer *self, int seconds) {
pthread_mutex_lock(&self->mutex); gsr_replay_buffer_lock(self);
const double now = clock_get_monotonic_seconds(); const double now = clock_get_monotonic_seconds();
if(self->num_packets == 0) { if(self->num_packets == 0) {
pthread_mutex_unlock(&self->mutex); gsr_replay_buffer_unlock(self);
return 0; return 0;
} }
@@ -199,14 +217,14 @@ size_t gsr_replay_buffer_find_packet_index_by_time_passed(gsr_replay_buffer *sel
} }
} }
pthread_mutex_unlock(&self->mutex); gsr_replay_buffer_unlock(self);
return index; return index;
} }
size_t gsr_replay_buffer_find_keyframe(gsr_replay_buffer *self, size_t start_index, int stream_index, bool invert_stream_index) { size_t gsr_replay_buffer_find_keyframe(gsr_replay_buffer *self, size_t start_index, int stream_index, bool invert_stream_index) {
assert(start_index < self->num_packets); assert(start_index < self->num_packets);
size_t keyframe_index = (size_t)-1; size_t keyframe_index = (size_t)-1;
pthread_mutex_lock(&self->mutex); gsr_replay_buffer_lock(self);
for(size_t i = start_index; i < self->num_packets; ++i) { for(size_t i = start_index; i < self->num_packets; ++i) {
const gsr_av_packet *packet = gsr_replay_buffer_get_packet_at_index(self, i); const gsr_av_packet *packet = gsr_replay_buffer_get_packet_at_index(self, i);
if((packet->packet.flags & AV_PKT_FLAG_KEY) && (invert_stream_index ? packet->packet.stream_index != stream_index : packet->packet.stream_index == stream_index)) { if((packet->packet.flags & AV_PKT_FLAG_KEY) && (invert_stream_index ? packet->packet.stream_index != stream_index : packet->packet.stream_index == stream_index)) {
@@ -214,6 +232,6 @@ size_t gsr_replay_buffer_find_keyframe(gsr_replay_buffer *self, size_t start_ind
break; break;
} }
} }
pthread_mutex_unlock(&self->mutex); gsr_replay_buffer_unlock(self);
return keyframe_index; return keyframe_index;
} }