WIP
This commit is contained in:
507
vav2/docs/working/MediaCodec_AV1_Vulkan_Integration.md
Normal file
507
vav2/docs/working/MediaCodec_AV1_Vulkan_Integration.md
Normal file
@@ -0,0 +1,507 @@
|
||||
> MediaCodec AV1 을 이용하여 Vulkan 1.1 의 텍스처로 디코딩하는 함수를 작성하고자 한다.
|
||||
webm 동영상 파일로부터 frame packet 을 파싱이 완료된 상태부터 MediaCodec 에 frame packet 을 제공하고, 그 결과로 Vulkan texture 를 반환받으려고
|
||||
한다.
|
||||
이를 위한 전체 아키텍처 설계서 문서를 상세하게 작성해줘. 작성된 설계서는 md 로 디테일한 구현 예시도 함께 작성해줘.
|
||||
|
||||
✦ 네, 알겠습니다. MediaCodec AV1 디코딩 결과를 Vulkan 1.1 텍스처로 직접 연결하는 고성능 아키텍처 설계 문서를 상세한 구현 예시와 함께 Markdown
|
||||
형식으로 작성해 드리겠습니다.
|
||||
|
||||
이 설계는 CPU-GPU 간의 불필요한 메모리 복사를 제거하는 '제로-카피(Zero-Copy)'를 목표로 하며, 안드로이드의 AHardwareBuffer와 Vulkan 확장
|
||||
기능을 핵심으로 사용합니다.
|
||||
|
||||
---
|
||||
|
||||
MediaCodec과 Vulkan을 이용한 AV1 디코딩 아키텍처 설계
|
||||
|
||||
1. 개요
|
||||
|
||||
본 문서는 안드로이드 MediaCodec을 사용하여 WebM 컨테이너로부터 파싱된 AV1 비디오 프레임을 디코딩하고, 그 결과를 Vulkan 1.1 텍스처(VkImage)로
|
||||
직접 변환하는 시스템의 아키텍처를 정의합니다.
|
||||
|
||||
핵심 목표는 디코딩된 프레임을 CPU 메모리로 복사하지 않고 GPU 메모리 내에서 처리하는 제로-카피(Zero-Copy) 파이프라인을 구축하여 최고의 렌더링
|
||||
성능을 달성하는 것입니다. 이를 위해 MediaCodec의 출력으로 AHardwareBuffer를 사용하고, 이 버퍼를 Vulkan에서 직접 참조(Import)하여 텍스처로
|
||||
사용하는 방법을 제안합니다.
|
||||
|
||||
2. 핵심 목표
|
||||
|
||||
* 고성능 제로-카피: 디코딩된 YUV 프레임을 CPU로 다운로드 후 다시 GPU로 업로드하는 과정을 생략하여 성능을 극대화합니다.
|
||||
* 효율적인 색상 변환: 하드웨어 디코더가 출력하는 YUV(NV12) 포맷을 Vulkan 셰이더에서 실시간으로 RGB로 변환하여 처리합니다.
|
||||
* 캡슐화 및 재사용성: 디코딩 및 Vulkan 텍스처 변환 로직을 하나의 클래스로 캡슐화하여 애플리케이션의 다른 부분과 분리하고 재사용성을 높입니다.
|
||||
* 현대적 API 활용: Android NDK의 AMediaCodec과 Vulkan의 외부 메모리 확장(VK_KHR_android_external_memory_android_hardware_buffer)을
|
||||
사용합니다.
|
||||
|
||||
3. 시스템 아키텍처
|
||||
|
||||
전체 시스템은 다음과 같은 구성 요소로 이루어집니다.
|
||||
|
||||
1. WebM Parser (외부): .webm 파일에서 AV1 비디오 프레임 데이터(Frame Packet)를 추출합니다. (설계 범위 밖)
|
||||
2. JNI Bridge: Java/Kotlin의 MediaCodec 설정 및 ImageReader 콜백과 네이티브 C++ 코드를 연결합니다.
|
||||
3. Video Decoder Module (C++):
|
||||
* `VulkanAV1Decoder` 클래스: 디코딩 프로세스 전체를 관리하는 핵심 클래스입니다.
|
||||
* `MediaCodec` (via JNI): 실제 AV1 디코딩을 수행하는 안드로이드 컴포넌트. 하드웨어 가속을 사용합니다.
|
||||
* `ImageReader` (via JNI): MediaCodec의 디코딩 출력 Surface를 제공하고, 출력된 프레임을 AHardwareBuffer 형태로 가져오는 역할을 합니다.
|
||||
4. Vulkan Renderer (C++):
|
||||
* VulkanAV1Decoder로부터 AHardwareBuffer를 받아 Vulkan VkImage로 임포트합니다.
|
||||
* YCbCr to RGB 변환을 수행하는 픽셀 셰이더를 이용해 최종 이미지를 화면에 렌더링합니다.
|
||||
|
||||
4. 주요 기술 및 API
|
||||
|
||||
4.1. Android
|
||||
|
||||
* `MediaCodec` (Java/Kotlin & NDK): 비디오 디코딩을 위한 핵심 API. MIMETYPE_VIDEO_AV1으로 설정하여 AV1 코덱을 사용합니다.
|
||||
* `ImageReader` (Java/Kotlin): MediaCodec의 출력 Surface를 생성하고, 디코딩된 프레임 버퍼에 접근할 수 있게 해주는 클래스.
|
||||
ImageFormat.PRIVATE과 USAGE_GPU_SAMPLED_IMAGE 플래그를 사용하여 AHardwareBuffer를 생성하도록 설정합니다.
|
||||
* `AHardwareBuffer` (NDK): 여러 프로세스나 API(ex: MediaCodec, Vulkan, OpenGL)간에 그래픽 버퍼를 공유하기 위한 표준 인터페이스. 제로-카피의
|
||||
핵심 요소입니다.
|
||||
|
||||
4.2. Vulkan
|
||||
|
||||
* `VK_KHR_android_external_memory_android_hardware_buffer`: AHardwareBuffer를 VkDeviceMemory로 임포트할 수 있게 해주는 필수 Vulkan
|
||||
확장입니다.
|
||||
* `VK_KHR_sampler_ycbcr_conversion`: MediaCodec이 주로 출력하는 NV12와 같은 YCbCr 포맷의 이미지를 셰이더에서 직접 샘플링하고 RGB로 자동
|
||||
변환할 수 있게 해주는 확장입니다.
|
||||
* `VkExternalMemoryImageCreateInfo`: 외부 메모리를 바인딩할 VkImage를 생성할 때 사용됩니다.
|
||||
* `VkImportAndroidHardwareBufferInfoANDROID`: AHardwareBuffer를 VkDeviceMemory로 임포트할 때 버퍼 정보를 전달하는 구조체입니다.
|
||||
|
||||
5. 상세 설계 및 구현 예시
|
||||
|
||||
5.1. VulkanAV1Decoder 클래스 설계 (C++)
|
||||
|
||||
디코딩 로직을 캡슐화할 C++ 클래스입니다.
|
||||
|
||||
1 // VulkanAV1Decoder.h
|
||||
2
|
||||
3 #include <vulkan/vulkan.h>
|
||||
4 #include <android/hardware_buffer.h>
|
||||
5 #include <android/native_window.h>
|
||||
6
|
||||
7 // 디코딩된 Vulkan 텍스처 정보를 담는 구조체
|
||||
8 struct DecodedVulkanTexture {
|
||||
9 VkImage image = VK_NULL_HANDLE;
|
||||
10 VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
11 VkImageView imageView = VK_NULL_HANDLE;
|
||||
12 VkSampler sampler = VK_NULL_HANDLE;
|
||||
13 VkSamplerYcbcrConversion ycbcrConversion = VK_NULL_HANDLE;
|
||||
14
|
||||
15 // 리소스 해제를 위한 정보
|
||||
16 VkDevice logicalDevice = VK_NULL_HANDLE;
|
||||
17
|
||||
18 void cleanup();
|
||||
19 };
|
||||
20
|
||||
21 class VulkanAV1Decoder {
|
||||
22 public:
|
||||
23 VulkanAV1Decoder(VkDevice device, VkPhysicalDevice physicalDevice);
|
||||
24 ~VulkanAV1Decoder();
|
||||
25
|
||||
26 // 초기화: MediaCodec, ImageReader 등을 JNI를 통해 설정
|
||||
27 bool initialize(JNIEnv* env, int width, int height);
|
||||
28
|
||||
29 // 디코딩: WebM에서 파싱된 프레임 데이터를 MediaCodec에 전달
|
||||
30 void feedFrame(JNIEnv* env, uint8_t* frameData, size_t dataSize);
|
||||
31
|
||||
32 // 결과 확인: 디코딩된 최신 프레임이 있다면 AHardwareBuffer 핸들을 반환
|
||||
33 AHardwareBuffer* acquireLatestFrame(JNIEnv* env);
|
||||
34
|
||||
35 // AHardwareBuffer를 Vulkan 텍스처로 임포트
|
||||
36 bool importHardwareBufferToVulkan(AHardwareBuffer* buffer, DecodedVulkanTexture& outTexture);
|
||||
37
|
||||
38 // 리소스 해제
|
||||
39 void cleanup(JNIEnv* env);
|
||||
40
|
||||
41 private:
|
||||
42 // Vulkan 관련 객체
|
||||
43 VkDevice m_vkDevice;
|
||||
44 VkPhysicalDevice m_vkPhysicalDevice;
|
||||
45
|
||||
46 // JNI 관련 객체 (Java 측 Decoder 객체)
|
||||
47 jobject m_jDecoderInstance;
|
||||
48 jclass m_jDecoderClass;
|
||||
49
|
||||
50 // JNI 메서드 ID
|
||||
51 jmethodID m_jFeedFrameMethod;
|
||||
52 jmethodID m_jAcquireLatestFrameMethod;
|
||||
53 jmethodID m_jCleanupMethod;
|
||||
54
|
||||
55 // Vulkan 확장 함수 포인터
|
||||
56 PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID;
|
||||
57 // ... 기타 필요한 함수 포인터
|
||||
58 };
|
||||
|
||||
5.2. Java/Kotlin 측 Decoder 설정
|
||||
|
||||
C++에서 호출할 MediaCodec과 ImageReader를 설정하는 Java 또는 Kotlin 클래스입니다.
|
||||
|
||||
1 // VideoDecoder.kt
|
||||
2 import android.media.*
|
||||
3 import android.os.Handler
|
||||
4 import android.os.HandlerThread
|
||||
5 import android.view.Surface
|
||||
6 import java.util.concurrent.ConcurrentLinkedQueue
|
||||
7
|
||||
8 class VideoDecoder(
|
||||
9 private val width: Int,
|
||||
10 private val height: Int
|
||||
11 ) {
|
||||
12 private lateinit var mediaCodec: MediaCodec
|
||||
13 private lateinit var imageReader: ImageReader
|
||||
14 private val handlerThread = HandlerThread("VideoDecoderThread")
|
||||
15 private val handler: Handler
|
||||
16
|
||||
17 // C++에서 접근할 HardwareBuffer 큐
|
||||
18 private val hardwareBufferQueue = ConcurrentLinkedQueue<HardwareBuffer>()
|
||||
19
|
||||
20 init {
|
||||
21 handlerThread.start()
|
||||
22 handler = Handler(handlerThread.looper)
|
||||
23 }
|
||||
24
|
||||
25 fun initialize(): Surface {
|
||||
26 // 1. ImageReader 생성: MediaCodec의 출력을 받을 Surface 제공자
|
||||
27 imageReader = ImageReader.newInstance(
|
||||
28 width, height,
|
||||
29 ImageFormat.PRIVATE, // AHardwareBuffer를 사용하기 위한 포맷
|
||||
30 2 // 버퍼 2개
|
||||
31 ).apply {
|
||||
32 // GPU에서 샘플링 가능한 버퍼로 사용 설정
|
||||
33 this.usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
|
||||
34 }
|
||||
35
|
||||
36 // 2. ImageReader에 리스너 설정
|
||||
37 imageReader.setOnImageAvailableListener({ reader ->
|
||||
38 val image = reader.acquireNextImage() ?: return@setOnImageAvailableListener
|
||||
39 val hardwareBuffer = image.hardwareBuffer ?: return@setOnImageAvailableListener
|
||||
40
|
||||
41 // 이전 버퍼는 닫고 새로운 버퍼를 큐에 추가
|
||||
42 hardwareBufferQueue.poll()?.close()
|
||||
43 hardwareBufferQueue.add(hardwareBuffer)
|
||||
44
|
||||
45 image.close()
|
||||
46 }, handler)
|
||||
47
|
||||
48 // 3. MediaFormat 설정
|
||||
49 val format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AV1, width, height)
|
||||
50 // YUV 4:2:0 8-bit 포맷을 기대
|
||||
51 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)
|
||||
52
|
||||
53 // 4. MediaCodec 생성 및 설정
|
||||
54 mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AV1)
|
||||
55 mediaCodec.configure(format, imageReader.surface, null, 0)
|
||||
56 mediaCodec.start()
|
||||
57
|
||||
58 return imageReader.surface
|
||||
59 }
|
||||
60
|
||||
61 // C++에서 호출할 함수 (JNI)
|
||||
62 fun feedFrame(frameData: ByteArray) {
|
||||
63 val inputBufferIndex = mediaCodec.dequeueInputBuffer(-1)
|
||||
64 if (inputBufferIndex >= 0) {
|
||||
65 val inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex)
|
||||
66 inputBuffer?.clear()
|
||||
67 inputBuffer?.put(frameData)
|
||||
68 mediaCodec.queueInputBuffer(inputBufferIndex, 0, frameData.size, System.nanoTime() / 1000, 0)
|
||||
69 }
|
||||
70 }
|
||||
71
|
||||
72 // C++에서 호출할 함수 (JNI)
|
||||
73 fun acquireLatestFrame(): HardwareBuffer? {
|
||||
74 // 큐에서 가장 최신 버퍼를 가져옴 (소유권 이전)
|
||||
75 return hardwareBufferQueue.poll()
|
||||
76 }
|
||||
77
|
||||
78 fun cleanup() {
|
||||
79 handlerThread.quitSafely()
|
||||
80 mediaCodec.stop()
|
||||
81 mediaCodec.release()
|
||||
82 imageReader.close()
|
||||
83 hardwareBufferQueue.forEach { it.close() }
|
||||
84 }
|
||||
85 }
|
||||
|
||||
5.3. JNI 브릿지 구현 (C++)
|
||||
|
||||
Java/Kotlin과 C++를 연결하는 부분입니다.
|
||||
|
||||
1 // jni_bridge.cpp
|
||||
2
|
||||
3 // ... JNIEnv*, jobject, jclass 등 포함
|
||||
4
|
||||
5 extern "C" JNIEXPORT jlong JNICALL
|
||||
6 Java_com_example_vav2player_VulkanBridge_nativeCreateDecoder(JNIEnv* env, jobject /* this */, jlong vkDevice, jlong
|
||||
vkPhysicalDevice) {
|
||||
7 // VulkanAV1Decoder 인스턴스 생성
|
||||
8 VulkanAV1Decoder* decoder = new VulkanAV1Decoder(
|
||||
9 reinterpret_cast<VkDevice>(vkDevice),
|
||||
10 reinterpret_cast<VkPhysicalDevice>(vkPhysicalDevice)
|
||||
11 );
|
||||
12 return reinterpret_cast<jlong>(decoder);
|
||||
13 }
|
||||
14
|
||||
15 extern "C" JNIEXPORT void JNICALL
|
||||
16 Java_com_example_vav2player_VulkanBridge_nativeInitializeDecoder(JNIEnv* env, jobject /* this */, jlong decoderPtr, jint width,
|
||||
jint height) {
|
||||
17 VulkanAV1Decoder* decoder = reinterpret_cast<VulkanAV1Decoder*>(decoderPtr);
|
||||
18 decoder->initialize(env, width, height);
|
||||
19 }
|
||||
20
|
||||
21 // ... feedFrame, acquireLatestFrame 등의 JNI 함수 구현 ...
|
||||
22
|
||||
23 // acquireLatestFrame 예시
|
||||
24 extern "C" JNIEXPORT jlong JNICALL
|
||||
25 Java_com_example_vav2player_VulkanBridge_nativeAcquireLatestHardwareBuffer(JNIEnv* env, jobject, jlong decoderPtr) {
|
||||
26 VulkanAV1Decoder* decoder = reinterpret_cast<VulkanAV1Decoder*>(decoderPtr);
|
||||
27 // Java의 acquireLatestFrame() 호출 후 HardwareBuffer 객체(jobject)를 받음
|
||||
28 jobject hardwareBufferJObject = decoder->acquireLatestFrameAsJObject(env);
|
||||
29 if (hardwareBufferJObject == nullptr) {
|
||||
30 return 0;
|
||||
31 }
|
||||
32 // AHardwareBuffer*로 변환
|
||||
33 AHardwareBuffer* ahb = AHardwareBuffer_fromHardwareBuffer(env, hardwareBufferJObject);
|
||||
34 // Java 객체 로컬 참조 해제
|
||||
35 env->DeleteLocalRef(hardwareBufferJObject);
|
||||
36 return reinterpret_cast<jlong>(ahb);
|
||||
37 }
|
||||
|
||||
5.4. AHardwareBuffer를 Vulkan 텍스처로 임포트 (C++)
|
||||
|
||||
VulkanAV1Decoder::importHardwareBufferToVulkan 함수의 상세 구현입니다.
|
||||
|
||||
1 // VulkanAV1Decoder.cpp
|
||||
2
|
||||
3 bool VulkanAV1Decoder::importHardwareBufferToVulkan(AHardwareBuffer* buffer, DecodedVulkanTexture& outTexture) {
|
||||
4 // 1. 필요한 Vulkan 확장 함수 포인터 가져오기 (초기화 시 1회만 수행)
|
||||
5 if (!vkGetAndroidHardwareBufferPropertiesANDROID) {
|
||||
6 vkGetAndroidHardwareBufferPropertiesANDROID = (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)vkGetDeviceProcAddr
|
||||
(m_vkDevice, "vkGetAndroidHardwareBufferPropertiesANDROID");
|
||||
7 if (!vkGetAndroidHardwareBufferPropertiesANDROID) {
|
||||
8 // 오류 처리: 확장 함수를 찾을 수 없음
|
||||
9 return false;
|
||||
10 }
|
||||
11 }
|
||||
12
|
||||
13 // 2. AHardwareBuffer 속성 가져오기
|
||||
14 VkAndroidHardwareBufferPropertiesANDROID bufferProperties = { .sType =
|
||||
VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID };
|
||||
15 VkAndroidHardwareBufferFormatPropertiesANDROID formatProperties = { .sType =
|
||||
VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID };
|
||||
16 bufferProperties.pNext = &formatProperties;
|
||||
17
|
||||
18 VkResult result = vkGetAndroidHardwareBufferPropertiesANDROID(m_vkDevice, buffer, &bufferProperties);
|
||||
19 if (result != VK_SUCCESS) {
|
||||
20 // 오류 처리
|
||||
21 return false;
|
||||
22 }
|
||||
23
|
||||
24 // 3. 적합한 메모리 타입 인덱스 찾기
|
||||
25 VkPhysicalDeviceMemoryProperties memProperties;
|
||||
26 vkGetPhysicalDeviceMemoryProperties(m_vkPhysicalDevice, &memProperties);
|
||||
27 uint32_t memoryTypeIndex = 0;
|
||||
28 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
29 if ((bufferProperties.memoryTypeBits & (1 << i)) &&
|
||||
30 (memProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
|
||||
31 memoryTypeIndex = i;
|
||||
32 break;
|
||||
33 }
|
||||
34 }
|
||||
35
|
||||
36 // 4. Vulkan 메모리 할당 정보 설정 (AHB 임포트)
|
||||
37 VkImportAndroidHardwareBufferInfoANDROID hardwareBufferInfo = { .sType =
|
||||
VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID };
|
||||
38 hardwareBufferInfo.buffer = buffer;
|
||||
39
|
||||
40 VkMemoryAllocateInfo memoryAllocateInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
||||
41 memoryAllocateInfo.pNext = &hardwareBufferInfo;
|
||||
42 memoryAllocateInfo.allocationSize = bufferProperties.allocationSize;
|
||||
43 memoryAllocateInfo.memoryTypeIndex = memoryTypeIndex;
|
||||
44
|
||||
45 result = vkAllocateMemory(m_vkDevice, &memoryAllocateInfo, nullptr, &outTexture.memory);
|
||||
46 if (result != VK_SUCCESS) {
|
||||
47 // 오류 처리
|
||||
48 return false;
|
||||
49 }
|
||||
50
|
||||
51 // 5. YCbCr 변환 객체 및 외부 메모리용 VkImage 생성
|
||||
52 VkExternalFormatANDROID externalFormat = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID };
|
||||
53 externalFormat.externalFormat = formatProperties.externalFormat;
|
||||
54
|
||||
55 VkExternalMemoryImageCreateInfo externalImageCreateInfo = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO };
|
||||
56 externalImageCreateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
|
||||
57
|
||||
58 VkImageCreateInfo imageCreateInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
||||
59 imageCreateInfo.pNext = &externalImageCreateInfo;
|
||||
60 externalImageCreateInfo.pNext = &externalFormat; // 체인 연결
|
||||
61 imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
62 imageCreateInfo.extent = { /* width */, /* height */, 1 };
|
||||
63 imageCreateInfo.mipLevels = 1;
|
||||
64 imageCreateInfo.arrayLayers = 1;
|
||||
65 imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
66 imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
67 imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
68 imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
69 imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
70 // format은 VK_FORMAT_UNDEFINED로 설정해야 함 (externalFormat 사용 시)
|
||||
71 imageCreateInfo.format = VK_FORMAT_UNDEFINED;
|
||||
72
|
||||
73 result = vkCreateImage(m_vkDevice, &imageCreateInfo, nullptr, &outTexture.image);
|
||||
74 if (result != VK_SUCCESS) { /* ... */ }
|
||||
75
|
||||
76 // 6. 이미지에 메모리 바인딩
|
||||
77 result = vkBindImageMemory(m_vkDevice, outTexture.image, outTexture.memory, 0);
|
||||
78 if (result != VK_SUCCESS) { /* ... */ }
|
||||
79
|
||||
80 // 7. Sampler YCbCr Conversion 생성
|
||||
81 VkSamplerYcbcrConversionCreateInfo ycbcrConversionCreateInfo = { .sType =
|
||||
VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO };
|
||||
82 ycbcrConversionCreateInfo.pNext = &externalFormat; // externalFormat을 다시 사용
|
||||
83 ycbcrConversionCreateInfo.format = formatProperties.format; // vkGetAndroidHardwareBufferPropertiesANDROID에서 얻은 포맷
|
||||
84 ycbcrConversionCreateInfo.ycbcrModel = formatProperties.suggestedYcbcrModel;
|
||||
85 ycbcrConversionCreateInfo.ycbcrRange = formatProperties.suggestedYcbcrRange;
|
||||
86 ycbcrConversionCreateInfo.components = formatProperties.samplerYcbcrConversionComponents;
|
||||
87 which
|
||||
88 ycbcrConversionCreateInfo.xChromaOffset = formatProperties.suggestedXChromaOffset;
|
||||
89 ycbcrConversionCreateInfo.yChromaOffset = formatProperties.suggestedYChromaOffset;
|
||||
90 ycbcrConversionCreateInfo.chromaFilter = VK_FILTER_LINEAR;
|
||||
91 ycbcrConversionCreateInfo.forceExplicitReconstruction = VK_FALSE;
|
||||
92
|
||||
93 result = vkCreateSamplerYcbcrConversion(m_vkDevice, &ycbcrConversionCreateInfo, nullptr, &outTexture.ycbcrConversion);
|
||||
94 if (result != VK_SUCCESS) { /* ... */ }
|
||||
95
|
||||
96 // 8. ImageView 및 Sampler 생성
|
||||
97 VkSamplerYcbcrConversionInfo samplerConversionInfo = { .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO };
|
||||
98 samplerConversionInfo.conversion = outTexture.ycbcrConversion;
|
||||
99
|
||||
100 VkImageViewCreateInfo imageViewCreateInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
|
||||
101 imageViewCreateInfo.pNext = &samplerConversionInfo;
|
||||
102 // ... (image, format, subresourceRange 등 설정)
|
||||
103 result = vkCreateImageView(m_vkDevice, &imageViewCreateInfo, nullptr, &outTexture.imageView);
|
||||
104
|
||||
105 VkSamplerCreateInfo samplerCreateInfo = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
|
||||
106 samplerCreateInfo.pNext = &samplerConversionInfo;
|
||||
107 // ... (magFilter, minFilter, addressMode 등 설정)
|
||||
108 result = vkCreateSampler(m_vkDevice, &samplerCreateInfo, nullptr, &outTexture.sampler);
|
||||
109
|
||||
110 outTexture.logicalDevice = m_vkDevice;
|
||||
111 return true;
|
||||
112 }
|
||||
|
||||
5.5. 렌더링 (GLSL 프래그먼트 셰이더)
|
||||
|
||||
VK_KHR_sampler_ycbcr_conversion 확장을 사용하면 YCbCr 텍스처를 일반 sampler2D처럼 사용할 수 있습니다. Vulkan이 셰이더 내에서 texture() 함수
|
||||
호출 시 자동으로 RGB 변환을 수행합니다.
|
||||
|
||||
1 #version 450
|
||||
2
|
||||
3 // YCbCr 샘플러를 일반 2D 샘플러로 선언
|
||||
4 layout(binding = 1) uniform sampler2D ycbcrSampler;
|
||||
5
|
||||
6 layout(location = 0) in vec2 inTexCoord;
|
||||
7 layout(location = 0) out vec4 outColor;
|
||||
8
|
||||
9 void main() {
|
||||
10 // texture() 함수가 YCbCr -> RGB 변환을 자동으로 수행
|
||||
11 outColor = texture(ycbcrSampler, inTexCoord);
|
||||
12 }
|
||||
|
||||
6. 결론
|
||||
|
||||
본 설계는 ImageReader와 AHardwareBuffer를 중간 다리로 사용하여 MediaCodec과 Vulkan을 효율적으로 연동하는 현대적인 접근 방식을 제시합니다. 이
|
||||
아키텍처를 통해 AV1 비디오를 디코딩하고 Vulkan 씬에 렌더링하는 과정에서 CPU 부하와 메모리 대역폭 사용을 최소화하여, 고해상도 비디오 재생
|
||||
애플리케이션에서도 부드러운 사용자 경험을 제공할 수 있습니다.
|
||||
|
||||
초기 설정은 다소 복잡하지만, 한번 구축되면 높은 성능과 확장성을 보장하는 견고한 기반이 될 것입니다.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
● 설계 문서와 현재 구현 비교 분석 완료
|
||||
|
||||
IMPLEMENTATION_COMPARISON_ANALYSIS.md 문서를 작성했습니다.
|
||||
|
||||
🔴 핵심 발견사항 (CRITICAL)
|
||||
|
||||
현재 구현이 설계 문서와 가장 중요한 부분에서 차이가 있습니다:
|
||||
|
||||
설계 문서 (올바른 방법):
|
||||
- VK_KHR_sampler_ycbcr_conversion 확장 사용
|
||||
- VkSamplerYcbcrConversion 객체로 NV12 → RGB 자동 변환
|
||||
- 단일 ImageView + YCbCr Sampler 사용
|
||||
- 쉐이더에서 texture() 호출 시 하드웨어가 자동으로 YUV→RGB 변환
|
||||
|
||||
현재 구현 (잘못된 방법):
|
||||
- ❌ VkSamplerYcbcrConversion 사용 안 함
|
||||
- ❌ Y 평면과 UV 평면을 별도의 ImageView로 분리
|
||||
- ❌ 일반 VkSampler 사용
|
||||
- ❌ 쉐이더에서 수동으로 YUV→RGB 변환
|
||||
|
||||
💥 VK_ERROR_DEVICE_LOST 원인 분석
|
||||
|
||||
추정 원인:
|
||||
1. 수동으로 NV12를 두 개의 plane view로 분리하는 방식은 메모리 레이아웃 가정이 잘못될 수 있음
|
||||
2. VkImageView 생성 시 VK_IMAGE_ASPECT_PLANE_0_BIT, VK_IMAGE_ASPECT_PLANE_1_BIT 사용이 AHardwareBuffer와 호환되지 않을 수 있음
|
||||
3. ImageView 재사용 시 descriptor set 손상 또는 invalid handle 참조
|
||||
|
||||
증거:
|
||||
- 프레임 1-5: 정상 작동 (imageIndex 0, 1, 2, 3 순차 사용)
|
||||
- 프레임 6: imageIndex=0 재사용 시 DEVICE_LOST
|
||||
- 패턴: 스왑체인 이미지 재사용 시 실패
|
||||
|
||||
✅ 해결 방법 (우선순위 1)
|
||||
|
||||
vulkan_renderer.cpp:2493-2597 수정 필요:
|
||||
|
||||
// 1. VkSamplerYcbcrConversion 생성 (AHardwareBuffer format properties 사용)
|
||||
VkSamplerYcbcrConversionCreateInfo ycbcrInfo = {};
|
||||
ycbcrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
|
||||
ycbcrInfo.format = ahb_format_props.format;
|
||||
ycbcrInfo.ycbcrModel = ahb_format_props.suggestedYcbcrModel;
|
||||
ycbcrInfo.ycbcrRange = ahb_format_props.suggestedYcbcrRange;
|
||||
ycbcrInfo.components = ahb_format_props.samplerYcbcrConversionComponents;
|
||||
ycbcrInfo.xChromaOffset = ahb_format_props.suggestedXChromaOffset;
|
||||
ycbcrInfo.yChromaOffset = ahb_format_props.suggestedYChromaOffset;
|
||||
ycbcrInfo.chromaFilter = VK_FILTER_LINEAR;
|
||||
|
||||
VkSamplerYcbcrConversion ycbcrConversion;
|
||||
vkCreateSamplerYcbcrConversion(m_device, &ycbcrInfo, nullptr, &ycbcrConversion);
|
||||
|
||||
// 2. 단일 ImageView 생성 (YCbCr conversion 연결)
|
||||
VkSamplerYcbcrConversionInfo conversionInfo = {};
|
||||
conversionInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO;
|
||||
conversionInfo.conversion = ycbcrConversion;
|
||||
|
||||
VkImageViewCreateInfo viewInfo = {};
|
||||
viewInfo.pNext = &conversionInfo; // CRITICAL: YCbCr conversion 연결
|
||||
viewInfo.format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; // NV12
|
||||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; // 전체 이미지
|
||||
|
||||
// 3. Sampler도 YCbCr conversion 연결
|
||||
VkSamplerCreateInfo samplerInfo = {};
|
||||
samplerInfo.pNext = &conversionInfo;
|
||||
|
||||
// 4. 쉐이더는 단일 sampler2D 사용 (자동 변환)
|
||||
|
||||
📊 기타 발견사항
|
||||
|
||||
장점:
|
||||
- ✅ NDK AMediaCodec 직접 사용 (설계보다 우수)
|
||||
- ✅ Thread-safe BufferProcessor
|
||||
- ✅ Async MediaCodec 지원
|
||||
|
||||
단점:
|
||||
- ❌ ImageReader listener 없음 (polling 방식)
|
||||
- ❌ ImageView 메모리 누수 (파괴 시 크래시)
|
||||
- ❌ Fence 동기화 복잡도 과다
|
||||
|
||||
🎯 권장 조치
|
||||
|
||||
1. 즉시 수정: VK_KHR_sampler_ycbcr_conversion 구현
|
||||
2. 예상 결과: DEVICE_LOST 에러 해결, 연속 재생 가능
|
||||
|
||||
자세한 내용은 IMPLEMENTATION_COMPARISON_ANALYSIS.md 문서를 참조하세요.
|
||||
Reference in New Issue
Block a user