From 1e985fd7080976998f8b444a7817b2b9baae401a Mon Sep 17 00:00:00 2001 From: ened Date: Tue, 14 Oct 2025 10:33:03 +0900 Subject: [PATCH] WIP --- todo30.txt | 317 ++++++++++++++++++ .../vav2player/app/build.gradle.kts | 2 +- .../src/main/cpp/vavcore_vulkan_bridge.cpp | 16 +- .../src/Decoder/MediaCodecAV1Decoder.cpp | 95 ++---- .../src/Decoder/MediaCodecAsyncHandler.cpp | 42 +++ .../src/Decoder/MediaCodecAsyncHandler.h | 1 + .../src/Decoder/MediaCodecSelector.cpp | 20 +- .../src/Decoder/VideoDecoderFactory.cpp | 24 ++ .../platforms/windows/vavcore/src/VavCore.cpp | 63 ++-- 9 files changed, 464 insertions(+), 116 deletions(-) create mode 100644 todo30.txt diff --git a/todo30.txt b/todo30.txt new file mode 100644 index 0000000..5fc7acb --- /dev/null +++ b/todo30.txt @@ -0,0 +1,317 @@ +● 문제를 찾았습니다! + + 에러: "Failed to start MediaCodec during async reset: -10000" (-10000 = AMEDIA_ERROR_INVALID_OPERATION) + + 원인: MediaCodec를 stop한 후 바로 start를 호출할 수 없습니다. MediaCodec 상태 머신에서 stop 후에는 configure를 다시 해야 start할 수 있습니다. + + MediaCodec 생명주기: + Uninitialized → Configure → Start → Running + ↑ ↓ + └── Stop ──┘ + + Stop 후에는 다시 Configure → Start 순서로 가야 합니다. + + 더 나은 해결책: Play 버튼을 누를 때 MediaCodec를 Reset하지 않고, WebMFileReader만 처음으로 돌리는 것이 더 안전합니다. MediaCodec는 계속 실행 + 상태를 유지하면서 새로운 프레임을 받으면 됩니다. + + VavCoreVulkanBridge의 Play 메서드를 확인하겠습니다: + + + + +● 완벽합니다! 이제 다각도로 종합 분석하겠습니다: + + 📊 시간순 이벤트 분석 + + 09:33:37.160 - AsyncHandler 초기화 성공 + 09:33:37.168 - MediaCodec RUNNING 상태 진입 + 09:33:37.170 - MediaCodec이 10개 input buffer 제공 (index 0~9) + → Queue에 10개 모두 쌓임 + 09:33:37.373 - Reset() 호출 (+203ms) + → "Cleared 10 stale input buffer indices" + → Queue 완전히 비워짐 + → MediaCodec은 건드리지 않음 (RUNNING 유지) + 09:33:37.875 - DecodeFrameAsync 시도 (+502ms) + → "No input buffer available after 500ms (queue size: 0)" + → Queue가 여전히 비어있음 + + 🔍 다각도 문제 분석 + + 가능성 1: Buffer 반환 문제 ⭐⭐⭐⭐⭐ + + 가장 유력한 원인입니다. + + MediaCodec async mode의 동작 원리: + 1. MediaCodec이 처음에 모든 available buffer를 제공 (0~9번) + 2. 앱이 buffer를 사용하면 queueInputBuffer() 호출 + 3. MediaCodec이 buffer 처리 후 다시 그 buffer를 제공 + + 우리가 한 일: + - Queue에서 index만 제거했음 + - MediaCodec에게는 아무 것도 알리지 않음 + - MediaCodec 입장: "모든 buffer를 이미 줬는데, 아직 반환 안 받았어" + + 증거: + Queue size: 10 → Reset() → Queue size: 0 → 500ms 대기 → Queue still 0 + + 가능성 2: Callback Thread 중단 ⭐⭐⭐ + + MediaCodec의 callback thread가 멈췄거나, callback을 더 이상 보내지 않을 수 있음. + + 근거: + - Reset() 전: Callback이 정상적으로 왔음 (10개) + - Reset() 후: Callback이 전혀 오지 않음 + + 반박: + - MediaCodec state는 여전히 RUNNING + - flush()를 호출하지 않았으므로 callback이 중단될 이유가 없음 + + 가능성 3: 타이밍 이슈 ⭐⭐ + + Reset() 직후 바로 decode를 시작해서 MediaCodec이 준비되지 않았을 수 있음. + + 근거: + - Reset()과 DecodeFrameAsync 사이에 502ms 소요 + - 충분한 시간인데도 buffer가 안 옴 + + 반박: + - 500ms는 충분히 긴 시간 + - MediaCodec RUNNING 상태가 유지됨 + + 가능성 4: AsyncHandler 상태 문제 ⭐⭐ + + AsyncHandler 자체가 "대기 중" 상태로 잘못 설정되었을 수 있음. + + 근거: + - Queue clear 로직만 있고, AsyncHandler 상태 리셋은 없음 + + 반박: + - DecodeFrameAsync는 정상적으로 호출됨 + - 500ms timeout이 작동함 (대기는 하고 있음) + + 가능성 5: MediaCodec 내부 상태 불일치 ⭐⭐⭐⭐ + + MediaCodec이 내부적으로 "모든 buffer 할당됨" 상태라고 생각. + + 근거: + - flush() 없이는 MediaCodec의 내부 buffer pool이 리셋되지 않음 + - Buffer lifecycle: FREE → ALLOCATED → QUEUED → PROCESSING → FREE + - 우리는 ALLOCATED 상태의 buffer를 그냥 버렸음 + + 💡 해결 방안 비교 + + 방안 A: Queue의 각 buffer를 빈 패킷으로 반환 ⭐⭐⭐⭐⭐ + + // Reset() 함수에서 + while (!queue.empty()) { + int index = queue.front(); + queue.pop(); + + // MediaCodec에게 "이 buffer 안 쓸게요" 알림 + AMediaCodec_queueInputBuffer(m_codec, index, 0, 0, 0, 0); + } + + 장점: + - MediaCodec을 건드리지 않음 (RUNNING 유지) + - Buffer lifecycle이 정상적으로 완료됨 + - MediaCodec이 buffer를 다시 제공할 것임 + + 단점: + - 빈 패킷이 처리되는 시간 필요 + - MediaCodec이 이 빈 패킷을 처리해야 함 + + 방안 B: flush() 후 약간 대기 ⭐⭐⭐ + + AMediaCodec_flush(m_codec); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + m_async_handler->ClearInputQueue(); + + 장점: + - MediaCodec 내부 상태가 완전히 리셋됨 + - Buffer pool이 초기화됨 + + 단점: + - 이전에 이미 실패했었음 + - "No input buffer after 500ms" 동일한 문제 + + 방안 C: MediaCodec 재생성 ⭐ + + 완전히 새로 만들기 (이전에 시도했던 stop→configure→start) + + 장점: + - 확실한 초기화 + + 단점: + - 이미 실패함 (error -10000) + - 너무 무거운 작업 + + 방안 D: Dequeue 강제 호출 ⭐⭐⭐⭐ + + // Reset()에서 모든 buffer를 강제로 dequeue + for (int i = 0; i < 10; i++) { + AMediaCodecBufferInfo info; + ssize_t idx = AMediaCodec_dequeueInputBuffer(m_codec, 0); + if (idx >= 0) { + // 즉시 빈 상태로 반환 + AMediaCodec_queueInputBuffer(m_codec, idx, 0, 0, 0, 0); + } + } + + 장점: + - Buffer를 명시적으로 회수 + - MediaCodec에게 명확한 신호 + + 단점: + - Async mode에서 dequeueInputBuffer 사용이 권장되지 않음 + + 🎯 추천 해결책: 방안 A + + 근거: + 1. MediaCodec의 buffer lifecycle을 존중 + 2. Async mode 원칙 위반하지 않음 + 3. 논리적으로 가장 명확함 + + 구현: + + bool MediaCodecAV1Decoder::Reset() { + if (m_use_async_mode && m_async_handler) { + LOGI("[Reset] Async mode detected - returning buffers and clearing queue"); + + // 1. Queue에 있는 모든 buffer를 빈 상태로 반환 + while (!m_async_handler->IsInputQueueEmpty()) { + int buffer_index = m_async_handler->PopInputBufferIndex(0); // no timeout + if (buffer_index >= 0) { + // 빈 패킷으로 반환 (MediaCodec이 buffer를 재사용할 수 있게) + AMediaCodec_queueInputBuffer(m_codec, buffer_index, 0, 0, 0, 0); + } + } + + LOGI("[Reset] All buffers returned, async mode reset complete"); + return true; + } + + // Sync mode... + } + + 이 방법이 작동할 가능성이 가장 높습니다. 구현할까요? + + + + + +● 🎯 Root Cause Found - Multi-Angle Analysis + + vavcore_vulkan_bridge.cpp:188-216 - The Play() method ALWAYS calls vavcore_reset() before starting playback: + + bool VavCoreVulkanBridge::Play() { + LOGI("Starting playback..."); + // Reset to beginning before starting playback + LOGI("Resetting video to beginning..."); + VavCoreResult resetResult = vavcore_reset(m_player); + if (resetResult != VAVCORE_SUCCESS) { + LOGE("Failed to reset video: %d", resetResult); + // Continue anyway - might still work if already at beginning + } else { + LOGI("Successfully reset video to beginning"); + m_currentPositionUs = 0; + m_frameNumber = 0; + } + SetPlaybackState(PlaybackState::PLAYING); + StartContinuousPlayback(); + return true; + } + + 📊 Multi-Angle Root Cause Analysis: + + 1️⃣ Design Perspective (★★★★★ PRIMARY CAUSE) + + - Design Decision: Play() calls Reset() to ensure video always starts from beginning + - Intent: Good practice for media players (reset to frame 0 before playback) + - Problem: Works fine for synchronous mode, but async mode implementation is incomplete + + 2️⃣ Implementation Perspective (★★★★★ CRITICAL BUG) + + - Current Reset() Implementation: Clears queue but doesn't return buffers + - MediaCodec Expectation: App must call queueInputBuffer() or releaseOutputBuffer() for each buffer + - Result: MediaCodec thinks all 10 buffers are still "in use" by the app + + 3️⃣ Lifecycle Perspective (★★★★ TIMING) + + 09:33:37.030 - User clicks "Test" button + 09:33:37.161 - MediaCodec initializes in ASYNC mode + 09:33:37.170 - MediaCodec provides 10 input buffers (indices 0-9) + 09:33:37.170 - AsyncHandler queues all 10 buffers + 09:33:37.373 - User clicks "Play" → Play() calls Reset() (+203ms after init) + 09:33:37.373 - Reset() clears queue (10 buffers lost in limbo) + 09:33:37.875 - DecodeFrameAsync: "No input buffer available" (+502ms) + + 4️⃣ MediaCodec State Machine Perspective (★★★★ BUFFER ACCOUNTING) + + - MediaCodec's View: "I gave you 10 buffers, you never returned them" + - Our View: "We cleared the queue, we don't need those buffers anymore" + - Mismatch: MediaCodec won't provide new buffers until old ones are returned + + 5️⃣ Architecture Perspective (★★ DESIGN PATTERN) + + - Async Mode Philosophy: Callback-driven buffer lifecycle + - onInputBufferAvailable: "Here's a buffer, use it and return it" + - onOutputBufferAvailable: "Here's decoded data, consume it and return it" + - Missing: Proper buffer return on Reset/Flush/Stop operations + + 🔧 The Complete Picture: + + Why Reset() exists: Play() intentionally calls Reset() to start video from beginning + + Why it fails: Reset() only clears our internal queue, doesn't notify MediaCodec + + Why 203ms timing: + 1. Video loads at 09:33:37.030 + 2. MediaCodec initializes (160ms) + 3. User auto-play delay (200ms - see MainActivity.java:678) + 4. Play() called → immediately calls Reset() + + Why "No input buffer available": + - MediaCodec buffer pool: 10 total buffers + - Buffers "loaned out": 10 (indices 0-9) + - Buffers available: 0 (all still marked as "in use") + - Queue cleared: Yes, but MediaCodec doesn't know that + + + + + + +> "Play() calls Reset() to ensure video always starts from beginning" 왜 Play() 전에 Reset() 호출해야하는지 이해가 되지 않는다. + +● Good question! You're right to question this design. Looking at the code in vavcore_vulkan_bridge.cpp:200-210: + + // Reset to beginning before starting playback + LOGI("Resetting video to beginning..."); + VavCoreResult resetResult = vavcore_reset(m_player); + + The Current Design Issue: + - Play() ALWAYS calls Reset(), even when video is already at position 0 (freshly loaded) + - This is unnecessary and causes the buffer starvation problem we're fixing + - It clears state that was just established during LoadVideoFile() + + Why it was done this way: + - Ensures consistent behavior: "Play button always starts from beginning" + - If user watched video to end, pressing Play starts over + - Simple state machine: STOPPED → Reset() → PLAYING + + Better Design Options: + 1. Conditional Reset: Only reset if m_currentPositionUs != 0 + 2. Separate Methods: PlayFromBeginning() vs Resume() + 3. Check Decoder State: Query decoder position before resetting + + For Now: I'm implementing the fix to make Reset() work properly with async mode by returning buffers to MediaCodec. This makes the current + design work, even if the design itself could be improved. + + Would you like me to also add a check to skip Reset() when already at position 0? That would optimize the common case of loading a video and + immediately playing it. + + + + + + diff --git a/vav2/platforms/android/applications/vav2player/app/build.gradle.kts b/vav2/platforms/android/applications/vav2player/app/build.gradle.kts index 8961055..07355bd 100644 --- a/vav2/platforms/android/applications/vav2player/app/build.gradle.kts +++ b/vav2/platforms/android/applications/vav2player/app/build.gradle.kts @@ -25,7 +25,7 @@ android { } ndk { - abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a")) + abiFilters.addAll(listOf("arm64-v8a")) } } diff --git a/vav2/platforms/android/applications/vav2player/app/src/main/cpp/vavcore_vulkan_bridge.cpp b/vav2/platforms/android/applications/vav2player/app/src/main/cpp/vavcore_vulkan_bridge.cpp index 2273e79..d804075 100644 --- a/vav2/platforms/android/applications/vav2player/app/src/main/cpp/vavcore_vulkan_bridge.cpp +++ b/vav2/platforms/android/applications/vav2player/app/src/main/cpp/vavcore_vulkan_bridge.cpp @@ -196,18 +196,10 @@ bool VavCoreVulkanBridge::Play() { return true; } - LOGI("Starting playback..."); - // Reset to beginning before starting playback - LOGI("Resetting video to beginning..."); - VavCoreResult resetResult = vavcore_reset(m_player); - if (resetResult != VAVCORE_SUCCESS) { - LOGE("Failed to reset video: %d", resetResult); - // Continue anyway - might still work if already at beginning - } else { - LOGI("Successfully reset video to beginning"); - m_currentPositionUs = 0; - m_frameNumber = 0; - } + LOGI("Starting playback from current position..."); + + // Play from current position (no reset - that's what Stop() is for) + // This allows Resume functionality after Pause() SetPlaybackState(PlaybackState::PLAYING); // Start continuous playback thread diff --git a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAV1Decoder.cpp b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAV1Decoder.cpp index 4483af8..1d408fa 100644 --- a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAV1Decoder.cpp +++ b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAV1Decoder.cpp @@ -389,11 +389,35 @@ bool MediaCodecAV1Decoder::Reset() { return false; } - // Delegate to buffer processor for reset - if (m_buffer_processor && m_buffer_processor->IsInitialized()) { - if (!m_buffer_processor->Reset()) { - LogError("Failed to reset buffer processor"); - return false; + // Optimization: Skip reset if decoder is already at beginning (no frames decoded yet) + // This avoids expensive buffer return/reallocation cycle when loading video and immediately playing + if (m_stats.frames_decoded == 0) { + LogInfo("Reset(): Decoder already at beginning (frames_decoded=0), skipping reset"); + return true; + } + + // CRITICAL: In async mode, MediaCodec should NOT be reset + // Flush removes all queued buffers but callback registration remains intact + // Input buffer callbacks will continue to arrive as buffers become available + // Solution: Return all queued buffers to MediaCodec, then clear the queue + if (IsAsyncModeEnabled()) { + LogInfo("Reset(): Async mode detected - returning buffers to MediaCodec"); + + // Return all queued buffers to MediaCodec before clearing + // This tells MediaCodec "I'm done with these buffers, you can reuse them" + if (m_async_handler) { + m_async_handler->ReturnAndClearInputBuffers(); + LogInfo("Reset(): Returned all buffers to MediaCodec and cleared queue"); + } + + LogInfo("Reset(): Async mode reset complete - MediaCodec still running, buffers returned"); + } else { + // Sync mode: use normal flush + if (m_buffer_processor && m_buffer_processor->IsInitialized()) { + if (!m_buffer_processor->Reset()) { + LogError("Failed to reset buffer processor"); + return false; + } } } @@ -929,64 +953,9 @@ bool MediaCodecAV1Decoder::DetectHardwareCapabilities() { } std::vector MediaCodecAV1Decoder::GetAvailableCodecs() { - std::vector codecs; - - // NOTE: NDK 26 removed AMediaCodecList API, so we test potential codec names directly - LogInfo("Enumerating AV1 decoders by testing codec names (NDK 26 compatibility)"); - - // Comprehensive list of potential AV1 decoders across different Android devices - std::vector potential_codecs = { - // Samsung Exynos decoders (Galaxy S24 Ultra, etc.) - "c2.exynos.av1.decoder", - "c2.exynos2400.av1.decoder", - "c2.sec.av1.decoder", - "OMX.Exynos.AV1.Decoder", - - // Qualcomm Snapdragon decoders - "c2.qti.av1.decoder", - "c2.qcom.av1.decoder", - "OMX.qcom.video.decoder.av1", - "OMX.qti.video.decoder.av1", - - // MediaTek Dimensity decoders - "c2.mtk.av1.decoder", - "OMX.MTK.VIDEO.DECODER.AV1", - - // Google standard decoders - "c2.android.av1.decoder", - "OMX.google.av1.decoder", - "c2.google.av1.decoder", - - // Generic/fallback decoders - "av1.decoder", - "video.av1.decoder" - }; - - // Test each codec name to see if it can be created - for (const std::string& codec_name : potential_codecs) { - AMediaCodec* test_codec = AMediaCodec_createCodecByName(codec_name.c_str()); - if (test_codec) { - LogInfo("Found available codec: " + codec_name); - codecs.push_back(codec_name); - AMediaCodec_delete(test_codec); - } else { - // Codec not available - this is expected behavior for cross-device compatibility - } - } - - if (codecs.empty()) { - LogWarning("No AV1 decoders found by name testing"); - - // Final fallback: try generic type-based decoder creation - AMediaCodec* fallback_codec = AMediaCodec_createDecoderByType("video/av01"); - if (fallback_codec) { - LogInfo("Found generic AV1 decoder via type"); - codecs.push_back("generic-av01-decoder"); - AMediaCodec_delete(fallback_codec); - } - } - - return codecs; + // Delegate to MediaCodecSelector to avoid duplication + // MediaCodecSelector has the comprehensive codec list and handles caching + return m_codec_selector->GetAvailableCodecNames(); } void MediaCodecAV1Decoder::LogError(const std::string& message) const { diff --git a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.cpp b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.cpp index 806e723..17d2bdd 100644 --- a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.cpp +++ b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.cpp @@ -317,6 +317,48 @@ void MediaCodecAsyncHandler::ClearInputBufferQueue() { LogInfo("Cleared " + std::to_string(cleared_count) + " stale input buffer indices from queue"); } +void MediaCodecAsyncHandler::ReturnAndClearInputBuffers() { + if (!m_codec) { + LogError("ReturnAndClearInputBuffers: MediaCodec not initialized"); + return; + } + + std::lock_guard lock(m_async_mutex); + int returned_count = 0; + + LogInfo("ReturnAndClearInputBuffers: Returning " + std::to_string(m_async_input_buffer_queue.size()) + + " buffers to MediaCodec"); + + // Return each queued buffer to MediaCodec with empty packet + while (!m_async_input_buffer_queue.empty()) { + int32_t buffer_index = m_async_input_buffer_queue.front(); + m_async_input_buffer_queue.pop(); + + // Queue input buffer with size=0 (empty packet) + // This tells MediaCodec "I'm done with this buffer, you can reuse it" + media_status_t status = AMediaCodec_queueInputBuffer( + m_codec, + buffer_index, + 0, // offset + 0, // size (empty packet) + 0, // timestamp + 0 // flags + ); + + if (status == AMEDIA_OK) { + returned_count++; + LogInfo("ReturnAndClearInputBuffers: Returned buffer " + std::to_string(buffer_index) + + " to MediaCodec"); + } else { + LogError("ReturnAndClearInputBuffers: Failed to return buffer " + std::to_string(buffer_index) + + " to MediaCodec: " + std::to_string(status)); + } + } + + LogInfo("ReturnAndClearInputBuffers: Successfully returned " + std::to_string(returned_count) + + " buffers to MediaCodec"); +} + bool MediaCodecAsyncHandler::ProcessAsyncOutputFrame(int32_t output_index, AMediaCodecBufferInfo* buffer_info, VideoFrame& output_frame) { if (!m_codec || output_index < 0 || !buffer_info) { LogError("ProcessAsyncOutputFrame: Invalid parameters"); diff --git a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.h b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.h index 81960b4..c84d2dd 100644 --- a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.h +++ b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecAsyncHandler.h @@ -67,6 +67,7 @@ public: // Queue management void ClearInputBufferQueue(); + void ReturnAndClearInputBuffers(); // Returns buffers to MediaCodec before clearing queue // Async callback handlers (static methods for C callback compatibility) static void OnAsyncInputAvailable(AMediaCodec* codec, void* userdata, int32_t index); diff --git a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSelector.cpp b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSelector.cpp index b0dcedd..3b518b4 100644 --- a/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSelector.cpp +++ b/vav2/platforms/windows/vavcore/src/Decoder/MediaCodecSelector.cpp @@ -248,10 +248,17 @@ std::vector MediaCodecSelector::GetAvailableCodec // Private: Get available codec names by testing potential names std::vector MediaCodecSelector::GetAvailableCodecNames() { - std::vector codecs; + // Static cache: enumerate codecs only once per app lifecycle + static std::vector cached_codecs; + static bool enumerated = false; + + if (enumerated) { + LOGI("Using cached codec list (already enumerated)"); + return cached_codecs; + } // NOTE: NDK 26 removed AMediaCodecList API, so we test potential codec names directly - LOGI("Enumerating AV1 decoders by testing codec names (NDK 26 compatibility)"); + LOGI("Enumerating AV1 decoders by testing codec names (NDK 26 compatibility) - FIRST TIME"); // Comprehensive list of potential AV1 decoders across different Android devices std::vector potential_codecs = { @@ -287,17 +294,20 @@ std::vector MediaCodecSelector::GetAvailableCodecNames() { for (const auto& codec_name : potential_codecs) { AMediaCodec* test_codec = AMediaCodec_createCodecByName(codec_name.c_str()); if (test_codec != nullptr) { - codecs.push_back(codec_name); + cached_codecs.push_back(codec_name); LOGI("Found available codec: %s", codec_name.c_str()); AMediaCodec_delete(test_codec); } } - if (codecs.empty()) { + if (cached_codecs.empty()) { LOGW("No AV1 codecs found on this device"); } - return codecs; + // Mark as enumerated and return cached result + enumerated = true; + LOGI("Codec enumeration complete - %zu codec(s) cached", cached_codecs.size()); + return cached_codecs; } // Check if codec name indicates AV1 support diff --git a/vav2/platforms/windows/vavcore/src/Decoder/VideoDecoderFactory.cpp b/vav2/platforms/windows/vavcore/src/Decoder/VideoDecoderFactory.cpp index 6d4170d..cec3f63 100644 --- a/vav2/platforms/windows/vavcore/src/Decoder/VideoDecoderFactory.cpp +++ b/vav2/platforms/windows/vavcore/src/Decoder/VideoDecoderFactory.cpp @@ -192,6 +192,30 @@ std::vector VideoDecoderFactory::GetAvailableDecoders(VideoCodecTyp void VideoDecoderFactory::RegisterAV1Decoder(const DecoderRegistration& registration) { auto& decoders = GetDecoderList(VideoCodecType::AV1); + + // Check for duplicate registration (prevent same decoder being registered multiple times) + for (const auto& existing : decoders) { + if (existing.name == registration.name) { +#ifdef DEBUG + // Debug build: Crash with assertion to detect bugs early + LOGF_ERROR("[VideoDecoderFactory] FATAL: Decoder '%s' already registered - duplicate registration is a bug!", + registration.name.c_str()); +#ifdef ANDROID + __android_log_assert("duplicate_decoder", "VavCore-DecoderFactory", + "Decoder '%s' already registered - this is a programming error", + registration.name.c_str()); +#else + assert(false && "Duplicate decoder registration detected"); +#endif +#else + // Release build: Log warning and skip silently + LOGF_WARNING("[VideoDecoderFactory] Decoder '%s' already registered - skipping duplicate registration", + registration.name.c_str()); +#endif + return; + } + } + decoders.push_back(registration); // Sort by priority (lower numbers = higher priority) diff --git a/vav2/platforms/windows/vavcore/src/VavCore.cpp b/vav2/platforms/windows/vavcore/src/VavCore.cpp index 6c58b81..72a4e37 100644 --- a/vav2/platforms/windows/vavcore/src/VavCore.cpp +++ b/vav2/platforms/windows/vavcore/src/VavCore.cpp @@ -23,10 +23,11 @@ using namespace VavCore; extern "C" bool PerformSafeDllInitialization(); extern "C" bool IsDllReadyForInitialization(); -// Forward declarations for decoder registration functions -extern "C" void RegisterAV1Decoders(); +// Forward declaration for Android JavaVM access #ifdef ANDROID -extern "C" void RegisterMediaCodecDecoders(); +namespace VavCore { + JavaVM* GetAndroidJavaVM(); +} #endif // Global state @@ -58,6 +59,26 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { g_jni_loaded = false; } +// Internal function to get JavaVM for use by MediaCodec decoders +// Defined here so it's available before first use +namespace VavCore { + JavaVM* GetAndroidJavaVM() { + std::lock_guard lock(g_mutex); + + // DEBUG: Log every call to GetAndroidJavaVM() + LOGF_INFO("[GetAndroidJavaVM] Called - g_android_java_vm = %p", g_android_java_vm); + LOGF_INFO("[GetAndroidJavaVM] g_jni_loaded = %d", g_jni_loaded); + + if (g_android_java_vm != nullptr) { + LOGF_INFO("[GetAndroidJavaVM] Returning valid JavaVM: %p", g_android_java_vm); + } else { + LOGF_ERROR("[GetAndroidJavaVM] JavaVM is NULL! JNI may not have been initialized."); + } + + return g_android_java_vm; + } +} + // Android-specific readiness check static bool IsAndroidLibraryReady() { return g_jni_loaded; @@ -248,19 +269,12 @@ VAVCORE_API VavCoreResult vavcore_initialize(void) { return VAVCORE_ERROR_INIT_FAILED; } -#endif - - // Register available decoders -#ifdef ANDROID - // Android: ONLY register MediaCodec hardware decoder - // Do NOT fallback to dav1d CPU decoder - RegisterMediaCodecDecoders(); -#else - // Windows: Register all available decoders including dav1d fallback - RegisterAV1Decoders(); #endif // Initialize decoder factory + // Note: InitializeFactory() handles platform-specific decoder registration internally + // - Android: Explicitly calls RegisterMediaCodecDecoders() due to JNI initialization order + // - Windows: Uses static initialization for decoder registration VideoDecoderFactory::InitializeFactory(); g_initialized = true; return VAVCORE_SUCCESS; @@ -1043,25 +1057,4 @@ VAVCORE_API VavCoreResult vavcore_convert_yuv_to_rgb( return VAVCORE_ERROR_NOT_SUPPORTED; } -} // extern "C" - -#ifdef ANDROID -// Internal function to get JavaVM for use by MediaCodec decoders -namespace VavCore { - JavaVM* GetAndroidJavaVM() { - std::lock_guard lock(g_mutex); - - // DEBUG: Log every call to GetAndroidJavaVM() - LOGF_INFO("[GetAndroidJavaVM] Called - g_android_java_vm = %p", g_android_java_vm); - LOGF_INFO("[GetAndroidJavaVM] g_jni_loaded = %d", g_jni_loaded); - - if (g_android_java_vm != nullptr) { - LOGF_INFO("[GetAndroidJavaVM] Returning valid JavaVM: %p", g_android_java_vm); - } else { - LOGF_ERROR("[GetAndroidJavaVM] JavaVM is NULL! JNI may not have been initialized."); - } - - return g_android_java_vm; - } -} -#endif \ No newline at end of file +} // extern "C" \ No newline at end of file