Введение в мультиэкспозицию и многокамерные системы iOS
Задумывались ли вы, почему в эпоху мощнейших чипов в iPhone большинство приложений продолжают работать с камерой по старинке — включая лишь один объектив за раз? Представьте, что вы создаете приложение для стриминга, где блогер одновременно транслирует свое лицо на фронталку и эмоции зрителей вокруг через ультраширик, или разрабатываете продвинутый сканер для 3D-моделирования. Сегодня это не магия Apple, а доступный через AVFoundation стек технологий, который мы научимся обуздывать прямо сейчас.
Современные смартфоны Apple оснащены мощными массивами из нескольких объективов. Обычные пользователи привыкли переключаться между ними в стандартном приложении «Камера»: сверхширокоугольный модуль для пейзажей, основной — для повседневных сценок, а телефото — для портретов. Однако для разработчиков iOS открываются гораздо более глубокие возможности благодаря фреймворку AVFoundation. Одной из самых передовых задач является одновременный захват изображения с двух физических линз с последующим программным или аппаратным слиянием в единый кадр.
Такой подход позволяет создавать уникальные фотографические эффекты: от расширенного динамического диапазона (HDR) до художественных коллажей и стереоскопических снимков. В этой статье мы разберем архитектуру многокамерного захвата, аппаратные ограничения и напишем практический пример кода на Swift.
Архитектура AVCaptureMultiCamSession и аппаратные ограничения
До iOS 13 разработчики были ограничены сессиями, позволяющими работать только с одной камерой за раз. С появлением AVCaptureMultiCamSession экосистема получила инструмент профессионального уровня. Он позволяет запускать захват с нескольких физических устройств одновременно — например, с фронтальной и задней камер или с двух задних модулей сразу.
Прежде чем писать код, необходимо проверить аппаратную поддержку устройства:
if !AVCaptureMultiCamSession.isMultiCamSupported {
// Устройство не поддерживает одновременную работу нескольких камер
print("Multi-cam is not supported on this device.")
}
Стоит учитывать серьезные аппаратные ограничения (запуск мульти-камеры — это как попытаться крутить три тяжелых Docker-контейнера на старом ноутбуке):
- Высокое энергопотребление и нагрузка на CPU/GPU.
- Риск троттлинга и быстрого нагрева устройства.
- Принудительное снижение частоты кадров (FPS) или разрешения со стороны ОС при перегрузке.
Но как подружить эти потоки без потери производительности? Давайте развернем конвейер данных.
Настройка потоков данных от двух объективов
Для реализации нашей задачи по захвату и слиянию кадров нужно настроить сессию на прием потоков от двух выбранных камер: например, .builtInWideAngleCamera и .builtInUltraWideCamera.
// Пример инициализации сессии
let multiCamSession = AVCaptureMultiCamSession()
// Настройка конфигурации инпутов и видеовыходов (AVCaptureVideoDataOutput)
// Требует обработки разрешений и настройки AVCaptureSession.Preset
Главная сложность на этом этапе — синхронизация таймстампов (PTS, Presentation Timestamp) от двух разных сенсоров, чтобы кадры идеально совпадали по времени (синхронизировать их сложнее, чем команду разработчиков к единому дедлайну).
Когда потоки синхронизированы и стабильно отдают буферы, наступает самый творческий этап — сведение картинок воедино.
Слияние кадров в единое изображение
Получив два буфера CMSampleBuffer, мы можем передать их в графический конвейер CoreImage или Metal для композитинга. Самый простой путь для прототипа — использование CoreGraphics или CIImage:
func merge(image1: CIImage, image2: CIImage) -> CIImage? {
let filter = CIFilter.sourceOverCompositing()
filter.inputImage = image1
filter.backgroundImage = image2
return filter.outputImage
}
Для продакшн-решений рекомендуется использовать шейдеры Metal, так как они обеспечивают минимальную задержку.