Реализация мульти-камерного стриминга с мобильного устройства
Одновременная съёмка с фронтальной и основной камеры — задача, которая стала технически реализуемой на 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 недели из-за фрагментации устройств. Стоимость рассчитывается индивидуально после анализа требований.







