Files
mxl-multiviewer/MxlSdkFeed.cpp
T
2026-05-19 00:43:04 +03:00

771 lines
18 KiB
C++

#include "MxlSdkFeed.hpp"
#include "MxlInstanceCache.hpp"
#include "PixelFormat.hpp"
#include <cstring>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <thread>
#if MXL_MULTIVIEWER_ENABLE_MXL_SDK
#include <cstdlib>
#include <mxl/flow.h>
#include <mxl/mxl.h>
#include <mxl/time.h>
namespace
{
std::string unescapeJsonString(
const char* begin,
const char* end)
{
std::string value;
value.reserve(static_cast<size_t>(end - begin));
for (const char* p = begin; p < end; ++p)
{
if (*p != '\\' || p + 1 >= end)
{
value.push_back(*p);
continue;
}
++p;
switch (*p)
{
case '"': value.push_back('"'); break;
case '\\': value.push_back('\\'); break;
case '/': value.push_back('/'); break;
case 'b': value.push_back('\b'); break;
case 'f': value.push_back('\f'); break;
case 'n': value.push_back('\n'); break;
case 'r': value.push_back('\r'); break;
case 't': value.push_back('\t'); break;
default:
value.push_back(*p);
break;
}
}
return value;
}
std::string parseStringFromJson(
const char* json,
const char* key)
{
std::string searchKey = std::string("\"") + key + "\"";
const char* pos = std::strstr(json, searchKey.c_str());
if (!pos)
{
return "";
}
pos += searchKey.size();
while (*pos == ' ' || *pos == '\t' || *pos == '\n' ||
*pos == '\r' || *pos == ':')
{
pos++;
}
if (*pos != '"')
{
return "";
}
++pos;
const char* valueBegin = pos;
bool escaped = false;
while (*pos != '\0')
{
if (!escaped && *pos == '"')
{
return unescapeJsonString(valueBegin, pos);
}
escaped = !escaped && *pos == '\\';
if (*pos != '\\')
{
escaped = false;
}
++pos;
}
return "";
}
std::string firstNonEmptyFlowLabel(const char* json)
{
const char* keys[] =
{
"name",
"flow_name",
"description",
"label"
};
for (const char* key : keys)
{
std::string value = parseStringFromJson(json, key);
if (!value.empty())
{
return value;
}
}
return "";
}
uint32_t parseUintFromJson(const char* json, const char* key)
{
std::string searchKey = std::string("\"") + key + "\"";
const char* pos = std::strstr(json, searchKey.c_str());
if (!pos)
{
return 0;
}
pos += searchKey.size();
while (*pos == ' ' || *pos == '\t' || *pos == '\n' ||
*pos == '\r' || *pos == ':')
{
pos++;
}
return static_cast<uint32_t>(std::strtoul(pos, nullptr, 10));
}
uint32_t deriveV210Width(uint32_t stride)
{
if (stride == 0 || stride % 16 != 0)
{
return 0;
}
return (stride / 16) * 6;
}
}
#endif
MxlSdkFeed::MxlSdkFeed(
const std::string& domain,
const std::string& flowId,
uint32_t outputWidth,
uint32_t outputHeight,
bool verbose)
: mDomain(domain)
, mFlowId(flowId)
{
(void)verbose;
mFrame.width = outputWidth;
mFrame.height = outputHeight;
mFrame.pixels.resize(
static_cast<size_t>(outputWidth) * outputHeight, 0);
#if MXL_MULTIVIEWER_ENABLE_MXL_SDK
mVerbose = verbose;
mGrainRate.numerator = 30000;
mGrainRate.denominator = 1001;
auto& cache = MxlInstanceCache::instance();
mInstanceHandle = cache.acquire(mDomain, mVerbose);
if (!mInstanceHandle)
{
std::cerr << "MxlSdkFeed: failed to create MXL instance"
<< " for domain: " << mDomain << std::endl;
}
#endif
}
MxlSdkFeed::~MxlSdkFeed()
{
#if MXL_MULTIVIEWER_ENABLE_MXL_SDK
closeFlow();
if (mInstanceHandle)
{
auto& cache = MxlInstanceCache::instance();
cache.release(mInstanceHandle);
mInstanceHandle = nullptr;
}
#endif
}
#if MXL_MULTIVIEWER_ENABLE_MXL_SDK
bool MxlSdkFeed::openFlow()
{
if (mFlowOpened)
{
return true;
}
if (mOpenRetryCount >= 300)
{
mFlowInvalid = true;
return false;
}
if (!mInstanceHandle || !mInstanceHandle->instance)
{
mFlowInvalid = true;
return false;
}
if (mOpenRetryCount > 0)
{
int delayMs = 100;
if (mOpenRetryCount > 5)
{
delayMs = 500;
}
if (mOpenRetryCount > 20)
{
delayMs = 2000;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(delayMs));
}
++mOpenRetryCount;
mxlInstance instance = mInstanceHandle->instance;
mxlStatus status = mxlCreateFlowReader(
instance,
mFlowId.c_str(),
nullptr,
&mReader);
if (status == MXL_ERR_FLOW_NOT_FOUND ||
status == MXL_ERR_CONFLICT)
{
mFlowInvalid = true;
return false;
}
if (status != MXL_STATUS_OK)
{
std::cerr << "MxlSdkFeed: failed to open flow " << mFlowId
<< " (status=" << status << ")" << std::endl;
mFlowInvalid = true;
return false;
}
mxlFlowConfigInfo configInfo{};
std::memset(&configInfo, 0, sizeof(configInfo));
status = mxlFlowReaderGetConfigInfo(mReader, &configInfo);
if (status != MXL_STATUS_OK)
{
std::cerr << "MxlSdkFeed: failed to get config info"
<< " (status=" << status << ")" << std::endl;
mxlReleaseFlowReader(instance, mReader);
mReader = nullptr;
return false;
}
mSourceStride = configInfo.discrete.sliceSizes[0];
mGrainRate = configInfo.common.grainRate;
if (mVerbose)
{
std::cerr << "MxlSdkFeed: stride=" << mSourceStride
<< " grainRate=" << mGrainRate.numerator
<< "/" << mGrainRate.denominator
<< " grainsPerFrame=" << configInfo.discrete.grainCount
<< std::endl;
}
mSourceWidth = deriveV210Width(mSourceStride);
if (mSourceWidth == 0)
{
std::cerr << "MxlSdkFeed: could not determine source width from stride="
<< mSourceStride << std::endl;
mxlReleaseFlowReader(instance, mReader);
mReader = nullptr;
return false;
}
size_t defBufSize = 0;
mxlStatus defStatus = mxlGetFlowDef(
instance,
mFlowId.c_str(),
nullptr,
&defBufSize);
if (mVerbose)
{
std::cerr << "MxlSdkFeed: mxlGetFlowDef status=" << defStatus
<< " bufSize=" << defBufSize << std::endl;
}
if (defBufSize > 0)
{
std::vector<char> defBuf(defBufSize + 1);
size_t readSize = defBufSize;
defStatus = mxlGetFlowDef(
instance,
mFlowId.c_str(),
defBuf.data(),
&readSize);
if (mVerbose)
{
std::cerr << "MxlSdkFeed: mxlGetFlowDef(read) status="
<< defStatus
<< " readSize=" << readSize << std::endl;
}
if (readSize > 0)
{
defBuf[readSize] = '\0';
uint32_t jsonWidth = parseUintFromJson(defBuf.data(), "frame_width");
uint32_t jsonHeight = parseUintFromJson(defBuf.data(), "frame_height");
std::string flowName =
firstNonEmptyFlowLabel(defBuf.data());
if (jsonWidth > 0)
{
mSourceWidth = jsonWidth;
}
if (jsonHeight > 0)
{
mSourceHeight = jsonHeight;
}
if (!flowName.empty())
{
mFlowName = std::move(flowName);
}
if (mVerbose)
{
std::cerr << "MxlSdkFeed: flow definition: "
<< jsonWidth << "x" << jsonHeight << std::endl;
if (!mFlowName.empty())
{
std::cerr << "MxlSdkFeed: flow name: "
<< mFlowName << std::endl;
}
}
}
}
if (mSourceHeight == 0)
{
mSourceHeight = 1080;
std::cerr << "MxlSdkFeed: assuming height=1080" << std::endl;
}
if (mGrainRate.denominator == 0)
{
mGrainRate.numerator = 30000;
mGrainRate.denominator = 1001;
}
mxlFlowRuntimeInfo runtimeInfo{};
if (mxlFlowReaderGetRuntimeInfo(mReader, &runtimeInfo) ==
MXL_STATUS_OK &&
runtimeInfo.headIndex > 0)
{
mLastDisplayedIndex = runtimeInfo.headIndex - 1;
}
else
{
mLastDisplayedIndex = mxlGetCurrentIndex(&mGrainRate);
}
if (mVerbose)
{
std::cerr << "MxlSdkFeed: opened flow " << mFlowId
<< " stride=" << mSourceStride
<< " grainRate=" << mGrainRate.numerator
<< "/" << mGrainRate.denominator
<< " src=" << mSourceWidth << "x" << mSourceHeight
<< std::endl;
}
mFlowOpened = true;
mFlowInvalid = false;
mOpenRetryCount = 0;
return true;
}
void MxlSdkFeed::closeFlow()
{
if (mReader && mInstanceHandle && mInstanceHandle->instance)
{
mxlReleaseFlowReader(mInstanceHandle->instance, mReader);
mReader = nullptr;
}
mFlowOpened = false;
mFlowInvalid = true;
mOpenRetryCount = 0;
mV210Payload.clear();
}
const VideoFrame& MxlSdkFeed::getFrame(uint32_t frameCounter)
{
(void)frameCounter;
if (!mFlowOpened && !openFlow())
{
return mFrame;
}
uint64_t index = mLastDisplayedIndex + 1;
const uint64_t currentIndex = mxlGetCurrentIndex(&mGrainRate);
if (index > currentIndex)
{
return mFrame;
}
mxlGrainInfo grain{};
grain.version = 2;
grain.size = sizeof(mxlGrainInfo);
uint8_t* payload = nullptr;
while (true)
{
mxlStatus status = mxlFlowReaderGetGrainNonBlocking(
mReader,
index,
&grain,
&payload);
if (status == MXL_STATUS_OK)
{
break;
}
if (status == MXL_ERR_OUT_OF_RANGE_TOO_EARLY)
{
return mFrame;
}
if (status == MXL_ERR_OUT_OF_RANGE_TOO_LATE)
{
mxlFlowRuntimeInfo runtimeInfo{};
if (mxlFlowReaderGetRuntimeInfo(mReader, &runtimeInfo) !=
MXL_STATUS_OK)
{
return mFrame;
}
if (runtimeInfo.headIndex == 0)
{
return mFrame;
}
index = runtimeInfo.headIndex - 1;
if (index <= mLastDisplayedIndex)
{
return mFrame;
}
continue;
}
if (status == MXL_ERR_FLOW_INVALID)
{
std::cerr << "MxlSdkFeed: flow invalidated, reconnecting"
<< std::endl;
closeFlow();
return mFrame;
}
std::cerr << "MxlSdkFeed: grain read error status="
<< static_cast<int>(status) << std::endl;
return mFrame;
}
mLastDisplayedIndex = index;
const size_t payloadSize =
static_cast<size_t>(mSourceStride) *
static_cast<size_t>(mSourceHeight);
if (payload == nullptr || payloadSize == 0)
{
return mFrame;
}
mV210Payload.resize(payloadSize);
std::memcpy(mV210Payload.data(), payload, payloadSize);
++mFrameVersion;
return mFrame;
}
bool MxlSdkFeed::readV210FrameInto(
uint32_t frameCounter,
void* destination,
size_t destinationSize)
{
(void)frameCounter;
if (!mFlowOpened && !openFlow())
{
return false;
}
uint64_t index = mLastDisplayedIndex + 1;
const uint64_t currentIndex = mxlGetCurrentIndex(&mGrainRate);
if (index > currentIndex)
{
return false;
}
mxlGrainInfo grain{};
grain.version = 2;
grain.size = sizeof(mxlGrainInfo);
uint8_t* payload = nullptr;
while (true)
{
mxlStatus status = mxlFlowReaderGetGrainNonBlocking(
mReader,
index,
&grain,
&payload);
if (status == MXL_STATUS_OK)
{
break;
}
if (status == MXL_ERR_OUT_OF_RANGE_TOO_EARLY)
{
return false;
}
if (status == MXL_ERR_OUT_OF_RANGE_TOO_LATE)
{
mxlFlowRuntimeInfo runtimeInfo{};
if (mxlFlowReaderGetRuntimeInfo(mReader, &runtimeInfo) !=
MXL_STATUS_OK)
{
return false;
}
if (runtimeInfo.headIndex == 0)
{
return false;
}
index = runtimeInfo.headIndex - 1;
if (index <= mLastDisplayedIndex)
{
return false;
}
continue;
}
if (status == MXL_ERR_FLOW_INVALID)
{
std::cerr << "MxlSdkFeed: flow invalidated, reconnecting"
<< std::endl;
closeFlow();
return false;
}
std::cerr << "MxlSdkFeed: grain read error status="
<< static_cast<int>(status) << std::endl;
return false;
}
const size_t payloadSize =
static_cast<size_t>(mSourceStride) *
static_cast<size_t>(mSourceHeight);
if (payload == nullptr ||
destination == nullptr ||
payloadSize == 0 ||
destinationSize < payloadSize)
{
return false;
}
std::memcpy(destination, payload, payloadSize);
mLastDisplayedIndex = index;
++mFrameVersion;
return true;
}
FeedRuntimeStatus MxlSdkFeed::status() const
{
if (mFlowOpened && !mV210Payload.empty())
{
return FeedRuntimeStatus::Live;
}
if (mFlowOpened)
{
return FeedRuntimeStatus::Connecting;
}
return mFlowInvalid
? FeedRuntimeStatus::Reconnecting
: FeedRuntimeStatus::Connecting;
}
std::string MxlSdkFeed::displayName() const
{
return mFlowName.empty() ? "MXL LIVE" : mFlowName;
}
std::string MxlSdkFeed::sourceInfo() const
{
std::ostringstream out;
if (!mFlowName.empty())
{
out << mFlowName;
}
if (mSourceWidth == 0 || mSourceHeight == 0)
{
return out.str();
}
if (!mFlowName.empty())
{
out << " ";
}
out << mSourceWidth << "x" << mSourceHeight;
if (mGrainRate.denominator > 0)
{
const double fps =
static_cast<double>(mGrainRate.numerator) /
static_cast<double>(mGrainRate.denominator);
out << "@";
if (mGrainRate.denominator == 1)
{
out << mGrainRate.numerator;
}
else
{
out << std::fixed << std::setprecision(2) << fps;
}
out << "p";
}
return out.str();
}
uint64_t MxlSdkFeed::frameVersion() const { return mFrameVersion; }
bool MxlSdkFeed::hasGrainIndex() const { return mFlowOpened; }
uint64_t MxlSdkFeed::grainIndex() const { return mLastDisplayedIndex; }
bool MxlSdkFeed::hasFrameRate() const
{
return mGrainRate.denominator > 0 &&
mGrainRate.numerator > 0;
}
double MxlSdkFeed::frameRate() const
{
if (!hasFrameRate())
{
return 0.0;
}
return static_cast<double>(mGrainRate.numerator) /
static_cast<double>(mGrainRate.denominator);
}
float MxlSdkFeed::srcAspectRatio() const
{
if (mSourceWidth > 0 && mSourceHeight > 0)
{
return static_cast<float>(mSourceWidth) /
static_cast<float>(mSourceHeight);
}
return 16.0f / 9.0f;
}
bool MxlSdkFeed::hasV210() const { return mFlowOpened && !mV210Payload.empty(); }
const uint8_t* MxlSdkFeed::v210Data() const { return mV210Payload.data(); }
bool MxlSdkFeed::supportsDirectV210Read() const { return mFlowOpened; }
uint32_t MxlSdkFeed::v210Width() const { return mSourceWidth; }
uint32_t MxlSdkFeed::v210Height() const { return mSourceHeight; }
uint32_t MxlSdkFeed::v210Stride() const { return mSourceStride; }
#else
bool MxlSdkFeed::openFlow() { return false; }
void MxlSdkFeed::closeFlow() {}
FeedRuntimeStatus MxlSdkFeed::status() const { return FeedRuntimeStatus::Error; }
std::string MxlSdkFeed::displayName() const { return "MXL SDK DISABLED"; }
std::string MxlSdkFeed::sourceInfo() const { return ""; }
uint64_t MxlSdkFeed::frameVersion() const { return 0; }
bool MxlSdkFeed::hasGrainIndex() const { return false; }
uint64_t MxlSdkFeed::grainIndex() const { return 0; }
bool MxlSdkFeed::hasFrameRate() const { return false; }
double MxlSdkFeed::frameRate() const { return 0.0; }
float MxlSdkFeed::srcAspectRatio() const { return 16.0f / 9.0f; }
bool MxlSdkFeed::hasV210() const { return false; }
const uint8_t* MxlSdkFeed::v210Data() const { return nullptr; }
bool MxlSdkFeed::supportsDirectV210Read() const { return false; }
bool MxlSdkFeed::readV210FrameInto(
uint32_t frameCounter,
void* destination,
size_t destinationSize)
{
(void)frameCounter;
(void)destination;
(void)destinationSize;
return false;
}
uint32_t MxlSdkFeed::v210Width() const { return 0; }
uint32_t MxlSdkFeed::v210Height() const { return 0; }
uint32_t MxlSdkFeed::v210Stride() const { return 0; }
const VideoFrame& MxlSdkFeed::getFrame(uint32_t frameCounter)
{
(void)frameCounter;
for (uint32_t y = 0; y < mFrame.height; ++y)
{
for (uint32_t x = 0; x < mFrame.width; ++x)
{
uint8_t v = ((x + y) % 2 == 0) ? 80 : 40;
mFrame.pixels[y * mFrame.width + x] =
packRgba8({v, v, v});
}
}
return mFrame;
}
#endif