Files
video-v1/vav2/docs/completed/android/MediaCodec_Priming_System_Design.md
2025-09-30 02:32:41 +09:00

16 KiB

MediaCodec 프라이밍 시스템 및 안정성 개선 설계

작성일: 2025년 9월 30일 상태: 설계 완료 - 구현 준비 카테고리: Android MediaCodec 최적화, 하드웨어 가속 안정성


🎯 프로젝트 개요

Android MediaCodec AV1 디코더의 출력 버퍼 타이밍 문제를 해결하기 위한 종합적인 안정성 개선 시스템입니다. 하드웨어 디코더의 비동기 특성과 초기화 지연을 고려한 3단계 해결책을 제시합니다.

핵심 문제

  • MediaCodec 하드웨어 디코더의 첫 프레임 출력 버퍼 지연 (No output buffer ready)
  • 비동기 입출력 버퍼 처리로 인한 타이밍 불일치
  • 하드웨어 초기화 시간으로 인한 재생 시작 지연
  • MediaCodec 실패 시 자동 복구 메커니즘 부재

해결 목표

  • 즉시 재생 시작: 프라이밍을 통한 버퍼 준비 상태 확보
  • 안정성 보장: 하드웨어 실패 시 소프트웨어 폴백
  • 성능 최적화: 하드웨어 가속 우선, 필요 시 자동 전환

🏗️ 1. 프라이밍 시스템 (Priming System)

1.1 설계 원리

MediaCodec 하드웨어 디코더는 비동기적으로 작동하며, 첫 번째 출력 버퍼가 준비되기까지 여러 입력 프레임이 필요합니다. 프라이밍 시스템은 재생 시작 전에 파이프라인을 미리 채워서 즉시 출력이 가능한 상태로 만듭니다.

// AndroidMediaCodecAV1Decoder.h 추가 멤버
class AndroidMediaCodecAV1Decoder : public IVideoDecoder {
private:
    // Priming system state
    bool m_is_primed = false;
    int m_priming_frame_count = 3;  // Prime with 3 frames
    std::queue<std::unique_ptr<VideoFrame>> m_primed_frames;

    // Priming methods
    bool PrimeDecoder();
    bool IsPrimed() const { return m_is_primed; }
    void ResetPriming();
};

1.2 프라이밍 프로세스

bool AndroidMediaCodecAV1Decoder::PrimeDecoder() {
    if (m_is_primed) {
        return true;  // Already primed
    }

    LogInfo("Starting MediaCodec priming process...");

    // Reset any existing state
    ResetPriming();

    // Prime with initial frames
    for (int i = 0; i < m_priming_frame_count; i++) {
        // Get next packet from file reader (via callback or parameter)
        VideoPacket priming_packet;
        if (!GetNextPrimingPacket(priming_packet)) {
            LogWarning("Not enough packets for full priming");
            break;
        }

        // Submit to MediaCodec input buffer
        if (!ProcessInputBuffer(priming_packet.data.get(), priming_packet.size)) {
            LogError("Failed to submit priming packet " + std::to_string(i));
            continue;
        }

        // Try to get output buffer (non-blocking)
        auto primed_frame = std::make_unique<VideoFrame>();
        if (ProcessOutputBuffer(*primed_frame)) {
            LogInfo("Primed frame " + std::to_string(i) + " ready");
            m_primed_frames.push(std::move(primed_frame));
        }

        // Small delay to allow hardware processing
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    bool success = !m_primed_frames.empty();
    if (success) {
        LogInfo("MediaCodec priming completed with " +
                std::to_string(m_primed_frames.size()) + " frames");
        m_is_primed = true;
    } else {
        LogWarning("MediaCodec priming failed - no frames ready");
    }

    return success;
}

1.3 프라이밍된 프레임 사용

bool AndroidMediaCodecAV1Decoder::DecodeFrame(const uint8_t* packet_data,
                                              size_t packet_size,
                                              VideoFrame& output_frame) {
    if (!m_initialized) {
        LogError("Decoder not initialized");
        return false;
    }

    // Use primed frame if available
    if (!m_primed_frames.empty()) {
        LogInfo("Using primed frame");
        output_frame = *m_primed_frames.front();
        m_primed_frames.pop();

        // Continue normal processing for next frames
        ProcessInputBuffer(packet_data, packet_size);
        return true;
    }

    // Normal decoding process
    if (!ProcessInputBuffer(packet_data, packet_size)) {
        LogError("Failed to process input buffer");
        return false;
    }

    if (!ProcessOutputBuffer(output_frame)) {
        LogError("Failed to process output buffer");
        return false;
    }

    return true;
}

🔄 2. 폴백 메커니즘 (Fallback System)

2.1 설계 원리

MediaCodec 하드웨어 디코더 실패 시 자동으로 dav1d 소프트웨어 디코더로 전환하여 재생 연속성을 보장합니다.

// AndroidMediaCodecAV1Decoder.h 폴백 관련 멤버
class AndroidMediaCodecAV1Decoder : public IVideoDecoder {
private:
    // Fallback system
    std::unique_ptr<AV1Decoder> m_fallback_decoder;  // dav1d decoder
    bool m_use_fallback = false;
    int m_consecutive_failures = 0;
    static const int MAX_FAILURES_BEFORE_FALLBACK = 5;

    // Fallback methods
    bool InitializeFallback();
    bool ShouldUseFallback() const;
    void TriggerFallback();
};

2.2 자동 폴백 트리거

bool AndroidMediaCodecAV1Decoder::DecodeFrame(const uint8_t* packet_data,
                                              size_t packet_size,
                                              VideoFrame& output_frame) {
    // Check if we should use fallback
    if (m_use_fallback) {
        return m_fallback_decoder->DecodeFrame(packet_data, packet_size, output_frame);
    }

    // Try MediaCodec decoding
    bool success = false;

    // Use primed frame if available
    if (!m_primed_frames.empty()) {
        output_frame = *m_primed_frames.front();
        m_primed_frames.pop();
        ProcessInputBuffer(packet_data, packet_size);  // Queue next frame
        success = true;
    } else {
        // Normal MediaCodec processing
        if (ProcessInputBuffer(packet_data, packet_size)) {
            success = ProcessOutputBuffer(output_frame);
        }
    }

    // Handle failure
    if (!success) {
        m_consecutive_failures++;
        LogWarning("MediaCodec decode failure " + std::to_string(m_consecutive_failures));

        if (ShouldUseFallback()) {
            LogInfo("Triggering fallback to dav1d decoder");
            TriggerFallback();
            return m_fallback_decoder->DecodeFrame(packet_data, packet_size, output_frame);
        }

        return false;
    }

    // Reset failure counter on success
    m_consecutive_failures = 0;
    return true;
}

bool AndroidMediaCodecAV1Decoder::ShouldUseFallback() const {
    return m_consecutive_failures >= MAX_FAILURES_BEFORE_FALLBACK;
}

void AndroidMediaCodecAV1Decoder::TriggerFallback() {
    LogInfo("Switching to software decoder (dav1d) fallback");

    if (!m_fallback_decoder) {
        InitializeFallback();
    }

    if (m_fallback_decoder && m_fallback_decoder->Initialize()) {
        m_use_fallback = true;
        LogInfo("Fallback decoder initialized successfully");
    } else {
        LogError("Failed to initialize fallback decoder");
    }
}

2.3 폴백 초기화

bool AndroidMediaCodecAV1Decoder::InitializeFallback() {
    LogInfo("Initializing dav1d fallback decoder");

    m_fallback_decoder = std::make_unique<AV1Decoder>();

    // Configure dav1d with same settings
    AV1Settings fallback_settings;
    fallback_settings.threads = std::thread::hardware_concurrency();
    fallback_settings.max_frame_delay = 1;  // Low latency

    if (!m_fallback_decoder->SetAV1Settings(fallback_settings)) {
        LogError("Failed to configure fallback decoder settings");
        return false;
    }

    LogInfo("Fallback decoder configured successfully");
    return true;
}

🔄 3. 상태 관리 개선 (Lifecycle Management)

3.1 설계 원리

MediaCodec와 VavCore의 상태를 정확히 동기화하여 생명주기 불일치로 인한 문제를 방지합니다.

// AndroidMediaCodecAV1Decoder.h 상태 관리 멤버
class AndroidMediaCodecAV1Decoder : public IVideoDecoder {
private:
    enum class DecoderState {
        UNINITIALIZED,
        INITIALIZING,
        CONFIGURED,
        PRIMING,
        READY,
        DECODING,
        FLUSHING,
        ERROR,
        FALLBACK_ACTIVE
    };

    DecoderState m_current_state = DecoderState::UNINITIALIZED;
    std::mutex m_state_mutex;

    // State management methods
    bool TransitionState(DecoderState from, DecoderState to);
    void SetState(DecoderState new_state);
    DecoderState GetState() const;
    bool IsValidTransition(DecoderState from, DecoderState to) const;
};

3.2 상태 전환 관리

bool AndroidMediaCodecAV1Decoder::Initialize() {
    std::lock_guard<std::mutex> lock(m_state_mutex);

    if (m_current_state != DecoderState::UNINITIALIZED) {
        LogError("Invalid state for initialization: " + StateToString(m_current_state));
        return false;
    }

    SetState(DecoderState::INITIALIZING);

    // Hardware initialization
    if (DetectHardwareCapabilities() && InitializeMediaCodec()) {
        SetState(DecoderState::CONFIGURED);
        LogInfo("Hardware decoder initialized successfully");

        // Start priming process
        SetState(DecoderState::PRIMING);
        if (PrimeDecoder()) {
            SetState(DecoderState::READY);
            m_initialized = true;
            return true;
        } else {
            LogWarning("Priming failed, decoder still usable");
            SetState(DecoderState::READY);
            m_initialized = true;
            return true;
        }
    }

    // Hardware initialization failed
    SetState(DecoderState::ERROR);
    LogWarning("Hardware decoder initialization failed");
    return false;
}

bool AndroidMediaCodecAV1Decoder::DecodeFrame(const uint8_t* packet_data,
                                              size_t packet_size,
                                              VideoFrame& output_frame) {
    std::lock_guard<std::mutex> lock(m_state_mutex);

    // State validation
    if (m_current_state == DecoderState::FALLBACK_ACTIVE) {
        return m_fallback_decoder->DecodeFrame(packet_data, packet_size, output_frame);
    }

    if (m_current_state != DecoderState::READY &&
        m_current_state != DecoderState::DECODING) {
        LogError("Invalid state for decoding: " + StateToString(m_current_state));
        return false;
    }

    SetState(DecoderState::DECODING);

    bool success = DecodeFrameInternal(packet_data, packet_size, output_frame);

    if (success) {
        // Stay in DECODING state for continuous playback
    } else {
        // Handle failure
        if (ShouldUseFallback()) {
            TriggerFallback();
            SetState(DecoderState::FALLBACK_ACTIVE);
            return m_fallback_decoder->DecodeFrame(packet_data, packet_size, output_frame);
        } else {
            SetState(DecoderState::ERROR);
        }
    }

    return success;
}

3.3 정리 및 리셋

void AndroidMediaCodecAV1Decoder::Cleanup() {
    std::lock_guard<std::mutex> lock(m_state_mutex);

    LogInfo("Cleaning up MediaCodec decoder, current state: " +
            StateToString(m_current_state));

    // Flush any remaining frames
    if (m_current_state == DecoderState::DECODING) {
        SetState(DecoderState::FLUSHING);
        FlushDecoder();
    }

    // Clean up primed frames
    ResetPriming();

    // Clean up MediaCodec
    CleanupMediaCodec();

    // Clean up fallback decoder
    if (m_fallback_decoder) {
        m_fallback_decoder->Cleanup();
        m_fallback_decoder.reset();
    }

    SetState(DecoderState::UNINITIALIZED);
    m_initialized = false;
    m_use_fallback = false;
    m_consecutive_failures = 0;

    LogInfo("MediaCodec decoder cleanup completed");
}

void AndroidMediaCodecAV1Decoder::Reset() {
    std::lock_guard<std::mutex> lock(m_state_mutex);

    LogInfo("Resetting MediaCodec decoder");

    if (m_current_state == DecoderState::FALLBACK_ACTIVE) {
        if (m_fallback_decoder) {
            m_fallback_decoder->Reset();
        }
    } else {
        // Reset MediaCodec state
        if (m_codec) {
            AMediaCodec_flush(m_codec);
        }
    }

    // Reset priming state
    ResetPriming();
    m_consecutive_failures = 0;

    // Try to return to READY state
    if (m_initialized) {
        SetState(DecoderState::READY);
    }

    LogInfo("MediaCodec decoder reset completed");
}

📊 4. 통합 구현 가이드

4.1 초기화 순서

// 1. Hardware detection and initialization
bool success = androidDecoder->Initialize();

// 2. Priming is automatically triggered during initialization
// 3. Fallback decoder is prepared but not initialized

// 4. Ready for decoding
if (success) {
    LogInfo("Decoder ready with priming: " +
            std::to_string(androidDecoder->GetPrimedFrameCount()));
}

4.2 재생 시작

// VavCoreVulkanBridge::Play() modification
bool VavCoreVulkanBridge::Play() {
    // ... existing code ...

    // Start continuous playback with primed pipeline
    StartContinuousPlayback();

    return true;
}

// PlaybackThreadMain에서 첫 프레임은 즉시 사용 가능
void VavCoreVulkanBridge::PlaybackThreadMain() {
    while (ShouldContinuePlayback()) {
        // ProcessNextFrame()은 이제 primed frame을 먼저 사용
        bool success = ProcessNextFrame();
        if (!success) {
            // Automatic fallback handling in decoder
            LogWarning("Frame processing failed, decoder handling fallback");
        }

        // Timing control remains the same
        auto sleepTime = m_frameDurationUs - frameProcessTime;
        if (sleepTime.count() > 0) {
            std::this_thread::sleep_for(sleepTime);
        }
    }
}

🎯 5. 예상 효과 및 성능 개선

5.1 즉시 재생 시작

  • Before: 첫 프레임까지 100-200ms 지연
  • After: 프라이밍으로 즉시 재생 시작 (<10ms)

5.2 안정성 보장

  • Hardware failure recovery: 자동 소프트웨어 폴백
  • Continuous playback: 디코더 실패 시에도 재생 중단 없음

5.3 사용자 경험

  • Smooth startup: 버퍼링 없는 즉시 재생
  • Reliable playback: 하드웨어 문제 시 자동 복구
  • Optimal performance: 가능한 한 하드웨어 가속 유지

🛠️ 6. 구현 단계

Phase 1: 프라이밍 시스템 (1-2일)

  1. PrimeDecoder() 메서드 구현
  2. 프라이밍 상태 관리 추가
  3. DecodeFrame() 수정하여 프라이밍 사용

Phase 2: 폴백 메커니즘 (1일)

  1. AV1Decoder 폴백 통합
  2. 자동 전환 로직 구현
  3. 실패 카운터 및 트리거 조건

Phase 3: 상태 관리 (1일)

  1. DecoderState enum 추가
  2. 상태 전환 검증 로직
  3. Thread-safe 상태 관리

Phase 4: 테스트 및 최적화 (1일)

  1. 통합 테스트
  2. 성능 측정 및 튜닝
  3. 로그 정리 및 문서화

💡 7. 추가 최적화 아이디어

7.1 적응형 프라이밍

  • 디바이스 성능에 따라 프라이밍 프레임 수 조정
  • 네트워크 스트리밍 시 대역폭 고려

7.2 지능형 폴백

  • 특정 해상도/코덱에서만 하드웨어 사용
  • 사용자 설정 기반 폴백 정책

7.3 성능 모니터링

  • 실시간 디코딩 성능 추적
  • 자동 품질 조정 시스템 연동

문서 완료일: 2025년 9월 30일 작성자: Claude Code 상태: 설계 완료 - 구현 준비

이 설계를 바탕으로 단계별 구현을 진행하면 MediaCodec의 안정성과 성능을 크게 개선할 수 있습니다. 🚀