Decklink abstraction
This commit is contained in:
159
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOFormat.cpp
Normal file
159
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOFormat.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#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::Bgra8:
|
||||
return "8-bit BGRA";
|
||||
case VideoIOPixelFormat::Uyvy8:
|
||||
default:
|
||||
return "8-bit YUV UYVY";
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
|
||||
{
|
||||
return format == VideoIOPixelFormat::V210;
|
||||
}
|
||||
|
||||
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::V210:
|
||||
default:
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth)
|
||||
{
|
||||
if (format == VideoIOPixelFormat::V210)
|
||||
return MinimumV210RowBytes(frameWidth);
|
||||
return frameWidth * VideoIOBytesPerPixel(format);
|
||||
}
|
||||
|
||||
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes)
|
||||
{
|
||||
return (rowBytes + 3u) / 4u;
|
||||
}
|
||||
|
||||
unsigned MinimumV210RowBytes(unsigned frameWidth)
|
||||
{
|
||||
return ((frameWidth + 5u) / 6u) * 16u;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
enum class VideoIOPixelFormat
|
||||
{
|
||||
Uyvy8,
|
||||
V210,
|
||||
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 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);
|
||||
137
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h
Normal file
137
apps/LoopThroughWithOpenGLCompositing/videoio/VideoIOTypes.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct VideoIOScheduleTime
|
||||
{
|
||||
int64_t streamTime = 0;
|
||||
int64_t duration = 0;
|
||||
int64_t timeScale = 0;
|
||||
uint64_t frameIndex = 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, 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 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 void AccountForCompletionResult(VideoIOCompletionResult result) = 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; }
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "VideoPlayoutScheduler.h"
|
||||
|
||||
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale)
|
||||
{
|
||||
mFrameDuration = frameDuration;
|
||||
mTimeScale = timeScale;
|
||||
Reset();
|
||||
}
|
||||
|
||||
void VideoPlayoutScheduler::Reset()
|
||||
{
|
||||
mScheduledFrameIndex = 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::AccountForCompletionResult(VideoIOCompletionResult result)
|
||||
{
|
||||
if (result == VideoIOCompletionResult::DisplayedLate || result == VideoIOCompletionResult::Dropped)
|
||||
mScheduledFrameIndex += 2;
|
||||
}
|
||||
|
||||
double VideoPlayoutScheduler::FrameBudgetMilliseconds() const
|
||||
{
|
||||
return mTimeScale != 0
|
||||
? (static_cast<double>(mFrameDuration) * 1000.0) / static_cast<double>(mTimeScale)
|
||||
: 0.0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class VideoPlayoutScheduler
|
||||
{
|
||||
public:
|
||||
void Configure(int64_t frameDuration, int64_t timeScale);
|
||||
void Reset();
|
||||
VideoIOScheduleTime NextScheduleTime();
|
||||
void AccountForCompletionResult(VideoIOCompletionResult result);
|
||||
double FrameBudgetMilliseconds() const;
|
||||
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
||||
int64_t TimeScale() const { return mTimeScale; }
|
||||
|
||||
private:
|
||||
int64_t mFrameDuration = 0;
|
||||
int64_t mTimeScale = 0;
|
||||
uint64_t mScheduledFrameIndex = 0;
|
||||
};
|
||||
Reference in New Issue
Block a user