Files
video-v1/vav2/docs/working/NVDECAV1Decoder_Sync_Stabilization_Design.md
2025-10-07 00:52:35 +09:00

5.5 KiB

NVDECAV1Decoder 동기화 로직 안정화 설계

  • 문서 작성일: 2025년 10월 6일
  • 작성자: Gemini

1. 문제 정의

현재 NVDECAV1Decoder::DecodeToSurface 함수는 디코딩 완료 및 프레임 반환 순서를 보장하기 위해 sleep_for를 사용하는 폴링(polling) 및 busy-wait 방식에 크게 의존하고 있다.

  1. FIFO 순서 보장 로직: while (m_returnCounter.load() != my_submission_id) 루프 내 sleep_for(1ms)는 불필요한 지연을 유발하며, 특정 조건에서 데드락(무한 루프)을 발생시킬 수 있는 위험이 있다.
  2. 디코딩 완료 대기 로직: frame_ready.wait_for는 해상도에 따라 수 초에 달하는 매우 긴 타임아웃을 사용한다. 이는 시스템의 잠재적 불안정성을 회피하기 위한 방어 코드(고육지책)로 보이며, 실제 문제 발생 시 애플리케이션이 장시간 멈추는 결과를 초래한다.

이 두 가지 sleep 기반 메커니즘은 시스템의 안정성을 저해하고, 예측 불가능한 성능 저하를 유발하는 핵심 원인이다.

2. 목표

대규모 아키텍처 변경 없이, DecodeToSurface 함수 내부의 동기화 로직을 안정적이고 효율적인 이벤트 기반 방식으로 수정한다.

  • sleep 기반 대기를 제거하여 데드락 위험을 최소화하고 안정성을 향상시킨다.
  • 불필요한 지연 시간을 제거하여 디코딩 파이프라인의 응답성을 높인다.
  • 함수의 외부 인터페이스는 변경하지 않고 내부 구현만 개선하여 하위 호환성을 유지한다.

3. 제안 설계

핵심 아이디어는 "전역적인 신호(global notification)를 개별적인 신호(per-slot notification)로" 변경하는 것이다.

3.1. DecodeSlot 구조체 수정

NVDECAV1Decoder.h 파일의 DecodeSlot 구조체에 각 슬롯별 동기화를 위한 std::condition_variablestd::mutex를 추가한다.

기존:

struct DecodeSlot {
    std::atomic<bool> in_use{false};
    std::atomic<bool> is_ready{false};
    // ...
};

변경 후:

struct DecodeSlot {
    std::atomic<bool> in_use{false};
    std::atomic<bool> is_ready{false};
    
    std::mutex slot_mutex;
    std::condition_variable slot_cv;

    // ...
};

3.2. PollingThreadFunc 수정 (신호 전송부)

폴링 스레드는 특정 슬롯(slot[i])의 디코딩 완료(cuvidGetDecodeStatus)를 감지하면, 해당 슬롯의 condition_variable에만 직접 신호를 보낸다.

기존 (개념):

// PollingThreadFunc
if (decode_complete) {
    slot.is_ready.store(true);
    // 전역 CV에 신호를 보냄
    global_cv.notify_all(); 
}

변경 후:

// PollingThreadFunc
if (decode_complete) {
    {
        std::lock_guard<std::mutex> lock(slot.slot_mutex);
        slot.is_ready.store(true);
    } // lock 해제
    slot.slot_cv.notify_one(); // 해당 슬롯을 기다리는 스레드만 깨움
}

3.3. DecodeToSurface 수정 (신호 수신부)

DecodeToSurface 함수는 더 이상 해상도 기반의 긴 타임아웃을 사용하지 않는다. 대신, 자신이 기다려야 할 슬롯(my_slot)을 특정한 후, 해당 슬롯의 condition_variable만 직접 기다린다.

기존:

// 7. Wait for decode to complete with adaptive timeout based on resolution
// ...
if (!my_slot.frame_ready.wait_for(lock, std::chrono::milliseconds(timeout_ms), ...)) {
    // 매우 긴 타임아웃 처리
    // ...
}

변경 후:

// 7. Wait for decode to complete by waiting on the specific slot's condition variable
DecodeSlot& my_slot = m_ringBuffer[my_slot_idx];
{
    std::unique_lock<std::mutex> lock(my_slot.slot_mutex);
    // is_ready 플래그를 확인하며 신호가 올 때까지 대기
    if (!my_slot.slot_cv.wait_for(lock, std::chrono::seconds(2), [&my_slot]{ return my_slot.is_ready.load(); })) {
        // 안전장치: 2초의 고정 타임아웃. 드라이버 멈춤 등 예외 상황 방지
        LOGF_ERROR("[DecodeToSurface] Decode timeout for slot %d after 2 seconds", my_slot_idx);
        my_slot.in_use.store(false);
        m_returnCounter.fetch_add(1); // 데드락 방지를 위해 카운터 증가
        return false;
    }
}
// is_ready가 true임이 보장됨

참고: FIFO 순서 보장을 위한 while 루프는 이 설계 변경의 범위에 포함되지 않으나, 디코딩 완료 대기 로직이 안정화되면 해당 루프의 데드락 위험도 간접적으로 감소한다.

4. 기대 효과

  1. 안정성 대폭 향상: sleep과 긴 타임아웃으로 인한 불확실성을 제거하고, 특정 프레임의 처리가 멈추더라도 다른 프레임의 대기 로직에 영향을 주지 않아 데드락 위험이 크게 감소한다.
  2. 지연 시간(Latency) 감소: 디코딩 완료 즉시 해당 프레임을 기다리던 스레드가 깨어나므로, 불필요한 sleep 지연이 사라지고 시스템 응답성이 향상된다.
  3. 코드 단순화 및 유지보수성 향상: 복잡한 동적 타임아웃 계산 로직이 사라지고, "신호-대기" 관계가 명확해져 코드 이해가 쉬워진다.

5. 작업 범위

  • 포함:
    • NVDECAV1Decoder.hDecodeSlot 구조체 수정
    • NVDECAV1Decoder.cppPollingThreadFunc, DecodeToSurface 함수 내부 로직 수정
  • 미포함:
    • NVDECAV1Decoder 클래스의 공개(public) 인터페이스 변경
    • Player 또는 Renderer 등 외부 클래스의 아키텍처 변경