From e6c1cf0ec1ce220912a6ef6b6fac5ba9b2e98276 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 5 Aug 2026 14:56:59 +0200 Subject: workaround ffmpeg hybrid_fragmented bug when no packets have been recorded yet --- include/ffmpeg_utils.h | 13 ++++++++++ src/encoder/encoder.c | 3 +++ src/ffmpeg_utils.c | 28 ++++++++++++++++++++++ src/recorder/muxer.c | 2 +- src/recorder/recorder.c | 2 +- src/recorder/replay_save.c | 4 +++- .../ffmpeg-mbedtls-default-ca-certs.patch | 2 +- 7 files changed, 50 insertions(+), 4 deletions(-) diff --git a/include/ffmpeg_utils.h b/include/ffmpeg_utils.h index 35524c2..6d7481b 100644 --- a/include/ffmpeg_utils.h +++ b/include/ffmpeg_utils.h @@ -1,6 +1,19 @@ #ifndef GSR_FFMPEG_UTILS_H #define GSR_FFMPEG_UTILS_H +#include + +typedef struct AVFormatContext AVFormatContext; + const char* gsr_av_error_to_string(int err); +/* Marks that a packet has been successfully written to |av_format_context|, see gsr_av_format_context_write_trailer */ +void gsr_av_format_context_mark_packet_written(AVFormatContext *av_format_context); +/* + The same as av_write_trailer, except that the trailer is not written when no packet has been written to a muxer + that uses the hybrid_fragmented movflags option (see set_format_context_options), because the mov muxer in FFmpeg + crashes when it finalizes a hybrid_fragmented file that has no packets. Returns 0 on success, just like av_write_trailer. +*/ +int gsr_av_format_context_write_trailer(AVFormatContext *av_format_context); + #endif /* GSR_FFMPEG_UTILS_H */ diff --git a/src/encoder/encoder.c b/src/encoder/encoder.c index 2df4666..3aebb7a 100644 --- a/src/encoder/encoder.c +++ b/src/encoder/encoder.c @@ -1,6 +1,7 @@ #include "../../include/encoder/encoder.h" #include "../../include/log.h" #include "../../include/utils.h" +#include "../../include/ffmpeg_utils.h" #include #include @@ -152,6 +153,8 @@ void gsr_encoder_receive_packets(gsr_encoder *self, AVCodecContext *codec_contex // 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) + gsr_av_format_context_mark_packet_written(recording_destination->format_context); if(ret < 0) { char error_buffer[AV_ERROR_MAX_STRING_SIZE]; if(av_strerror(ret, error_buffer, sizeof(error_buffer)) < 0) diff --git a/src/ffmpeg_utils.c b/src/ffmpeg_utils.c index ce239a1..fc4e4ed 100644 --- a/src/ffmpeg_utils.c +++ b/src/ffmpeg_utils.c @@ -1,7 +1,10 @@ #include "../include/ffmpeg_utils.h" +#include "../include/log.h" #include #include +#include +#include static _Thread_local char av_error_buffer[AV_ERROR_MAX_STRING_SIZE]; @@ -10,3 +13,28 @@ const char* gsr_av_error_to_string(int err) { strcpy(av_error_buffer, "Unknown error"); return av_error_buffer; } + +void gsr_av_format_context_mark_packet_written(AVFormatContext *av_format_context) { + av_format_context->opaque = (void*)1; +} + +static bool av_format_context_uses_hybrid_fragmented(AVFormatContext *av_format_context) { + if(LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(62, 6, 101)) + return false; + + const AVOption *opt = av_opt_find(av_format_context->priv_data, "movflags", NULL, 0, 0); + if(!opt || !opt->unit) + return false; + + return av_opt_find(av_format_context->priv_data, "hybrid_fragmented", opt->unit, 0, 0) != NULL; +} + +int gsr_av_format_context_write_trailer(AVFormatContext *av_format_context) { + const bool packet_written = av_format_context->opaque != NULL; + if(!packet_written && av_format_context_uses_hybrid_fragmented(av_format_context)) { + gsr_log(GSR_LOG_LEVEL_WARNING, "not finalizing the video file because it has no video/audio data"); + return 0; + } + + return av_write_trailer(av_format_context); +} diff --git a/src/recorder/muxer.c b/src/recorder/muxer.c index ff4ec06..806921d 100644 --- a/src/recorder/muxer.c +++ b/src/recorder/muxer.c @@ -197,7 +197,7 @@ bool gsr_recording_output_start(gsr_recording_output *self, const char *filename bool gsr_recording_output_stop(gsr_recording_output *self) { bool trailer_written = true; - if(av_write_trailer(self->av_format_context) != 0) { + if(gsr_av_format_context_write_trailer(self->av_format_context) != 0) { //trailer_written = false; } diff --git a/src/recorder/recorder.c b/src/recorder/recorder.c index 4bffb8e..fc0ccf0 100644 --- a/src/recorder/recorder.c +++ b/src/recorder/recorder.c @@ -886,7 +886,7 @@ static void gsr_recorder_stop_recording(gsr_recorder *self) { gsr_audio_capture_join_threads(&self->audio_capture); // TODO: Replace this with start_recording_create_steams - if(!self->settings.is_replaying && av_write_trailer(self->av_format_context) != 0) { + if(!self->settings.is_replaying && gsr_av_format_context_write_trailer(self->av_format_context) != 0) { //fprintf(stderr, "Failed to write trailer\n"); } diff --git a/src/recorder/replay_save.c b/src/recorder/replay_save.c index c3a1bea..3251616 100644 --- a/src/recorder/replay_save.c +++ b/src/recorder/replay_save.c @@ -96,7 +96,9 @@ static void* replay_save_thread(void *userdata) { av_packet_rescale_ts(&av_packet, codec_context->time_base, stream->time_base); const int ret = av_write_frame(self->recording_output.av_format_context, &av_packet); - if(ret < 0) + if(ret >= 0) + gsr_av_format_context_mark_packet_written(self->recording_output.av_format_context); + else gsr_log(GSR_LOG_LEVEL_ERROR, "Failed to write frame index %d to muxer, reason: %s (%d)", av_packet.stream_index, gsr_av_error_to_string(ret), ret); free(replay_packet_data); diff --git a/subprojects/packagefiles/ffmpeg-mbedtls-default-ca-certs.patch b/subprojects/packagefiles/ffmpeg-mbedtls-default-ca-certs.patch index a22d4ae..2a36992 100644 --- a/subprojects/packagefiles/ffmpeg-mbedtls-default-ca-certs.patch +++ b/subprojects/packagefiles/ffmpeg-mbedtls-default-ca-certs.patch @@ -1,6 +1,6 @@ --- a/libavformat/tls_mbedtls.c +++ b/libavformat/tls_mbedtls.c -@@ -42,6 +42,66 @@ +@@ -42,6 +42,64 @@ #include "libavutil/avstring.h" #include "libavutil/random_seed.h" #include "libavutil/intreadwrite.h" -- cgit v1.2.3