feat: adaptive quality in desktop, relay quality directive, Oboe state polling

- Wire AdaptiveQualityController into desktop engine send/recv tasks
  (mirrors Android pattern: AtomicU8 pending_profile, auto-mode check)
- Wire same into Android engine send task (was only in recv before)
- QualityDirective SignalMessage variant for relay-initiated codec switch
- ParticipantQuality tracking in relay RoomManager (per-participant
  AdaptiveQualityController, weakest-link tier computation)
- Relay broadcasts QualityDirective to all participants when room-wide
  tier degrades (coordinated codec switching)
- Oboe stream state polling: poll getState() for up to 2s after
  requestStart() to ensure both streams reach Started before proceeding
  (fixes intermittent silent calls on cold start, Nothing Phone A059)

Tasks: #7, #25, #26, #31, #35

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-12 19:54:04 +04:00
parent 766c9df442
commit 22045bc5e6
5 changed files with 266 additions and 10 deletions

View File

@@ -8,6 +8,8 @@
#include <android/log.h>
#include <cstring>
#include <atomic>
#include <chrono>
#include <thread>
#define LOG_TAG "wzp-oboe"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
@@ -388,6 +390,38 @@ int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
return -5;
}
// Log initial stream states right after requestStart() returns.
// On well-behaved HALs both will already be Started; on others
// (Nothing A059) they may still be in Starting state.
LOGI("requestStart returned: capture_state=%d playout_state=%d",
(int)g_capture_stream->getState(),
(int)g_playout_stream->getState());
// Poll until both streams report Started state, up to 2s timeout.
// Some Android HALs (Nothing A059) delay transitioning from Starting
// to Started; proceeding before the transition completes causes the
// first capture/playout callbacks to be dropped silently.
{
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(2000);
int poll_count = 0;
while (std::chrono::steady_clock::now() < deadline) {
auto cap_state = g_capture_stream->getState();
auto play_state = g_playout_stream->getState();
if (cap_state == oboe::StreamState::Started &&
play_state == oboe::StreamState::Started) {
LOGI("both streams Started after %d polls", poll_count);
break;
}
poll_count++;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Log final state even on timeout (helps diagnose HAL quirks)
LOGI("stream states after poll: capture=%d playout=%d (polls=%d)",
(int)g_capture_stream->getState(),
(int)g_playout_stream->getState(),
poll_count);
}
LOGI("Oboe started: sr=%d burst=%d ch=%d",
config->sample_rate, config->frames_per_burst, config->channel_count);
return 0;