Log debug messages when the number of connections to output changes#1402
Conversation
There was a problem hiding this comment.
Pull request overview
Reduces unnecessary NDI send workload by pausing video/audio frame submission when there are no active NDI receivers, aiming to lower CPU usage for Main/Preview/Filter outputs.
Changes:
- Adds receiver-connection checks and early-returns to skip NDI send calls when no receivers are connected.
- Introduces throttling (once per second) for connection checks in
ndi-output.cpp. - Adds connection-aware logging for sender pause/resume events.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
src/ndi-output.cpp |
Adds mutex-protected sender access plus throttled connection checks to skip sending when no receivers exist. |
src/ndi-filter.cpp |
Adds connection checks and pause/resume logging to dedicated filter sender paths. |
Comments suppressed due to low confidence (1)
src/ndi-filter.cpp:305
NDIlib_send_create_t send_descis not zero-initialized; if the SDK struct gains fields, they will be indeterminate and can break sender creation. Initialize the struct (e.g.,NDIlib_send_create_t send_desc{};) before populating fields.
NDIlib_send_create_t send_desc;
send_desc.p_ndi_name = name;
if (groups && groups[0])
send_desc.p_groups = groups;
else
send_desc.p_groups = nullptr;
send_desc.clock_video = false;
send_desc.clock_audio = false;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
paulpv
left a comment
There was a problem hiding this comment.
I am very surprised this is an issue at all.
I would have expected that NDI itself to gate/no-op if there are no connections.
The code that this PR gates:
ndi-filter.cppndi_filter_raw_videohttps://github.com/DistroAV/DistroAV/pull/1402/changes?diff=split#diff-51f07640ecc9f12c60ce2759b94a6bc25936be9209635c900079a8b98b8725e2R160-R178ndi_filter_asyncaudiohttps://github.com/DistroAV/DistroAV/pull/1402/changes?diff=split#diff-51f07640ecc9f12c60ce2759b94a6bc25936be9209635c900079a8b98b8725e2R490-R527
ndi-output.cppndi_output_rawvideohttps://github.com/DistroAV/DistroAV/pull/1402/changes?diff=split#diff-df1f3ab07fec0ccbc1165e39e70ee06ea2309318cdf5e3143e775edc2f9b6305R366-R389ndi_output_rawaudiohttps://github.com/DistroAV/DistroAV/pull/1402/changes?diff=split#diff-df1f3ab07fec0ccbc1165e39e70ee06ea2309318cdf5e3143e775edc2f9b6305R430-R460
That gated code is relatively pretty minimal.
Part of that code is just a buffer grow check that would be called very rarely once settled.
There isn't even a pthread_mutex_lock/pthread_mutex_unlock in the ndi-output.cpp code; there is in the ndi-filter.cpp code.
I would not expect a simple conv_function (only called in ndi-output.cpp) or memcpy (called in both ndi-output.cpp and ndi-filter.cpp) to be the reason for so much reported CPU usage.
Has this code (with or without the PR) been profiled with a 3rd party tool to confirm its CPU budget?
I also consider this reported problem a big of a moot red herring.
Am I correct in interpreting the problem to be "There is unexpected sending overhead when connections < 1, but there is expected sending overhead when sending to > 0".
Sure, that seems on the surface like a reasonable request, but effectively only a user that has recipients the minority of the time has anything to complain about.
The user is not concerned/complaining about the sending overhead when there are > 0 recipients, but only when there are not any recipients are they somehow magically not OK with the exact same overhead that they were OK with when they did have recipients.
Rephrased: The user accepts that the plugin has an overhead when in use. They are using the plugin. What real-world difference does it make that the plugin uses the same amount of overhead whether there are 0 recipients or > 0 recipients?
Regardless, I still think that:
- The gated code overhead seems very minimal and should not be accounting for the reported CPU usage.
- If there is a large CPU usage when sending to 0 recipients then that seems like an NDI problem.
- Maybe it would be nice of us to self-gate to prevent this overhead, but this seems moot since there is no effective real-world use case difference between a user accepting that there is an overhead when there are > 0 recipients (which should be the case the vast majority of the time) and them being performance pedantic that same acceptable to them overhead is now not acceptable to them when there are 0 recipients (which should be the case the vast minority of the time).
The ground floor base overhead of the plugin is the overhead that it uses when it is in use.
It does not real-world use case matter if we optimize out any overhead of the plugin when it is not in use.
It would be like someone expecting a radio broadcast transmitter to use less power when nobody is listening.
Self-gating when connections == 0 may still be a pragmatic optimization, but architecturally it’s an opportunistic idle-path optimization, not a correction of an abnormal runtime condition.
Or maybe I missed the point of the problem or the overhead of that gated code really is that high. :/
|
Since there is not real observable improvement in performance by not outputting if there are no receivers, we will only output a LOG_DEBUG message whenever the no of connections on an output changes. We only check once every second. |
When there are no receivers for a DistroAV output, whether it be Main/Preview/Filter, we still call the NDI send API. This causes CPU overhead which can be significant. Testing a 30 fps media source at full HD (1920x1024):
Filter output off: -10% CPU
Preview output off: -5% CPU
Main output off: < -3% CPU
Here are the pertinent log messages with normal logging (no -verbose) showing when output is paused and started based on connections.
This was tested by starting OBS, then selecting OBS, then OBS Preview, then DistroAV NDI Filter Output as sources in a single instance of NDI Studio Monitor.
The output thread will check the number of connections once a second, and will wait at most 10ms to return the no of connections.