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 <noreply@anthropic.com>
- FlowDef.hpp: add make_audio_flow_def (NMOS IS-04 audio/Lnn flow)
- NDIReceiver: replace capture_v210 with capture() — single recv call
dispatches to FrameKind::{Video,Audio,None}; AudioInfo struct carries
sample_rate/channels/samples/channel_stride
- ndiin: optional audio writer when graph wires audio_flow_id; single
loop with 5ms NDI poll; audio grains written immediately on arrival;
video grains written when mxlGetCurrentIndex reaches video_index
- build_graph: rename ndiin video port flow_id → video_flow_id; add
audio_flow_id edge (no sink yet — wired to ndiin only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NDIContext struct handles NDIlib_initialize/send_create/send_destroy/destroy
so NDIlib_destroy() is guaranteed even if send_create fails (was leaked before)
- malloc/free for 10-bit and 16-bit frame buffers → std::vector<uint8_t>
- NDI frame structs point into vector data, no manual lifetime management
- static_cast for FourCC instead of C-style cast
- Tidy: ndi_frame_count replaces ndi_frame_counter, ++prefix form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>