From 9bb2b85010284cb58acc2f4650d7768c0d39e36e8c0acb912bdcb29d0bd991e0 Mon Sep 17 00:00:00 2001 From: copecone Date: Sat, 20 Jun 2026 17:05:05 +0900 Subject: [PATCH] Update Color Conversion Shader --- Bundle/Linux/shaders | Bin 10607 -> 12391 bytes Bundle/Win64/shaders | Bin 2915 -> 3250 bytes ...VideoDecoder.cs => FFmpegNativeDecoder.cs} | 294 +++++++++++++++++- Util/FFmpeg/FFmpegNativeEncoder.cs | 29 +- 4 files changed, 299 insertions(+), 24 deletions(-) rename Util/FFmpeg/{FFmpegVideoDecoder.cs => FFmpegNativeDecoder.cs} (67%) diff --git a/Bundle/Linux/shaders b/Bundle/Linux/shaders index 899ccbacb28a7b6c782b32372277a1d83c436a7315a84c7b1739c8569a1bb244..8955a157c33ad0fb5742766978a777c71af11179444cbad9581f3ce5aba83c4b 100644 GIT binary patch delta 12364 zcmV-SFtg9^Qs-chBmyvJktQM&M8E(5FV_G;4gd)p000^Q@E8D*TSR{n*)Wk8>^&|y zDa0pXy^ni(L_{;ewS=7+{ZkGE?^aw#NQ*z-0nlEhuZ>UA8xOY}EpUz0J-Qhzml^h; zF>u&LQ&37ViR%#o>wKp7J(Qqlj!&2pAXt1aVvc?3L{hqdIc8n?{V><-S5AkWgS8!Y`j1T16k#zD+rN z{N!|>VT_IgEH27_46`Jwg+!jf%j>YRBdBZ_E!xp)R>qPxaLx$;1j z2SO-3`R(>nh|Ah=L`b!PZj@9wdB?*i-K&}y$xQ^hsmy=8mT55*=|3-ETcmFa;I=aI zFL50!!Z@!_iH1l*5k>X(22Ww&iRkuq5v)y8pGE$IoKzK2G4`*PWjg{pW^RdJR2@pV z)S1))h7XY7cF*epK9a9`t#IBi0z#*Cxw7RqI*0|@vu7&iKW9yM+Hw;PwyGeulkK;80>lok}j!jG2EnMAHI$;Pdp zXq@YE16N`g+>07KOCsk%`n|D;(!D$C6=2B;?YUuw&7aa27h(2OBLXju&n&^2ZB^9U z4s{pz8T7AHUKFcL`3YamA904tP7UICl-SJ!R6Bph7N$}KY8a8Kqy9ukBvB1zcjgc^ zZ&(sVO3$2frWZPon+4YkHF@kAl&?r@B{pVvTi8OjPcY1IJ181ZwMP|hE;@)RHi-eh z^TXO?H+nN8wkBm}OY|&BW&Sc(CKi&Yld)sy?R-8$DCJ;X&{;chympIu1t)@$#C%W{ z`u%@;Js_@bI8QG0bxK3(_xJ+}gtYSkmvht{utxK!7@T5-HOjCO?}DGx#Fz^Jge!?H z5;I+utfa#I-qBJ| zEol$QLCId^VRhEqf;h(>@VNQQR2OV{RdE?@`i8I`^3|a7(CZqAX;O*>^?r}64|C}{ zVKbEk)8u+KH|IGPPy+(37g1F)rYqzuc)-!zq8O zR1dcb{A0FJbZSIPMxEbduSGGb;UNE2Y(qWKQ?9Q2%cE^?+(|=@P~{r-NI`{pN%9JW z4=$1C5=sD!ACi2#Fmr0}ptU{lg{^M_g^q9l7)>i7qLy-CZN5=Rn~P8;5sisgk^91!m| zl*`K+0yRtobPWi!W4}zw51G8LT)U=ahPrU$wdAXksUIWY)4pcK(eRbw#?3j`T6nwfW**#aB^Jy`J^>is(I*LWujngej8QK;u^fK1sE9a&zzWvmW8v9Lzg zM07dKcH!b+W>288c$O<-nQQRtotnmIG+sO}{#BP)o{jzMll{s?6i9!4t&8yEk|%wD zgHXQ$z-}Bntuil<02rj%^NR|#l!zz`=sGs5Xg(xMb?OeXBe&n{5IE_{sn2`Pirj`1 z9yY^+;ZqteOS5ExF7yvs{`o|}Eiq?Um;vTZz{N~~tnvd2o!6C%AaKKD7K zmi>GG8~g($Z$*seOHD_J7U*QCF*(;h0Q9#X*dq1K10cwz)Cz6H*U>;>u2OKkLwFoRqf5rx63&X>e~b#p#*GbpAgcVm(Wx9l$B_r$g0yn@y7rAV4%RZ<(9k z05i3p94NmwQm7$2kf{<%`MxT9bleajc5|DAP`^FHEgXM0?@`WWsTCAy@EbQ>8zD%8mvz%NmO*mEm-yqnvyULOz;1i72Rc z5@~-@X=Z}MUo91<#!_~93{bO z)%=q3)hx3;Q9;g?$t=xtubE8R7RDrKT3wx_AZ;AeF_h%GNB>f_w=2jR;e_)yEm&z? zw3cO+E8Bn*mT2k{MK!DE@X3gV*sq)+?9Y%1w%vdCaLB~b^@a}V?@$)pow)(Up$0Ka zPg}7EOK+VhiDq)|bYlw=&xAisbY%mrMfR^e|L)I)hxX{he$j(BAWBIy2XbGLH7GgX>wd-S>^heT9nYlxK3L!X!jAD+U{g3Qh{40ON zX5Hz$+2>Bfi|R;k$I?a&qKGqRw_jD00)7!+S;|h|_DHZK&7z` z3GEM}t@IvlGJwFHevSB?v*GLWX)1rr{oezPd=YBO1A@g|D%lMnK?JDkg|R!SbhJMt z5xlRn6h7|FLzK6g(m%Dc;w!6)N{78}Y8f4{I64IskACK_;ZJK?=Be^;dy8`Ame0RQ z7!sKj^G0xUGD1RfOHj+;@uBXy+anKkuHt)vlQ_{vT9TfFVf0+uBiPG*I6;5H=E>kl zUtTsY_-jnSIpRnr3KlN)sDTM%0DCtXGTIeE;+?Kug*i4|PdZ_Gt~X#?F?ZA}8#wcf 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 delta 10566 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 diff --git a/Bundle/Win64/shaders b/Bundle/Win64/shaders index 26679639736ed728edd46d3a919df33db3bf1eb3e8e3f09a6ded28d367a3338f..bb65621640aeeabc9b4eeada02786edb826ea79bc6801e48f79e765c34785cc9 100644 GIT binary patch delta 3060 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 CDEue@ delta 2722 zcmV;T3SITG8RHg^BmxU#ktQMrX;=UN3e%BJQ8*))BDKxVzL6m!2knX}#tDwj8rEz6 zt#FBlo%DM_V_RRlGTW9Eu=6rVDw-E}Z znNokQL}j+W9N0KEC)5ME6nSdv4x09(Q%JAg%9Fm&ov679q>;M8!WXPm5ekzpP-Oo`Re5czxAAbQMiK(S}mL)FWL+CYX@z^;M zO0XqY1%IvE6#nuBB55W4UQVD?*{x5ce+F%f;|wc)IsTF@W27RC{E$~43G-9k+&eT& zHQ?6_&tlt>3c?kOFXpzuyZ6 zXT}7O^2kEsmi)h|+MP$>n z6YXc3xYi+r-_Eb_W7&Li&yc$YhdNCe$mc*Yco7bFPHP{e3_61D;I5cys7;Diz zBAbN2y=<~}6|r6m?+nLvqwBiwCn4{TQhRxNH$4!(M5-J65S*iXABu|5PsMfv=5gNx6e6frf60AQI9zdH`LK&&GX; ze;`FmJMC!)JVeDsu3}mQeALsGt5i*@Wa-eh#nJo?O=r?JaoUFAp}>MnHvf@*76dCH zP&oAa3m_4mjNkf&daI8{($da%=ua|4@PFA^VFc*bzf#FikbeK|1~9O-~A}_{rSB~52@tRvplYgX?S=nLD9i%=pix% zpy}$$U+t0dLhViJ6v^ha;Z6(UtVM_EB{<<=He-Zb;JfY(ixdoHUiyJF0^LLHD1TO@ zb!TYIPIH3d5v^f-Cxu);oE*9kgW)-qCHI&E|Kx<}f1yX8X68MT=oelXKK=QUnsEZS z&IB5RXn1mAQFtLzhw&%>mKs@oK>ckA#9K}%8⪻+U3pf7`LFrEHrEKnBTixR9H5dXveGsbo2nUOMhWOCnU;gL(4Go*yTKL*O3i?YJWI==rBu$rs;NG zuKpw0Uer!eS?jz{PeNEw+Na^e_(&YsaY=Fu3>5&%C)gPo59<+UsY%}|>;A)eng&W; z(ib48n7+uwLA82A4BWF;GP#upX0*VV1fy<731sW*j#9=cdH5w-p@Dnc=YKCKz*kDC z_jw_@nfv1pp3v01rK$eroTZIX8tr^s22DGI!fOnEHb~8f)RoGw$fj9v#42d|iDBVX zgvAHD7w@<=25vpgjIW9R9HEJ%?@AfYhvJ(J{1&ksrS&xbVk20$ji6Ntu zLvh%3nAC^5JGWLwsNWi1_J8Q8XzwQyduq1};7MxgTUC1(xJz)(O)tata(pVS7vMrn z?MZbY?v!NN8Lx{e%0(EcGxq~NMK9t!WS=AmigG^2fX|y)S>KnN8jk|W@(A^qnYgA^ zpm7`y42&+b(U2tg$FAk(fz&*$s}Muzeztyp`)zRSvmL86u7@Bbu75Wh5{}sZ$K}Dl zzoTayN5{*e60M_*)s991g0{m7N0$qsBC&C^9j`$1a#j2QD=Bc0WWOvdR@1C?oXoa~ zgz0Y1leM*v>K)0Auad%Hd-1bwtCn(N+JeEMd*GDOHy8=(UnHoNwcC2!AB@)dz0W*d z2{P`4UmlUKq9LgzFn{K6whgVs5;EUGyn~y&)k1>*za7cfq=;Mm8t+Lbzw*|pY_$8; z%k-gDDK-WQkL9`Ta!9s&Q_@`bHGSMs#SMV9>d`!n-v>L2_b`D`^_zJg(0!P!hfYRc z2c*9rc=8Jo*7{Qr^Wrj%(*maNiOfnb9 z_vC_sNH5ZnzzR0<+05+L0L%<4L^^1r5Y>VTco7~?UJgK390PG51n@W9wWngAH@8Z1 z&b}p*|DA1}!G9r1r5vQfVTK)q0RZUTe9MxdF|S4z$-x1c#-Gt=p0Wu0;`YvO*wcdZ zx`P3f2%9E)oc?f2mM$>;qwlUc!HH$GCdzd0BAabsx#V z6PzApew#}yXNGDS!fi}$f7xa9rDZ_I3e27k1NDRLc7GP547v=AC8YCi9Vy_Rc+~ME zXfVJ+P|<6C2qU$i?)Zk78(lMG-HX=D;@BXsQk1HQpeM8GZ^_C)7eCGGZ5S$0G0B|+mQl_j7I>Z`pYqYg->SeS?KnDvnx~-{eI%WwStdEKrgvvHpS}|0FOEIItY(o24c$ZrKqK< z`^!KeI;g{}$SwCBz?4emo;|oOS*-zJ27iD?(v)m`tku%-POojY*~Kpq&zi)9CojkY z$8Bx7aQ-(UsT&tgZv))Hx`})+4}H8($R%CA$Um9+h|_67EKThE^XwLjS?EYrMY<|^ zkWVZr%KYeytPHADF$cIS@soh$#I)~SkN`mbw{YuwN;&mn8E@6jps+15B5Z|k6i=zS zKfb&wxG<2<1^h$7iW?=7(jc?JJybG2mZp^n$OhJ7Z=UL!oB>*iF#l!GXR+m1YcI>$ c2!P@mfr>(f!&@lf - public sealed unsafe class FFmpegVideoDecoder : IDisposable + public sealed unsafe class FFmpegNativeDecoder : IDisposable { private AVFormatContext* _pFormatContext = null; private AVCodecContext* _pCodecContext = null; @@ -33,6 +32,7 @@ namespace ADOFAIRenderer.Util.FFmpeg // SwsContext 갱신 감지용 필드 private AVPixelFormat _srcPixFmt = AVPixelFormat.AV_PIX_FMT_NONE; + private AVPixelFormat _dstPixFmt = AVPixelFormat.AV_PIX_FMT_NONE; private int _srcWidth; private int _srcHeight; @@ -57,7 +57,10 @@ namespace ADOFAIRenderer.Util.FFmpeg /// 현재 재생 시간 (초). public double CurrentTime => _currentFrameIndex / FPS; - public FFmpegVideoDecoder(string videoPath) + /// 비디오 원본 코덱의 픽셀 포맷. + public AVPixelFormat PixelFormat => _pCodecContext != null ? _pCodecContext->pix_fmt : AVPixelFormat.AV_PIX_FMT_NONE; + + public FFmpegNativeDecoder(string videoPath) { Open(videoPath); } @@ -192,9 +195,9 @@ namespace ADOFAIRenderer.Util.FFmpeg } } - private void InitOrUpdateSwsContext(AVPixelFormat srcFormat, int srcWidth, int srcHeight) + private void InitOrUpdateSwsContext(AVPixelFormat srcFormat, int srcWidth, int srcHeight, AVPixelFormat dstFormat) { - if (_pSwsContext != null && _srcPixFmt == srcFormat && _srcWidth == srcWidth && _srcHeight == srcHeight) + if (_pSwsContext != null && _srcPixFmt == srcFormat && _srcWidth == srcWidth && _srcHeight == srcHeight && _dstPixFmt == dstFormat) { return; } @@ -208,11 +211,11 @@ namespace ADOFAIRenderer.Util.FFmpeg _srcPixFmt = srcFormat; _srcWidth = srcWidth; _srcHeight = srcHeight; + _dstPixFmt = dstFormat; - // Unity 표준인 RGBA32(AV_PIX_FMT_RGBA) 포맷으로 변환하도록 설정 _pSwsContext = ffmpeg.sws_getContext( srcWidth, srcHeight, srcFormat, - Width, Height, AVPixelFormat.AV_PIX_FMT_RGBA, + Width, Height, dstFormat, (int)SwsFlags.SWS_BILINEAR, null, null, null ); @@ -262,7 +265,7 @@ namespace ADOFAIRenderer.Util.FFmpeg if (_hasCachedFrame) { timestamp = GetTimestampAndUpdateIndex(_pCachedFrame); - InitOrUpdateSwsContext(_pCodecContext->pix_fmt, _pCachedFrame->width, _pCachedFrame->height); + InitOrUpdateSwsContext(_pCodecContext->pix_fmt, _pCachedFrame->width, _pCachedFrame->height, AVPixelFormat.AV_PIX_FMT_RGBA); if (vflip) { @@ -293,7 +296,7 @@ namespace ADOFAIRenderer.Util.FFmpeg if (ret == 0) { timestamp = GetTimestampAndUpdateIndex(_pDecodedFrame); - InitOrUpdateSwsContext(_pCodecContext->pix_fmt, _pDecodedFrame->width, _pDecodedFrame->height); + InitOrUpdateSwsContext(_pCodecContext->pix_fmt, _pDecodedFrame->width, _pDecodedFrame->height, AVPixelFormat.AV_PIX_FMT_RGBA); if (vflip) { @@ -354,6 +357,277 @@ namespace ADOFAIRenderer.Util.FFmpeg return TryReadFrame((byte*)outBuffer.GetUnsafePtr(), stride, vflip, out timestamp); } + /// + /// 다음 비디오 프레임을 읽어 Y, U, V 채널 데이터를 각각 복사합니다. + /// 비디오 원본이 YUV420p 계열(YUV420P, YUVJ420P)이면 CPU 컬러 변환(sws_scale)을 우회하고 메모리를 다이렉트로 복사하여 속도가 매우 빠릅니다. + /// 그 외 포맷일 경우 자동으로 YUV420p로 변환 후 복사합니다. + /// + public bool TryReadYUV420PFrame( + byte* yBuffer, int yStride, + byte* uBuffer, int uStride, + byte* vBuffer, int vStride, + out double timestamp) + { + timestamp = 0; + lock (_lock) + { + if (!_isOpened || _disposed) return false; + + AVFrame* frame = null; + + if (_hasCachedFrame) + { + frame = _pCachedFrame; + _hasCachedFrame = false; + } + else + { + while (true) + { + int ret = ffmpeg.avcodec_receive_frame(_pCodecContext, _pDecodedFrame); + if (ret == 0) + { + frame = _pDecodedFrame; + break; + } + + if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN)) + { + if (_isDraining) return false; + + if (!ReadAndSendPacket()) + { + _isDraining = true; + ffmpeg.avcodec_send_packet(_pCodecContext, null); + } + } + else if (ret == ffmpeg.AVERROR_EOF) + { + return false; + } + else + { + throw new Exception($"디코딩 중 오류 발생: {GetFFmpegErrorString(ret)}"); + } + } + } + + if (frame == null) return false; + + timestamp = GetTimestampAndUpdateIndex(frame); + + if (frame->format != (int)AVPixelFormat.AV_PIX_FMT_YUV420P && frame->format != (int)AVPixelFormat.AV_PIX_FMT_YUVJ420P) + { + InitOrUpdateSwsContext((AVPixelFormat)frame->format, frame->width, frame->height, AVPixelFormat.AV_PIX_FMT_YUV420P); + + _pConvertedFrame->data[0] = yBuffer; + _pConvertedFrame->data[1] = uBuffer; + _pConvertedFrame->data[2] = vBuffer; + _pConvertedFrame->linesize[0] = yStride; + _pConvertedFrame->linesize[1] = uStride; + _pConvertedFrame->linesize[2] = vStride; + + ffmpeg.sws_scale( + _pSwsContext, + frame->data, frame->linesize, + 0, frame->height, + _pConvertedFrame->data, _pConvertedFrame->linesize + ); + } + else + { + int w = frame->width; + int h = frame->height; + int uvW = w / 2; + int uvH = h / 2; + + // Y plane 복사 + for (int y = 0; y < h; y++) + { + Buffer.MemoryCopy( + frame->data[0] + y * frame->linesize[0], + yBuffer + y * yStride, + w, w + ); + } + + // U plane 복사 + for (int y = 0; y < uvH; y++) + { + Buffer.MemoryCopy( + frame->data[1] + y * frame->linesize[1], + uBuffer + y * uStride, + uvW, uvW + ); + } + + // V plane 복사 + for (int y = 0; y < uvH; y++) + { + Buffer.MemoryCopy( + frame->data[2] + y * frame->linesize[2], + vBuffer + y * vStride, + uvW, uvW + ); + } + } + + ffmpeg.av_frame_unref(frame); + return true; + } + } + + public bool TryReadYUV420PFrame( + NativeArray yBuffer, + NativeArray uBuffer, + NativeArray vBuffer, + out double timestamp) + { + int w = Width; + int h = Height; + int uvW = w / 2; + int uvH = h / 2; + + if (yBuffer.Length < w * h || uBuffer.Length < uvW * uvH || vBuffer.Length < uvW * uvH) + { + throw new ArgumentException("버퍼 크기가 비디오 YUV 평면 해상도보다 작습니다."); + } + + return TryReadYUV420PFrame( + (byte*)yBuffer.GetUnsafePtr(), w, + (byte*)uBuffer.GetUnsafePtr(), uvW, + (byte*)vBuffer.GetUnsafePtr(), uvW, + out timestamp + ); + } + + /// + /// 다음 비디오 프레임을 읽어 NV12 형식의 Y, UV 채널 데이터를 각각 복사합니다. + /// 비디오 원본이 NV12 계열이면 CPU 컬러 변환(sws_scale)을 우회하고 메모리를 다이렉트로 복사하여 속도가 매우 빠릅니다. + /// 그 외 포맷일 경우 자동으로 NV12로 변환 후 복사합니다. + /// + public bool TryReadNV12Frame( + byte* yBuffer, int yStride, + byte* uvBuffer, int uvStride, + out double timestamp) + { + timestamp = 0; + lock (_lock) + { + if (!_isOpened || _disposed) return false; + + AVFrame* frame = null; + + if (_hasCachedFrame) + { + frame = _pCachedFrame; + _hasCachedFrame = false; + } + else + { + while (true) + { + int ret = ffmpeg.avcodec_receive_frame(_pCodecContext, _pDecodedFrame); + if (ret == 0) + { + frame = _pDecodedFrame; + break; + } + + if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN)) + { + if (_isDraining) return false; + + if (!ReadAndSendPacket()) + { + _isDraining = true; + ffmpeg.avcodec_send_packet(_pCodecContext, null); + } + } + else if (ret == ffmpeg.AVERROR_EOF) + { + return false; + } + else + { + throw new Exception($"디코딩 중 오류 발생: {GetFFmpegErrorString(ret)}"); + } + } + } + + if (frame == null) return false; + + timestamp = GetTimestampAndUpdateIndex(frame); + + if (frame->format != (int)AVPixelFormat.AV_PIX_FMT_NV12) + { + InitOrUpdateSwsContext((AVPixelFormat)frame->format, frame->width, frame->height, AVPixelFormat.AV_PIX_FMT_NV12); + + _pConvertedFrame->data[0] = yBuffer; + _pConvertedFrame->data[1] = uvBuffer; + _pConvertedFrame->linesize[0] = yStride; + _pConvertedFrame->linesize[1] = uvStride; + + ffmpeg.sws_scale( + _pSwsContext, + frame->data, frame->linesize, + 0, frame->height, + _pConvertedFrame->data, _pConvertedFrame->linesize + ); + } + else + { + int w = frame->width; + int h = frame->height; + int uvH = h / 2; + + // Y plane 복사 + for (int y = 0; y < h; y++) + { + Buffer.MemoryCopy( + frame->data[0] + y * frame->linesize[0], + yBuffer + y * yStride, + w, w + ); + } + + // UV plane 복사 + for (int y = 0; y < uvH; y++) + { + Buffer.MemoryCopy( + frame->data[1] + y * frame->linesize[1], + uvBuffer + y * uvStride, + w, w + ); + } + } + + ffmpeg.av_frame_unref(frame); + return true; + } + } + + public bool TryReadNV12Frame( + NativeArray yBuffer, + NativeArray uvBuffer, + out double timestamp) + { + int w = Width; + int h = Height; + int uvH = h / 2; + + if (yBuffer.Length < w * h || uvBuffer.Length < w * uvH) + { + throw new ArgumentException("버퍼 크기가 비디오 NV12 평면 해상도보다 작습니다."); + } + + return TryReadNV12Frame( + (byte*)yBuffer.GetUnsafePtr(), w, + (byte*)uvBuffer.GetUnsafePtr(), w, + out timestamp + ); + } + /// /// 지정한 개수만큼 프레임을 디코딩 후 바로 버립니다. (빠른 전방 탐색용) /// @@ -574,7 +848,7 @@ namespace ADOFAIRenderer.Util.FFmpeg } } - ~FFmpegVideoDecoder() + ~FFmpegNativeDecoder() { Dispose(); } diff --git a/Util/FFmpeg/FFmpegNativeEncoder.cs b/Util/FFmpeg/FFmpegNativeEncoder.cs index d846ae6..c7c3477 100644 --- a/Util/FFmpeg/FFmpegNativeEncoder.cs +++ b/Util/FFmpeg/FFmpegNativeEncoder.cs @@ -438,19 +438,22 @@ namespace ADOFAIRenderer.Util.FFmpeg var qsvReady = false; int err; - // ── 전략 1: child_device_type=d3d11va 옵션 ── + bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); + string childDeviceName = isWindows ? "d3d11va" : "vaapi"; + AVHWDeviceType childDeviceType = isWindows ? AVHWDeviceType.AV_HWDEVICE_TYPE_D3D11VA : AVHWDeviceType.AV_HWDEVICE_TYPE_VAAPI; + + // ── 전략 1: child_device_type 옵션 ── // av_hwdevice_ctx_create에 "child_device_type" 딕셔너리를 전달하면 - // FFmpeg이 내부적으로 D3D11 디바이스를 생성하고 QSV 세션에 연결합니다. - // 대부분의 최신 Intel 드라이버에서 가장 안정적으로 동작합니다. + // FFmpeg이 내부적으로 적절한 하위 디바이스(Windows: D3D11, Linux: VAAPI)를 생성하고 QSV 세션에 연결합니다. { AVDictionary* opts = null; - ffmpeg.av_dict_set(&opts, "child_device_type", "d3d11va", 0); + ffmpeg.av_dict_set(&opts, "child_device_type", childDeviceName, 0); err = ffmpeg.av_hwdevice_ctx_create(&hwDeviceCtx, AVHWDeviceType.AV_HWDEVICE_TYPE_QSV, null, opts, 0); ffmpeg.av_dict_free(&opts); if (err >= 0) { qsvReady = true; - Melon.Logger.Msg($"{encoderName}: child_device_type=d3d11va 방식으로 QSV 디바이스 생성 성공"); + Melon.Logger.Msg($"{encoderName}: child_device_type={childDeviceName} 방식으로 QSV 디바이스 생성 성공"); } else { @@ -458,24 +461,22 @@ namespace ADOFAIRenderer.Util.FFmpeg } } - // ── 전략 2: D3D11VA → QSV 파생(derive) ── - // D3D11VA 디바이스를 먼저 생성한 뒤, + // ── 전략 2: 하위 디바이스 → QSV 파생(derive) ── + // 하위 디바이스(D3D11VA / VAAPI)를 먼저 생성한 뒤, // av_hwdevice_ctx_create_derived로 QSV 디바이스를 파생시킵니다. - // 두 디바이스가 동일한 GPU를 공유하게 됩니다. if (!qsvReady) { - // ReSharper disable once InconsistentNaming - AVBufferRef* d3d11DeviceCtx = null; - err = ffmpeg.av_hwdevice_ctx_create(&d3d11DeviceCtx, AVHWDeviceType.AV_HWDEVICE_TYPE_D3D11VA, null, null, 0); + AVBufferRef* childDeviceCtx = null; + err = ffmpeg.av_hwdevice_ctx_create(&childDeviceCtx, childDeviceType, null, null, 0); if (err >= 0) { - err = ffmpeg.av_hwdevice_ctx_create_derived(&hwDeviceCtx, AVHWDeviceType.AV_HWDEVICE_TYPE_QSV, d3d11DeviceCtx, 0); + err = ffmpeg.av_hwdevice_ctx_create_derived(&hwDeviceCtx, AVHWDeviceType.AV_HWDEVICE_TYPE_QSV, childDeviceCtx, 0); if (err >= 0) { qsvReady = true; - Melon.Logger.Msg($"{encoderName}: D3D11VA → QSV 파생 방식으로 디바이스 생성 성공"); + Melon.Logger.Msg($"{encoderName}: {childDeviceType} → QSV 파생 방식으로 디바이스 생성 성공"); } - ffmpeg.av_buffer_unref(&d3d11DeviceCtx); + ffmpeg.av_buffer_unref(&childDeviceCtx); } }