# 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_variable`과 `std::mutex`를 추가한다. **기존:** ```cpp struct DecodeSlot { std::atomic in_use{false}; std::atomic is_ready{false}; // ... }; ``` **변경 후:** ```cpp struct DecodeSlot { std::atomic in_use{false}; std::atomic is_ready{false}; std::mutex slot_mutex; std::condition_variable slot_cv; // ... }; ``` ### 3.2. `PollingThreadFunc` 수정 (신호 전송부) 폴링 스레드는 특정 슬롯(`slot[i]`)의 디코딩 완료(`cuvidGetDecodeStatus`)를 감지하면, 해당 슬롯의 `condition_variable`에만 직접 신호를 보낸다. **기존 (개념):** ```cpp // PollingThreadFunc if (decode_complete) { slot.is_ready.store(true); // 전역 CV에 신호를 보냄 global_cv.notify_all(); } ``` **변경 후:** ```cpp // PollingThreadFunc if (decode_complete) { { std::lock_guard lock(slot.slot_mutex); slot.is_ready.store(true); } // lock 해제 slot.slot_cv.notify_one(); // 해당 슬롯을 기다리는 스레드만 깨움 } ``` ### 3.3. `DecodeToSurface` 수정 (신호 수신부) `DecodeToSurface` 함수는 더 이상 해상도 기반의 긴 타임아웃을 사용하지 않는다. 대신, 자신이 기다려야 할 슬롯(`my_slot`)을 특정한 후, 해당 슬롯의 `condition_variable`만 직접 기다린다. **기존:** ```cpp // 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), ...)) { // 매우 긴 타임아웃 처리 // ... } ``` **변경 후:** ```cpp // 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 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.h`의 `DecodeSlot` 구조체 수정 - `NVDECAV1Decoder.cpp`의 `PollingThreadFunc`, `DecodeToSurface` 함수 내부 로직 수정 - **미포함:** - `NVDECAV1Decoder` 클래스의 공개(public) 인터페이스 변경 - `Player` 또는 `Renderer` 등 외부 클래스의 아키텍처 변경