Folder fixes
All checks were successful
CI / React UI Build (push) Successful in 37s
CI / Native Windows Build And Tests (push) Successful in 2m18s
CI / Windows Release Package (push) Successful in 2m0s

This commit is contained in:
2026-05-18 14:57:25 +10:00
parent f461a05c65
commit 1d4eb7a34c
47 changed files with 28243 additions and 1211 deletions

20433
src/video/DeckLinkAPI_h.h Normal file

File diff suppressed because it is too large Load Diff

486
src/video/DeckLinkAPI_i.c Normal file
View File

@@ -0,0 +1,486 @@
/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */
/* link this file in with the server and any clients */
/* File created by MIDL compiler version 8.01.0628 */
/* at Tue Jan 19 14:14:07 2038
*/
/* Compiler settings for ..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\include\DeckLinkAPI.idl:
Oicf, W1, Zp8, env=Win64 (32b run), target_arch=AMD64 8.01.0628
protocol : all , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
/* @@MIDL_FILE_HEADING( ) */
#ifdef __cplusplus
extern "C"{
#endif
#include <rpc.h>
#include <rpcndr.h>
#ifdef _MIDL_USE_GUIDDEF_
#ifndef INITGUID
#define INITGUID
#include <guiddef.h>
#undef INITGUID
#else
#include <guiddef.h>
#endif
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8)
#else // !_MIDL_USE_GUIDDEF_
#ifndef __IID_DEFINED__
#define __IID_DEFINED__
typedef struct _IID
{
unsigned long x;
unsigned short s1;
unsigned short s2;
unsigned char c[8];
} IID;
#endif // __IID_DEFINED__
#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
EXTERN_C __declspec(selectany) const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
#endif // !_MIDL_USE_GUIDDEF_
MIDL_DEFINE_GUID(IID, LIBID_DeckLinkAPI,0xD864517A,0xEDD5,0x466D,0x86,0x7D,0xC8,0x19,0xF1,0xC0,0x52,0xBB);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkTimecode,0xBC6CFBD3,0x8317,0x4325,0xAC,0x1C,0x12,0x16,0x39,0x1E,0x93,0x40);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayModeIterator,0x9C88499F,0xF601,0x4021,0xB8,0x0B,0x03,0x2E,0x4E,0xB4,0x1C,0x35);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayMode,0x3EB2C1AB,0x0A3D,0x4523,0xA3,0xAD,0xF4,0x0D,0x7F,0xB1,0x4E,0x78);
MIDL_DEFINE_GUID(IID, IID_IDeckLink,0xC418FBDD,0x0587,0x48ED,0x8F,0xE5,0x64,0x0F,0x0A,0x14,0xAF,0x91);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration,0x5a68ffd4,0x1c12,0x4ede,0xa6,0xd2,0x45,0x45,0x1d,0x38,0x5f,0xc1);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderConfiguration,0x138050E5,0xC60A,0x4552,0xBF,0x3F,0x0F,0x35,0x80,0x49,0x32,0x7E);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeckControlStatusCallback,0x53436FFB,0xB434,0x4906,0xBA,0xDC,0xAE,0x30,0x60,0xFF,0xE8,0xEF);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeckControl,0x8E1C3ACE,0x19C7,0x4E00,0x8B,0x92,0xD8,0x04,0x31,0xD9,0x58,0xBE);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingDeviceNotificationCallback,0xF9531D64,0x3305,0x4B29,0xA3,0x87,0x7F,0x74,0xBB,0x0D,0x0E,0x84);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingH264InputCallback,0x823C475F,0x55AE,0x46F9,0x89,0x0C,0x53,0x7C,0xC5,0xCE,0xDC,0xCA);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingDiscovery,0x2C837444,0xF989,0x4D87,0x90,0x1A,0x47,0xC8,0xA3,0x6D,0x09,0x6D);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingVideoEncodingMode,0x1AB8035B,0xCD13,0x458D,0xB6,0xDF,0x5E,0x8F,0x7C,0x21,0x41,0xD9);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingMutableVideoEncodingMode,0x19BF7D90,0x1E0A,0x400D,0xB2,0xC6,0xFF,0xC4,0xE7,0x8A,0xD4,0x9D);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingVideoEncodingModePresetIterator,0x7AC731A3,0xC950,0x4AD0,0x80,0x4A,0x83,0x77,0xAA,0x51,0xC6,0xC4);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingDeviceInput,0x24B6B6EC,0x1727,0x44BB,0x98,0x18,0x34,0xFF,0x08,0x6A,0xCF,0x98);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingH264NALPacket,0xE260E955,0x14BE,0x4395,0x97,0x75,0x9F,0x02,0xCC,0x0A,0x9D,0x89);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingAudioPacket,0xD9EB5902,0x1AD2,0x43F4,0x9E,0x2C,0x3C,0xFA,0x50,0xB5,0xEE,0x19);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingMPEG2TSPacket,0x91810D1C,0x4FB3,0x4AAA,0xAE,0x56,0xFA,0x30,0x1D,0x3D,0xFA,0x4C);
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingH264NALParser,0x5867F18C,0x5BFA,0x4CCC,0xB2,0xA7,0x9D,0xFD,0x14,0x04,0x17,0xD2);
MIDL_DEFINE_GUID(CLSID, CLSID_CBMDStreamingDiscovery,0x23A4EDF5,0xA0E5,0x432C,0x94,0xEF,0x3B,0xAB,0xB5,0xF8,0x1C,0x82);
MIDL_DEFINE_GUID(CLSID, CLSID_CBMDStreamingH264NALParser,0x7753EFBD,0x951C,0x407C,0x97,0xA5,0x23,0xC7,0x37,0xB7,0x3B,0x52);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoOutputCallback,0x5BE6DF26,0x02CE,0x433E,0x99,0xD9,0x9A,0x87,0xC3,0xAC,0x17,0x1F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback,0x3A94F075,0xC37D,0x4BA8,0xBC,0xC0,0x1D,0x77,0x8C,0x8F,0x88,0x1B);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInputCallback,0xACF13E61,0xF4A0,0x4974,0xA6,0xA7,0x59,0xAF,0xF6,0x26,0x8B,0x31);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocator,0xF35DFA8D,0x9078,0x4622,0x95,0xBB,0x56,0x89,0x40,0x54,0xEB,0x0F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocatorProvider,0x6DF6F20A,0xD8DF,0x45D2,0x89,0x14,0x38,0x3C,0xE7,0xE6,0x24,0x3F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAudioOutputCallback,0x403C681B,0x7F46,0x4A12,0xB9,0x93,0x2B,0xB1,0x27,0x08,0x4E,0xE6);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIterator,0x50FB36CD,0x3063,0x4B73,0xBD,0xBB,0x95,0x80,0x87,0xF2,0xD8,0xBA);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAPIInformation,0x7BEA3C68,0x730D,0x4322,0xAF,0x34,0x8A,0x71,0x52,0xB5,0x32,0xA4);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowAttributes,0xCDA938DA,0x6479,0x40C6,0xB2,0xEC,0xA3,0x57,0x9B,0x3A,0xEE,0xCD);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowStatus,0x31C41656,0x4992,0x4396,0xBB,0xE9,0x5F,0x84,0x06,0xAA,0xB5,0xAF);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowSetting,0x86DD9174,0x27D3,0x4032,0xB2,0xAD,0x60,0x67,0xC3,0xBB,0x24,0x24);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlow,0xC5FC83C7,0x5B8E,0x42A7,0x9A,0x40,0x7C,0x06,0x59,0x55,0xD4,0xE1);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowIterator,0xBD296AB2,0xA5C5,0x4153,0x88,0x8F,0xAA,0xB1,0xFD,0xBD,0x8A,0x5C);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput,0x5F227C95,0x39D7,0x46C7,0x8B,0x7D,0x9C,0x81,0x79,0x5F,0xBB,0xE4);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput,0x6A515F8A,0xFBCE,0x4853,0xB0,0xF7,0x2A,0x09,0xDB,0x1E,0xCA,0x0B);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPExtensions,0x46CF7903,0xA9FD,0x4D0B,0x8F,0xFC,0x01,0x03,0x72,0x2A,0xB4,0x42);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkHDMIInputEDID,0xABBBACBC,0x45BC,0x4665,0x9D,0x92,0xAC,0xE6,0xE5,0xA9,0x79,0x02);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInput,0x46C1332E,0x6FD9,0x472A,0x85,0x91,0xFE,0x59,0xC2,0x21,0x92,0xE1);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBuffer,0x81F03D70,0xDE13,0x4B17,0x87,0x3A,0xC8,0xAC,0x96,0x89,0xC6,0x82);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame,0x6502091C,0x615F,0x4F51,0xBA,0xF6,0x45,0xC4,0x25,0x6D,0xD5,0xB0);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkMutableVideoFrame,0xCF9EB134,0x0374,0x4C5B,0x95,0xFA,0x1E,0xC1,0x48,0x19,0xFF,0x62);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame3DExtensions,0xD4DBE9C6,0xB4D2,0x49D3,0xAB,0xF2,0xB4,0xE8,0x6C,0x73,0x91,0xB0);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameMetadataExtensions,0xE232A5B7,0x4DB4,0x44C9,0x91,0x52,0xF4,0x7C,0x12,0xE5,0xF0,0x51);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameMutableMetadataExtensions,0xCC198FC6,0x8298,0x4419,0x94,0x2D,0x83,0x57,0xEC,0x35,0x5E,0x58);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame,0xC9ADD3D2,0xBE52,0x488D,0xAB,0x2D,0x7F,0xDE,0xF7,0xAF,0x0C,0x95);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacket,0xF5C0D498,0x5CD3,0x4C77,0x97,0x73,0x8E,0xFA,0x20,0xBB,0x33,0x4B);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacketIterator,0x10F1AA88,0x54BE,0x42F7,0xB9,0xF8,0xEC,0x2F,0x5F,0x09,0x95,0x51);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameAncillaryPackets,0x8A72D630,0x8070,0x4D05,0x8A,0x93,0xE6,0x0C,0x40,0xEE,0x08,0x8A);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameAncillary,0x732E723C,0xD1A4,0x4E29,0x9E,0x8E,0x4A,0x88,0x79,0x7A,0x00,0x04);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderPacket,0xB693F36C,0x316E,0x4AF1,0xB6,0xC2,0xF3,0x89,0xA4,0xBC,0xA6,0x20);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderVideoPacket,0x4E7FD944,0xE8C7,0x4EAC,0xB8,0xC0,0x7B,0x77,0xF8,0x0F,0x5A,0xE0);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderAudioPacket,0x49E8EDC8,0x693B,0x4E14,0x8E,0xF6,0x12,0xC6,0x58,0xF5,0xA0,0x7A);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkH265NALPacket,0x639C8E0B,0x68D5,0x4BDE,0xA6,0xD4,0x95,0xF3,0xAE,0xAF,0xF2,0xE7);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAudioInputPacket,0xE43D5870,0x2894,0x11DE,0x8C,0x30,0x08,0x00,0x20,0x0C,0x9A,0x66);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkScreenPreviewCallback,0xD4FA2345,0x9FBA,0x4497,0x95,0xC3,0xC0,0xC3,0xCE,0xD3,0xCD,0xA8);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkGLScreenPreviewHelper,0xCEB778E2,0xC202,0x4EC8,0x90,0x85,0x0C,0xD2,0x85,0xCC,0x55,0x22);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDX9ScreenPreviewHelper,0xF2DD78CA,0x2921,0x4AC2,0xB5,0xBC,0xBF,0xDC,0xC2,0x03,0x5A,0x1F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkWPFDX9ScreenPreviewHelper,0xC59346CD,0x9326,0x4266,0xAC,0x2D,0x5C,0x19,0x0F,0x57,0x99,0xEE);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotificationCallback,0xb002a1ec,0x070d,0x4288,0x82,0x89,0xbd,0x5d,0x36,0xe5,0xff,0x0d);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotification,0x1d70faac,0xfd27,0x4866,0x9d,0xe6,0x09,0x39,0xd1,0xe4,0xc7,0xf1);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileAttributes,0xF47551D7,0xAD22,0x47AF,0xBC,0xFD,0x6B,0xE8,0x8A,0xA8,0x79,0xD9);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileIterator,0x29E5A8C0,0x8BE4,0x46EB,0x93,0xAC,0x31,0xDA,0xAB,0x5B,0x7B,0xF2);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfile,0x16093466,0x674A,0x432B,0x9D,0xA0,0x1A,0xC2,0xC5,0xA8,0x24,0x1C);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileCallback,0xA4F9341E,0x97AA,0x4E04,0x89,0x35,0x15,0xF8,0x09,0x89,0x8C,0xEA);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileManager,0x30D41429,0x3998,0x4B6D,0x84,0xF8,0x78,0xC9,0x4A,0x79,0x7C,0x6E);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkStatistics,0x21CB2ED1,0x4429,0x42BE,0xAA,0xF3,0x22,0xA3,0xB1,0xDD,0x3A,0xE0);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkStatus,0x2A04A635,0xED42,0x41EF,0x93,0x42,0x0E,0x11,0xF8,0xCF,0x6B,0x5E);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkKeyer,0x89AFCAF5,0x65F8,0x421E,0x98,0xF7,0x96,0xFE,0x5F,0x5B,0xFB,0xA3);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion,0x94C536D6,0xC821,0x42F5,0xA6,0x00,0xC6,0x66,0x29,0x95,0x51,0x01);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeviceNotificationCallback,0x4997053B,0x0ADF,0x4CC8,0xAC,0x70,0x7A,0x50,0xC4,0xBE,0x72,0x8F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDiscovery,0xCDBF631C,0xBC76,0x45FA,0xB4,0x4D,0xC5,0x50,0x59,0xBC,0x61,0x01);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkIterator,0xBA6C6F44,0x6DA5,0x4DCE,0x94,0xAA,0xEE,0x2D,0x13,0x72,0xA6,0x76);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkAPIInformation,0x263CA19F,0xED09,0x482E,0x9F,0x9D,0x84,0x00,0x57,0x83,0xA2,0x37);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGLScreenPreviewHelper,0x1E332DAE,0x0D04,0x49EB,0xB8,0xA1,0xB6,0xE0,0x0B,0x2B,0x6B,0xD0);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGL3ScreenPreviewHelper,0x166804E4,0x15EF,0x4BFD,0xB6,0x23,0xB5,0xBA,0x92,0x16,0x67,0xC5);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDX9ScreenPreviewHelper,0x0EB111ED,0xADA6,0x43A6,0x8B,0x16,0xCA,0x5D,0x27,0xEE,0xA1,0x5E);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkWPFDX9ScreenPreviewHelper,0x5E64496D,0x4BB2,0x45D5,0x9B,0x63,0xBF,0x1B,0x46,0x3B,0x18,0xAF);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion,0x771AD62D,0x671F,0x4442,0xAC,0x90,0xB0,0x70,0xC5,0x41,0x09,0x0A);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDiscovery,0x22FBFC33,0x8D07,0x495C,0xA5,0xBF,0xDA,0xB5,0xEA,0x9B,0x82,0xDB);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoFrameAncillaryPackets,0x6F47097E,0xB390,0x4650,0xBC,0xB6,0xC4,0xD5,0x2F,0xAA,0x16,0x43);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkStatus_v15_3_1,0x5F558200,0x4028,0x49BC,0xBE,0xAC,0xDB,0x3F,0xA4,0xA9,0x6E,0x46);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v15_3_1,0x912F634B,0x2D4E,0x40A4,0x8A,0xAB,0x8D,0x80,0xB7,0x3F,0x12,0x89);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBuffer_v15_3_1,0xCCB4B64A,0x5C86,0x4E02,0xB7,0x78,0x88,0x5D,0x35,0x27,0x09,0xFE);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocator_v15_3_1,0x3481A4DF,0x2B11,0x4E55,0xAC,0x61,0x83,0x6B,0x87,0x98,0x5E,0x9A);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocatorProvider_v15_3_1,0x08B80403,0xBFF2,0x49D0,0xB4,0x48,0x8C,0x90,0x8B,0x9E,0x9F,0xC9);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v15_3_1,0x4095DB82,0xE294,0x4B8C,0xAA,0xA8,0x3B,0x9E,0x80,0xC4,0x93,0x36);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v15_3_1,0x1A8077F1,0x9FE2,0x4533,0x81,0x47,0x22,0x94,0x30,0x5E,0x25,0x3F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion_v15_3_1,0xA48755D9,0x8BD5,0x4727,0xA1,0xE9,0x06,0x9F,0xDE,0xDB,0xA6,0xE9);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotification_v15_3_1,0xB85DF4C8,0xBDF5,0x47C1,0x80,0x64,0x28,0x16,0x2E,0xBD,0xD4,0xEB);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion_v15_3_1,0x89BA47BD,0x1FE2,0x4D76,0x9B,0xFE,0xDE,0x85,0x04,0x9C,0x49,0x87);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileAttributes_v15_3_1,0x17D4BF8E,0x4911,0x473A,0x80,0xA0,0x73,0x1C,0xF6,0xFF,0x34,0x5B);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoOutputCallback_v14_2_1,0x20AA5225,0x1958,0x47CB,0x82,0x0B,0x80,0xA8,0xD5,0x21,0xA6,0xEE);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback_v14_2_1,0xC6FCE4C9,0xC4E4,0x4047,0x82,0xFB,0x5D,0x23,0x82,0x32,0xA9,0x02);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkMemoryAllocator_v14_2_1,0xB36EB6E7,0x9D29,0x4AA8,0x92,0xEF,0x84,0x3B,0x87,0xA2,0x89,0xE8);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v14_2_1,0xBE2D9020,0x461E,0x442F,0x84,0xB7,0xE9,0x49,0xCB,0x95,0x3B,0x9D);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v14_2_1,0xC21CDB6E,0xF414,0x46E4,0xA6,0x36,0x80,0xA5,0x66,0xE0,0xED,0x37);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInput_v14_2_1,0xF222551D,0x13DF,0x4FD8,0xB5,0x87,0x9D,0x4F,0x19,0xEC,0x12,0xC9);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame_v14_2_1,0x3F716FE0,0xF023,0x4111,0xBE,0x5D,0xEF,0x44,0x14,0xC0,0x5B,0x17);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkMutableVideoFrame_v14_2_1,0x69E2639F,0x40DA,0x4E19,0xB6,0xF2,0x20,0xAC,0xE8,0x15,0xC3,0x90);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame3DExtensions_v14_2_1,0xDA0F7E4A,0xEDC7,0x48A8,0x9C,0xDD,0x2D,0xB5,0x1C,0x72,0x9C,0xD7);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame_v14_2_1,0x05CFE374,0x537C,0x4094,0x9A,0x57,0x68,0x05,0x25,0x11,0x8F,0x44);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkScreenPreviewCallback_v14_2_1,0xB1D3F49A,0x85FE,0x4C5D,0x95,0xC8,0x0B,0x5D,0x5D,0xCC,0xD4,0x38);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkGLScreenPreviewHelper_v14_2_1,0x504E2209,0xCAC7,0x4C1A,0x9F,0xB4,0xC5,0xBB,0x62,0x74,0xD2,0x2F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDX9ScreenPreviewHelper_v14_2_1,0x2094B522,0xD1A1,0x40C0,0x9A,0xC7,0x1C,0x01,0x22,0x18,0xEF,0x02);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkWPFDX9ScreenPreviewHelper_v14_2_1,0xAD8EC84A,0x7DDE,0x11E9,0x8F,0x9E,0x2A,0x86,0xE4,0x08,0x5A,0x59);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion_v14_2_1,0x3BBCB8A2,0xDA2C,0x42D9,0xB5,0xD8,0x88,0x08,0x36,0x44,0xE9,0x9A);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGLScreenPreviewHelper_v14_2_1,0xF63E77C7,0xB655,0x4A4A,0x9A,0xD0,0x3C,0xA8,0x5D,0x39,0x43,0x43);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGL3ScreenPreviewHelper_v14_2_1,0x00696A71,0xEBC7,0x491F,0xAC,0x02,0x18,0xD3,0x39,0x3F,0x33,0xF0);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDX9ScreenPreviewHelper_v14_2_1,0xCC010023,0xE01D,0x4525,0x9D,0x59,0x80,0xC8,0xAB,0x3D,0xC7,0xA0);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkWPFDX9ScreenPreviewHelper_v14_2_1,0xEF2A8478,0x7DDF,0x11E9,0x8F,0x9E,0x2A,0x86,0xE4,0x08,0x5A,0x59);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion_v14_2_1,0x7DBBBB11,0x5B7B,0x467D,0xAE,0xA4,0xCE,0xA4,0x68,0xFD,0x36,0x8C);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback_v11_5_1,0xDD04E5EC,0x7415,0x42AB,0xAE,0x4A,0xE8,0x0C,0x4D,0xFC,0x04,0x4A);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v11_5_1,0x9434C6E4,0xB15D,0x4B1C,0x97,0x9E,0x66,0x1E,0x3D,0xDC,0xB4,0xB9);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_11,0xEF90380B,0x4AE5,0x4346,0x90,0x77,0xE2,0x88,0xE1,0x49,0xF1,0x29);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAttributes_v10_11,0xABC11843,0xD966,0x44CB,0x96,0xE2,0xA1,0xCB,0x5D,0x31,0x35,0xC4);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotification_v10_11,0x0A1FB207,0xE215,0x441B,0x9B,0x19,0x6F,0xA1,0x57,0x59,0x46,0xC5);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v10_11,0xCC5C8A6E,0x3F2F,0x4B3A,0x87,0xEA,0xFD,0x78,0xAF,0x30,0x05,0x64);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v10_11,0xAF22762B,0xDFAC,0x4846,0xAA,0x79,0xFA,0x88,0x83,0x56,0x09,0x95);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInput_v10_11,0x270587DA,0x6B7D,0x42E7,0xA1,0xF0,0x6D,0x85,0x3F,0x58,0x11,0x85);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkIterator_v10_11,0x87D2693F,0x8D4A,0x45C7,0xB4,0x3F,0x10,0xAC,0xBA,0x25,0xE6,0x8F);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDiscovery_v10_11,0x652615D4,0x26CD,0x4514,0xB1,0x61,0x2F,0xD5,0x07,0x2E,0xD0,0x08);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_9,0xCB71734A,0xFE37,0x4E8D,0x8E,0x13,0x80,0x21,0x33,0xA1,0xC3,0xF2);
MIDL_DEFINE_GUID(CLSID, CLSID_CBMDStreamingDiscovery_v10_8,0x0CAA31F6,0x8A26,0x40B0,0x86,0xA4,0xBF,0x58,0xDC,0xCA,0x71,0x0C);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_4,0x1E69FCF6,0x4203,0x4936,0x80,0x76,0x2A,0x9F,0x4C,0xFD,0x50,0xCB);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_2,0xC679A35B,0x610C,0x4D09,0xB7,0x48,0x1D,0x04,0x78,0x10,0x0F,0xC0);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacket_v15_2,0xCC5BBF7E,0x029C,0x4D3B,0x91,0x58,0x60,0x00,0xEF,0x5E,0x36,0x70);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacketIterator_v15_2,0x3FC8994B,0x88FB,0x4C17,0x96,0x8F,0x9A,0xAB,0x69,0xD9,0x64,0xA7);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameAncillaryPackets_v15_2,0x6C186C0F,0x459E,0x41D8,0xAE,0xE2,0x48,0x12,0xD8,0x1A,0xEE,0x68);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoFrameAncillaryPackets_v15_2,0xF891AD29,0xD0C2,0x46E9,0xA9,0x26,0x4E,0x2D,0x0D,0xD8,0xCF,0xAD);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameMetadataExtensions_v11_5,0xD5973DC9,0x6432,0x46D0,0x8F,0x0B,0x24,0x96,0xF8,0xA1,0x23,0x8F);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v11_4,0x065A0F6C,0xC508,0x4D0D,0xB9,0x19,0xF5,0xEB,0x0E,0xBF,0xC9,0x6B);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v11_4,0x2A88CF76,0xF494,0x4216,0xA7,0xEF,0xDC,0x74,0xEE,0xB8,0x38,0x82);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkIterator_v10_8,0x1F2E109A,0x8F4F,0x49E4,0x92,0x03,0x13,0x55,0x95,0xCB,0x6F,0xA5);
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDiscovery_v10_8,0x1073A05C,0xD885,0x47E9,0xB3,0xC6,0x12,0x9B,0x3F,0x9F,0x64,0x8B);
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderConfiguration_v10_5,0x67455668,0x0848,0x45DF,0x8D,0x8E,0x35,0x0A,0x77,0xC9,0xA0,0x28);
#undef MIDL_DEFINE_GUID
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,146 @@
#include "DeckLinkDisplayMode.h"
#include <cctype>
std::string NormalizeModeToken(const std::string& value)
{
std::string normalized;
for (unsigned char ch : value)
{
if (std::isalnum(ch))
normalized.push_back(static_cast<char>(std::tolower(ch)));
}
return normalized;
}
bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName)
{
VideoFormat videoMode;
if (!ResolveConfiguredVideoFormat(videoFormat, frameRate, videoMode))
return false;
displayMode = videoMode.displayMode;
displayModeName = videoMode.displayName;
return true;
}
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode)
{
const std::string formatToken = NormalizeModeToken(videoFormat);
const std::string frameToken = NormalizeModeToken(frameRate);
const std::string combinedToken = formatToken + frameToken;
struct ModeOption
{
const char* token;
BMDDisplayMode mode;
const char* displayName;
};
static const ModeOption options[] =
{
{ "720p50", bmdModeHD720p50, "720p50" },
{ "hd720p50", bmdModeHD720p50, "720p50" },
{ "720p5994", bmdModeHD720p5994, "720p59.94" },
{ "hd720p5994", bmdModeHD720p5994, "720p59.94" },
{ "720p60", bmdModeHD720p60, "720p60" },
{ "hd720p60", bmdModeHD720p60, "720p60" },
{ "1080i50", bmdModeHD1080i50, "1080i50" },
{ "hd1080i50", bmdModeHD1080i50, "1080i50" },
{ "1080i5994", bmdModeHD1080i5994, "1080i59.94" },
{ "hd1080i5994", bmdModeHD1080i5994, "1080i59.94" },
{ "1080i60", bmdModeHD1080i6000, "1080i60" },
{ "hd1080i60", bmdModeHD1080i6000, "1080i60" },
{ "1080p2398", bmdModeHD1080p2398, "1080p23.98" },
{ "hd1080p2398", bmdModeHD1080p2398, "1080p23.98" },
{ "1080p24", bmdModeHD1080p24, "1080p24" },
{ "hd1080p24", bmdModeHD1080p24, "1080p24" },
{ "1080p25", bmdModeHD1080p25, "1080p25" },
{ "hd1080p25", bmdModeHD1080p25, "1080p25" },
{ "1080p2997", bmdModeHD1080p2997, "1080p29.97" },
{ "hd1080p2997", bmdModeHD1080p2997, "1080p29.97" },
{ "1080p30", bmdModeHD1080p30, "1080p30" },
{ "hd1080p30", bmdModeHD1080p30, "1080p30" },
{ "1080p50", bmdModeHD1080p50, "1080p50" },
{ "hd1080p50", bmdModeHD1080p50, "1080p50" },
{ "1080p5994", bmdModeHD1080p5994, "1080p59.94" },
{ "hd1080p5994", bmdModeHD1080p5994, "1080p59.94" },
{ "1080p60", bmdModeHD1080p6000, "1080p60" },
{ "hd1080p60", bmdModeHD1080p6000, "1080p60" },
{ "2160p2398", bmdMode4K2160p2398, "2160p23.98" },
{ "4k2160p2398", bmdMode4K2160p2398, "2160p23.98" },
{ "2160p24", bmdMode4K2160p24, "2160p24" },
{ "4k2160p24", bmdMode4K2160p24, "2160p24" },
{ "2160p25", bmdMode4K2160p25, "2160p25" },
{ "4k2160p25", bmdMode4K2160p25, "2160p25" },
{ "2160p2997", bmdMode4K2160p2997, "2160p29.97" },
{ "4k2160p2997", bmdMode4K2160p2997, "2160p29.97" },
{ "2160p30", bmdMode4K2160p30, "2160p30" },
{ "4k2160p30", bmdMode4K2160p30, "2160p30" },
{ "2160p50", bmdMode4K2160p50, "2160p50" },
{ "4k2160p50", bmdMode4K2160p50, "2160p50" },
{ "2160p5994", bmdMode4K2160p5994, "2160p59.94" },
{ "4k2160p5994", bmdMode4K2160p5994, "2160p59.94" },
{ "2160p60", bmdMode4K2160p60, "2160p60" },
{ "4k2160p60", bmdMode4K2160p60, "2160p60" }
};
for (const ModeOption& option : options)
{
if (combinedToken == option.token || (frameToken.empty() && formatToken == option.token))
{
videoMode.displayMode = option.mode;
videoMode.displayName = option.displayName;
return true;
}
}
return false;
}
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error)
{
if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input))
{
error = "Unsupported DeckLink inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
inputVideoFormat + " / " + inputFrameRate;
return false;
}
if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output))
{
error = "Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json: " +
outputVideoFormat + " / " + outputFrameRate;
return false;
}
return true;
}
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode)
{
if (!iterator || !foundMode)
return false;
*foundMode = NULL;
IDeckLinkDisplayMode* candidate = NULL;
while (iterator->Next(&candidate) == S_OK)
{
if (candidate->GetDisplayMode() == targetMode)
{
*foundMode = candidate;
return true;
}
candidate->Release();
candidate = NULL;
}
return false;
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "DeckLinkAPI_h.h"
#include <string>
struct FrameSize
{
unsigned width = 0;
unsigned height = 0;
bool IsEmpty() const { return width == 0 || height == 0; }
};
inline bool operator==(const FrameSize& left, const FrameSize& right)
{
return left.width == right.width && left.height == right.height;
}
inline bool operator!=(const FrameSize& left, const FrameSize& right)
{
return !(left == right);
}
struct VideoFormat
{
BMDDisplayMode displayMode = bmdModeHD1080p5994;
std::string displayName = "1080p59.94";
};
struct VideoFormatSelection
{
VideoFormat input;
VideoFormat output;
};
std::string NormalizeModeToken(const std::string& value);
bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName);
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode);
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error);
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode);

View File

@@ -0,0 +1,100 @@
#include "DeckLinkFrameTransfer.h"
#include "DeckLinkSession.h"
////////////////////////////////////////////
// DeckLink Capture Delegate Class
////////////////////////////////////////////
CaptureDelegate::CaptureDelegate(DeckLinkSession* pOwner) :
m_pOwner(pOwner),
mRefCount(1)
{
}
HRESULT CaptureDelegate::QueryInterface(REFIID, LPVOID* ppv)
{
*ppv = NULL;
return E_NOINTERFACE;
}
ULONG CaptureDelegate::AddRef()
{
return InterlockedIncrement(&mRefCount);
}
ULONG CaptureDelegate::Release()
{
int newCount = InterlockedDecrement(&mRefCount);
if (newCount == 0)
delete this;
return newCount;
}
HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputFrame, IDeckLinkAudioInputPacket*)
{
if (!inputFrame)
{
// It's possible to receive a NULL inputFrame, but a valid audioPacket. Ignore audio-only frame.
return S_OK;
}
bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource;
m_pOwner->HandleVideoInputFrame(inputFrame, hasNoInputSource);
return S_OK;
}
HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags)
{
return S_OK;
}
////////////////////////////////////////////
// DeckLink Playout Delegate Class
////////////////////////////////////////////
PlayoutDelegate::PlayoutDelegate(DeckLinkSession* pOwner) :
m_pOwner(pOwner),
mRefCount(1)
{
}
HRESULT PlayoutDelegate::QueryInterface(REFIID, LPVOID* ppv)
{
*ppv = NULL;
return E_NOINTERFACE;
}
ULONG PlayoutDelegate::AddRef()
{
return InterlockedIncrement(&mRefCount);
}
ULONG PlayoutDelegate::Release()
{
int newCount = InterlockedDecrement(&mRefCount);
if (newCount == 0)
delete this;
return newCount;
}
HRESULT PlayoutDelegate::ScheduledFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
{
switch (result)
{
case bmdOutputFrameDisplayedLate:
case bmdOutputFrameDropped:
case bmdOutputFrameCompleted:
case bmdOutputFrameFlushed:
// Late/drop counts are recorded by VideoBackend; keep this callback lean.
break;
default:
OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n");
}
m_pOwner->HandlePlayoutFrameCompleted(completedFrame, result);
return S_OK;
}
HRESULT PlayoutDelegate::ScheduledPlaybackHasStopped()
{
return S_OK;
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <windows.h>
#include <atomic>
#include "DeckLinkAPI_h.h"
class DeckLinkSession;
////////////////////////////////////////////
// Capture Delegate Class
////////////////////////////////////////////
class CaptureDelegate : public IDeckLinkInputCallback
{
DeckLinkSession* m_pOwner;
LONG mRefCount;
public:
CaptureDelegate(DeckLinkSession* pOwner);
// IUnknown needs only a dummy implementation
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket);
virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags);
};
////////////////////////////////////////////
// Render Delegate Class
////////////////////////////////////////////
class PlayoutDelegate : public IDeckLinkVideoOutputCallback
{
DeckLinkSession* m_pOwner;
LONG mRefCount;
public:
PlayoutDelegate(DeckLinkSession* pOwner);
// IUnknown needs only a dummy implementation
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result);
virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped();
};

View File

@@ -0,0 +1,933 @@
#include "DeckLinkSession.h"
#include <atlbase.h>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <new>
#include <sstream>
#include <utility>
#include <vector>
namespace
{
constexpr int64_t kMinimumHealthyScheduleLeadFrames = 4;
constexpr int64_t kProactiveScheduleLeadFloorFrames = 1;
class SystemMemoryDeckLinkVideoBuffer : public IDeckLinkVideoBuffer
{
public:
SystemMemoryDeckLinkVideoBuffer(void* bytes, unsigned long long sizeBytes) :
mBytes(bytes),
mSizeBytes(sizeBytes),
mRefCount(1)
{
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv) override
{
if (ppv == nullptr)
return E_POINTER;
if (iid == IID_IUnknown || iid == IID_IDeckLinkVideoBuffer)
{
*ppv = static_cast<IDeckLinkVideoBuffer*>(this);
AddRef();
return S_OK;
}
*ppv = nullptr;
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() override
{
return ++mRefCount;
}
ULONG STDMETHODCALLTYPE Release() override
{
const ULONG refCount = --mRefCount;
if (refCount == 0)
delete this;
return refCount;
}
HRESULT STDMETHODCALLTYPE GetBytes(void** buffer) override
{
if (buffer == nullptr)
return E_POINTER;
*buffer = mBytes;
return mBytes != nullptr ? S_OK : E_FAIL;
}
HRESULT STDMETHODCALLTYPE GetSize(unsigned long long* bufferSize) override
{
if (bufferSize == nullptr)
return E_POINTER;
*bufferSize = mSizeBytes;
return S_OK;
}
HRESULT STDMETHODCALLTYPE StartAccess(BMDBufferAccessFlags) override
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE EndAccess(BMDBufferAccessFlags) override
{
return S_OK;
}
private:
void* mBytes = nullptr;
unsigned long long mSizeBytes = 0;
std::atomic<ULONG> mRefCount;
};
std::string BstrToUtf8(BSTR value)
{
if (value == nullptr)
return std::string();
const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
if (requiredBytes <= 1)
return std::string();
std::vector<char> utf8Name(static_cast<std::size_t>(requiredBytes), '\0');
if (WideCharToMultiByte(CP_UTF8, 0, value, -1, utf8Name.data(), requiredBytes, NULL, NULL) <= 0)
return std::string();
return std::string(utf8Name.data());
}
bool InputSupportsFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
{
if (input == nullptr)
return false;
BOOL supported = FALSE;
BMDDisplayMode actualMode = bmdModeUnknown;
const HRESULT result = input->DoesSupportVideoMode(
bmdVideoConnectionUnspecified,
displayMode,
pixelFormat,
bmdNoVideoInputConversion,
bmdSupportedVideoModeDefault,
&actualMode,
&supported);
return result == S_OK && supported != FALSE;
}
bool OutputSupportsFormat(IDeckLinkOutput* output, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
{
if (output == nullptr)
return false;
BOOL supported = FALSE;
BMDDisplayMode actualMode = bmdModeUnknown;
const HRESULT result = output->DoesSupportVideoMode(
bmdVideoConnectionUnspecified,
displayMode,
pixelFormat,
bmdNoVideoOutputConversion,
bmdSupportedVideoModeDefault,
&actualMode,
&supported);
return result == S_OK && supported != FALSE;
}
}
DeckLinkSession::~DeckLinkSession()
{
ReleaseResources();
}
void DeckLinkSession::ReleaseResources()
{
if (input != nullptr)
input->SetCallback(nullptr);
captureDelegate.Release();
input.Release();
if (output != nullptr)
output->SetScheduledFrameCompletionCallback(nullptr);
if (keyer != nullptr)
{
keyer->Disable();
mState.externalKeyingActive = false;
}
keyer.Release();
playoutDelegate.Release();
outputVideoFrameQueue.clear();
output.Release();
}
bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
{
CComPtr<IDeckLinkIterator> deckLinkIterator;
CComPtr<IDeckLinkDisplayMode> inputMode;
CComPtr<IDeckLinkDisplayMode> outputMode;
mState.inputDisplayModeName = videoModes.input.displayName;
mState.outputDisplayModeName = videoModes.output.displayName;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&deckLinkIterator));
if (FAILED(result))
{
error = "Please install the Blackmagic DeckLink drivers to use the features of this application.";
return false;
}
CComPtr<IDeckLink> deckLink;
while (deckLinkIterator->Next(&deckLink) == S_OK)
{
int64_t duplexMode;
bool deviceSupportsInternalKeying = false;
bool deviceSupportsExternalKeying = false;
std::string modelName;
CComPtr<IDeckLinkProfileAttributes> deckLinkAttributes;
if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
{
printf("Could not obtain the IDeckLinkProfileAttributes interface\n");
deckLink.Release();
continue;
}
result = deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &duplexMode);
BOOL attributeFlag = FALSE;
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &attributeFlag) == S_OK)
deviceSupportsInternalKeying = (attributeFlag != FALSE);
attributeFlag = FALSE;
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
deviceSupportsExternalKeying = (attributeFlag != FALSE);
CComBSTR modelNameBstr;
if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK)
modelName = BstrToUtf8(modelNameBstr);
if (result != S_OK || duplexMode == bmdDuplexInactive)
{
deckLink.Release();
continue;
}
bool inputUsed = false;
if (!input && deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&input) == S_OK)
inputUsed = true;
if (!output && (!inputUsed || (duplexMode == bmdDuplexFull)))
{
if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK)
output.Release();
else
{
mState.outputModelName = modelName;
mState.supportsInternalKeying = deviceSupportsInternalKeying;
mState.supportsExternalKeying = deviceSupportsExternalKeying;
}
}
deckLink.Release();
if (output && input)
break;
}
if (!output)
{
error = "Expected an Output DeckLink device";
ReleaseResources();
return false;
}
CComPtr<IDeckLinkDisplayModeIterator> inputDisplayModeIterator;
if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK)
{
error = "Cannot get input Display Mode Iterator.";
ReleaseResources();
return false;
}
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, videoModes.input.displayMode, &inputMode))
{
error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName;
ReleaseResources();
return false;
}
inputDisplayModeIterator.Release();
CComPtr<IDeckLinkDisplayModeIterator> outputDisplayModeIterator;
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
{
error = "Cannot get output Display Mode Iterator.";
ReleaseResources();
return false;
}
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, videoModes.output.displayMode, &outputMode))
{
error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName;
ReleaseResources();
return false;
}
mState.outputFrameSize = { static_cast<unsigned>(outputMode->GetWidth()), static_cast<unsigned>(outputMode->GetHeight()) };
mState.inputFrameSize = inputMode
? FrameSize{ static_cast<unsigned>(inputMode->GetWidth()), static_cast<unsigned>(inputMode->GetHeight()) }
: mState.outputFrameSize;
if (!input)
mState.inputDisplayModeName = "No input - black frame";
BMDTimeValue frameDuration = 0;
BMDTimeScale frameTimescale = 0;
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
mScheduler.Configure(frameDuration, frameTimescale, mPlayoutPolicy);
mState.frameBudgetMilliseconds = mScheduler.FrameBudgetMilliseconds();
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
mState.outputFrameRowBytes = mState.outputFrameSize.width * 4u;
mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
mState.outputPackTextureWidth = mState.outputFrameSize.width;
mState.hasInputDevice = input != nullptr;
mState.hasInputSource = false;
return true;
}
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
{
if (!output)
{
error = "Expected an Output DeckLink device";
return false;
}
mState.formatStatusMessage.clear();
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV);
mState.inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8;
if (input != nullptr && !inputTenBitSupported)
mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. ";
const bool outputTenBitSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUV);
const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUVA);
mState.outputPixelFormat = outputAlphaRequired
? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8)
: (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8);
if (outputAlphaRequired && outputTenBitYuvaSupported)
mState.formatStatusMessage += "External keying requires alpha; using 10-bit YUVA output. ";
else if (outputAlphaRequired)
mState.formatStatusMessage += "External keying requires alpha, but DeckLink output does not report 10-bit YUVA support for the configured mode; using 8-bit BGRA output. ";
else if (!outputTenBitSupported)
mState.formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. ";
int deckLinkOutputRowBytes = 0;
if (output->RowBytesForPixelFormat(DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat), mState.outputFrameSize.width, &deckLinkOutputRowBytes) != S_OK)
{
error = "DeckLink output setup failed while calculating output row bytes.";
return false;
}
mState.outputFrameRowBytes = static_cast<unsigned>(deckLinkOutputRowBytes);
mState.outputPackTextureWidth = OutputIsTenBit()
? PackedTextureWidthFromRowBytes(mState.outputFrameRowBytes)
: mState.outputFrameSize.width;
if (InputIsTenBit())
{
int deckLinkInputRowBytes = 0;
if (output->RowBytesForPixelFormat(bmdFormat10BitYUV, mState.inputFrameSize.width, &deckLinkInputRowBytes) == S_OK)
mState.inputFrameRowBytes = static_cast<unsigned>(deckLinkInputRowBytes);
else
mState.inputFrameRowBytes = MinimumV210RowBytes(mState.inputFrameSize.width);
}
else
{
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
}
mState.captureTextureWidth = InputIsTenBit()
? PackedTextureWidthFromRowBytes(mState.inputFrameRowBytes)
: mState.inputFrameSize.width / 2u;
std::ostringstream status;
status << "DeckLink formats: capture " << (input ? VideoIOPixelFormatName(mState.inputPixelFormat) : "none")
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat) << ".";
if (!mState.formatStatusMessage.empty())
status << " " << mState.formatStatusMessage;
mState.formatStatusMessage = status.str();
return true;
}
bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error)
{
mInputFrameCallback = std::move(callback);
if (!input)
{
mState.hasInputSource = false;
mState.inputDisplayModeName = "No input - black frame";
return true;
}
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat);
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
{
if (mState.inputPixelFormat == VideoIOPixelFormat::V210)
{
OutputDebugStringA("DeckLink 10-bit input could not be enabled; falling back to 8-bit capture.\n");
mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
if (input->EnableVideoInput(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK)
{
std::ostringstream status;
status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat)
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat)
<< ". DeckLink 10-bit input enable failed; using 8-bit capture.";
mState.formatStatusMessage = status.str();
goto input_enabled;
}
}
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
input.Release();
mState.hasInputDevice = false;
mState.hasInputSource = false;
mState.inputDisplayModeName = "No input - black frame";
return true;
}
input_enabled:
captureDelegate.Attach(new (std::nothrow) CaptureDelegate(this));
if (captureDelegate == nullptr)
{
error = "DeckLink input setup failed while creating the capture callback.";
return false;
}
if (input->SetCallback(captureDelegate) != S_OK)
{
error = "DeckLink input setup failed while installing the capture callback.";
return false;
}
return true;
}
bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
{
mOutputFrameCallback = std::move(callback);
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK)
{
error = "DeckLink output setup failed while enabling video output.";
return false;
}
if (output->QueryInterface(IID_IDeckLinkKeyer, (void**)&keyer) == S_OK && keyer != NULL)
mState.keyerInterfaceAvailable = true;
if (externalKeyingEnabled)
{
if (!mState.supportsExternalKeying)
{
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
}
else if (!mState.keyerInterfaceAvailable)
{
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface.";
}
else if (keyer->Enable(TRUE) != S_OK || keyer->SetLevel(255) != S_OK)
{
mState.statusMessage = "External keying was requested, but enabling the DeckLink keyer failed.";
}
else
{
mState.externalKeyingActive = true;
mState.statusMessage = "External keying is active on the selected DeckLink output.";
}
}
else if (mState.supportsExternalKeying)
{
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
}
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
mPlayoutPolicy = policy;
for (unsigned i = 0; i < policy.outputFramePoolSize; i++)
{
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
const BMDPixelFormat deckLinkOutputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat);
if (output->CreateVideoFrame(mState.outputFrameSize.width, mState.outputFrameSize.height, mState.outputFrameRowBytes, deckLinkOutputPixelFormat, bmdFrameFlagFlipVertical, &outputFrame) != S_OK)
{
error = "DeckLink output setup failed while creating an output video frame.";
return false;
}
outputVideoFrameQueue.push_back(outputFrame);
}
playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(this));
if (playoutDelegate == nullptr)
{
error = "DeckLink output setup failed while creating the playout callback.";
return false;
}
if (output->SetScheduledFrameCompletionCallback(playoutDelegate) != S_OK)
{
error = "DeckLink output setup failed while installing the scheduled-frame callback.";
return false;
}
if (!mState.formatStatusMessage.empty())
mState.statusMessage = mState.statusMessage.empty() ? mState.formatStatusMessage : mState.formatStatusMessage + " " + mState.statusMessage;
return true;
}
double DeckLinkSession::FrameBudgetMilliseconds() const
{
return mScheduler.FrameBudgetMilliseconds();
}
bool DeckLinkSession::AcquireNextOutputVideoFrame(CComPtr<IDeckLinkMutableVideoFrame>& outputVideoFrame)
{
if (outputVideoFrameQueue.empty())
return false;
outputVideoFrame = outputVideoFrameQueue.front();
outputVideoFrameQueue.pop_front();
return outputVideoFrame != nullptr;
}
bool DeckLinkSession::PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame)
{
if (outputVideoFrame == nullptr)
return false;
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
return false;
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
return false;
void* pFrame = nullptr;
outputVideoFrameBuffer->GetBytes(&pFrame);
frame.bytes = pFrame;
frame.rowBytes = outputVideoFrame->GetRowBytes();
frame.width = mState.outputFrameSize.width;
frame.height = mState.outputFrameSize.height;
frame.pixelFormat = mState.outputPixelFormat;
outputVideoFrame->AddRef();
frame.nativeFrame = outputVideoFrame;
frame.nativeBuffer = outputVideoFrameBuffer.Detach();
return true;
}
bool DeckLinkSession::ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
{
if (outputVideoFrame == nullptr || output == nullptr)
{
++mState.deckLinkScheduleFailureCount;
return false;
}
if (mScheduleRealignmentPending)
{
RealignScheduleCursorToPlayback();
mScheduleRealignmentPending = false;
}
UpdateScheduleLeadTelemetry();
MaybeRealignScheduleCursorForLowLead();
const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime();
const auto scheduleStart = std::chrono::steady_clock::now();
const HRESULT result = output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale);
const auto scheduleEnd = std::chrono::steady_clock::now();
mState.deckLinkScheduleCallMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(scheduleEnd - scheduleStart).count();
if (result != S_OK)
++mState.deckLinkScheduleFailureCount;
RefreshBufferedVideoFrameCount();
return result == S_OK;
}
void DeckLinkSession::UpdateScheduleLeadTelemetry()
{
if (output == nullptr)
{
mState.deckLinkScheduleLeadAvailable = false;
return;
}
BMDTimeValue streamTime = 0;
double playbackSpeed = 0.0;
if (output->GetScheduledStreamTime(mScheduler.TimeScale(), &streamTime, &playbackSpeed) != S_OK || playbackSpeed <= 0.0)
{
mState.deckLinkScheduleLeadAvailable = false;
return;
}
const uint64_t playbackFrameIndex = streamTime >= 0 && mScheduler.FrameDuration() > 0
? static_cast<uint64_t>(streamTime / mScheduler.FrameDuration())
: 0;
const uint64_t nextScheduleFrameIndex = mScheduler.ScheduledFrameIndex();
mState.deckLinkScheduleLeadAvailable = true;
mState.deckLinkPlaybackStreamTime = streamTime;
mState.deckLinkPlaybackFrameIndex = playbackFrameIndex;
mState.deckLinkNextScheduleFrameIndex = nextScheduleFrameIndex;
mState.deckLinkScheduleLeadFrames = static_cast<int64_t>(nextScheduleFrameIndex) - static_cast<int64_t>(playbackFrameIndex);
}
void DeckLinkSession::MaybeRealignScheduleCursorForLowLead()
{
if (!mState.deckLinkScheduleLeadAvailable)
return;
if (mState.deckLinkScheduleLeadFrames >= kMinimumHealthyScheduleLeadFrames)
{
mProactiveScheduleRealignmentArmed = true;
return;
}
if (!mProactiveScheduleRealignmentArmed || mState.deckLinkScheduleLeadFrames > kProactiveScheduleLeadFloorFrames)
return;
RealignScheduleCursorToPlayback();
mProactiveScheduleRealignmentArmed = false;
}
void DeckLinkSession::RealignScheduleCursorToPlayback()
{
if (output == nullptr)
return;
BMDTimeValue streamTime = 0;
double playbackSpeed = 0.0;
if (output->GetScheduledStreamTime(mScheduler.TimeScale(), &streamTime, &playbackSpeed) != S_OK || playbackSpeed <= 0.0)
return;
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
mScheduler.AlignNextScheduleTimeToPlayback(streamTime, policy.targetPrerollFrames);
++mState.deckLinkScheduleRealignmentCount;
UpdateScheduleLeadTelemetry();
}
bool DeckLinkSession::ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame)
{
if (output == nullptr || frame.bytes == nullptr || frame.rowBytes <= 0 || frame.height == 0)
return false;
CComPtr<IDeckLinkVideoBuffer> videoBuffer;
videoBuffer.Attach(new (std::nothrow) SystemMemoryDeckLinkVideoBuffer(
frame.bytes,
static_cast<unsigned long long>(frame.rowBytes) * static_cast<unsigned long long>(frame.height)));
if (videoBuffer == nullptr)
return false;
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
const BMDPixelFormat pixelFormat = DeckLinkPixelFormatForVideoIO(frame.pixelFormat);
if (output->CreateVideoFrameWithBuffer(
frame.width,
frame.height,
frame.rowBytes,
pixelFormat,
bmdFrameFlagFlipVertical,
videoBuffer,
&outputVideoFrame) != S_OK)
{
return false;
}
IDeckLinkVideoFrame* scheduledFrame = outputVideoFrame;
{
std::lock_guard<std::mutex> lock(mScheduledSystemFrameMutex);
mScheduledSystemFrameBuffers[scheduledFrame] = frame.bytes;
}
if (ScheduleFrame(outputVideoFrame))
return true;
{
std::lock_guard<std::mutex> lock(mScheduledSystemFrameMutex);
mScheduledSystemFrameBuffers.erase(scheduledFrame);
}
return false;
}
bool DeckLinkSession::ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
{
if (outputVideoFrame == nullptr)
return false;
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
return false;
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
return false;
void* pFrame = nullptr;
outputVideoFrameBuffer->GetBytes((void**)&pFrame);
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mState.outputFrameSize.height);
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
return ScheduleFrame(outputVideoFrame);
}
void DeckLinkSession::RefreshBufferedVideoFrameCount()
{
if (output == nullptr)
{
mState.actualDeckLinkBufferedFramesAvailable = false;
return;
}
unsigned int bufferedFrameCount = 0;
if (output->GetBufferedVideoFrameCount(&bufferedFrameCount) == S_OK)
{
mState.actualDeckLinkBufferedFrames = bufferedFrameCount;
mState.actualDeckLinkBufferedFramesAvailable = true;
}
else
{
mState.actualDeckLinkBufferedFramesAvailable = false;
}
}
bool DeckLinkSession::BeginOutputFrame(VideoIOOutputFrame& frame)
{
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
return AcquireNextOutputVideoFrame(outputVideoFrame) && PopulateOutputFrame(outputVideoFrame, frame);
}
void DeckLinkSession::EndOutputFrame(VideoIOOutputFrame& frame)
{
IDeckLinkVideoBuffer* outputVideoFrameBuffer = static_cast<IDeckLinkVideoBuffer*>(frame.nativeBuffer);
if (outputVideoFrameBuffer != nullptr)
{
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
outputVideoFrameBuffer->Release();
}
frame.nativeBuffer = nullptr;
frame.bytes = nullptr;
}
VideoPlayoutRecoveryDecision DeckLinkSession::AccountForCompletionResult(VideoIOCompletionResult completionResult, uint64_t readyQueueDepth)
{
return mScheduler.AccountForCompletionResult(completionResult, readyQueueDepth);
}
bool DeckLinkSession::ScheduleOutputFrame(const VideoIOOutputFrame& frame)
{
if (frame.nativeFrame == nullptr)
return ScheduleSystemMemoryFrame(frame);
IDeckLinkMutableVideoFrame* outputVideoFrame = static_cast<IDeckLinkMutableVideoFrame*>(frame.nativeFrame);
const bool scheduled = ScheduleFrame(outputVideoFrame);
if (outputVideoFrame != nullptr)
outputVideoFrame->Release();
return scheduled;
}
bool DeckLinkSession::PrepareOutputSchedule()
{
mScheduler.Reset();
RefreshBufferedVideoFrameCount();
return output != nullptr;
}
bool DeckLinkSession::StartInputStreams()
{
if (!input)
return true;
if (input->StartStreams() != S_OK)
{
MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
return true;
}
bool DeckLinkSession::StartScheduledPlayback()
{
if (!output)
{
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
if (output->StartScheduledPlayback(0, mScheduler.TimeScale(), 1.0) != S_OK)
{
MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
RefreshBufferedVideoFrameCount();
return true;
}
bool DeckLinkSession::Start()
{
if (!output)
{
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
if (outputVideoFrameQueue.empty())
{
MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
mPlayoutPolicy = policy;
if (!PrepareOutputSchedule())
return false;
for (unsigned i = 0; i < policy.targetPrerollFrames; i++)
{
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
if (!AcquireNextOutputVideoFrame(outputVideoFrame))
{
MessageBoxA(NULL, "Could not acquire a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
if (!ScheduleBlackFrame(outputVideoFrame))
{
MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
return false;
}
}
return StartInputStreams() && StartScheduledPlayback();
}
bool DeckLinkSession::Stop()
{
if (keyer != nullptr)
{
keyer->Disable();
mState.externalKeyingActive = false;
}
if (input)
{
input->StopStreams();
input->DisableVideoInput();
}
if (output)
{
output->StopScheduledPlayback(0, NULL, 0);
output->DisableVideoOutput();
}
return true;
}
void DeckLinkSession::HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
{
mState.hasInputSource = !hasNoInputSource;
if (hasNoInputSource || mInputFrameCallback == nullptr)
{
VideoIOFrame frame;
frame.width = mState.inputFrameSize.width;
frame.height = mState.inputFrameSize.height;
frame.pixelFormat = mState.inputPixelFormat;
frame.hasNoInputSource = hasNoInputSource;
if (mInputFrameCallback)
mInputFrameCallback(frame);
return;
}
CComPtr<IDeckLinkVideoBuffer> inputFrameBuffer;
void* videoPixels = nullptr;
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK)
return;
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
return;
inputFrameBuffer->GetBytes(&videoPixels);
VideoIOFrame frame;
frame.bytes = videoPixels;
frame.rowBytes = inputFrame->GetRowBytes();
frame.width = static_cast<unsigned>(inputFrame->GetWidth());
frame.height = static_cast<unsigned>(inputFrame->GetHeight());
frame.pixelFormat = mState.inputPixelFormat;
frame.hasNoInputSource = hasNoInputSource;
mInputFrameCallback(frame);
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
}
void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
{
RefreshBufferedVideoFrameCount();
void* completedSystemBuffer = nullptr;
if (completedFrame != nullptr)
{
bool externalSystemFrame = false;
{
std::lock_guard<std::mutex> lock(mScheduledSystemFrameMutex);
auto externalFrame = mScheduledSystemFrameBuffers.find(completedFrame);
if (externalFrame != mScheduledSystemFrameBuffers.end())
{
completedSystemBuffer = externalFrame->second;
mScheduledSystemFrameBuffers.erase(externalFrame);
externalSystemFrame = true;
}
}
if (!externalSystemFrame)
{
CComPtr<IDeckLinkMutableVideoFrame> reusableFrame;
if (completedFrame->QueryInterface(IID_IDeckLinkMutableVideoFrame, reinterpret_cast<void**>(&reusableFrame)) == S_OK &&
reusableFrame != nullptr)
{
outputVideoFrameQueue.push_back(reusableFrame);
}
}
}
if (!mOutputFrameCallback)
return;
VideoIOCompletion completion;
completion.result = TranslateCompletionResult(completionResult);
if (completion.result == VideoIOCompletionResult::DisplayedLate || completion.result == VideoIOCompletionResult::Dropped)
{
if (mScheduleRealignmentArmed)
{
mScheduleRealignmentPending = true;
mScheduleRealignmentArmed = false;
}
}
else if (completion.result == VideoIOCompletionResult::Completed)
{
mScheduleRealignmentArmed = true;
}
completion.outputFrameBuffer = completedSystemBuffer;
mOutputFrameCallback(completion);
}
VideoIOCompletionResult DeckLinkSession::TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult)
{
switch (completionResult)
{
case bmdOutputFrameDisplayedLate:
return VideoIOCompletionResult::DisplayedLate;
case bmdOutputFrameDropped:
return VideoIOCompletionResult::Dropped;
case bmdOutputFrameFlushed:
return VideoIOCompletionResult::Flushed;
case bmdOutputFrameCompleted:
return VideoIOCompletionResult::Completed;
default:
return VideoIOCompletionResult::Unknown;
}
}

102
src/video/DeckLinkSession.h Normal file
View File

@@ -0,0 +1,102 @@
#pragma once
#include "DeckLinkAPI_h.h"
#include "DeckLinkDisplayMode.h"
#include "DeckLinkFrameTransfer.h"
#include "DeckLinkVideoIOFormat.h"
#include "VideoIOFormat.h"
#include "VideoIOTypes.h"
#include "VideoPlayoutPolicy.h"
#include "VideoPlayoutScheduler.h"
#include <atlbase.h>
#include <deque>
#include <mutex>
#include <string>
#include <unordered_map>
class OpenGLComposite;
class DeckLinkSession : public VideoIODevice
{
public:
DeckLinkSession() = default;
~DeckLinkSession();
void ReleaseResources() override;
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) override;
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) override;
bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) override;
bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) override;
bool PrepareOutputSchedule() override;
bool StartInputStreams() override;
bool StartScheduledPlayback() override;
bool Start() override;
bool Stop() override;
bool HasInputDevice() const { return mState.hasInputDevice; }
bool HasInputSource() const { return mState.hasInputSource; }
void SetInputSourceMissing(bool missing) { mState.hasInputSource = !missing; }
bool InputOutputDimensionsDiffer() const { return mState.inputFrameSize != mState.outputFrameSize; }
const FrameSize& InputFrameSize() const { return mState.inputFrameSize; }
const FrameSize& OutputFrameSize() const { return mState.outputFrameSize; }
unsigned InputFrameWidth() const { return mState.inputFrameSize.width; }
unsigned InputFrameHeight() const { return mState.inputFrameSize.height; }
unsigned OutputFrameWidth() const { return mState.outputFrameSize.width; }
unsigned OutputFrameHeight() const { return mState.outputFrameSize.height; }
VideoIOPixelFormat InputPixelFormat() const { return mState.inputPixelFormat; }
VideoIOPixelFormat OutputPixelFormat() const { return mState.outputPixelFormat; }
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.inputPixelFormat); }
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.outputPixelFormat); }
unsigned InputFrameRowBytes() const { return mState.inputFrameRowBytes; }
unsigned OutputFrameRowBytes() const { return mState.outputFrameRowBytes; }
unsigned CaptureTextureWidth() const { return mState.captureTextureWidth; }
unsigned OutputPackTextureWidth() const { return mState.outputPackTextureWidth; }
const std::string& FormatStatusMessage() const { return mState.formatStatusMessage; }
const std::string& InputDisplayModeName() const { return mState.inputDisplayModeName; }
const std::string& OutputModelName() const { return mState.outputModelName; }
bool SupportsInternalKeying() const { return mState.supportsInternalKeying; }
bool SupportsExternalKeying() const { return mState.supportsExternalKeying; }
bool KeyerInterfaceAvailable() const { return mState.keyerInterfaceAvailable; }
bool ExternalKeyingActive() const { return mState.externalKeyingActive; }
const std::string& StatusMessage() const { return mState.statusMessage; }
void SetStatusMessage(const std::string& message) { mState.statusMessage = message; }
const VideoIOState& State() const override { return mState; }
VideoIOState& MutableState() override { return mState; }
double FrameBudgetMilliseconds() const;
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult completionResult, uint64_t readyQueueDepth) override;
bool BeginOutputFrame(VideoIOOutputFrame& frame) override;
void EndOutputFrame(VideoIOOutputFrame& frame) override;
bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) override;
void HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
void HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult);
private:
bool AcquireNextOutputVideoFrame(CComPtr<IDeckLinkMutableVideoFrame>& outputVideoFrame);
bool PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame);
bool ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
void UpdateScheduleLeadTelemetry();
void MaybeRealignScheduleCursorForLowLead();
void RealignScheduleCursorToPlayback();
bool ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame);
bool ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
void RefreshBufferedVideoFrameCount();
static VideoIOCompletionResult TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult);
CComPtr<CaptureDelegate> captureDelegate;
CComPtr<PlayoutDelegate> playoutDelegate;
CComPtr<IDeckLinkInput> input;
CComPtr<IDeckLinkOutput> output;
CComPtr<IDeckLinkKeyer> keyer;
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
std::mutex mScheduledSystemFrameMutex;
std::unordered_map<IDeckLinkVideoFrame*, void*> mScheduledSystemFrameBuffers;
VideoIOState mState;
VideoPlayoutPolicy mPlayoutPolicy;
VideoPlayoutScheduler mScheduler;
bool mScheduleRealignmentPending = false;
bool mScheduleRealignmentArmed = true;
bool mProactiveScheduleRealignmentArmed = true;
InputFrameCallback mInputFrameCallback;
OutputFrameCallback mOutputFrameCallback;
};

View File

@@ -0,0 +1,28 @@
#include "DeckLinkVideoIOFormat.h"
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format)
{
switch (format)
{
case VideoIOPixelFormat::V210:
return bmdFormat10BitYUV;
case VideoIOPixelFormat::Yuva10:
return bmdFormat10BitYUVA;
case VideoIOPixelFormat::Bgra8:
return bmdFormat8BitBGRA;
case VideoIOPixelFormat::Uyvy8:
default:
return bmdFormat8BitYUV;
}
}
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format)
{
if (format == bmdFormat10BitYUV)
return VideoIOPixelFormat::V210;
if (format == bmdFormat10BitYUVA)
return VideoIOPixelFormat::Yuva10;
if (format == bmdFormat8BitBGRA)
return VideoIOPixelFormat::Bgra8;
return VideoIOPixelFormat::Uyvy8;
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "DeckLinkAPI_h.h"
#include "VideoIOFormat.h"
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format);
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format);

View File

@@ -0,0 +1,89 @@
#include "OutputProductionController.h"
#include <algorithm>
namespace
{
std::size_t ClampReadyLimit(unsigned value, std::size_t capacity)
{
const std::size_t requested = static_cast<std::size_t>(value);
if (capacity == 0)
return requested;
return (std::min)(requested, capacity);
}
}
OutputProductionController::OutputProductionController(const VideoPlayoutPolicy& policy) :
mPolicy(NormalizeVideoPlayoutPolicy(policy))
{
}
void OutputProductionController::Configure(const VideoPlayoutPolicy& policy)
{
mPolicy = NormalizeVideoPlayoutPolicy(policy);
}
OutputProductionDecision OutputProductionController::Decide(const OutputProductionPressure& pressure) const
{
OutputProductionDecision decision;
const std::size_t configuredMaxReadyFrames = static_cast<std::size_t>(mPolicy.maxReadyFrames);
const std::size_t effectiveMaxReadyFrames = pressure.readyQueueCapacity > 0
? (std::min)(configuredMaxReadyFrames, pressure.readyQueueCapacity)
: configuredMaxReadyFrames;
const std::size_t effectiveTargetReadyFrames = (std::min)(
ClampReadyLimit(mPolicy.targetReadyFrames, pressure.readyQueueCapacity),
effectiveMaxReadyFrames);
decision.targetReadyFrames = effectiveTargetReadyFrames;
decision.maxReadyFrames = effectiveMaxReadyFrames;
if (effectiveMaxReadyFrames == 0)
{
decision.action = OutputProductionAction::Throttle;
decision.reason = "no-ready-frame-capacity";
return decision;
}
if (pressure.readyQueueDepth >= effectiveMaxReadyFrames)
{
decision.action = OutputProductionAction::Throttle;
decision.reason = "ready-queue-full";
return decision;
}
if (pressure.readyQueueDepth < effectiveTargetReadyFrames)
{
decision.action = OutputProductionAction::Produce;
decision.requestedFrames = effectiveTargetReadyFrames - pressure.readyQueueDepth;
decision.reason = "ready-queue-below-target";
return decision;
}
if ((pressure.lateStreak > 0 || pressure.dropStreak > 0 || pressure.readyQueueUnderrunCount > 0) &&
pressure.readyQueueDepth < effectiveMaxReadyFrames)
{
decision.action = OutputProductionAction::Produce;
decision.requestedFrames = 1;
decision.reason = "playout-pressure";
return decision;
}
decision.action = OutputProductionAction::Wait;
decision.reason = "ready-queue-at-target";
return decision;
}
const char* OutputProductionActionName(OutputProductionAction action)
{
switch (action)
{
case OutputProductionAction::Produce:
return "Produce";
case OutputProductionAction::Throttle:
return "Throttle";
case OutputProductionAction::Wait:
default:
return "Wait";
}
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include "VideoPlayoutPolicy.h"
#include <cstddef>
#include <cstdint>
#include <string>
enum class OutputProductionAction
{
Produce,
Wait,
Throttle
};
struct OutputProductionPressure
{
std::size_t readyQueueDepth = 0;
std::size_t readyQueueCapacity = 0;
uint64_t readyQueueUnderrunCount = 0;
uint64_t lateStreak = 0;
uint64_t dropStreak = 0;
};
struct OutputProductionDecision
{
OutputProductionAction action = OutputProductionAction::Wait;
std::size_t requestedFrames = 0;
std::size_t targetReadyFrames = 0;
std::size_t maxReadyFrames = 0;
std::string reason;
};
class OutputProductionController
{
public:
explicit OutputProductionController(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy());
void Configure(const VideoPlayoutPolicy& policy);
OutputProductionDecision Decide(const OutputProductionPressure& pressure) const;
private:
VideoPlayoutPolicy mPolicy;
};
const char* OutputProductionActionName(OutputProductionAction action);

View File

@@ -0,0 +1,102 @@
#include "RenderCadenceController.h"
#include <algorithm>
#include <cmath>
void RenderCadenceController::Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy)
{
mTargetFrameDuration = IsPositive(targetFrameDuration) ? targetFrameDuration : std::chrono::milliseconds(1);
mPolicy = policy;
if (mPolicy.skipThresholdFrames < 1.0)
mPolicy.skipThresholdFrames = 1.0;
Reset(firstRenderTime);
}
void RenderCadenceController::Reset(TimePoint firstRenderTime)
{
mNextRenderTime = firstRenderTime;
mNextFrameIndex = 0;
mMetrics = RenderCadenceMetrics();
}
RenderCadenceDecision RenderCadenceController::Tick(TimePoint now)
{
RenderCadenceDecision decision;
decision.frameIndex = mNextFrameIndex;
decision.renderTargetTime = mNextRenderTime;
decision.nextRenderTime = mNextRenderTime;
if (now < mNextRenderTime)
{
decision.action = RenderCadenceAction::Wait;
decision.waitDuration = mNextRenderTime - now;
decision.reason = "waiting-for-next-render-tick";
return decision;
}
const Duration lateness = now - mNextRenderTime;
const uint64_t skippedTicks = SkippedTicksForLateness(lateness);
if (skippedTicks > 0)
{
decision.skippedTicks = skippedTicks;
decision.frameIndex = mNextFrameIndex + skippedTicks;
decision.renderTargetTime = mNextRenderTime + (mTargetFrameDuration * skippedTicks);
decision.reason = "late-skip-render-ticks";
mMetrics.skippedTickCount += skippedTicks;
}
else
{
decision.reason = IsPositive(lateness) ? "late-render-now" : "on-time-render";
}
decision.action = RenderCadenceAction::Render;
decision.lateness = now > decision.renderTargetTime
? now - decision.renderTargetTime
: Duration::zero();
mNextFrameIndex = decision.frameIndex + 1;
mNextRenderTime = decision.renderTargetTime + mTargetFrameDuration;
decision.nextRenderTime = mNextRenderTime;
++mMetrics.renderedFrameCount;
mMetrics.nextFrameIndex = mNextFrameIndex;
mMetrics.lastLateness = decision.lateness;
if (IsPositive(decision.lateness))
{
++mMetrics.lateFrameCount;
mMetrics.maxLateness = (std::max)(mMetrics.maxLateness, decision.lateness);
}
return decision;
}
uint64_t RenderCadenceController::SkippedTicksForLateness(Duration lateness) const
{
if (!mPolicy.skipLateTicks || !IsPositive(lateness) || !IsPositive(mTargetFrameDuration))
return 0;
const double lateFrames = static_cast<double>(lateness.count()) / static_cast<double>(mTargetFrameDuration.count());
if (lateFrames < mPolicy.skipThresholdFrames)
return 0;
const uint64_t elapsedTicks = static_cast<uint64_t>(std::floor(lateFrames));
if (elapsedTicks == 0)
return 0;
return (std::min)(elapsedTicks, mPolicy.maxSkippedTicksPerDecision);
}
bool RenderCadenceController::IsPositive(Duration duration)
{
return duration > Duration::zero();
}
const char* RenderCadenceActionName(RenderCadenceAction action)
{
switch (action)
{
case RenderCadenceAction::Render:
return "Render";
case RenderCadenceAction::Wait:
default:
return "Wait";
}
}

View File

@@ -0,0 +1,68 @@
#pragma once
#include <chrono>
#include <cstdint>
enum class RenderCadenceAction
{
Wait,
Render
};
struct RenderCadencePolicy
{
bool skipLateTicks = true;
uint64_t maxSkippedTicksPerDecision = 4;
double skipThresholdFrames = 2.0;
};
struct RenderCadenceDecision
{
RenderCadenceAction action = RenderCadenceAction::Wait;
uint64_t frameIndex = 0;
uint64_t skippedTicks = 0;
std::chrono::steady_clock::time_point renderTargetTime;
std::chrono::steady_clock::time_point nextRenderTime;
std::chrono::steady_clock::duration waitDuration = std::chrono::steady_clock::duration::zero();
std::chrono::steady_clock::duration lateness = std::chrono::steady_clock::duration::zero();
const char* reason = "waiting-for-next-render-tick";
};
struct RenderCadenceMetrics
{
uint64_t nextFrameIndex = 0;
uint64_t renderedFrameCount = 0;
uint64_t skippedTickCount = 0;
uint64_t lateFrameCount = 0;
std::chrono::steady_clock::duration lastLateness = std::chrono::steady_clock::duration::zero();
std::chrono::steady_clock::duration maxLateness = std::chrono::steady_clock::duration::zero();
};
class RenderCadenceController
{
public:
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
void Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy = RenderCadencePolicy());
void Reset(TimePoint firstRenderTime);
RenderCadenceDecision Tick(TimePoint now);
Duration TargetFrameDuration() const { return mTargetFrameDuration; }
TimePoint NextRenderTime() const { return mNextRenderTime; }
uint64_t NextFrameIndex() const { return mNextFrameIndex; }
const RenderCadenceMetrics& Metrics() const { return mMetrics; }
private:
uint64_t SkippedTicksForLateness(Duration lateness) const;
static bool IsPositive(Duration duration);
Duration mTargetFrameDuration = std::chrono::milliseconds(16);
TimePoint mNextRenderTime;
uint64_t mNextFrameIndex = 0;
RenderCadencePolicy mPolicy;
RenderCadenceMetrics mMetrics;
};
const char* RenderCadenceActionName(RenderCadenceAction action);

View File

@@ -0,0 +1,93 @@
#include "RenderOutputQueue.h"
RenderOutputQueue::RenderOutputQueue(const VideoPlayoutPolicy& policy) :
mPolicy(NormalizeVideoPlayoutPolicy(policy))
{
}
void RenderOutputQueue::Configure(const VideoPlayoutPolicy& policy)
{
std::lock_guard<std::mutex> lock(mMutex);
mPolicy = NormalizeVideoPlayoutPolicy(policy);
while (mReadyFrames.size() > CapacityLocked())
{
ReleaseFrame(mReadyFrames.front());
mReadyFrames.pop_front();
++mDroppedCount;
}
}
bool RenderOutputQueue::Push(RenderOutputFrame frame)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mReadyFrames.size() >= CapacityLocked())
{
ReleaseFrame(mReadyFrames.front());
mReadyFrames.pop_front();
++mDroppedCount;
}
mReadyFrames.push_back(frame);
++mPushedCount;
return true;
}
bool RenderOutputQueue::TryPop(RenderOutputFrame& frame)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mReadyFrames.empty())
{
++mUnderrunCount;
return false;
}
frame = mReadyFrames.front();
mReadyFrames.pop_front();
++mPoppedCount;
return true;
}
bool RenderOutputQueue::DropOldestFrame()
{
std::lock_guard<std::mutex> lock(mMutex);
if (mReadyFrames.empty())
return false;
ReleaseFrame(mReadyFrames.front());
mReadyFrames.pop_front();
++mDroppedCount;
return true;
}
void RenderOutputQueue::Clear()
{
std::lock_guard<std::mutex> lock(mMutex);
for (RenderOutputFrame& frame : mReadyFrames)
ReleaseFrame(frame);
mReadyFrames.clear();
}
RenderOutputQueueMetrics RenderOutputQueue::GetMetrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
RenderOutputQueueMetrics metrics;
metrics.depth = mReadyFrames.size();
metrics.capacity = CapacityLocked();
metrics.pushedCount = mPushedCount;
metrics.poppedCount = mPoppedCount;
metrics.droppedCount = mDroppedCount;
metrics.underrunCount = mUnderrunCount;
return metrics;
}
std::size_t RenderOutputQueue::CapacityLocked() const
{
return static_cast<std::size_t>(mPolicy.maxReadyFrames);
}
void RenderOutputQueue::ReleaseFrame(RenderOutputFrame& frame)
{
if (frame.releaseFrame)
frame.releaseFrame(frame.frame);
frame.releaseFrame = {};
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include "VideoIOTypes.h"
#include "VideoPlayoutPolicy.h"
#include <cstdint>
#include <deque>
#include <functional>
#include <mutex>
struct RenderOutputFrame
{
VideoIOOutputFrame frame;
uint64_t frameIndex = 0;
bool stale = false;
std::function<void(VideoIOOutputFrame& frame)> releaseFrame;
};
struct RenderOutputQueueMetrics
{
std::size_t depth = 0;
std::size_t capacity = 0;
uint64_t pushedCount = 0;
uint64_t poppedCount = 0;
uint64_t droppedCount = 0;
uint64_t underrunCount = 0;
};
class RenderOutputQueue
{
public:
explicit RenderOutputQueue(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy());
void Configure(const VideoPlayoutPolicy& policy);
bool Push(RenderOutputFrame frame);
bool TryPop(RenderOutputFrame& frame);
bool DropOldestFrame();
void Clear();
RenderOutputQueueMetrics GetMetrics() const;
private:
std::size_t CapacityLocked() const;
static void ReleaseFrame(RenderOutputFrame& frame);
mutable std::mutex mMutex;
VideoPlayoutPolicy mPolicy;
std::deque<RenderOutputFrame> mReadyFrames;
uint64_t mPushedCount = 0;
uint64_t mPoppedCount = 0;
uint64_t mDroppedCount = 0;
uint64_t mUnderrunCount = 0;
};

View File

@@ -0,0 +1,260 @@
#include "SystemOutputFramePool.h"
#include <algorithm>
namespace
{
SystemOutputFramePoolConfig NormalizeConfig(SystemOutputFramePoolConfig config)
{
if (config.rowBytes == 0)
config.rowBytes = VideoIORowBytes(config.pixelFormat, config.width);
return config;
}
}
SystemOutputFramePool::SystemOutputFramePool(const SystemOutputFramePoolConfig& config)
{
Configure(config);
}
void SystemOutputFramePool::Configure(const SystemOutputFramePoolConfig& config)
{
std::lock_guard<std::mutex> lock(mMutex);
mConfig = NormalizeConfig(config);
mReadySlots.clear();
mSlots.clear();
mSlots.resize(mConfig.capacity);
const std::size_t byteCount = FrameByteCount();
for (StoredSlot& slot : mSlots)
{
slot.bytes.resize(byteCount);
slot.state = OutputFrameSlotState::Free;
++slot.generation;
}
mAcquireMissCount = 0;
mReadyUnderrunCount = 0;
}
SystemOutputFramePoolConfig SystemOutputFramePool::Config() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mConfig;
}
bool SystemOutputFramePool::AcquireFreeSlot(OutputFrameSlot& slot)
{
std::lock_guard<std::mutex> lock(mMutex);
for (std::size_t index = 0; index < mSlots.size(); ++index)
{
if (mSlots[index].state != OutputFrameSlotState::Free)
continue;
mSlots[index].state = OutputFrameSlotState::Rendering;
++mSlots[index].generation;
FillOutputSlotLocked(index, slot);
return true;
}
slot = OutputFrameSlot();
++mAcquireMissCount;
return false;
}
bool SystemOutputFramePool::AcquireRenderingSlot(OutputFrameSlot& slot)
{
return AcquireFreeSlot(slot);
}
bool SystemOutputFramePool::PublishReadySlot(const OutputFrameSlot& slot)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!TransitionSlotLocked(slot, OutputFrameSlotState::Rendering, OutputFrameSlotState::Completed))
return false;
mReadySlots.push_back(slot.index);
return true;
}
bool SystemOutputFramePool::PublishCompletedSlot(const OutputFrameSlot& slot)
{
return PublishReadySlot(slot);
}
bool SystemOutputFramePool::ConsumeReadySlot(OutputFrameSlot& slot)
{
std::lock_guard<std::mutex> lock(mMutex);
while (!mReadySlots.empty())
{
const std::size_t index = mReadySlots.front();
mReadySlots.pop_front();
if (index >= mSlots.size() || mSlots[index].state != OutputFrameSlotState::Completed)
continue;
FillOutputSlotLocked(index, slot);
return true;
}
slot = OutputFrameSlot();
++mReadyUnderrunCount;
return false;
}
bool SystemOutputFramePool::ConsumeCompletedSlot(OutputFrameSlot& slot)
{
return ConsumeReadySlot(slot);
}
bool SystemOutputFramePool::MarkScheduled(const OutputFrameSlot& slot)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!IsValidSlotLocked(slot))
return false;
if (mSlots[slot.index].state != OutputFrameSlotState::Completed)
return false;
RemoveReadyIndexLocked(slot.index);
mSlots[slot.index].state = OutputFrameSlotState::Scheduled;
return true;
}
bool SystemOutputFramePool::MarkScheduledByBuffer(void* bytes)
{
if (bytes == nullptr)
return false;
std::lock_guard<std::mutex> lock(mMutex);
for (std::size_t index = 0; index < mSlots.size(); ++index)
{
if (mSlots[index].bytes.empty() || mSlots[index].bytes.data() != bytes)
continue;
if (mSlots[index].state != OutputFrameSlotState::Completed)
return false;
RemoveReadyIndexLocked(index);
mSlots[index].state = OutputFrameSlotState::Scheduled;
return true;
}
return false;
}
bool SystemOutputFramePool::ReleaseSlot(const OutputFrameSlot& slot)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!IsValidSlotLocked(slot) || mSlots[slot.index].state == OutputFrameSlotState::Free)
return false;
return ReleaseSlotByIndexLocked(slot.index);
}
bool SystemOutputFramePool::ReleaseScheduledSlot(const OutputFrameSlot& slot)
{
std::lock_guard<std::mutex> lock(mMutex);
return TransitionSlotLocked(slot, OutputFrameSlotState::Scheduled, OutputFrameSlotState::Free);
}
bool SystemOutputFramePool::ReleaseSlotByBuffer(void* bytes)
{
if (bytes == nullptr)
return false;
std::lock_guard<std::mutex> lock(mMutex);
for (std::size_t index = 0; index < mSlots.size(); ++index)
{
if (!mSlots[index].bytes.empty() && mSlots[index].bytes.data() == bytes)
return ReleaseSlotByIndexLocked(index);
}
return false;
}
void SystemOutputFramePool::Clear()
{
std::lock_guard<std::mutex> lock(mMutex);
mReadySlots.clear();
for (StoredSlot& slot : mSlots)
{
slot.state = OutputFrameSlotState::Free;
++slot.generation;
}
}
SystemOutputFramePoolMetrics SystemOutputFramePool::GetMetrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
SystemOutputFramePoolMetrics metrics;
metrics.capacity = mSlots.size();
metrics.readyCount = mReadySlots.size();
metrics.acquireMissCount = mAcquireMissCount;
metrics.readyUnderrunCount = mReadyUnderrunCount;
for (const StoredSlot& slot : mSlots)
{
switch (slot.state)
{
case OutputFrameSlotState::Free:
++metrics.freeCount;
break;
case OutputFrameSlotState::Rendering:
++metrics.renderingCount;
++metrics.acquiredCount;
break;
case OutputFrameSlotState::Completed:
++metrics.completedCount;
break;
case OutputFrameSlotState::Scheduled:
++metrics.scheduledCount;
break;
}
}
return metrics;
}
bool SystemOutputFramePool::IsValidSlotLocked(const OutputFrameSlot& slot) const
{
return slot.index < mSlots.size() && mSlots[slot.index].generation == slot.generation;
}
bool SystemOutputFramePool::TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState)
{
if (!IsValidSlotLocked(slot) || mSlots[slot.index].state != expectedState)
return false;
mSlots[slot.index].state = nextState;
return true;
}
void SystemOutputFramePool::FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot)
{
StoredSlot& storedSlot = mSlots[index];
slot.index = index;
slot.generation = storedSlot.generation;
slot.frame.bytes = storedSlot.bytes.empty() ? nullptr : storedSlot.bytes.data();
slot.frame.rowBytes = static_cast<long>(mConfig.rowBytes);
slot.frame.width = mConfig.width;
slot.frame.height = mConfig.height;
slot.frame.pixelFormat = mConfig.pixelFormat;
slot.frame.nativeFrame = nullptr;
slot.frame.nativeBuffer = slot.frame.bytes;
}
void SystemOutputFramePool::RemoveReadyIndexLocked(std::size_t index)
{
mReadySlots.erase(std::remove(mReadySlots.begin(), mReadySlots.end(), index), mReadySlots.end());
}
bool SystemOutputFramePool::ReleaseSlotByIndexLocked(std::size_t index)
{
if (index >= mSlots.size() || mSlots[index].state == OutputFrameSlotState::Free)
return false;
RemoveReadyIndexLocked(index);
mSlots[index].state = OutputFrameSlotState::Free;
return true;
}
std::size_t SystemOutputFramePool::FrameByteCount() const
{
return static_cast<std::size_t>(mConfig.rowBytes) * static_cast<std::size_t>(mConfig.height);
}

View File

@@ -0,0 +1,94 @@
#pragma once
#include "VideoIOTypes.h"
#include <cstddef>
#include <cstdint>
#include <deque>
#include <mutex>
#include <vector>
enum class OutputFrameSlotState
{
Free,
Rendering,
Completed,
Scheduled
};
struct SystemOutputFramePoolConfig
{
unsigned width = 0;
unsigned height = 0;
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
unsigned rowBytes = 0;
std::size_t capacity = 0;
};
struct OutputFrameSlot
{
VideoIOOutputFrame frame;
std::size_t index = 0;
uint64_t generation = 0;
};
struct SystemOutputFramePoolMetrics
{
std::size_t capacity = 0;
std::size_t freeCount = 0;
std::size_t renderingCount = 0;
std::size_t completedCount = 0;
std::size_t scheduledCount = 0;
std::size_t acquiredCount = 0;
std::size_t readyCount = 0;
std::size_t consumedCount = 0;
uint64_t acquireMissCount = 0;
uint64_t readyUnderrunCount = 0;
};
class SystemOutputFramePool
{
public:
SystemOutputFramePool() = default;
explicit SystemOutputFramePool(const SystemOutputFramePoolConfig& config);
void Configure(const SystemOutputFramePoolConfig& config);
SystemOutputFramePoolConfig Config() const;
bool AcquireFreeSlot(OutputFrameSlot& slot);
bool AcquireRenderingSlot(OutputFrameSlot& slot);
bool PublishReadySlot(const OutputFrameSlot& slot);
bool PublishCompletedSlot(const OutputFrameSlot& slot);
bool ConsumeReadySlot(OutputFrameSlot& slot);
bool ConsumeCompletedSlot(OutputFrameSlot& slot);
bool MarkScheduled(const OutputFrameSlot& slot);
bool MarkScheduledByBuffer(void* bytes);
bool ReleaseSlot(const OutputFrameSlot& slot);
bool ReleaseScheduledSlot(const OutputFrameSlot& slot);
bool ReleaseSlotByBuffer(void* bytes);
void Clear();
SystemOutputFramePoolMetrics GetMetrics() const;
private:
struct StoredSlot
{
std::vector<unsigned char> bytes;
OutputFrameSlotState state = OutputFrameSlotState::Free;
uint64_t generation = 1;
};
bool IsValidSlotLocked(const OutputFrameSlot& slot) const;
bool TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState);
void FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot);
void RemoveReadyIndexLocked(std::size_t index);
bool ReleaseSlotByIndexLocked(std::size_t index);
std::size_t FrameByteCount() const;
mutable std::mutex mMutex;
SystemOutputFramePoolConfig mConfig;
std::vector<StoredSlot> mSlots;
std::deque<std::size_t> mReadySlots;
uint64_t mAcquireMissCount = 0;
uint64_t mReadyUnderrunCount = 0;
};

1095
src/video/VideoBackend.cpp Normal file

File diff suppressed because it is too large Load Diff

161
src/video/VideoBackend.h Normal file
View File

@@ -0,0 +1,161 @@
#pragma once
#include "OutputProductionController.h"
#include "RenderCadenceController.h"
#include "RenderOutputQueue.h"
#include "SystemOutputFramePool.h"
#include "VideoBackendLifecycle.h"
#include "VideoIOTypes.h"
#include "VideoPlayoutPolicy.h"
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
class HealthTelemetry;
class OpenGLVideoIOBridge;
class RenderEngine;
class RuntimeEventDispatcher;
class VideoIODevice;
class VideoBackend
{
public:
VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry, RuntimeEventDispatcher& runtimeEventDispatcher);
~VideoBackend();
void ReleaseResources();
VideoBackendLifecycleState LifecycleState() const;
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error);
bool ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
bool Start();
bool Stop();
const VideoIOState& State() const;
VideoIOState& MutableState();
bool BeginOutputFrame(VideoIOOutputFrame& frame);
void EndOutputFrame(VideoIOOutputFrame& frame);
bool ScheduleOutputFrame(const VideoIOOutputFrame& frame);
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth);
void RecordBackendPlayoutHealth(VideoIOCompletionResult result, const VideoPlayoutRecoveryDecision& recoveryDecision);
bool HasInputDevice() const;
bool HasInputSource() const;
unsigned InputFrameWidth() const;
unsigned InputFrameHeight() const;
unsigned OutputFrameWidth() const;
unsigned OutputFrameHeight() const;
unsigned CaptureTextureWidth() const;
unsigned OutputPackTextureWidth() const;
VideoIOPixelFormat InputPixelFormat() const;
const std::string& InputDisplayModeName() const;
const std::string& OutputModelName() const;
bool SupportsInternalKeying() const;
bool SupportsExternalKeying() const;
bool KeyerInterfaceAvailable() const;
bool ExternalKeyingActive() const;
const std::string& StatusMessage() const;
bool ShouldPrioritizeOutputOverPreview() const;
void SetStatusMessage(const std::string& message);
void PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage = std::string());
void ReportNoInputDeviceSignalStatus();
private:
void HandleInputFrame(const VideoIOFrame& frame);
void HandleOutputFrameCompletion(const VideoIOCompletion& completion);
void StartOutputCompletionWorker();
void StopOutputCompletionWorker();
void OutputCompletionWorkerMain();
void StartOutputProducerWorker();
void StopOutputProducerWorker();
void OutputProducerWorkerMain();
void NotifyOutputProducer();
bool WarmupOutputPreroll();
std::chrono::milliseconds OutputProducerWakeInterval() const;
void ProcessOutputFrameCompletion(const VideoIOCompletion& completion);
std::size_t ProduceReadyOutputFrames(const VideoIOCompletion& completion, std::size_t maxFrames);
OutputProductionPressure BuildOutputProductionPressure(const RenderOutputQueueMetrics& metrics) const;
bool RenderReadyOutputFrame(const VideoIOState& state, const VideoIOCompletion& completion);
std::size_t ScheduleReadyOutputFramesToTarget();
bool ScheduleReadyOutputFrame();
bool ScheduleBlackUnderrunFrame();
void RecordFramePacing(VideoIOCompletionResult completionResult);
void RecordReadyQueueDepthSample(const RenderOutputQueueMetrics& metrics);
void RecordDeckLinkBufferTelemetry();
void RecordSystemMemoryPlayoutStats();
void RecordOutputRenderDuration(double renderMilliseconds, double acquireMilliseconds, double renderRequestMilliseconds, double endAccessMilliseconds);
bool ApplyLifecycleTransition(VideoBackendLifecycleState state, const std::string& message);
bool ApplyLifecycleFailure(const std::string& message);
void PublishBackendStateChanged(const std::string& state, const std::string& message);
void PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state);
void PublishInputFrameArrived(const VideoIOFrame& frame);
void PublishOutputFrameScheduled(const VideoIOOutputFrame& frame);
void PublishOutputFrameCompleted(const VideoIOCompletion& completion);
void PublishTimingSample(const std::string& subsystem, const std::string& metric, double value, const std::string& unit);
static std::string CompletionResultName(VideoIOCompletionResult result);
static std::string PixelFormatName(VideoIOPixelFormat pixelFormat);
static bool IsEnvironmentFlagEnabled(const char* name);
HealthTelemetry& mHealthTelemetry;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
VideoBackendLifecycle mLifecycle;
VideoPlayoutPolicy mPlayoutPolicy;
OutputProductionController mOutputProductionController;
RenderCadenceController mRenderCadenceController;
RenderOutputQueue mReadyOutputQueue;
SystemOutputFramePool mSystemOutputFramePool;
std::unique_ptr<VideoIODevice> mVideoIODevice;
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
std::mutex mOutputCompletionMutex;
std::condition_variable mOutputCompletionCondition;
std::deque<VideoIOCompletion> mPendingOutputCompletions;
std::thread mOutputCompletionWorker;
std::mutex mOutputProducerMutex;
std::condition_variable mOutputProducerCondition;
std::thread mOutputProducerWorker;
VideoIOCompletion mLastOutputProductionCompletion;
std::chrono::steady_clock::time_point mLastOutputProductionTime;
std::mutex mOutputProductionMutex;
std::mutex mOutputSchedulingMutex;
mutable std::mutex mOutputMetricsMutex;
bool mOutputCompletionWorkerRunning = false;
bool mOutputCompletionWorkerStopping = false;
bool mOutputProducerWorkerRunning = false;
bool mOutputProducerWorkerStopping = false;
bool mInputCaptureDisabled = false;
uint64_t mNextReadyOutputFrameIndex = 0;
uint64_t mInputFrameIndex = 0;
uint64_t mOutputFrameScheduleIndex = 0;
uint64_t mOutputFrameCompletionIndex = 0;
bool mHasLastInputSignal = false;
bool mLastInputSignal = false;
unsigned mLastInputSignalWidth = 0;
unsigned mLastInputSignalHeight = 0;
std::string mLastInputSignalModeName;
std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
double mCompletionIntervalMilliseconds = 0.0;
double mSmoothedCompletionIntervalMilliseconds = 0.0;
double mMaxCompletionIntervalMilliseconds = 0.0;
bool mHasReadyQueueDepthBaseline = false;
std::size_t mMinReadyQueueDepth = 0;
std::size_t mMaxReadyQueueDepth = 0;
uint64_t mReadyQueueZeroDepthCount = 0;
double mOutputRenderMilliseconds = 0.0;
double mSmoothedOutputRenderMilliseconds = 0.0;
double mMaxOutputRenderMilliseconds = 0.0;
double mOutputFrameAcquireMilliseconds = 0.0;
double mOutputFrameRenderRequestMilliseconds = 0.0;
double mOutputFrameEndAccessMilliseconds = 0.0;
uint64_t mLastLateStreak = 0;
uint64_t mLastDropStreak = 0;
uint64_t mLateFrameCount = 0;
uint64_t mDroppedFrameCount = 0;
uint64_t mFlushedFrameCount = 0;
};

View File

@@ -0,0 +1,123 @@
#include "VideoBackendLifecycle.h"
VideoBackendLifecycleState VideoBackendLifecycle::State() const
{
return mState;
}
const std::string& VideoBackendLifecycle::FailureReason() const
{
return mFailureReason;
}
VideoBackendLifecycleTransition VideoBackendLifecycle::TransitionTo(VideoBackendLifecycleState next, const std::string& reason)
{
VideoBackendLifecycleTransition transition;
transition.previous = mState;
transition.current = next;
transition.reason = reason;
transition.accepted = CanTransition(mState, next);
if (!transition.accepted)
{
transition.current = mState;
transition.errorMessage = std::string("Invalid video backend lifecycle transition from ") +
StateName(mState) + " to " + StateName(next) + ".";
return transition;
}
mState = next;
transition.current = mState;
if (mState != VideoBackendLifecycleState::Failed)
mFailureReason.clear();
return transition;
}
VideoBackendLifecycleTransition VideoBackendLifecycle::Fail(const std::string& reason)
{
VideoBackendLifecycleTransition transition = TransitionTo(VideoBackendLifecycleState::Failed, reason);
if (transition.accepted)
mFailureReason = reason;
return transition;
}
bool VideoBackendLifecycle::CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next)
{
if (current == next)
return true;
switch (current)
{
case VideoBackendLifecycleState::Uninitialized:
return next == VideoBackendLifecycleState::Discovering ||
next == VideoBackendLifecycleState::Stopped ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Discovering:
return next == VideoBackendLifecycleState::Discovered ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Discovered:
return next == VideoBackendLifecycleState::Configuring ||
next == VideoBackendLifecycleState::Stopped ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Configuring:
return next == VideoBackendLifecycleState::Configured ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Configured:
return next == VideoBackendLifecycleState::Prerolling ||
next == VideoBackendLifecycleState::Stopped ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Prerolling:
return next == VideoBackendLifecycleState::Running ||
next == VideoBackendLifecycleState::Stopping ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Running:
return next == VideoBackendLifecycleState::Degraded ||
next == VideoBackendLifecycleState::Stopping ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Degraded:
return next == VideoBackendLifecycleState::Running ||
next == VideoBackendLifecycleState::Stopping ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Stopping:
return next == VideoBackendLifecycleState::Stopped ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Stopped:
return next == VideoBackendLifecycleState::Discovering ||
next == VideoBackendLifecycleState::Failed;
case VideoBackendLifecycleState::Failed:
return next == VideoBackendLifecycleState::Stopped ||
next == VideoBackendLifecycleState::Discovering;
default:
return false;
}
}
const char* VideoBackendLifecycle::StateName(VideoBackendLifecycleState state)
{
switch (state)
{
case VideoBackendLifecycleState::Uninitialized:
return "uninitialized";
case VideoBackendLifecycleState::Discovering:
return "discovering";
case VideoBackendLifecycleState::Discovered:
return "discovered";
case VideoBackendLifecycleState::Configuring:
return "configuring";
case VideoBackendLifecycleState::Configured:
return "configured";
case VideoBackendLifecycleState::Prerolling:
return "prerolling";
case VideoBackendLifecycleState::Running:
return "running";
case VideoBackendLifecycleState::Degraded:
return "degraded";
case VideoBackendLifecycleState::Stopping:
return "stopping";
case VideoBackendLifecycleState::Stopped:
return "stopped";
case VideoBackendLifecycleState::Failed:
return "failed";
default:
return "unknown";
}
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include <string>
enum class VideoBackendLifecycleState
{
Uninitialized,
Discovering,
Discovered,
Configuring,
Configured,
Prerolling,
Running,
Degraded,
Stopping,
Stopped,
Failed
};
struct VideoBackendLifecycleTransition
{
VideoBackendLifecycleState previous = VideoBackendLifecycleState::Uninitialized;
VideoBackendLifecycleState current = VideoBackendLifecycleState::Uninitialized;
bool accepted = false;
std::string reason;
std::string errorMessage;
};
class VideoBackendLifecycle
{
public:
VideoBackendLifecycleState State() const;
const std::string& FailureReason() const;
VideoBackendLifecycleTransition TransitionTo(VideoBackendLifecycleState next, const std::string& reason);
VideoBackendLifecycleTransition Fail(const std::string& reason);
static bool CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next);
static const char* StateName(VideoBackendLifecycleState state);
private:
VideoBackendLifecycleState mState = VideoBackendLifecycleState::Uninitialized;
std::string mFailureReason;
};

170
src/video/VideoIOFormat.cpp Normal file
View File

@@ -0,0 +1,170 @@
#include "VideoIOFormat.h"
#include <algorithm>
#include <cmath>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace
{
uint16_t Clamp10(int value, int minimum, int maximum)
{
return static_cast<uint16_t>(std::max(minimum, std::min(maximum, value)));
}
uint32_t MakeV210Word(uint16_t a, uint16_t b, uint16_t c)
{
return (static_cast<uint32_t>(a) & 0x3ffu)
| ((static_cast<uint32_t>(b) & 0x3ffu) << 10)
| ((static_cast<uint32_t>(c) & 0x3ffu) << 20);
}
void StoreWord(std::array<uint8_t, 16>& bytes, std::size_t wordIndex, uint32_t word)
{
const std::size_t offset = wordIndex * 4;
bytes[offset + 0] = static_cast<uint8_t>(word & 0xffu);
bytes[offset + 1] = static_cast<uint8_t>((word >> 8) & 0xffu);
bytes[offset + 2] = static_cast<uint8_t>((word >> 16) & 0xffu);
bytes[offset + 3] = static_cast<uint8_t>((word >> 24) & 0xffu);
}
uint32_t LoadWord(const std::array<uint8_t, 16>& bytes, std::size_t wordIndex)
{
const std::size_t offset = wordIndex * 4;
return static_cast<uint32_t>(bytes[offset + 0])
| (static_cast<uint32_t>(bytes[offset + 1]) << 8)
| (static_cast<uint32_t>(bytes[offset + 2]) << 16)
| (static_cast<uint32_t>(bytes[offset + 3]) << 24);
}
uint16_t Component(uint32_t word, unsigned index)
{
return static_cast<uint16_t>((word >> (index * 10)) & 0x3ffu);
}
}
const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
{
switch (format)
{
case VideoIOPixelFormat::V210:
return "10-bit YUV v210";
case VideoIOPixelFormat::Yuva10:
return "10-bit YUVA Ay10";
case VideoIOPixelFormat::Bgra8:
return "8-bit BGRA";
case VideoIOPixelFormat::Uyvy8:
default:
return "8-bit YUV UYVY";
}
}
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
{
return format == VideoIOPixelFormat::V210 || format == VideoIOPixelFormat::Yuva10;
}
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported)
{
return tenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Uyvy8;
}
unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format)
{
switch (format)
{
case VideoIOPixelFormat::Uyvy8:
return 2u;
case VideoIOPixelFormat::Bgra8:
return 4u;
case VideoIOPixelFormat::Yuva10:
return 4u;
case VideoIOPixelFormat::V210:
default:
return 0u;
}
}
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth)
{
if (format == VideoIOPixelFormat::V210)
return MinimumV210RowBytes(frameWidth);
if (format == VideoIOPixelFormat::Yuva10)
return MinimumYuva10RowBytes(frameWidth);
return frameWidth * VideoIOBytesPerPixel(format);
}
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes)
{
return (rowBytes + 3u) / 4u;
}
unsigned MinimumV210RowBytes(unsigned frameWidth)
{
return ((frameWidth + 5u) / 6u) * 16u;
}
unsigned MinimumYuva10RowBytes(unsigned frameWidth)
{
return ((frameWidth + 63u) / 64u) * 256u;
}
unsigned ActiveV210WordsForWidth(unsigned frameWidth)
{
return ((frameWidth + 5u) / 6u) * 4u;
}
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue)
{
red = std::max(0.0f, std::min(1.0f, red));
green = std::max(0.0f, std::min(1.0f, green));
blue = std::max(0.0f, std::min(1.0f, blue));
const float y = 0.2126f * red + 0.7152f * green + 0.0722f * blue;
const float cb = (blue - y) / 1.8556f + 0.5f;
const float cr = (red - y) / 1.5748f + 0.5f;
V210CodeValues values;
values.y = Clamp10(static_cast<int>(std::lround(64.0f + y * 876.0f)), 64, 940);
values.cb = Clamp10(static_cast<int>(std::lround(64.0f + cb * 896.0f)), 64, 960);
values.cr = Clamp10(static_cast<int>(std::lround(64.0f + cr * 896.0f)), 64, 960);
return values;
}
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block)
{
std::array<uint8_t, 16> bytes = {};
StoreWord(bytes, 0, MakeV210Word(block.cb[0], block.y[0], block.cr[0]));
StoreWord(bytes, 1, MakeV210Word(block.y[1], block.cb[1], block.y[2]));
StoreWord(bytes, 2, MakeV210Word(block.cr[1], block.y[3], block.cb[2]));
StoreWord(bytes, 3, MakeV210Word(block.y[4], block.cr[2], block.y[5]));
return bytes;
}
V210SixPixelBlock UnpackV210Block(const std::array<uint8_t, 16>& bytes)
{
const uint32_t word0 = LoadWord(bytes, 0);
const uint32_t word1 = LoadWord(bytes, 1);
const uint32_t word2 = LoadWord(bytes, 2);
const uint32_t word3 = LoadWord(bytes, 3);
V210SixPixelBlock block;
block.cb[0] = Component(word0, 0);
block.y[0] = Component(word0, 1);
block.cr[0] = Component(word0, 2);
block.y[1] = Component(word1, 0);
block.cb[1] = Component(word1, 1);
block.y[2] = Component(word1, 2);
block.cr[1] = Component(word2, 0);
block.y[3] = Component(word2, 1);
block.cb[2] = Component(word2, 2);
block.y[4] = Component(word3, 0);
block.cr[2] = Component(word3, 1);
block.y[5] = Component(word3, 2);
return block;
}

39
src/video/VideoIOFormat.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <array>
#include <cstdint>
enum class VideoIOPixelFormat
{
Uyvy8,
V210,
Yuva10,
Bgra8
};
struct V210CodeValues
{
uint16_t y = 64;
uint16_t cb = 512;
uint16_t cr = 512;
};
struct V210SixPixelBlock
{
std::array<uint16_t, 6> y = {};
std::array<uint16_t, 3> cb = {};
std::array<uint16_t, 3> cr = {};
};
const char* VideoIOPixelFormatName(VideoIOPixelFormat format);
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format);
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported);
unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format);
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth);
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes);
unsigned MinimumV210RowBytes(unsigned frameWidth);
unsigned MinimumYuva10RowBytes(unsigned frameWidth);
unsigned ActiveV210WordsForWidth(unsigned frameWidth);
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue);
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block);
V210SixPixelBlock UnpackV210Block(const std::array<uint8_t, 16>& bytes);

164
src/video/VideoIOTypes.h Normal file
View File

@@ -0,0 +1,164 @@
#pragma once
#include "DeckLinkDisplayMode.h"
#include "VideoIOFormat.h"
#include <cstdint>
#include <functional>
#include <string>
enum class VideoIOBackend
{
DeckLink
};
enum class VideoIOCompletionResult
{
Completed,
DisplayedLate,
Dropped,
Flushed,
Unknown
};
struct VideoIOConfig
{
VideoFormatSelection videoModes;
bool externalKeyingEnabled = false;
bool preferTenBit = true;
};
struct VideoIOState
{
FrameSize inputFrameSize;
FrameSize outputFrameSize;
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
VideoIOPixelFormat outputPixelFormat = VideoIOPixelFormat::Bgra8;
unsigned inputFrameRowBytes = 0;
unsigned outputFrameRowBytes = 0;
unsigned captureTextureWidth = 0;
unsigned outputPackTextureWidth = 0;
std::string inputDisplayModeName = "1080p59.94";
std::string outputDisplayModeName = "1080p59.94";
std::string outputModelName;
std::string statusMessage;
std::string formatStatusMessage;
bool hasInputDevice = false;
bool hasInputSource = false;
bool supportsInternalKeying = false;
bool supportsExternalKeying = false;
bool keyerInterfaceAvailable = false;
bool externalKeyingActive = false;
double frameBudgetMilliseconds = 0.0;
bool actualDeckLinkBufferedFramesAvailable = false;
uint64_t actualDeckLinkBufferedFrames = 0;
double deckLinkScheduleCallMilliseconds = 0.0;
uint64_t deckLinkScheduleFailureCount = 0;
bool deckLinkScheduleLeadAvailable = false;
int64_t deckLinkPlaybackStreamTime = 0;
uint64_t deckLinkPlaybackFrameIndex = 0;
uint64_t deckLinkNextScheduleFrameIndex = 0;
int64_t deckLinkScheduleLeadFrames = 0;
uint64_t deckLinkScheduleRealignmentCount = 0;
};
struct VideoIOFrame
{
void* bytes = nullptr;
long rowBytes = 0;
unsigned width = 0;
unsigned height = 0;
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Uyvy8;
bool hasNoInputSource = false;
};
struct VideoIOOutputFrame
{
void* bytes = nullptr;
long rowBytes = 0;
unsigned width = 0;
unsigned height = 0;
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
void* nativeFrame = nullptr;
void* nativeBuffer = nullptr;
};
struct VideoIOCompletion
{
VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
void* outputFrameBuffer = nullptr;
};
struct VideoIOScheduleTime
{
int64_t streamTime = 0;
int64_t duration = 0;
int64_t timeScale = 0;
uint64_t frameIndex = 0;
};
struct VideoPlayoutRecoveryDecision
{
VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
uint64_t completedFrameIndex = 0;
uint64_t scheduledFrameIndex = 0;
uint64_t readyQueueDepth = 0;
uint64_t scheduledLeadFrames = 0;
uint64_t measuredLagFrames = 0;
uint64_t catchUpFrames = 0;
uint64_t lateStreak = 0;
uint64_t dropStreak = 0;
};
class VideoIODevice
{
public:
using InputFrameCallback = std::function<void(const VideoIOFrame&)>;
using OutputFrameCallback = std::function<void(const VideoIOCompletion&)>;
virtual ~VideoIODevice() = default;
virtual void ReleaseResources() = 0;
virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0;
virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) = 0;
virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0;
virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0;
virtual bool PrepareOutputSchedule() = 0;
virtual bool StartInputStreams() = 0;
virtual bool StartScheduledPlayback() = 0;
virtual bool Start() = 0;
virtual bool Stop() = 0;
virtual const VideoIOState& State() const = 0;
virtual VideoIOState& MutableState() = 0;
virtual bool BeginOutputFrame(VideoIOOutputFrame& frame) = 0;
virtual void EndOutputFrame(VideoIOOutputFrame& frame) = 0;
virtual bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) = 0;
virtual VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) = 0;
bool HasInputDevice() const { return State().hasInputDevice; }
bool HasInputSource() const { return State().hasInputSource; }
bool InputOutputDimensionsDiffer() const { return State().inputFrameSize != State().outputFrameSize; }
const FrameSize& InputFrameSize() const { return State().inputFrameSize; }
const FrameSize& OutputFrameSize() const { return State().outputFrameSize; }
unsigned InputFrameWidth() const { return State().inputFrameSize.width; }
unsigned InputFrameHeight() const { return State().inputFrameSize.height; }
unsigned OutputFrameWidth() const { return State().outputFrameSize.width; }
unsigned OutputFrameHeight() const { return State().outputFrameSize.height; }
VideoIOPixelFormat InputPixelFormat() const { return State().inputPixelFormat; }
VideoIOPixelFormat OutputPixelFormat() const { return State().outputPixelFormat; }
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().inputPixelFormat); }
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().outputPixelFormat); }
unsigned InputFrameRowBytes() const { return State().inputFrameRowBytes; }
unsigned OutputFrameRowBytes() const { return State().outputFrameRowBytes; }
unsigned CaptureTextureWidth() const { return State().captureTextureWidth; }
unsigned OutputPackTextureWidth() const { return State().outputPackTextureWidth; }
const std::string& FormatStatusMessage() const { return State().formatStatusMessage; }
const std::string& InputDisplayModeName() const { return State().inputDisplayModeName; }
const std::string& OutputModelName() const { return State().outputModelName; }
bool SupportsInternalKeying() const { return State().supportsInternalKeying; }
bool SupportsExternalKeying() const { return State().supportsExternalKeying; }
bool KeyerInterfaceAvailable() const { return State().keyerInterfaceAvailable; }
bool ExternalKeyingActive() const { return State().externalKeyingActive; }
const std::string& StatusMessage() const { return State().statusMessage; }
double FrameBudgetMilliseconds() const { return State().frameBudgetMilliseconds; }
void SetStatusMessage(const std::string& message) { MutableState().statusMessage = message; }
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include <cstdint>
enum class VideoUnderrunBehavior
{
ReuseLastCompletedFrame,
BlackFrame
};
struct VideoPlayoutPolicy
{
unsigned outputFramePoolSize = 10;
unsigned targetPrerollFrames = 4;
unsigned targetReadyFrames = 2;
unsigned maxReadyFrames = 4;
unsigned minimumSpareDeviceFrames = 1;
uint64_t lateOrDropCatchUpFrames = 0;
VideoUnderrunBehavior underrunBehavior = VideoUnderrunBehavior::ReuseLastCompletedFrame;
bool adaptiveHeadroomEnabled = false;
};
inline VideoPlayoutPolicy NormalizeVideoPlayoutPolicy(VideoPlayoutPolicy policy)
{
if (policy.outputFramePoolSize == 0)
policy.outputFramePoolSize = 1;
if (policy.targetPrerollFrames == 0)
policy.targetPrerollFrames = 1;
if (policy.targetReadyFrames == 0)
policy.targetReadyFrames = 1;
if (policy.maxReadyFrames < policy.targetReadyFrames)
policy.maxReadyFrames = policy.targetReadyFrames;
const unsigned minimumOutputFramePoolSize = policy.targetPrerollFrames + policy.maxReadyFrames + policy.minimumSpareDeviceFrames;
if (policy.outputFramePoolSize < minimumOutputFramePoolSize)
policy.outputFramePoolSize = minimumOutputFramePoolSize;
return policy;
}

View File

@@ -0,0 +1,108 @@
#include "VideoPlayoutScheduler.h"
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale)
{
Configure(frameDuration, timeScale, VideoPlayoutPolicy());
}
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale, const VideoPlayoutPolicy& policy)
{
mFrameDuration = frameDuration;
mTimeScale = timeScale;
mPolicy = NormalizeVideoPlayoutPolicy(policy);
Reset();
}
void VideoPlayoutScheduler::Reset()
{
mScheduledFrameIndex = 0;
mCompletedFrameIndex = 0;
mLateStreak = 0;
mDropStreak = 0;
}
VideoIOScheduleTime VideoPlayoutScheduler::NextScheduleTime()
{
VideoIOScheduleTime time;
time.streamTime = static_cast<int64_t>(mScheduledFrameIndex) * mFrameDuration;
time.duration = mFrameDuration;
time.timeScale = mTimeScale;
time.frameIndex = mScheduledFrameIndex;
++mScheduledFrameIndex;
return time;
}
void VideoPlayoutScheduler::AlignNextScheduleTimeToPlayback(int64_t streamTime, uint64_t leadFrames)
{
if (mFrameDuration <= 0 || streamTime < 0)
return;
const uint64_t playbackFrameIndex = static_cast<uint64_t>(streamTime / mFrameDuration);
const uint64_t minimumScheduleIndex = playbackFrameIndex + leadFrames;
if (minimumScheduleIndex > mScheduledFrameIndex)
mScheduledFrameIndex = minimumScheduleIndex;
}
VideoPlayoutRecoveryDecision VideoPlayoutScheduler::AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth)
{
++mCompletedFrameIndex;
if (result == VideoIOCompletionResult::DisplayedLate)
++mLateStreak;
else
mLateStreak = 0;
if (result == VideoIOCompletionResult::Dropped)
++mDropStreak;
else
mDropStreak = 0;
const uint64_t measuredLagFrames = MeasureLag(result, readyQueueDepth);
const uint64_t catchUpFrames = measuredLagFrames < mPolicy.lateOrDropCatchUpFrames
? measuredLagFrames
: mPolicy.lateOrDropCatchUpFrames;
if (catchUpFrames > 0)
mScheduledFrameIndex += catchUpFrames;
VideoPlayoutRecoveryDecision decision;
decision.result = result;
decision.completedFrameIndex = mCompletedFrameIndex;
decision.scheduledFrameIndex = mScheduledFrameIndex;
decision.readyQueueDepth = readyQueueDepth;
decision.scheduledLeadFrames = mScheduledFrameIndex > mCompletedFrameIndex
? mScheduledFrameIndex - mCompletedFrameIndex
: 0;
decision.measuredLagFrames = measuredLagFrames;
decision.catchUpFrames = catchUpFrames;
decision.lateStreak = mLateStreak;
decision.dropStreak = mDropStreak;
return decision;
}
double VideoPlayoutScheduler::FrameBudgetMilliseconds() const
{
return mTimeScale != 0
? (static_cast<double>(mFrameDuration) * 1000.0) / static_cast<double>(mTimeScale)
: 0.0;
}
uint64_t VideoPlayoutScheduler::MeasureLag(VideoIOCompletionResult result, uint64_t readyQueueDepth) const
{
if (result != VideoIOCompletionResult::DisplayedLate && result != VideoIOCompletionResult::Dropped)
return 0;
uint64_t lagFrames = 1;
if (result == VideoIOCompletionResult::DisplayedLate && mLateStreak > lagFrames)
lagFrames = mLateStreak;
if (result == VideoIOCompletionResult::Dropped && mDropStreak * 2 > lagFrames)
lagFrames = mDropStreak * 2;
if (mCompletedFrameIndex >= mScheduledFrameIndex)
{
const uint64_t scheduleLagFrames = mCompletedFrameIndex - mScheduledFrameIndex + 1;
if (scheduleLagFrames > lagFrames)
lagFrames = scheduleLagFrames;
}
if (readyQueueDepth < mPolicy.targetReadyFrames && mPolicy.targetReadyFrames - readyQueueDepth > lagFrames)
lagFrames = mPolicy.targetReadyFrames - readyQueueDepth;
return lagFrames;
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include "VideoIOTypes.h"
#include "VideoPlayoutPolicy.h"
#include <cstdint>
class VideoPlayoutScheduler
{
public:
void Configure(int64_t frameDuration, int64_t timeScale);
void Configure(int64_t frameDuration, int64_t timeScale, const VideoPlayoutPolicy& policy);
void Reset();
VideoIOScheduleTime NextScheduleTime();
void AlignNextScheduleTimeToPlayback(int64_t streamTime, uint64_t leadFrames);
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth = 0);
double FrameBudgetMilliseconds() const;
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
uint64_t CompletedFrameIndex() const { return mCompletedFrameIndex; }
int64_t FrameDuration() const { return mFrameDuration; }
uint64_t LateStreak() const { return mLateStreak; }
uint64_t DropStreak() const { return mDropStreak; }
int64_t TimeScale() const { return mTimeScale; }
const VideoPlayoutPolicy& Policy() const { return mPolicy; }
private:
uint64_t MeasureLag(VideoIOCompletionResult result, uint64_t readyQueueDepth) const;
int64_t mFrameDuration = 0;
int64_t mTimeScale = 0;
uint64_t mScheduledFrameIndex = 0;
uint64_t mCompletedFrameIndex = 0;
uint64_t mLateStreak = 0;
uint64_t mDropStreak = 0;
VideoPlayoutPolicy mPolicy;
};