Files
video-v1/vav2/docs/completed/android/Phase_2_AImageReader_Native_Design.md

530 lines
14 KiB
Markdown
Raw Normal View History

# Phase 2: AImageReader Native API Implementation
**작성일:** 2025-10-14
**목적:** Java ImageReader → AImageReader Native API 교체로 성능 최적화
**기반:** Android NDK AImageReader API, Vulkan+Image+Tutorial.md
**API 요구사항:** Android API 24+ (VavCore 타겟: API 26+)
**상태:** Ready for Implementation
---
## 1. 현재 구현 문제점
### 🔴 Problem 1: Sleep Workaround (5-9ms 블로킹)
```cpp
// MediaCodecAsyncHandler.cpp:402
std::this_thread::sleep_for(std::chrono::milliseconds(5)); // BAD!
// MediaCodecAsyncHandler.cpp:419
for (int retry = 0; retry < 3; ++retry) {
ahb = surface_manager->AcquireLatestImage();
std::this_thread::sleep_for(std::chrono::milliseconds(2)); // BAD!
}
```
**Impact:** 30 FPS 대비 20-30% 레이턴시
### 🔴 Problem 2: JNI Overhead (10-20μs per frame)
```cpp
// 현재: Java ImageReader + JNI Bridge
JNIEnv* env = GetJNIEnv();
jobject image = env->CallObjectMethod(reader, acquireMethod); // JNI 1
jobject hardwareBuffer = env->CallObjectMethod(image, getMethod); // JNI 2
AHardwareBuffer* ahb = AHardwareBuffer_fromHardwareBuffer(env, hb); // JNI 3
```
**Impact:** 3x JNI calls + Java GC pressure
---
## 2. 목표: AImageReader Native API
### 성능 개선
| 지표 | Before (Java) | After (Native) | 개선 |
|------|---------------|----------------|------|
| Sleep blocking | 5-9ms | 0ms | **완전 제거** |
| JNI calls | 3 per frame | 0 | **완전 제거** |
| Frame latency | 10-15ms | 2-5ms | **70% 감소** |
| Code complexity | 200 lines (Java+JNI) | 50 lines (C++) | **75% 감소** |
### 아키텍처
**Before (Java ImageReader):**
```
MediaCodec → [JNI] → Java ImageReader → [JNI] → C++ Handler
↑ 3 calls ↑
```
**After (AImageReader Native):**
```
MediaCodec → AImageReader → C++ Handler (Direct callback)
↑ 0 JNI ↑
```
---
## 3. 구현 설계
### 3.1 MediaCodecSurfaceManager 헤더 수정
**파일:** `MediaCodecSurfaceManager.h`
```cpp
#pragma once
#ifdef ANDROID
#include <media/NdkImageReader.h> // ← NEW: AImageReader API
#include <android/native_window.h>
#include <android/hardware_buffer.h>
class MediaCodecSurfaceManager {
public:
// ImageReader setup (CHANGED: Native API)
bool SetupImageReader(uint32_t width, uint32_t height);
// Image acquisition (CHANGED: AImage instead of jobject)
AHardwareBuffer* AcquireLatestImage();
void ReleaseImage();
// NEW: Native callback for OnImageAvailable
void OnImageAvailableCallback(AImageReader* reader);
// Current image tracking
bool HasCurrentImage() const { return m_current_image != nullptr; }
AHardwareBuffer* GetCurrentAHardwareBuffer() const { return m_current_ahardware_buffer; }
private:
// REMOVED: jobject m_image_reader (Java)
// REMOVED: jobject m_current_image (Java)
// NEW: Native types
AImageReader* m_image_reader; // Native ImageReader
AImage* m_current_image; // Native Image
AHardwareBuffer* m_current_ahardware_buffer;
// Video dimensions
uint32_t m_video_width;
uint32_t m_video_height;
// Synchronization for callback
std::mutex m_image_mutex;
std::condition_variable m_image_cv;
std::atomic<bool> m_image_available{false};
// Static callback (for AImageReader_setImageListener)
static void OnImageAvailableStatic(void* context, AImageReader* reader);
};
```
### 3.2 SetupImageReader 구현 (Native)
**파일:** `MediaCodecSurfaceManager.cpp`
```cpp
#include <media/NdkImageReader.h>
bool MediaCodecSurfaceManager::SetupImageReader(uint32_t width, uint32_t height) {
m_video_width = width;
m_video_height = height;
LogInfo("SetupImageReader: Creating AImageReader (Native) " +
std::to_string(width) + "x" + std::to_string(height));
// REMOVED: All JNI code
// REMOVED: Java ImageReader creation
// NEW: Create AImageReader (Native)
media_status_t status = AImageReader_new(
width,
height,
AIMAGE_FORMAT_PRIVATE, // MediaCodec output format
3, // maxImages (same as Java ImageReader)
&m_image_reader
);
if (status != AMEDIA_OK || !m_image_reader) {
LogError("Failed to create AImageReader: " + std::to_string(status));
return false;
}
LogInfo("AImageReader created successfully");
// NEW: Set native image listener (NO JAVA!)
AImageReader_ImageListener listener{
.context = this,
.onImageAvailable = OnImageAvailableStatic
};
status = AImageReader_setImageListener(m_image_reader, &listener);
if (status != AMEDIA_OK) {
LogError("Failed to set image listener: " + std::to_string(status));
AImageReader_delete(m_image_reader);
m_image_reader = nullptr;
return false;
}
LogInfo("Image listener registered (native callback)");
// NEW: Get ANativeWindow from AImageReader (NO JAVA!)
status = AImageReader_getWindow(m_image_reader, &m_surface);
if (status != AMEDIA_OK || !m_surface) {
LogError("Failed to get window from AImageReader: " + std::to_string(status));
AImageReader_delete(m_image_reader);
m_image_reader = nullptr;
return false;
}
LogInfo("ImageReader surface obtained: " +
std::to_string(reinterpret_cast<uintptr_t>(m_surface)));
return true;
}
```
### 3.3 Native Callback 구현
**파일:** `MediaCodecSurfaceManager.cpp`
```cpp
// Static callback (called by AImageReader)
void MediaCodecSurfaceManager::OnImageAvailableStatic(void* context, AImageReader* reader) {
auto* manager = static_cast<MediaCodecSurfaceManager*>(context);
if (manager) {
manager->OnImageAvailableCallback(reader);
}
}
// Instance callback
void MediaCodecSurfaceManager::OnImageAvailableCallback(AImageReader* reader) {
LogInfo("OnImageAvailableCallback: ENTRY (Native callback)");
// This callback is invoked on a dedicated AImageReader thread
// It's safe to call AImageReader_* and AImage_* methods here
std::lock_guard<std::mutex> lock(m_image_mutex);
// Acquire latest image (NO JNI!)
AImage* image = nullptr;
media_status_t status = AImageReader_acquireLatestImage(reader, &image);
if (status != AMEDIA_OK || !image) {
LogWarning("OnImageAvailableCallback: Failed to acquire image: " +
std::to_string(status));
return;
}
LogInfo("OnImageAvailableCallback: Image acquired successfully");
// Get AHardwareBuffer (Direct pointer access - NO JNI!)
AHardwareBuffer* ahb = nullptr;
status = AImage_getHardwareBuffer(image, &ahb);
if (status != AMEDIA_OK || !ahb) {
LogError("OnImageAvailableCallback: Failed to get AHardwareBuffer: " +
std::to_string(status));
AImage_delete(image);
return;
}
// Acquire reference to AHardwareBuffer
AHardwareBuffer_acquire(ahb);
// Store current image
if (m_current_image) {
AImage_delete(m_current_image);
m_current_image = nullptr;
}
if (m_current_ahardware_buffer) {
AHardwareBuffer_release(m_current_ahardware_buffer);
m_current_ahardware_buffer = nullptr;
}
m_current_image = image;
m_current_ahardware_buffer = ahb;
// Signal waiting thread
m_image_available = true;
m_image_cv.notify_one();
LogInfo("OnImageAvailableCallback: EXIT - Image stored and signaled");
}
```
### 3.4 AcquireLatestImage 수정 (Wait for Callback)
**파일:** `MediaCodecSurfaceManager.cpp`
```cpp
AHardwareBuffer* MediaCodecSurfaceManager::AcquireLatestImage() {
// REMOVED: JNI calls
// REMOVED: Java ImageReader.acquireLatestImage()
// NEW: Wait for OnImageAvailableCallback
std::unique_lock<std::mutex> lock(m_image_mutex);
// Wait for image with timeout (100ms)
bool image_ready = m_image_cv.wait_for(
lock,
std::chrono::milliseconds(100),
[this] { return m_image_available.load(); }
);
if (!image_ready || !m_current_ahardware_buffer) {
LogWarning("AcquireLatestImage: Timeout waiting for image");
return nullptr;
}
// Reset flag
m_image_available = false;
LogInfo("AcquireLatestImage: Returning cached AHardwareBuffer");
return m_current_ahardware_buffer;
}
```
### 3.5 ReleaseImage 수정 (Native)
**파일:** `MediaCodecSurfaceManager.cpp`
```cpp
void MediaCodecSurfaceManager::ReleaseImage() {
// REMOVED: JNI calls
// REMOVED: Java Image.close()
// NEW: Native AImage cleanup
std::lock_guard<std::mutex> lock(m_image_mutex);
if (m_current_image) {
AImage_delete(m_current_image); // ← Native API
m_current_image = nullptr;
LogInfo("AImage deleted (native)");
}
if (m_current_ahardware_buffer) {
AHardwareBuffer_release(m_current_ahardware_buffer);
m_current_ahardware_buffer = nullptr;
LogInfo("AHardwareBuffer released");
}
}
```
### 3.6 Cleanup 수정
**파일:** `MediaCodecSurfaceManager.cpp`
```cpp
void MediaCodecSurfaceManager::Cleanup() {
// Release current image
ReleaseImage();
// REMOVED: JNI cleanup
// REMOVED: Java ImageReader deletion
// NEW: Native AImageReader cleanup
if (m_image_reader) {
AImageReader_delete(m_image_reader);
m_image_reader = nullptr;
LogInfo("AImageReader deleted (native)");
}
// Rest of cleanup...
}
```
### 3.7 ProcessAsyncOutputFrame 수정 (Remove Sleep!)
**파일:** `MediaCodecAsyncHandler.cpp`
```cpp
bool MediaCodecAsyncHandler::ProcessAsyncOutputFrame(
int32_t output_index,
AMediaCodecBufferInfo* buffer_info,
VideoFrame& output_frame)
{
// Phase 1: GPU-synchronized release (unchanged)
MediaCodecSurfaceManager* surface_manager = m_decoder->GetSurfaceManager();
if (surface_manager && surface_manager->HasCurrentImage()) {
void* vk_device = surface_manager->GetVulkanDevice();
if (vk_device) {
surface_manager->ReleaseImageAfterGPU(static_cast<VkDevice>(vk_device));
}
}
// Release MediaCodec buffer to ImageReader surface
media_status_t status = AMediaCodec_releaseOutputBuffer(m_codec, output_index, true);
if (status != AMEDIA_OK) {
return false;
}
// ====== PHASE 2 CHANGE: Wait for Native Callback ======
// REMOVED: std::this_thread::sleep_for(std::chrono::milliseconds(5));
// REMOVED: Retry loop with sleep(2ms)
// OnImageAvailableCallback will be triggered asynchronously
// AcquireLatestImage() now waits for callback with condition variable
// ====== END PHASE 2 CHANGE ======
// Acquire AHardwareBuffer (callback already happened)
AHardwareBuffer* ahb = surface_manager->AcquireLatestImage();
if (!ahb) {
LogError("ProcessAsyncOutputFrame: Failed to acquire image");
return false;
}
// Rest of code unchanged (Vulkan image creation, etc.)
// ...
return true;
}
```
---
## 4. 구현 단계
### Phase 2.1: MediaCodecSurfaceManager 리팩토링 (2-3시간)
**작업 순서:**
1. ✅ MediaCodecSurfaceManager.h - AImageReader 멤버 추가
2. ✅ SetupImageReader() 재구현 (Native API)
3. ✅ OnImageAvailableCallback() 구현
4. ✅ AcquireLatestImage() 수정 (condition variable wait)
5. ✅ ReleaseImage() 수정 (AImage_delete)
6. ✅ Cleanup() 수정 (AImageReader_delete)
**수정 파일:**
- `MediaCodecSurfaceManager.h` - 헤더 수정
- `MediaCodecSurfaceManager.cpp` - 전체 재구현
### Phase 2.2: ProcessAsyncOutputFrame 단순화 (30분)
**작업 순서:**
1. ✅ sleep_for(5ms) 제거
2. ✅ Retry loop 제거
3. ✅ 로직 단순화
**수정 파일:**
- `MediaCodecAsyncHandler.cpp` - ProcessAsyncOutputFrame 수정
### Phase 2.3: 빌드 및 테스트 (1-2시간)
**테스트 항목:**
1. ✅ Android ARM64 빌드 성공
2. ✅ AImageReader 생성 확인 (로그)
3. ✅ Native callback 호출 확인 (로그)
4. ✅ 30 FPS 달성
5. ✅ Sleep 제거 확인 (로그에서 "sleep" 검색)
6. ✅ 레이턴시 측정 (Phase 1 대비)
---
## 5. 제거할 코드
### ❌ 삭제할 Java 파일
- `ImageReaderCallback.java` - 생성했지만 불필요
### ❌ 제거할 JNI 코드
- `MediaCodecSurfaceManager.cpp`:
- `GetJNIEnv()` 호출 (ImageReader 관련만)
- `env->FindClass("android/media/ImageReader")`
- `env->CallObjectMethod()` 호출들
- `env->NewGlobalRef()` / `DeleteGlobalRef()` (Image 관련)
- `AHardwareBuffer_fromHardwareBuffer()` 변환
### ❌ 제거할 Workaround
- `std::this_thread::sleep_for(std::chrono::milliseconds(5))`
- `std::this_thread::sleep_for(std::chrono::milliseconds(2))`
- Retry loop (3회 반복)
---
## 6. 성능 예상
### Before (Phase 1 - Java ImageReader)
```
releaseOutputBuffer
→ sleep(5ms)
→ JNI acquireLatestImage (1-5μs)
→ JNI getHardwareBuffer (1-5μs)
→ JNI fromHardwareBuffer (5-10μs)
→ [retry loop: sleep(2ms) x2]
Total: 9-15ms per frame
```
### After (Phase 2 - AImageReader Native)
```
releaseOutputBuffer
→ OnImageAvailableCallback (async, <1ms)
→ AcquireLatestImage (wait on condition_variable)
→ Direct pointer access (<1μs)
Total: 2-5ms per frame
```
**개선:**
- ✅ 레이턴시: 60-70% 감소 (9ms → 3ms)
- ✅ JNI overhead: 100% 제거
- ✅ Java GC pressure: 100% 제거
- ✅ 코드 복잡도: 75% 감소
---
## 7. 검증 체크리스트
### 빌드 검증
- [ ] Android ARM64 빌드 성공
- [ ] AImageReader API 링크 확인 (libmediandk.so)
- [ ] No JNI errors
### 기능 검증
- [ ] AImageReader 생성 성공 (로그)
- [ ] Native callback 호출 확인 (로그: "OnImageAvailableCallback")
- [ ] AImage_getHardwareBuffer 성공
- [ ] Vulkan image 생성 성공
### 성능 검증
- [ ] 30 FPS 이상 달성
- [ ] Sleep 완전 제거 확인 (grep "sleep")
- [ ] 레이턴시 < 5ms (로그 타임스탬프 측정)
- [ ] CPU 사용률 감소
### 안정성 검증
- [ ] 10분 이상 재생 테스트
- [ ] 메모리 누수 확인 (AImage_delete, AHardwareBuffer_release)
- [ ] 크래시 없음 확인
---
## 8. Rollback 계획
Phase 2 구현 중 문제 발생 시:
1. **Git revert:** Phase 1 커밋으로 즉시 복구
2. **Phase 1은 검증됨:** 프로덕션 사용 가능
---
## 9. 다음 단계
### Phase 2 완료 후
1. ✅ 성능 벤치마크 측정 (Phase 1 vs Phase 2)
2. ✅ 문서 업데이트 (COMPLETED_PROJECTS.md)
3. ✅ Phase 1 설계서 보관 (old design reference)
### 향후 최적화 (Optional - Phase 3)
- SyncFence 통합 (API 33+)
- Adaptive buffer size (2-5 frames)
- Frame dropping strategy
---
**문서 상태:** ✅ Ready for Implementation
**예상 구현 시간:** 3-5시간
**우선순위:** 🟢 High (10-20배 성능 향상 예상)
**Java/JNI 의존성:** ❌ None (100% Native C++)