initial commit
This commit is contained in:
+631
@@ -0,0 +1,631 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
mV210Payload = payload;
|
||||
++mFrameVersion;
|
||||
|
||||
return mFrame;
|
||||
}
|
||||
|
||||
FeedRuntimeStatus MxlSdkFeed::status() const
|
||||
{
|
||||
if (mFlowOpened && mV210Payload != nullptr)
|
||||
{
|
||||
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; }
|
||||
|
||||
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 != nullptr; }
|
||||
const uint8_t* MxlSdkFeed::v210Data() const { return mV210Payload; }
|
||||
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; }
|
||||
float MxlSdkFeed::srcAspectRatio() const { return 16.0f / 9.0f; }
|
||||
bool MxlSdkFeed::hasV210() const { return false; }
|
||||
const uint8_t* MxlSdkFeed::v210Data() const { return nullptr; }
|
||||
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
|
||||
Reference in New Issue
Block a user