Fix Incomplete Output File Handling

This commit is contained in:
copecone committed 2026-10-11 23:53:41 +09:00
1 parent c0e902fc1a
commit 93539307b1
2 files changed
+65 -42

No files matched your search

+24 -30
View File
@@ -1520,8 +1520,13 @@ namespace ADOFAIRenderer.Util.FFmpeg
/// 2. av_write_trailer: Writes container trailer (end-of-file metadata).
/// For MP4, the moov atom (index info) is completed here.
/// ⚠️ If trailer is not written, the file becomes corrupted and unplayable!
/// av_write_trailer also flushes the output buffer and returns the write error of the output (AVIOContext.error), if any.
///
/// Throws an exception when a flush or the trailer fails (e.g. the disk is full), because the file is then incomplete.
/// End() runs once: after it returns or throws, further calls return immediately.
///
/// Reference: https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html
/// Reference: https://github.com/FFmpeg/FFmpeg/blob/release/8.0/libavformat/mux.c (av_write_trailer)
/// </summary>
public void End()
{
@@ -1529,37 +1534,29 @@ namespace ADOFAIRenderer.Util.FFmpeg
{
if (_disposed || !_isInitialized) return;
// 1. Flush delayed frames from video encoder (send null frame as "end of input" signal)
try
{
// 1. Flush delayed frames from video encoder (send null frame as "end of input" signal)
SendFrameAndWritePackets(null);
}
catch (Exception ex)
{
// Log error occurring during flush process
Melon<RenderMod>.Logger.Error($"Error during video encoder flush: {ex.Message}");
}
// 1.5. Flush remaining samples in audio FIFO + flush audio encoder
if (_audioEnabled)
{
try
// 1.5. Flush remaining samples in audio FIFO + flush audio encoder
if (_audioEnabled)
{
FlushAudioEncoder();
}
catch (Exception ex)
// 2. Write container trailer (moov atom and file termination metadata)
if (_pFormatContext != null)
{
Melon<RenderMod>.Logger.Error($"Error during audio encoder flush: {ex.Message}");
var ret = ffmpeg.av_write_trailer(_pFormatContext);
if (ret < 0)
throw new Exception($"Failed to write the container trailer — {GetFFmpegErrorString(ret)} (Code: {ret})");
}
}
// 2. Write container trailer (moov atom and file termination metadata)
if (_pFormatContext != null)
finally
{
ffmpeg.av_write_trailer(_pFormatContext);
_isInitialized = false;
}
_isInitialized = false;
}
}
@@ -1570,11 +1567,13 @@ namespace ADOFAIRenderer.Util.FFmpeg
/// <summary>
/// Actual resource cleanup logic. Cleanup order is important:
///
/// 1. Call End() (if not called yet) → flush delayed frames + write trailer
/// 2. SwsContext (scaler) → independent, can be freed first
/// 3. AVFrame (src/dst) → freed before codec as codec may reference them
/// 4. AVCodecContext (codec) → hw_frames_ctx/hw_device_ctx freed together
/// 5. AVFormatContext (format/IO) → includes closing file handle
/// 1. SwsContext (scaler) → independent, can be freed first
/// 2. AVFrame (src/dst) → freed before codec as codec may reference them
/// 3. AVCodecContext (codec) → hw_frames_ctx/hw_device_ctx freed together
/// 4. AVFormatContext (format/IO) → includes closing file handle
///
/// Dispose does not finish the file (no encoder flush, no trailer). A file is complete only after End() returned.
/// Without End(), the output file is incomplete and is left to the caller (the renderer deletes it).
///
/// Reference:
/// av_frame_free: https://ffmpeg.org/doxygen/trunk/group__lavu__frame.html
@@ -1588,12 +1587,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
{
if (_disposed) return;
_disposed = true;
// Trigger End() safely in case Dispose is called without calling End() first
if (_isInitialized)
{
try { End(); } catch { /* Ignore */ }
}
_isInitialized = false;
// Release Sws scaler
if (_pSwsContext != null)