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

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

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

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

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

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", &param, &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 */