Реалізація мульти-камерного стримінга з мобільного пристрою
Одночасна съємка з фронтальної та основної камери — задача, яка стала технічно реалізуємою на iOS з появою AVCaptureMultiCamSession в iOS 13. До цього будь-який «мульти-камерний» стрім був симуляцією: переключення з затримкою або заздалегідь записаний другий джерело. Тепер на iPhone XS та новіше можна захопити обидва потоки одночасно — з обмеженнями, про які нижче.
Архітектурні обмеження, які потрібно знати до початку
AVCaptureMultiCamSession підтримується не на всіх пристроях. Перед ініціалізацією обов'язкова перевірка:
guard AVCaptureMultiCamSession.isMultiCamSupported else {
// fallback на AVCaptureSession з однією камерою
return
}
При активній мульти-кам сесії максимальне розширення кожної камери знижується — на iPhone 13 Pro можна отримати максимум 1920×1440 з основної та 1280×960 з фронтальної одночасно. Спроба встановити 4K на обох приводить до AVCaptureSessionRuntimeErrorNotification з кодом AVError.outOfMemory. В продакшні — фіксуємо 1280×720 для обох, чого достатньо для стрима.
Тепловий режим. Одночасна робота двох ISP (Image Signal Processor) і GPU-композиція нагрівають пристрій швидко. На iPhone 12 mini при 30-хвилинному стримі з двох камер срабатує thermal throttling, і система принудово знижує framerate до 20fps. Рішення — моніторити ProcessInfo.ThermalState і при .serious переключатися на одну камеру.
Android з Camera2 API: одночасна съємка підтримується через CameraManager.getCameraCharacteristics + LOGICAL_MULTI_CAMERA або явне відкриття двох фізичних камер. На практиці підтримка залежить від OEM — Samsung Galaxy S22+ відає два потоки, бюджетні Xiaomi можуть повернути ERROR_CAMERA_IN_USE. Перед релізом обов'язкове тестування на цільовому парку пристроїв.
Як будуємо пайплайн
На iOS схема: AVCaptureMultiCamSession → два AVCaptureDeviceInput → два AVCaptureVideoDataOutput → Metal-композитор → VideoToolbox-енкодер → RTMP/SRT.
Ключовий момент — композиція. Два відео потоки не можна напряму відати в один енкодер. Потрібно мішати кадри через MTKView або CIFilter. Використовуємо Metal з кастомним шейдером: основна камера займає full-frame, фронтальна рендерується в PiP-прямокутник у кутку.
// Отримуємо CMSampleBuffer від кожної камери в різних чергах
let backQueue = DispatchQueue(label: "back.camera")
let frontQueue = DispatchQueue(label: "front.camera")
backOutput.setSampleBufferDelegate(self, queue: backQueue)
frontOutput.setSampleBufferDelegate(self, queue: frontQueue)
// Синхронізуємо через AVCaptureDataOutputSynchronizer
let synchronizer = AVCaptureDataOutputSynchronizer(
dataOutputs: [backOutput, frontOutput]
)
synchronizer.setDelegate(self, queue: syncQueue)
AVCaptureDataOutputSynchronizer — обов'язковий елемент. Без нього кадри з двох камер приходять з рассинхронізацією до 33ms (один кадр при 30fps), й у PiP-вікні видно «дёрганье» відносно основного потока.
Для SRT-трансляції (як більш стабільної альтернативи RTMP на мобільному) — використовуємо libsrt, скомпільовану під iOS/Android, або HaishinKit 2.x з вбудованою SRT-підтримкою.
Управління PiP-позицією під час стрима
Позицію PiP-вікна робимо перетаскуваною через UIPanGestureRecognizer з привязкою до кутів через snap-анімацію. Координати зберігаємо в UserDefaults — користувач не повинен переставляти кожний раз.
При переключенні орієнтації Metal-шейдер отримує нові координати PiP автоматично через CADisplayLink, який пересчитує layout на кожному кадрі.
Типічні помилки
- Не додавати
AVCaptureMultiCamSessionу фоновий режим (UIBackgroundModes: audio) — при сворачиванні додатка iOS убне сесію через 30 секунд - Ігнорувати
sessionWasInterruptedпри вхідному дзвінку — потрібно приостановлювати стрім і возобновляти вsessionInterruptionEnded - Використовувати
DispatchQueue.mainдля обробкиCMSampleBuffer— декодування і Metal-рендеринг на головному потоці дропають UI на 8–12ms на кожний кадр
Терміни
iOS-реалізація з Metal-композитором, PiP, SRT/RTMP-трансляцією й тестами на тепловий режим: 4–6 тижнів. Android з Camera2 API — плюс 2–3 тижні з-за фрагментації пристроїв. Вартість розраховується індивідуально після аналізу вимог.







