先放demo原始碼地址:https://github.com/xxxzhou/aoce 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 我直接用的這位同學編譯好的https://github.com/xufuji456/FFmpegAndroid,你也可以在https://github.com/xxxzhou/aoce_thirdparty下載我整理好的。
如下目錄放置就好。
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開發並不熟悉,歡迎大家指正其中錯誤的地方。