Complete managed macOS platform bring-up
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / apple-client (push) Canceled after 0s
.NET port / cpp-conformance (push) Canceled after 0s

This commit is contained in:
2026-09-18 18:51:53 +02:00
parent ab460ae12b
commit 310c0f09dd
21 changed files with 421 additions and 83 deletions
+21 -11
View File
@@ -566,13 +566,19 @@ void AudioEngine::init_recv_stream(uint32_t ssrc, const codec::OpusParams& p,
}
void AudioEngine::set_pcm_sink(PcmSink cb, void* user) {
pcm_sink_user_.store(user, std::memory_order_relaxed);
pcm_sink_.store(cb, std::memory_order_release);
std::lock_guard setter_lk(pcm_sink_set_mu_);
pcm_sink_.store(nullptr);
while (pcm_sink_active_.load() != 0) std::this_thread::yield();
pcm_sink_user_.store(user);
pcm_sink_.store(cb);
}
void AudioEngine::set_mixed_output_sink(MixedSink cb, void* user) {
mixed_sink_user_.store(user, std::memory_order_relaxed);
mixed_sink_.store(cb, std::memory_order_release);
std::lock_guard setter_lk(mixed_sink_set_mu_);
mixed_sink_.store(nullptr);
while (mixed_sink_active_.load() != 0) std::this_thread::yield();
mixed_sink_user_.store(user);
mixed_sink_.store(cb);
}
void AudioEngine::capture_data_cb(ma_device* dev, void* /*out*/,
@@ -734,17 +740,19 @@ void AudioEngine::on_playback(int16_t* out, ma_uint32 frames) {
}
// PCM sink: deliver decoded per-stream audio to external consumer (bots,
// transcription, recording) before it enters the hardware mix. Atomic relaxed-
// load is safe on the RT thread — the fn-ptr and user-ptr are independent
// pointer-sized values written together by set_pcm_sink (release store).
if (auto sink = pcm_sink_.load(std::memory_order_relaxed)) {
sink(pcm_sink_user_.load(std::memory_order_relaxed),
// transcription, recording) before it enters the hardware mix. The reader count
// makes disabling the callback a quiescence barrier while keeping this RT path
// lock-free and non-blocking.
pcm_sink_active_.fetch_add(1);
if (auto sink = pcm_sink_.load()) {
sink(pcm_sink_user_.load(),
stream.user_id, stream.stream_id,
stream.decode_scratch.data(),
static_cast<size_t>(n),
static_cast<uint32_t>(dec_channels),
params_.sample_rate);
}
pcm_sink_active_.fetch_sub(1);
stream.push_ring(stream.decode_scratch.data(), static_cast<size_t>(n));
stream.playout_ts += static_cast<uint32_t>(n);
@@ -797,10 +805,12 @@ void AudioEngine::run_mixer_timer() {
auto next = clock::now();
while (!mixer_timer_stop_.load(std::memory_order_acquire)) {
on_playback(mixer_scratch_.data(), spc);
if (auto sink = mixed_sink_.load(std::memory_order_relaxed)) {
sink(mixed_sink_user_.load(std::memory_order_relaxed), mixer_scratch_.data(), spc, ch,
mixed_sink_active_.fetch_add(1);
if (auto sink = mixed_sink_.load()) {
sink(mixed_sink_user_.load(), mixer_scratch_.data(), spc, ch,
params_.sample_rate);
}
mixed_sink_active_.fetch_sub(1);
next += period;
// If we fell badly behind (e.g. the thread was descheduled), reset the deadline rather
// than spin to catch up — the VPIO renderer rides its own clock + jitter ring.
+8 -2
View File
@@ -514,16 +514,22 @@ class AudioEngine {
int frame_samples_ = 960; // 20 ms @48 kHz
// External PCM tap: atomic fn-ptr + user-ptr pair. Written by set_pcm_sink (any thread);
// read by on_playback (RT thread) via relaxed load — safe for pointer-sized atomics.
// External PCM tap. The in-flight counter lets set_pcm_sink(nullptr, nullptr) wait for an
// already-entered callback without making the RT thread lock or block. Sequentially
// consistent operations are intentional here: once the setter observes zero readers, a
// later reader must observe the disabled callback before it can dereference the old user.
std::atomic<PcmSink> pcm_sink_{nullptr};
std::atomic<void*> pcm_sink_user_{nullptr};
std::atomic<uint32_t> pcm_sink_active_{0};
std::mutex pcm_sink_set_mu_;
// External mixed-output sink + mode flag (iOS VPIO). mixed_sink_ is written by
// set_mixed_output_sink (any thread); read by run_mixer_timer via relaxed load.
// external_playback_ is read in start() to gate hardware-playback-device creation.
std::atomic<MixedSink> mixed_sink_{nullptr};
std::atomic<void*> mixed_sink_user_{nullptr};
std::atomic<uint32_t> mixed_sink_active_{0};
std::mutex mixed_sink_set_mu_;
bool external_playback_ = false;
::OpusDREDDecoder* dred_dec_ = nullptr; // shared DRED decoder; null if unsupported