diff --git a/src/call/call_audio.h b/src/call/call_audio.h index fdf7e949..73e393ce 100644 --- a/src/call/call_audio.h +++ b/src/call/call_audio.h @@ -57,8 +57,8 @@ int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count, int64_t int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask, int64_t capture_pts_us); int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted, int64_t capture_pts_us); -/* аудио-поток: выдать до max_samples финального PCM (jitter + stretch + тоны). - * Возвращает число сэмплов (может быть < max — андерфлоу/конец тона). */ +/* аудио-поток: выдать max_samples финального PCM (jitter + stretch + тоны + тишина). + * Активный движок всегда выдаёт полный блок; 0 означает отсутствие движка/ошибку аргументов. */ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples); /* uasync-поток (из call.c): принятый Opus-кадр — кладём в джиттер-буфер (call_jitter_push, diff --git a/tools/chatgui/tests/test_voice_audio_io.cpp b/tools/chatgui/tests/test_voice_audio_io.cpp index b3b7e2cc..8e3ceef5 100644 --- a/tools/chatgui/tests/test_voice_audio_io.cpp +++ b/tools/chatgui/tests/test_voice_audio_io.cpp @@ -111,10 +111,21 @@ int main() { REQUIRE(masks.size() == 960); for (int i = 0; i < 960; ++i) REQUIRE(masks[i] == (i >= 480) && capture[i] == 1234); voice.stop(); reset(); + pulls = 0; output_position = 0; + REQUIRE(voice.start(VoiceAudioIo::Kind::Call, 42)); + await([] { return pulls.load() >= 2; }); + REQUIRE(output_position == 0); + voice.mix(partial, 240, now_us() + 500000, true); + await([] { return output_position.load() == 240; }); + REQUIRE(output_delay > 4500); + voice.mix(partial, 720, now_us(), true); + await([] { return output_position.load() == 960; }); + voice.stop(); voice.start(VoiceAudioIo::Kind::Call, 42); REQUIRE(voice.startCapture(1, false)); - voice.capture(pcm.data(), 960, now_us() - 300000); // Backend latency не является возрастом worker-очереди. + int64_t capturePts = now_us() - 300000; + voice.capture(pcm.data(), 960, capturePts); // Backend latency не является возрастом worker-очереди. await([] { return blocks.load() == 1; }); voice.stopCapture(); voice.stop(); - REQUIRE(capture.size() == 960); reset(); + REQUIRE(capture.size() == 960 && capture_time == capturePts); reset(); // Референс противофазный: mono downmix дал бы ноль и не смог подавить эхо. voice.start(VoiceAudioIo::Kind::Call, 43); REQUIRE(voice.startCapture(1, true)); uint32_t rng = 1234567; diff --git a/tools/chatgui/tests/test_voice_tx.cpp b/tools/chatgui/tests/test_voice_tx.cpp index 501000ac..5c19319c 100644 --- a/tools/chatgui/tests/test_voice_tx.cpp +++ b/tools/chatgui/tests/test_voice_tx.cpp @@ -579,6 +579,14 @@ int main() { uasync_poll(instance.ua, 0); encoder = opus_codec_encoder_create(48000, 1); REQUIRE(encoder); tail.fill(0); memcpy(tail.data(), input.data(), 300 * sizeof(int16_t)); check_packet(encoder, tail.data(), 0); + int delay = 0; REQUIRE(opus_codec_encoder_delay(encoder, &delay) == 0); + REQUIRE(last_call_timing.pts_us == 123456789 && last_call_timing.epoch == 1 && last_call_timing.delay_samples == (unsigned)delay); + REQUIRE(call_audio_feed_prepared_pcm(42, input.data(), mask.data(), 123476789) == 0); + uasync_poll(instance.ua, 0); + REQUIRE(last_call_timing.pts_us == 123476789 && last_call_timing.epoch == 1); + REQUIRE(call_audio_feed_prepared_pcm(42, input.data(), mask.data(), 124000000) == 0); + uasync_poll(instance.ua, 0); + REQUIRE(last_call_timing.pts_us == 124000000 && last_call_timing.epoch == 2); call_audio_stop(); opus_codec_encoder_destroy(encoder); call_audio_destroy(&instance); radio_audio_destroy(&instance); uasync_destroy(instance.ua, 0); puts("voice TX: exact Opus tail, rapid bursts, discontinuity, capture policy, final mute PASS");