Files
mxl-multiviewer/SyntheticV210Feed.cpp
T
2026-05-19 15:02:22 +03:00

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));
}
}