如何獲取obs視頻幀的二進(jìn)制數(shù)據(jù)
前面幾篇文章梳理了obs的錄屏和推流流程,幾條縱線整理下來,算是基本理清了obs的工作流程。
現(xiàn)在回到第一個(gè)目標(biāo):捕捉桌面的幀數(shù)據(jù),用rendertarget顯示并輸出到UE5材質(zhì)。
那么,幀數(shù)據(jù)到底存放在哪里?如何讀取?
現(xiàn)在錄屏效率最高的方法,是直接調(diào)用gpu方法去從顯存拿數(shù)據(jù),dx下的方法是AcquireNextFrame函數(shù)。
在整個(gè)工程搜索這個(gè)函數(shù),果然obs在windows下是用這個(gè)方法實(shí)現(xiàn)的錄屏
//obs 錄屏核心代碼//用dx截取當(dāng)前屏幕幀EXPORT bool gs_duplicator_update_frame(gs_duplicator_t *d){
DXGI_OUTDUPL_FRAME_INFO info;
ComPtr<ID3D11Texture2D> tex;
ComPtr<IDXGIResource> res;
HRESULT hr; if (!d->duplicator) { return false;
} if (d->updated) { return true;
}
hr = d->duplicator->AcquireNextFrame(0, &info, res.Assign()); if (hr == DXGI_ERROR_ACCESS_LOST) { return false;
} else if (hr == DXGI_ERROR_WAIT_TIMEOUT) { return true;
} else if (FAILED(hr)) { blog(LOG_ERROR, ? ? "gs_duplicator_update_frame: Failed to update "
? ? "frame (%08lX)",
? ? hr); return true;
} ? ?//關(guān)鍵步驟,這一步調(diào)用dx查詢接口,將屏幕幀寫入tex
hr = res->QueryInterface(__uuidof(ID3D11Texture2D),
(void **)tex.Assign()); if (FAILED(hr)) { blog(LOG_ERROR, ? ? "gs_duplicator_update_frame: Failed to query "
? ? "ID3D11Texture2D (%08lX)",
? ? hr);
d->duplicator->ReleaseFrame(); return true;
} ? ?//copy材質(zhì)到d->duplicator->texture
copy_texture(d, tex);
d->duplicator->ReleaseFrame();
d->updated = true; return true;
}
看到這里,明白了怪不得之前所有結(jié)構(gòu)都找不到圖像幀的二進(jìn)制data數(shù)據(jù)。
因?yàn)閛bs用的都是directX或openGL 的texture來存儲(chǔ)data數(shù)據(jù),這樣做的好處是copy和渲染都直接在顯存操作,避免了內(nèi)存和顯存交換數(shù)據(jù)進(jìn)行的效率損耗。
obs是優(yōu)雅了,但太渾然一體了。導(dǎo)致我想開個(gè)口子從obs拿二進(jìn)制數(shù)據(jù)到Unrea5進(jìn)行渲染就不容易做了,最簡單的辦法就是在obs中增加一個(gè)內(nèi)存數(shù)據(jù)desktopdata,直接掛在obs下面,并增加相應(yīng)的訪問接口。
對(duì)應(yīng)的獲取可以用這個(gè)接口,從顯存map地址可供cpu訪問
bool gs_texture_map(gs_texture_t *tex, uint8_t **ptr, uint32_t *linesize){
HRESULT hr; if (tex->type != GS_TEXTURE_2D) return false;
gs_texture_2d *tex2d = static_cast<gs_texture_2d *>(tex);
D3D11_MAPPED_SUBRESOURCE map;
hr = tex2d->device->context->Map(tex2d->texture, 0,
D3D11_MAP_WRITE_DISCARD, 0, &map); if (FAILED(hr)) return false;
*ptr = (uint8_t *)map.pData;
*linesize = map.RowPitch; return true;
}
?
但這樣效率肯定不會(huì)高,因?yàn)閛bs調(diào)用顯存接口錄屏之后, 還需要從顯存往內(nèi)存desktopdata copy一次數(shù)據(jù)。
然后我用desktopdata數(shù)據(jù)再從內(nèi)存copy到Unreal5的顯存,這會(huì)中斷unreal5本身的渲染,去等待我這次copy完成。
有貌似完美的解決方法,如果我把desktopdata創(chuàng)建到顯存,去暫存錄屏數(shù)據(jù),讓unreal5直接訪問顯存的desktopdata,去copy材質(zhì),理論上是完美的,但有一個(gè)最大的雷,obs用的是dx11,unreal5.1用的是dx12,這樣copy感覺會(huì)遇到一些不可測(cè)的風(fēng)險(xiǎn)。
剩下的方法有:
1 不用obs的獲取桌面方法,直接在unreal5里用dx12重寫獲取桌面接口,但也意味著無法使用obs的rtmp推流相關(guān)流程和接口,工作量很大。而且obs最厲害的是音頻和視頻多通道混合,這些都是我想用的。
2 幫obs升級(jí)dx12,這個(gè)可以干,但工作量同樣很大,但比1還是簡單一些。