SHA256
Refactor Encoder Detection
This commit is contained in:
3 files changed
+159
-57
No files matched your search
@@ -282,7 +282,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 코덱에 대해 시도할 인코더 이름 목록을 우선순위 순으로 반환합니다.
|
||||
/// 선택된 코덱에 대해 시도할 인코더 목록을 우선순위 순으로 반환합니다.
|
||||
///
|
||||
/// 우선순위: 하드웨어 인코더(NVENC → AMF → QSV) → 소프트웨어 인코더(libx264 등)
|
||||
///
|
||||
@@ -293,9 +293,9 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
///
|
||||
/// Start()에서 이 목록을 순회하며 첫 번째로 구동 가능한 인코더를 사용합니다.
|
||||
/// </summary>
|
||||
private string[] GetEncoderCandidates()
|
||||
private VideoEncoder[] GetEncoderCandidates()
|
||||
{
|
||||
return GPUUtil.GetPrioritizedEncoderCandidates(_settings.Codec, _settings.ForceSoftware).ToArray();
|
||||
return GPUUtil.GetPrioritizedEncoders(_settings.Codec, _settings.ForceSoftware).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -366,12 +366,12 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
var candidates = GetEncoderCandidates();
|
||||
var openedSuccessfully = false;
|
||||
|
||||
foreach (var encoderName in candidates)
|
||||
foreach (var encoder in candidates)
|
||||
{
|
||||
// avcodec_find_encoder_by_name: 이름(예: "h264_nvenc")으로 인코더를 검색합니다.
|
||||
// 해당 인코더가 빌드에 포함되어 있지 않으면 null을 반환합니다.
|
||||
// 참고: https://ffmpeg.org/doxygen/trunk/group__lavc__encoding.html
|
||||
var pCodec = ffmpeg.avcodec_find_encoder_by_name(encoderName);
|
||||
var pCodec = ffmpeg.avcodec_find_encoder_by_name(encoder.Name);
|
||||
if (pCodec == null) continue;
|
||||
|
||||
// avcodec_alloc_context3: 선택된 인코더용 코덱 컨텍스트를 할당합니다.
|
||||
@@ -402,17 +402,17 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
//
|
||||
// 참고: https://ffmpeg.org/doxygen/trunk/pixfmt_8h.html
|
||||
var selectedPixFmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
|
||||
if (encoderName.Contains("qsv"))
|
||||
if (encoder.Kind == EncoderKind.QSV)
|
||||
{
|
||||
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_NV12;
|
||||
_pCodecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_QSV;
|
||||
}
|
||||
else if (encoderName.Contains("nvenc") || encoderName.Contains("amf"))
|
||||
else if (encoder.Kind is EncoderKind.NVENC or EncoderKind.AMF)
|
||||
{
|
||||
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_NV12;
|
||||
_pCodecContext->pix_fmt = selectedPixFmt;
|
||||
}
|
||||
else if (encoderName == "libvvenc")
|
||||
else if (encoder == VideoEncoder.LibVvenc)
|
||||
{
|
||||
// libvvenc: 10비트 YUV420P10LE만 지원 (이 빌드에서 yuv420p 미지원 확인됨)
|
||||
selectedPixFmt = AVPixelFormat.AV_PIX_FMT_YUV420P10LE;
|
||||
@@ -452,7 +452,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
// 키프레임은 완전한 이미지를 담고 있어 탐색(seek) 지점이 됩니다.
|
||||
// 너무 크면 탐색 정밀도가 떨어지고, 너무 작으면 파일 크기가 증가합니다.
|
||||
var calculatedGop = Math.Max(1, (int)Math.Round(_settings.FPS * _settings.GopSize));
|
||||
if (encoderName == "libvvenc")
|
||||
if (encoder == VideoEncoder.LibVvenc)
|
||||
{
|
||||
// libvvenc (VVenC) requires IntraPeriod (gop_size) to be a multiple of its GOPSize (typically 32).
|
||||
_pCodecContext->gop_size = Math.Max(32, ((calculatedGop + 15) / 32) * 32);
|
||||
@@ -473,11 +473,11 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
// SVT-AV1 특수 사항: max_b_frames를 AVCodecContext에서 직접 설정하면
|
||||
// "bad parameter" 에러가 발생하므로, svtav1-params를 통해 제어합니다.
|
||||
// pred-struct=1: Low Delay (IPPP) 구조로 B-프레임 비활성화
|
||||
if (encoderName == "libsvtav1")
|
||||
if (encoder == VideoEncoder.LibSvtAv1)
|
||||
{
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "svtav1-params", "pred-struct=1", 0);
|
||||
}
|
||||
else if (encoderName == "libvvenc")
|
||||
else if (encoder == VideoEncoder.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.
|
||||
@@ -500,7 +500,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
}
|
||||
|
||||
// ── 품질/비트레이트 설정 적용 ──
|
||||
ApplyQualitySettings(encoderName);
|
||||
ApplyQualitySettings(encoder);
|
||||
|
||||
// ── QSV 인코더 하드웨어 디바이스 초기화 ──
|
||||
// QSV(Quick Sync Video)는 Intel GPU의 고정 기능 인코딩 하드웨어를 사용합니다.
|
||||
@@ -519,7 +519,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
// hw_device_ctx: 시스템 메모리에서 인코더가 직접 프레임을 읽음 (호환성 우수)
|
||||
//
|
||||
// 참고: https://ffmpeg.org/doxygen/trunk/group__lavu__hwcontext.html
|
||||
if (encoderName.Contains("qsv"))
|
||||
if (encoder.Kind == EncoderKind.QSV)
|
||||
{
|
||||
AVBufferRef* hwDeviceCtx = null;
|
||||
var qsvReady = false;
|
||||
@@ -540,11 +540,11 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
if (err >= 0)
|
||||
{
|
||||
qsvReady = true;
|
||||
Melon<RenderMod>.Logger.Msg($"{encoderName}: child_device_type={childDeviceName} 방식으로 QSV 디바이스 생성 성공");
|
||||
Melon<RenderMod>.Logger.Msg($"{encoder.Name}: child_device_type={childDeviceName} 방식으로 QSV 디바이스 생성 성공");
|
||||
}
|
||||
else
|
||||
{
|
||||
Melon<RenderMod>.Logger.Warning($"{encoderName}: child_device_type 방식 실패 — {GetFFmpegErrorString(err)} (Code: {err})");
|
||||
Melon<RenderMod>.Logger.Warning($"{encoder.Name}: child_device_type 방식 실패 — {GetFFmpegErrorString(err)} (Code: {err})");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,7 +561,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
if (err >= 0)
|
||||
{
|
||||
qsvReady = true;
|
||||
Melon<RenderMod>.Logger.Msg($"{encoderName}: {childDeviceType} → QSV 파생 방식으로 디바이스 생성 성공");
|
||||
Melon<RenderMod>.Logger.Msg($"{encoder.Name}: {childDeviceType} → QSV 파생 방식으로 디바이스 생성 성공");
|
||||
}
|
||||
ffmpeg.av_buffer_unref(&childDeviceCtx);
|
||||
}
|
||||
@@ -576,7 +576,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
|
||||
if (!qsvReady)
|
||||
{
|
||||
Melon<RenderMod>.Logger.Warning($"{encoderName}: 모든 QSV 디바이스 생성 전략 실패. 다음 후보 코덱을 테스트합니다.");
|
||||
Melon<RenderMod>.Logger.Warning($"{encoder.Name}: 모든 QSV 디바이스 생성 전략 실패. 다음 후보 코덱을 테스트합니다.");
|
||||
{
|
||||
var pCtx = _pCodecContext;
|
||||
ffmpeg.avcodec_free_context(&pCtx);
|
||||
@@ -618,7 +618,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
_pCodecContext->hw_frames_ctx = ffmpeg.av_buffer_ref(hwFramesRef);
|
||||
_isQsvEncoder = true;
|
||||
framesCtxReady = true;
|
||||
Melon<RenderMod>.Logger.Msg($"{encoderName}: hw_frames_ctx 초기화 성공 (GPU 프레임 풀 모드)");
|
||||
Melon<RenderMod>.Logger.Msg($"{encoder.Name}: hw_frames_ctx 초기화 성공 (GPU 프레임 풀 모드)");
|
||||
|
||||
_pHwFrame = ffmpeg.av_frame_alloc();
|
||||
if (_pHwFrame == null)
|
||||
@@ -626,7 +626,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
}
|
||||
else
|
||||
{
|
||||
Melon<RenderMod>.Logger.Warning($"{encoderName}: hw_frames_ctx 실패 — {GetFFmpegErrorString(err)} (Code: {err}). hw_device_ctx 모드로 폴백합니다.");
|
||||
Melon<RenderMod>.Logger.Warning($"{encoder.Name}: hw_frames_ctx 실패 — {GetFFmpegErrorString(err)} (Code: {err}). hw_device_ctx 모드로 폴백합니다.");
|
||||
}
|
||||
ffmpeg.av_buffer_unref(&hwFramesRef);
|
||||
}
|
||||
@@ -657,30 +657,30 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
// 느린 프리셋: 인코딩 느리지만 같은 품질에서 파일 크기 감소
|
||||
//
|
||||
// 참고: https://ffmpeg.org/doxygen/trunk/group__opt__set__funcs.html
|
||||
if (encoderName.Contains("nvenc"))
|
||||
if (encoder.Kind == EncoderKind.NVENC)
|
||||
{
|
||||
// NVENC 프리셋 범위: p1(최고속) ~ p7(최고품질). p4 = 중간(default)
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "p4", 0);
|
||||
}
|
||||
else if (encoderName.Contains("libx264") || encoderName.Contains("libx265"))
|
||||
else if (encoder == VideoEncoder.Libx264 || encoder == VideoEncoder.Libx265)
|
||||
{
|
||||
// x264/x265 프리셋: ultrafast, superfast, veryfast, faster, fast, medium, slow, ...
|
||||
// CPU 인코딩은 병목이 되기 쉬우므로 빠른 프리셋 사용
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "veryfast", 0);
|
||||
}
|
||||
else if (encoderName == "libsvtav1")
|
||||
else if (encoder == VideoEncoder.LibSvtAv1)
|
||||
{
|
||||
// SVT-AV1 프리셋: 0(최고 품질/최저 속도) ~ 13(최고 속도/최저 품질)
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "10", 0);
|
||||
}
|
||||
else if (encoderName == "libaom-av1")
|
||||
else if (encoder == VideoEncoder.LibAomAv1)
|
||||
{
|
||||
// libaom cpu-used: 0(최고 품질) ~ 8(최고 속도)
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "cpu-used", "6", 0);
|
||||
// "realtime" usage: 단일 패스, 저지연 인코딩 모드
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "usage", "realtime", 0);
|
||||
}
|
||||
else if (encoderName == "libvvenc")
|
||||
else if (encoder == VideoEncoder.LibVvenc)
|
||||
{
|
||||
// libvvenc 프리셋: slower, slow, medium, fast, faster. 디폴트가 medium이므로 faster로 변경하여 성능 확보
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "faster", 0);
|
||||
@@ -693,20 +693,20 @@ 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}");
|
||||
Melon<RenderMod>.Logger.Msg($"[인코더 진단] {encoder.Name}: {_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)
|
||||
{
|
||||
bool isHardware = encoderName.Contains("nvenc") || encoderName.Contains("amf") || encoderName.Contains("qsv");
|
||||
string accelStatus = isHardware ? "하드웨어 가속 활성화" : "소프트웨어 CPU 사용";
|
||||
Melon<RenderMod>.Logger.Msg($"비디오 인코더 구동 성공: {encoderName} ({accelStatus})");
|
||||
var isHardware = encoder.IsHardware;
|
||||
var accelStatus = isHardware ? "하드웨어 가속 활성화" : "소프트웨어 CPU 사용";
|
||||
Melon<RenderMod>.Logger.Msg($"비디오 인코더 구동 성공: {encoder.Name} ({accelStatus})");
|
||||
_dstPixFmt = selectedPixFmt;
|
||||
openedSuccessfully = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 구동 실패 시 코덱 컨텍스트를 해제하고 다음 인코더를 시도합니다.
|
||||
Melon<RenderMod>.Logger.Warning($"{encoderName} 하드웨어 인코더 구동 실패: {GetFFmpegErrorString(openResult)} (Code: {openResult}). 다음 후보 코덱을 테스트합니다.");
|
||||
Melon<RenderMod>.Logger.Warning($"{encoder.Name} 하드웨어 인코더 구동 실패: {GetFFmpegErrorString(openResult)} (Code: {openResult}). 다음 후보 코덱을 테스트합니다.");
|
||||
var pCodecCtx = _pCodecContext;
|
||||
ffmpeg.avcodec_free_context(&pCodecCtx);
|
||||
_pCodecContext = null;
|
||||
@@ -832,7 +832,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
///
|
||||
/// 참고: https://ffmpeg.org/doxygen/trunk/structAVCodecContext.html
|
||||
/// </summary>
|
||||
private void ApplyQualitySettings(string encoderName)
|
||||
private void ApplyQualitySettings(VideoEncoder encoder)
|
||||
{
|
||||
switch (_settings.Mode)
|
||||
{
|
||||
@@ -842,7 +842,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
|
||||
// libvvenc는 rc_min_rate/rc_max_rate/rc_buffer_size를 지원하지 않으므로
|
||||
// bit_rate만 설정하고 나머지는 내부 rate control에 위임합니다.
|
||||
if (encoderName != "libvvenc")
|
||||
if (encoder != VideoEncoder.LibVvenc)
|
||||
{
|
||||
// CBR에서는 목표/최대/최소 비트레이트를 모두 동일하게 설정합니다.
|
||||
// rc_buffer_size는 비트레이트 버퍼링의 크기를 결정합니다.
|
||||
@@ -851,7 +851,7 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
|
||||
// nal-hrd=cbr: x264/x265 전용 옵션. NAL HRD(Hypothetical Reference Decoder) 규격에
|
||||
// 맞는 CBR 비트스트림을 생성합니다. 스트리밍 호환성을 위해 필요할 수 있습니다.
|
||||
if (encoderName.Contains("libx264") || encoderName.Contains("libx265"))
|
||||
if (encoder == VideoEncoder.Libx264 || encoder == VideoEncoder.Libx265)
|
||||
{
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "nal-hrd", "cbr", 0);
|
||||
}
|
||||
@@ -862,8 +862,8 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
// kbps → bps 변환
|
||||
_pCodecContext->bit_rate = _settings.TargetBitrate * 1000L;
|
||||
|
||||
// libvvenc는 rc_max_rate/rc_buffer_size를 지원하지 않으므로 건너릴니다.
|
||||
if (encoderName != "libvvenc")
|
||||
// libvvenc는 rc_max_rate/rc_buffer_size를 지원하지 않으므로 건너뜁니다.
|
||||
if (encoder != VideoEncoder.LibVvenc)
|
||||
{
|
||||
// MaxBitrate가 TargetBitrate보다 큰 경우 그대로 사용,
|
||||
// 그렇지 않으면 TargetBitrate의 1.5배를 최대값으로 설정합니다.
|
||||
@@ -882,24 +882,24 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
||||
// 다른 코덱에서도 비슷한 수준의 품질을 제공합니다.
|
||||
var qpValue = _settings.QualityValue is >= 0 and <= 51 ? _settings.QualityValue : 18;
|
||||
|
||||
if (encoderName.Contains("nvenc"))
|
||||
if (encoder.Kind == EncoderKind.NVENC)
|
||||
{
|
||||
// NVENC: "qp" 옵션 하나로 I/P/B 프레임 모두에 동일한 QP 적용
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp", qpValue.ToString(), 0);
|
||||
}
|
||||
else if (encoderName.Contains("amf"))
|
||||
else if (encoder.Kind == EncoderKind.AMF)
|
||||
{
|
||||
// AMF: I프레임(qp_i)과 P프레임(qp_p)의 QP를 별도로 설정해야 합니다.
|
||||
// B-프레임은 비활성화했으므로 qp_b는 설정하지 않습니다.
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp_i", qpValue.ToString(), 0);
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp_p", qpValue.ToString(), 0);
|
||||
}
|
||||
else if (encoderName.Contains("qsv"))
|
||||
else if (encoder.Kind == EncoderKind.QSV)
|
||||
{
|
||||
// QSV: "global_quality" 옵션으로 ICQ(Intelligent Constant Quality) 모드 사용
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "global_quality", qpValue.ToString(), 0);
|
||||
}
|
||||
else if (encoderName == "libvvenc")
|
||||
else if (encoder == VideoEncoder.LibVvenc)
|
||||
{
|
||||
// libvvenc: "qp" 옵션으로 Constant QP 모드 지정
|
||||
ffmpeg.av_opt_set(_pCodecContext->priv_data, "qp", qpValue.ToString(), 0);
|
||||
|
||||
Reference in new issue
Block a user