diff --git a/Util/FFmpeg/FFmpegNativeEncoder.cs b/Util/FFmpeg/FFmpegNativeEncoder.cs
index b4cb7cc..bf37a9a 100644
--- a/Util/FFmpeg/FFmpegNativeEncoder.cs
+++ b/Util/FFmpeg/FFmpegNativeEncoder.cs
@@ -824,16 +824,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
if (_pSrcFrame == null || _pDstFrame == null || _pPacket == null)
throw new Exception("Failed to allocate frame or packet structure.");
- // Set format and size of destination frame, then allocate actual pixel buffer.
- // av_frame_get_buffer: Allocates memory matching format, width, height and
- // sets data[]/linesize[] pointers. Second argument 0 means default alignment.
- _pDstFrame->format = (int)_dstPixFmt;
- _pDstFrame->width = _settings.Width;
- _pDstFrame->height = _settings.Height;
- _pDstFrame->color_range = AVColorRange.AVCOL_RANGE_JPEG;
-
- if (ffmpeg.av_frame_get_buffer(_pDstFrame, 0) < 0)
- throw new Exception($"Failed to allocate {_dstPixFmt} frame buffer.");
+ AllocateDstFrameBuffer();
// sws_getContext: Initialize software scaler (colorspace converter).
// Input: RGBA (Unity capture format, 4 bytes/pixel)
@@ -1201,12 +1192,50 @@ namespace ADOFAIRenderer.Util.FFmpeg
}
}
+ ///
+ /// Sets the format, size and color range of the destination frame (_pDstFrame) and allocates a new pixel buffer for it.
+ /// av_frame_get_buffer allocates memory matching format, width and height and sets the data[]/linesize[] pointers.
+ /// Second argument 0 means default alignment.
+ /// Reference: https://ffmpeg.org/doxygen/trunk/group__lavu__frame.html (av_frame_get_buffer)
+ ///
+ private void AllocateDstFrameBuffer()
+ {
+ _pDstFrame->format = (int)_dstPixFmt;
+ _pDstFrame->width = _settings.Width;
+ _pDstFrame->height = _settings.Height;
+ _pDstFrame->color_range = AVColorRange.AVCOL_RANGE_JPEG;
+
+ var ret = ffmpeg.av_frame_get_buffer(_pDstFrame, 0);
+ if (ret < 0)
+ throw new Exception($"Failed to allocate {_dstPixFmt} frame buffer — {GetFFmpegErrorString(ret)} (Code: {ret})");
+ }
+
+ ///
+ /// Makes the pixel buffer of the destination frame (_pDstFrame) writable before the next frame is written into it.
+ ///
+ /// After avcodec_send_frame, the encoder may keep a reference to the buffer of the submitted frame
+ /// until that frame is encoded (e.g. QSV in system memory input mode). The buffer is then shared and not writable
+ /// (av_frame_is_writable returns 0).
+ /// In that case the frame releases its reference (av_frame_unref) and a new buffer is allocated.
+ /// The encoder keeps the previous buffer until it no longer needs it. The previous contents are not copied,
+ /// because every plane is overwritten by the next frame.
+ ///
+ /// Reference: https://ffmpeg.org/doxygen/trunk/group__lavu__frame.html (av_frame_is_writable, av_frame_unref)
+ ///
+ private void PrepareDstFrameForWrite()
+ {
+ if (ffmpeg.av_frame_is_writable(_pDstFrame) != 0) return;
+
+ ffmpeg.av_frame_unref(_pDstFrame);
+ AllocateDstFrameBuffer();
+ }
+
///
/// Encodes a frame by directly copying already pre-converted NV12 or YUV420P frame data.
///
private void WritePreConvertedFrame(byte* buffer)
{
- ffmpeg.av_frame_make_writable(_pDstFrame);
+ PrepareDstFrameForWrite();
var width = _settings.Width;
var height = _settings.Height;
@@ -1320,20 +1349,17 @@ namespace ADOFAIRenderer.Util.FFmpeg
/// Core logic for actual frame encoding.
///
/// Processing flow:
- /// 1. av_frame_make_writable: Check if destination frame buffer is writable
+ /// 1. PrepareDstFrameForWrite: Makes the destination frame buffer writable
/// (Allocates a new buffer if encoder is still referencing previous frame's buffer)
/// 2. sws_scale: RGBA → YUV420P/NV12 colorspace conversion
/// 3. (QSV hw_frames mode) av_hwframe_transfer_data: Upload NV12 → QSV hardware surface
/// 4. Pass to encoder after setting PTS
///
- /// Reference: https://ffmpeg.org/doxygen/trunk/group__lavu__frame.html (av_frame_make_writable)
/// Reference: https://ffmpeg.org/doxygen/trunk/group__libsws.html (sws_scale)
///
private void WriteFrame()
{
- // Ensure writable state since encoder might still be referencing previous frame's buffer.
- // Internally allocates a new buffer if referenced.
- ffmpeg.av_frame_make_writable(_pDstFrame);
+ PrepareDstFrameForWrite();
// ── Colorspace conversion: RGBA → YUV420P or NV12 ──
// sws_scale parameters explanation: