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:
@@ -98,6 +98,25 @@ internal static partial class NativeMethods
|
||||
internal static unsafe partial VcResult vc_test_inject_capture(nint c, uint streamId,
|
||||
short* pcm, nuint samples);
|
||||
|
||||
// ── External PCM feed / tap ──────────────────────────────────────────────────────────
|
||||
|
||||
[LibraryImport(LibName)]
|
||||
internal static unsafe partial VcResult vc_stream_feed_pcm(nint c, uint streamId,
|
||||
short* pcm, nuint samplesPerChannel, uint channels);
|
||||
|
||||
// Delegate type for the PCM sink callback — callers convert to a native function
|
||||
// pointer via Marshal.GetFunctionPointerForDelegate (for instance members) or by casting
|
||||
// a static lambda to delegate* unmanaged<> (for [UnmanagedCallersOnly] statics).
|
||||
// Keep the delegate alive for the lifetime of the sink registration.
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void VcPcmSinkCallback(IntPtr user, uint userId, uint streamId,
|
||||
IntPtr pcm, nuint samplesPerChannel, uint channels, uint sampleRate);
|
||||
|
||||
// cb is a raw function pointer (IntPtr.Zero = disable). Use
|
||||
// Marshal.GetFunctionPointerForDelegate(sinkDelegate) to convert from VcPcmSinkCallback.
|
||||
[LibraryImport(LibName)]
|
||||
internal static partial VcResult vc_set_pcm_sink(nint c, nint cb, IntPtr user);
|
||||
|
||||
// ── Text ─────────────────────────────────────────────────────────────────────────────
|
||||
[LibraryImport(LibName, StringMarshalling = StringMarshalling.Utf8)]
|
||||
internal static partial VcResult vc_send_text(nint c, VcTextScope scope, uint targetId,
|
||||
|
||||
Reference in New Issue
Block a user