Add synthetic v210 feed source
This commit is contained in:
+11
-1
@@ -686,6 +686,13 @@ std::optional<FeedKind> parseFeedKind(std::string_view text)
|
||||
return FeedKind::SmpteBars;
|
||||
}
|
||||
|
||||
if (text == "v210" ||
|
||||
text == "synthetic-v210" ||
|
||||
text == "v210test")
|
||||
{
|
||||
return FeedKind::SyntheticV210;
|
||||
}
|
||||
|
||||
if (text == "mxlsdk")
|
||||
{
|
||||
return FeedKind::MxlSdk;
|
||||
@@ -724,6 +731,9 @@ std::string_view feedKindName(FeedKind kind)
|
||||
case FeedKind::SmpteBars:
|
||||
return "smpte";
|
||||
|
||||
case FeedKind::SyntheticV210:
|
||||
return "v210";
|
||||
|
||||
case FeedKind::MxlSdk:
|
||||
return "mxlsdk";
|
||||
|
||||
@@ -754,7 +764,7 @@ void printUsage(const char* executableName)
|
||||
<< " --flow <idx> <uuid> Set MXL flow UUID for feed slot\n"
|
||||
<< " --version Print version\n"
|
||||
<< " --help Print this help\n"
|
||||
<< "Feed kinds: nosignal, none, fake, mxl, smpte, mxlsdk\n"
|
||||
<< "Feed kinds: nosignal, none, fake, mxl, smpte, v210, mxlsdk\n"
|
||||
<< "Default: 2x2 grid, all slots nosignal"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ enum class FeedKind
|
||||
NoSignal,
|
||||
MxlPlaceholder,
|
||||
SmpteBars,
|
||||
SyntheticV210,
|
||||
MxlSdk
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ add_executable(mxl_multiviewer
|
||||
MxlInstanceCache.cpp
|
||||
MxlSdkFeed.cpp
|
||||
SmpteBarsFeed.cpp
|
||||
SyntheticV210Feed.cpp
|
||||
TileLayout.cpp
|
||||
VulkanContext.cpp
|
||||
VulkanUtils.cpp
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "MxlFeed.hpp"
|
||||
#include "MxlSdkFeed.hpp"
|
||||
#include "SmpteBarsFeed.hpp"
|
||||
#include "SyntheticV210Feed.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@@ -74,6 +75,18 @@ std::unique_ptr<IVideoFeed> createFeed(
|
||||
frameHeight
|
||||
);
|
||||
|
||||
case FeedKind::SyntheticV210:
|
||||
if (verbose)
|
||||
{
|
||||
std::cerr << "Feed " << (feedIndex + 1)
|
||||
<< ": synthetic v210" << std::endl;
|
||||
}
|
||||
return std::make_unique<SyntheticV210Feed>(
|
||||
feedIndex,
|
||||
frameWidth,
|
||||
frameHeight
|
||||
);
|
||||
|
||||
case FeedKind::NoSignal:
|
||||
default:
|
||||
if (verbose)
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
#include "SyntheticV210Feed.hpp"
|
||||
#include "PixelFormat.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
std::memcpy(destination, payload.data(), payload.size());
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "IVideoFeed.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class SyntheticV210Feed : public IVideoFeed
|
||||
{
|
||||
public:
|
||||
SyntheticV210Feed(
|
||||
uint32_t feedIndex,
|
||||
uint32_t frameWidth,
|
||||
uint32_t frameHeight);
|
||||
|
||||
const VideoFrame& getFrame(uint32_t frameCounter) override;
|
||||
FeedRuntimeStatus status() const override;
|
||||
std::string displayName() const override;
|
||||
std::string sourceInfo() const override;
|
||||
uint64_t frameVersion() const override;
|
||||
bool hasGrainIndex() const override;
|
||||
uint64_t grainIndex() const override;
|
||||
bool hasFrameRate() const override;
|
||||
double frameRate() const override;
|
||||
float srcAspectRatio() const override;
|
||||
bool hasV210() const override;
|
||||
const uint8_t* v210Data() const override;
|
||||
bool supportsDirectV210Read() const override;
|
||||
bool readV210FrameInto(
|
||||
uint32_t frameCounter,
|
||||
void* destination,
|
||||
size_t destinationSize) override;
|
||||
uint32_t v210Width() const override;
|
||||
uint32_t v210Height() const override;
|
||||
uint32_t v210Stride() const override;
|
||||
|
||||
private:
|
||||
uint64_t syntheticIndex(uint32_t frameCounter) const;
|
||||
void generatePayloads();
|
||||
void encodeLine(
|
||||
uint32_t y,
|
||||
uint32_t patternIndex,
|
||||
uint8_t* line) const;
|
||||
|
||||
uint32_t mFeedIndex = 0;
|
||||
uint32_t mSourceWidth = 1920;
|
||||
uint32_t mSourceHeight = 1080;
|
||||
uint32_t mSourceStride = 0;
|
||||
double mFrameRate = 60.0;
|
||||
uint64_t mFrameVersion = 0;
|
||||
uint64_t mGrainIndex = 0;
|
||||
std::vector<std::vector<uint8_t>> mPayloads;
|
||||
VideoFrame mFrame;
|
||||
};
|
||||
Reference in New Issue
Block a user