forked from bartvdbraak/blender
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 */
|