SHA256
Improve VVC Support
This commit is contained in:
2 files changed
+70
-19
No files matched your search
@@ -444,6 +444,10 @@ namespace ADOFAIRenderer.Core
|
|||||||
{
|
{
|
||||||
ext = ".webm";
|
ext = ".webm";
|
||||||
}
|
}
|
||||||
|
else if (selectedCodec == Codec.VVC)
|
||||||
|
{
|
||||||
|
ext = ".mkv";
|
||||||
|
}
|
||||||
|
|
||||||
var startTime = DateTime.Now;
|
var startTime = DateTime.Now;
|
||||||
var fileName = startTime.ToString("yyyy-MM-dd HH-mm-ss") + ext;
|
var fileName = startTime.ToString("yyyy-MM-dd HH-mm-ss") + ext;
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
// GetEncoderCandidates()가 반환하는 우선순위 목록을 순회하며
|
// GetEncoderCandidates()가 반환하는 우선순위 목록을 순회하며
|
||||||
// 첫 번째로 사용 가능한 인코더를 찾습니다.
|
// 첫 번째로 사용 가능한 인코더를 찾습니다.
|
||||||
var candidates = GetEncoderCandidates();
|
var candidates = GetEncoderCandidates();
|
||||||
bool openedSuccessfully = false;
|
var openedSuccessfully = false;
|
||||||
|
|
||||||
foreach (var encoderName in candidates)
|
foreach (var encoderName in candidates)
|
||||||
{
|
{
|
||||||
@@ -295,10 +295,14 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
_pCodecContext = ffmpeg.avcodec_alloc_context3(pCodec);
|
_pCodecContext = ffmpeg.avcodec_alloc_context3(pCodec);
|
||||||
if (_pCodecContext == null) continue;
|
if (_pCodecContext == null) continue;
|
||||||
|
|
||||||
|
_pCodecContext->strict_std_compliance = ffmpeg.FF_COMPLIANCE_EXPERIMENTAL;
|
||||||
|
|
||||||
// ── 기본 비디오 파라미터 설정 ──
|
// ── 기본 비디오 파라미터 설정 ──
|
||||||
_pCodecContext->width = _settings.Width;
|
_pCodecContext->width = _settings.Width;
|
||||||
_pCodecContext->height = _settings.Height;
|
_pCodecContext->height = _settings.Height;
|
||||||
_pCodecContext->thread_count = 0; // 멀티스레딩 활성화 (0 = 자동 설정)
|
// libvvenc는 자체적으로 멀티스레딩을 관리하므로 FFmpeg 측 스레딩을 비활성화합니다.
|
||||||
|
// 다른 인코더는 thread_count = 0 (자동 설정)을 사용합니다.
|
||||||
|
_pCodecContext->thread_count = encoderName == "libvvenc" ? 1 : 0;
|
||||||
|
|
||||||
// ── 픽셀 포맷 설정 ──
|
// ── 픽셀 포맷 설정 ──
|
||||||
// 비디오 인코더는 일반적으로 YUV420P 또는 NV12 형식의 입력을 기대합니다.
|
// 비디오 인코더는 일반적으로 YUV420P 또는 NV12 형식의 입력을 기대합니다.
|
||||||
@@ -321,6 +325,12 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_NV12;
|
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_NV12;
|
||||||
_pCodecContext->pix_fmt = selectedPixFmt;
|
_pCodecContext->pix_fmt = selectedPixFmt;
|
||||||
}
|
}
|
||||||
|
else if (encoderName == "libvvenc")
|
||||||
|
{
|
||||||
|
// libvvenc: 10비트 YUV420P10LE만 지원 (이 빌드에서 yuv420p 미지원 확인됨)
|
||||||
|
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_YUV420P10LE;
|
||||||
|
_pCodecContext->pix_fmt = selectedPixFmt;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_pCodecContext->pix_fmt = selectedPixFmt;
|
_pCodecContext->pix_fmt = selectedPixFmt;
|
||||||
@@ -348,7 +358,16 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
// GOP: 키프레임(I-frame) 간격. 예) gop_size=120, 60fps → 2초마다 키프레임.
|
// GOP: 키프레임(I-frame) 간격. 예) gop_size=120, 60fps → 2초마다 키프레임.
|
||||||
// 키프레임은 완전한 이미지를 담고 있어 탐색(seek) 지점이 됩니다.
|
// 키프레임은 완전한 이미지를 담고 있어 탐색(seek) 지점이 됩니다.
|
||||||
// 너무 크면 탐색 정밀도가 떨어지고, 너무 작으면 파일 크기가 증가합니다.
|
// 너무 크면 탐색 정밀도가 떨어지고, 너무 작으면 파일 크기가 증가합니다.
|
||||||
_pCodecContext->gop_size = (int)Math.Round(_settings.FPS * 2);
|
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-프레임 비활성화 ──
|
||||||
// B-프레임(양방향 예측 프레임): 앞뒤 프레임을 참조하여 압축률을 높이지만,
|
// B-프레임(양방향 예측 프레임): 앞뒤 프레임을 참조하여 압축률을 높이지만,
|
||||||
@@ -365,6 +384,12 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
{
|
{
|
||||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "svtav1-params", "pred-struct=1", 0);
|
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
|
else
|
||||||
{
|
{
|
||||||
_pCodecContext->max_b_frames = 0;
|
_pCodecContext->max_b_frames = 0;
|
||||||
@@ -561,6 +586,11 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
// "realtime" usage: 단일 패스, 저지연 인코딩 모드
|
// "realtime" usage: 단일 패스, 저지연 인코딩 모드
|
||||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "usage", "realtime", 0);
|
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 등)은 기본 프리셋을 그대로 사용합니다.
|
// 다른 인코더들(AMF, QSV 등)은 기본 프리셋을 그대로 사용합니다.
|
||||||
// 지원하지 않는 옵션을 설정하면 에러가 발생할 수 있으므로 건너뜁니다.
|
// 지원하지 않는 옵션을 설정하면 에러가 발생할 수 있으므로 건너뜁니다.
|
||||||
|
|
||||||
@@ -569,6 +599,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
// 성공하면 0 이상을 반환하고, 이후 avcodec_send_frame/avcodec_receive_packet으로
|
// 성공하면 0 이상을 반환하고, 이후 avcodec_send_frame/avcodec_receive_packet으로
|
||||||
// 프레임 인코딩이 가능해집니다.
|
// 프레임 인코딩이 가능해집니다.
|
||||||
// 참고: https://ffmpeg.org/doxygen/trunk/group__lavc__core.html
|
// 참고: 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);
|
var openResult = ffmpeg.avcodec_open2(_pCodecContext, pCodec, null);
|
||||||
if (openResult >= 0)
|
if (openResult >= 0)
|
||||||
{
|
{
|
||||||
@@ -699,16 +730,23 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
{
|
{
|
||||||
case RateControlMode.CBR:
|
case RateControlMode.CBR:
|
||||||
// kbps → bps 변환 (FFmpeg의 bit_rate 필드는 bps 단위)
|
// kbps → bps 변환 (FFmpeg의 bit_rate 필드는 bps 단위)
|
||||||
// CBR에서는 목표/최대/최소 비트레이트를 모두 동일하게 설정합니다.
|
_pCodecContext->bit_rate = _settings.TargetBitrate * 1000L;
|
||||||
// rc_buffer_size는 비트레이트 버퍼링의 크기를 결정합니다.
|
|
||||||
_pCodecContext->bit_rate = _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) 규격에
|
// libvvenc는 rc_min_rate/rc_max_rate/rc_buffer_size를 지원하지 않으므로
|
||||||
// 맞는 CBR 비트스트림을 생성합니다. 스트리밍 호환성을 위해 필요할 수 있습니다.
|
// bit_rate만 설정하고 나머지는 내부 rate control에 위임합니다.
|
||||||
if (encoderName.Contains("libx264") || encoderName.Contains("libx265"))
|
if (encoderName != "libvvenc")
|
||||||
{
|
{
|
||||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "nal-hrd", "cbr", 0);
|
// CBR에서는 목표/최대/최소 비트레이트를 모두 동일하게 설정합니다.
|
||||||
|
// rc_buffer_size는 비트레이트 버퍼링의 크기를 결정합니다.
|
||||||
|
_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) 규격에
|
||||||
|
// 맞는 CBR 비트스트림을 생성합니다. 스트리밍 호환성을 위해 필요할 수 있습니다.
|
||||||
|
if (encoderName.Contains("libx264") || encoderName.Contains("libx265"))
|
||||||
|
{
|
||||||
|
ffmpeg.av_opt_set(_pCodecContext->priv_data, "nal-hrd", "cbr", 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -716,15 +754,19 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
// kbps → bps 변환
|
// kbps → bps 변환
|
||||||
_pCodecContext->bit_rate = _settings.TargetBitrate * 1000L;
|
_pCodecContext->bit_rate = _settings.TargetBitrate * 1000L;
|
||||||
|
|
||||||
// MaxBitrate가 TargetBitrate보다 큰 경우 그대로 사용,
|
// libvvenc는 rc_max_rate/rc_buffer_size를 지원하지 않으므로 건너릴니다.
|
||||||
// 그렇지 않으면 TargetBitrate의 1.5배를 최대값으로 설정합니다.
|
if (encoderName != "libvvenc")
|
||||||
var maxRate = _settings.MaxBitrate > _settings.TargetBitrate
|
{
|
||||||
? _settings.MaxBitrate * 1000L : _settings.TargetBitrate * 1500L;
|
// MaxBitrate가 TargetBitrate보다 큰 경우 그대로 사용,
|
||||||
|
// 그렇지 않으면 TargetBitrate의 1.5배를 최대값으로 설정합니다.
|
||||||
|
var maxRate = _settings.MaxBitrate > _settings.TargetBitrate
|
||||||
|
? _settings.MaxBitrate * 1000L : _settings.TargetBitrate * 1500L;
|
||||||
|
|
||||||
_pCodecContext->rc_max_rate = maxRate;
|
_pCodecContext->rc_max_rate = maxRate;
|
||||||
// 버퍼 크기를 최대 비트레이트의 2배로 설정하여
|
// 버퍼 크기를 최대 비트레이트의 2배로 설정하여
|
||||||
// 순간적인 비트레이트 스파이크를 수용합니다.
|
// 순간적인 비트레이트 스파이크를 수용합니다.
|
||||||
_pCodecContext->rc_buffer_size = (int)(maxRate * 2);
|
_pCodecContext->rc_buffer_size = (int)(maxRate * 2);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RateControlMode.CQP:
|
case RateControlMode.CQP:
|
||||||
@@ -749,6 +791,11 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
// QSV: "global_quality" 옵션으로 ICQ(Intelligent Constant Quality) 모드 사용
|
// QSV: "global_quality" 옵션으로 ICQ(Intelligent Constant Quality) 모드 사용
|
||||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "global_quality", qpValue.ToString(), 0);
|
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 등)
|
else // 소프트웨어 인코더 (libx264, libx265, libvpx-vp9 등)
|
||||||
{
|
{
|
||||||
// CRF(Constant Rate Factor): QP보다 더 스마트한 품질 제어.
|
// CRF(Constant Rate Factor): QP보다 더 스마트한 품질 제어.
|
||||||
|
|||||||
Reference in new issue
Block a user