forked from bartvdbraak/blender
1229 lines
36 KiB
C++
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_and_fetch_uint32(&mRefCount, 1U);
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::Release(void)
|
|
{
|
|
uint32_t newCount = atomic_sub_and_fetch_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
|
|
|