mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-03-31 09:07:13 +09:00
Add -replay-storage option to specify if temporary replay data should be stored in ram or disk
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
89
src/main.cpp
89
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
91
src/replay_buffer/replay_buffer.c
Normal file
91
src/replay_buffer/replay_buffer.c
Normal file
@@ -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);
|
||||
}
|
||||
429
src/replay_buffer/replay_buffer_disk.c
Normal file
429
src/replay_buffer/replay_buffer_disk.c
Normal file
@@ -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;
|
||||
}
|
||||
256
src/replay_buffer/replay_buffer_ram.c
Normal file
256
src/replay_buffer/replay_buffer_ram.c
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user