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;
|
return FeedKind::SmpteBars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text == "v210" ||
|
||||||
|
text == "synthetic-v210" ||
|
||||||
|
text == "v210test")
|
||||||
|
{
|
||||||
|
return FeedKind::SyntheticV210;
|
||||||
|
}
|
||||||
|
|
||||||
if (text == "mxlsdk")
|
if (text == "mxlsdk")
|
||||||
{
|
{
|
||||||
return FeedKind::MxlSdk;
|
return FeedKind::MxlSdk;
|
||||||
@@ -724,6 +731,9 @@ std::string_view feedKindName(FeedKind kind)
|
|||||||
case FeedKind::SmpteBars:
|
case FeedKind::SmpteBars:
|
||||||
return "smpte";
|
return "smpte";
|
||||||
|
|
||||||
|
case FeedKind::SyntheticV210:
|
||||||
|
return "v210";
|
||||||
|
|
||||||
case FeedKind::MxlSdk:
|
case FeedKind::MxlSdk:
|
||||||
return "mxlsdk";
|
return "mxlsdk";
|
||||||
|
|
||||||
@@ -754,7 +764,7 @@ void printUsage(const char* executableName)
|
|||||||
<< " --flow <idx> <uuid> Set MXL flow UUID for feed slot\n"
|
<< " --flow <idx> <uuid> Set MXL flow UUID for feed slot\n"
|
||||||
<< " --version Print version\n"
|
<< " --version Print version\n"
|
||||||
<< " --help Print this help\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"
|
<< "Default: 2x2 grid, all slots nosignal"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ enum class FeedKind
|
|||||||
NoSignal,
|
NoSignal,
|
||||||
MxlPlaceholder,
|
MxlPlaceholder,
|
||||||
SmpteBars,
|
SmpteBars,
|
||||||
|
SyntheticV210,
|
||||||
MxlSdk
|
MxlSdk
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ add_executable(mxl_multiviewer
|
|||||||
MxlInstanceCache.cpp
|
MxlInstanceCache.cpp
|
||||||
MxlSdkFeed.cpp
|
MxlSdkFeed.cpp
|
||||||
SmpteBarsFeed.cpp
|
SmpteBarsFeed.cpp
|
||||||
|
SyntheticV210Feed.cpp
|
||||||
TileLayout.cpp
|
TileLayout.cpp
|
||||||
VulkanContext.cpp
|
VulkanContext.cpp
|
||||||
VulkanUtils.cpp
|
VulkanUtils.cpp
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "MxlFeed.hpp"
|
#include "MxlFeed.hpp"
|
||||||
#include "MxlSdkFeed.hpp"
|
#include "MxlSdkFeed.hpp"
|
||||||
#include "SmpteBarsFeed.hpp"
|
#include "SmpteBarsFeed.hpp"
|
||||||
|
#include "SyntheticV210Feed.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@@ -74,6 +75,18 @@ std::unique_ptr<IVideoFeed> createFeed(
|
|||||||
frameHeight
|
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:
|
case FeedKind::NoSignal:
|
||||||
default:
|
default:
|
||||||
if (verbose)
|
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