This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/gameengine/VideoTexture/VideoDeckLink.cpp
Benoit Bolsee eea89417f4 BGE: DeckLink card support for video capture and streaming.
You can capture and stream video in the BGE using the DeckLink video
   cards from Black Magic Design. You need a card and Desktop Video software
   version 10.4 or above to use these features in the BGE.
   Many thanks to Nuno Estanquiero who tested the patch extensively
   on a variety of Decklink products, it wouldn't have been possible without
   his help.
   You can find a brief summary of the decklink features here: https://wiki.blender.org/index.php/Dev:Source/GameEngine/Decklink
   The full API details and samples are in the Python API documentation.

bge.texture.VideoDeckLink(format, capture=0):

   Use this object to capture a video stream. the format argument describes
   the video and pixel formats and the capture argument the card number.
   This object can be used as a source for bge.texture.Texture so that the frame
   is sent to the GPU, or by itself using the new refresh method to get the video
   frame in a buffer.
   The frames are usually not in RGB but in YUV format (8bit or 10bit); they
   require a shader to extract the RGB components in the GPU. Details and sample
   shaders in the documentation.
   3D video capture is supported: the frames are double height with left and right
   eyes in top-bottom order. The 'eye' uniform (see setUniformEyef) can be used to
   sample the 3D frame when the BGE is also in stereo mode. This allows to composite
   a 3D video stream with a 3D scene and render it in stereo.
   In Windows, and if you have a nVidia Quadro GPU, you can benefit of an additional
   performance boost by using 'GPUDirect': a method to send a video frame to the GPU
   without going through the OGL driver. The 'pinned memory' OGL extension is also
   supported (only on high-end AMD GPU) with the same effect.

bge.texture.DeckLink(cardIdx=0, format=""):

   Use this object to send video frame to a DeckLink card. Only the immediate mode
   is supported, the scheduled mode is not implemented.
   This object is similar to bge.texture.Texture: you need to attach a image source
   and call refresh() to compute and send the frame to the card.
   This object is best suited for video keying: a video stream (not captured) flows
   through the card and the frame you send to the card are displayed above it (the
   card does the compositing automatically based on the alpha channel).
   At the time of this commit, 3D video keying is supported in the BGE but not in the
   DeckLink card due to a color space issue.
2016-06-11 22:26:05 +02:00

1229 lines
36 KiB
C++

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2015, Blender Foundation
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): Blender Foundation.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file gameengine/VideoTexture/VideoDeckLink.cpp
* \ingroup bgevideotex
*/
#ifdef WITH_GAMEENGINE_DECKLINK
// FFmpeg defines its own version of stdint.h on Windows.
// Decklink needs FFmpeg, so it uses its version of stdint.h
// this is necessary for INT64_C macro
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif
// this is necessary for UINTPTR_MAX (used by atomic-ops)
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#ifdef __STDC_LIMIT_MACROS /* else it may be unused */
#endif
#endif
#include <stdint.h>
#include <string.h>
#ifndef WIN32
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/mman.h>
#endif
#include "atomic_ops.h"
#include "MEM_guardedalloc.h"
#include "PIL_time.h"
#include "VideoDeckLink.h"
#include "DeckLink.h"
#include "Exception.h"
#include "KX_KetsjiEngine.h"
#include "KX_PythonInit.h"
extern ExceptionID DeckLinkInternalError;
ExceptionID SourceVideoOnlyCapture, VideoDeckLinkBadFormat, VideoDeckLinkOpenCard, VideoDeckLinkDvpInternalError, VideoDeckLinkPinMemoryError;
ExpDesc SourceVideoOnlyCaptureDesc(SourceVideoOnlyCapture, "This video source only allows live capture");
ExpDesc VideoDeckLinkBadFormatDesc(VideoDeckLinkBadFormat, "Invalid or unsupported capture format, should be <mode>/<pixel>[/3D]");
ExpDesc VideoDeckLinkOpenCardDesc(VideoDeckLinkOpenCard, "Cannot open capture card, check if driver installed");
ExpDesc VideoDeckLinkDvpInternalErrorDesc(VideoDeckLinkDvpInternalError, "DVP API internal error, please report");
ExpDesc VideoDeckLinkPinMemoryErrorDesc(VideoDeckLinkPinMemoryError, "Error pinning memory");
#ifdef WIN32
////////////////////////////////////////////
// SynInfo
//
// Sets up a semaphore which is shared between the GPU and CPU and used to
// synchronise access to DVP buffers.
#define DVP_CHECK(cmd) if ((cmd) != DVP_STATUS_OK) THRWEXCP(VideoDeckLinkDvpInternalError, S_OK)
struct SyncInfo
{
SyncInfo(uint32_t semaphoreAllocSize, uint32_t semaphoreAddrAlignment)
{
mSemUnaligned = (uint32_t*)malloc(semaphoreAllocSize + semaphoreAddrAlignment - 1);
// Apply alignment constraints
uint64_t val = (uint64_t)mSemUnaligned;
val += semaphoreAddrAlignment - 1;
val &= ~((uint64_t)semaphoreAddrAlignment - 1);
mSem = (uint32_t*)val;
// Initialise
mSem[0] = 0;
mReleaseValue = 0;
mAcquireValue = 0;
// Setup DVP sync object and import it
DVPSyncObjectDesc syncObjectDesc;
syncObjectDesc.externalClientWaitFunc = NULL;
syncObjectDesc.sem = (uint32_t*)mSem;
DVP_CHECK(dvpImportSyncObject(&syncObjectDesc, &mDvpSync));
}
~SyncInfo()
{
dvpFreeSyncObject(mDvpSync);
free((void*)mSemUnaligned);
}
volatile uint32_t* mSem;
volatile uint32_t* mSemUnaligned;
volatile uint32_t mReleaseValue;
volatile uint32_t mAcquireValue;
DVPSyncObjectHandle mDvpSync;
};
////////////////////////////////////////////
// TextureTransferDvp: transfer with GPUDirect
////////////////////////////////////////////
class TextureTransferDvp : public TextureTransfer
{
public:
TextureTransferDvp(DVPBufferHandle dvpTextureHandle, TextureDesc *pDesc, void *address, uint32_t allocatedSize)
{
DVPSysmemBufferDesc sysMemBuffersDesc;
mExtSync = NULL;
mGpuSync = NULL;
mDvpSysMemHandle = 0;
mDvpTextureHandle = 0;
mTextureHeight = 0;
mAllocatedSize = 0;
mBuffer = NULL;
if (!_PinBuffer(address, allocatedSize))
THRWEXCP(VideoDeckLinkPinMemoryError, S_OK);
mAllocatedSize = allocatedSize;
mBuffer = address;
try {
if (!mBufferAddrAlignment) {
DVP_CHECK(dvpGetRequiredConstantsGLCtx(&mBufferAddrAlignment, &mBufferGpuStrideAlignment,
&mSemaphoreAddrAlignment, &mSemaphoreAllocSize,
&mSemaphorePayloadOffset, &mSemaphorePayloadSize));
}
mExtSync = new SyncInfo(mSemaphoreAllocSize, mSemaphoreAddrAlignment);
mGpuSync = new SyncInfo(mSemaphoreAllocSize, mSemaphoreAddrAlignment);
sysMemBuffersDesc.width = pDesc->width;
sysMemBuffersDesc.height = pDesc->height;
sysMemBuffersDesc.stride = pDesc->stride;
switch (pDesc->format) {
case GL_RED_INTEGER:
sysMemBuffersDesc.format = DVP_RED_INTEGER;
break;
default:
sysMemBuffersDesc.format = DVP_BGRA;
break;
}
switch (pDesc->type) {
case GL_UNSIGNED_BYTE:
sysMemBuffersDesc.type = DVP_UNSIGNED_BYTE;
break;
case GL_UNSIGNED_INT_2_10_10_10_REV:
sysMemBuffersDesc.type = DVP_UNSIGNED_INT_2_10_10_10_REV;
break;
case GL_UNSIGNED_INT_8_8_8_8:
sysMemBuffersDesc.type = DVP_UNSIGNED_INT_8_8_8_8;
break;
case GL_UNSIGNED_INT_10_10_10_2:
sysMemBuffersDesc.type = DVP_UNSIGNED_INT_10_10_10_2;
break;
default:
sysMemBuffersDesc.type = DVP_UNSIGNED_INT;
break;
}
sysMemBuffersDesc.size = pDesc->width * pDesc->height * 4;
sysMemBuffersDesc.bufAddr = mBuffer;
DVP_CHECK(dvpCreateBuffer(&sysMemBuffersDesc, &mDvpSysMemHandle));
DVP_CHECK(dvpBindToGLCtx(mDvpSysMemHandle));
mDvpTextureHandle = dvpTextureHandle;
mTextureHeight = pDesc->height;
}
catch (Exception &) {
clean();
throw;
}
}
~TextureTransferDvp()
{
clean();
}
virtual void PerformTransfer()
{
// perform the transfer
// tell DVP that the old texture buffer will no longer be used
dvpMapBufferEndAPI(mDvpTextureHandle);
// do we need this?
mGpuSync->mReleaseValue++;
dvpBegin();
// Copy from system memory to GPU texture
dvpMapBufferWaitDVP(mDvpTextureHandle);
dvpMemcpyLined(mDvpSysMemHandle, mExtSync->mDvpSync, mExtSync->mAcquireValue, DVP_TIMEOUT_IGNORED,
mDvpTextureHandle, mGpuSync->mDvpSync, mGpuSync->mReleaseValue, 0, mTextureHeight);
dvpMapBufferEndDVP(mDvpTextureHandle);
dvpEnd();
dvpMapBufferWaitAPI(mDvpTextureHandle);
// the transfer is now complete and the texture is ready for use
}
private:
static uint32_t mBufferAddrAlignment;
static uint32_t mBufferGpuStrideAlignment;
static uint32_t mSemaphoreAddrAlignment;
static uint32_t mSemaphoreAllocSize;
static uint32_t mSemaphorePayloadOffset;
static uint32_t mSemaphorePayloadSize;
void clean()
{
if (mDvpSysMemHandle) {
dvpUnbindFromGLCtx(mDvpSysMemHandle);
dvpDestroyBuffer(mDvpSysMemHandle);
}
if (mExtSync)
delete mExtSync;
if (mGpuSync)
delete mGpuSync;
if (mBuffer)
_UnpinBuffer(mBuffer, mAllocatedSize);
}
SyncInfo* mExtSync;
SyncInfo* mGpuSync;
DVPBufferHandle mDvpSysMemHandle;
DVPBufferHandle mDvpTextureHandle;
uint32_t mTextureHeight;
uint32_t mAllocatedSize;
void* mBuffer;
};
uint32_t TextureTransferDvp::mBufferAddrAlignment;
uint32_t TextureTransferDvp::mBufferGpuStrideAlignment;
uint32_t TextureTransferDvp::mSemaphoreAddrAlignment;
uint32_t TextureTransferDvp::mSemaphoreAllocSize;
uint32_t TextureTransferDvp::mSemaphorePayloadOffset;
uint32_t TextureTransferDvp::mSemaphorePayloadSize;
#endif
////////////////////////////////////////////
// TextureTransferOGL: transfer using standard OGL buffers
////////////////////////////////////////////
class TextureTransferOGL : public TextureTransfer
{
public:
TextureTransferOGL(GLuint texId, TextureDesc *pDesc, void *address)
{
memcpy(&mDesc, pDesc, sizeof(mDesc));
mTexId = texId;
mBuffer = address;
// as we cache transfer object, we will create one texture to hold the buffer
glGenBuffers(1, &mUnpinnedTextureBuffer);
// create a storage for it
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mUnpinnedTextureBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, pDesc->size, NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
~TextureTransferOGL()
{
glDeleteBuffers(1, &mUnpinnedTextureBuffer);
}
virtual void PerformTransfer()
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mUnpinnedTextureBuffer);
glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, mDesc.size, mBuffer);
glBindTexture(GL_TEXTURE_2D, mTexId);
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDesc.width, mDesc.height, mDesc.format, mDesc.type, NULL);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
private:
// intermediate texture to receive the buffer
GLuint mUnpinnedTextureBuffer;
// target texture to receive the image
GLuint mTexId;
// buffer
void *mBuffer;
// characteristic of the image
TextureDesc mDesc;
};
////////////////////////////////////////////
// TextureTransferPMB: transfer using pinned memory buffer
////////////////////////////////////////////
class TextureTransferPMD : public TextureTransfer
{
public:
TextureTransferPMD(GLuint texId, TextureDesc *pDesc, void *address, uint32_t allocatedSize)
{
memcpy(&mDesc, pDesc, sizeof(mDesc));
mTexId = texId;
mBuffer = address;
mAllocatedSize = allocatedSize;
_PinBuffer(address, allocatedSize);
// as we cache transfer object, we will create one texture to hold the buffer
glGenBuffers(1, &mPinnedTextureBuffer);
// create a storage for it
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, mPinnedTextureBuffer);
glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, pDesc->size, address, GL_STREAM_DRAW);
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
}
~TextureTransferPMD()
{
glDeleteBuffers(1, &mPinnedTextureBuffer);
if (mBuffer)
_UnpinBuffer(mBuffer, mAllocatedSize);
}
virtual void PerformTransfer()
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mPinnedTextureBuffer);
glBindTexture(GL_TEXTURE_2D, mTexId);
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDesc.width, mDesc.height, mDesc.format, mDesc.type, NULL);
// wait for the trasnfer to complete
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, 40 * 1000 * 1000); // timeout in nanosec
glDeleteSync(fence);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
private:
// intermediate texture to receive the buffer
GLuint mPinnedTextureBuffer;
// target texture to receive the image
GLuint mTexId;
// buffer
void *mBuffer;
// the allocated size
uint32_t mAllocatedSize;
// characteristic of the image
TextureDesc mDesc;
};
bool TextureTransfer::_PinBuffer(void *address, uint32_t size)
{
#ifdef WIN32
return VirtualLock(address, size);
#elif defined(_POSIX_MEMLOCK_RANGE)
return !mlock(address, size);
#endif
}
void TextureTransfer::_UnpinBuffer(void* address, uint32_t size)
{
#ifdef WIN32
VirtualUnlock(address, size);
#elif defined(_POSIX_MEMLOCK_RANGE)
munlock(address, size);
#endif
}
////////////////////////////////////////////
// PinnedMemoryAllocator
////////////////////////////////////////////
// static members
bool PinnedMemoryAllocator::mGPUDirectInitialized = false;
bool PinnedMemoryAllocator::mHasDvp = false;
bool PinnedMemoryAllocator::mHasAMDPinnedMemory = false;
size_t PinnedMemoryAllocator::mReservedProcessMemory = 0;
bool PinnedMemoryAllocator::ReserveMemory(size_t size)
{
#ifdef WIN32
// Increase the process working set size to allow pinning of memory.
if (size <= mReservedProcessMemory)
return true;
SIZE_T dwMin = 0, dwMax = 0;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_SET_QUOTA, FALSE, GetCurrentProcessId());
if (!hProcess)
return false;
// Retrieve the working set size of the process.
if (!dwMin && !GetProcessWorkingSetSize(hProcess, &dwMin, &dwMax))
return false;
BOOL res = SetProcessWorkingSetSize(hProcess, (size - mReservedProcessMemory) + dwMin, (size - mReservedProcessMemory) + dwMax);
if (!res)
return false;
mReservedProcessMemory = size;
CloseHandle(hProcess);
return true;
#else
struct rlimit rlim;
if (getrlimit(RLIMIT_MEMLOCK, &rlim) == 0) {
if (rlim.rlim_cur < size) {
if (rlim.rlim_max < size)
rlim.rlim_max = size;
rlim.rlim_cur = size;
return !setrlimit(RLIMIT_MEMLOCK, &rlim);
}
}
return false;
#endif
}
PinnedMemoryAllocator::PinnedMemoryAllocator(unsigned cacheSize, size_t memSize) :
mRefCount(1U),
#ifdef WIN32
mDvpCaptureTextureHandle(0),
#endif
mTexId(0),
mBufferCacheSize(cacheSize)
{
pthread_mutex_init(&mMutex, NULL);
// do it once
if (!mGPUDirectInitialized) {
#ifdef WIN32
// In windows, AMD_pinned_memory option is not available,
// we must use special DVP API only available for Quadro cards
const char* renderer = (const char *)glGetString(GL_RENDERER);
mHasDvp = (strstr(renderer, "Quadro") != NULL);
if (mHasDvp) {
// In case the DLL is not in place, don't fail, just fallback on OpenGL
if (dvpInitGLContext(DVP_DEVICE_FLAGS_SHARE_APP_CONTEXT) != DVP_STATUS_OK) {
printf("Warning: Could not initialize DVP context, fallback on OpenGL transfer.\nInstall dvp.dll to take advantage of nVidia GPUDirect.\n");
mHasDvp = false;
}
}
#endif
if (GLEW_AMD_pinned_memory)
mHasAMDPinnedMemory = true;
mGPUDirectInitialized = true;
}
if (mHasDvp || mHasAMDPinnedMemory) {
ReserveMemory(memSize);
}
}
PinnedMemoryAllocator::~PinnedMemoryAllocator()
{
void *address;
// first clean the cache if not already done
while (!mBufferCache.empty()) {
address = mBufferCache.back();
mBufferCache.pop_back();
_ReleaseBuffer(address);
}
// clean preallocated buffers
while (!mAllocatedSize.empty()) {
address = mAllocatedSize.begin()->first;
_ReleaseBuffer(address);
}
#ifdef WIN32
if (mDvpCaptureTextureHandle)
dvpDestroyBuffer(mDvpCaptureTextureHandle);
#endif
}
void PinnedMemoryAllocator::TransferBuffer(void* address, TextureDesc* texDesc, GLuint texId)
{
uint32_t allocatedSize = 0;
TextureTransfer *pTransfer = NULL;
Lock();
if (mAllocatedSize.count(address) > 0)
allocatedSize = mAllocatedSize[address];
Unlock();
if (!allocatedSize)
// internal error!!
return;
if (mTexId != texId)
{
// first time we try to send data to the GPU, allocate a buffer for the texture
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, texDesc->internalFormat, texDesc->width, texDesc->height, 0, texDesc->format, texDesc->type, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
mTexId = texId;
}
#ifdef WIN32
if (mHasDvp)
{
if (!mDvpCaptureTextureHandle)
{
// bind DVP to the OGL texture
DVP_CHECK(dvpCreateGPUTextureGL(texId, &mDvpCaptureTextureHandle));
}
}
#endif
Lock();
if (mPinnedBuffer.count(address) > 0)
{
pTransfer = mPinnedBuffer[address];
}
Unlock();
if (!pTransfer)
{
#ifdef WIN32
if (mHasDvp)
pTransfer = new TextureTransferDvp(mDvpCaptureTextureHandle, texDesc, address, allocatedSize);
else
#endif
if (mHasAMDPinnedMemory) {
pTransfer = new TextureTransferPMD(texId, texDesc, address, allocatedSize);
}
else {
pTransfer = new TextureTransferOGL(texId, texDesc, address);
}
if (pTransfer)
{
Lock();
mPinnedBuffer[address] = pTransfer;
Unlock();
}
}
if (pTransfer)
pTransfer->PerformTransfer();
}
// IUnknown methods
HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::QueryInterface(REFIID /*iid*/, LPVOID* /*ppv*/)
{
return E_NOTIMPL;
}
ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::AddRef(void)
{
return atomic_add_uint32(&mRefCount, 1U);
}
ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::Release(void)
{
uint32_t newCount = atomic_sub_uint32(&mRefCount, 1U);
if (newCount == 0)
delete this;
return (ULONG)newCount;
}
// IDeckLinkMemoryAllocator methods
HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::AllocateBuffer(dl_size_t bufferSize, void* *allocatedBuffer)
{
Lock();
if (mBufferCache.empty())
{
// Allocate memory on a page boundary
// Note: aligned alloc exist in Blender but only for small alignment, use direct allocation then.
// Note: the DeckLink API tries to allocate up to 65 buffer in advance, we will limit this to 3
// because we don't need any caching
if (mAllocatedSize.size() >= mBufferCacheSize)
*allocatedBuffer = NULL;
else {
#ifdef WIN32
*allocatedBuffer = VirtualAlloc(NULL, bufferSize, MEM_COMMIT | MEM_RESERVE | MEM_WRITE_WATCH, PAGE_READWRITE);
#else
if (posix_memalign(allocatedBuffer, 4096, bufferSize) != 0)
*allocatedBuffer = NULL;
#endif
mAllocatedSize[*allocatedBuffer] = bufferSize;
}
}
else {
// Re-use most recently ReleaseBuffer'd address
*allocatedBuffer = mBufferCache.back();
mBufferCache.pop_back();
}
Unlock();
return (*allocatedBuffer) ? S_OK : E_OUTOFMEMORY;
}
HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::ReleaseBuffer(void* buffer)
{
HRESULT result = S_OK;
Lock();
if (mBufferCache.size() < mBufferCacheSize) {
mBufferCache.push_back(buffer);
}
else {
result = _ReleaseBuffer(buffer);
}
Unlock();
return result;
}
HRESULT PinnedMemoryAllocator::_ReleaseBuffer(void* buffer)
{
TextureTransfer *pTransfer;
if (mAllocatedSize.count(buffer) == 0) {
// Internal error!!
return S_OK;
}
else {
// No room left in cache, so un-pin (if it was pinned) and free this buffer
if (mPinnedBuffer.count(buffer) > 0) {
pTransfer = mPinnedBuffer[buffer];
mPinnedBuffer.erase(buffer);
delete pTransfer;
}
#ifdef WIN32
VirtualFree(buffer, 0, MEM_RELEASE);
#else
free(buffer);
#endif
mAllocatedSize.erase(buffer);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::Commit()
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::Decommit()
{
void *buffer;
Lock();
while (!mBufferCache.empty()) {
// Cleanup any frames allocated and pinned in AllocateBuffer() but not freed in ReleaseBuffer()
buffer = mBufferCache.back();
mBufferCache.pop_back();
_ReleaseBuffer(buffer);
}
Unlock();
return S_OK;
}
////////////////////////////////////////////
// Capture Delegate Class
////////////////////////////////////////////
CaptureDelegate::CaptureDelegate(VideoDeckLink* pOwner) : mpOwner(pOwner)
{
}
HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputFrame, IDeckLinkAudioInputPacket* /*audioPacket*/)
{
if (!inputFrame) {
// It's possible to receive a NULL inputFrame, but a valid audioPacket. Ignore audio-only frame.
return S_OK;
}
if ((inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource) {
// let's not bother transferring frames if there is no source
return S_OK;
}
mpOwner->VideoFrameArrived(inputFrame);
return S_OK;
}
HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode *newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags)
{
return S_OK;
}
// macro for exception handling and logging
#define CATCH_EXCP catch (Exception & exp) \
{ exp.report(); m_status = SourceError; }
// class VideoDeckLink
// constructor
VideoDeckLink::VideoDeckLink (HRESULT * hRslt) : VideoBase(),
mDLInput(NULL),
mUse3D(false),
mFrameWidth(0),
mFrameHeight(0),
mpAllocator(NULL),
mpCaptureDelegate(NULL),
mpCacheFrame(NULL),
mClosing(false)
{
mDisplayMode = (BMDDisplayMode)0;
mPixelFormat = (BMDPixelFormat)0;
pthread_mutex_init(&mCacheMutex, NULL);
}
// destructor
VideoDeckLink::~VideoDeckLink ()
{
LockCache();
mClosing = true;
if (mpCacheFrame)
{
mpCacheFrame->Release();
mpCacheFrame = NULL;
}
UnlockCache();
if (mDLInput != NULL)
{
// Cleanup for Capture
mDLInput->StopStreams();
mDLInput->SetCallback(NULL);
mDLInput->DisableVideoInput();
mDLInput->DisableAudioInput();
mDLInput->FlushStreams();
if (mDLInput->Release() != 0) {
printf("Reference count not NULL on DeckLink device when closing it, please report!\n");
}
mDLInput = NULL;
}
if (mpAllocator)
{
// if the device was properly cleared, this should be 0
if (mpAllocator->Release() != 0) {
printf("Reference count not NULL on Allocator when closing it, please report!\n");
}
mpAllocator = NULL;
}
if (mpCaptureDelegate)
{
delete mpCaptureDelegate;
mpCaptureDelegate = NULL;
}
}
void VideoDeckLink::refresh(void)
{
m_avail = false;
}
// release components
bool VideoDeckLink::release()
{
// release
return true;
}
// open video file
void VideoDeckLink::openFile (char *filename)
{
// only live capture on this device
THRWEXCP(SourceVideoOnlyCapture, S_OK);
}
// open video capture device
void VideoDeckLink::openCam (char *format, short camIdx)
{
IDeckLinkDisplayModeIterator* pDLDisplayModeIterator;
BMDDisplayModeSupport modeSupport;
IDeckLinkDisplayMode* pDLDisplayMode;
IDeckLinkIterator* pIterator;
BMDTimeValue frameDuration;
BMDTimeScale frameTimescale;
IDeckLink* pDL;
uint32_t displayFlags, inputFlags;
char *pPixel, *p3D, *pEnd, *pSize;
size_t len;
int i, modeIdx, cacheSize;
// format is constructed as <displayMode>/<pixelFormat>[/3D][:<cacheSize>]
// <displayMode> takes the form of BMDDisplayMode identifier minus the 'bmdMode' prefix.
// This implementation understands all the modes defined in SDK 10.3.1 but you can alternatively
// use the 4 characters internal representation of the mode (e.g. 'HD1080p24' == '24ps')
// <pixelFormat> takes the form of BMDPixelFormat identifier minus the 'bmdFormat' prefix.
// This implementation understand all the formats defined in SDK 10.32.1 but you can alternatively
// use the 4 characters internal representation of the format (e.g. '10BitRGB' == 'r210')
// Not all combinations of mode and pixel format are possible and it also depends on the card!
// Use /3D postfix if you are capturing a 3D stream with frame packing
// Example: To capture FullHD 1920x1080@24Hz with 3D packing and 4:4:4 10 bits RGB pixel format, use
// "HD1080p24/10BitRGB/3D" (same as "24ps/r210/3D")
// (this will be the normal capture format for FullHD on the DeckLink 4k extreme)
if ((pSize = strchr(format, ':')) != NULL) {
cacheSize = strtol(pSize+1, &pEnd, 10);
}
else {
cacheSize = 8;
pSize = format + strlen(format);
}
if ((pPixel = strchr(format, '/')) == NULL ||
((p3D = strchr(pPixel + 1, '/')) != NULL && strncmp(p3D, "/3D", pSize-p3D)))
THRWEXCP(VideoDeckLinkBadFormat, S_OK);
mUse3D = (p3D) ? true : false;
// to simplify pixel format parsing
if (!p3D)
p3D = pSize;
// read the mode
len = (size_t)(pPixel - format);
// accept integer display mode
try {
// throws if bad mode
decklink_ReadDisplayMode(format, len, &mDisplayMode);
// found a valid mode, remember that we do not look for an index
modeIdx = -1;
}
catch (Exception &) {
// accept also purely numerical mode as a mode index
modeIdx = strtol(format, &pEnd, 10);
if (pEnd != pPixel || modeIdx < 0)
// not a pure number, give up
throw;
}
// skip /
pPixel++;
len = (size_t)(p3D - pPixel);
// throws if bad format
decklink_ReadPixelFormat(pPixel, len, &mPixelFormat);
// Caution: DeckLink API used from this point, make sure entity are released before throwing
// open the card
pIterator = BMD_CreateDeckLinkIterator();
if (pIterator) {
i = 0;
while (pIterator->Next(&pDL) == S_OK) {
if (i == camIdx) {
if (pDL->QueryInterface(IID_IDeckLinkInput, (void**)&mDLInput) != S_OK)
mDLInput = NULL;
pDL->Release();
break;
}
i++;
pDL->Release();
}
pIterator->Release();
}
if (!mDLInput)
THRWEXCP(VideoDeckLinkOpenCard, S_OK);
// check if display mode and pixel format are supported
if (mDLInput->GetDisplayModeIterator(&pDLDisplayModeIterator) != S_OK)
THRWEXCP(DeckLinkInternalError, S_OK);
pDLDisplayMode = NULL;
displayFlags = (mUse3D) ? bmdDisplayModeSupports3D : 0;
inputFlags = (mUse3D) ? bmdVideoInputDualStream3D : bmdVideoInputFlagDefault;
while (pDLDisplayModeIterator->Next(&pDLDisplayMode) == S_OK)
{
if (modeIdx == 0 || pDLDisplayMode->GetDisplayMode() == mDisplayMode) {
// in case we get here because of modeIdx, make sure we have mDisplayMode set
mDisplayMode = pDLDisplayMode->GetDisplayMode();
if ((pDLDisplayMode->GetFlags() & displayFlags) == displayFlags &&
mDLInput->DoesSupportVideoMode(mDisplayMode, mPixelFormat, inputFlags, &modeSupport, NULL) == S_OK &&
modeSupport == bmdDisplayModeSupported)
{
break;
}
}
pDLDisplayMode->Release();
pDLDisplayMode = NULL;
if (modeIdx-- == 0) {
// reached the correct mode index but it does not meet the pixel format, give up
break;
}
}
pDLDisplayModeIterator->Release();
if (pDLDisplayMode == NULL)
THRWEXCP(VideoDeckLinkBadFormat, S_OK);
mFrameWidth = pDLDisplayMode->GetWidth();
mFrameHeight = pDLDisplayMode->GetHeight();
mTextureDesc.height = (mUse3D) ? 2 * mFrameHeight : mFrameHeight;
pDLDisplayMode->GetFrameRate(&frameDuration, &frameTimescale);
pDLDisplayMode->Release();
// for information, in case the application wants to know
m_size[0] = mFrameWidth;
m_size[1] = mTextureDesc.height;
m_frameRate = (float)frameTimescale / (float)frameDuration;
switch (mPixelFormat)
{
case bmdFormat8BitYUV:
// 2 pixels per word
mTextureDesc.stride = mFrameWidth * 2;
mTextureDesc.width = mFrameWidth / 2;
mTextureDesc.internalFormat = GL_RGBA;
mTextureDesc.format = GL_BGRA;
mTextureDesc.type = GL_UNSIGNED_BYTE;
break;
case bmdFormat10BitYUV:
// 6 pixels in 4 words, rounded to 48 pixels
mTextureDesc.stride = ((mFrameWidth + 47) / 48) * 128;
mTextureDesc.width = mTextureDesc.stride/4;
mTextureDesc.internalFormat = GL_RGB10_A2;
mTextureDesc.format = GL_BGRA;
mTextureDesc.type = GL_UNSIGNED_INT_2_10_10_10_REV;
break;
case bmdFormat8BitARGB:
mTextureDesc.stride = mFrameWidth * 4;
mTextureDesc.width = mFrameWidth;
mTextureDesc.internalFormat = GL_RGBA;
mTextureDesc.format = GL_BGRA;
mTextureDesc.type = GL_UNSIGNED_INT_8_8_8_8;
break;
case bmdFormat8BitBGRA:
mTextureDesc.stride = mFrameWidth * 4;
mTextureDesc.width = mFrameWidth;
mTextureDesc.internalFormat = GL_RGBA;
mTextureDesc.format = GL_BGRA;
mTextureDesc.type = GL_UNSIGNED_BYTE;
break;
case bmdFormat10BitRGBXLE:
// 1 pixel per word, rounded to 64 pixels
mTextureDesc.stride = ((mFrameWidth + 63) / 64) * 256;
mTextureDesc.width = mTextureDesc.stride/4;
mTextureDesc.internalFormat = GL_RGB10_A2;
mTextureDesc.format = GL_RGBA;
mTextureDesc.type = GL_UNSIGNED_INT_10_10_10_2;
break;
case bmdFormat10BitRGBX:
case bmdFormat10BitRGB:
// 1 pixel per word, rounded to 64 pixels
mTextureDesc.stride = ((mFrameWidth + 63) / 64) * 256;
mTextureDesc.width = mTextureDesc.stride/4;
mTextureDesc.internalFormat = GL_R32UI;
mTextureDesc.format = GL_RED_INTEGER;
mTextureDesc.type = GL_UNSIGNED_INT;
break;
case bmdFormat12BitRGB:
case bmdFormat12BitRGBLE:
// 8 pixels in 9 word
mTextureDesc.stride = (mFrameWidth * 36) / 8;
mTextureDesc.width = mTextureDesc.stride/4;
mTextureDesc.internalFormat = GL_R32UI;
mTextureDesc.format = GL_RED_INTEGER;
mTextureDesc.type = GL_UNSIGNED_INT;
break;
default:
// for unknown pixel format, this will be resolved when a frame arrives
mTextureDesc.format = GL_RED_INTEGER;
mTextureDesc.type = GL_UNSIGNED_INT;
break;
}
// reserve memory for cache frame + 1 to accomodate for pixel format that we don't know yet
// note: we can't use stride as it is not yet known if the pixel format is unknown
// use instead the frame width as in worst case it's not much different (e.g. HD720/10BITYUV: 1296 pixels versus 1280)
// note: some pixel format take more than 4 bytes take that into account (9/8 versus 1)
mpAllocator = new PinnedMemoryAllocator(cacheSize, mFrameWidth*mTextureDesc.height * 4 * (1+cacheSize*9/8));
if (mDLInput->SetVideoInputFrameMemoryAllocator(mpAllocator) != S_OK)
THRWEXCP(DeckLinkInternalError, S_OK);
mpCaptureDelegate = new CaptureDelegate(this);
if (mDLInput->SetCallback(mpCaptureDelegate) != S_OK)
THRWEXCP(DeckLinkInternalError, S_OK);
if (mDLInput->EnableVideoInput(mDisplayMode, mPixelFormat, ((mUse3D) ? bmdVideoInputDualStream3D : bmdVideoInputFlagDefault)) != S_OK)
// this shouldn't failed, we tested above
THRWEXCP(DeckLinkInternalError, S_OK);
// just in case it is needed to capture from certain cards, we don't check error because we don't need audio
mDLInput->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2);
// open base class
VideoBase::openCam(format, camIdx);
// ready to capture, will start when application calls play()
}
// play video
bool VideoDeckLink::play (void)
{
try
{
// if object is able to play
if (VideoBase::play())
{
mDLInput->FlushStreams();
return (mDLInput->StartStreams() == S_OK);
}
}
CATCH_EXCP;
return false;
}
// pause video
bool VideoDeckLink::pause (void)
{
try
{
if (VideoBase::pause())
{
mDLInput->PauseStreams();
return true;
}
}
CATCH_EXCP;
return false;
}
// stop video
bool VideoDeckLink::stop (void)
{
try
{
VideoBase::stop();
mDLInput->StopStreams();
return true;
}
CATCH_EXCP;
return false;
}
// set video range
void VideoDeckLink::setRange (double start, double stop)
{
}
// set framerate
void VideoDeckLink::setFrameRate (float rate)
{
}
// image calculation
// send cache frame directly to GPU
void VideoDeckLink::calcImage (unsigned int texId, double ts)
{
IDeckLinkVideoInputFrame* pFrame;
LockCache();
pFrame = mpCacheFrame;
mpCacheFrame = NULL;
UnlockCache();
if (pFrame) {
// BUG: the dvpBindToGLCtx function fails the first time it is used, don't know why.
// This causes an exception to be thrown.
// This should be fixed but in the meantime we will catch the exception because
// it is crucial that we release the frame to keep the reference count right on the DeckLink device
try {
uint32_t rowSize = pFrame->GetRowBytes();
uint32_t textureSize = rowSize * pFrame->GetHeight();
void* videoPixels = NULL;
void* rightEyePixels = NULL;
if (!mTextureDesc.stride) {
// we could not compute the texture size earlier (unknown pixel size)
// let's do it now
mTextureDesc.stride = rowSize;
mTextureDesc.width = mTextureDesc.stride / 4;
}
if (mTextureDesc.stride != rowSize) {
// unexpected frame size, ignore
// TBD: print a warning
}
else {
pFrame->GetBytes(&videoPixels);
if (mUse3D) {
IDeckLinkVideoFrame3DExtensions *if3DExtensions = NULL;
IDeckLinkVideoFrame *rightEyeFrame = NULL;
if (pFrame->QueryInterface(IID_IDeckLinkVideoFrame3DExtensions, (void **)&if3DExtensions) == S_OK &&
if3DExtensions->GetFrameForRightEye(&rightEyeFrame) == S_OK) {
rightEyeFrame->GetBytes(&rightEyePixels);
textureSize += ((uint64_t)rightEyePixels - (uint64_t)videoPixels);
}
if (rightEyeFrame)
rightEyeFrame->Release();
if (if3DExtensions)
if3DExtensions->Release();
}
mTextureDesc.size = mTextureDesc.width * mTextureDesc.height * 4;
if (mTextureDesc.size == textureSize) {
// this means that both left and right frame are contiguous and that there is no padding
// do the transfer
mpAllocator->TransferBuffer(videoPixels, &mTextureDesc, texId);
}
}
}
catch (Exception &) {
pFrame->Release();
throw;
}
// this will trigger PinnedMemoryAllocator::RealaseBuffer
pFrame->Release();
}
// currently we don't pass the image to the application
m_avail = false;
}
// A frame is available from the board
// Called from an internal thread, just pass the frame to the main thread
void VideoDeckLink::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame)
{
IDeckLinkVideoInputFrame* pOldFrame = NULL;
LockCache();
if (!mClosing)
{
pOldFrame = mpCacheFrame;
mpCacheFrame = inputFrame;
inputFrame->AddRef();
}
UnlockCache();
// old frame no longer needed, just release it
if (pOldFrame)
pOldFrame->Release();
}
// python methods
// object initialization
static int VideoDeckLink_init(PyObject *pySelf, PyObject *args, PyObject *kwds)
{
static const char *kwlist[] = { "format", "capture", NULL };
PyImage *self = reinterpret_cast<PyImage*>(pySelf);
// see openCam for a description of format
char * format = NULL;
// capture device number, i.e. DeckLink card number, default first one
short capt = 0;
if (!GLEW_VERSION_1_5) {
PyErr_SetString(PyExc_RuntimeError, "VideoDeckLink requires at least OpenGL 1.5");
return -1;
}
// get parameters
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|h",
const_cast<char**>(kwlist), &format, &capt))
return -1;
try {
// create video object
Video_init<VideoDeckLink>(self);
// open video source, control comes back to VideoDeckLink::openCam
Video_open(getVideo(self), format, capt);
}
catch (Exception & exp) {
exp.report();
return -1;
}
// initialization succeded
return 0;
}
// methods structure
static PyMethodDef videoMethods[] =
{ // methods from VideoBase class
{"play", (PyCFunction)Video_play, METH_NOARGS, "Play (restart) video"},
{"pause", (PyCFunction)Video_pause, METH_NOARGS, "pause video"},
{"stop", (PyCFunction)Video_stop, METH_NOARGS, "stop video (play will replay it from start)"},
{"refresh", (PyCFunction)Video_refresh, METH_VARARGS, "Refresh video - get its status"},
{NULL}
};
// attributes structure
static PyGetSetDef videoGetSets[] =
{ // methods from VideoBase class
{(char*)"status", (getter)Video_getStatus, NULL, (char*)"video status", NULL},
{(char*)"framerate", (getter)Video_getFrameRate, NULL, (char*)"frame rate", NULL},
// attributes from ImageBase class
{(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL},
{(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL},
{(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL},
{(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbor)", NULL},
{(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL},
{(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL},
{NULL}
};
// python type declaration
PyTypeObject VideoDeckLinkType =
{
PyVarObject_HEAD_INIT(NULL, 0)
"VideoTexture.VideoDeckLink", /*tp_name*/
sizeof(PyImage), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)Image_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
&imageBufferProcs, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
"DeckLink video source", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
videoMethods, /* tp_methods */
0, /* tp_members */
videoGetSets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)VideoDeckLink_init, /* tp_init */
0, /* tp_alloc */
Image_allocNew, /* tp_new */
};
////////////////////////////////////////////
// DeckLink Capture Delegate Class
////////////////////////////////////////////
#endif // WITH_GAMEENGINE_DECKLINK