aboutsummaryrefslogtreecommitdiff
path: root/src/encoder
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2025-04-21 23:02:29 +0200
committerdec05eba <dec05eba@protonmail.com>2025-04-21 23:02:29 +0200
commit81f155bf6306a2aa378b03920ed2b6f44e013016 (patch)
tree4da99d1db6d44cfa87c3970d14525ad13b7a85f4 /src/encoder
parentce7b47a877a3e47632f27c44c333a96221885202 (diff)
Refactor video encoding packet receiving, replay buffer and finish SIGRTMIN for recording while replay/replaying. Add -ro option to specify the directory
Diffstat (limited to 'src/encoder')
-rw-r--r--src/encoder/video/video.c159
1 files changed, 153 insertions, 6 deletions
diff --git a/src/encoder/video/video.c b/src/encoder/video/video.c
index 76d53b0..82711ce 100644
--- a/src/encoder/video/video.c
+++ b/src/encoder/video/video.c
@@ -1,12 +1,55 @@
#include "../../../include/encoder/video/video.h"
+#include "../../../include/utils.h"
+
+#include <string.h>
+#include <stdio.h>
#include <assert.h>
-bool gsr_video_encoder_start(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame) {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+bool gsr_video_encoder_start(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame, size_t replay_buffer_num_packets) {
assert(!encoder->started);
+ encoder->num_recording_destinations = 0;
+ encoder->recording_destination_id = 0;
+
+ if(pthread_mutex_init(&encoder->file_write_mutex, NULL) != 0) {
+ fprintf(stderr, "gsr error: gsr_video_encoder_start: failed to create mutex\n");
+ return false;
+ }
+
+ memset(&encoder->replay_buffer, 0, sizeof(encoder->replay_buffer));
+ if(replay_buffer_num_packets > 0) {
+ if(!gsr_replay_buffer_init(&encoder->replay_buffer, replay_buffer_num_packets)) {
+ fprintf(stderr, "gsr error: gsr_video_encoder_start: failed to create replay buffer\n");
+ goto error;
+ }
+ encoder->has_replay_buffer = true;
+ }
+
bool res = encoder->start(encoder, video_codec_context, frame);
- if(res)
+ if(res) {
encoder->started = true;
- return res;
+ return true;
+ } else {
+ goto error;
+ }
+
+ error:
+ pthread_mutex_destroy(&encoder->file_write_mutex);
+ gsr_replay_buffer_deinit(&encoder->replay_buffer);
+ return false;
+}
+
+void gsr_video_encoder_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context) {
+ assert(encoder->started);
+ encoder->started = false;
+ pthread_mutex_destroy(&encoder->file_write_mutex);
+ gsr_replay_buffer_deinit(&encoder->replay_buffer);
+ encoder->has_replay_buffer = false;
+ encoder->num_recording_destinations = 0;
+ encoder->recording_destination_id = 0;
+ encoder->destroy(encoder, video_codec_context);
}
void gsr_video_encoder_copy_textures_to_frame(gsr_video_encoder *encoder, AVFrame *frame, gsr_color_conversion *color_conversion) {
@@ -20,7 +63,111 @@ void gsr_video_encoder_get_textures(gsr_video_encoder *encoder, unsigned int *te
encoder->get_textures(encoder, textures, num_textures, destination_color);
}
-void gsr_video_encoder_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context) {
- assert(encoder->started);
- encoder->destroy(encoder, video_codec_context);
+void gsr_video_encoder_receive_packets(gsr_video_encoder *encoder, AVCodecContext *codec_context, int64_t pts, int stream_index) {
+ for (;;) {
+ AVPacket *av_packet = av_packet_alloc();
+ if(!av_packet)
+ break;
+
+ av_packet->data = NULL;
+ av_packet->size = 0;
+ int res = avcodec_receive_packet(codec_context, av_packet);
+ if(res == 0) { // we have a packet, send the packet to the muxer
+ av_packet->stream_index = stream_index;
+ av_packet->pts = pts;
+ av_packet->dts = pts;
+
+ if(encoder->has_replay_buffer) {
+ const double time_now = clock_get_monotonic_seconds();
+ if(!gsr_replay_buffer_append(&encoder->replay_buffer, av_packet, time_now))
+ fprintf(stderr, "gsr error: gsr_video_encoder_receive_packets: failed to add replay buffer data\n");
+ }
+
+ pthread_mutex_lock(&encoder->file_write_mutex);
+ const bool is_keyframe = av_packet->flags & AV_PKT_FLAG_KEY;
+ for(size_t i = 0; i < encoder->num_recording_destinations; ++i) {
+ gsr_video_encoder_recording_destination *recording_destination = &encoder->recording_destinations[i];
+ if(recording_destination->codec_context != codec_context)
+ continue;
+
+ if(is_keyframe)
+ recording_destination->has_received_keyframe = true;
+ else if(!recording_destination->has_received_keyframe)
+ continue;
+
+ av_packet->pts = pts - recording_destination->start_pts;
+ av_packet->dts = pts - recording_destination->start_pts;
+
+ av_packet_rescale_ts(av_packet, codec_context->time_base, recording_destination->stream->time_base);
+ // TODO: Is av_interleaved_write_frame needed?. Answer: might be needed for mkv but dont use it! it causes frames to be inconsistent, skipping frames and duplicating frames.
+ // TODO: av_interleaved_write_frame might be needed for cfr, or always for flv
+ const int ret = av_write_frame(recording_destination->format_context, av_packet);
+ if(ret < 0) {
+ char error_buffer[AV_ERROR_MAX_STRING_SIZE];
+ if(av_strerror(ret, error_buffer, sizeof(error_buffer)) < 0)
+ snprintf(error_buffer, sizeof(error_buffer), "Unknown error");
+ fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", av_packet->stream_index, error_buffer, ret);
+ }
+ }
+ pthread_mutex_unlock(&encoder->file_write_mutex);
+
+ av_packet_free(&av_packet);
+ } else if (res == AVERROR(EAGAIN)) { // we have no packet
+ // fprintf(stderr, "No packet!\n");
+ av_packet_free(&av_packet);
+ break;
+ } else if (res == AVERROR_EOF) { // this is the end of the stream
+ av_packet_free(&av_packet);
+ fprintf(stderr, "End of stream!\n");
+ break;
+ } else {
+ av_packet_free(&av_packet);
+ fprintf(stderr, "Unexpected error: %d\n", res);
+ break;
+ }
+ }
+}
+
+size_t gsr_video_encoder_add_recording_destination(gsr_video_encoder *encoder, AVCodecContext *codec_context, AVFormatContext *format_context, AVStream *stream, int64_t start_pts) {
+ if(encoder->num_recording_destinations >= GSR_MAX_RECORDING_DESTINATIONS) {
+ fprintf(stderr, "gsr error: gsr_video_encoder_add_recording_destination: failed to add destination, reached the max amount of recording destinations (%d)\n", GSR_MAX_RECORDING_DESTINATIONS);
+ return (size_t)-1;
+ }
+
+ for(size_t i = 0; i < encoder->num_recording_destinations; ++i) {
+ if(encoder->recording_destinations[i].stream == stream) {
+ fprintf(stderr, "gsr error: gsr_video_encoder_add_recording_destination: failed to add destination, the stream %p already exists as an output\n", (void*)stream);
+ return (size_t)-1;
+ }
+ }
+
+ pthread_mutex_lock(&encoder->file_write_mutex);
+ gsr_video_encoder_recording_destination *recording_destination = &encoder->recording_destinations[encoder->num_recording_destinations];
+ recording_destination->id = encoder->recording_destination_id;
+ recording_destination->codec_context = codec_context;
+ recording_destination->format_context = format_context;
+ recording_destination->stream = stream;
+ recording_destination->start_pts = start_pts;
+ recording_destination->has_received_keyframe = false;
+
+ ++encoder->recording_destination_id;
+ ++encoder->num_recording_destinations;
+ pthread_mutex_unlock(&encoder->file_write_mutex);
+
+ return recording_destination->id;
+}
+
+bool gsr_video_encoder_remove_recording_destination(gsr_video_encoder *encoder, size_t id) {
+ bool found = false;
+ pthread_mutex_lock(&encoder->file_write_mutex);
+ for(size_t i = 0; i < encoder->num_recording_destinations; ++i) {
+ if(encoder->recording_destinations[i].id == id) {
+ encoder->recording_destinations[i] = encoder->recording_destinations[encoder->num_recording_destinations - 1];
+ --encoder->num_recording_destinations;
+ found = true;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&encoder->file_write_mutex);
+ return found;
}