# 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 // ← NEW: AImageReader API #include #include 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 m_image_available{false}; // Static callback (for AImageReader_setImageListener) static void OnImageAvailableStatic(void* context, AImageReader* reader); }; ``` ### 3.2 SetupImageReader 구현 (Native) **파일:** `MediaCodecSurfaceManager.cpp` ```cpp #include 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(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(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 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 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 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(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++)