From c0e902fc1a60d3f93e9617784e4135cc82604ec87edfaad16bdca8b0080c2e58 Mon Sep 17 00:00:00 2001 From: copecone Date: Sun, 11 Oct 2026 23:28:47 +0900 Subject: [PATCH] Reallocate Shared Video Frame Buffer Without Copying --- Util/FFmpeg/FFmpegNativeEncoder.cs | 58 +++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 16 deletions(-) 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: