Improve VVC Support

This commit is contained in:
copecone committed 2026-06-10 16:57:25 +09:00
1 parent 0a879cdafa
commit 382e8eef80
2 files changed
+54 -3

No files matched your search

+4
View File
@@ -444,6 +444,10 @@ namespace ADOFAIRenderer.Core
{
ext = ".webm";
}
else if (selectedCodec == Codec.VVC)
{
ext = ".mkv";
}
var startTime = DateTime.Now;
var fileName = startTime.ToString("yyyy-MM-dd HH-mm-ss") + ext;
+50 -3
View File
@@ -279,7 +279,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
// GetEncoderCandidates()가 반환하는 우선순위 목록을 순회하며
// 첫 번째로 사용 가능한 인코더를 찾습니다.
var candidates = GetEncoderCandidates();
bool openedSuccessfully = false;
var openedSuccessfully = false;
foreach (var encoderName in candidates)
{
@@ -295,10 +295,14 @@ namespace ADOFAIRenderer.Util.FFmpeg
_pCodecContext = ffmpeg.avcodec_alloc_context3(pCodec);
if (_pCodecContext == null) continue;
_pCodecContext->strict_std_compliance = ffmpeg.FF_COMPLIANCE_EXPERIMENTAL;
// ── 기본 비디오 파라미터 설정 ──
_pCodecContext->width = _settings.Width;
_pCodecContext->height = _settings.Height;
_pCodecContext->thread_count = 0; // 멀티스레딩 활성화 (0 = 자동 설정)
// libvvenc는 자체적으로 멀티스레딩을 관리하므로 FFmpeg 측 스레딩을 비활성화합니다.
// 다른 인코더는 thread_count = 0 (자동 설정)을 사용합니다.
_pCodecContext->thread_count = encoderName == "libvvenc" ? 1 : 0;
// ── 픽셀 포맷 설정 ──
// 비디오 인코더는 일반적으로 YUV420P 또는 NV12 형식의 입력을 기대합니다.
@@ -321,6 +325,12 @@ namespace ADOFAIRenderer.Util.FFmpeg
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_NV12;
_pCodecContext->pix_fmt = selectedPixFmt;
}
else if (encoderName == "libvvenc")
{
// libvvenc: 10비트 YUV420P10LE만 지원 (이 빌드에서 yuv420p 미지원 확인됨)
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_YUV420P10LE;
_pCodecContext->pix_fmt = selectedPixFmt;
}
else
{
_pCodecContext->pix_fmt = selectedPixFmt;
@@ -348,7 +358,16 @@ namespace ADOFAIRenderer.Util.FFmpeg
// GOP: 키프레임(I-frame) 간격. 예) gop_size=120, 60fps → 2초마다 키프레임.
// 키프레임은 완전한 이미지를 담고 있어 탐색(seek) 지점이 됩니다.
// 너무 크면 탐색 정밀도가 떨어지고, 너무 작으면 파일 크기가 증가합니다.
if (encoderName == "libvvenc")
{
// libvvenc (VVenC) requires IntraPeriod (gop_size) to be a multiple of its GOPSize (typically 32).
var calculatedGop = (int)Math.Round(_settings.FPS * 2);
_pCodecContext->gop_size = Math.Max(32, ((calculatedGop + 15) / 32) * 32);
}
else
{
_pCodecContext->gop_size = (int)Math.Round(_settings.FPS * 2);
}
// ── B-프레임 비활성화 ──
// B-프레임(양방향 예측 프레임): 앞뒤 프레임을 참조하여 압축률을 높이지만,
@@ -365,6 +384,12 @@ namespace ADOFAIRenderer.Util.FFmpeg
{
ffmpeg.av_opt_set(_pCodecContext->priv_data, "svtav1-params", "pred-struct=1", 0);
}
else if (encoderName == "libvvenc")
{
// libvvenc relies on hierarchical GOP structures; forcing max_b_frames = 0 causes initialization failure.
// We explicitly set max_b_frames to -1 to allow libvvenc to use its default GOP reference distance.
_pCodecContext->max_b_frames = -1;
}
else
{
_pCodecContext->max_b_frames = 0;
@@ -561,6 +586,11 @@ namespace ADOFAIRenderer.Util.FFmpeg
// "realtime" usage: 단일 패스, 저지연 인코딩 모드
ffmpeg.av_opt_set(_pCodecContext->priv_data, "usage", "realtime", 0);
}
else if (encoderName == "libvvenc")
{
// libvvenc 프리셋: slower, slow, medium, fast, faster. 디폴트가 medium이므로 faster로 변경하여 성능 확보
ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "faster", 0);
}
// 다른 인코더들(AMF, QSV 등)은 기본 프리셋을 그대로 사용합니다.
// 지원하지 않는 옵션을 설정하면 에러가 발생할 수 있으므로 건너뜁니다.
@@ -569,6 +599,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
// 성공하면 0 이상을 반환하고, 이후 avcodec_send_frame/avcodec_receive_packet으로
// 프레임 인코딩이 가능해집니다.
// 참고: https://ffmpeg.org/doxygen/trunk/group__lavc__core.html
Melon<RenderMod>.Logger.Msg($"[인코더 진단] {encoderName}: {_pCodecContext->width}x{_pCodecContext->height}, pix_fmt={_pCodecContext->pix_fmt}, gop={_pCodecContext->gop_size}, max_b_frames={_pCodecContext->max_b_frames}, framerate={_pCodecContext->framerate.num}/{_pCodecContext->framerate.den}, thread_count={_pCodecContext->thread_count}, bit_rate={_pCodecContext->bit_rate}, rc_max_rate={_pCodecContext->rc_max_rate}, rc_buffer_size={_pCodecContext->rc_buffer_size}, mode={_settings.Mode}");
var openResult = ffmpeg.avcodec_open2(_pCodecContext, pCodec, null);
if (openResult >= 0)
{
@@ -699,9 +730,15 @@ namespace ADOFAIRenderer.Util.FFmpeg
{
case RateControlMode.CBR:
// kbps → bps 변환 (FFmpeg의 bit_rate 필드는 bps 단위)
_pCodecContext->bit_rate = _settings.TargetBitrate * 1000L;
// libvvenc는 rc_min_rate/rc_max_rate/rc_buffer_size를 지원하지 않으므로
// bit_rate만 설정하고 나머지는 내부 rate control에 위임합니다.
if (encoderName != "libvvenc")
{
// CBR에서는 목표/최대/최소 비트레이트를 모두 동일하게 설정합니다.
// rc_buffer_size는 비트레이트 버퍼링의 크기를 결정합니다.
_pCodecContext->bit_rate = _pCodecContext->rc_max_rate = _pCodecContext->rc_min_rate = _settings.TargetBitrate * 1000L;
_pCodecContext->rc_max_rate = _pCodecContext->rc_min_rate = _settings.TargetBitrate * 1000L;
_pCodecContext->rc_buffer_size = _settings.TargetBitrate * 1000;
// nal-hrd=cbr: x264/x265 전용 옵션. NAL HRD(Hypothetical Reference Decoder) 규격에
@@ -710,12 +747,16 @@ namespace ADOFAIRenderer.Util.FFmpeg
{
ffmpeg.av_opt_set(_pCodecContext->priv_data, "nal-hrd", "cbr", 0);
}
}
break;
case RateControlMode.VBR:
// kbps → bps 변환
_pCodecContext->bit_rate = _settings.TargetBitrate * 1000L;
// libvvenc는 rc_max_rate/rc_buffer_size를 지원하지 않으므로 건너릴니다.
if (encoderName != "libvvenc")
{
// MaxBitrate가 TargetBitrate보다 큰 경우 그대로 사용,
// 그렇지 않으면 TargetBitrate의 1.5배를 최대값으로 설정합니다.
var maxRate = _settings.MaxBitrate > _settings.TargetBitrate
@@ -725,6 +766,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
// 버퍼 크기를 최대 비트레이트의 2배로 설정하여
// 순간적인 비트레이트 스파이크를 수용합니다.
_pCodecContext->rc_buffer_size = (int)(maxRate * 2);
}
break;
case RateControlMode.CQP:
@@ -749,6 +791,11 @@ namespace ADOFAIRenderer.Util.FFmpeg
// QSV: "global_quality" 옵션으로 ICQ(Intelligent Constant Quality) 모드 사용
ffmpeg.av_opt_set(_pCodecContext->priv_data, "global_quality", qpValue.ToString(), 0);
}
else if (encoderName == "libvvenc")
{
// libvvenc: "qp" 옵션으로 Constant QP 모드 지정
ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp", qpValue.ToString(), 0);
}
else // 소프트웨어 인코더 (libx264, libx265, libvpx-vp9 등)
{
// CRF(Constant Rate Factor): QP보다 더 스마트한 품질 제어.