feat: external PCM feed/tap API (vc_stream_feed_pcm + vc_set_pcm_sink)
Promotes vc_test_inject_capture (mono-only, TEST-ONLY) to a public, stereo-capable production API and adds a symmetric PCM tap on the receive side. Enables ReplayKit (iOS), ScreenCaptureKit (macOS), bots, soundboards, and custom clients — all without a hardware audio device. Core C++: - voicecat.h: new vc_stream_feed_pcm, vc_pcm_sink_cb typedef, vc_set_pcm_sink; vc_test_inject_capture kept as deprecated alias - audio_engine: stereo-aware inject_capture (channels param + ring reset on channel-count change); atomic pcm_sink_ fired per decoded frame in on_playback; RemoteStream carries user_id/stream_id for RT-safe sink metadata; init_recv_stream takes user_id+stream_id - client.cpp: stream_feed_pcm / set_pcm_sink implementations; sync_remote_streams passes user_id/stream_id to init_recv_stream - voicecat.cpp: trampolines + channels=1/2 validation Tests: test_external_pcm (headless, 3 sub-tests: mono round-trip, stereo feed L≠R, sink metadata+disable). ctest 23/23. Swift: feedPcm / setPcmSink in VoiceCatClient.swift + 4 XCTest smoke tests (ExternalPcmTests.swift). C#: StreamFeedPcm / SetPcmSink in VoiceCatClient.cs + NativeMethods.cs (vc_stream_feed_pcm unsafe P/Invoke, VcPcmSinkCallback delegate, vc_set_pcm_sink via nint) + 4 xUnit smoke tests (ExternalPcmTests.cs). Docs: architecture.md §4 new subsection, voice.md §9 updated (macOS/iOS now reference vc_stream_feed_pcm), protocol.md §8 explicit no-protocol-change note, roadmap.md M5 entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,20 @@ public sealed class VoiceCatClient : IDisposable
|
||||
return (r, r == VcResult.Ok ? Marshaling.ToManaged(in native) : null);
|
||||
}
|
||||
|
||||
// ── External PCM feed / tap ─────────────────────────────────────────────────────────
|
||||
public unsafe VcResult StreamFeedPcm(uint streamId, ReadOnlySpan<short> pcm,
|
||||
int samplesPerChannel, uint channels)
|
||||
{
|
||||
fixed (short* p = pcm)
|
||||
return NativeMethods.vc_stream_feed_pcm(_handle.DangerousGetHandle(),
|
||||
streamId, p, (nuint)samplesPerChannel, channels);
|
||||
}
|
||||
|
||||
// Pass Marshal.GetFunctionPointerForDelegate(cb) for a managed delegate, or
|
||||
// IntPtr.Zero to disable. Keep the delegate alive for the lifetime of the registration.
|
||||
public VcResult SetPcmSink(nint cb, IntPtr user) =>
|
||||
NativeMethods.vc_set_pcm_sink(_handle.DangerousGetHandle(), cb, user);
|
||||
|
||||
// ── M5: Moderation & admin ───────────────────────────────────────────────────────────
|
||||
public VcResult KickUser(uint userId, string? reason = null) =>
|
||||
NativeMethods.vc_kick_user(_handle.DangerousGetHandle(), userId, reason);
|
||||
|
||||
Reference in New Issue
Block a user