#include "SyntheticV210Feed.hpp" #include "PixelFormat.hpp" #include #include #include #include #include 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( 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(word & 0xFFu); dst[1] = static_cast((word >> 8) & 0xFFu); dst[2] = static_cast((word >> 16) & 0xFFu); dst[3] = static_cast((word >> 24) & 0xFFu); } double syntheticRateForFeed(uint32_t feedIndex) { static constexpr std::array 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(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(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(mSourceWidth) / static_cast(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& 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( std::chrono::duration_cast( 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( (static_cast(frameCounter) * mFrameRate) / 60.0); } void SyntheticV210Feed::generatePayloads() { const size_t payloadSize = static_cast(mSourceStride) * mSourceHeight; mPayloads.resize(SyntheticPatternCount); for (uint32_t pattern = 0; pattern < SyntheticPatternCount; ++pattern) { std::vector& payload = mPayloads[pattern]; payload.assign(payloadSize, 0); for (uint32_t y = 0; y < mSourceHeight; ++y) { encodeLine( y, pattern, payload.data() + static_cast(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(xBand) * 96); const uint32_t altLuma = clamp10(96 + static_cast(yBand) * 96); const uint32_t cb = clamp10(512 + (static_cast(xBand) - 3) * 56); const uint32_t cr = clamp10(512 + (static_cast(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)); } }