android下vulkan與opengles紋理互通

  先放demo源碼地址: 06_mediaplayer

  效果圖:

  

  主要幾個點:

  • 用ffmpeg打開rtmp流。
  • 使用vulkan Compute shader處理yuv420P/yuv422P數據格式成rgba.
  • 初始化android surface為vulkan的交換鏈,把如上結果複製到交換鏈上顯示。
  • 如果是opengles surface,如何不通過CPU直接把數據從vulkan複製到opengles里。

  這個demo主要是為了驗證用vulkan做GPGPU處理後,能輸出到vulkan/opengles紋理,後續有可能的話,明年下半年業餘時間在這項目用vulkan Compute shader移植GPUImage學習下,設計的運行平台是window/android,你可以在window平台用vscode(安裝相應cmake/C++插件)運行查看調試過程,也可以用android studio打開裡面的android文件夾,運行06_mediaplayer查看對應如上效果。  

集成ffmpeg

  window平台自己編譯後放入aoce/thirdparty/ffmpeg/x64中,android 我直接用的這位同學編譯好的,你也可以在下載我整理好的。

  如下目錄放置就好。

  

   cmake里填寫好相關window/android平台邏輯以後,你可以選擇在window端直接用vscode,也可以用MVS打開cmake生成的sln文件,用android studio打開android文件夾,自動Sync gradle引用cmake並編譯好相關C++項目。

  相應ffmpeg播放器FMediaPlayer的實現我參照了android下的MediaPlayer的介面設計,主要是prepare這個介面讓我意識到我在oeip不合理的地方,按照android下的MediaPlayer的介面重新實現了下ffmpeg里播放實現,現在只是簡單走了下流程,後期還需要設計各個狀態的轉化。

用vulkan的Compute shader做GPGPU計算

  這個是我在 Vulkan在Android使用Compute shader 就準備做的事,設想是用有序無環圖來構建GPU計算流程,這個demo也算是簡單驗證下這個流程,後續會驗證設計的多輸入輸出部分,如下構建。  

    // 生成一張執行圖
    vkGraph = AoceManager::Get().getPipeGraphFactory(gpuType)->createGraph();
    auto *layerFactory = AoceManager::Get().getLayerFactory(gpuType);
    inputLayer = layerFactory->crateInput();
    outputLayer = layerFactory->createOutput();
    // 輸出GPU數據
    outputLayer->updateParamet({false, true});
    yuv2rgbLayer = layerFactory->createYUV2RGBA();
    // 生成圖
    vkGraph->addNode(inputLayer)->addNode(yuv2rgbLayer)->addNode(outputLayer);

  其中yuv2rgbLayer現在完成了nv12/yuv420P/yuy422P/yuyvI這些常見格式的解析,貼一段yuv420P的程式碼,CS程式碼需要配合執行緒組的劃分來看,大家可以在github查看完整流程,YUV轉換的程式碼主要注意盡量不要多個執行緒同時讀或者寫某個地址就好。  

void yuv420p(){
    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
    ivec2 size = imageSize(outTex);
    if(uv.x >= size.x/2 || uv.y >= size.y/2){
        return;
    }
	ivec2 nuv = u12u2(u22u1(uv.xy, size.x/2), size.x);
	ivec2 uindex = nuv + ivec2(0,size.y);
	ivec2 vindex = nuv + ivec2(0,size.y*5/4);

	float y1 = imageLoad(inTex,ivec2(uv.x*2,uv.y*2)).r;
	float y2 = imageLoad(inTex,ivec2(uv.x*2+1,uv.y*2)).r;
	float y3 = imageLoad(inTex,ivec2(uv.x*2,uv.y*2+1)).r;
	float y4 = imageLoad(inTex,ivec2(uv.x*2+1,uv.y*2+1)).r;	
	float u = imageLoad(inTex,uindex.xy).r -0.5f;
        float v = imageLoad(inTex,vindex.xy).r -0.5f;

	vec4 rgba1 = yuv2Rgb(y1,u,v,1.f);
	vec4 rgba2 = yuv2Rgb(y2,u,v,1.f);
	vec4 rgba3 = yuv2Rgb(y3,u,v,1.f);
	vec4 rgba4 = yuv2Rgb(y4,u,v,1.f);	

	imageStore(outTex, ivec2(uv.x*2,uv.y*2),rgba1); 
	imageStore(outTex, ivec2(uv.x*2+1,uv.y*2),rgba2); 
	imageStore(outTex, ivec2(uv.x*2,uv.y*2+1),rgba3); 
	imageStore(outTex, ivec2(uv.x*2+1,uv.y*2+1),rgba4); 
}

用vulkan顯示

  Vulkan在Android使用Compute shader 裡面,我使用native window完成vulkan展示與顯示,但是很多時候,窗口並不是特定的,這個項目的設計目標之一也是無窗口使用Vulkan的Compute shader完成影像處理,後續高效支援對接android UI/UE4/Unity3d/window UI SDK都方便。

  在這,我們需要查看運行結果,在這我們換種方式,不用native window,不然方便的android UI都不能用了,在native window的實現方式中,我們知道vulkan 交換鏈只需要ANativeWindow,相應的UI消息循環顯示影像處理結果過程我們並不需要,通過查看 android圖形框架指明了surface對應的C++類就是ANativeWindow,這樣我們可以直接使用surface來完成vulkan 影像呈現工程,相關實現可以看vulkan模組下的vulkanwindow里的initsurface實現。

  其中這個demo的window/andorid刷新部分有些不同,window利用本身主執行緒的窗口空閑時間,把ffmpeg解碼執行緒上的數據經vulkan處理後複製到交換鏈當前顯示影像中,而android本身的Surface沒有找到GLSurfaceView.Renderer類似的onDrawFrame時機,所以在android vulkan中,處理與複製呈現給交換鏈全在ffmpeg解碼執行緒上,不過android 下面的opengles顯示又和window一樣,主執行緒直接複製呈現,而ffmpeg解碼執行緒用vulkan處理。

opengles顯示

  本來我想著用vulkan處理了,就能放棄opengl相關,但是至少現在還不現實,android端本身影像APP,以及對應android上的UE4/Unity3D,opengl es可能是更成熟的選擇。

  如何把vulkan的計算結果直接複製給opengles,通過文檔 android文檔AHardwareBuffer 其中有句話AHardwareBuffers可以綁定到EGL/OpenGL和Vulkan原語,通過相應提示用法VK_ANDROID_external_memory_android_hardware_buffer擴展/eglGetNativeClientBufferANDROID,我們繼續搜索,查找到 google vulkantest 這裡有段源碼,詳細說明了如何把vulkan里的vkImage綁定到AHardwareBuffer上,opengles部分根據eglGetNativeClientBufferANDROID查找到如何把AHardwareBuffer綁定到對應的opengles紋理上,這樣我們通過vkImage-AHardwareBuffer-opengl texture的綁定,對應其封裝在aoce_vulkan模組的hardwareImage類中。

  AHardwareBuffer後續還可以繼續挖,android原生平台提供的多媒體相關的SDK如攝像機,MediaPlay等都有AHardwareBuffer的身影,後續可以嘗試直接把相關綁定到vulkan加快運算。

  本人android開發並不熟悉,歡迎大家指正其中錯誤的地方。