本文主要講實現過程的一些坑。html
先說下要實現的目標,主要功能在UE4/Unity中都要用,能同時捕獲多個攝像頭,而且捕獲的圖片要達到1080p25楨上,而且須要通過複雜的圖片處理(如深度圖與顏色圖的對齊,結合深度攝像頭的天然背景扣色,用於直播傳輸帶Alpha通道的編碼等)後丟給UE4/Unity3D,而且要CPU要佔用小,以便在UE4/Unity有大的餘地作更多的事件。ios
市面上通常1080p後的攝像頭,在20楨以上都不會提供原生的rgba32數據,通常常見的有壓縮格式mjpg,末壓縮的通常是nv12,牛點的會提供yuv2,正常的實現會把內存流裏的數據提供給對應的UE4/Unity3D,而後作複雜的圖片處理。這種作法對於咱們來講,有三個問題,一是內存流裏的數據提供給UE4/Unity3D對應的gpu時,unity裏操做texture只能在主線程,試過Texture2D.LoadRawTextureData(IntPtr data,int size),不知爲啥慢的接受不能,在UE4中同樣,只能在渲染線程和遊戲線程中更新紋理,這二個線程互相之間有同步等待的關係,可是還算好,UE4中一個攝像頭的狀況還能接受。第二是上面說的圖片處理,不說後面複雜的,首先要把上面的nv12/yuv2轉化成UE4/Unity3D能用的rgba32紋理,在UE4/Unity3D就要分別實現一次,對程序員一點都不友好,後續改動全是二套,可能有的同窗要說了,爲啥不在交給UE4/Unity3D以前,把數據處理成rgba32格式,簡單點,你網上找段nv12轉rgba代碼試試,1080p直接佔用你10%左右cpu,而且後面還有不少的複雜處理,在這以前用CPU處理是不可行的。第三,若是前面能夠受點了,那麼若是同時開二個或是多個攝像頭的1080p,你還要處理各個設備的數據同步問題,這個解決後,你也就只能看幻燈片了。程序員
根據如上問題,咱們要解決的是,如何把UE4/Unity3D的圖片處理邏輯寫在一塊,如何讓咱們程序快速的從插件中獲得處理後的圖片數據,如何讓多個攝像頭能同時工做。咱們深化一下,裏面所說的都是圖片處理,對於cpu來講,是拿諸葛亮去作臭皮匠的活。那咱們至關於能不能給每一個攝像頭一個線程,每一個線程有一個GPU的上下文,線程最開始把流的數據讀到gpu中,而後處理圖片,最後給UE4/Unity3D,給UE4/Unity3D這一步咱們要考慮的是如何給,是用通用的方式,先給到cpu上內存,而後由UE4/Unity3D分別讀內存流裏的數據到各自引擎裏的渲染dx上下文,或是能不能直接由咱們攝像頭裏的gpu上下文的顯存數據移到對應UE4/Unity的gpu上下文的顯存數據中。第一種至關於GPU-CPU-GPU,第二種就是GPU-GPU,第二種確定要快的多,由於GPU與CPU的數據交互很費時,因此在下面,咱們來打造一個UE4/Unity3D通用的插件,由各自的設備讀取數據的線程把數據放入各自的gpu上下文中,通過複雜的圖像處理後,分別找到對應UE4/Unity3D上的gpu中的對應位置,把數據直接複製過去,是一條起點通過cpu後,就直接一步到位全經GPU處理後直接到引擎裏顯示的方案。web
肯定技術實現後,就是技術方案選型,語言方面,兩者通用的插件,沒的選,只有C++,平臺只考慮window平臺,沒有那麼多的需求,時間與精力去考慮多平臺,那麼攝像頭捕獲程序咱們選擇ms最新的多媒體技術Media Foundation,包含經常使用的視頻編解碼,GPU方面咱們只要圖像處理,以及和UE4/Unity能原始對接的接口,那麼也沒選擇,Dx11是UE4/Unity3D在window平臺默認的底層渲染技術,DX11裏的ComputeShader來處理圖像邏輯很少很多,邏輯更清晰,與dx11的紋理無逢對接。嗯,至於爲啥之前我是作opengl,爲啥不選opengl/opencl這路,首先在window平臺,方便性,效率,與UE4/Unity3D引擎對接都是dx11方便,其次,我自認爲對opengl熟悉,那麼對dx也不會陌生,順便能熟悉下dx也很興奮。由於都是window平臺方案,後面生成的庫想不到的小,只有400K左右實現了不少功能,包含webcamera,decklink視頻捕獲,realsense深度攝像頭,以及與UE4/Unity3D的CPU/GPU對接,記錄系統聲音,PCM轉ACC,記錄H264視頻,ACC/MP3與H264視頻合成,以及視頻播放。算法
在這隻講上面攝像頭捕獲相關,以下是相關的類圖關係,爲了顯示清晰,只列出主要相關的類。windows
主要說下,分別是數據處理與數據提供。數據處理的設計參照遊戲引擎的後處理特效的設計,每一個處理一個特定的功能,能夠組合連接,不一樣於遊戲的後處理特效,Computeshader對於數據的長寬很敏感,因此設計的時候要考慮好這個。api
ComputeBasic作爲數據處理的基類,一是處理如上長寬的變化,二是處理ComputeBasic與ComputeBasic的鏈接,針對數據長寬的變化,主要是把初始化/註銷buffer與初始化/註銷shader分開,當數據維度變化後,針對buffer的那一塊所有從新生成,二是定義維度如何影響下一塊,畢竟不少功能在自己上就會改變維度,如yuv轉換,壓縮與解壓等。第二塊ComputeBasic與ComputeBasic的鏈接在如上的基礎上,連接後下一塊自動知道相應的大小,並提供針對整個功能鏈表功能。下面就是數據處理的各個具體實現,選擇實現基類定義的一些接口,就有了基類處理連接下一塊與大小變化自動處理,每一個具體的數據處理類只須要實現具體功能就可。網絡
以下貼一個常見的yuv2rgb的Computeshader與代碼實現。app
//YUV2,YuYv, 指定交叉格式。 enum YUV422Format { BC_YUYV = 0, BC_YVYU = 1, BC_UYVY = 2, BC_VYUY = 3, }; //提供yuyv,yvyu,uyuv轉rgba32的能力 class YuyvTrgbCompute : public ComputeBasic { public: YuyvTrgbCompute(MainCompute* mainCompute, bool resultText = false, YUV422Format bcFormat = BC_YUYV) :ComputeBasic(mainCompute) { bResultTex = resultText; format = bcFormat; } ~YuyvTrgbCompute(); public: virtual void UpdateData(void* data, int index = 0) override; virtual void UpdateBuffer(ID3D11Texture2D* buffer, int index = 0) override; virtual void ShowDebugBuffer(int index = 0) override; virtual void OnChangeSize(TexSize& newSize) override; public: // 經過 ComputeBasic 繼承 virtual bool InitShader() override; virtual bool InitBuffer() override; virtual bool runCompute() override; public: //0 yuyv,1 yvyu, YUV422Format format; }; #include "YuyvTrgbCompute.h" YuyvTrgbCompute::~YuyvTrgbCompute() { } void YuyvTrgbCompute::UpdateData(void * data, int index) { } void YuyvTrgbCompute::UpdateBuffer(ID3D11Texture2D * buffer, int index) { g_tBuf = buffer; mc->CreateBufferSRV(g_tBuf, &g_pBufSRV); bInitInput = true; } void YuyvTrgbCompute::ShowDebugBuffer(int index) { } void YuyvTrgbCompute::OnChangeSize(TexSize & newSize) { nextSize.width = newSize.width * 2; nextSize.height = newSize.height; } bool YuyvTrgbCompute::InitShader() { char* strResultTex = bResultTex ? "1" : "0"; char* yfront = (format == BC_YUYV || format == BC_YVYU) ? "1" : "0"; char* ufront = (format == BC_YUYV || format == BC_UYVY) ? "1" : "0"; const D3D_SHADER_MACRO defines[] = { "RESULT_TEX",strResultTex,"YFRONT",yfront,"UFRONT",ufront, "SIZE_X", "4", "SIZE_Y","4" ,nullptr,nullptr }; //bInitShader = mc->CreateComputeShader(L"rgba2yuv420p.hlsl", "main", &g_pCS, defines); bInitShader = mc->CreateCustomComputeShader(MAKEINTRESOURCE(112), "main", &g_pCS, defines); return bInitShader; } bool YuyvTrgbCompute::InitBuffer() { bInitBuffer = mc->CreateConstBuffer(&nextSize, sizeof(nextSize), &g_cBuf); if (bResultTex) { bInitBuffer &= mc->CreateTextureBuffer(nullptr, nextSize.width, nextSize.height, DXGI_FORMAT_R8G8B8A8_UNORM, &g_tBufResult); bInitBuffer &= mc->CreateBufferUAV(g_tBufResult, &g_pBufResultUAV); } else { bInitBuffer &= mc->CreateStructuredBuffer(sizeof(UINT), nextSize.width*nextSize.height, nullptr, &g_pBufResult); bInitBuffer &= mc->CreateBufferUAV(g_pBufResult, &g_pBufResultUAV); } return bInitBuffer; } bool YuyvTrgbCompute::runCompute() { vector<ID3D11ShaderResourceView*> srvs = { g_pBufSRV }; vector<ID3D11UnorderedAccessView*> uavs = { g_pBufResultUAV }; vector<ID3D11Buffer*> cons = { g_cBuf }; mc->RunComputeShader(g_pCS, nextSize.width / SIZE_X, nextSize.height / SIZE_Y, 1, srvs, uavs, cons); return true; }
#include "Common.hlsl" #ifndef YFRONT #define YFRONT 1 #endif #ifndef UFRONT #define UFRONT 1 #endif cbuffer texSize : register(b0) { uint width; uint height; } Texture2D<float4> colorData : register(t0); #if RESULT_TEX RWTexture2D<float4> outData : register(u0); #else RWStructuredBuffer<uint> outData : register(u0); #endif uint u22u1(uint2 uv) { return uv.y * width + uv.x; } [numthreads(SIZE_X, SIZE_Y, 1)] void main(uint3 DTid : SV_DispatchThreadID) { uint2 uv = DTid.xy; uint3 tuv = uint3(DTid.x / 2, DTid.y, DTid.z); float4 fyuyv = colorData.Load(tuv); int4 yuyv = fyuyv * 255; //二點一個計算 uint offset = DTid.x % 2; #if UFRONT uint uIndex = 0; uint vIndex = 2; #else uint uIndex = 2; uint vIndex = 0; #endif #if YFRONT int y = yuyv[2 * offset]; uint uvOffset = 1; #else int y = yuyv[2 * offset + 1]; uint uvOffset = 0; #endif int u = yuyv[uIndex + uvOffset]; int v = yuyv[vIndex + uvOffset]; uint4 rgba = yuv2Rgb(y, u - 128, v - 128, 255); //rgba = yuyv; #if RESULT_TEX outData[uv] = float4(rgba / 255.0); #else uint index = u22u1(uv); outData[index] = rgba.r | rgba.g << 8 | rgba.b << 16 | rgba.a << 24; #endif }
簡單說下別的類主要功能,代碼和上面yuv2rgb就邏輯上有區別。異步
NormTextureCompute:規範輸入流成正常的4通道數據,如r/rg/rgb變成rgba數據,以及去掉紋理須要32倍寬限制(內存塊數據直接放入更新到紋理中可能不對,寬度會自動修正,MSDN說是4的倍數,可是在我機器上測試要知足32倍整數,先放入buffer,再經過compute shader寫入紋理就可避免。).
keyingCompute:整合咱們公司另外一牛人根據何愷明大神的導向濾波算法,扣圖達到髮絲極,固然算法也是很是複雜,七段compute shader組合而成。
Yuv420TrgbaCompute/RgbaTyuvCompute是一套能夠帶Alpha通道的平面420的傳輸,具體細節就不說了。其中Compute shader若是邏輯要針對texture屢次採樣,可能考慮使用groupshared/GroupMemoryBarrierWithGroupSync來改變邏輯提升效率。
TextureOperateCompute:提供通道映射的能力,如bgra轉rgba,rgba轉rggr,還有上下,左右翻轉功能。
RotateCompute:翻轉的能力,注意若是是貼圖如1920*1080翻轉90後是1080*1920,直接GPU複製不會引發問題,可是從CPU讀出來,內存數據寬度是1088面不是1080,因此相應你用這個數據後須要生成的是1088*1920的圖,注意上面所說的32倍寬。
ZipCompute/UnzipCompute:提供壓縮4 byte成1個int,或是反過來,主要是有些數據如yuv420,nv12這種寬度對應,可是在compute shader裏都是4字節的存放,內存讀出來的數據先通過unzip,反倒後續使用。
DistortionCompute:提供針對原圖的UV從新映射,主要用於攝像頭校訂。
DepthKeyingCompute:深度攝像頭專用,結合深度圖與上面的keyingCompute的導向濾波算法,對應如上的RealSense Camera,能夠作到天然環境下的扣圖。
DepthProcessCompute:深度攝像頭專用,原始深度圖須要作不少處理,好比對齊,去燥,這裏主要把RealSense SDK的CPU算法全改爲Compute Shader,否則作不到二個RealSense對齊等算法後還能同時30楨,RealSense的SDK設計有些奇怪,須要在同一線程把全部設備的數據全拿出來,可是能夠在不一樣的線程去處理這些數據,因此這裏先是在同一線程讀取數據,而後是各取設備裏的線程去讀取對應數據,而後用GPU處理,這樣才能作到選擇最高分辨率顏色1080,深度720後加上後處理還能多個RealSense同時30楨。
在這總結下Compute shader趕上的坑。
1. ID3D11Texture2D的寬度最好爲32的倍數,否則map數據須要根據RowPitch來調整。(在這上一共遇到二次,第一次組織內存數據上傳到紋理中,發現結果不對,第二次,1920*1080的圖處理後加上倒轉成1080*1920,經過在UE4/Unity3D的GPU更新沒問題,卻是在測試項目裏讀出內存數據放入opencv的mat裏,發現圖不對了,後面想到這裏,試了試用1088*1920,結果顯示正確).
2. Compute shader運行的結果全是0,沒有報錯,多是一個buffer給了幾個uav。
3. Const buffer中結構與傳入的C++結構對應,如int與float不對應,就會致使傳入的數據不對,而且數據要是4字節整倍,不要想把bool放入,bool應轉化成int放入。
4. Compute shader若是邏輯要針對texture屢次採樣,可能考慮使用groupshared/GroupMemoryBarrierWithGroupSync來改變邏輯提升效率。
5. Compute shader裏面加入了頭文件,若是Compute shader變成了資源文件,那麼頭文件引用就會失效,用ID3DInclude包含頭文件來編譯Compute shader.
6. Compute shader使用條件編譯符,能夠在頭部用ifndef包含下默認定義,這樣還能夠用hlsl編譯不會出錯,後面編譯shader傳的編譯指令會覆蓋默認。
7. 在uav裏每段GroupMemoryBarrierWithGroupSync裏反覆讀寫uav,結果可能不是你想要的,試試分開shader,每次寫調用一次Dispatch。
8. uv與線程調度的每塊對應關係,若是你感受你算的uv放入你紋理算的不對,如屢次blur感受有移位,能夠試下以下uv算法float2 uv = float2((DTid.x+0.5) / sizeX, (DTid.y +0.5)/ sizeY),這條是上面寫keyingCompute的牛人根據遇到問題總結出來的。(20181115,在查看CUDA提供的例子裏simpleLayeredTexture裏有提到,在紋理中,訪問原始數據點須要0.5f偏移和除法,這樣就不會激活雙線性插值,這個應該是科學的解釋)。
Compute shader的部分到此結束,咱們開始說下MainCompute,這個類包裝一個DX的設備與上下文,若是想每一個設備或是線程想不互相干擾,在各自線程聲明本身的MainCompute,每一個數據處理ComputeBasic初始化時會要求一個MainCompute,這樣設備與處理就綁定在本身的上下文中互不影響。
對於數據提供者,我在上面拉出三個,一個是咱們本文在講的視頻捕獲設備,二是如何網絡直播傳輸帶alpha的rgba數據,三是經過Mediafoundation生成H264視頻,這三個部分咱們均可以經過上面dx讓GPU來完成其中所須要的大量圖片處理。
本文主要記錄視頻捕獲設備的基本實現思路,如上圖,主要有二種視頻捕獲設備,一種是免驅的webcamera,一種是decklink,兩者繼承VideoDevice,後續的設想,視頻以及上面不支持的採集視頻均可以繼承這個類,經過這個類提供同種接口。
Mediafoundation與decklink都提供異步讀取數據,須要注意的是,這裏異步只是隱藏了線程的實現,用同步只是多了個線程的調用,你仍是要把你邏輯傳入給他隱藏的線程調用,而且由於線程的隱藏,你更要注意操做相關資源時須要處理相應同步數據,否則關閉時,很容易由於二個線程互相還握有同一資源,形成關閉時崩潰。
在這也總結下我經常使用的三種同步方法,一是鎖定資源,同步訪問,直接用std::lock_guard區域鎖就好,若是是關閉對象,還有線程在引用對象上變量,加個flag配合使用。二是在一個線程裏等待另外一個線程執行完,鎖的資源通常不在同一函數內,通常用std::unique_lock/std::condition_variable配合使用,使用信號量wait_for合理時間。三是在一個線程等待多個線程完成,用std::future/std::async配合完成比較輕鬆。
我取一段VideoProcess裏的代碼,能夠看下如何根據不一樣的源始數據與目標數據,來組裝ComputeBasic列表的流水線。
如上,數據通過流水線處理完成後,就是包裝,考慮到導出給UE4/Unity3D兩者能使用同一份,咱們使用C的導出方式,以下列出幾個API.
考慮到C++用靜態函數當函數指針會致使代碼寫法與可讀性變髒,故帶有函數指針的方法會提供二個,如上。
DLL內部指針傳遞全用的C++11標準裏的function,再給C/C#的接口時,會加一個C裏的函數針轉接一下。
包裝好後,咱們再創建一個C++的測試項目,針對圖形上的顯示,咱們引用opencv來作測試,注意一點,opencv默認使用bgra,咱們要先用如上的通道映射成rgba,而後使用opencv查看查看各個功能是否正常,根據測試,再反饋前面調整相應邏輯,使接口更方便使用,咱們能夠給UE4/Unity3D使用了。
下面貼出共享紋理buffer如何複製到另外一個dx上下文的代碼,這段代碼是解決如何在二個不一樣的上下文之間共享。經過這樣,咱們就能直接在UE4/Unity3D中線程與當前線程中不一樣的上下文之間直接複製GPU數據。
一 獲得共享buffer的共享句柄。 mc->CreateTextureBuffer(nullptr, showWidth, showHeight, DXGI_FORMAT_R8G8B8A8_UNORM, &keyingTexture, false, true); textureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; keyingSharedHandle = MainCompute::GetSharedHandle(keyingTexture); HANDLE MainCompute::GetSharedHandle(ID3D11Resource * source) { HANDLE Hnd = nullptr; // QI IDXGIResource interface to synchronized shared surface. IDXGIResource* DXGIResource = nullptr; HRESULT hr = source->QueryInterface(__uuidof(IDXGIResource), reinterpret_cast<void**>(&DXGIResource)); if (SUCCEEDED(hr)) { // Obtain handle to IDXGIResource object. DXGIResource->GetSharedHandle(&Hnd); DXGIResource->Release(); DXGIResource = nullptr; } return Hnd; } 二 咱們在當前上下文中給共享紋理賦值,注意同步。 CComPtr<IDXGIKeyedMutex> pDX11Mutex = nullptr; auto hResult = keyingTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), (LPVOID*)&pDX11Mutex); DWORD result = pDX11Mutex->AcquireSync(0, syncTime); if (result == WAIT_OBJECT_0) { mc->CopyBuffer(keyingTexture, tc->GetResultBuffer()); result = pDX11Mutex->ReleaseSync(1); if (result == WAIT_OBJECT_0 && onProcessData) onProcessData(videoFormat.width, videoFormat.height, keyingSharedHandle, KeyingGPU); } 三 從UE4/Unity3D的上下文獲得這個共享buffer的數據 void copySharedToTexture(ID3D11Device * d3ddevice, HANDLE & sharedHandle, ID3D11Texture2D * texture) { if (!d3ddevice) return; ID3D11DeviceContext* d3dcontext = nullptr; d3ddevice->GetImmediateContext(&d3dcontext); if (!d3dcontext) return; if (sharedHandle && texture) { CComPtr<ID3D11Texture2D> pBuffer = nullptr; HRESULT hr = d3ddevice->OpenSharedResource(sharedHandle, __uuidof(ID3D11Texture2D), (void**)(&pBuffer)); if (FAILED(hr)) { LogMessage(error, "open shared texture error."); } CComPtr<IDXGIKeyedMutex> pDX11Mutex = nullptr; auto hResult = pBuffer->QueryInterface(__uuidof(IDXGIKeyedMutex), (LPVOID*)&pDX11Mutex); if (FAILED(hResult) || (pDX11Mutex == nullptr)) { LogMessage(error, "get IDXGIKeyedMutex failed."); return; } DWORD result = pDX11Mutex->AcquireSync(1, syncTime); if (result == WAIT_OBJECT_0) { d3dcontext->CopyResource(texture, pBuffer); result = pDX11Mutex->ReleaseSync(0); } } }
咱們先說UE4裏的,先看下在UE4裏簡單封裝調用。
struct CameraSetting { KeyingSetting ks = {}; TextureOperate sourceTo = {}; TextureOperate keyingTo = {}; TextureOperate transportTo = {}; DepthSetting ds = {}; }; class MRCORETEST_API CameraCommon { private: UTexture2D* sourceTex = nullptr; UTexture2D* keyingTex = nullptr; DeviceInfo* device = nullptr; CameraSetting* cameraSetting = nullptr; TArray<VideoFormat> formatList; VideoFormat format = {}; bool bSetCamera = false; bool bSetFormat = false; public: int GetIndex() { return device->id; } bool IsInit() { return bSetCamera && bSetFormat; } bool IsOpen() { if (IsInit()) return bOpen(device->id); else return false; } bool IsDepth() { if (bSetCamera) return bDepth(device->id); return false; } UTexture2D* GetSourceTex() { return sourceTex; } UTexture2D* GetKeyingTex() { return keyingTex; } DeviceInfo* GetDevice() { return device; } DeviceDataType GetDataType() { if (IsOpen()) return getDataType(device->id); return DeviceDataType::None; } TArray<VideoFormat>& GetFormatList() { return formatList; } VideoFormat& GetCurrentFormat() { return format; } CameraSetting& GetCameraSetting() { return *cameraSetting; } public: void SetCameraIndex(int index, CameraSetting* cameraSet = nullptr); void SetFormatIndex(int index); void Update(); bool Open(bool bTrans, bool bKeying = true); void Close(); void UserPostProcess(bool bUser); void UserBG(bool bUser); bool SaveBGFile(FString path); bool LoadBG(FString path); private: void InitTexture(); void updateTexture(UTexture2D** texture, int width, int height); void findFormatIndex(int cameraIndex, int& first, int& second, int& three); public: CameraCommon(); ~CameraCommon(); }; #include "CameraCommon.h" CameraCommon::CameraCommon() { device = new DeviceInfo(); cameraSetting = new CameraSetting(); } CameraCommon::~CameraCommon() { delete device; delete cameraSetting; if (sourceTex->IsValidLowLevel()) { sourceTex->RemoveFromRoot(); sourceTex->ConditionalBeginDestroy(); sourceTex = nullptr; } if (keyingTex->IsValidLowLevel()) { keyingTex->RemoveFromRoot(); keyingTex->ConditionalBeginDestroy(); keyingTex = nullptr; } } void CameraCommon::SetCameraIndex(int index, CameraSetting * cameraSet) { bSetFormat = false; bSetCamera = false; int count = getDeviceCount(); if (index >= 0 && index < count) { getDeviceIndex(index, device); bSetCamera = true; } if (bSetCamera) { int count = getFormatCount(index); formatList.SetNumUninitialized(count); getFormatList(index, formatList.GetData(), count); //獲取當前參數的默認設置 if (cameraSet != nullptr) { memcpy(&cameraSetting, cameraSet, sizeof(CameraSetting)); } else { getKeySetting(index, &cameraSetting->ks); getTextureOperate(index, &cameraSetting->sourceTo, SourceRGBA32); getTextureOperate(index, &cameraSetting->keyingTo, Keying); getTextureOperate(index, &cameraSetting->transportTo, TransportYUVA); getDepthSetting(index, &cameraSetting->ds); } } } void CameraCommon::SetFormatIndex(int index) { if (!bSetCamera) return; bSetFormat = false; if (index < 0 || index >= formatList.Num()) { int first = -1; int second = -1; int three = 0; findFormatIndex(0, first, second, three); index = first >= 0 ? first : (second >= 0) ? second : three; } if (index >= 0 && index < formatList.Num()) { format = formatList[index]; bSetFormat = true; InitTexture(); } } void CameraCommon::Update() { if (!IsOpen()) return; auto dataType = GetDataType(); if ((dataType & SourceRGBA32GPU) == SourceRGBA32GPU || (dataType & SourceRGBA32) == SourceRGBA32) { updateTextureOperate(device->id, &cameraSetting->sourceTo, SourceRGBA32); } if ((dataType & KeyingGPU) == KeyingGPU || (dataType & Keying) == Keying) { updateKeySetting(device->id, &cameraSetting->ks); updateTextureOperate(device->id, &cameraSetting->keyingTo, Keying); } if ((dataType & TransportYUVA) == TransportYUVA) { updateTextureOperate(device->id, &cameraSetting->transportTo, TransportYUVA); } if (IsDepth()) updateDepthSetting(device->id, &cameraSetting->ds); ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER( UpdateCameraTexture, int, cameraIndex, device->id, { void* device = RHICmdList.GetNativeDevice(); if (device != nullptr) { updateCameraGPU(cameraIndex, device); } }); } bool CameraCommon::Open(bool bTrans, bool bKeying) { if (!IsInit() || IsOpen()) return false; auto dataType = SourceRGBA32GPU; if (bKeying) dataType = (DeviceDataType)(dataType | KeyingGPU); if (bTrans) dataType = (DeviceDataType)(dataType | TransportYUVA); return openCamera(device->id, format.index, dataType); } void CameraCommon::Close() { if (!IsInit() || !IsOpen()) return; closeCamera(device->id); bSetFormat = false; bSetCamera = false; } void CameraCommon::UserPostProcess(bool bUser) { if (!IsInit() || !IsDepth()) return; setUserPostProcess(device->id, bUser); } void CameraCommon::UserBG(bool bUser) { if (!IsOpen() || !IsDepth()) return; saveBG(device->id, bUser); } bool CameraCommon::SaveBGFile(FString path) { if (!IsOpen() || !IsDepth()) return false; return saveBGToFile(device->id, *path); } bool CameraCommon::LoadBG(FString path) { if (!IsOpen() || !IsDepth()) return false; return loadBG(device->id, *path); } void CameraCommon::InitTexture() { if (!bSetFormat) return; updateTexture(&sourceTex, format.width, format.height); updateTexture(&keyingTex, format.width, format.height); ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER( SetCameraTexture, int, cameraIndex, device->id, UTexture2D*, cameraSourceTex, sourceTex, UTexture2D*, cameraKeyingTex, keyingTex, { auto sourceResource = cameraSourceTex->Resource->TextureRHI->GetNativeResource(); auto keyingResource = cameraKeyingTex->Resource->TextureRHI->GetNativeResource(); setGameraGPU(cameraIndex, sourceResource, keyingResource); }); } void CameraCommon::updateTexture(UTexture2D ** ptexture, int width, int height) { UTexture2D * texture = *ptexture; bool bValid = texture->IsValidLowLevel(); bool bChange = false; if (bValid) { int twidth = texture->GetSizeX(); int theight = texture->GetSizeY(); bChange = (twidth != width) || (theight != height); if (bChange) { texture->RemoveFromRoot(); texture->ConditionalBeginDestroy(); texture = nullptr; } } if (!bValid || bChange) { *ptexture = UTexture2D::CreateTransient(width, height, PF_R8G8B8A8); (*ptexture)->UpdateResource(); (*ptexture)->AddToRoot(); } } void CameraCommon::findFormatIndex(int cameraIndex, int & first, int & second, int & three) { first = -1; second = -1; three = 0; if (!bSetFormat) return; int index = 0; VideoFormat preFormat = formatList[0]; for (VideoFormat format : formatList) { if (format.width == 1920 && format.height == 1080) { if (format.fps == 30) { //MJPG須要解碼,損失CPU,默認不找MJPG格式的 if (format.videoType != VideoType::MJPG) { first = index; } else if (first == -1) { first = index; } } else if (format.fps >= 20 && format.fps <= 60) { if (format.videoType != VideoType::MJPG) { second = index; } else if (second == -1) { second = index; } } } //若是沒有1920*1080 20fps的,選一個最合適的 if (format.height >= preFormat.height && format.width >= preFormat.height && format.fps >= 20 && format.fps <= 60) { //楨優先而後是格式 if (format.fps > preFormat.fps || (format.fps == preFormat.fps && format.videoType != VideoType::MJPG)) { three = index; preFormat = format; } } index++; } }
這個腳本用的是GPU更新方式,CPU更新方式把註冊對應事件能拿到對應CPU數據而後本身填充到UE4紋理,這裏就不提供了,代碼很少,GPU更新去掉中間的CPU轉接過程效率會高很多,開多少個設備都不影響遊戲的顯示。ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER我原來介紹UE4的渲染線程裏提過,這裏就簡單提下,由於咱們要拿到原生的Dx11的設備與dx11的紋理指針,咱們必需在RHI線程中才能訪問到,這個宏能幫咱們生成一個類,提交宏裏的邏輯到渲染線程的隊列中去執行。
以下是UE4中顯示效果。
Unity3D裏的要麻煩一點,直接在C#裏是拿不到Unity3D在用的dx11上下文與對應的紋理指針,好在Unity3D也提供原生的插件讓咱們來作到這一點(https://docs.unity3d.com/530/Documentation/Manual/NativePluginInterface.html),咱們須要的是寫一個Unity3D的原生插件,再包裝一下咱們上面提供的接口。能夠看到,咱們並沒作太多的事,主要就是拿到d3d11設備,調用咱們以前的接口。
以下在Unity3D裏的包裝。
#pragma once #include "IUnityGraphics.h" typedef void (UNITY_INTERFACE_API * UnityRenderingEvent)(int eventId); #ifdef __cplusplus extern "C" { #endif // If exported by a plugin, this function will be called when the plugin is loaded. void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces); // If exported by a plugin, this function will be called when the plugin is about to be unloaded. void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload(); void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType); void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API SetCameraTexture(int cameraID, void* sourceTexture, void* keyingTexture); void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UpdateTexture(int cameraID); UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc(); #ifdef __cplusplus } #endif #include "UnityExport.h" #include <d3d11.h> #include <MRCommon.h> #include "IUnityGraphicsD3D11.h" #include <atlbase.h> #include <map> #include <memory> static IUnityInterfaces* s_UnityInterfaces = nullptr; static IUnityGraphics* s_Graphics = nullptr; static UnityGfxRenderer s_DeviceType = kUnityGfxRendererNull; static ID3D11Device* g_D3D11Device = nullptr; static ID3D11DeviceContext* g_pContext = nullptr; void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces * unityInterfaces) { s_UnityInterfaces = unityInterfaces; s_Graphics = s_UnityInterfaces->Get<IUnityGraphics>(); s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent); // Run OnGraphicsDeviceEvent(initialize) manually on plugin load OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); } void UNITY_INTERFACE_API UnityPluginUnload() { s_Graphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent); } void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType) { switch (eventType) { case kUnityGfxDeviceEventInitialize: { s_DeviceType = s_Graphics->GetRenderer(); if (s_DeviceType != kUnityGfxRendererD3D11) { writeMessage(error, "暫時只支持dx11."); } IUnityGraphicsD3D11* d3d11 = s_UnityInterfaces->Get<IUnityGraphicsD3D11>(); g_D3D11Device = d3d11->GetDevice(); g_D3D11Device->GetImmediateContext(&g_pContext); break; } case kUnityGfxDeviceEventShutdown: writeMessage(error, "unity mr關閉."); break; }; } void UNITY_INTERFACE_API SetCameraTexture(int cameraID, void * sourceTexture, void * keyingTexture) { ID3D11Texture2D* sourceResource = nullptr; ID3D11Texture2D* keyingResource = nullptr; if (sourceTexture) sourceResource = reinterpret_cast<ID3D11Texture2D*>(sourceTexture); if (keyingTexture) keyingResource = reinterpret_cast<ID3D11Texture2D*>(keyingTexture); setGameraGPU(cameraID, sourceResource, keyingResource); } void UNITY_INTERFACE_API UpdateTexture(int cameraID) { if (g_D3D11Device == nullptr) return; updateCameraGPU(cameraID, g_D3D11Device); } UNITY_INTERFACE_EXPORT UnityRenderingEvent UNITY_INTERFACE_API GetRenderEventFunc() { return UpdateTexture; }
說一下這裏的一個坑,當時並沒想爲何要用GL.IssuePluginEvent,直接調用的更新紋理,發現運行時很容易崩,後面想了下,和UE4裏同樣,應該是有個渲染線程專門來更新渲染,Unity3d腳本里雖然暴露給咱們的只有一個主線程,但若是直接在這個主線程裏調用原生d3d11更新設備,二邊線程可能操縱了同一塊資源,後面改成使用GL.IssuePluginEvent來發送給底層渲染線程來更新紋理,GL.IssuePluginEvent須要傳入的是個函數也是這個緣由,問題解決。這裏Unity3D裏沒提供CPU更新,由於慢的我受不了。
以下是Unity針對上面接口的再包裝,主要是C++與C#的交互封裝。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct DeviceInfo { public int id; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public char[] deviceName; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public char[] deviceID; } [Serializable] public struct TextureOperate { public bool bFlipX;// = false; public bool bFlipY;// = false; public int mapR;// = 0; public int mapG;// = 1; public int mapB;// = 2; public int mapA;// = 3; }; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void LogHandlerDelegate(int level, string message); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void OnDataReviceDelegate(int width, int height, IntPtr data, DeviceDataType dataType); public static class MRCommonHelper { const string mrDll = "MRCommon"; const string mrUnityDll = "MRUnityPlugins"; public const CallingConvention importCall = CallingConvention.Cdecl; [DllImport(mrDll, CallingConvention = importCall)] public static extern void SetLogHandleC(LogHandlerDelegate handler); [DllImport(mrDll, CallingConvention = importCall)] public static extern void initMedia(); [DllImport(mrDll, CallingConvention = importCall)] public static extern void shutdownMedia(); [DllImport(mrDll, CallingConvention = importCall)] public static extern int getDeviceCount(); [DllImport(mrDll, CallingConvention = importCall)] public static extern int getDeviceList(IntPtr deviceList, int lenght); [DllImport(mrDll, CallingConvention = importCall)] public static extern int getFormatList(int deviceIndex, IntPtr formatList, int lenght); [DllImport(mrDll, CallingConvention = importCall)] public static extern int getFormatCount(int deviceIndex); [DllImport(mrDll, CallingConvention = importCall)] public static extern void getDeviceParametrs(int deviceIndex, out CamParametrs parametrs); [DllImport(mrDll, CallingConvention = importCall)] public static extern void setDeviceParametrs(int deviceIndex, ref CamParametrs parametrs); [DllImport(mrDll, CallingConvention = importCall)] public static extern void updateSetting(int deviceIndex, ref KeyingSetting ksetting, ref TextureOperate tsetting); [DllImport(mrDll, CallingConvention = importCall)] public static extern void getSetting(int deviceIndex, ref KeyingSetting ksetting, ref TextureOperate tsetting); [DllImport(mrDll, CallingConvention = importCall)] public static extern void openCamera(int deviceIndex, int formatIndex, DeviceDataType dataType); [DllImport(mrDll, CallingConvention = importCall)] public static extern void closeCamera(int deviceIndex); [DllImport(mrDll, CallingConvention = importCall)] public static extern void setRotate(int deviceIndex,bool bRotate); [DllImport(mrDll, CallingConvention = importCall)] public static extern void SetDeviceReciveHandleC(int deviceIndex, OnDataReviceDelegate handle); [DllImport(mrUnityDll)] public static extern void SetCameraTexture(int cameraID, IntPtr sourceTexture, IntPtr keyingTexture); [DllImport(mrUnityDll)] public static extern IntPtr GetRenderEventFunc(); } [Serializable] public class CameraDevice { public int id = -1; public string deviceName = string.Empty; public string deviceID = string.Empty; public bool bOpen = false; } public class MediaManager : MSingleton<MediaManager> { private List<CameraDevice> cameraList = null; protected override void Init() { MRCommonHelper.initMedia(); MRCommonHelper.SetLogHandleC(logMessage); cameraList = GetCameraDeviceList(); } public void logMessage(int level, string message) { Debug.Log(message); } public bool GetCamera(int index, ref CameraDevice cameraDevice) { if (index >= 0 && index < cameraList.Count) { cameraDevice = cameraList[index]; return true; } cameraDevice = null; return false; } public List<CameraDevice> GetCameraDeviceList() { List<CameraDevice> cameraList = new List<CameraDevice>(); int count = MRCommonHelper.getDeviceCount(); Console.WriteLine(count); DeviceInfo[] deviceList = new DeviceInfo[count]; int deviceLenght = Marshal.SizeOf(typeof(DeviceInfo)); byte[] data = new byte[deviceLenght * count]; GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); IntPtr pin = handle.AddrOfPinnedObject(); MRCommonHelper.getDeviceList(pin, count); for (int i = 0; i < count; i++) { deviceList[i] = ByteArrayToStructure<DeviceInfo>(data, pin, i * deviceLenght); } handle.Free(); foreach (var device in deviceList) { CameraDevice camera = new CameraDevice(); camera.id = device.id; camera.deviceID = new string(device.deviceID); camera.deviceName = new string(device.deviceName); cameraList.Add(camera); } return cameraList; } public List<VideoFormat> GetCameraFormatList(int index) { List<VideoFormat> cameraList = new List<VideoFormat>(); int count = MRCommonHelper.getFormatCount(index); Console.WriteLine(count); VideoFormat[] deviceList = new VideoFormat[count]; int deviceLenght = Marshal.SizeOf(typeof(VideoFormat)); byte[] data = new byte[deviceLenght * count]; GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); IntPtr pin = handle.AddrOfPinnedObject(); MRCommonHelper.getFormatList(index, pin, count); for (int i = 0; i < count; i++) { deviceList[i] = ByteArrayToStructure<VideoFormat>(data, pin, i * deviceLenght); } handle.Free(); cameraList.AddRange(deviceList); return cameraList; } T ByteArrayToStructure<T>(byte[] bytes, IntPtr pin, int offset) where T : struct { try { return (T)Marshal.PtrToStructure(new IntPtr(pin.ToInt64() + offset), typeof(T)); } catch (Exception e) { return default(T); } } public override void Close() { MRCommonHelper.shutdownMedia(); } }
原本認爲就一點代碼,發現仍是有點多,其實就是一個包裝的皮,調出的這些代碼主要記錄下C++與C#的主要交互,關於多媒體音頻與視頻的部分就先不放出來了。
以下和UE4的邏輯差很少。
public class CameraCommon { private Texture2D sourceTex = null; private Texture2D keyingTex = null; private CameraDevice device = null; private List<VideoFormat> formatList = null; private VideoFormat format = new VideoFormat(); private bool bSetCamera = false; private bool bSetFormat = false; public CameraSetting cameraSetting = new CameraSetting(); public int Index { get { if (device == null) return -1; return device.id; } } public bool IsOpen { get { if (IsInit) return MRCommonHelper.bOpen(device.id); return false; } } public bool IsInit { get { return bSetCamera && bSetFormat; } } public bool IsDepth { get { if (bSetCamera) return MRCommonHelper.bDepth(device.id); ; return false; } } public Texture2D SourceTex { get { return sourceTex; } } public Texture2D KeyingTex { get { return keyingTex; } } public CameraDevice Device { get { return device; } } public List<VideoFormat> FormatList { get { return formatList; } } public VideoFormat Format { get { return format; } } /// <summary> /// 攝像機可能不是由當前DLL打開,咱們直接得到 /// </summary> public DeviceDataType DataType { get { if (IsOpen) return MRCommonHelper.getDataType(device.id); return DeviceDataType.None; } } public void SetCameraIndex(int index, CameraSetting cameraSet = null) { bSetFormat = false; bSetCamera = false; if (index >= 0 && index < MRCommonManager.Instance.CameraList.Count) { device = MRCommonManager.Instance.CameraList[index]; bSetCamera = true; } if (bSetCamera) { formatList = MRCommonManager.Instance.GetCameraFormatList(index); //獲取當前參數的默認設置 if (cameraSet != null) cameraSetting = cameraSet; if (cameraSetting.IsDefault()) { MRCommonHelper.getKeySetting(index, ref cameraSetting.ks); MRCommonHelper.getTextureOperate(index, ref cameraSetting.sourceTo, DeviceDataType.SourceRGBA32); MRCommonHelper.getTextureOperate(index, ref cameraSetting.keyingTo, DeviceDataType.Keying); MRCommonHelper.getTextureOperate(index, ref cameraSetting.transportTo, DeviceDataType.TransportYUVA); MRCommonHelper.getDepthSetting(index, ref cameraSetting.ds); } } } public void SetFormatIndex(int index) { if (!bSetCamera) return; bSetFormat = false; if (index < 0 || index >= formatList.Count) { int first = -1; int second = -1; int three = 0; MRCommonManager.Instance.FindFormatIndex(0, ref first, ref second, ref three); index = first >= 0 ? first : (second >= 0) ? second : three; Debug.Log("formindex:" + index + " first:" + first + " second:" + second + " three:" + three); } if (index >= 0 && index < formatList.Count) { format = formatList[index]; bSetFormat = true; InitTexture(); } } /// <summary> /// 這個函數要在Unity裏主線程執行 /// </summary> public void InitTexture() { if (IsInit) { if (sourceTex == null || sourceTex.width != format.width || sourceTex.height != format.height) { sourceTex = new Texture2D(format.width, format.height, TextureFormat.RGBA32, false); sourceTex.Apply(); } if (keyingTex == null || keyingTex.width != format.width || keyingTex.height != format.height) { keyingTex = new Texture2D(format.width, format.height, TextureFormat.RGBA32, false); keyingTex.Apply(); } MRUnityHelper.SetCameraTexture(device.id, sourceTex.GetNativeTexturePtr(), keyingTex.GetNativeTexturePtr()); } } /// <summary> /// 這個函數要在Unity裏主線程執行 /// </summary> public bool Update() { if (IsOpen) { var dataType = MRCommonHelper.getDataType(device.id); if ((dataType & DeviceDataType.SourceRGBA32GPU) == DeviceDataType.SourceRGBA32GPU || (dataType & DeviceDataType.SourceRGBA32) == DeviceDataType.SourceRGBA32) { MRCommonHelper.updateTextureOperate(device.id, ref cameraSetting.sourceTo, DeviceDataType.SourceRGBA32); } if ((dataType & DeviceDataType.KeyingGPU) == DeviceDataType.KeyingGPU || (dataType & DeviceDataType.Keying) == DeviceDataType.Keying) { MRCommonHelper.updateKeySetting(device.id, ref cameraSetting.ks); MRCommonHelper.updateTextureOperate(device.id, ref cameraSetting.keyingTo, DeviceDataType.Keying); } if ((dataType & DeviceDataType.TransportYUVA) == DeviceDataType.TransportYUVA) { MRCommonHelper.updateTextureOperate(device.id, ref cameraSetting.transportTo, DeviceDataType.TransportYUVA); } if (IsDepth) MRCommonHelper.updateDepthSetting(device.id, ref cameraSetting.ds); GL.IssuePluginEvent(MRUnityHelper.GetRenderCameraFunc(), device.id); return true; } return false; } public void Open(bool bTrans, bool bKeying = true) { if (!IsInit || IsOpen) return; var dataType = DeviceDataType.SourceRGBA32GPU; if (bKeying) dataType = dataType | DeviceDataType.KeyingGPU; if (bTrans) dataType = dataType | DeviceDataType.TransportYUVA; MRCommonHelper.openCamera(device.id, format.index, dataType); } public void Close() { if (!IsInit || !IsOpen) return; MRCommonHelper.closeCamera(device.id); bSetFormat = false; bSetCamera = false; } public void UserPostProcess(bool bUser) { if (!IsInit || !IsDepth) return; MRCommonHelper.setUserPostProcess(device.id, bUser); } public void UserBG(bool bUser) { if (!IsOpen || !IsDepth) return; MRCommonHelper.saveBG(device.id, bUser); } public bool SaveBGFile(string path) { if (!IsOpen || !IsDepth) return false; return MRCommonHelper.saveBGToFile(device.id, path); } public bool LoadBG(string path) { if (!IsOpen || !IsDepth) return false; return MRCommonHelper.loadBG(device.id, path); } }
以下是在Unity裏的顯示效果。
二個設備,每一個設備都開的都是1920*1080*30FPS,其中有張原圖,有張扣色圖,四張圖一塊兒更新,不影響Unity一點。
到這差很少就完了,最說簡單說用Mediafoundation完成別的一些功能上遇到的坑 :
生成視頻時:你提供的楨率要和你寫入的速度對應上,否則視頻的快慢會改變。
錄系統聲音時,用的是wasapi,錄到必定大小,而後寫到文件,釋放內存,而後又錄。順便說下,C++標準中,想後期改文件,須要用std::ios::in | std::ios::ate,用app只能在最後追加,不能修改前面的值,單獨的ate會刪除文件。
用MF合成音頻與視頻文件,視頻文件是H264壓縮格式,音頻也必需是mp3或是acc,pcm轉acc就和把圖片流壓縮成h264在MF中的寫法差很少,沒什麼問題,在合成時發現,單獨把音頻或是視頻寫到一個文件很快,可是一塊就很是慢,其中搜到說是同時寫視頻與音頻的會有一個同步時間戳的問題,後面須要把寫音頻與寫視頻分開用線程寫,結果秒合,以下是改進後代碼。
//合成視頻 hr = pSinkWriter->BeginWriting(); //https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/20727947-98fe-4245-ad3a-8056d168a1b5/imfsinkwriter-very-slow-and-use-of-mfsinkwriterdisablethrottling?forum=mediafoundationdevelopment //這裏是個大坑,在一個線程同時寫音頻與視頻會致使pSinkWriter->WriteSample很是慢,由於同時寫的時候,會自動去同步音頻與視頻的時間戳. //在同一線程就會形成要同步時就卡一段時間,故用二個線程同時寫,讓pSinkWriter->WriteSample能自動同步不須要等待 std::future<bool> writeVideo = std::async([&videoReader, &videoIndex, &pSinkWriter, &audioTime]() { bool result = true; LONGLONG videoTimeStamp = 0;// 100-nanosecond units.100納秒 1秒= 1000000000納秒 while (true) { DWORD streamIndex, flags; CComPtr<IMFSample> videoSample = nullptr; HRESULT hr = videoReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &streamIndex, &flags, &videoTimeStamp, &videoSample); if (SUCCEEDED(hr) && videoSample) { videoSample->SetSampleTime(videoTimeStamp); hr = pSinkWriter->WriteSample(videoIndex, videoSample); } else { if (FAILED(hr)) result = false; break; } if (videoTimeStamp > audioTime) break; } return result; }); std::future<bool> writeAudio = std::async([&audioReader, &audioIndex, &pSinkWriter, &videoTime]() { bool result = true; LONGLONG audioTimeStamp = 0; while (true) { DWORD streamIndex, flags; CComPtr<IMFSample> audioSample = nullptr; HRESULT hr = audioReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &streamIndex, &flags, &audioTimeStamp, &audioSample); if (SUCCEEDED(hr) && audioSample) { audioSample->SetSampleTime(audioTimeStamp); hr = pSinkWriter->WriteSample(audioIndex, audioSample); } else { if (FAILED(hr)) result = false; break; } if (audioTimeStamp > videoTime) break; } return result; }); bool result = writeVideo.get() && writeAudio.get(); pSinkWriter->Finalize();
之後有時間講下,如何用VS2015編寫C++的安卓插件,還有用vs2015與安卓模擬器調試在C++項目中調試,並把生成的so文件給unity3d使用。