WIP
This commit is contained in:
317
todo30.txt
Normal file
317
todo30.txt
Normal file
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ android {
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a"))
|
||||
abiFilters.addAll(listOf("arm64-v8a"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<std::string> MediaCodecAV1Decoder::GetAvailableCodecs() {
|
||||
std::vector<std::string> 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<std::string> 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 {
|
||||
|
||||
@@ -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<std::mutex> 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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -248,10 +248,17 @@ std::vector<MediaCodecSelector::CodecInfo> MediaCodecSelector::GetAvailableCodec
|
||||
|
||||
// Private: Get available codec names by testing potential names
|
||||
std::vector<std::string> MediaCodecSelector::GetAvailableCodecNames() {
|
||||
std::vector<std::string> codecs;
|
||||
// Static cache: enumerate codecs only once per app lifecycle
|
||||
static std::vector<std::string> 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<std::string> potential_codecs = {
|
||||
@@ -287,17 +294,20 @@ std::vector<std::string> 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
|
||||
|
||||
@@ -192,6 +192,30 @@ std::vector<std::string> 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)
|
||||
|
||||
@@ -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<std::mutex> 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<std::mutex> 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
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user