diff options
author | Bastien Montagne <montagne29@wanadoo.fr> | 2016-06-13 13:36:24 +0300 |
---|---|---|
committer | Bastien Montagne <montagne29@wanadoo.fr> | 2016-06-13 13:36:24 +0300 |
commit | 224edde807d76dcccfdb2b204cfa1739d544eca0 (patch) | |
tree | 034b5e1afecc9473d2932010b88bc8c642f9fe59 /source/gameengine/VideoTexture/VideoDeckLink.cpp | |
parent | af2d39d902c0e302a32b979a8d47ff8e067c0d65 (diff) | |
parent | 617c4d6adbfe64b3a72b5c48f918f39d30aa18dc (diff) |
Merge branch 'master' into custom-normals-bmeshcustom-normals-bmesh
Diffstat (limited to 'source/gameengine/VideoTexture/VideoDeckLink.cpp')
-rw-r--r-- | source/gameengine/VideoTexture/VideoDeckLink.cpp | 1228 |
1 files changed, 1228 insertions, 0 deletions
diff --git a/source/gameengine/VideoTexture/VideoDeckLink.cpp b/source/gameengine/VideoTexture/VideoDeckLink.cpp new file mode 100644 index 00000000000..c8d3c28c551 --- /dev/null +++ b/source/gameengine/VideoTexture/VideoDeckLink.cpp @@ -0,0 +1,1228 @@ +/* + * ***** 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 + |