From 1c984176cda32d73c6dcda13421fabbba6cab7d6218ebb284467d563055e271c Mon Sep 17 00:00:00 2001 From: copecone Date: Tue, 2 Jun 2026 19:49:51 +0900 Subject: [PATCH] Update `FFmpegNativeEncoder` to Implement Direct API Encoding --- Util/FFmpeg/FFmpegNativeEncoder.cs | 94 ++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/Util/FFmpeg/FFmpegNativeEncoder.cs b/Util/FFmpeg/FFmpegNativeEncoder.cs index 7629f42..0a5c36b 100644 --- a/Util/FFmpeg/FFmpegNativeEncoder.cs +++ b/Util/FFmpeg/FFmpegNativeEncoder.cs @@ -1,7 +1,7 @@ using System; using FFmpeg.AutoGen; -using HarmonyLib; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; namespace ADOFAIRenderer.Util.FFmpeg { @@ -172,12 +172,36 @@ namespace ADOFAIRenderer.Util.FFmpeg public void WriteFrameFromBuffer(byte* buffer) { - + if (!_isInitialized) throw new InvalidOperationException("인코더가 초기화되지 않았습니다."); + + _pSrcFrame->data[0] = buffer; + _pSrcFrame->linesize[0] = _settings.Width * 4; // RGBA = 4 bytes per pixel + _pSrcFrame->width = _settings.Width; + _pSrcFrame->height = _settings.Height; + _pSrcFrame->format = (int)AVPixelFormat.AV_PIX_FMT_RGBA; + + WriteFrame(); } public void WriteFrameFromNativeBuffer(NativeArray buffer) { - + if (!_isInitialized) throw new InvalidOperationException("인코더가 초기화되지 않았습니다."); + WriteFrameFromBuffer((byte*)buffer.GetUnsafeReadOnlyPtr()); + } + + public void SendDataFromNativeBuffer(IntPtr pointer, int length) + { + if (!_isInitialized) throw new InvalidOperationException("인코더가 초기화되지 않았습니다."); + WriteFrameFromBuffer((byte*)pointer.ToPointer()); + } + + public void SendDataFromBuffer(byte[] buffer, int length) + { + if (!_isInitialized) throw new InvalidOperationException("인코더가 초기화되지 않았습니다."); + fixed (byte* pBuffer = buffer) + { + WriteFrameFromBuffer(pBuffer); + } } private void WriteFrame() @@ -198,7 +222,69 @@ namespace ADOFAIRenderer.Util.FFmpeg private void SendFrameAndWritePackets(AVFrame* pFrame) { - + int response = ffmpeg.avcodec_send_frame(_pCodecContext, pFrame); + if (response < 0) + { + throw new Exception($"인코더에 프레임을 전송하는 도중 에러가 발생했습니다. (Code: {response})"); + } + + AVPacket* pPacket = ffmpeg.av_packet_alloc(); + try + { + while (true) + { + response = ffmpeg.avcodec_receive_packet(_pCodecContext, pPacket); + if (response == ffmpeg.AVERROR(ffmpeg.EAGAIN) || response == ffmpeg.AVERROR_EOF) + { + break; + } + else if (response < 0) + { + throw new Exception($"인코더에서 패킷을 수신하는 도중 에러가 발생했습니다. (Code: {response})"); + } + + // 스트림 타임베이스로 패킷 PTS/DTS 재조정 + ffmpeg.av_packet_rescale_ts(pPacket, _pCodecContext->time_base, _pStream->time_base); + pPacket->stream_index = _pStream->index; + + // 파일에 패킷 쓰기 + response = ffmpeg.av_interleaved_write_frame(_pFormatContext, pPacket); + if (response < 0) + { + throw new Exception($"패킷을 파일에 쓰는 도중 에러가 발생했습니다. (Code: {response})"); + } + + ffmpeg.av_packet_unref(pPacket); + } + } + finally + { + ffmpeg.av_packet_free(&pPacket); + } + } + + public void End() + { + if (!_isInitialized) return; + + // 1. 지연된 프레임 플러시 (null 프레임 전송) + try + { + SendFrameAndWritePackets(null); + } + catch (Exception ex) + { + // 플러시 과정 중 발생한 에러 기록 + UnityEngine.Debug.LogError($"인코더 플러시 중 오류 발생: {ex.Message}"); + } + + // 2. 트레일러 쓰기 + if (_pFormatContext != null) + { + ffmpeg.av_write_trailer(_pFormatContext); + } + + _isInitialized = false; } public void Dispose()