Раздели аудиопотоки и привяжи AEC к фактическому общему выходу
This commit is contained in:
1 parent
d7fb0c83f9
commit
bd082a26bf
30 files changed
+1839
-412
No files matched your search
@@ -82,11 +82,40 @@ static void pump(int milliseconds) {
|
||||
|
||||
static void pulse_recovery(SoundManager* manager, const QString& control) {
|
||||
REQUIRE(manager->context()->backend == ma_backend_pulseaudio);
|
||||
instance.ua = uasync_create(); REQUIRE(instance.ua);
|
||||
REQUIRE(call_audio_init(&instance) == 0 && radio_audio_init(&instance) == 0);
|
||||
auto* call = CallAudioEngine::instance(); auto* radio = RadioAudioEngine::instance();
|
||||
REQUIRE(call->start(42) == 0 && radio->start(7) == 0);
|
||||
QString separate;
|
||||
for (const auto& output : manager->enumeratePlaybackDevices()) {
|
||||
ma_device_id id{};
|
||||
if (manager->deviceId(output.id, &id) && !strcmp(id.pulse, "utun_test_voice")) separate = output.id;
|
||||
}
|
||||
REQUIRE(!separate.isEmpty()); call->setPlaybackDevice(separate);
|
||||
AudioRecorder recorder; recorder.init(); recorder.startRecording();
|
||||
std::vector<int16_t> pcm(48000 * 20, 1000);
|
||||
auto pcmId = manager->playRawPcm(pcm.data(), (int)pcm.size(), 48000, 1);
|
||||
for (int i = 0; i < 100 && recorder.durationMs() < 300; ++i) pump(50);
|
||||
REQUIRE(recorder.durationMs() >= 300 && manager->pcmCursor(pcmId) > 0);
|
||||
QFile routesReady(control + "/routes_ready"); REQUIRE(routesReady.open(QIODevice::WriteOnly)); routesReady.close();
|
||||
for (int i = 0; i < 100 && !QFile::exists(control + "/moved"); ++i) pump(50);
|
||||
REQUIRE(QFile::exists(control + "/moved"));
|
||||
pump(500);
|
||||
int outputs = 0;
|
||||
for (auto* device : devices) if (device->type == ma_device_type_playback) ++outputs;
|
||||
REQUIRE(outputs == 1); // Сервер переместил default stream на voice sink: получился один микшер.
|
||||
qInfo("PulseAudio route move: call/radio/media merged into one output PASS");
|
||||
for (auto* device : devices) if (device->type == ma_device_type_playback) REQUIRE(ma_device_stop(device) == MA_SUCCESS);
|
||||
pump(1000); outputs = 0;
|
||||
uint64_t wantedRoute = UINT64_C(14695981039346656037);
|
||||
for (const char* name = "utun_test_voice"; *name; ++name) { wantedRoute ^= (unsigned char)*name; wantedRoute *= UINT64_C(1099511628211); }
|
||||
bool foundWanted = false;
|
||||
for (auto* device : devices) if (device->type == ma_device_type_playback) {
|
||||
++outputs;
|
||||
if (static_cast<AudioDevice*>(device->pUserData)->route() == wantedRoute) foundWanted = true;
|
||||
}
|
||||
REQUIRE(foundWanted && outputs >= 1 && outputs <= 2); // Сервер может восстановить прежний move для default stream.
|
||||
qInfo("PulseAudio output recovery: desired call route preserved outputs=%d PASS", outputs);
|
||||
int duration = recorder.durationMs();
|
||||
auto cursor = manager->pcmCursor(pcmId);
|
||||
QFile ready(control + "/ready"); REQUIRE(ready.open(QIODevice::WriteOnly)); ready.close();
|
||||
@@ -95,9 +124,12 @@ static void pulse_recovery(SoundManager* manager, const QString& control) {
|
||||
QFile closed(control + "/closed"); REQUIRE(closed.open(QIODevice::WriteOnly)); closed.close();
|
||||
for (int i = 0; i < 150 && (!manager->context() || recorder.durationMs() <= duration + 200); ++i) pump(50);
|
||||
REQUIRE(manager->context() && recorder.durationMs() > duration + 200 && manager->pcmCursor(pcmId) > cursor);
|
||||
REQUIRE(call->isActive() && call->callId() == 42 && radio->isActive() && radio->groupId() == 7);
|
||||
qInfo("PulseAudio progress: capture=%d -> %dms playback=%llu -> %llu frames", duration, recorder.durationMs(),
|
||||
(unsigned long long)cursor, (unsigned long long)manager->pcmCursor(pcmId));
|
||||
recorder.stopRecording(); manager->shutdown();
|
||||
call->stop(); radio->stop(); recorder.stopRecording();
|
||||
call_audio_destroy(&instance); radio_audio_destroy(&instance); uasync_destroy(instance.ua, 0); instance.ua = nullptr;
|
||||
manager->shutdown();
|
||||
REQUIRE(devices.empty());
|
||||
qInfo("real PulseAudio: capture/playback progress restored after server restart PASS");
|
||||
}
|
||||
@@ -144,6 +176,12 @@ int main(int argc, char** argv) {
|
||||
REQUIRE(a.pcm[0] == 1000 && b.pcm[0] == 1000); // pUserData маршрутизирует каждый callback своему recorder.
|
||||
a.pcm_release(a.pcm_owner); b.pcm_release(b.pcm_owner);
|
||||
}
|
||||
{
|
||||
AudioRecorder meter; meter.init(); meter.startRecording(true); pump(60); meter.stopRecording();
|
||||
attachment_send_req request{};
|
||||
REQUIRE(meter.durationMs() > 0 && !meter.takeRecording(&request));
|
||||
REQUIRE(!request.pcm && !request.pcm_owner && !request.compressor);
|
||||
}
|
||||
instance.ua = uasync_create(); REQUIRE(instance.ua);
|
||||
REQUIRE(call_audio_init(&instance) == 0 && radio_audio_init(&instance) == 0);
|
||||
auto* call = CallAudioEngine::instance();
|
||||
@@ -151,7 +189,7 @@ int main(int argc, char** argv) {
|
||||
pump(60);
|
||||
ma_device* playback = nullptr;
|
||||
for (auto* device : devices)
|
||||
if (device->type == ma_device_type_playback && device->playback.format == ma_format_s16 && device->playback.channels == 1)
|
||||
if (device->type == ma_device_type_playback && device->playback.format == ma_format_f32 && device->playback.channels == 2)
|
||||
playback = device;
|
||||
REQUIRE(playback);
|
||||
int previousCapture = captureStarts, previousPlayback = playbackStarts;
|
||||
@@ -203,10 +241,22 @@ int main(int argc, char** argv) {
|
||||
manager->pausePcm(player24);
|
||||
cursor24 = manager->pcmCursor(player24);
|
||||
pump(80);
|
||||
REQUIRE(manager->pcmCursor(player24) == cursor24 && manager->pcmCursor(player48) > cursor48);
|
||||
uint64_t pausedAfter = manager->pcmCursor(player24), neighbourAfter = manager->pcmCursor(player48);
|
||||
qInfo("PCM pause: paused=%llu->%llu neighbour=%llu->%llu", (unsigned long long)cursor24, (unsigned long long)pausedAfter,
|
||||
(unsigned long long)cursor48, (unsigned long long)neighbourAfter);
|
||||
REQUIRE(pausedAfter == cursor24 && neighbourAfter > cursor48);
|
||||
REQUIRE(manager->seekPcm(player24, 12000));
|
||||
REQUIRE(manager->pcmCursor(player24) == 12000 && !manager->isPcmPlaying(player24));
|
||||
manager->resumePcm(player24);
|
||||
// Пауза синхронизируется с уже начатым чтением графа, сосед продолжает играть.
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
pump(1);
|
||||
manager->pausePcm(player24);
|
||||
uint64_t paused = manager->pcmCursor(player24);
|
||||
pump(1);
|
||||
REQUIRE(manager->pcmCursor(player24) == paused);
|
||||
manager->resumePcm(player24);
|
||||
}
|
||||
manager->stopPcm(player48);
|
||||
pump(60);
|
||||
REQUIRE(manager->pcmCursor(player24) > 12000 && manager->pcmCursor(player48) == 0);
|
||||
|
||||
Reference in new issue
Block a user