diff options
author | dec05eba <dec05eba@protonmail.com> | 2025-05-04 20:36:34 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2025-05-04 21:24:07 +0200 |
commit | 36c74d3411a543c4452d367275182e0a8619cec4 (patch) | |
tree | 719bda3bd2c97472c54122253070ce39835910f3 /src | |
parent | 2ca5f3361c341dee5087a109a721103dd95869ff (diff) |
Add -replay-storage option to specify if temporary replay data should be stored in ram or disk
Diffstat (limited to 'src')
-rw-r--r-- | src/args_parser.c | 19 | ||||
-rw-r--r-- | src/encoder/encoder.c | 17 | ||||
-rw-r--r-- | src/main.cpp | 89 | ||||
-rw-r--r-- | src/replay_buffer.c | 237 | ||||
-rw-r--r-- | src/replay_buffer/replay_buffer.c | 91 | ||||
-rw-r--r-- | src/replay_buffer/replay_buffer_disk.c | 429 | ||||
-rw-r--r-- | src/replay_buffer/replay_buffer_ram.c | 256 |
7 files changed, 866 insertions, 272 deletions
diff --git a/src/args_parser.c b/src/args_parser.c index 7aee396..a678691 100644 --- a/src/args_parser.c +++ b/src/args_parser.c @@ -69,6 +69,11 @@ static const ArgEnum tune_enums[] = { { .name = "quality", .value = GSR_TUNE_QUALITY }, }; +static const ArgEnum replay_storage_enums[] = { + { .name = "ram", .value = GSR_REPLAY_STORAGE_RAM }, + { .name = "disk", .value = GSR_REPLAY_STORAGE_DISK }, +}; + static void arg_deinit(Arg *arg) { if(arg->values) { free(arg->values); @@ -185,7 +190,7 @@ static double args_get_double_by_key(Arg *args, int num_args, const char *key, d 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|region> [-c <container_format>] [-s WxH] [-region WxH+X+Y] [-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] [-tune performance|quality] [-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>] [-ro <output_directory>] [--list-capture-options [card_path]] [--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|region> [-c <container_format>] [-s WxH] [-region WxH+X+Y] [-f <fps>] [-a <audio_input>] [-q <quality>] [-r <replay_buffer_size_sec>] [-replay-storage ram|disk] [-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] [-tune performance|quality] [-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>] [-ro <output_directory>] [--list-capture-options [card_path]] [--list-audio-devices] [--list-application-audio] [-v yes|no] [-gl-debug yes|no] [--version] [-h|--help]\n", program_name); fflush(stdout); } @@ -249,9 +254,14 @@ static void usage_full() { printf("\n"); printf(" -r Replay buffer time in seconds. If this is set, then only the last seconds as set by this option will be stored\n"); printf(" and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature This option has be between 5 and 1200.\n"); - 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(" Note that the video data is stored in RAM (unless -replay-storage disk is used), 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(" -replay-storage\n"); + printf(" Specify where temporary replay is stored. Should be either 'ram' or 'disk'. If set to 'disk' then replay data is stored in temporary files in the same directory as -o.\n"); + printf(" Preferably avoid setting this to 'disk' unless -o is set to a HDD, as constant writes to a SSD can reduce the life-time of the SSD.\n"); + printf(" Optional, set to 'ram' 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"); @@ -389,6 +399,7 @@ static void usage_full() { printf(" %s -w screen -f 60 -a default_output -a default_input -o video.mp4\n", program_name); printf(" %s -w screen -f 60 -a \"default_output|default_input\" -o video.mp4\n", program_name); printf(" %s -w screen -f 60 -a default_output -c mkv -r 60 -o \"$HOME/Videos\"\n", program_name); + printf(" %s -w screen -f 60 -a default_output -c mkv -r 1800 -replay-storage disk -bm cbr -q 40000 -o \"$HOME/Videos\"\n", program_name); printf(" %s -w screen -f 60 -a default_output -c mkv -sc script.sh -r 60 -o \"$HOME/Videos\"\n", program_name); printf(" %s -w portal -f 60 -a default_output -restore-portal-session yes -o video.mp4\n", program_name); printf(" %s -w screen -f 60 -a default_output -bm cbr -q 15000 -o video.mp4\n", program_name); @@ -436,6 +447,7 @@ static bool args_parser_set_values(args_parser *self) { self->video_codec = (gsr_video_codec)args_get_enum_by_key(self->args, NUM_ARGS, "-k", GSR_VIDEO_CODEC_AUTO); self->audio_codec = (gsr_audio_codec)args_get_enum_by_key(self->args, NUM_ARGS, "-ac", GSR_AUDIO_CODEC_OPUS); self->bitrate_mode = (gsr_bitrate_mode)args_get_enum_by_key(self->args, NUM_ARGS, "-bm", GSR_BITRATE_MODE_AUTO); + self->replay_storage = (gsr_replay_storage)args_get_enum_by_key(self->args, NUM_ARGS, "-replay-storage", GSR_REPLAY_STORAGE_RAM); const char *window = args_get_value_by_key(self->args, NUM_ARGS, "-w"); snprintf(self->window, sizeof(self->window), "%s", window); @@ -712,7 +724,7 @@ bool args_parser_parse(args_parser *self, int argc, char **argv, const args_hand self->args[arg_index++] = (Arg){ .key = "-q", .optional = true, .list = false, .type = ARG_TYPE_STRING }; self->args[arg_index++] = (Arg){ .key = "-o", .optional = true, .list = false, .type = ARG_TYPE_STRING }; self->args[arg_index++] = (Arg){ .key = "-ro", .optional = true, .list = false, .type = ARG_TYPE_STRING }; - self->args[arg_index++] = (Arg){ .key = "-r", .optional = true, .list = false, .type = ARG_TYPE_I64, .integer_value_min = 2, .integer_value_max = 10800 }; + self->args[arg_index++] = (Arg){ .key = "-r", .optional = true, .list = false, .type = ARG_TYPE_I64, .integer_value_min = 2, .integer_value_max = 86400 }; self->args[arg_index++] = (Arg){ .key = "-restart-replay-on-save", .optional = true, .list = false, .type = ARG_TYPE_BOOLEAN }; self->args[arg_index++] = (Arg){ .key = "-k", .optional = true, .list = false, .type = ARG_TYPE_ENUM, .enum_values = video_codec_enums, .num_enum_values = sizeof(video_codec_enums)/sizeof(ArgEnum) }; self->args[arg_index++] = (Arg){ .key = "-ac", .optional = true, .list = false, .type = ARG_TYPE_ENUM, .enum_values = audio_codec_enums, .num_enum_values = sizeof(audio_codec_enums)/sizeof(ArgEnum) }; @@ -732,6 +744,7 @@ bool args_parser_parse(args_parser *self, int argc, char **argv, const args_hand self->args[arg_index++] = (Arg){ .key = "-restore-portal-session", .optional = true, .list = false, .type = ARG_TYPE_BOOLEAN }; self->args[arg_index++] = (Arg){ .key = "-portal-session-token-filepath", .optional = true, .list = false, .type = ARG_TYPE_STRING }; self->args[arg_index++] = (Arg){ .key = "-encoder", .optional = true, .list = false, .type = ARG_TYPE_ENUM, .enum_values = video_encoder_enums, .num_enum_values = sizeof(video_encoder_enums)/sizeof(ArgEnum) }; + self->args[arg_index++] = (Arg){ .key = "-replay-storage", .optional = true, .list = false, .type = ARG_TYPE_ENUM, .enum_values = replay_storage_enums, .num_enum_values = sizeof(replay_storage_enums)/sizeof(ArgEnum) }; assert(arg_index == NUM_ARGS); for(int i = 1; i < argc; i += 2) { diff --git a/src/encoder/encoder.c b/src/encoder/encoder.c index d35cbbe..0f8eda5 100644 --- a/src/encoder/encoder.c +++ b/src/encoder/encoder.c @@ -7,7 +7,7 @@ #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> -bool gsr_encoder_init(gsr_encoder *self, size_t replay_buffer_num_packets) { +bool gsr_encoder_init(gsr_encoder *self, gsr_replay_storage replay_storage, size_t replay_buffer_num_packets, double replay_buffer_time, const char *replay_directory) { memset(self, 0, sizeof(*self)); self->num_recording_destinations = 0; self->recording_destination_id_counter = 0; @@ -19,12 +19,12 @@ bool gsr_encoder_init(gsr_encoder *self, size_t replay_buffer_num_packets) { self->mutex_created = true; if(replay_buffer_num_packets > 0) { - if(!gsr_replay_buffer_init(&self->replay_buffer, replay_buffer_num_packets)) { + self->replay_buffer = gsr_replay_buffer_create(replay_storage, replay_directory, replay_buffer_time, replay_buffer_num_packets); + if(!self->replay_buffer) { fprintf(stderr, "gsr error: gsr_encoder_init: failed to create replay buffer\n"); gsr_encoder_deinit(self); return false; } - self->has_replay_buffer = true; } return true; @@ -36,8 +36,11 @@ void gsr_encoder_deinit(gsr_encoder *self) { pthread_mutex_destroy(&self->file_write_mutex); } - gsr_replay_buffer_deinit(&self->replay_buffer); - self->has_replay_buffer = false; + if(self->replay_buffer) { + gsr_replay_buffer_destroy(self->replay_buffer); + self->replay_buffer = NULL; + } + self->num_recording_destinations = 0; self->recording_destination_id_counter = 0; } @@ -56,9 +59,9 @@ void gsr_encoder_receive_packets(gsr_encoder *self, AVCodecContext *codec_contex av_packet->pts = pts; av_packet->dts = pts; - if(self->has_replay_buffer) { + if(self->replay_buffer) { const double time_now = clock_get_monotonic_seconds(); - if(!gsr_replay_buffer_append(&self->replay_buffer, av_packet, time_now)) + if(!gsr_replay_buffer_append(self->replay_buffer, av_packet, time_now)) fprintf(stderr, "gsr error: gsr_encoder_receive_packets: failed to add replay buffer data\n"); } diff --git a/src/main.cpp b/src/main.cpp index a2ce24e..6471dbc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,7 +32,6 @@ extern "C" { #include <stdio.h> #include <stdlib.h> #include <string> -#include <vector> #include <thread> #include <mutex> #include <signal.h> @@ -41,6 +40,7 @@ extern "C" { #include <sys/wait.h> #include <inttypes.h> #include <libgen.h> +#include <malloc.h> #include "../include/sound.hpp" @@ -1262,24 +1262,24 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str if(save_replay_thread.valid()) return; - const size_t search_start_index = current_save_replay_seconds == save_replay_seconds_full ? 0 : gsr_replay_buffer_find_packet_index_by_time_passed(replay_buffer, current_save_replay_seconds); - const size_t video_start_index = gsr_replay_buffer_find_keyframe(replay_buffer, search_start_index, video_stream_index, false); - if(video_start_index == (size_t)-1) { + const gsr_replay_buffer_iterator search_start_iterator = current_save_replay_seconds == save_replay_seconds_full ? gsr_replay_buffer_iterator{0, 0} : gsr_replay_buffer_find_packet_index_by_time_passed(replay_buffer, current_save_replay_seconds); + const gsr_replay_buffer_iterator video_start_iterator = gsr_replay_buffer_find_keyframe(replay_buffer, search_start_iterator, video_stream_index, false); + if(video_start_iterator.packet_index == (size_t)-1) { fprintf(stderr, "gsr error: failed to save replay: failed to find a video keyframe. perhaps replay was saved too fast, before anything has been recorded\n"); return; } - const size_t audio_start_index = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_index, video_stream_index, true); + const gsr_replay_buffer_iterator audio_start_iterator = gsr_replay_buffer_find_keyframe(replay_buffer, video_start_iterator, video_stream_index, true); // 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"); // return; // } - 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 = audio_start_index == (size_t)-1 ? 0 : gsr_replay_buffer_get_packet_at_index(replay_buffer, audio_start_index)->packet.pts; + const int64_t video_pts_offset = gsr_replay_buffer_iterator_get_packet(replay_buffer, video_start_iterator)->pts; + const int64_t audio_pts_offset = audio_start_iterator.packet_index == (size_t)-1 ? 0 : gsr_replay_buffer_iterator_get_packet(replay_buffer, audio_start_iterator)->pts; - gsr_replay_buffer cloned_replay_buffer; - if(!gsr_replay_buffer_clone(replay_buffer, &cloned_replay_buffer)) { + gsr_replay_buffer *cloned_replay_buffer = gsr_replay_buffer_clone(replay_buffer); + if(!cloned_replay_buffer) { // TODO: Return this error to mark the replay as failed fprintf(stderr, "gsr error: failed to save replay: failed to clone replay buffer\n"); return; @@ -1292,20 +1292,35 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str save_replay_output_filepath = std::move(output_filepath); - save_replay_thread = std::async(std::launch::async, [video_stream_index, recording_start_result, video_start_index, video_pts_offset, audio_pts_offset, video_codec_context, cloned_replay_buffer]() mutable { - for(size_t i = video_start_index; i < cloned_replay_buffer.num_packets; ++i) { - const gsr_av_packet *packet = gsr_replay_buffer_get_packet_at_index(&cloned_replay_buffer, i); + save_replay_thread = std::async(std::launch::async, [video_stream_index, recording_start_result, video_start_iterator, video_pts_offset, audio_pts_offset, video_codec_context, cloned_replay_buffer]() mutable { + gsr_replay_buffer_iterator replay_iterator = video_start_iterator; + for(;;) { + AVPacket *replay_packet = gsr_replay_buffer_iterator_get_packet(cloned_replay_buffer, replay_iterator); + uint8_t *replay_packet_data = NULL; + if(replay_packet) + replay_packet_data = gsr_replay_buffer_iterator_get_packet_data(cloned_replay_buffer, replay_iterator); + + if(!replay_packet) { + fprintf(stderr, "no replay packet\n"); + break; + } + + if(!replay_packet->data && !replay_packet_data) { + fprintf(stderr, "no replay packet data\n"); + break; + } + // TODO: Check if successful AVPacket av_packet; memset(&av_packet, 0, sizeof(av_packet)); - //av_packet_from_data(av_packet, packet->packet.data, packet->packet.size); - av_packet.data = packet->packet.data; - av_packet.size = packet->packet.size; - av_packet.stream_index = packet->packet.stream_index; - av_packet.pts = packet->packet.pts; - av_packet.dts = packet->packet.pts; - av_packet.flags = packet->packet.flags; - //av_packet.duration = packet->packet.duration; + //av_packet_from_data(av_packet, replay_packet->data, replay_packet->size); + av_packet.data = replay_packet->data ? replay_packet->data : replay_packet_data; + av_packet.size = replay_packet->size; + av_packet.stream_index = replay_packet->stream_index; + av_packet.pts = replay_packet->pts; + av_packet.dts = replay_packet->pts; + av_packet.flags = replay_packet->flags; + //av_packet.duration = replay_packet->duration; AVStream *stream = recording_start_result.video_stream; AVCodecContext *codec_context = video_codec_context; @@ -1317,8 +1332,10 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str RecordingStartAudio *recording_start_audio = get_recording_start_item_by_stream_index(recording_start_result, av_packet.stream_index); if(!recording_start_audio) { fprintf(stderr, "gsr error: save_replay_async: failed to find audio stream by index: %d\n", av_packet.stream_index); + free(replay_packet_data); continue; } + const AudioTrack *audio_track = recording_start_audio->audio_track; stream = recording_start_audio->stream; codec_context = audio_track->codec_context; @@ -1332,13 +1349,17 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str const int ret = av_write_frame(recording_start_result.av_format_context, &av_packet); if(ret < 0) - fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", packet->packet.stream_index, av_error_to_string(ret), ret); + fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", av_packet.stream_index, av_error_to_string(ret), ret); + + free(replay_packet_data); //av_packet_free(&av_packet); + if(!gsr_replay_buffer_iterator_next(cloned_replay_buffer, &replay_iterator)) + break; } stop_recording_close_streams(recording_start_result.av_format_context); - gsr_replay_buffer_deinit(&cloned_replay_buffer); + gsr_replay_buffer_destroy(cloned_replay_buffer); }); } @@ -2890,8 +2911,24 @@ static size_t calculate_estimated_replay_buffer_packets(int64_t replay_buffer_si return replay_buffer_size_secs * (fps + audio_fps * audio_inputs.size()); } +static void set_display_server_environment_variables() { + // Some users dont have properly setup environments (no display manager that does systemctl --user import-environment DISPLAY WAYLAND_DISPLAY) + const char *display = getenv("DISPLAY"); + if(!display) { + display = ":0"; + setenv("DISPLAY", display, true); + } + + const char *wayland_display = getenv("WAYLAND_DISPLAY"); + if(!wayland_display) { + wayland_display = "wayland-1"; + setenv("WAYLAND_DISPLAY", wayland_display, true); + } +} + int main(int argc, char **argv) { setlocale(LC_ALL, "C"); // Sigh... stupid C + mallopt(M_MMAP_THRESHOLD, 65536); signal(SIGINT, stop_handler); signal(SIGTERM, stop_handler); @@ -2905,6 +2942,8 @@ int main(int argc, char **argv) { signal(SIGRTMIN+5, save_replay_10_minutes_handler); signal(SIGRTMIN+6, save_replay_30_minutes_handler); + set_display_server_environment_variables(); + // Stop nvidia driver from buffering frames setenv("__GL_MaxFramesAllowed", "1", true); // If this is set to 1 then cuGraphicsGLRegisterImage will fail for egl context with error: invalid OpenGL or DirectX context, @@ -3141,7 +3180,7 @@ int main(int argc, char **argv) { const size_t estimated_replay_buffer_packets = calculate_estimated_replay_buffer_packets(arg_parser.replay_buffer_size_secs, arg_parser.fps, arg_parser.audio_codec, requested_audio_inputs); gsr_encoder encoder; - if(!gsr_encoder_init(&encoder, estimated_replay_buffer_packets)) { + if(!gsr_encoder_init(&encoder, arg_parser.replay_storage, estimated_replay_buffer_packets, arg_parser.replay_buffer_size_secs, arg_parser.filename)) { fprintf(stderr, "Error: failed to create encoder\n"); _exit(1); } @@ -3720,10 +3759,10 @@ int main(int argc, char **argv) { save_replay_seconds = 0; save_replay_output_filepath.clear(); - save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, &encoder.replay_buffer, arg_parser.filename, arg_parser.container_format, file_extension, arg_parser.date_folders, hdr, capture, current_save_replay_seconds); + save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, encoder.replay_buffer, arg_parser.filename, arg_parser.container_format, file_extension, arg_parser.date_folders, hdr, capture, current_save_replay_seconds); if(arg_parser.restart_replay_on_save && current_save_replay_seconds == save_replay_seconds_full) { - gsr_replay_buffer_clear(&encoder.replay_buffer); + gsr_replay_buffer_clear(encoder.replay_buffer); replay_start_time = clock_get_monotonic_seconds() - paused_time_offset; } } diff --git a/src/replay_buffer.c b/src/replay_buffer.c deleted file mode 100644 index 739b02b..0000000 --- a/src/replay_buffer.c +++ /dev/null @@ -1,237 +0,0 @@ -#include "../include/replay_buffer.h" -#include "../include/utils.h" - -#include <stdlib.h> -#include <string.h> -#include <assert.h> - -#include <libavutil/mem.h> - -gsr_av_packet* gsr_av_packet_create(const AVPacket *av_packet, double timestamp) { - gsr_av_packet *self = malloc(sizeof(gsr_av_packet)); - if(!self) - return NULL; - - self->ref_counter = 1; - self->packet = *av_packet; - // Why are we doing this you ask? there is a new ffmpeg bug that causes cpu usage to increase over time when you have - // packets that are not being free'd until later. So we copy the packet data, free the packet and then reconstruct - // the packet later on when we need it, to keep packets alive only for a short period. - self->packet.data = av_memdup(av_packet->data, av_packet->size); - self->timestamp = timestamp; - if(!self->packet.data) { - free(self); - return NULL; - } - - return self; -} - -gsr_av_packet* gsr_av_packet_ref(gsr_av_packet *self) { - if(self->ref_counter >= 1) - ++self->ref_counter; - return self; -} - -static void gsr_av_packet_free(gsr_av_packet *self) { - self->ref_counter = 0; - if(self->packet.data) { - av_free(self->packet.data); - self->packet.data = NULL; - } - free(self); -} - -void gsr_av_packet_unref(gsr_av_packet *self) { - if(self->ref_counter >= 1) - --self->ref_counter; - - if(self->ref_counter <= 0) - gsr_av_packet_free(self); -} - -bool gsr_replay_buffer_init(gsr_replay_buffer *self, size_t replay_buffer_num_packets) { - assert(replay_buffer_num_packets > 0); - memset(self, 0, sizeof(*self)); - self->mutex_initialized = false; - self->original_replay_buffer = NULL; - if(pthread_mutex_init(&self->mutex, NULL) != 0) - return false; - - self->mutex_initialized = true; - self->capacity_num_packets = replay_buffer_num_packets; - self->num_packets = 0; - self->index = 0; - self->packets = calloc(self->capacity_num_packets, sizeof(gsr_av_packet*)); - if(!self->packets) { - gsr_replay_buffer_deinit(self); - return false; - } - return true; -} - -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) - 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) { - if(self->packets[i]) { - gsr_av_packet_unref(self->packets[i]); - self->packets[i] = NULL; - } - } - self->num_packets = 0; - gsr_replay_buffer_unlock(self); - - if(self->packets) { - free(self->packets); - self->packets = NULL; - } - - self->capacity_num_packets = 0; - self->index = 0; - - if(self->mutex_initialized && !self->original_replay_buffer) { - pthread_mutex_destroy(&self->mutex); - self->mutex_initialized = false; - } - - self->original_replay_buffer = NULL; -} - -bool gsr_replay_buffer_append(gsr_replay_buffer *self, const AVPacket *av_packet, double timestamp) { - gsr_replay_buffer_lock(self); - gsr_av_packet *packet = gsr_av_packet_create(av_packet, timestamp); - if(!packet) { - gsr_replay_buffer_unlock(self); - return false; - } - - if(self->packets[self->index]) { - gsr_av_packet_unref(self->packets[self->index]); - self->packets[self->index] = NULL; - } - self->packets[self->index] = packet; - - self->index = (self->index + 1) % self->capacity_num_packets; - ++self->num_packets; - if(self->num_packets > self->capacity_num_packets) - self->num_packets = self->capacity_num_packets; - - gsr_replay_buffer_unlock(self); - return true; -} - -void gsr_replay_buffer_clear(gsr_replay_buffer *self) { - gsr_replay_buffer_lock(self); - for(size_t i = 0; i < self->num_packets; ++i) { - if(self->packets[i]) { - gsr_av_packet_unref(self->packets[i]); - self->packets[i] = NULL; - } - } - self->num_packets = 0; - self->index = 0; - gsr_replay_buffer_unlock(self); -} - -gsr_av_packet* gsr_replay_buffer_get_packet_at_index(gsr_replay_buffer *self, size_t index) { - assert(index < self->num_packets); - size_t start_index = 0; - if(self->num_packets < self->capacity_num_packets) - start_index = self->num_packets - self->index; - else - start_index = self->index; - - const size_t offset = (start_index + index) % self->capacity_num_packets; - return self->packets[offset]; -} - -bool gsr_replay_buffer_clone(gsr_replay_buffer *self, gsr_replay_buffer *destination) { - gsr_replay_buffer_lock(self); - memset(destination, 0, sizeof(*destination)); - destination->original_replay_buffer = self; - destination->mutex = self->mutex; - destination->capacity_num_packets = self->capacity_num_packets; - destination->mutex_initialized = self->mutex_initialized; - destination->index = self->index; - destination->packets = calloc(destination->capacity_num_packets, sizeof(gsr_av_packet*)); - if(!destination->packets) { - gsr_replay_buffer_unlock(self); - return false; - } - - destination->num_packets = self->num_packets; - for(size_t i = 0; i < destination->num_packets; ++i) { - destination->packets[i] = gsr_av_packet_ref(self->packets[i]); - } - - gsr_replay_buffer_unlock(self); - return true; -} - -/* Binary search */ -size_t gsr_replay_buffer_find_packet_index_by_time_passed(gsr_replay_buffer *self, int seconds) { - gsr_replay_buffer_lock(self); - - const double now = clock_get_monotonic_seconds(); - if(self->num_packets == 0) { - gsr_replay_buffer_unlock(self); - return 0; - } - - size_t lower_bound = 0; - size_t upper_bound = self->num_packets; - size_t index = 0; - - for(;;) { - index = lower_bound + (upper_bound - lower_bound) / 2; - const gsr_av_packet *packet = gsr_replay_buffer_get_packet_at_index(self, index); - const double time_passed_since_packet = now - packet->timestamp; - if(time_passed_since_packet >= seconds) { - if(lower_bound == index) - break; - lower_bound = index; - } else { - if(upper_bound == index) - break; - upper_bound = index; - } - } - - gsr_replay_buffer_unlock(self); - return 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); - size_t keyframe_index = (size_t)-1; - gsr_replay_buffer_lock(self); - 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); - if((packet->packet.flags & AV_PKT_FLAG_KEY) && (invert_stream_index ? packet->packet.stream_index != stream_index : packet->packet.stream_index == stream_index)) { - keyframe_index = i; - break; - } - } - gsr_replay_buffer_unlock(self); - return keyframe_index; -} diff --git a/src/replay_buffer/replay_buffer.c b/src/replay_buffer/replay_buffer.c new file mode 100644 index 0000000..92aa645 --- /dev/null +++ b/src/replay_buffer/replay_buffer.c @@ -0,0 +1,91 @@ +#include "../../include/replay_buffer/replay_buffer.h" +#include "../../include/replay_buffer/replay_buffer_ram.h" +#include "../../include/replay_buffer/replay_buffer_disk.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +gsr_replay_buffer* gsr_replay_buffer_create(gsr_replay_storage replay_storage, const char *replay_directory, double replay_buffer_time, size_t replay_buffer_num_packets) { + gsr_replay_buffer *replay_buffer = NULL; + switch(replay_storage) { + case GSR_REPLAY_STORAGE_RAM: + replay_buffer = gsr_replay_buffer_ram_create(replay_buffer_num_packets); + break; + case GSR_REPLAY_STORAGE_DISK: + replay_buffer = gsr_replay_buffer_disk_create(replay_directory, replay_buffer_time); + break; + } + + replay_buffer->mutex_initialized = false; + replay_buffer->original_replay_buffer = NULL; + if(pthread_mutex_init(&replay_buffer->mutex, NULL) != 0) { + gsr_replay_buffer_destroy(replay_buffer); + return NULL; + } + + replay_buffer->mutex_initialized = true; + return replay_buffer; +} + +void gsr_replay_buffer_destroy(gsr_replay_buffer *self) { + self->destroy(self); + if(self->mutex_initialized && !self->original_replay_buffer) { + pthread_mutex_destroy(&self->mutex); + self->mutex_initialized = false; + } + self->original_replay_buffer = NULL; + free(self); +} + +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) + pthread_mutex_lock(&self->mutex); +} + +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); +} + +bool gsr_replay_buffer_append(gsr_replay_buffer *self, const AVPacket *av_packet, double timestamp) { + return self->append(self, av_packet, timestamp); +} + +void gsr_replay_buffer_clear(gsr_replay_buffer *self) { + self->clear(self); +} + +AVPacket* gsr_replay_buffer_iterator_get_packet(gsr_replay_buffer *self, gsr_replay_buffer_iterator iterator) { + return self->iterator_get_packet(self, iterator); +} + +uint8_t* gsr_replay_buffer_iterator_get_packet_data(gsr_replay_buffer *self, gsr_replay_buffer_iterator iterator) { + return self->iterator_get_packet_data(self, iterator); +} + +gsr_replay_buffer* gsr_replay_buffer_clone(gsr_replay_buffer *self) { + return self->clone(self); +} + +gsr_replay_buffer_iterator gsr_replay_buffer_find_packet_index_by_time_passed(gsr_replay_buffer *self, int seconds) { + return self->find_packet_index_by_time_passed(self, seconds); +} + +gsr_replay_buffer_iterator gsr_replay_buffer_find_keyframe(gsr_replay_buffer *self, gsr_replay_buffer_iterator start_iterator, int stream_index, bool invert_stream_index) { + return self->find_keyframe(self, start_iterator, stream_index, invert_stream_index); +} + +bool gsr_replay_buffer_iterator_next(gsr_replay_buffer *self, gsr_replay_buffer_iterator *iterator) { + return self->iterator_next(self, iterator); +} diff --git a/src/replay_buffer/replay_buffer_disk.c b/src/replay_buffer/replay_buffer_disk.c new file mode 100644 index 0000000..0716f3b --- /dev/null +++ b/src/replay_buffer/replay_buffer_disk.c @@ -0,0 +1,429 @@ +#include "../../include/replay_buffer/replay_buffer_disk.h" +#include "../../include/utils.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <errno.h> +#include <assert.h> + +#define REPLAY_BUFFER_FILE_SIZE_BYTES 1024 * 1024 * 256 /* 256MB */ +#define FILE_PREFIX "Replay" + +static void gsr_replay_buffer_disk_set_impl_funcs(gsr_replay_buffer_disk *self); + +static void gsr_av_packet_disk_init(gsr_av_packet_disk *self, const AVPacket *av_packet, size_t data_index, double timestamp) { + self->packet = *av_packet; + self->packet.data = NULL; + self->data_index = data_index; + self->timestamp = timestamp; +} + +static gsr_replay_buffer_file* gsr_replay_buffer_file_create(char *replay_directory, size_t replay_storage_counter, double timestamp, int *replay_storage_fd) { + gsr_replay_buffer_file *self = calloc(1, sizeof(gsr_replay_buffer_file)); + if(!self) { + fprintf(stderr, "gsr error: gsr_av_packet_file_init: failed to create buffer file\n"); + return NULL; + } + + if(create_directory_recursive(replay_directory) != 0) { + fprintf(stderr, "gsr error: gsr_av_packet_file_init: failed to create replay directory: %s\n", replay_directory); + free(self); + return NULL; + } + + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s_%d.mp4", replay_directory, FILE_PREFIX, (int)replay_storage_counter); + *replay_storage_fd = creat(filename, 0700); + if(*replay_storage_fd <= 0) { + fprintf(stderr, "gsr error: gsr_av_packet_file_init: failed to create replay file: %s\n", filename); + free(self); + return NULL; + } + + self->id = replay_storage_counter; + self->start_timestamp = timestamp; + self->end_timestamp = timestamp; + self->ref_counter = 1; + self->fd = -1; + + self->packets = NULL; + self->capacity_num_packets = 0; + self->num_packets = 0; + return self; +} + +static gsr_replay_buffer_file* gsr_replay_buffer_file_ref(gsr_replay_buffer_file *self) { + if(self->ref_counter >= 1) + ++self->ref_counter; + return self; +} + +static void gsr_replay_buffer_file_free(gsr_replay_buffer_file *self, const char *replay_directory) { + self->ref_counter = 0; + + if(self->fd > 0) { + close(self->fd); + self->fd = -1; + } + + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s_%d.mp4", replay_directory, FILE_PREFIX, (int)self->id); + remove(filename); + + if(self->packets) { + free(self->packets); + self->packets = NULL; + } + self->num_packets = 0; + self->capacity_num_packets = 0; + + free(self); +} + +static void gsr_replay_buffer_file_unref(gsr_replay_buffer_file *self, const char *replay_directory) { + if(self->ref_counter > 0) + --self->ref_counter; + + if(self->ref_counter <= 0) + gsr_replay_buffer_file_free(self, replay_directory); +} + +static void gsr_replay_buffer_disk_clear(gsr_replay_buffer *replay_buffer) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + gsr_replay_buffer_lock(&self->replay_buffer); + for(size_t i = 0; i < self->num_files; ++i) { + gsr_replay_buffer_file_unref(self->files[i], self->replay_directory); + } + self->num_files = 0; + gsr_replay_buffer_unlock(&self->replay_buffer); + + if(self->storage_fd > 0) { + close(self->storage_fd); + self->storage_fd = 0; + } + + self->storage_counter = 0; + self->storage_num_bytes_written = 0; +} + +static void gsr_replay_buffer_disk_destroy(gsr_replay_buffer *replay_buffer) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + gsr_replay_buffer_disk_clear(replay_buffer); + + if(self->owns_directory) { + remove(self->replay_directory); + self->owns_directory = false; + } +} + +static bool file_write_all(int fd, const uint8_t *data, size_t size, size_t *bytes_written_total) { + *bytes_written_total = 0; + while(*bytes_written_total < size) { + const ssize_t bytes_written = write(fd, data + *bytes_written_total, size - *bytes_written_total); + if(bytes_written == -1) { + if(errno == EAGAIN) + continue; + else + return false; + } + *bytes_written_total += bytes_written; + } + return true; +} + +static bool gsr_replay_buffer_disk_create_next_file(gsr_replay_buffer_disk *self, double timestamp) { + if(self->num_files + 1 >= GSR_REPLAY_BUFFER_CAPACITY_NUM_FILES) { + fprintf(stderr, "gsr error: gsr_replay_buffer_disk_create_next_file: too many replay buffer files created! (> %d), either reduce the replay buffer time or report this as a bug\n", (int)GSR_REPLAY_BUFFER_CAPACITY_NUM_FILES); + return false; + } + + gsr_replay_buffer_file *replay_buffer_file = gsr_replay_buffer_file_create(self->replay_directory, self->storage_counter, timestamp, &self->storage_fd); + if(!replay_buffer_file) + return false; + + self->files[self->num_files] = replay_buffer_file; + ++self->num_files; + ++self->storage_counter; + return true; +} + +static bool gsr_replay_buffer_disk_append_to_current_file(gsr_replay_buffer_disk *self, const AVPacket *av_packet, double timestamp) { + gsr_replay_buffer_file *replay_buffer_file = self->files[self->num_files - 1]; + replay_buffer_file->end_timestamp = timestamp; + + if(replay_buffer_file->num_packets + 1 >= replay_buffer_file->capacity_num_packets) { + size_t new_capacity_num_packets = replay_buffer_file->capacity_num_packets * 2; + if(new_capacity_num_packets == 0) + new_capacity_num_packets = 256; + + void *new_packets = realloc(replay_buffer_file->packets, new_capacity_num_packets * sizeof(gsr_av_packet_disk)); + if(!new_packets) { + fprintf(stderr, "gsr error: gsr_replay_buffer_disk_append_to_current_file: failed to reallocate replay buffer file packets\n"); + return false; + } + + replay_buffer_file->capacity_num_packets = new_capacity_num_packets; + replay_buffer_file->packets = new_packets; + } + + gsr_av_packet_disk *packet = &replay_buffer_file->packets[replay_buffer_file->num_packets]; + gsr_av_packet_disk_init(packet, av_packet, self->storage_num_bytes_written, timestamp); + ++replay_buffer_file->num_packets; + + size_t bytes_written = 0; + const bool file_written = file_write_all(self->storage_fd, av_packet->data, av_packet->size, &bytes_written); + self->storage_num_bytes_written += bytes_written; + if(self->storage_num_bytes_written >= REPLAY_BUFFER_FILE_SIZE_BYTES) { + self->storage_num_bytes_written = 0; + close(self->storage_fd); + self->storage_fd = 0; + } + + return file_written; +} + +static void gsr_replay_buffer_disk_remove_first_file(gsr_replay_buffer_disk *self) { + gsr_replay_buffer_file_unref(self->files[0], self->replay_directory); + for(size_t i = 1; i < self->num_files; ++i) { + self->files[i - 1] = self->files[i]; + } + --self->num_files; +} + +static bool gsr_replay_buffer_disk_append(gsr_replay_buffer *replay_buffer, const AVPacket *av_packet, double timestamp) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + bool success = false; + gsr_replay_buffer_lock(&self->replay_buffer); + + if(self->storage_fd <= 0) { + if(!gsr_replay_buffer_disk_create_next_file(self, timestamp)) + goto done; + } + + const bool data_written = gsr_replay_buffer_disk_append_to_current_file(self, av_packet, timestamp); + + if(self->num_files > 1) { + const double buffer_time_accumulated = timestamp - self->files[1]->start_timestamp; + if(buffer_time_accumulated >= self->replay_buffer_time) + gsr_replay_buffer_disk_remove_first_file(self); + } + + success = data_written; + + done: + gsr_replay_buffer_unlock(&self->replay_buffer); + return success; +} + +static AVPacket* gsr_replay_buffer_disk_iterator_get_packet(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator iterator) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + assert(iterator.file_index < self->num_files); + assert(iterator.packet_index < self->files[iterator.file_index]->num_packets); + return &self->files[iterator.file_index]->packets[iterator.packet_index].packet; +} + +static uint8_t* gsr_replay_buffer_disk_iterator_get_packet_data(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator iterator) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + assert(iterator.file_index < self->num_files); + gsr_replay_buffer_file *file = self->files[iterator.file_index]; + assert(iterator.packet_index < file->num_packets); + + if(file->fd <= 0) { + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s/%s_%d.mp4", self->replay_directory, FILE_PREFIX, (int)file->id); + file->fd = open(filename, O_RDONLY); + if(file->fd <= 0) + return NULL; + } + + const gsr_av_packet_disk *packet = &self->files[iterator.file_index]->packets[iterator.packet_index]; + if(lseek(file->fd, packet->data_index, SEEK_SET) == -1) + return NULL; + + uint8_t *packet_data = malloc(packet->packet.size); + if(read(file->fd, packet_data, packet->packet.size) != packet->packet.size) { + free(packet_data); + return NULL; + } + + return packet_data; +} + +static gsr_replay_buffer* gsr_replay_buffer_disk_clone(gsr_replay_buffer *replay_buffer) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + gsr_replay_buffer_disk *destination = calloc(1, sizeof(gsr_replay_buffer_disk)); + if(!destination) + return NULL; + + gsr_replay_buffer_disk_set_impl_funcs(destination); + gsr_replay_buffer_lock(&self->replay_buffer); + + destination->replay_buffer.original_replay_buffer = replay_buffer; + destination->replay_buffer.mutex = self->replay_buffer.mutex; + destination->replay_buffer.mutex_initialized = self->replay_buffer.mutex_initialized; + destination->replay_buffer_time = self->replay_buffer_time; + destination->storage_counter = self->storage_counter; + destination->storage_num_bytes_written = self->storage_num_bytes_written; + destination->storage_fd = 0; // We only want to read from the clone. If there is a need to write to it in the future then TODO change this + + for(size_t i = 0; i < self->num_files; ++i) { + destination->files[i] = gsr_replay_buffer_file_ref(self->files[i]); + } + destination->num_files = self->num_files; + + snprintf(destination->replay_directory, sizeof(destination->replay_directory), "%s", self->replay_directory); + destination->owns_directory = false; + + gsr_replay_buffer_unlock(&self->replay_buffer); + return (gsr_replay_buffer*)destination; +} + +/* Binary search */ +static size_t gsr_replay_buffer_file_find_packet_index_by_time_passed(const gsr_replay_buffer_file *self, int seconds) { + const double now = clock_get_monotonic_seconds(); + if(self->num_packets == 0) { + return 0; + } + + size_t lower_bound = 0; + size_t upper_bound = self->num_packets; + size_t index = 0; + + for(;;) { + index = lower_bound + (upper_bound - lower_bound) / 2; + const gsr_av_packet_disk *packet = &self->packets[index]; + const double time_passed_since_packet = now - packet->timestamp; + if(time_passed_since_packet >= seconds) { + if(lower_bound == index) + break; + lower_bound = index; + } else { + if(upper_bound == index) + break; + upper_bound = index; + } + } + + return index; +} + +/* Binary search */ +static gsr_replay_buffer_iterator gsr_replay_buffer_disk_find_file_index_by_time_passed(gsr_replay_buffer *replay_buffer, int seconds) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + gsr_replay_buffer_lock(&self->replay_buffer); + + const double now = clock_get_monotonic_seconds(); + if(self->num_files == 0) { + gsr_replay_buffer_unlock(&self->replay_buffer); + return (gsr_replay_buffer_iterator){0, 0}; + } + + size_t lower_bound = 0; + size_t upper_bound = self->num_files; + size_t file_index = 0; + + for(;;) { + file_index = lower_bound + (upper_bound - lower_bound) / 2; + const gsr_replay_buffer_file *file = self->files[file_index]; + const double time_passed_since_file_start = now - file->start_timestamp; + const double time_passed_since_file_end = now - file->end_timestamp; + if(time_passed_since_file_start >= seconds && time_passed_since_file_end <= seconds) { + break; + } else if(time_passed_since_file_start >= seconds) { + if(lower_bound == file_index) + break; + lower_bound = file_index; + } else { + if(upper_bound == file_index) + break; + upper_bound = file_index; + } + } + + const gsr_replay_buffer_file *file = self->files[file_index]; + const size_t packet_index = gsr_replay_buffer_file_find_packet_index_by_time_passed(file, seconds); + + gsr_replay_buffer_unlock(&self->replay_buffer); + return (gsr_replay_buffer_iterator){packet_index, file_index}; +} + +static gsr_replay_buffer_iterator gsr_replay_buffer_disk_find_keyframe(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator start_iterator, int stream_index, bool invert_stream_index) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + gsr_replay_buffer_iterator keyframe_iterator = {(size_t)-1, 0}; + gsr_replay_buffer_lock(&self->replay_buffer); + for(size_t file_index = start_iterator.file_index; file_index < self->num_files; ++file_index) { + const gsr_replay_buffer_file *file = self->files[file_index]; + for(size_t packet_index = start_iterator.packet_index; packet_index < file->num_packets; ++packet_index) { + const gsr_av_packet_disk *packet = &file->packets[packet_index]; + if((packet->packet.flags & AV_PKT_FLAG_KEY) && (invert_stream_index ? packet->packet.stream_index != stream_index : packet->packet.stream_index == stream_index)) { + keyframe_iterator.packet_index = packet_index; + keyframe_iterator.file_index = file_index; + break; + } + } + } + gsr_replay_buffer_unlock(&self->replay_buffer); + return keyframe_iterator; +} + +static bool gsr_replay_buffer_disk_iterator_next(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator *iterator) { + gsr_replay_buffer_disk *self = (gsr_replay_buffer_disk*)replay_buffer; + if(iterator->file_index >= self->num_files) + return false; + + if(iterator->packet_index + 1 >= self->files[iterator->file_index]->num_packets) { + if(iterator->file_index + 1 >= self->num_files) + return false; + + if(self->files[iterator->file_index + 1]->num_packets == 0) + return false; + + ++iterator->file_index; + iterator->packet_index = 0; + return true; + } else { + ++iterator->packet_index; + return true; + } +} + +static void get_current_time(char *time_str, size_t time_str_size) { + time_t now = time(NULL); + struct tm *t = localtime(&now); + strftime(time_str, time_str_size - 1, "%Y-%m-%d_%H-%M-%S", t); +} + +static void gsr_replay_buffer_disk_set_impl_funcs(gsr_replay_buffer_disk *self) { + self->replay_buffer.destroy = gsr_replay_buffer_disk_destroy; + self->replay_buffer.append = gsr_replay_buffer_disk_append; + self->replay_buffer.clear = gsr_replay_buffer_disk_clear; + self->replay_buffer.iterator_get_packet = gsr_replay_buffer_disk_iterator_get_packet; + self->replay_buffer.iterator_get_packet_data = gsr_replay_buffer_disk_iterator_get_packet_data; + self->replay_buffer.clone = gsr_replay_buffer_disk_clone; + self->replay_buffer.find_packet_index_by_time_passed = gsr_replay_buffer_disk_find_file_index_by_time_passed; + self->replay_buffer.find_keyframe = gsr_replay_buffer_disk_find_keyframe; + self->replay_buffer.iterator_next = gsr_replay_buffer_disk_iterator_next; +} + +gsr_replay_buffer* gsr_replay_buffer_disk_create(const char *replay_directory, double replay_buffer_time) { + assert(replay_buffer_time > 0); + gsr_replay_buffer_disk *replay_buffer = calloc(1, sizeof(gsr_replay_buffer_disk)); + if(!replay_buffer) + return NULL; + + char time_str[128]; + get_current_time(time_str, sizeof(time_str)); + + replay_buffer->num_files = 0; + replay_buffer->storage_counter = 0; + replay_buffer->replay_buffer_time = replay_buffer_time; + snprintf(replay_buffer->replay_directory, sizeof(replay_buffer->replay_directory), "%s/gsr-replay-%s.gsr", replay_directory, time_str); + replay_buffer->owns_directory = true; + + gsr_replay_buffer_disk_set_impl_funcs(replay_buffer); + return (gsr_replay_buffer*)replay_buffer; +} diff --git a/src/replay_buffer/replay_buffer_ram.c b/src/replay_buffer/replay_buffer_ram.c new file mode 100644 index 0000000..890588f --- /dev/null +++ b/src/replay_buffer/replay_buffer_ram.c @@ -0,0 +1,256 @@ +#include "../../include/replay_buffer/replay_buffer_ram.h" +#include "../../include/utils.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <libavutil/mem.h> + +static void gsr_replay_buffer_ram_set_impl_funcs(gsr_replay_buffer_ram *self); + +static gsr_av_packet_ram* gsr_av_packet_ram_create(const AVPacket *av_packet, double timestamp) { + gsr_av_packet_ram *self = malloc(sizeof(gsr_av_packet_ram)); + if(!self) + return NULL; + + self->ref_counter = 1; + self->packet = *av_packet; + self->timestamp = timestamp; + // Why are we doing this you ask? there is a ffmpeg bug that causes cpu usage to increase over time when you have + // packets that are not being free'd until later. So we copy the packet data, free the packet and then reconstruct + // the packet later on when we need it, to keep packets alive only for a short period. + self->packet.data = av_memdup(av_packet->data, av_packet->size); + if(!self->packet.data) { + free(self); + return NULL; + } + + return self; +} + +static gsr_av_packet_ram* gsr_av_packet_ram_ref(gsr_av_packet_ram *self) { + if(self->ref_counter >= 1) + ++self->ref_counter; + return self; +} + +static void gsr_av_packet_ram_free(gsr_av_packet_ram *self) { + self->ref_counter = 0; + if(self->packet.data) { + av_free(self->packet.data); + self->packet.data = NULL; + } + free(self); +} + +static void gsr_av_packet_ram_unref(gsr_av_packet_ram *self) { + if(self->ref_counter >= 1) + --self->ref_counter; + + if(self->ref_counter <= 0) + gsr_av_packet_ram_free(self); +} + +static void gsr_replay_buffer_ram_destroy(gsr_replay_buffer *replay_buffer) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + gsr_replay_buffer_lock(&self->replay_buffer); + for(size_t i = 0; i < self->num_packets; ++i) { + if(self->packets[i]) { + gsr_av_packet_ram_unref(self->packets[i]); + self->packets[i] = NULL; + } + } + self->num_packets = 0; + gsr_replay_buffer_unlock(&self->replay_buffer); + + if(self->packets) { + free(self->packets); + self->packets = NULL; + } + + self->capacity_num_packets = 0; + self->index = 0; +} + +static bool gsr_replay_buffer_ram_append(gsr_replay_buffer *replay_buffer, const AVPacket *av_packet, double timestamp) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + gsr_replay_buffer_lock(&self->replay_buffer); + gsr_av_packet_ram *packet = gsr_av_packet_ram_create(av_packet, timestamp); + if(!packet) { + gsr_replay_buffer_unlock(&self->replay_buffer); + return false; + } + + if(self->packets[self->index]) { + gsr_av_packet_ram_unref(self->packets[self->index]); + self->packets[self->index] = NULL; + } + self->packets[self->index] = packet; + + self->index = (self->index + 1) % self->capacity_num_packets; + ++self->num_packets; + if(self->num_packets > self->capacity_num_packets) + self->num_packets = self->capacity_num_packets; + + gsr_replay_buffer_unlock(&self->replay_buffer); + return true; +} + +static void gsr_replay_buffer_ram_clear(gsr_replay_buffer *replay_buffer) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + gsr_replay_buffer_lock(&self->replay_buffer); + for(size_t i = 0; i < self->num_packets; ++i) { + if(self->packets[i]) { + gsr_av_packet_ram_unref(self->packets[i]); + self->packets[i] = NULL; + } + } + self->num_packets = 0; + self->index = 0; + gsr_replay_buffer_unlock(&self->replay_buffer); +} + +static gsr_av_packet_ram* gsr_replay_buffer_ram_get_packet_at_index(gsr_replay_buffer *replay_buffer, size_t index) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + assert(index < self->num_packets); + size_t start_index = 0; + if(self->num_packets < self->capacity_num_packets) + start_index = self->num_packets - self->index; + else + start_index = self->index; + + const size_t offset = (start_index + index) % self->capacity_num_packets; + return self->packets[offset]; +} + +static AVPacket* gsr_replay_buffer_ram_iterator_get_packet(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator iterator) { + return &gsr_replay_buffer_ram_get_packet_at_index(replay_buffer, iterator.packet_index)->packet; +} + +static uint8_t* gsr_replay_buffer_ram_iterator_get_packet_data(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator iterator) { + (void)replay_buffer; + (void)iterator; + return NULL; +} + +static gsr_replay_buffer* gsr_replay_buffer_ram_clone(gsr_replay_buffer *replay_buffer) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + gsr_replay_buffer_ram *destination = calloc(1, sizeof(gsr_replay_buffer_ram)); + if(!destination) + return NULL; + + gsr_replay_buffer_ram_set_impl_funcs(destination); + gsr_replay_buffer_lock(&self->replay_buffer); + + destination->replay_buffer.original_replay_buffer = replay_buffer; + destination->replay_buffer.mutex = self->replay_buffer.mutex; + destination->replay_buffer.mutex_initialized = self->replay_buffer.mutex_initialized; + destination->capacity_num_packets = self->capacity_num_packets; + destination->index = self->index; + destination->packets = calloc(destination->capacity_num_packets, sizeof(gsr_av_packet_ram*)); + if(!destination->packets) { + free(destination); + gsr_replay_buffer_unlock(&self->replay_buffer); + return NULL; + } + + destination->num_packets = self->num_packets; + for(size_t i = 0; i < destination->num_packets; ++i) { + destination->packets[i] = gsr_av_packet_ram_ref(self->packets[i]); + } + + gsr_replay_buffer_unlock(&self->replay_buffer); + return (gsr_replay_buffer*)destination; +} + +/* Binary search */ +static gsr_replay_buffer_iterator gsr_replay_buffer_ram_find_packet_index_by_time_passed(gsr_replay_buffer *replay_buffer, int seconds) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + gsr_replay_buffer_lock(&self->replay_buffer); + + const double now = clock_get_monotonic_seconds(); + if(self->num_packets == 0) { + gsr_replay_buffer_unlock(&self->replay_buffer); + return (gsr_replay_buffer_iterator){0, 0}; + } + + size_t lower_bound = 0; + size_t upper_bound = self->num_packets; + size_t index = 0; + + for(;;) { + index = lower_bound + (upper_bound - lower_bound) / 2; + const gsr_av_packet_ram *packet = gsr_replay_buffer_ram_get_packet_at_index(replay_buffer, index); + const double time_passed_since_packet = now - packet->timestamp; + if(time_passed_since_packet >= seconds) { + if(lower_bound == index) + break; + lower_bound = index; + } else { + if(upper_bound == index) + break; + upper_bound = index; + } + } + + gsr_replay_buffer_unlock(&self->replay_buffer); + return (gsr_replay_buffer_iterator){index, 0}; +} + +static gsr_replay_buffer_iterator gsr_replay_buffer_ram_find_keyframe(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator start_iterator, int stream_index, bool invert_stream_index) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + size_t keyframe_index = (size_t)-1; + gsr_replay_buffer_lock(&self->replay_buffer); + for(size_t i = start_iterator.packet_index; i < self->num_packets; ++i) { + const gsr_av_packet_ram *packet = gsr_replay_buffer_ram_get_packet_at_index(replay_buffer, i); + if((packet->packet.flags & AV_PKT_FLAG_KEY) && (invert_stream_index ? packet->packet.stream_index != stream_index : packet->packet.stream_index == stream_index)) { + keyframe_index = i; + break; + } + } + gsr_replay_buffer_unlock(&self->replay_buffer); + return (gsr_replay_buffer_iterator){keyframe_index, 0}; +} + +static bool gsr_replay_buffer_ram_iterator_next(gsr_replay_buffer *replay_buffer, gsr_replay_buffer_iterator *iterator) { + gsr_replay_buffer_ram *self = (gsr_replay_buffer_ram*)replay_buffer; + if(iterator->packet_index + 1 < self->num_packets) { + ++iterator->packet_index; + return true; + } else { + return false; + } +} + +static void gsr_replay_buffer_ram_set_impl_funcs(gsr_replay_buffer_ram *self) { + self->replay_buffer.destroy = gsr_replay_buffer_ram_destroy; + self->replay_buffer.append = gsr_replay_buffer_ram_append; + self->replay_buffer.clear = gsr_replay_buffer_ram_clear; + self->replay_buffer.iterator_get_packet = gsr_replay_buffer_ram_iterator_get_packet; + self->replay_buffer.iterator_get_packet_data = gsr_replay_buffer_ram_iterator_get_packet_data; + self->replay_buffer.clone = gsr_replay_buffer_ram_clone; + self->replay_buffer.find_packet_index_by_time_passed = gsr_replay_buffer_ram_find_packet_index_by_time_passed; + self->replay_buffer.find_keyframe = gsr_replay_buffer_ram_find_keyframe; + self->replay_buffer.iterator_next = gsr_replay_buffer_ram_iterator_next; +} + +gsr_replay_buffer* gsr_replay_buffer_ram_create(size_t replay_buffer_num_packets) { + assert(replay_buffer_num_packets > 0); + gsr_replay_buffer_ram *replay_buffer = calloc(1, sizeof(gsr_replay_buffer_ram)); + if(!replay_buffer) + return NULL; + + replay_buffer->capacity_num_packets = replay_buffer_num_packets; + replay_buffer->num_packets = 0; + replay_buffer->index = 0; + replay_buffer->packets = calloc(replay_buffer->capacity_num_packets, sizeof(gsr_av_packet_ram*)); + if(!replay_buffer->packets) { + gsr_replay_buffer_ram_destroy(&replay_buffer->replay_buffer); + free(replay_buffer); + return NULL; + } + + gsr_replay_buffer_ram_set_impl_funcs(replay_buffer); + return (gsr_replay_buffer*)replay_buffer; +} |