Реалізація двустороннього аудіо з IoT-пристроєм через мобільний додаток
Домофон, відеоняня, переговорне пристрій — спільний знаменник: телефон чує пристрій і одночасно говорит в нього. На відміну від звичайного VoIP-звонку між двома телефонами, тут одна сторона — вбудований Linux-мікрокомп'ютер (ESP32, Raspberry Pi, NXP i.MX), який не вміє в SIP-стек або WebRTC без додаткового ПО. Це змінює вибір архітектури.
WebRTC — основний вибір для мінімальної затримки
Затримка туди-обратно (RTT) для розбірливої мови — не вище 300-400 мс. HLS та RTMP не підходять. SIP можливий, але навантажен протокольним overhead. WebRTC створювався саме для цього сценарію.
Сторона IoT-пристроя: libwebrtc на Linux або спеціалізовані рішення: aiortc (Python), Pion (Go), GStreamer з плагіном webrtcbin. Pion — мініималістичний та легко деплоїться на Raspberry Pi. GStreamer webrtcbin — якщо пристрій уже використовує GStreamer для відео-пайпліну.
Сторона мобільного додатку:
iOS: GoogleWebRTC (pod 'WebRTC-SDK') або нативний WebRTCFramework. Створюємо RTCPeerConnection з аудіотреком:
let audioConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
let audioSource = factory.audioSource(with: audioConstraints)
let audioTrack = factory.audioTrack(with: audioSource, trackId: "audio0")
peerConnection.add(audioTrack, streamIds: ["stream0"])
RTCAudioSession налаштовуємо на .voiceChat категорію — автоматично включає еховідтискання та подавлення фонового шуму (AEC/NS), вбудовані в WebRTC.
Android: io.getstream:stream-webrtc-android або офіційний WebRTC від Google. AudioManager.MODE_IN_COMMUNICATION — обов'язковий для правильної маршрутизації аудіо (earpiece/speakerphone).
Flutter: flutter_webrtc. Налаштування mediaConstraints для аудіо:
final Map<String, dynamic> mediaConstraints = {
'audio': {
'echoCancellation': true,
'noiseSuppression': true,
'autoGainControl': true,
}
};
Еховідтискання: головна біль двустороннього аудіо
Без AEC (Acoustic Echo Cancellation): мікрофон телефона улавлює звук з динаміка (або навпаки — пристрій чує сам себе) — користувач чує еховідпис з затримкою 200 мс. Непригодно для використання.
WebRTC містить вбудований AEC3 (третє поколення). Працює автоматично при правильній категорії аудіосесії. Проблема виникає коли:
- Пристрій IoT не підтримує echo reference path — тоді AEC на боці пристрою неефективна. Рішення: переносимо AEC на сторону сервера (медіасервер з включеним processing).
- Bluetooth-гарнітура + WebRTC — на Android
AudioManagerв режиміCOMMUNICATIONпереключає профіль BT на HFP (вузькосмугови 8 кГц). Для широкосмугового аудіо потрібен A2DP, але він не підтримує запис. Компромісс: або низька якість з BT, або AirPods/wired headphones.
SIP як альтернатива
Якщо IoT-пристрій підтримує SIP (багато IP-домофонів: Grandstream, Panasonic, Commax), то на мобільному використовуємо SIP-клієнт.
iOS: PJSIP (C-бібліотека) з Swift-обгорткою або Linphone SDK. Android: MjSip або той же PJSIP через JNI, або готовий Linphone SDK for Android. Flutter: sip_ua (Dart SIP, працює через WebSocket-транспорт).
SIP на мобільному потребує реєстрації на Asterisk/FreeSWITCH сервері. Звонок з домофона → SIP INVITE → сервер → push-уведомлення на телефон (через CallKit на iOS, ConnectionService/IncomingCallNotification на Android). Без push — уведомлення не приходит при закритому додатку.
CallKit (iOS): входящий звонок виглядає як звичайний телефонний звонок — повноекранний інтерфейс з ім'ям домофона. CXProvider, CXCallUpdate — стандартна інтеграція. Обов'язковий voip Background Mode в Info.plist + APNs VoIP-сертифікат.
Android ConnectionService: аналог CallKit. TelecomManager.addNewIncomingCall() — показує системний інтерфейс входящого вклику. Працює з Android 6+.
Шум окремого й агресивне шумопотискання
На вулиці вітер, стройка рядом — IoT-пристрій відправляє зашумлений поток. Додаткове шумопотискання: RTCRtpSender з RTCDefaultVideoEncoderFactory — аудіо-тільки. WebRTC RNNoise інтегрований в нативний WebRTC і включається через AudioProcessing::Config::NoiseSuppression.
Для серйозної обробки на сервері: Janus з модулем janus_audiobridge.janus_plugin застосовує шумопотискання перед мішуванням.
Тестування
Головна проблема: відтворити NAT traversal у тестовій середовищі. Використовуємо Coturn в Docker для локального тестування TURN. Тест на симетричному NAT (корпоративна мережа з жорсткими правилами) — обов'язковий. Без TURN-сервера приблизно 15-20% з'єднань не встановляться.
Строки: двустороннє WebRTC-аудіо з IoT-пристроєм (Linux/Pion) + iOS або Android клієнт — 5-7 робочих днів. З SIP інтеграцією та CallKit — 8-12 днів.







