本文主要從應用的角度介紹android的native層AHardwareBuffer創建紋理以及保存渲染數據。
HardwareBuffer
要介紹native層的AHardwareBuffer,就需要先從Java層的HardwareBuffer說起。Android官方對于HardwareBuffer介紹如下:
HardwareBuffer wraps a native AHardwareBuffer object, which is a low-level object representing a memory buffer accessible by various hardware units. HardwareBuffer allows sharing buffers across different application processes. In particular, HardwareBuffers may be mappable to memory accessibly to various hardware systems, such as the?GPU, a sensor or context hub, or other auxiliary processing units. For more information, see the NDK documentation for AHardwareBuffer.
HardwareBuffer 官方介紹為一種底層的內存 buffer 對象,可在不同進程間共享,可映射到不同硬件系統,如 GPU、傳感器等,從構造函數可以看出,其可以指定 format 和 usage,用來讓底層選擇最合適的實現。
從HardwareBuffer的源碼中可以了解到,HardwareBuffer只是 GraphicBuffer 的一個包裝。在Android早期版本(API<=25), Java層并沒有提供底層的GraphicBuffer API,通常使用底層由GraphicBuffer實現的Surface。因此本質上是 Android 系統開放了更底層的 API,我們才可以有更高效的實現。接下來看具體如何基于HardwareBuffer跨進程傳輸紋理。
通過 AHardwareBuffer_toHardwareBuffer 函數,可以將native層的AHardwareBuffer 對象轉為 Java HardwareBuffer 對象,其本身實現了 Parcelable 接口,可以直接通過 AIDL 傳遞到另一個進程,其中具體的實現就是 Android 系統 GraphicBuffer 跨進程的方案,底層通過 fd 實現,B進程獲取對應的HardwareBuffer后,可以通過AHardwareBuffer_fromHardwareBuffer繼續轉換為native層的AHardwareBuffer。?
AHardwareBuffer
接下來主要介紹使用AHardwareBuffer創建紋理以及通過AHardwareBuffer讀取紋理圖像的流程
AHardwareBuffer創建紋理
創建紋理的流程較為簡單,創建AHardwareBuffer_Desc句柄,結構體賦值,本文以創建NV21的OES紋理為例,代碼如下:
FUN_BEGIN_TIME("RenderContext::CreateOESTexture")if(textureID == 0){AHardwareBuffer_Desc h_buffer_desc = {0};h_buffer_desc.stride = frameData->i32Width;h_buffer_desc.height = frameData->i32Height;h_buffer_desc.width = frameData->i32Width;h_buffer_desc.layers = 1;h_buffer_desc.format = 0x11;h_buffer_desc.usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN|AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;int ret = AHardwareBuffer_allocate(&h_buffer_desc, &inputHWBuffer);EGLint attr[] = {EGL_NONE};EGLDisplay edp;edp = (EGLDisplay)eglGetCurrentDisplay();inputEGLImage) = eglCreateImageKHR(edp, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, eglGetNativeClientBufferANDROID(inputHWBuffer), attr);glGenTextures(1, &textureID);glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureID);glTexParameteri(GL_TEXTURE_EXTERNAL_OES , GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_EXTERNAL_OES , GL_TEXTURE_MAG_FILTER, GL_LINEAR);glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES , (GLeglImageOES)inputEGLImage);GLUtils::CheckGLError("eglCreateImageKHR");}AHardwareBuffer_Planes planes_info = {0};int ret = AHardwareBuffer_lockPlanes(inputHWBuffer,AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK,-1,nullptr,&planes_info);if (ret != 0) {LOGI("Failed to AHardwareBuffer_lockPlanes");}else{memcpy(planes_info.planes[0].data,frameData->ppu8Plane[0],frameData->i32Width * frameData->i32Height*3/2);ret = AHardwareBuffer_unlock(inputHWBuffer, nullptr);if (ret != 0) {LOGI("Failed to AHardwareBuffer_unlock");}}glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureID);
FUN_END_TIME("RenderContext::CreateOESTexture")
AHardwareBuffer讀取紋理圖像數據
讀取紋理圖像數據的方式和創建紋理的方式類似,通過上述創建紋理的方式,我們實現了AHardwareBuffer 和 EGLImageKHR的綁定,因此,我們可以通過反向思維,將紋理讀取出來,代碼如下:
FUN_BEGIN_TIME("RenderContext::ReadOESTexture")unsigned char *ptrReader = nullptr;ret = AHardwareBuffer_lock(inputHWBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1, nullptr, (void **) &ptrReader); memcpy(dstBuffer, ptrReader, imgWidth * imgHeight * 3 / 2);ret = AHardwareBuffer_unlock(inputHWBuffer, nullptr);
FUN_END_TIME("RenderContext::ReadOESTexture")
至此,我們可以將dstBuffer通過字節,或者其他形式,保存為圖像數據。
總結
針對Android側,我們需要理清GraphicBuffer、AHardwareBuffer、ANativeWindowBuffer之間的關系。從聯系上,GraphicBuffer 繼承了ANativeWindowBuffer,所以可以直接通過static_cast<>類型轉換成ANativeWindowBuffer,不過由于是多繼承,所以轉完有一個地址偏移(static_cast 自動完成)。而AHardwareBuffer只是一個抽象的概念,沒有具體類型,與GraphicBuffer 沒有任何繼承關系,也沒有具體的類型,是個空結構體,類似于void 類型。從源碼可以看到,aosp封裝的那些AHardwareBuffer_xxx接口,本質上底層都是通過AHardwareBuffer_to_GraphicBuffer,轉成GraphicBuffer,依舊用GraphicBuffer的形式做的后續處理。