14 KiB
14 KiB
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 블로킹)
// 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)
// 현재: 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
#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
#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
// 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
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
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
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
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시간)
작업 순서:
- ✅ MediaCodecSurfaceManager.h - AImageReader 멤버 추가
- ✅ SetupImageReader() 재구현 (Native API)
- ✅ OnImageAvailableCallback() 구현
- ✅ AcquireLatestImage() 수정 (condition variable wait)
- ✅ ReleaseImage() 수정 (AImage_delete)
- ✅ Cleanup() 수정 (AImageReader_delete)
수정 파일:
MediaCodecSurfaceManager.h- 헤더 수정MediaCodecSurfaceManager.cpp- 전체 재구현
Phase 2.2: ProcessAsyncOutputFrame 단순화 (30분)
작업 순서:
- ✅ sleep_for(5ms) 제거
- ✅ Retry loop 제거
- ✅ 로직 단순화
수정 파일:
MediaCodecAsyncHandler.cpp- ProcessAsyncOutputFrame 수정
Phase 2.3: 빌드 및 테스트 (1-2시간)
테스트 항목:
- ✅ Android ARM64 빌드 성공
- ✅ AImageReader 생성 확인 (로그)
- ✅ Native callback 호출 확인 (로그)
- ✅ 30 FPS 달성
- ✅ Sleep 제거 확인 (로그에서 "sleep" 검색)
- ✅ 레이턴시 측정 (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 구현 중 문제 발생 시:
- Git revert: Phase 1 커밋으로 즉시 복구
- Phase 1은 검증됨: 프로덕션 사용 가능
9. 다음 단계
Phase 2 완료 후
- ✅ 성능 벤치마크 측정 (Phase 1 vs Phase 2)
- ✅ 문서 업데이트 (COMPLETED_PROJECTS.md)
- ✅ 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++)