|
|
|
|
@ -21,7 +21,8 @@ VoiceAudioIo::~VoiceAudioIo() { stop(); }
|
|
|
|
|
bool VoiceAudioIo::start(Kind kind, uint64_t session) { |
|
|
|
|
stop(); |
|
|
|
|
m_kind = kind; m_session = session; |
|
|
|
|
m_captureEvents.reset(); m_references.reset(); m_playback.reset(); |
|
|
|
|
m_captureEvents.reset(); m_references.reset(); m_playback.reset(); m_playProgress.reset(); |
|
|
|
|
m_progressDropped = 0; |
|
|
|
|
m_playPosition = 960; m_playEpoch = 1; |
|
|
|
|
m_captureDropped = m_referenceDropped = m_playbackUnderruns = 0; |
|
|
|
|
m_running = true; |
|
|
|
|
@ -121,8 +122,10 @@ void VoiceAudioIo::reference(const float* pcm, unsigned frames, int64_t firstTim
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Выход всегда обслуживается независимо от capture/VAD. Уже потреблённый PCM становится референсом у владельца микшера. */ |
|
|
|
|
void VoiceAudioIo::mix(float* stereo, unsigned frames) { |
|
|
|
|
void VoiceAudioIo::mix(float* stereo, unsigned frames, int64_t firstTimeUs) { |
|
|
|
|
uint64_t epoch = m_playEpoch.load(); |
|
|
|
|
if (!firstTimeUs) firstTimeUs = voice_time_us(); |
|
|
|
|
unsigned consumed = 0; |
|
|
|
|
for (unsigned i = 0; i < frames; ++i) { |
|
|
|
|
if (m_playCurrent.epoch != epoch) m_playPosition = 960; |
|
|
|
|
if (m_playPosition == 960) { |
|
|
|
|
@ -135,26 +138,45 @@ void VoiceAudioIo::mix(float* stereo, unsigned frames) {
|
|
|
|
|
stereo[2*i] += m_playCurrent.pcm[2*m_playPosition] / 32768.0f; |
|
|
|
|
stereo[2*i+1] += m_playCurrent.pcm[2*m_playPosition+1] / 32768.0f; |
|
|
|
|
++m_playPosition; |
|
|
|
|
++consumed; |
|
|
|
|
} |
|
|
|
|
if (m_kind == Kind::Radio && consumed) { |
|
|
|
|
PlaybackProgress progress{epoch, m_playCurrent.position + m_playPosition, |
|
|
|
|
firstTimeUs + (int64_t)consumed * 1000000 / 48000}; |
|
|
|
|
if (!m_playProgress.push(progress)) m_progressDropped.fetch_add(1, std::memory_order_relaxed); |
|
|
|
|
} |
|
|
|
|
m_wake.notify_all(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void VoiceAudioIo::playbackLoop() { |
|
|
|
|
uint64_t epoch = m_playEpoch.load(); |
|
|
|
|
uint64_t position = 0; |
|
|
|
|
if (m_kind == Kind::Radio) radio_audio_playback_progress(m_session, 0, 0); |
|
|
|
|
while (m_running) { |
|
|
|
|
uint64_t requested = m_playEpoch.load(); |
|
|
|
|
if (requested != epoch) { |
|
|
|
|
if (m_kind == Kind::Call) call_audio_reset_io(m_session, 1); |
|
|
|
|
else radio_audio_reset_playback(m_session); |
|
|
|
|
position = 0; |
|
|
|
|
epoch = requested; |
|
|
|
|
} |
|
|
|
|
PlaybackProgress progress{}; |
|
|
|
|
for (unsigned count = 0; count < 32 && m_playProgress.pop(progress); ++count) { |
|
|
|
|
if (progress.epoch != epoch) continue; |
|
|
|
|
int64_t remaining = progress.endTimeUs - voice_time_us(); |
|
|
|
|
radio_audio_playback_progress(m_session, progress.position, remaining > 0 ? (remaining + 99) / 100 : 0); |
|
|
|
|
} |
|
|
|
|
unsigned lost = m_progressDropped.exchange(0); |
|
|
|
|
if (lost) DEBUG_WARN(DEBUG_CATEGORY_RADIO, "voice-io: playback progress overflow session=%llu dropped=%u", |
|
|
|
|
(unsigned long long)m_session, lost); |
|
|
|
|
if (m_playback.size() < 2) { |
|
|
|
|
Playback block{}; block.epoch = epoch; |
|
|
|
|
Playback block{}; block.epoch = epoch; block.position = position; |
|
|
|
|
if (m_kind == Kind::Call) { |
|
|
|
|
int16_t mono[960]{}; |
|
|
|
|
int count = std::clamp(call_audio_pull_pcm(m_session, mono, 960), 0, 960); |
|
|
|
|
for (int i = 0; i < count; ++i) block.pcm[2*i] = block.pcm[2*i+1] = mono[i]; |
|
|
|
|
} else radio_audio_pull_pcm(m_session, block.pcm, 1920); |
|
|
|
|
position += 960; |
|
|
|
|
if (!m_playback.push(block)) DEBUG_WARN(DEBUG_CATEGORY_CALL, "voice-io: playback queue contention"); |
|
|
|
|
} else { |
|
|
|
|
std::unique_lock lock(m_waitMutex); |
|
|
|
|
|