16 KiB
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일)
- PrimeDecoder() 메서드 구현
- 프라이밍 상태 관리 추가
- DecodeFrame() 수정하여 프라이밍 사용
Phase 2: 폴백 메커니즘 (1일)
- AV1Decoder 폴백 통합
- 자동 전환 로직 구현
- 실패 카운터 및 트리거 조건
Phase 3: 상태 관리 (1일)
- DecoderState enum 추가
- 상태 전환 검증 로직
- Thread-safe 상태 관리
Phase 4: 테스트 및 최적화 (1일)
- 통합 테스트
- 성능 측정 및 튜닝
- 로그 정리 및 문서화
💡 7. 추가 최적화 아이디어
7.1 적응형 프라이밍
- 디바이스 성능에 따라 프라이밍 프레임 수 조정
- 네트워크 스트리밍 시 대역폭 고려
7.2 지능형 폴백
- 특정 해상도/코덱에서만 하드웨어 사용
- 사용자 설정 기반 폴백 정책
7.3 성능 모니터링
- 실시간 디코딩 성능 추적
- 자동 품질 조정 시스템 연동
문서 완료일: 2025년 9월 30일 작성자: Claude Code 상태: ✅ 설계 완료 - 구현 준비
이 설계를 바탕으로 단계별 구현을 진행하면 MediaCodec의 안정성과 성능을 크게 개선할 수 있습니다. 🚀