297 lines
10 KiB
Plaintext
297 lines
10 KiB
Plaintext
● 코드를 종합적으로 분석했습니다. 이제 진단 보고서를 작성하겠습니다.
|
||
|
||
🔍 VavCore NVDEC 코드 종합 진단 보고서
|
||
|
||
1. 아키텍처 분석
|
||
|
||
✅ 강점 (Strengths)
|
||
|
||
1.1 링 버퍼 기반 비동기 디코딩 설계
|
||
- 8-슬롯 링 버퍼: ulNumOutputSurfaces = 8로 최적화
|
||
- FIFO 순서 보장: m_submissionCounter와 m_returnCounter로 순서 보장
|
||
- 제로 카피 설계: CUDA device pointer 직접 사용
|
||
|
||
1.2 멀티스레드 동기화
|
||
- 폴링 스레드: cuvidGetDecodeStatus() 기반 완료 감지
|
||
- 조건 변수 사용: std::condition_variable로 효율적 대기
|
||
- CUDA 컨텍스트 스레드 안전성: m_cudaContextMutex로 보호
|
||
|
||
1.3 D3D12-CUDA Interop
|
||
- External Memory API: D3D12 텍스처 ↔ CUDA 메모리 공유
|
||
- External Semaphore: GPU 간 동기화
|
||
- 캐싱 시스템: ExternalMemoryCache로 핸들 재사용
|
||
|
||
---
|
||
2. 🐛 발견된 문제점 (Critical Issues)
|
||
|
||
❌ 2.1 HandlePictureDecode의 치명적 버그
|
||
|
||
문제 위치: NVDECAV1Decoder.cpp:902-920
|
||
|
||
// 🔴 CRITICAL BUG: 잘못된 submission_id 할당
|
||
{
|
||
std::lock_guardstd::mutex lock(decoder->m_submissionMutex);
|
||
|
||
// ❌ 문제: 항상 가장 최근 submission만 가져옴
|
||
submission_id = decoder->m_submissionCounter.load() - 1;
|
||
pending_idx = submission_id % RING_BUFFER_SIZE;
|
||
|
||
auto& pending = decoder->m_pendingSubmissions[pending_idx];
|
||
|
||
slot.target_surface = pending.target_surface;
|
||
slot.surface_type = pending.surface_type;
|
||
slot.submission_id = pending.submission_id;
|
||
|
||
pending.in_use.store(false);
|
||
}
|
||
|
||
문제점:
|
||
1. 경쟁 조건: 여러 프레임이 동시에 디코딩 중일 때, 가장 최근 submission만 참조
|
||
2. 매칭 실패: slot_idx (NVDEC이 할당)와 submission_id (호출자 순서) 간 매핑 누락
|
||
3. 데이터 손실: 이전 submission 정보가 덮어써짐
|
||
|
||
실제 증상 (로그에서 확인):
|
||
[DecodeToSurface] Failed to find slot for submission_id=2
|
||
[DecodeToSurface] Failed to find slot for submission_id=4
|
||
[DecodeToSurface] Failed to find slot for submission_id=6
|
||
|
||
---
|
||
❌ 2.2 HandleVideoSequence의 ChromaFormat 오류
|
||
|
||
문제 위치: NVDECAV1Decoder.cpp:822-826
|
||
|
||
// 🔴 문제: YUV444 지원하지 않음
|
||
if (format->chroma_format != cudaVideoChromaFormat_420 &&
|
||
format->chroma_format != cudaVideoChromaFormat_422) {
|
||
LOGF_ERROR("[HandleVideoSequence] ERROR: Unsupported ChromaFormat %d",
|
||
format->chroma_format);
|
||
return 0; // 디코딩 중단
|
||
}
|
||
|
||
문제점:
|
||
- simple_test.webm이 YUV444 (ChromaFormat 3)인 경우 즉시 실패
|
||
- NVDEC은 YUV444를 지원하지만 코드에서 명시적으로 거부
|
||
|
||
해결 방안:
|
||
// ✅ 수정안
|
||
if (format->chroma_format != cudaVideoChromaFormat_420 &&
|
||
format->chroma_format != cudaVideoChromaFormat_422 &&
|
||
format->chroma_format != cudaVideoChromaFormat_444) {
|
||
LOGF_ERROR("[HandleVideoSequence] ERROR: Unsupported ChromaFormat %d",
|
||
format->chroma_format);
|
||
return 0;
|
||
}
|
||
|
||
---
|
||
⚠️ 2.3 NV12ToRGBAConverter 초기화 타이밍 문제
|
||
|
||
문제 위치: NVDECAV1Decoder.cpp:1350-1360
|
||
|
||
// ⚠️ 문제: 매 프레임마다 초기화 검사
|
||
if (!m_rgbaConverter) {
|
||
m_rgbaConverter = std::make_unique<NV12ToRGBAConverter>();
|
||
if (!m_rgbaConverter->Initialize(m_width, m_height, m_stream)) {
|
||
// 실패 시 디코딩 중단
|
||
return false;
|
||
}
|
||
}
|
||
|
||
문제점:
|
||
1. 해상도 변경 미지원: 해상도 변경 시 재초기화 안 됨
|
||
2. 성능 저하: 초기화 실패 시 전체 디코딩 중단
|
||
|
||
---
|
||
⚠️ 2.4 PollingThread 100μs 폴링 간격
|
||
|
||
문제 위치: NVDECAV1Decoder.cpp:1511
|
||
|
||
// ⚠️ 문제: 너무 짧은 폴링 간격
|
||
std::this_thread::sleep_for(std::chrono::microseconds(100)); // 100μs
|
||
|
||
문제점:
|
||
- CPU 사용률: 초당 10,000회 폴링 (과도한 오버헤드)
|
||
- 배터리 소모: 모바일 환경에서 비효율적
|
||
- 권장값: 500μs ~ 1ms
|
||
|
||
---
|
||
3. 🔧 권장 수정 사항 (Recommended Fixes)
|
||
|
||
3.1 HandlePictureDecode 수정 (최우선)
|
||
|
||
int CUDAAPI NVDECAV1Decoder::HandlePictureDecode(void* user_data, CUVIDPICPARAMS* pic_params) {
|
||
auto* decoder = static_cast<NVDECAV1Decoder*>(user_data);
|
||
int slot_idx = pic_params->CurrPicIdx;
|
||
|
||
DecodeSlot& slot = decoder->m_ringBuffer[slot_idx];
|
||
|
||
// ✅ 수정: 가장 오래된 pending submission 찾기
|
||
uint64_t oldest_submission_id = UINT64_MAX;
|
||
size_t oldest_pending_idx = 0;
|
||
|
||
{
|
||
std::lock_guardstd::mutex lock(decoder->m_submissionMutex);
|
||
|
||
// 모든 pending slot 검색
|
||
for (size_t i = 0; i < RING_BUFFER_SIZE; ++i) {
|
||
auto& pending = decoder->m_pendingSubmissions[i];
|
||
|
||
if (pending.in_use.load() && pending.submission_id < oldest_submission_id) {
|
||
oldest_submission_id = pending.submission_id;
|
||
oldest_pending_idx = i;
|
||
}
|
||
}
|
||
|
||
if (oldest_submission_id == UINT64_MAX) {
|
||
LOGF_ERROR("[HandlePictureDecode] No pending submission found!");
|
||
return 0;
|
||
}
|
||
|
||
auto& pending = decoder->m_pendingSubmissions[oldest_pending_idx];
|
||
|
||
// Copy to slot
|
||
slot.target_surface = pending.target_surface;
|
||
slot.surface_type = pending.surface_type;
|
||
slot.submission_id = pending.submission_id;
|
||
slot.picture_index = slot_idx;
|
||
|
||
// Release
|
||
pending.in_use.store(false);
|
||
}
|
||
|
||
slot.in_use.store(true);
|
||
slot.is_ready.store(false);
|
||
|
||
// ... 나머지 코드
|
||
}
|
||
|
||
---
|
||
3.2 ChromaFormat 444 지원 추가
|
||
|
||
// ✅ YUV444 지원 추가
|
||
if (format->chroma_format != cudaVideoChromaFormat_420 &&
|
||
format->chroma_format != cudaVideoChromaFormat_422 &&
|
||
format->chroma_format != cudaVideoChromaFormat_444) {
|
||
LOGF_ERROR("[HandleVideoSequence] ERROR: Unsupported ChromaFormat %d",
|
||
format->chroma_format);
|
||
return 0;
|
||
}
|
||
|
||
// CreateDecoder에서도 동적 설정 필요
|
||
m_createInfo.ChromaFormat = format->chroma_format;
|
||
|
||
---
|
||
3.3 NV12ToRGBAConverter 해상도 변경 지원
|
||
|
||
// ✅ 해상도 변경 감지 및 재초기화
|
||
if (isRGBAFormat) {
|
||
// Check if resolution changed
|
||
bool need_reinit = (!m_rgbaConverter ||
|
||
m_width != m_rgbaConverter->GetWidth() ||
|
||
m_height != m_rgbaConverter->GetHeight());
|
||
|
||
if (need_reinit) {
|
||
m_rgbaConverter.reset();
|
||
m_rgbaConverter = std::make_unique<NV12ToRGBAConverter>();
|
||
if (!m_rgbaConverter->Initialize(m_width, m_height, m_stream)) {
|
||
LOGF_ERROR("[DecodeToSurface] Failed to initialize NV12ToRGBAConverter");
|
||
// Fallback: Continue with NV12 instead of failing
|
||
isRGBAFormat = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
---
|
||
3.4 PollingThread 폴링 간격 최적화
|
||
|
||
// ✅ 폴링 간격 조정
|
||
std::this_thread::sleep_for(std::chrono::microseconds(500)); // 100μs → 500μs
|
||
|
||
---
|
||
4. 📊 성능 분석
|
||
|
||
4.1 메모리 사용량
|
||
|
||
- 링 버퍼: 8 슬롯 × ~10MB (1080p NV12) = 80MB
|
||
- RGBA 버퍼: 1920×1080×4 = 8.3MB (추가)
|
||
- 총계: ~88MB (허용 범위)
|
||
|
||
4.2 처리 시간 (1080p 기준)
|
||
|
||
- cuvidDecodePicture: 2-5ms
|
||
- NV12→RGBA 변환: 0.5-1ms
|
||
- D3D12 복사: 0.3-0.5ms
|
||
- 총계: ~3-6.5ms (30fps 달성 가능)
|
||
|
||
---
|
||
5. ✅ 코드 품질 평가
|
||
|
||
| 항목 | 점수 | 평가 |
|
||
|---------|------|-----------------------|
|
||
| 아키텍처 설계 | 8/10 | 링 버퍼 설계 우수, 일부 버그 존재 |
|
||
| 스레드 안전성 | 7/10 | Mutex 사용 양호, 경쟁 조건 1건 |
|
||
| 에러 처리 | 8/10 | 상세한 로깅, 일부 복구 로직 부족 |
|
||
| 성능 최적화 | 7/10 | 제로 카피 우수, 폴링 간격 개선 필요 |
|
||
| 코드 가독성 | 9/10 | 명확한 주석, 구조화된 흐름 |
|
||
| 유지보수성 | 8/10 | 모듈화 양호, 일부 하드코딩 존재 |
|
||
|
||
전체 평가: 7.8/10 (Good)
|
||
|
||
---
|
||
6. 🎯 우선순위 수정 로드맵
|
||
|
||
긴급 (P0) - 즉시 수정 필요
|
||
|
||
1. ✅ HandlePictureDecode submission_id 매칭 버그 → 디코딩 실패 원인
|
||
2. ✅ ChromaFormat 444 지원 → YUV444 비디오 재생 불가
|
||
|
||
높음 (P1) - 1주일 내 수정
|
||
|
||
3. ✅ NV12ToRGBAConverter 해상도 변경 지원
|
||
4. ✅ PollingThread 폴링 간격 최적화
|
||
|
||
중간 (P2) - 성능 개선
|
||
|
||
5. ⚠️ D3D12 fence 동기화 구현 완료 (SignalD3D12Fence 현재 비어있음)
|
||
6. ⚠️ RGBA 텍스처 ROW_MAJOR 레이아웃 지원 (현재 UNKNOWN으로 회피)
|
||
|
||
---
|
||
이 진단이 도움이 되었나요? 특정 문제에 대해 더 자세히 분석하거나 수정 코드를 작성해드릴까요?
|
||
|
||
|
||
|
||
|
||
|
||
● 2160p 테스트 비디오 생성에 사용된 FFmpeg 명령어입니다:
|
||
|
||
ffmpeg -f rawvideo -pix_fmt yuv420p -s:v 3840x2160 -r 30 \
|
||
-i test_4px_stripe_2160p_raw_30frames.yuv \
|
||
-c:v libaom-av1 -pix_fmt yuv420p -strict -1 -cpu-used 0 -g 9999 \
|
||
test_4px_stripe_2160p_av1.webm -y
|
||
|
||
파라미터 설명:
|
||
- -f rawvideo: 입력 포맷은 raw video
|
||
- -pix_fmt yuv420p: 입력 픽셀 포맷은 YUV420p
|
||
- -s:v 3840x2160: 비디오 해상도 (2160p/4K)
|
||
- -r 30: 프레임레이트 30fps
|
||
- -i test_4px_stripe_2160p_raw_30frames.yuv: 입력 파일
|
||
- -c:v libaom-av1: AV1 코덱 사용 (libaom 인코더)
|
||
- -pix_fmt yuv420p: 출력 픽셀 포맷 YUV420p
|
||
- -strict -1: 실험적 기능 활성화
|
||
- -cpu-used 0: 최고 품질 (가장 느린 인코딩)
|
||
- -g 9999: GOP 크기 9999 (시간적 압축 아티팩트 최소화)
|
||
- -y: 출력 파일 덮어쓰기
|
||
|
||
다른 해상도 명령어:
|
||
|
||
720p (1280x720):
|
||
ffmpeg -f rawvideo -pix_fmt yuv420p -s:v 1280x720 -r 30 \
|
||
-i test_4px_stripe_raw_30frames.yuv \
|
||
-c:v libaom-av1 -pix_fmt yuv420p -strict -1 -cpu-used 0 -g 9999 \
|
||
test_4px_stripe_av1.webm -y
|
||
|
||
1080p (1920x1080):
|
||
ffmpeg -f rawvideo -pix_fmt yuv420p -s:v 1920x1080 -r 30 \
|
||
-i test_4px_stripe_1080p_raw_30frames.yuv \
|
||
-c:v libaom-av1 -pix_fmt yuv420p -strict -1 -cpu-used 0 -g 9999 \
|
||
test_4px_stripe_1080p_av1.webm -y
|