
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.
814 lines
24 KiB
C++
814 lines
24 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/Texture.cpp
|
|
* \ingroup bgevideotex
|
|
*/
|
|
|
|
#ifdef WITH_GAMEENGINE_DECKLINK
|
|
|
|
// implementation
|
|
|
|
// 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
|
|
#endif
|
|
|
|
#include "atomic_ops.h"
|
|
|
|
#include "EXP_PyObjectPlus.h"
|
|
#include "KX_KetsjiEngine.h"
|
|
#include "KX_PythonInit.h"
|
|
#include "DeckLink.h"
|
|
|
|
#include <memory.h>
|
|
|
|
// macro for exception handling and logging
|
|
#define CATCH_EXCP catch (Exception & exp) \
|
|
{ exp.report(); return NULL; }
|
|
|
|
static struct
|
|
{
|
|
const char *name;
|
|
BMDDisplayMode mode;
|
|
} sModeStringTab[] = {
|
|
{ "NTSC", bmdModeNTSC },
|
|
{ "NTSC2398", bmdModeNTSC2398 },
|
|
{ "PAL", bmdModePAL },
|
|
{ "NTSCp", bmdModeNTSCp },
|
|
{ "PALp", bmdModePALp },
|
|
|
|
/* HD 1080 Modes */
|
|
|
|
{ "HD1080p2398", bmdModeHD1080p2398 },
|
|
{ "HD1080p24", bmdModeHD1080p24 },
|
|
{ "HD1080p25", bmdModeHD1080p25 },
|
|
{ "HD1080p2997", bmdModeHD1080p2997 },
|
|
{ "HD1080p30", bmdModeHD1080p30 },
|
|
{ "HD1080i50", bmdModeHD1080i50 },
|
|
{ "HD1080i5994", bmdModeHD1080i5994 },
|
|
{ "HD1080i6000", bmdModeHD1080i6000 },
|
|
{ "HD1080p50", bmdModeHD1080p50 },
|
|
{ "HD1080p5994", bmdModeHD1080p5994 },
|
|
{ "HD1080p6000", bmdModeHD1080p6000 },
|
|
|
|
/* HD 720 Modes */
|
|
|
|
{ "HD720p50", bmdModeHD720p50 },
|
|
{ "HD720p5994", bmdModeHD720p5994 },
|
|
{ "HD720p60", bmdModeHD720p60 },
|
|
|
|
/* 2k Modes */
|
|
|
|
{ "2k2398", bmdMode2k2398 },
|
|
{ "2k24", bmdMode2k24 },
|
|
{ "2k25", bmdMode2k25 },
|
|
|
|
/* DCI Modes (output only) */
|
|
|
|
{ "2kDCI2398", bmdMode2kDCI2398 },
|
|
{ "2kDCI24", bmdMode2kDCI24 },
|
|
{ "2kDCI25", bmdMode2kDCI25 },
|
|
|
|
/* 4k Modes */
|
|
|
|
{ "4K2160p2398", bmdMode4K2160p2398 },
|
|
{ "4K2160p24", bmdMode4K2160p24 },
|
|
{ "4K2160p25", bmdMode4K2160p25 },
|
|
{ "4K2160p2997", bmdMode4K2160p2997 },
|
|
{ "4K2160p30", bmdMode4K2160p30 },
|
|
{ "4K2160p50", bmdMode4K2160p50 },
|
|
{ "4K2160p5994", bmdMode4K2160p5994 },
|
|
{ "4K2160p60", bmdMode4K2160p60 },
|
|
// sentinel
|
|
{ NULL }
|
|
};
|
|
|
|
static struct
|
|
{
|
|
const char *name;
|
|
BMDPixelFormat format;
|
|
} sFormatStringTab[] = {
|
|
{ "8BitYUV", bmdFormat8BitYUV },
|
|
{ "10BitYUV", bmdFormat10BitYUV },
|
|
{ "8BitARGB", bmdFormat8BitARGB },
|
|
{ "8BitBGRA", bmdFormat8BitBGRA },
|
|
{ "10BitRGB", bmdFormat10BitRGB },
|
|
{ "12BitRGB", bmdFormat12BitRGB },
|
|
{ "12BitRGBLE", bmdFormat12BitRGBLE },
|
|
{ "10BitRGBXLE", bmdFormat10BitRGBXLE },
|
|
{ "10BitRGBX", bmdFormat10BitRGBX },
|
|
// sentinel
|
|
{ NULL }
|
|
};
|
|
|
|
ExceptionID DeckLinkBadDisplayMode, DeckLinkBadPixelFormat;
|
|
ExpDesc DeckLinkBadDisplayModeDesc(DeckLinkBadDisplayMode, "Invalid or unsupported display mode");
|
|
ExpDesc DeckLinkBadPixelFormatDesc(DeckLinkBadPixelFormat, "Invalid or unsupported pixel format");
|
|
|
|
HRESULT decklink_ReadDisplayMode(const char *format, size_t len, BMDDisplayMode *displayMode)
|
|
{
|
|
int i;
|
|
|
|
if (len == 0)
|
|
len = strlen(format);
|
|
for (i = 0; sModeStringTab[i].name != NULL; i++) {
|
|
if (strlen(sModeStringTab[i].name) == len &&
|
|
!strncmp(sModeStringTab[i].name, format, len))
|
|
{
|
|
*displayMode = sModeStringTab[i].mode;
|
|
return S_OK;
|
|
}
|
|
}
|
|
if (len != 4)
|
|
THRWEXCP(DeckLinkBadDisplayMode, S_OK);
|
|
// assume the user entered directly the mode value as a 4 char string
|
|
*displayMode = (BMDDisplayMode)((((uint32_t)format[0]) << 24) + (((uint32_t)format[1]) << 16) + (((uint32_t)format[2]) << 8) + ((uint32_t)format[3]));
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT decklink_ReadPixelFormat(const char *format, size_t len, BMDPixelFormat *pixelFormat)
|
|
{
|
|
int i;
|
|
|
|
if (!len)
|
|
len = strlen(format);
|
|
for (i = 0; sFormatStringTab[i].name != NULL; i++) {
|
|
if (strlen(sFormatStringTab[i].name) == len &&
|
|
!strncmp(sFormatStringTab[i].name, format, len))
|
|
{
|
|
*pixelFormat = sFormatStringTab[i].format;
|
|
return S_OK;
|
|
}
|
|
}
|
|
if (len != 4)
|
|
THRWEXCP(DeckLinkBadPixelFormat, S_OK);
|
|
// assume the user entered directly the mode value as a 4 char string
|
|
*pixelFormat = (BMDPixelFormat)((((uint32_t)format[0]) << 24) + (((uint32_t)format[1]) << 16) + (((uint32_t)format[2]) << 8) + ((uint32_t)format[3]));
|
|
return S_OK;
|
|
}
|
|
|
|
class DeckLink3DFrameWrapper : public IDeckLinkVideoFrame, IDeckLinkVideoFrame3DExtensions
|
|
{
|
|
public:
|
|
// IUnknown
|
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv)
|
|
{
|
|
if (!memcmp(&iid, &IID_IDeckLinkVideoFrame3DExtensions, sizeof(iid))) {
|
|
if (mpRightEye) {
|
|
*ppv = (IDeckLinkVideoFrame3DExtensions*)this;
|
|
return S_OK;
|
|
}
|
|
}
|
|
return E_NOTIMPL;
|
|
}
|
|
virtual ULONG STDMETHODCALLTYPE AddRef(void) { return 1U; }
|
|
virtual ULONG STDMETHODCALLTYPE Release(void) { return 1U; }
|
|
// IDeckLinkVideoFrame
|
|
virtual long STDMETHODCALLTYPE GetWidth(void) { return mpLeftEye->GetWidth(); }
|
|
virtual long STDMETHODCALLTYPE GetHeight(void) { return mpLeftEye->GetHeight(); }
|
|
virtual long STDMETHODCALLTYPE GetRowBytes(void) { return mpLeftEye->GetRowBytes(); }
|
|
virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat(void) { return mpLeftEye->GetPixelFormat(); }
|
|
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags(void) { return mpLeftEye->GetFlags(); }
|
|
virtual HRESULT STDMETHODCALLTYPE GetBytes(void **buffer) { return mpLeftEye->GetBytes(buffer); }
|
|
virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format,IDeckLinkTimecode **timecode)
|
|
{ return mpLeftEye->GetTimecode(format, timecode); }
|
|
virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary **ancillary)
|
|
{ return mpLeftEye->GetAncillaryData(ancillary); }
|
|
// IDeckLinkVideoFrame3DExtensions
|
|
virtual BMDVideo3DPackingFormat STDMETHODCALLTYPE Get3DPackingFormat(void)
|
|
{
|
|
return bmdVideo3DPackingLeftOnly;
|
|
}
|
|
virtual HRESULT STDMETHODCALLTYPE GetFrameForRightEye(
|
|
/* [out] */ IDeckLinkVideoFrame **rightEyeFrame)
|
|
{
|
|
mpRightEye->AddRef();
|
|
*rightEyeFrame = mpRightEye;
|
|
return S_OK;
|
|
}
|
|
// Constructor
|
|
DeckLink3DFrameWrapper(IDeckLinkVideoFrame *leftEye, IDeckLinkVideoFrame *rightEye)
|
|
{
|
|
mpLeftEye = leftEye;
|
|
mpRightEye = rightEye;
|
|
}
|
|
// no need for a destructor, it's just a wrapper
|
|
private:
|
|
IDeckLinkVideoFrame *mpLeftEye;
|
|
IDeckLinkVideoFrame *mpRightEye;
|
|
};
|
|
|
|
static void decklink_Reset(DeckLink *self)
|
|
{
|
|
self->m_lastClock = 0.0;
|
|
self->mDLOutput = NULL;
|
|
self->mUse3D = false;
|
|
self->mDisplayMode = bmdModeUnknown;
|
|
self->mKeyingSupported = false;
|
|
self->mHDKeyingSupported = false;
|
|
self->mSize[0] = 0;
|
|
self->mSize[1] = 0;
|
|
self->mFrameSize = 0;
|
|
self->mLeftFrame = NULL;
|
|
self->mRightFrame = NULL;
|
|
self->mKeyer = NULL;
|
|
self->mUseKeying = false;
|
|
self->mKeyingLevel = 255;
|
|
self->mUseExtend = false;
|
|
}
|
|
|
|
#ifdef __BIG_ENDIAN__
|
|
#define CONV_PIXEL(i) ((((i)>>16)&0xFF00)+(((i)&0xFF00)<<16)+((i)&0xFF00FF))
|
|
#else
|
|
#define CONV_PIXEL(i) ((((i)&0xFF)<<16)+(((i)>>16)&0xFF)+((i)&0xFF00FF00))
|
|
#endif
|
|
|
|
// adapt the pixel format and picture size from VideoTexture (RGBA) to DeckLink (BGRA)
|
|
static void decklink_ConvImage(uint32_t *dest, const short *destSize, const uint32_t *source, const short *srcSize, bool extend)
|
|
{
|
|
short w, h, x, y;
|
|
const uint32_t *s;
|
|
uint32_t *d, p;
|
|
bool sameSize = (destSize[0] == srcSize[0] && destSize[1] == srcSize[1]);
|
|
|
|
if (sameSize || !extend) {
|
|
// here we convert pixel by pixel
|
|
w = (destSize[0] < srcSize[0]) ? destSize[0] : srcSize[0];
|
|
h = (destSize[1] < srcSize[1]) ? destSize[1] : srcSize[1];
|
|
for (y = 0; y < h; ++y) {
|
|
s = source + y*srcSize[0];
|
|
d = dest + y*destSize[0];
|
|
for (x = 0; x < w; ++x, ++s, ++d) {
|
|
*d = CONV_PIXEL(*s);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// here we scale
|
|
// interpolation accumulator
|
|
int accHeight = srcSize[1] >> 1;
|
|
d = dest;
|
|
s = source;
|
|
// process image rows
|
|
for (y = 0; y < srcSize[1]; ++y) {
|
|
// increase height accum
|
|
accHeight += destSize[1];
|
|
// if pixel row has to be drawn
|
|
if (accHeight >= srcSize[1]) {
|
|
// decrease accum
|
|
accHeight -= srcSize[1];
|
|
// width accum
|
|
int accWidth = srcSize[0] >> 1;
|
|
// process row
|
|
for (x = 0; x < srcSize[0]; ++x, ++s) {
|
|
// increase width accum
|
|
accWidth += destSize[0];
|
|
// convert pixel
|
|
p = CONV_PIXEL(*s);
|
|
// if pixel has to be drown one or more times
|
|
while (accWidth >= srcSize[0]) {
|
|
// decrease accum
|
|
accWidth -= srcSize[0];
|
|
*d++ = p;
|
|
}
|
|
}
|
|
// if there should be more identical lines
|
|
while (accHeight >= srcSize[1]) {
|
|
accHeight -= srcSize[1];
|
|
// copy previous line
|
|
memcpy(d, d - destSize[0], 4 * destSize[0]);
|
|
d += destSize[0];
|
|
}
|
|
}
|
|
else {
|
|
// if we skip a source line
|
|
s += srcSize[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// DeckLink object allocation
|
|
static PyObject *DeckLink_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
{
|
|
// allocate object
|
|
DeckLink * self = reinterpret_cast<DeckLink*>(type->tp_alloc(type, 0));
|
|
// initialize object structure
|
|
decklink_Reset(self);
|
|
// m_leftEye is a python object, it's handled by python
|
|
self->m_leftEye = NULL;
|
|
self->m_rightEye = NULL;
|
|
// return allocated object
|
|
return reinterpret_cast<PyObject*>(self);
|
|
}
|
|
|
|
|
|
// forward declaration
|
|
PyObject *DeckLink_close(DeckLink *self);
|
|
int DeckLink_setSource(DeckLink *self, PyObject *value, void *closure);
|
|
|
|
|
|
// DeckLink object deallocation
|
|
static void DeckLink_dealloc(DeckLink *self)
|
|
{
|
|
// release renderer
|
|
Py_XDECREF(self->m_leftEye);
|
|
// close decklink
|
|
PyObject *ret = DeckLink_close(self);
|
|
Py_DECREF(ret);
|
|
// release object
|
|
Py_TYPE((PyObject *)self)->tp_free((PyObject *)self);
|
|
}
|
|
|
|
|
|
ExceptionID AutoDetectionNotAvail, DeckLinkOpenCard, DeckLinkBadFormat, DeckLinkInternalError;
|
|
ExpDesc AutoDetectionNotAvailDesc(AutoDetectionNotAvail, "Auto detection not yet available");
|
|
ExpDesc DeckLinkOpenCardDesc(DeckLinkOpenCard, "Cannot open card for output");
|
|
ExpDesc DeckLinkBadFormatDesc(DeckLinkBadFormat, "Invalid or unsupported output format, use <mode>[/3D]");
|
|
ExpDesc DeckLinkInternalErrorDesc(DeckLinkInternalError, "DeckLink API internal error, please report");
|
|
|
|
// DeckLink object initialization
|
|
static int DeckLink_init(DeckLink *self, PyObject *args, PyObject *kwds)
|
|
{
|
|
IDeckLinkIterator* pIterator;
|
|
IDeckLinkAttributes* pAttributes;
|
|
IDeckLinkDisplayModeIterator* pDisplayModeIterator;
|
|
IDeckLinkDisplayMode* pDisplayMode;
|
|
IDeckLink* pDL;
|
|
char* p3D;
|
|
BOOL flag;
|
|
size_t len;
|
|
int i;
|
|
uint32_t displayFlags;
|
|
BMDVideoOutputFlags outputFlags;
|
|
BMDDisplayModeSupport support;
|
|
uint32_t* bytes;
|
|
|
|
|
|
// material ID
|
|
short cardIdx = 0;
|
|
// texture ID
|
|
char *format = NULL;
|
|
|
|
static const char *kwlist[] = {"cardIdx", "format", NULL};
|
|
// get parameters
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|hs",
|
|
const_cast<char**>(kwlist), &cardIdx, &format))
|
|
return -1;
|
|
|
|
try {
|
|
if (format == NULL) {
|
|
THRWEXCP(AutoDetectionNotAvail, S_OK);
|
|
}
|
|
|
|
if ((p3D = strchr(format, '/')) != NULL && strcmp(p3D, "/3D"))
|
|
THRWEXCP(DeckLinkBadFormat, S_OK);
|
|
self->mUse3D = (p3D) ? true : false;
|
|
// read the mode
|
|
len = (p3D) ? (size_t)(p3D - format) : strlen(format);
|
|
// throws if bad mode
|
|
decklink_ReadDisplayMode(format, len, &self->mDisplayMode);
|
|
|
|
pIterator = BMD_CreateDeckLinkIterator();
|
|
pDL = NULL;
|
|
if (pIterator) {
|
|
i = 0;
|
|
while (pIterator->Next(&pDL) == S_OK) {
|
|
if (i == cardIdx) {
|
|
break;
|
|
}
|
|
i++;
|
|
pDL->Release();
|
|
pDL = NULL;
|
|
}
|
|
pIterator->Release();
|
|
}
|
|
|
|
if (!pDL) {
|
|
THRWEXCP(DeckLinkOpenCard, S_OK);
|
|
}
|
|
// detect the capabilities
|
|
if (pDL->QueryInterface(IID_IDeckLinkAttributes, (void**)&pAttributes) == S_OK) {
|
|
if (pAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &flag) == S_OK && flag) {
|
|
self->mKeyingSupported = true;
|
|
if (pAttributes->GetFlag(BMDDeckLinkSupportsHDKeying, &flag) == S_OK && flag) {
|
|
self->mHDKeyingSupported = true;
|
|
}
|
|
}
|
|
pAttributes->Release();
|
|
}
|
|
|
|
if (pDL->QueryInterface(IID_IDeckLinkOutput, (void**)&self->mDLOutput) != S_OK) {
|
|
self->mDLOutput = NULL;
|
|
}
|
|
if (self->mKeyingSupported) {
|
|
pDL->QueryInterface(IID_IDeckLinkKeyer, (void **)&self->mKeyer);
|
|
}
|
|
// we don't need the device anymore, release to avoid leaking
|
|
pDL->Release();
|
|
|
|
if (!self->mDLOutput)
|
|
THRWEXCP(DeckLinkOpenCard, S_OK);
|
|
|
|
if (self->mDLOutput->GetDisplayModeIterator(&pDisplayModeIterator) != S_OK)
|
|
THRWEXCP(DeckLinkInternalError, S_OK);
|
|
|
|
displayFlags = (self->mUse3D) ? bmdDisplayModeSupports3D : 0;
|
|
outputFlags = (self->mUse3D) ? bmdVideoOutputDualStream3D : bmdVideoOutputFlagDefault;
|
|
pDisplayMode = NULL;
|
|
i = 0;
|
|
while (pDisplayModeIterator->Next(&pDisplayMode) == S_OK) {
|
|
if (pDisplayMode->GetDisplayMode() == self->mDisplayMode
|
|
&& (pDisplayMode->GetFlags() & displayFlags) == displayFlags) {
|
|
if (self->mDLOutput->DoesSupportVideoMode(self->mDisplayMode, bmdFormat8BitBGRA, outputFlags, &support, NULL) != S_OK ||
|
|
support == bmdDisplayModeNotSupported)
|
|
{
|
|
printf("Warning: DeckLink card %d reports no BGRA support, proceed anyway\n", cardIdx);
|
|
}
|
|
break;
|
|
}
|
|
pDisplayMode->Release();
|
|
pDisplayMode = NULL;
|
|
i++;
|
|
}
|
|
pDisplayModeIterator->Release();
|
|
|
|
if (!pDisplayMode)
|
|
THRWEXCP(DeckLinkBadFormat, S_OK);
|
|
self->mSize[0] = pDisplayMode->GetWidth();
|
|
self->mSize[1] = pDisplayMode->GetHeight();
|
|
self->mFrameSize = 4*self->mSize[0]*self->mSize[1];
|
|
pDisplayMode->Release();
|
|
if (self->mDLOutput->EnableVideoOutput(self->mDisplayMode, outputFlags) != S_OK)
|
|
// this shouldn't fail
|
|
THRWEXCP(DeckLinkOpenCard, S_OK);
|
|
|
|
if (self->mDLOutput->CreateVideoFrame(self->mSize[0], self->mSize[1], self->mSize[0] * 4, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, &self->mLeftFrame) != S_OK)
|
|
THRWEXCP(DeckLinkInternalError, S_OK);
|
|
// clear alpha channel in the frame buffer
|
|
self->mLeftFrame->GetBytes((void **)&bytes);
|
|
memset(bytes, 0, self->mFrameSize);
|
|
if (self->mUse3D) {
|
|
if (self->mDLOutput->CreateVideoFrame(self->mSize[0], self->mSize[1], self->mSize[0] * 4, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, &self->mRightFrame) != S_OK)
|
|
THRWEXCP(DeckLinkInternalError, S_OK);
|
|
// clear alpha channel in the frame buffer
|
|
self->mRightFrame->GetBytes((void **)&bytes);
|
|
memset(bytes, 0, self->mFrameSize);
|
|
}
|
|
}
|
|
catch (Exception & exp)
|
|
{
|
|
printf("DeckLink: exception when opening card %d: %s\n", cardIdx, exp.what());
|
|
exp.report();
|
|
// normally, the object should be deallocated
|
|
return -1;
|
|
}
|
|
// initialization succeeded
|
|
return 0;
|
|
}
|
|
|
|
|
|
// close added decklink
|
|
PyObject *DeckLink_close(DeckLink * self)
|
|
{
|
|
if (self->mLeftFrame)
|
|
self->mLeftFrame->Release();
|
|
if (self->mRightFrame)
|
|
self->mRightFrame->Release();
|
|
if (self->mKeyer)
|
|
self->mKeyer->Release();
|
|
if (self->mDLOutput)
|
|
self->mDLOutput->Release();
|
|
decklink_Reset(self);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
// refresh decklink key frame
|
|
static PyObject *DeckLink_refresh(DeckLink *self, PyObject *args)
|
|
{
|
|
// get parameter - refresh source
|
|
PyObject *param;
|
|
double ts = -1.0;
|
|
|
|
if (!PyArg_ParseTuple(args, "O|d:refresh", ¶m, &ts) || !PyBool_Check(param)) {
|
|
// report error
|
|
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
|
|
return NULL;
|
|
}
|
|
// some trick here: we are in the business of loading a key frame in decklink,
|
|
// no use to do it if we are still in the same rendering frame.
|
|
// We find this out by looking at the engine current clock time
|
|
KX_KetsjiEngine* engine = KX_GetActiveEngine();
|
|
if (engine->GetClockTime() != self->m_lastClock)
|
|
{
|
|
self->m_lastClock = engine->GetClockTime();
|
|
// set source refresh
|
|
bool refreshSource = (param == Py_True);
|
|
uint32_t *leftEye = NULL;
|
|
uint32_t *rightEye = NULL;
|
|
// try to process key frame from source
|
|
try {
|
|
// check if optimization is possible
|
|
if (self->m_leftEye != NULL) {
|
|
ImageBase *leftImage = self->m_leftEye->m_image;
|
|
short * srcSize = leftImage->getSize();
|
|
self->mLeftFrame->GetBytes((void **)&leftEye);
|
|
if (srcSize[0] == self->mSize[0] && srcSize[1] == self->mSize[1])
|
|
{
|
|
// buffer has same size, can load directly
|
|
if (!leftImage->loadImage(leftEye, self->mFrameSize, GL_BGRA, ts))
|
|
leftEye = NULL;
|
|
}
|
|
else {
|
|
// scaling is required, go the hard way
|
|
unsigned int *src = leftImage->getImage(0, ts);
|
|
if (src != NULL)
|
|
decklink_ConvImage(leftEye, self->mSize, src, srcSize, self->mUseExtend);
|
|
else
|
|
leftEye = NULL;
|
|
}
|
|
}
|
|
if (leftEye) {
|
|
if (self->mUse3D && self->m_rightEye != NULL) {
|
|
ImageBase *rightImage = self->m_rightEye->m_image;
|
|
short * srcSize = rightImage->getSize();
|
|
self->mRightFrame->GetBytes((void **)&rightEye);
|
|
if (srcSize[0] == self->mSize[0] && srcSize[1] == self->mSize[1])
|
|
{
|
|
// buffer has same size, can load directly
|
|
rightImage->loadImage(rightEye, self->mFrameSize, GL_BGRA, ts);
|
|
}
|
|
else {
|
|
// scaling is required, go the hard way
|
|
unsigned int *src = rightImage->getImage(0, ts);
|
|
if (src != NULL)
|
|
decklink_ConvImage(rightEye, self->mSize, src, srcSize, self->mUseExtend);
|
|
}
|
|
}
|
|
if (self->mUse3D) {
|
|
DeckLink3DFrameWrapper frame3D(
|
|
(IDeckLinkVideoFrame*)self->mLeftFrame,
|
|
(IDeckLinkVideoFrame*)self->mRightFrame);
|
|
self->mDLOutput->DisplayVideoFrameSync(&frame3D);
|
|
}
|
|
else {
|
|
self->mDLOutput->DisplayVideoFrameSync((IDeckLinkVideoFrame*)self->mLeftFrame);
|
|
}
|
|
}
|
|
// refresh texture source, if required
|
|
if (refreshSource) {
|
|
if (self->m_leftEye)
|
|
self->m_leftEye->m_image->refresh();
|
|
if (self->m_rightEye)
|
|
self->m_rightEye->m_image->refresh();
|
|
}
|
|
}
|
|
CATCH_EXCP;
|
|
}
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
// get source object
|
|
static PyObject *DeckLink_getSource(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
// if source exists
|
|
if (self->m_leftEye != NULL) {
|
|
Py_INCREF(self->m_leftEye);
|
|
return reinterpret_cast<PyObject*>(self->m_leftEye);
|
|
}
|
|
// otherwise return None
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
// set source object
|
|
int DeckLink_setSource(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
// check new value
|
|
if (value == NULL || !pyImageTypes.in(Py_TYPE(value))) {
|
|
// report value error
|
|
PyErr_SetString(PyExc_TypeError, "Invalid type of value");
|
|
return -1;
|
|
}
|
|
// increase ref count for new value
|
|
Py_INCREF(value);
|
|
// release previous
|
|
Py_XDECREF(self->m_leftEye);
|
|
// set new value
|
|
self->m_leftEye = reinterpret_cast<PyImage*>(value);
|
|
// return success
|
|
return 0;
|
|
}
|
|
|
|
// get source object
|
|
static PyObject *DeckLink_getRight(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
// if source exists
|
|
if (self->m_rightEye != NULL)
|
|
{
|
|
Py_INCREF(self->m_rightEye);
|
|
return reinterpret_cast<PyObject*>(self->m_rightEye);
|
|
}
|
|
// otherwise return None
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
|
|
// set source object
|
|
static int DeckLink_setRight(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
// check new value
|
|
if (value == NULL || !pyImageTypes.in(Py_TYPE(value)))
|
|
{
|
|
// report value error
|
|
PyErr_SetString(PyExc_TypeError, "Invalid type of value");
|
|
return -1;
|
|
}
|
|
// increase ref count for new value
|
|
Py_INCREF(value);
|
|
// release previous
|
|
Py_XDECREF(self->m_rightEye);
|
|
// set new value
|
|
self->m_rightEye = reinterpret_cast<PyImage*>(value);
|
|
// return success
|
|
return 0;
|
|
}
|
|
|
|
|
|
static PyObject *DeckLink_getKeying(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
if (self->mUseKeying) Py_RETURN_TRUE;
|
|
else Py_RETURN_FALSE;
|
|
}
|
|
|
|
static int DeckLink_setKeying(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
if (value == NULL || !PyBool_Check(value))
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
|
|
return -1;
|
|
}
|
|
if (self->mKeyer != NULL)
|
|
{
|
|
if (value == Py_True)
|
|
{
|
|
if (self->mKeyer->Enable(false) != S_OK)
|
|
{
|
|
PyErr_SetString(PyExc_RuntimeError, "Error enabling keyer");
|
|
return -1;
|
|
}
|
|
self->mUseKeying = true;
|
|
self->mKeyer->SetLevel(self->mKeyingLevel);
|
|
}
|
|
else
|
|
{
|
|
self->mKeyer->Disable();
|
|
self->mUseKeying = false;
|
|
}
|
|
}
|
|
// success
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *DeckLink_getLevel(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
return Py_BuildValue("h", self->mKeyingLevel);
|
|
}
|
|
|
|
static int DeckLink_setLevel(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
long level;
|
|
if (value == NULL || !PyLong_Check(value)) {
|
|
PyErr_SetString(PyExc_TypeError, "The value must be an integer from 0 to 255");
|
|
return -1;
|
|
}
|
|
level = PyLong_AsLong(value);
|
|
if (level > 255)
|
|
level = 255;
|
|
else if (level < 0)
|
|
level = 0;
|
|
self->mKeyingLevel = (uint8_t)level;
|
|
if (self->mUseKeying) {
|
|
if (self->mKeyer->SetLevel(self->mKeyingLevel) != S_OK) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Error changin level of keyer");
|
|
return -1;
|
|
}
|
|
}
|
|
// success
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *DeckLink_getExtend(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
if (self->mUseExtend) Py_RETURN_TRUE;
|
|
else Py_RETURN_FALSE;
|
|
}
|
|
|
|
static int DeckLink_setExtend(DeckLink *self, PyObject *value, void *closure)
|
|
{
|
|
if (value == NULL || !PyBool_Check(value))
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "The value must be a bool");
|
|
return -1;
|
|
}
|
|
self->mUseExtend = (value == Py_True);
|
|
return 0;
|
|
}
|
|
|
|
// class DeckLink methods
|
|
static PyMethodDef decklinkMethods[] =
|
|
{
|
|
{ "close", (PyCFunction)DeckLink_close, METH_NOARGS, "Close dynamic decklink and restore original"},
|
|
{ "refresh", (PyCFunction)DeckLink_refresh, METH_VARARGS, "Refresh decklink from source"},
|
|
{NULL} /* Sentinel */
|
|
};
|
|
|
|
// class DeckLink attributes
|
|
static PyGetSetDef decklinkGetSets[] =
|
|
{
|
|
{ (char*)"source", (getter)DeckLink_getSource, (setter)DeckLink_setSource, (char*)"source of decklink (left eye)", NULL},
|
|
{ (char*)"right", (getter)DeckLink_getRight, (setter)DeckLink_setRight, (char*)"source of decklink (right eye)", NULL },
|
|
{ (char*)"keying", (getter)DeckLink_getKeying, (setter)DeckLink_setKeying, (char*)"whether keying is enabled (frame is alpha-composited with passthrough output)", NULL },
|
|
{ (char*)"level", (getter)DeckLink_getLevel, (setter)DeckLink_setLevel, (char*)"change the level of keying (overall alpha level of key frame, 0 to 255)", NULL },
|
|
{ (char*)"extend", (getter)DeckLink_getExtend, (setter)DeckLink_setExtend, (char*)"whether image should stretched to fit frame", NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
|
|
// class DeckLink declaration
|
|
PyTypeObject DeckLinkType =
|
|
{
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
"VideoTexture.DeckLink", /*tp_name*/
|
|
sizeof(DeckLink), /*tp_basicsize*/
|
|
0, /*tp_itemsize*/
|
|
(destructor)DeckLink_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 objects", /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
decklinkMethods, /* tp_methods */
|
|
0, /* tp_members */
|
|
decklinkGetSets, /* tp_getset */
|
|
0, /* tp_base */
|
|
0, /* tp_dict */
|
|
0, /* tp_descr_get */
|
|
0, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
(initproc)DeckLink_init, /* tp_init */
|
|
0, /* tp_alloc */
|
|
DeckLink_new, /* tp_new */
|
|
};
|
|
|
|
#endif /* WITH_GAMEENGINE_DECKLINK */
|