NDI cleanup to avoid calling NDI while holding the output mutex
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Failing after 1m46s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-22 16:51:48 +10:00
parent f8c3c60611
commit 80c6fd2434
2 changed files with 62 additions and 35 deletions

View File

@@ -134,7 +134,7 @@ bool NdiOutput::StartScheduledPlayback(std::string& error)
bool NdiOutput::ScheduleFrame(const VideoIOOutputFrame& frame) bool NdiOutput::ScheduleFrame(const VideoIOOutputFrame& frame)
{ {
if (!mRunning.load(std::memory_order_acquire) || frame.bytes == nullptr || frame.pixelFormat != VideoIOPixelFormat::Bgra8) if (frame.bytes == nullptr || frame.pixelFormat != VideoIOPixelFormat::Bgra8)
{ {
++mScheduleFailures; ++mScheduleFailures;
return false; return false;
@@ -143,44 +143,79 @@ bool NdiOutput::ScheduleFrame(const VideoIOOutputFrame& frame)
NDIlib_video_frame_v2_t ndiFrame; NDIlib_video_frame_v2_t ndiFrame;
SetCommonVideoFrameFields(ndiFrame, frame, mConfig); SetCommonVideoFrameFields(ndiFrame, frame, mConfig);
std::lock_guard<std::mutex> lock(mMutex); void* completedBuffer = nullptr;
if (mSender == nullptr)
{ {
++mScheduleFailures; std::lock_guard<std::mutex> sdkLock(mSdkMutex);
return false; void* sender = nullptr;
{
std::lock_guard<std::mutex> stateLock(mMutex);
if (!mRunning.load(std::memory_order_acquire) || mSender == nullptr)
{
++mScheduleFailures;
return false;
}
sender = mSender;
}
const auto scheduleStart = std::chrono::steady_clock::now();
NDIlib_send_send_video_async_v2(static_cast<NDIlib_send_instance_t>(sender), &ndiFrame);
mScheduleCallMilliseconds.store(
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::steady_clock::now() - scheduleStart).count(),
std::memory_order_relaxed);
std::lock_guard<std::mutex> stateLock(mMutex);
completedBuffer = mPendingBuffer;
mPendingBuffer = frame.nativeBuffer != nullptr ? frame.nativeBuffer : frame.bytes;
} }
const auto scheduleStart = std::chrono::steady_clock::now(); CompleteBuffer(completedBuffer, VideoIOCompletionResult::Completed);
NDIlib_send_send_video_async_v2(static_cast<NDIlib_send_instance_t>(mSender), &ndiFrame);
mScheduleCallMilliseconds.store(
std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::steady_clock::now() - scheduleStart).count(),
std::memory_order_relaxed);
CompletePendingLocked(VideoIOCompletionResult::Completed);
mPendingBuffer = frame.nativeBuffer != nullptr ? frame.nativeBuffer : frame.bytes;
return true; return true;
} }
void NdiOutput::Stop() void NdiOutput::Stop()
{ {
std::lock_guard<std::mutex> lock(mMutex); void* flushedBuffer = nullptr;
if (mSender != nullptr) {
FlushPendingLocked(); std::lock_guard<std::mutex> sdkLock(mSdkMutex);
mRunning.store(false, std::memory_order_release); void* sender = nullptr;
{
std::lock_guard<std::mutex> stateLock(mMutex);
sender = mSender;
}
if (sender != nullptr)
NDIlib_send_send_video_async_v2(static_cast<NDIlib_send_instance_t>(sender), nullptr);
std::lock_guard<std::mutex> stateLock(mMutex);
flushedBuffer = mPendingBuffer;
mPendingBuffer = nullptr;
mRunning.store(false, std::memory_order_release);
}
CompleteBuffer(flushedBuffer, VideoIOCompletionResult::Flushed);
} }
void NdiOutput::ReleaseResources() void NdiOutput::ReleaseResources()
{ {
void* sender = nullptr;
void* flushedBuffer = nullptr;
{ {
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> sdkLock(mSdkMutex);
if (mSender != nullptr)
{ {
FlushPendingLocked(); std::lock_guard<std::mutex> stateLock(mMutex);
NDIlib_send_destroy(static_cast<NDIlib_send_instance_t>(mSender)); sender = mSender;
mSender = nullptr; mSender = nullptr;
ReleaseNdiRuntime(); flushedBuffer = mPendingBuffer;
mPendingBuffer = nullptr;
}
if (sender != nullptr)
{
NDIlib_send_send_video_async_v2(static_cast<NDIlib_send_instance_t>(sender), nullptr);
NDIlib_send_destroy(static_cast<NDIlib_send_instance_t>(sender));
} }
} }
CompleteBuffer(flushedBuffer, VideoIOCompletionResult::Flushed);
if (sender != nullptr)
ReleaseNdiRuntime();
mInitialized.store(false, std::memory_order_release); mInitialized.store(false, std::memory_order_release);
mRunning.store(false, std::memory_order_release); mRunning.store(false, std::memory_order_release);
} }
@@ -201,15 +236,14 @@ NdiOutputMetrics NdiOutput::Metrics() const
return metrics; return metrics;
} }
void NdiOutput::CompletePendingLocked(VideoIOCompletionResult result) void NdiOutput::CompleteBuffer(void* buffer, VideoIOCompletionResult result)
{ {
if (mPendingBuffer == nullptr) if (buffer == nullptr)
return; return;
VideoIOCompletion completion; VideoIOCompletion completion;
completion.result = result; completion.result = result;
completion.outputFrameBuffer = mPendingBuffer; completion.outputFrameBuffer = buffer;
mPendingBuffer = nullptr;
++mCompletions; ++mCompletions;
if (result == VideoIOCompletionResult::Dropped) if (result == VideoIOCompletionResult::Dropped)
++mDropped; ++mDropped;
@@ -220,13 +254,6 @@ void NdiOutput::CompletePendingLocked(VideoIOCompletionResult result)
mCompletionCallback(completion); mCompletionCallback(completion);
} }
void NdiOutput::FlushPendingLocked()
{
if (mSender != nullptr)
NDIlib_send_send_video_async_v2(static_cast<NDIlib_send_instance_t>(mSender), nullptr);
CompletePendingLocked(VideoIOCompletionResult::Flushed);
}
void NdiOutput::PopulateState(VideoIOState& state, const VideoOutputEdgeConfig& config, const std::string& senderName) void NdiOutput::PopulateState(VideoIOState& state, const VideoOutputEdgeConfig& config, const std::string& senderName)
{ {
state = VideoIOState(); state = VideoIOState();

View File

@@ -30,13 +30,13 @@ public:
NdiOutputMetrics Metrics() const override; NdiOutputMetrics Metrics() const override;
private: private:
void CompletePendingLocked(VideoIOCompletionResult result); void CompleteBuffer(void* buffer, VideoIOCompletionResult result);
void FlushPendingLocked();
static void PopulateState(VideoIOState& state, const VideoOutputEdgeConfig& config, const std::string& senderName); static void PopulateState(VideoIOState& state, const VideoOutputEdgeConfig& config, const std::string& senderName);
std::string mSenderName; std::string mSenderName;
CompletionCallback mCompletionCallback; CompletionCallback mCompletionCallback;
mutable std::mutex mMutex; mutable std::mutex mMutex;
mutable std::mutex mSdkMutex;
void* mSender = nullptr; void* mSender = nullptr;
void* mPendingBuffer = nullptr; void* mPendingBuffer = nullptr;
VideoOutputEdgeConfig mConfig; VideoOutputEdgeConfig mConfig;