При попытке реализовать одновременную запись с фронтальной и задней камеры разработчики сталкиваются с рядом неочевидных проблем: AVCaptureMultiCamSession молча возвращает ошибку, если превышен лимит hardwareCost, а на Android перечень логических мультикамер ограничен производителем. Без грамотной архитектуры мультикамерная сессия либо не запустится, либо приведёт к падению производительности. В проекте для логистической компании мы столкнулись с необходимостью записи одновременно с водителем (фронтальная камера) и дорогой (задняя). Пришлось учесть аппаратные ограничения и обеспечить graceful degradation для старых устройств.
Почему мультикамерная съемка сложнее, чем кажется?
На iOS AVCaptureMultiCamSession доступен только на устройствах с A12 и новее — это первое, что проверяем через isMultiCamSupported. На старых устройствах делаем graceful degradation: только одна камера или последовательная запись. На Android мультикамерность через Camera2 API требует проверки CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_TYPE и списка физических камер через getPhysicalCameraIds(). Одновременный захват с двух физических камер доступен с Android 9 (API 28), но реальная поддержка сильно варьируется. Например, Samsung и Pixel работают стабильно, а некоторые MediaTek-устройства имеют ограничения.
Сравнение iOS и Android
| Критерий |
iOS (AVCaptureMultiCamSession) |
Android (Camera2) |
| Минимальная версия |
A12+ (iPhone XS) |
API 28 (Android 9) |
| Пиковая нагрузка (1080p/30fps) |
hardwareCost 0.9–1.0 |
зависит от SoC |
| Системная обратная связь |
systemPressureStateNotification |
CaptureResult.STATISTICS_SCENE_FLICKER |
| PiP в реальном времени |
через AVCaptureVideoDataOutput |
через ImageReader + OpenGL |
| Требуется ли проверка? |
isMultiCamSupported |
LOGICAL_MULTI_CAMERA + getPhysicalCameraIds() |
Как решить проблему перегрева при мультикамерной съемке?
AVCaptureMultiCamSession потребляет значительно больше энергии, чем одиночная сессия. На iPhone 12 mini hardwareCost при двух потоках в 1080/30fps может достигать 1.0 — превышение приведёт к тому, что startRunning() молча вернёт ошибку. Решение: уменьшаем разрешение одной из камер до 720p или снижаем frameRate до 24fps. Подписываемся на systemPressureStateNotification, при уровне .critical снижаем frameRate до 15fps, при .shutdown останавливаем запись. Такой подход эффективнее, чем попытка эмулировать мультикамеру через отдельные сессии — нагрузка на процессор снижается, а стабильность растёт.
Пиковый сценарий: Picture-in-Picture при записи — разработка мультикамерной съемки
Два видеопотока записываются в отдельные файлы. Для финального PiP-видео используем AVMutableComposition: основной поток на весь экран, второй — масштабируем через AVMutableVideoCompositionLayerInstruction.setTransform() в угол. AVVideoCompositionInstruction с двумя layerInstructions собирает итоговый файл.
Как реализовать мультикамерную съемку на iOS?
let session = AVCaptureMultiCamSession()
// Задняя камера
let backInput = try AVCaptureDeviceInput(device: backCamera)
let backOutput = AVCaptureMovieFileOutput()
let backConnection = AVCaptureMultiCamSession.Connection(
inputPort: backInput.ports[0], output: backOutput)
// Фронтальная камера
let frontInput = try AVCaptureDeviceInput(device: frontCamera)
let frontOutput = AVCaptureMovieFileOutput()
session.addInputWithNoConnections(backInput)
session.addOutput(backOutput)
session.addInputWithNoConnections(frontInput)
session.addOutput(frontOutput)
session.addConnection(backConnection)
Важно: hardwareCost и systemPressureCost сессии могут превысить допустимый предел. Уменьшаем разрешение одной из камер до 720p, если hardwareCost > 1.0. Иначе startRunning() завершится с ошибкой без явного сообщения.
Что делать, если устройство не поддерживает мультикамеру?
Если проверка isMultiCamSupported возвращает false, используем альтернативную архитектуру: запись поочерёдно с одной камеры, а затем монтаж. На iOS можно применить AVCaptureSession без мультикамерного режима, на Android — стандартную сессию с одной камерой. Это снижает нагрузку и гарантирует стабильность на старых устройствах. Такой подход мы применяли в проектах для логистических компаний, где требовалась запись одновременно с водителем и дорогой — в итоге получили стабильную работу на 90% устройств.
Реализация на Android (Kotlin)
val cameraManager = getSystemService(CameraManager::class.java)
val multiCameraId = cameraManager.cameraIdList.firstOrNull { id ->
val chars = cameraManager.getCameraCharacteristics(id)
chars.get(REQUEST_AVAILABLE_CAPABILITIES)
?.contains(REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) == true
}
Открываем CameraDevice, создаём CaptureSession с поверхностями обеих камер. Для CameraX — CameraSelector с физическим ID через CameraSelector.Builder().addCameraFilter().
Сравнение устройств по совместимости
| Модель |
iOS |
Android |
Комментарий |
| iPhone 12 Pro |
AVCaptureMultiCamSession |
— |
Стабильно работает |
| Samsung Galaxy S21 |
— |
Logical multi-camera |
Рекомендуется |
| Xiaomi Mi 11 |
— |
Logical multi-camera |
Возможны ограничения |
| Google Pixel 5 |
— |
Logical multi-camera |
Хорошая поддержка |
Flutter: когда нужна кросс-платформа
Прямой мультикамерный захват через пакет camera не поддерживается. Решение — MethodChannel + нативная реализация на Swift/Kotlin, которую передаём во Flutter через Texture виджет. Такой подход требует больше времени на разработку, но даёт полный контроль над ресурсами.
Процесс работы
-
Аналитика: определяем целевые устройства и требования к качеству видео.
-
Проектирование: выбираем стек (нативный/кросс-платформа) и архитектуру (раздельные потоки или PiP).
-
Реализация: пишем код с учётом аппаратных ограничений и graceful degradation.
- Тестирование: проверяем на реальных устройствах (минимум 5 моделей) в разных условиях освещения и температуры.
- Деплой: настраиваем provisioning profiles, загружаем в App Store Connect и Google Play Console.
Сроки и что входит
Срок реализации базовой версии (iOS + Android) — от 3 до 5 дней. PiP-композиция финального видео добавляет ещё 1–2 дня. В стоимость входит:
- Исходный код с комментариями
- Документация по интеграции
- Настройка Code Signing и provisioning profiles
- Проверка на 5 реальных устройствах
- Консультация после запуска
Более 5 лет опыта в мобильной разработке и 50+ реализованных проектов с мультикамерной съемкой гарантируют стабильную работу приложения. Получите консультацию по вашему проекту — свяжитесь с нами для технической оценки. Apple Developer Documentation: AVCaptureMultiCamSession
Как выбрать подход к камере на мобильных платформах
Приложения, где пользователи снимают, слушают или смотрят, технически одни из самых требовательных. Мы сталкиваемся с этим каждый день. Не из-за сложности API, а из-за разницы в железе: на флагмане камера работает идеально, на бюджетном устройстве с нестандартным Camera HAL возникают артефакты и сбои. На iOS стабилизация одного поколения отличается от другого. Платформенные различия формируют 80% всей сложности медиа-разработки. Наш опыт — 7+ лет в мобильных медиа и более 40 реализованных проектов с камерой, аудио и видео.
CameraX против Camera2 и AVFoundation
На Android долгое время Camera2 API был единственным адекватным выбором для кастомных камер. Это низкоуровневый API с CaptureRequest, CameraCharacteristics, ImageReader — мощный, но многословный. Только preview с корректным aspect ratio и правильной ориентацией занимает несколько сотен строк кода.
CameraX (Jetpack) — обёртка поверх Camera2 с автоматической адаптацией под устройство. Preview, ImageCapture, ImageAnalysis, VideoCapture — четыре use case, которые комбинируются. Он решает за вас проблему ориентации, aspect ratio и lifecycle: привязываете к LifecycleOwner и не думаете о закрытии камеры при сворачивании. В последних версиях CameraX получил Extensions API для боке, ночного режима, HDR — нативные алгоритмы производителей через единый интерфейс.
Когда нужен Camera2 напрямую: RAW-съёмка через ImageFormat.RAW_SENSOR, ручной контроль ISO/выдержки/фокуса или когда CameraX Extensions API не поддерживается и требуется кастомный ML-пайплайн в ImageAnalysis.
На iOS AVFoundation — единственный путь для кастомной камеры. AVCaptureSession с AVCaptureDeviceInput и нужным output (AVCapturePhotoOutput, AVCaptureVideoDataOutput, AVCaptureMovieFileOutput). Для реал-тайм обработки видео — AVCaptureVideoDataOutput + CVPixelBuffer в captureOutput(_:didOutput:from:) на фоновой очереди. Именно тут CoreML-модели получают кадры для инференса.
Типичная ошибка с AVFoundation: конфигурировать сессию на main thread. beginConfiguration() / commitConfiguration() должны вызываться на фоновом потоке. Иначе preview фризит, пользователь видит заморозку интерфейса. Эта ошибка встречается в 70% проектов, которые мы аудировали.
Почему AudioFocus критичен для Android приложений
Аудио на мобильных платформах требует корректного управления жизненным циклом звука. AudioFocus — механизм координации между приложениями. AudioManager.requestAudioFocus() с OnAudioFocusChangeListener. Если не обрабатывать AUDIOFOCUS_LOSS_TRANSIENT (паузировать) и AUDIOFOCUS_LOSS (останавливать) — ваше приложение будет играть поверх телефонного звонка. Это гарантированный плохой отзыв в Google Play. Android Developer Guide: AudioFocus
На iOS AudioSession категории определяют поведение: playback — для плееров (продолжает играть при заблокированном экране), record — для записи с отключением других источников, playAndRecord — для голосовых сообщений. Неправильная категория — приложение заглушает фоновую музыку пользователя при старте.
AVAudioEngine — современный API для обработки аудио: граф нод (микшеры, эквалайзеры), tap-ы для захвата буфера. Для речи в реальном времени — SFSpeechRecognizer + inputNode.installTap.
На Android для записи с шумоподавлением — NoiseSuppressor.isAvailable() + create(audioRecord.audioSessionId). Работает не на всех устройствах, нужен fallback.
Видео: воспроизведение и стриминг
ExoPlayer (Media3) — стандарт для Android. Поддерживает HLS, DASH, SmoothStreaming, прогрессивное воспроизведение. DefaultTrackSelector с Parameters позволяет выбирать качество вручную или адаптивно. DRM через DefaultDrmSessionManager с Widevine L1/L3.
Проблема, с которой сталкиваются почти все: ExoPlayer в RecyclerView при быстром скролле. Нужен PlayerPool — пул переиспользуемых плееров. Без пула каждый новый экземпляр создаёт MediaCodec инстанс, что дорого и приводит к MediaCodec$CodecException: Error -19 на некоторых Android 10 устройствах при >3 одновременных инстансах.
AVPlayer / AVPlayerViewController на iOS — для воспроизведения. Для кастомного UI — AVPlayerLayer + собственные контролы. HLS работает нативно через AVPlayer(url:) с m3u8. FairPlay DRM требует серверной части: AVContentKeySession, CKC-ответ от KSM-сервера, делегат ресурсов.
Для Flutter — video_player как базовый слой, chewie для UI. Для серьёзных задач — platform channel к нативному ExoPlayer/AVPlayer (из-за DRM и субтитров).
| Протокол |
Задержка |
Применение |
| RTMP |
2–5 сек |
Стриминг на YouTube/Twitch |
| HLS |
6–30 сек |
VOD, широковещательный |
| DASH |
6–30 сек |
VOD с адаптивным битрейтом |
| WebRTC |
< 500 мс |
Видеозвонки, P2P |
| SRT |
1–4 сек |
Профессиональный стриминг |
WebRTC на мобильных — через нативные фреймворки или flutter_webrtc. Реальная сложность — не в самом протоколе, а в сигналинге и TURN-серверах. Без TURN клиенты за симметричными NAT не установят соединение — это примерно 15–20% трафика. Coturn — стандартный open-source сервер.
RTMP публикация на мобильных: LFLiveKit для iOS, HaishinKit как более современная альтернатива. На Android — rtmp-rtsp-stream-client-java или через FFmpeg с JNI. Последнее даёт максимальную гибкость, но бинарник растёт на 10–15 МБ.
Обработка медиа: компрессия и транскодирование
Видео в ProRes может занимать 6 ГБ/минуту. Перед загрузкой нужна компрессия. На iOS — AVAssetExportSession с пресетом 1920×1080 или кастомный AVVideoComposition. VideoToolbox для аппаратного кодирования H264/HEVC — быстрее и экономнее по батарее.
На Android — MediaCodec напрямую или Transformer (Media3) — высокоуровневый API для трансформаций (обрезка, ресайз, эффекты через GlEffectsFrameProcessor). Для изображений — BitmapFactory.Options.inSampleSize для даунсемплинга, Glide / Coil для кеширования. Coil на Coroutines хорошо вписывается в Compose. Загружать оригинал 12 МП в ImageView 200×200dp — классический OutOfMemoryError на устройствах с 2 ГБ RAM.
Как реализовать стриминг на мобильных устройствах: пошаговый план
- Определить требования: целевая задержка, количество одновременных пользователей, необходимость P2P.
- Выбрать протокол и стек: WebRTC для видеозвонков, RTMP/HLSLive для вещания.
- Настроить сигналинг (SIP, WebSocket, MQTT) и TURN-сервер.
- Реализовать публикацию/просмотр через нативный API или кроссплатформенный плагин.
- Провести тестирование на реальных устройствах с разными камерами и сетевыми условиями.
- Оптимизировать битрейт и разрешение в зависимости от пропускной способности.
Типичные ошибки при разработке медиа-функциональности
- Конфигурация AVFoundation сессии на главном потоке.
- Отсутствие обработки AudioFocus Loss на Android.
- Игнорирование
MediaCodec ограничений на дешёвых устройствах.
- Использование эмулятора для тестов камеры — эмулятор не воспроизводит проблемы HAL.
- Утечка памяти при пересоздании медиаплееров без пула.
Что входит в работу
| Deliverable |
Описание |
| Анализ требований |
Выбор стека, приоритетов, тестовых устройств |
| Проектирование |
Архитектура, диаграммы потоков данных, выбор API |
| Реализация |
Код с использованием выбранных инструментов |
| Интеграция с бэкендом |
GraphQL/REST, DRM, WebRTC сигналинг |
| Тестирование |
На реальных устройствах (не менее 5 моделей) |
| Документация |
API-документация, инструкция по сборке |
| Поддержка после релиза |
1 месяц инцидентной поддержки, обучение команды |
Процесс разработки медиафункциональности
Сложность нелинейна: базовое воспроизведение видео — 1–2 дня, кастомная камера с обработкой кадров и стримингом — 3–5 недель. Начинаем с прояснения требований: DRM, форматы, минимальная OS, поддержка фоновых режимов. Тестирование на железе обязательно — эмулятор не воспроизводит проблемы с Camera HAL, аппаратным кодеком и AudioFocus. Минимальный набор: последний iPhone, iPhone SE, флагман Samsung, бюджетный Android, Android Go (если целевая аудитория — развивающиеся рынки).
Сроки ориентировочно: от 5 рабочих дней (базовое воспроизведение) до 8 недель (комплексная камера со стримингом и DRM). Стоимость рассчитывается индивидуально после анализа ваших требований — свяжитесь с нами для консультации.
Фраза услуги: «Работа с медиа в мобильных приложениях» — это наш профиль. Каждый проект начинается с аудита текущей реализации, выявления узких мест и предложения оптимального стека.
Коммерческие сигналы: закажите аудит вашей медиа-функциональности, получите консультацию инженера без обязательств.