diff options
author | dec05eba <dec05eba@protonmail.com> | 2025-04-21 23:02:29 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2025-04-21 23:02:29 +0200 |
commit | 81f155bf6306a2aa378b03920ed2b6f44e013016 (patch) | |
tree | 4da99d1db6d44cfa87c3970d14525ad13b7a85f4 /src/encoder | |
parent | ce7b47a877a3e47632f27c44c333a96221885202 (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.c | 159 |
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; } |