WIP
This commit is contained in:
1311
todo29.txt
1311
todo29.txt
File diff suppressed because it is too large
Load Diff
69
vav2/docs/working/mediacodec-recreation.md
Normal file
69
vav2/docs/working/mediacodec-recreation.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# MediaCodec Recreation Issue Analysis
|
||||
|
||||
## Why MediaCodec Recreation is Needed
|
||||
|
||||
### Problem
|
||||
MediaCodec is initially configured with `surface=nullptr` (CPU mode), but later we need it to output to ImageReader surface (GPU mode) when Vulkan device is registered.
|
||||
|
||||
### Android MediaCodec Limitation
|
||||
`AMediaCodec_setOutputSurface()` API has limitations:
|
||||
- Only works for video tunneling mode
|
||||
- **Does NOT work reliably with async mode callbacks**
|
||||
- Cannot change surface after codec is started in async mode
|
||||
|
||||
### Current Flow
|
||||
1. `Initialize()` creates MediaCodec with surface=nullptr
|
||||
2. MediaCodec starts with async callbacks
|
||||
3. `SetVulkanDevice()` is called later
|
||||
4. ImageReader surface is created
|
||||
5. Need to switch MediaCodec output to ImageReader surface
|
||||
6. **setOutputSurface() doesn't work in async mode → Must recreate codec**
|
||||
|
||||
## Current Issue After Recreation
|
||||
|
||||
Even after recreation, `getInputBuffer()` returns null for buffer indices from callbacks.
|
||||
|
||||
### Root Cause
|
||||
**Race condition after `AMediaCodec_start()`:**
|
||||
- Callbacks fire immediately when codec starts
|
||||
- Buffer indices are enqueued (0-9)
|
||||
- BUT codec internal buffer allocation isn't complete yet
|
||||
- When `getInputBuffer()` is called with these indices → returns null
|
||||
|
||||
### Evidence from Logs
|
||||
```
|
||||
23:32:11.938 - Codec started, callbacks fire (indices 0-9 queued)
|
||||
23:32:12.142 - DecodeFrameAsync gets index 0
|
||||
23:32:12.142 - getInputBuffer(index=0) → returns null!
|
||||
```
|
||||
|
||||
## Possible Solutions
|
||||
|
||||
### Option 1: Wait after start() then clear queue
|
||||
After `AMediaCodec_start()`, wait 10-50ms for codec to stabilize, then clear old indices and wait for new callbacks.
|
||||
|
||||
### Option 2: Retry logic
|
||||
If `getInputBuffer()` returns null, put index back in queue and retry with next index.
|
||||
|
||||
### Option 3: Don't recreate - use software decode path
|
||||
Accept that async mode with surface switching doesn't work well, fall back to CPU decoding.
|
||||
|
||||
### Option 4: Change initialization order (BEST)
|
||||
- Call `SetVulkanDevice()` BEFORE `Initialize()`
|
||||
- But this requires changing application code
|
||||
|
||||
## Recommendation
|
||||
|
||||
Try **Option 1** first - it's the simplest fix:
|
||||
```cpp
|
||||
// After AMediaCodec_start()
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
// Clear old buffer indices
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_async_handler->m_async_mutex);
|
||||
while (!m_async_handler->m_async_input_buffer_queue.empty()) {
|
||||
m_async_handler->m_async_input_buffer_queue.pop();
|
||||
}
|
||||
}
|
||||
// Wait for new callbacks with valid buffers
|
||||
```
|
||||
@@ -1,19 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<!-- Video Display Area -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<!-- Video Display Area - Full Screen -->
|
||||
<com.vavcore.player.VulkanVideoView
|
||||
android:id="@+id/vulkan_video_view"
|
||||
android:layout_width="match_parent"
|
||||
@@ -36,15 +30,16 @@
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:indeterminateTint="@color/primary_color" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- Status and Performance Info Panel -->
|
||||
<!-- Status and Performance Info Panel - Overlay with semi-transparent background -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:background="@color/control_background">
|
||||
android:padding="8dp"
|
||||
android:background="#80000000"
|
||||
android:clickable="false"
|
||||
android:focusable="false">
|
||||
|
||||
<!-- Status Text -->
|
||||
<TextView
|
||||
@@ -53,8 +48,12 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/status_ready"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center_horizontal" />
|
||||
android:textSize="12sp"
|
||||
android:gravity="center_horizontal"
|
||||
android:shadowColor="#000000"
|
||||
android:shadowDx="1"
|
||||
android:shadowDy="1"
|
||||
android:shadowRadius="2" />
|
||||
|
||||
<!-- Performance Metrics -->
|
||||
<TextView
|
||||
@@ -63,11 +62,15 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/performance_idle"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="10sp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:fontFamily="monospace" />
|
||||
android:fontFamily="monospace"
|
||||
android:shadowColor="#000000"
|
||||
android:shadowDx="1"
|
||||
android:shadowDy="1"
|
||||
android:shadowRadius="2" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -1,10 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Vav2Player_Android" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
<style name="Theme.Vav2Player_Android" parent="android:Theme.Material.NoActionBar" />
|
||||
|
||||
<style name="Theme.VavCorePlayer" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<style name="Theme.VavCorePlayer" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:colorBackground">@android:color/black</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
<item name="android:textColorPrimary">@android:color/white</item>
|
||||
<item name="android:textColorSecondary">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -11,6 +11,8 @@
|
||||
// MediaCodec list functionality may need alternative implementation
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <sys/system_properties.h>
|
||||
#if __ANDROID_API__ >= 29
|
||||
#include <android/api-level.h>
|
||||
@@ -133,7 +135,7 @@ bool MediaCodecAV1Decoder::SetupVulkanPipeline() {
|
||||
// Initialization helper: Finalize initialization
|
||||
bool MediaCodecAV1Decoder::FinalizeInitialization() {
|
||||
m_initialized = true;
|
||||
if (m_buffer_processor) {
|
||||
if (m_buffer_processor && !IsAsyncModeEnabled()) {
|
||||
m_buffer_processor->ResetPriming();
|
||||
}
|
||||
LogInfo("MediaCodec decoder initialization completed successfully");
|
||||
@@ -153,17 +155,38 @@ bool MediaCodecAV1Decoder::Initialize(const VideoMetadata& metadata) {
|
||||
// Continue anyway - DetectHardwareCapabilities logs details
|
||||
}
|
||||
|
||||
// Step 3: Initialize codec with fallback strategy
|
||||
if (!InitializeCodecWithFallback()) {
|
||||
LogWarning("All hardware AV1 decoders failed, falling back to software (dav1d)");
|
||||
m_hardware_accelerated = false;
|
||||
return false; // Return false to let factory try next decoder (dav1d)
|
||||
// Step 3: Vulkan device set? ImageReader is MANDATORY (GPU-only requirement)
|
||||
if (m_surface_manager->GetVulkanDevice()) {
|
||||
LogInfo("Vulkan device set - setting up ImageReader (GPU h/w processing required)");
|
||||
|
||||
JavaVM* javaVM = GetAndroidJavaVM();
|
||||
if (!javaVM) {
|
||||
LogError("Vulkan device set but JavaVM unavailable");
|
||||
return false; // Hard failure - GPU requirement not met
|
||||
}
|
||||
|
||||
// Step 4: Setup Vulkan pipeline if Vulkan device is already set
|
||||
if (!SetupVulkanPipeline()) {
|
||||
LogWarning("Vulkan pipeline setup failed - continuing with CPU fallback");
|
||||
// Not fatal - continue initialization
|
||||
m_surface_manager->SetJavaVM(javaVM);
|
||||
m_surface_manager->SetVideoDimensions(m_width, m_height);
|
||||
|
||||
if (!m_surface_manager->SetupImageReader(m_width, m_height)) {
|
||||
LogError("Vulkan device set but ImageReader setup failed");
|
||||
return false; // Hard failure - GPU requirement not met
|
||||
}
|
||||
|
||||
m_surface = m_surface_manager->GetAndroidSurface();
|
||||
if (!m_surface) {
|
||||
LogError("Vulkan device set but Surface unavailable");
|
||||
return false; // Hard failure - GPU requirement not met
|
||||
}
|
||||
|
||||
LogInfo("Vulkan zero-copy pipeline ready");
|
||||
}
|
||||
|
||||
// Step 4: Initialize codec with fallback strategy
|
||||
if (!InitializeCodecWithFallback()) {
|
||||
LogError("Hardware AV1 decoder initialization failed");
|
||||
m_hardware_accelerated = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: Finalize initialization
|
||||
@@ -543,125 +566,56 @@ JNIEnv* MediaCodecAV1Decoder::GetJNIEnv() const {
|
||||
}
|
||||
|
||||
bool MediaCodecAV1Decoder::SetVulkanDevice(void* vk_device, void* vk_instance, void* vk_physical_device) {
|
||||
if (!m_initialized) {
|
||||
LogError("Cannot set Vulkan device - decoder not initialized");
|
||||
// Pass JavaVM to surface manager before setting Vulkan device
|
||||
JavaVM* javaVM = GetAndroidJavaVM();
|
||||
if (javaVM) {
|
||||
m_surface_manager->SetJavaVM(javaVM);
|
||||
LogInfo("JavaVM passed to surface manager in SetVulkanDevice()");
|
||||
} else {
|
||||
LogWarning("JavaVM not available in SetVulkanDevice() - ImageReader may fail");
|
||||
}
|
||||
|
||||
// Always store Vulkan device in surface manager (works before or after initialization)
|
||||
bool result = m_surface_manager->SetVulkanDevice(vk_device, vk_instance, vk_physical_device);
|
||||
if (result) {
|
||||
LogInfo("Vulkan device registered with surface manager");
|
||||
} else {
|
||||
LogError("Failed to register Vulkan device with surface manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If decoder is not yet initialized, Initialize() will use the stored Vulkan device
|
||||
if (!m_initialized) {
|
||||
LogInfo("Decoder not initialized yet - Vulkan device stored for later use during Initialize()");
|
||||
return true;
|
||||
}
|
||||
|
||||
// If decoder is already initialized, we need to handle it differently
|
||||
if (!m_hardware_accelerated) {
|
||||
LogWarning("Vulkan image requires hardware acceleration");
|
||||
return false;
|
||||
}
|
||||
|
||||
// CRITICAL: Pass JavaVM to surface manager before setting Vulkan device
|
||||
// This is needed for ImageReader initialization on decoder thread
|
||||
LogInfo("[SetVulkanDevice] About to call GetAndroidJavaVM()...");
|
||||
JavaVM* javaVM = GetAndroidJavaVM();
|
||||
LogInfo("[SetVulkanDevice] GetAndroidJavaVM() returned: " + std::string(javaVM ? "VALID" : "NULL") + " (" + std::to_string(reinterpret_cast<uintptr_t>(javaVM)) + ")");
|
||||
|
||||
if (javaVM) {
|
||||
LogInfo("[SetVulkanDevice] JavaVM is valid, passing to surface manager...");
|
||||
m_surface_manager->SetJavaVM(javaVM);
|
||||
LogInfo("JavaVM passed to surface manager in SetVulkanDevice()");
|
||||
} else {
|
||||
LogError("[SetVulkanDevice] JavaVM is NULL! ImageReader initialization will fail!");
|
||||
LogWarning("JavaVM not available in SetVulkanDevice() - ImageReader cannot be initialized");
|
||||
// Check if ImageReader surface is already configured (early initialization path)
|
||||
ANativeWindow* current_surface = m_surface_manager->GetAndroidSurface();
|
||||
if (current_surface && current_surface == m_surface) {
|
||||
LogInfo("ImageReader surface already configured during Initialize() - no recreation needed!");
|
||||
return true; // Early return - avoid recreation
|
||||
}
|
||||
|
||||
// Delegate to surface manager
|
||||
bool result = m_surface_manager->SetVulkanDevice(vk_device, vk_instance, vk_physical_device);
|
||||
if (result) {
|
||||
LogInfo("Vulkan device set successfully");
|
||||
|
||||
// CRITICAL FIX: If video dimensions are already set (decoder initialized after Vulkan device),
|
||||
// we need to setup ImageReader and reconfigure MediaCodec!
|
||||
// Decoder was initialized without Vulkan, now Vulkan device is being set
|
||||
// This is the problematic late-registration path
|
||||
if (m_width > 0 && m_height > 0) {
|
||||
LogInfo("Setting up ImageReader → VkImage pipeline after Vulkan device registration");
|
||||
|
||||
// Set video dimensions for ImageReader allocation
|
||||
m_surface_manager->SetVideoDimensions(m_width, m_height);
|
||||
|
||||
// Setup ImageReader with video dimensions
|
||||
if (!m_surface_manager->SetupImageReader(m_width, m_height)) {
|
||||
LogError("Failed to setup ImageReader after Vulkan device set");
|
||||
return false;
|
||||
LogWarning("Vulkan device set AFTER decoder initialization - ImageReader cannot be added");
|
||||
LogWarning("MediaCodec must be created with surface from the start");
|
||||
LogWarning("Continuing with CPU fallback - video will work but without zero-copy GPU pipeline");
|
||||
// Return true because decoder is still functional with CPU path
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get Surface from ImageReader for MediaCodec
|
||||
m_surface = m_surface_manager->GetAndroidSurface();
|
||||
if (!m_surface) {
|
||||
LogError("Failed to get Surface from ImageReader after Vulkan device set");
|
||||
return false;
|
||||
}
|
||||
|
||||
// CRITICAL: MediaCodec cannot dynamically change surface after being configured with nullptr
|
||||
// We must stop, reconfigure, and restart MediaCodec with the ImageReader surface
|
||||
LogInfo("Reconfiguring MediaCodec with ImageReader surface");
|
||||
|
||||
// Step 1: Cleanup async mode before stopping MediaCodec
|
||||
CleanupAsyncMode();
|
||||
|
||||
// Step 2: Stop MediaCodec
|
||||
media_status_t status = AMediaCodec_stop(m_codec);
|
||||
if (status != AMEDIA_OK) {
|
||||
LogError("Failed to stop MediaCodec for reconfiguration: " + std::to_string(status));
|
||||
return false;
|
||||
}
|
||||
LogInfo("MediaCodec stopped for reconfiguration");
|
||||
|
||||
// Step 3: Reconfigure MediaCodec with ImageReader surface
|
||||
status = AMediaCodec_configure(
|
||||
m_codec,
|
||||
m_format,
|
||||
m_surface, // ImageReader surface
|
||||
nullptr, // No crypto
|
||||
0 // Decoder flag
|
||||
);
|
||||
if (status != AMEDIA_OK) {
|
||||
LogError("Failed to reconfigure MediaCodec with ImageReader surface: " + std::to_string(status));
|
||||
return false;
|
||||
}
|
||||
LogInfo("MediaCodec reconfigured with ImageReader surface");
|
||||
|
||||
// Step 4: Setup async callbacks AFTER configure but BEFORE start
|
||||
LogInfo("Setting up async callbacks for reconfigured MediaCodec");
|
||||
if (InitializeAsyncMode()) {
|
||||
if (EnableAsyncMode(true)) {
|
||||
LogInfo("Async callbacks registered successfully after reconfiguration");
|
||||
} else {
|
||||
LogWarning("Failed to activate async mode - continuing with sync mode");
|
||||
}
|
||||
} else {
|
||||
LogWarning("Failed to initialize async mode - continuing with sync mode");
|
||||
}
|
||||
|
||||
// Step 5: Start MediaCodec
|
||||
status = AMediaCodec_start(m_codec);
|
||||
if (status != AMEDIA_OK) {
|
||||
LogError("Failed to restart MediaCodec after reconfiguration: " + std::to_string(status));
|
||||
return false;
|
||||
}
|
||||
LogInfo("MediaCodec restarted successfully with ImageReader output");
|
||||
|
||||
// Step 6: Re-prime the decoder after reconfiguration
|
||||
if (m_buffer_processor) {
|
||||
m_buffer_processor->ResetPriming();
|
||||
if (m_buffer_processor->PrimeDecoder()) {
|
||||
LogInfo("MediaCodec re-primed after reconfiguration");
|
||||
} else {
|
||||
LogWarning("MediaCodec priming failed after reconfiguration, but continuing");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MediaCodecAV1Decoder::CreateVulkanImage(void* vk_device, void* vk_instance) {
|
||||
// Delegate to surface manager
|
||||
return m_surface_manager->CreateVulkanImage(vk_device, vk_instance);
|
||||
}
|
||||
|
||||
bool MediaCodecAV1Decoder::SetupAHardwareBuffer() {
|
||||
// Delegate to surface manager
|
||||
bool result = m_surface_manager->SetupAHardwareBuffer();
|
||||
@@ -753,12 +707,17 @@ bool MediaCodecAV1Decoder::InitializeMediaCodec() {
|
||||
|
||||
// Prime the decoder immediately after initialization (before any decoding starts)
|
||||
// This ensures no concurrent dequeue issues
|
||||
// NOTE: Skip priming in async mode - callbacks handle buffer management
|
||||
if (!IsAsyncModeEnabled()) {
|
||||
if (!m_buffer_processor->PrimeDecoder()) {
|
||||
LogWarning("MediaCodec priming failed, but continuing initialization");
|
||||
// Not a fatal error - decoder can work without priming
|
||||
} else {
|
||||
LogInfo("MediaCodec primed successfully during initialization");
|
||||
}
|
||||
} else {
|
||||
LogInfo("Skipping priming - async mode uses callback-based buffer management");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ public:
|
||||
JNIEnv* GetJNIEnv() const;
|
||||
|
||||
// Vulkan image support (public for testing)
|
||||
bool CreateVulkanImage(void* vk_device, void* vk_instance);
|
||||
bool SetupAHardwareBuffer();
|
||||
bool CreateSurfaceFromAHardwareBuffer(AHardwareBuffer* buffer);
|
||||
|
||||
|
||||
@@ -47,11 +47,14 @@ void MediaCodecAsyncHandler::Cleanup() {
|
||||
m_decoder = nullptr;
|
||||
m_async_processing_active = false;
|
||||
|
||||
// Clear async queue
|
||||
// Clear async queues
|
||||
std::lock_guard<std::mutex> lock(m_async_mutex);
|
||||
while (!m_async_output_queue.empty()) {
|
||||
m_async_output_queue.pop();
|
||||
}
|
||||
while (!m_async_input_buffer_queue.empty()) {
|
||||
m_async_input_buffer_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaCodecAsyncHandler::SupportsAsyncMode() const {
|
||||
@@ -83,8 +86,14 @@ bool MediaCodecAsyncHandler::InitializeAsyncMode() {
|
||||
|
||||
// Setup async callbacks
|
||||
m_async_callbacks.onInputBufferAvailable = [this](int32_t index) {
|
||||
// Input buffer available - not used in current implementation
|
||||
// Can be used for async input enqueue in future optimization
|
||||
// Input buffer available - store in queue for DecodeFrameAsync
|
||||
LogInfo("onInputBufferAvailable callback: index=" + std::to_string(index) +
|
||||
", MediaCodec=" + std::to_string(reinterpret_cast<uintptr_t>(m_codec)));
|
||||
std::lock_guard<std::mutex> lock(m_async_mutex);
|
||||
m_async_input_buffer_queue.push(index);
|
||||
LogInfo("Input buffer queued: index=" + std::to_string(index) +
|
||||
", queue size=" + std::to_string(m_async_input_buffer_queue.size()));
|
||||
m_async_condition.notify_one();
|
||||
};
|
||||
|
||||
m_async_callbacks.onOutputBufferAvailable = [this](int32_t index, AMediaCodecBufferInfo* bufferInfo) {
|
||||
@@ -140,6 +149,9 @@ bool MediaCodecAsyncHandler::InitializeAsyncMode() {
|
||||
m_async_mode_enabled = true;
|
||||
m_async_processing_active = true;
|
||||
LogInfo("Async mode initialized successfully");
|
||||
|
||||
// NOTE: Input buffer callbacks will be triggered automatically after MediaCodec start
|
||||
// No need to wait here - callbacks are asynchronous
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -150,16 +162,42 @@ void MediaCodecAsyncHandler::CleanupAsyncMode() {
|
||||
|
||||
LogInfo("Cleaning up async mode");
|
||||
m_async_processing_active = false;
|
||||
|
||||
// CRITICAL: Unregister async callbacks from MediaCodec BEFORE clearing state
|
||||
// This prevents lingering callbacks from firing after codec deletion
|
||||
if (m_codec) {
|
||||
LogInfo("Unregistering async callbacks from MediaCodec");
|
||||
media_status_t status = AMediaCodec_setAsyncNotifyCallback(
|
||||
m_codec,
|
||||
{
|
||||
.onAsyncInputAvailable = nullptr,
|
||||
.onAsyncOutputAvailable = nullptr,
|
||||
.onAsyncFormatChanged = nullptr,
|
||||
.onAsyncError = nullptr
|
||||
},
|
||||
nullptr // no userdata
|
||||
);
|
||||
|
||||
if (status != AMEDIA_OK) {
|
||||
LogWarning("Failed to unregister async callbacks: " + std::to_string(status));
|
||||
} else {
|
||||
LogInfo("Async callbacks unregistered successfully");
|
||||
}
|
||||
}
|
||||
|
||||
m_async_mode_enabled = false;
|
||||
|
||||
// Wake up any waiting threads
|
||||
m_async_condition.notify_all();
|
||||
|
||||
// Clear async queue
|
||||
// Clear async queues
|
||||
std::lock_guard<std::mutex> lock(m_async_mutex);
|
||||
while (!m_async_output_queue.empty()) {
|
||||
m_async_output_queue.pop();
|
||||
}
|
||||
while (!m_async_input_buffer_queue.empty()) {
|
||||
m_async_input_buffer_queue.pop();
|
||||
}
|
||||
|
||||
LogInfo("Async mode cleanup complete");
|
||||
}
|
||||
@@ -170,20 +208,56 @@ bool MediaCodecAsyncHandler::DecodeFrameAsync(const uint8_t* packet_data, size_t
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enqueue input buffer
|
||||
ssize_t input_index = AMediaCodec_dequeueInputBuffer(m_codec, 10000); // 10ms timeout
|
||||
if (input_index < 0) {
|
||||
LogWarning("DecodeFrameAsync: No input buffer available");
|
||||
// Wait for input buffer from async callback
|
||||
ssize_t input_index = -1;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_async_mutex);
|
||||
|
||||
// Wait for input buffer with progressive timeout
|
||||
// First decode might need longer wait for callbacks to start
|
||||
int timeout_ms = m_async_input_buffer_queue.empty() ? 500 : 100; // 500ms first time, 100ms thereafter
|
||||
|
||||
bool buffer_available = m_async_condition.wait_for(
|
||||
lock,
|
||||
std::chrono::milliseconds(timeout_ms),
|
||||
[this] { return !m_async_input_buffer_queue.empty() || !m_async_processing_active; }
|
||||
);
|
||||
|
||||
if (!buffer_available || m_async_input_buffer_queue.empty()) {
|
||||
LogWarning("DecodeFrameAsync: No input buffer available after " + std::to_string(timeout_ms) + "ms (queue size: " +
|
||||
std::to_string(m_async_input_buffer_queue.size()) + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get input buffer index from queue
|
||||
input_index = m_async_input_buffer_queue.front();
|
||||
m_async_input_buffer_queue.pop();
|
||||
LogInfo("DecodeFrameAsync: Got input buffer index " + std::to_string(input_index) +
|
||||
" from queue (remaining: " + std::to_string(m_async_input_buffer_queue.size()) + ")");
|
||||
}
|
||||
|
||||
// Log codec state before attempting getInputBuffer
|
||||
LogInfo("DecodeFrameAsync: About to call getInputBuffer with index=" + std::to_string(input_index) +
|
||||
", MediaCodec=" + std::to_string(reinterpret_cast<uintptr_t>(m_codec)) +
|
||||
", async_mode_enabled=" + std::to_string(m_async_mode_enabled) +
|
||||
", async_processing_active=" + std::to_string(m_async_processing_active));
|
||||
|
||||
size_t buffer_capacity = 0;
|
||||
uint8_t* input_buffer = AMediaCodec_getInputBuffer(m_codec, input_index, &buffer_capacity);
|
||||
|
||||
LogInfo("DecodeFrameAsync: getInputBuffer returned: buffer=" +
|
||||
std::to_string(reinterpret_cast<uintptr_t>(input_buffer)) +
|
||||
", capacity=" + std::to_string(buffer_capacity));
|
||||
|
||||
if (!input_buffer) {
|
||||
LogError("DecodeFrameAsync: Failed to get input buffer");
|
||||
LogError("DecodeFrameAsync: Failed to get input buffer for index " + std::to_string(input_index) +
|
||||
", buffer_capacity=" + std::to_string(buffer_capacity));
|
||||
LogError("DecodeFrameAsync: MediaCodec=" + std::to_string(reinterpret_cast<uintptr_t>(m_codec)));
|
||||
return false;
|
||||
}
|
||||
|
||||
LogInfo("DecodeFrameAsync: Got input buffer successfully, capacity=" + std::to_string(buffer_capacity));
|
||||
|
||||
if (packet_size > buffer_capacity) {
|
||||
LogError("DecodeFrameAsync: Packet size exceeds buffer capacity");
|
||||
AMediaCodec_queueInputBuffer(m_codec, input_index, 0, 0, 0, 0);
|
||||
@@ -233,6 +307,16 @@ bool MediaCodecAsyncHandler::WaitForAsyncFrame(VideoFrame& output_frame, int tim
|
||||
return true;
|
||||
}
|
||||
|
||||
void MediaCodecAsyncHandler::ClearInputBufferQueue() {
|
||||
std::lock_guard<std::mutex> lock(m_async_mutex);
|
||||
int cleared_count = 0;
|
||||
while (!m_async_input_buffer_queue.empty()) {
|
||||
m_async_input_buffer_queue.pop();
|
||||
cleared_count++;
|
||||
}
|
||||
LogInfo("Cleared " + std::to_string(cleared_count) + " stale input buffer indices from queue");
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -329,9 +413,16 @@ bool MediaCodecAsyncHandler::ProcessAsyncOutputFrame(int32_t output_index, AMedi
|
||||
|
||||
void MediaCodecAsyncHandler::OnAsyncInputAvailable(AMediaCodec* codec, void* userdata, int32_t index) {
|
||||
auto* handler = static_cast<MediaCodecAsyncHandler*>(userdata);
|
||||
if (handler && handler->m_async_callbacks.onInputBufferAvailable) {
|
||||
if (handler) {
|
||||
// Log codec pointer from callback parameter vs stored pointer
|
||||
handler->LogInfo("OnAsyncInputAvailable: index=" + std::to_string(index) +
|
||||
", callback_codec=" + std::to_string(reinterpret_cast<uintptr_t>(codec)) +
|
||||
", stored_codec=" + std::to_string(reinterpret_cast<uintptr_t>(handler->m_codec)));
|
||||
|
||||
if (handler->m_async_callbacks.onInputBufferAvailable) {
|
||||
handler->m_async_callbacks.onInputBufferAvailable(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MediaCodecAsyncHandler::OnAsyncOutputAvailable(AMediaCodec* codec, void* userdata, int32_t index, AMediaCodecBufferInfo* bufferInfo) {
|
||||
|
||||
@@ -65,6 +65,9 @@ public:
|
||||
bool DecodeFrameAsync(const uint8_t* packet_data, size_t packet_size, VideoFrame& output_frame);
|
||||
bool WaitForAsyncFrame(VideoFrame& output_frame, int timeout_ms = 100);
|
||||
|
||||
// Queue management
|
||||
void ClearInputBufferQueue();
|
||||
|
||||
// Async callback handlers (static methods for C callback compatibility)
|
||||
static void OnAsyncInputAvailable(AMediaCodec* codec, void* userdata, int32_t index);
|
||||
static void OnAsyncOutputAvailable(AMediaCodec* codec, void* userdata, int32_t index, AMediaCodecBufferInfo* bufferInfo);
|
||||
@@ -100,6 +103,9 @@ private:
|
||||
// Async output queue
|
||||
std::queue<AsyncFrameData> m_async_output_queue;
|
||||
|
||||
// Async input buffer index queue
|
||||
std::queue<int32_t> m_async_input_buffer_queue;
|
||||
|
||||
// Async callbacks
|
||||
MediaCodecAsyncCallbacks m_async_callbacks;
|
||||
};
|
||||
|
||||
@@ -397,25 +397,26 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f
|
||||
player->impl->pendingD3DSurfaceType = VAVCORE_SURFACE_CPU;
|
||||
}
|
||||
|
||||
LOGF_DEBUG("[VavCore] Initializing decoder...");
|
||||
|
||||
// Initialize decoder
|
||||
if (!player->impl->decoder->Initialize(player->impl->metadata)) {
|
||||
LOGF_ERROR("[VavCore] Decoder initialization failed (unsupported format or hardware unavailable)");
|
||||
player->impl->decoder.reset();
|
||||
player->impl->fileReader->CloseFile();
|
||||
return VAVCORE_ERROR_DECODER_UNAVAILABLE;
|
||||
}
|
||||
|
||||
LOGF_DEBUG("[VavCore] Decoder initialized successfully!");
|
||||
|
||||
#ifdef ANDROID
|
||||
// Apply pending Vulkan device AFTER decoder initialization
|
||||
// CRITICAL: Apply Vulkan device BEFORE decoder initialization
|
||||
// This allows MediaCodec to be created with ImageReader surface from the start
|
||||
if (player->impl->has_vulkan_device) {
|
||||
LOGF_DEBUG("[VavCore] Applying pending Vulkan device after decoder initialization...");
|
||||
LOGF_DEBUG("[VavCore] Applying pending Vulkan device BEFORE decoder initialization...");
|
||||
LOGF_DEBUG("[VavCore] Vulkan device: %p, instance: %p, physical device: %p",
|
||||
player->impl->vulkan_device, player->impl->vulkan_instance, player->impl->vulkan_physical_device);
|
||||
|
||||
// Pre-check: Vulkan device requires JavaVM for ImageReader initialization
|
||||
// If JavaVM is not available, decoder initialization will 100% fail
|
||||
JavaVM* javaVM = VavCore::GetAndroidJavaVM();
|
||||
if (!javaVM) {
|
||||
LOGF_ERROR("[VavCore] CRITICAL: Vulkan device set but JavaVM unavailable!");
|
||||
LOGF_ERROR("[VavCore] This means libVavCore.so was not properly loaded or JNI_OnLoad failed.");
|
||||
LOGF_ERROR("[VavCore] GPU hardware processing requires JNI (Android requirement).");
|
||||
player->impl->decoder.reset();
|
||||
player->impl->fileReader->CloseFile();
|
||||
return VAVCORE_ERROR_INIT_FAILED;
|
||||
}
|
||||
|
||||
bool vulkan_success = player->impl->decoder->SetVulkanDevice(
|
||||
player->impl->vulkan_device,
|
||||
player->impl->vulkan_instance,
|
||||
@@ -423,16 +424,29 @@ VAVCORE_API VavCoreResult vavcore_open_file(VavCorePlayer* player, const char* f
|
||||
);
|
||||
|
||||
if (vulkan_success) {
|
||||
LOGF_INFO("[VavCore] Vulkan device successfully registered with decoder");
|
||||
LOGF_INFO("[VavCore] Vulkan device registered with decoder BEFORE initialization");
|
||||
} else {
|
||||
LOGF_WARNING("[VavCore] Failed to register Vulkan device with decoder (will use CPU fallback)");
|
||||
LOGF_ERROR("[VavCore] Failed to register Vulkan device with decoder");
|
||||
LOGF_ERROR("[VavCore] GPU hardware processing requirement not met");
|
||||
player->impl->decoder.reset();
|
||||
player->impl->fileReader->CloseFile();
|
||||
return VAVCORE_ERROR_INIT_FAILED;
|
||||
}
|
||||
|
||||
// Note: We keep has_vulkan_device=true even if registration failed
|
||||
// This allows retry on next decoder recreation
|
||||
}
|
||||
#endif
|
||||
|
||||
LOGF_DEBUG("[VavCore] Initializing decoder...");
|
||||
|
||||
// Initialize decoder (now with Vulkan device already set!)
|
||||
if (!player->impl->decoder->Initialize(player->impl->metadata)) {
|
||||
LOGF_ERROR("[VavCore] Decoder initialization failed (unsupported format or hardware unavailable)");
|
||||
player->impl->decoder.reset();
|
||||
player->impl->fileReader->CloseFile();
|
||||
return VAVCORE_ERROR_DECODER_UNAVAILABLE;
|
||||
}
|
||||
|
||||
LOGF_DEBUG("[VavCore] Decoder initialized successfully!");
|
||||
|
||||
// Apply debug options to newly created decoder
|
||||
player->impl->decoder->SetDebugOptions(&player->impl->debugOptions);
|
||||
LOGF_DEBUG("[VavCore] Debug options applied to decoder");
|
||||
|
||||
Reference in New Issue
Block a user