276 lines
6.3 KiB
C++
276 lines
6.3 KiB
C++
#include "SyntheticV210Feed.hpp"
|
|
#include "PixelFormat.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
|
|
namespace
|
|
{
|
|
constexpr uint32_t SyntheticWidth = 1920;
|
|
constexpr uint32_t SyntheticHeight = 1080;
|
|
constexpr uint32_t SyntheticPatternCount = 4;
|
|
|
|
uint32_t clamp10(int32_t value)
|
|
{
|
|
return static_cast<uint32_t>(
|
|
value < 0 ? 0 : (value > 1023 ? 1023 : value));
|
|
}
|
|
|
|
uint32_t packWord(uint32_t a, uint32_t b, uint32_t c)
|
|
{
|
|
return (a & 0x3FFu) |
|
|
((b & 0x3FFu) << 10) |
|
|
((c & 0x3FFu) << 20);
|
|
}
|
|
|
|
void storeWord(uint8_t* dst, uint32_t word)
|
|
{
|
|
dst[0] = static_cast<uint8_t>(word & 0xFFu);
|
|
dst[1] = static_cast<uint8_t>((word >> 8) & 0xFFu);
|
|
dst[2] = static_cast<uint8_t>((word >> 16) & 0xFFu);
|
|
dst[3] = static_cast<uint8_t>((word >> 24) & 0xFFu);
|
|
}
|
|
|
|
double syntheticRateForFeed(uint32_t feedIndex)
|
|
{
|
|
static constexpr std::array<double, 9> Rates =
|
|
{
|
|
25.0, 25.0, 25.0,
|
|
50.0, 50.0, 60.0,
|
|
25.0, 50.0, 60.0
|
|
};
|
|
|
|
return Rates[feedIndex % Rates.size()];
|
|
}
|
|
}
|
|
|
|
SyntheticV210Feed::SyntheticV210Feed(
|
|
uint32_t feedIndex,
|
|
uint32_t frameWidth,
|
|
uint32_t frameHeight)
|
|
: mFeedIndex(feedIndex)
|
|
{
|
|
mSourceWidth = SyntheticWidth;
|
|
mSourceHeight = SyntheticHeight;
|
|
mSourceStride = v210LineStride(mSourceWidth);
|
|
mFrameRate = syntheticRateForFeed(feedIndex);
|
|
|
|
mFrame.width = frameWidth;
|
|
mFrame.height = frameHeight;
|
|
mFrame.pixels.resize(
|
|
static_cast<size_t>(frameWidth) * frameHeight,
|
|
packRgba8({24, 24, 24}));
|
|
|
|
generatePayloads();
|
|
}
|
|
|
|
const VideoFrame& SyntheticV210Feed::getFrame(uint32_t frameCounter)
|
|
{
|
|
const uint64_t index = syntheticIndex(frameCounter);
|
|
if (index != mGrainIndex)
|
|
{
|
|
mGrainIndex = index;
|
|
++mFrameVersion;
|
|
}
|
|
|
|
return mFrame;
|
|
}
|
|
|
|
FeedRuntimeStatus SyntheticV210Feed::status() const
|
|
{
|
|
return FeedRuntimeStatus::Live;
|
|
}
|
|
|
|
std::string SyntheticV210Feed::displayName() const
|
|
{
|
|
return "SYNTH V210";
|
|
}
|
|
|
|
std::string SyntheticV210Feed::sourceInfo() const
|
|
{
|
|
std::ostringstream out;
|
|
out << "synthetic v210 "
|
|
<< mSourceWidth << "x" << mSourceHeight
|
|
<< "@" << static_cast<uint32_t>(mFrameRate);
|
|
return out.str();
|
|
}
|
|
|
|
uint64_t SyntheticV210Feed::frameVersion() const
|
|
{
|
|
return mFrameVersion;
|
|
}
|
|
|
|
bool SyntheticV210Feed::hasGrainIndex() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
uint64_t SyntheticV210Feed::grainIndex() const
|
|
{
|
|
return mGrainIndex;
|
|
}
|
|
|
|
bool SyntheticV210Feed::hasFrameRate() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
double SyntheticV210Feed::frameRate() const
|
|
{
|
|
return mFrameRate;
|
|
}
|
|
|
|
float SyntheticV210Feed::srcAspectRatio() const
|
|
{
|
|
return static_cast<float>(mSourceWidth) /
|
|
static_cast<float>(mSourceHeight);
|
|
}
|
|
|
|
bool SyntheticV210Feed::hasV210() const
|
|
{
|
|
return !mPayloads.empty();
|
|
}
|
|
|
|
const uint8_t* SyntheticV210Feed::v210Data() const
|
|
{
|
|
if (mPayloads.empty())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return mPayloads[mGrainIndex % mPayloads.size()].data();
|
|
}
|
|
|
|
bool SyntheticV210Feed::supportsDirectV210Read() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool SyntheticV210Feed::readV210FrameInto(
|
|
uint32_t frameCounter,
|
|
void* destination,
|
|
size_t destinationSize,
|
|
V210ReadTiming* timing)
|
|
{
|
|
if (timing != nullptr)
|
|
{
|
|
timing->sourceReadNs = 0;
|
|
timing->payloadCopyNs = 0;
|
|
}
|
|
|
|
if (destination == nullptr || mPayloads.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint64_t index = syntheticIndex(frameCounter);
|
|
if (index == mGrainIndex)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const std::vector<uint8_t>& payload =
|
|
mPayloads[index % mPayloads.size()];
|
|
if (destinationSize < payload.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto copyStart = std::chrono::steady_clock::now();
|
|
std::memcpy(destination, payload.data(), payload.size());
|
|
const auto copyEnd = std::chrono::steady_clock::now();
|
|
|
|
if (timing != nullptr)
|
|
{
|
|
timing->payloadCopyNs =
|
|
static_cast<uint64_t>(
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
copyEnd - copyStart).count());
|
|
}
|
|
|
|
mGrainIndex = index;
|
|
++mFrameVersion;
|
|
return true;
|
|
}
|
|
|
|
uint32_t SyntheticV210Feed::v210Width() const
|
|
{
|
|
return mSourceWidth;
|
|
}
|
|
|
|
uint32_t SyntheticV210Feed::v210Height() const
|
|
{
|
|
return mSourceHeight;
|
|
}
|
|
|
|
uint32_t SyntheticV210Feed::v210Stride() const
|
|
{
|
|
return mSourceStride;
|
|
}
|
|
|
|
uint64_t SyntheticV210Feed::syntheticIndex(
|
|
uint32_t frameCounter) const
|
|
{
|
|
return static_cast<uint64_t>(
|
|
(static_cast<double>(frameCounter) * mFrameRate) / 60.0);
|
|
}
|
|
|
|
void SyntheticV210Feed::generatePayloads()
|
|
{
|
|
const size_t payloadSize =
|
|
static_cast<size_t>(mSourceStride) * mSourceHeight;
|
|
mPayloads.resize(SyntheticPatternCount);
|
|
|
|
for (uint32_t pattern = 0;
|
|
pattern < SyntheticPatternCount;
|
|
++pattern)
|
|
{
|
|
std::vector<uint8_t>& payload = mPayloads[pattern];
|
|
payload.assign(payloadSize, 0);
|
|
|
|
for (uint32_t y = 0; y < mSourceHeight; ++y)
|
|
{
|
|
encodeLine(
|
|
y,
|
|
pattern,
|
|
payload.data() +
|
|
static_cast<size_t>(y) * mSourceStride);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SyntheticV210Feed::encodeLine(
|
|
uint32_t y,
|
|
uint32_t patternIndex,
|
|
uint8_t* line) const
|
|
{
|
|
const uint32_t groups = (mSourceWidth + 5) / 6;
|
|
const uint32_t bandHeight = std::max(1u, mSourceHeight / 8u);
|
|
const uint32_t yBand = (y / bandHeight + patternIndex) % 8u;
|
|
|
|
for (uint32_t group = 0; group < groups; ++group)
|
|
{
|
|
const uint32_t x = group * 6u;
|
|
const uint32_t xBand =
|
|
((x * 8u) / mSourceWidth + mFeedIndex + patternIndex) %
|
|
8u;
|
|
const uint32_t luma =
|
|
clamp10(96 + static_cast<int32_t>(xBand) * 96);
|
|
const uint32_t altLuma =
|
|
clamp10(96 + static_cast<int32_t>(yBand) * 96);
|
|
const uint32_t cb =
|
|
clamp10(512 + (static_cast<int32_t>(xBand) - 3) * 56);
|
|
const uint32_t cr =
|
|
clamp10(512 + (static_cast<int32_t>(yBand) - 3) * 56);
|
|
|
|
uint8_t* dst = line + group * 16u;
|
|
storeWord(dst + 0, packWord(cb, luma, cr));
|
|
storeWord(dst + 4, packWord(altLuma, cb, luma));
|
|
storeWord(dst + 8, packWord(cr, altLuma, cb));
|
|
storeWord(dst + 12, packWord(luma, cr, altLuma));
|
|
}
|
|
}
|