From aa39a821dbe3b0c63c18e031905457775e338b5d Mon Sep 17 00:00:00 2001 From: JohannesItten Date: Wed, 1 Jul 2026 12:56:55 +0300 Subject: [PATCH] fix: use continuous sample API for audio flow in ndiin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audio flows in MXL use mxlFlowWriterOpenSamples/CommitSamples, not the grain API (mxlFlowWriterOpenGrain is for discrete video flows). Using the grain API silently did nothing — hence "last published index: 0". MXL audio is float32 planar: each channel occupies its own ring buffer region accessed at base.fragments[*].pointer + c * slice.stride. Two fragments handle ring buffer wraparound. No int32 conversion needed. Also log continuous.channelCount/bufferLength instead of the discrete sliceSizes field (wrong union member for audio). Co-Authored-By: Claude Sonnet 4.6 --- nodes/ndiin/main.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/nodes/ndiin/main.cpp b/nodes/ndiin/main.cpp index 54d395d..efc2f74 100644 --- a/nodes/ndiin/main.cpp +++ b/nodes/ndiin/main.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -75,7 +74,8 @@ class NDIInNode : public dmf::NodeBase { log("audio mxlCreateFlowWriter failed (status=%d) — continuing without audio", ast); has_audio = false; } else { - log("audio grain=%u B", audio_cfg.discrete.sliceSizes[0]); + log("audio channels=%u buffer=%u samples", + audio_cfg.continuous.channelCount, audio_cfg.continuous.bufferLength); } } @@ -104,22 +104,25 @@ class NDIInNode : public dmf::NodeBase { if (kind == dmf::NDIReceiver::FrameKind::Video) { have_video = true; } else if (kind == dmf::NDIReceiver::FrameKind::Audio && has_audio) { - mxlGrainInfo grain{}; - uint8_t* buf = nullptr; - if (mxlFlowWriterOpenGrain(audio_writer, audio_index, &grain, &buf) == MXL_STATUS_OK) { - // Convert float32 planar → int32 interleaved PCM - const int audio_grain_size = audio_cfg.discrete.sliceSizes[0]; - const int capacity = audio_grain_size / (channels * static_cast(sizeof(int32_t))); - const int to_write = std::min(audio_info.samples, capacity); - auto* out = reinterpret_cast(buf); - for (int s = 0; s < to_write; ++s) - for (int c = 0; c < audio_info.channels; ++c) { - float f = audio_buf[c * audio_info.channel_stride + s]; - f = std::max(-1.0f, std::min(1.0f, f)); - *out++ = static_cast(f * 2147483647.0f); + mxlMutableWrappedMultiBufferSlice slice{}; + if (mxlFlowWriterOpenSamples(audio_writer, audio_index, + static_cast(audio_info.samples), &slice) == MXL_STATUS_OK) { + // MXL audio is float32 planar: each channel occupies its own ring buffer + // region, accessed at base + c * stride. Fragments handle ring wraparound. + const size_t frag0 = slice.base.fragments[0].size / sizeof(float); + const size_t frag1 = slice.base.fragments[1].size / sizeof(float); + for (int c = 0; c < audio_info.channels; ++c) { + const float* src = &audio_buf[c * audio_info.channel_stride]; + auto* dst0 = reinterpret_cast( + static_cast(slice.base.fragments[0].pointer) + c * slice.stride); + std::memcpy(dst0, src, frag0 * sizeof(float)); + if (frag1 > 0) { + auto* dst1 = reinterpret_cast( + static_cast(slice.base.fragments[1].pointer) + c * slice.stride); + std::memcpy(dst1, src + frag0, frag1 * sizeof(float)); } - grain.validSlices = grain.totalSlices; - mxlFlowWriterCommitGrain(audio_writer, &grain); + } + mxlFlowWriterCommitSamples(audio_writer); } audio_index += audio_info.samples; }