Работа с камерой в iOS приложениях

This post is also available in: Английский

Камера в последних устройствах на iOS — это один  из важных факторов популярности этих устройств. Возможность снимать и аппаратно кодировать в H.264 видео высокого разрешения, появившаяся в iPhone 4, была встречена с большим воодушевлением пользователями и разработчиками новых приложений. Продолжая серию статей об AV Foundation и сопустствующих ему фреймворках, в этой статье мы расскажем о том, как снять видеопоток с камеры, и какие существуют возможности по его обработке, сохранению и распространению.

В iPhone, начиная с модели 3GS, появилась возможность записывать видео с задней камеры в разрешении 480p, в четвертой модели и в iPad 2 появилась передняя камера с обычным разрешением, а задняя была улучшена до разрешения 720p, что по общепринятой классификации относится к видео высокой четкости, а в модели 4S и в новом iPad камера уже снимает видео в 1080p, то есть в идеальном для просмотра на большинстве современных телевизоров разрешении.

Съемка фото в UIKit

Большинство функций, имеющих отношение к работе с камерой, входят в AV Foundation Framework. Но, как и в случае с Media Player Framework, Apple предоставила разработчикам простой и удобный в использовании класс для съемки фото и видеороликов с уже готовым пользовательским интерфейсом —

1
UIImagePickerController

, включающим полный набор функций: управление вспышкой, переключение между камерами, зум, выбор точки фокусировки, выбор из библиотеки, редактирование снятых фотографий. Для его использования, приложение должно выполнить следующие действия:

  1. После выбора пользователем одного из источников — библиотека фотографий, камера или альбом сохраненных фотографий — проверить, имеет ли используемое устройство доступ к получению данных из выбранного источника, с помощью метода
    1
    isSourceTypeAvailable:

    .

  2. Проверить методом
    1
    availableMediaTypesForSourceType:

    , какие из видов контента доступны через выбранный источник: фотографии, видеоролики или все вместе.

  3. Сконфигурировать контроллер, присвоив свойству
    1
    mediaType

    перечень видов контента, которые приложение ожидает получить.

  4. Отобразить контроллер и после выбора пользователем контента или по нажатию кнопки «Отмена» скрыть контроллер с экрана.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (BOOL) startCameraControllerFromViewController:
               (UIViewController*) controller
               usingDelegate: (id <UIImagePickerControllerDelegate,
                                   UINavigationControllerDelegate>) delegate {

    if (([UIImagePickerController isSourceTypeAvailable:
                 UIImagePickerControllerSourceTypeCamera] == NO)
            || (delegate == nil)
            || (controller == nil))
        return NO;

    UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
    cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;

    // Displays a control that allows the user to choose picture or
    // movie capture, if both are available:
    cameraUI.mediaTypes =
        [UIImagePickerController availableMediaTypesForSourceType:
            UIImagePickerControllerSourceTypeCamera];

    // Hides the controls for moving & scaling pictures, or for
    // trimming movies. To instead show the controls, use YES.
    cameraUI.allowsEditing = NO;

    cameraUI.delegate = delegate;

    [controller presentModalViewController: cameraUI animated: YES];
    return YES;
}

1
UIImagePickerController

поддерживает ограниченную кастомизацию пользовательского интерфейса через задание свойства

1
cameraOverlayView

, которое должно содержать view для отображения поверх стандартного интерфейса.

Для того, чтобы в полном объеме задействовать в своем приложении возможности съемки или реализовать какую-то нестандартную функциональность, придется обратиться к AV Foundation Framework.

AV Foundation Framework

Получение данных с камеры и микрофона осуществляется в рамках сессии съемки (capture session). Сессия координирует потоки данных из входов в выходы, один из которых может быть, например, видеофайлом. Кроме того, к сессии можно привязать слой предпросмотра, на котором будет показано изображение с камеры. Сессия представляет собой граф, в котором вершины — это объекты ввода

1
AVCaptureInput

и вывода

1
AVCaptureOutput

, а связи — объекты

1
AVCaptureConnection

. Устройства ввода, являющиеся источником данных для объектов ввода, описываются классом

1
AVCaptureDevice

.

Для инициализации сессии, необходимо добавить нужные входы и выходы, при этом связи создадутся автоматически по принципу «все входы соединены со всеми выходами», а затем задать для сессии одно из предустановленных качеств: высокое, среднее, низкое и т.д. После инициализации можно запускать сессию методом

1
startRunning

и останавливать методом

1
stopRunning

.

На различных моделях устройств могут быть доступны различные по количеству и характеристикам камеры, какие-то устройства могут оказаться недоступными из-за того, что они используются другими приложениями. Поэтому для поиска и выбора подходящего устройства класс

1
AVCaptureDevice

предоставляет ряд методов:

1
hasMediaType:

для подбора по виду контента (аудио/видео),

1
supportsAVCaptureSessionPreset:

для проверки поддерживаемых установок качества,

1
isFocusModeSupported:

для определения поддерживаемых режимов автофокусировки, и т.д. Если устройство поддерживает автофокусировку, подстветку или автоматический баланс белого, то все эти функции можно включить или выключить.

В рамках запущенной сессии можно переключаться с одного устройства на другое, например, с фронтальной камеры на заднюю, не останавливая сессию. Для этого, как и для изменения других настроек сессии, соответствующий код необходимо написать внутри вызовов

1
beginConfiguration

,

1
commitConfiguration

, чтобы минимизировать время применения новых настроек.


1
2
3
4
[session beginConfiguration];
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
[session commitConfiguration];

В качестве выходов в сессии съемки могут участвовать объекты следующих классов:

  • 1
    AVCaptureMovieFileOutput

    для сохранения данных сессии в видеофайл.

  • 1
    AVCaptureVideoDataOutput

    для получения отдельных видеосэмплов, снятых камерой.

  • 1
    AVCaptureAudioDataOutput

    для получения отдельных звуковых сэмплов, снятых микрофоном.

  • 1
    AVCaptureStillImageOutput

    для сохранения статического изображения с камеры с сопутствующими метаданными.

Сохранение в файл

1
AVCaptureMovieFileOutput

позволяет задать следующие основные параметры записи: максимальную длительность записанного файла, по достижению которой запись прекратится автоматически, требуемый для записи объем свободного дискового пространства. Параметры качества выходного видеофайла зависят от параметров сессии и поддерживаемых режимов записи на используемой модели устройства. Метод

1
startRecordingToOutputFileURL: recordingDelegate:

начинает запись в файл, а по завершению вызывается метод делегата

1
captureOutput: didFinishRecordingToOutputFileAtURL: fromConnections: error:

. В этом методе очень важно проверить причину завершения записи, так как она может прерваться по совершенно разным, в том числе и не зависящим от приложения, причинам: переполнение диска, отсоединение микрофона, поступление входящего звонка и т.д.

Для добавления в записанный видеофайл или изображение информации о геолокации используется свойство

1
metadata

, содержащее массив объетов

1
AVMutableMetadataItem

. Помимо геолокации поддерживаются также следующие метаданные: дата записи, язык, название, расширенное описание и прочие.

Покадровая обработка

Выходы

1
AVCaptureVideoDataOutput

и

1
AVCaptureAudioDataOutput

после запуска сессии начинают передавать в метод делегата

1
captureOutput: didOutputSampleBuffer: fromConnection:

отдельные несжатые сэмплы, снятые с камеры или микрофона. Метод делегата вызывается в фоновой последовательной очереди, что гарантирует обработку кадров в заданном порядке без блокирования основной очереди. При обработке кадров важно следить за тем, чтобы приложение успевало их обрабатывать с той же скоростью, с которой они поступают на вход. Для этого могут быть полезными возможности аппаратного ускорения, предоставляемые фреймворками OpenGL ES и Accelerate Framework.

В качестве контейнера, содержащего данные сэмпла выступает структура типа

1
CMSampleBuffer

. Функции библиотеки Core Media позволяют преобразовывать эту структуру в массив байтов, содержащих медиаинформацию сэмпла, и обратно или в объект UIImage для видеокадров, а также получить сопутствующую информацию о сэмпле: описание формата данных, количество аудиоканалов, частоту дискретизации, временную метку, длительность, и т.д. Программист имеет возможность задать необходимый формат сэмплов до начала записи в зависимости от того, какой формат будет более удобен для обработки. Например, для обработки кадров средствами Core Graphics или OpenGL лучше подойдет кадр с пикселями закодированными в формате BGRA.

Предварительный просмотр

Для предпросмотра изображения с камеры слой

1
AVCaptureVideoPreviewLayer

инициализуется с использованием сессии съемки:


1
2
3
4
5
6
7
AVCaptureSession *captureSession = <#Get a capture session#>;
CALayer *viewLayer = <#Get a layer from the view in which
                      you want to present the preview#>;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer =
             [[AVCaptureVideoPreviewLayer alloc]
                           initWithSession:captureSession];
[viewLayer addSublayer:captureVideoPreviewLayer];

Слой предпросмотра поддерживает несколько режимов масштабирования, также как и

1
AVPlayerLayer

: масштабирование с полным заполнением, с сохранением соотношения сторон и с обрезанием сторон.

Класс

1
AVCaptureAudioChannel

позволяет получить текущий максимальный и усредненный уровень звука для отображения в интерфейсе приложения.

Пример использования вышеописанных средств фреймворка AV Foundation можно найти в разделе руководства Putting it all Together: Capturing Video Frames as UIImage Objects.

Покадровая запись видеофайла

Класс

1
AVAssetWriter

позволяет осуществлять покадровую запись видеофайлов. Звуковые сэмплы и кадры видео при этом можно получать как из сессии съемки, так и формировать самостоятельно внутри приложения, используя готовые изображения, скриншоты, рисунки, выполненные с помощью фреймворка Core Graphics, или трехмерные OpenGL сцены. Трэки результирующего видеофайла кодируются одним из поддерживаемых устройством кодеков, например, H.264 и AAC. При этом всегда, когда есть такая возможность, кодирование производится аппаратно, не создавая нагрузку на центральный процессор устройства.

При инициализации

1
AVAssetWriter

создается один или несколько входов

1
AVAssetWriterInput

, соответствующих отдельным звуковым и видеодорожкам выходного файла. Для них задаются параметры выходного потока, такие как битрейт, частота дискретизации, разрешение видео. После этого вызывается метод

1
startWriting

для начала записи видеофайла и методы

1
startSessionAtSourceTime:

и

1
endSessionAtSourceTime:

перед началом и концом записи отдельных отрезков видео. В ходе записи сессии кадры добавляются методом

1
[AVAssetWriterInput appendSampleBuffer:]

.

Assets Library Framework

Еще один небольшой фреймворк — Assets Library позволяет приложению получить доступ на чтение и запись к библиотеке фотографий и записанных видеороликов устройства, которой управляет стандартное приложение «Фото». Большинство методов этого фреймворка являются асинхронными, в том числе и по той причине, что для совершения различных действий операционная система может запрашивать у пользователя разрешение на доступ к библиотеке. Для поиска нужных фотографий фреймворк предоставляет программный интерфейс очень похожий на тот, который доступен обычным пользователям в приложении Фото: выбор из различных коллекций, фильтрация по различным признакам.

Core Image

Фреймворк Core Image предназначен для обработки статических изображений, например фотографий. В iOS 5 в нем появился ряд новых интересных функций, которые в некоторых случаях можно использовать в том числе и при съемке видео:

  • Определение характерных признаков на фотографии.
  • Новые фильтры для обработки фотографий: удаление эффекта красных глаз и автоматическое улучшение фотографии.

В данный момент единственный поддерживаемый признак, который можно выявить по фотографии, — это человеческое лицо. С помощью класса

1
CIFaceDetector

можно найти ту область изображения, в которой предположительно находится лицо. Объект найденной характеристики

1
CIFaceFeature

содержит следующую информацию: границы лица, местонахождение правого глаза, левого глаза и рта. Стоит отметить, что эта функция не делает распознавание лиц в полном смысле этого слова, то есть не может отличить одно лицо от другого или определить, что лицо принадлежит определенному человеку.

Для использовании функций Core Image при съемке видео, необходимо задать выходной формат BGRA и конвертировать снимаемые кадры в объекты типа

1
CIImage

с помощью одного из методов инициализации этого класса.

Живое вещание видео в сеть

К сожалению iOS SDK не предоставляет встроенных возможностей для вещания сжатого видео снятого с камеры в реальном времени, хотя аппаратные возможности мобильных устройств Apple позволяют это делать, что подтверждается существованием приложений, которые это делают, таких как Facetime, Skype и Ustream. В текущей версии SDK отсутствует поддержка двух критически важных для поддержки живого вещания функций:

  • Формирование сжатых видеобуферов в памяти, пригодных для отправки по сети.
  • Поддержка существующих сетевых протоколов для вещания с минимальными задержками, таких как MMS, RTSP или RTMP.

К счастью, возможности платформы iOS не ограничиваются встроенными SDK. Мы можем задействовать в приложении функциональность любой библиотеки, написанной на языках C, C++, Objective-C или других, способных компилировать код в виде подключаемой библиотеки, при условии, что ее код достаточно хорошо переносим между UNIX-совместимыми ОС и нормально компилируется для целевой платформы armv7. Большинство библиотек с открытым исходным кодом подпадают под эти требования.

Другое ограничение, с которым разработчик может столкнуться при использовании некоторых открытых библиотек — это то, что популярная открытая лицензия GPL не совместима с правилами App Store. Один из примеров такой библиотеки — libx264 для кодирования видеопотока кодеком H.264. Возможно, на этапе review это никак не повлияет на решение по вашему приложению, однако, в дальнейшем любой из разработчиков библиотеки легко сможет удалить ваше приложение из App Store, написав жалобу в Apple. Помимо GPL существуют и другие, более совместимые с закрытой коммерческой разработкой лицензии, такие как LGPL, MIT, BSD, и большинство открытых библиотек используют именно их. Например, код ffmpeg частично, а libfaac и librtmp целиком лицензированы по LGPL.

Однако, и использование сторонних библиотек не решает полностью проблему живого вещания. Дело в том, что ни одна из известных на сегодняшний день открытых библиотек не способна задействовать возможности аппаратного кодирования видео, доступные на устройствах Apple. А кодирование средствами CPU осуществляется недостаточно быстро и отнимает огромное количество ресурсов процессора, а самое главное — быстро сажает батарею. К счастью, все же существует обходной путь для аппаратного кодирования видео в реальном времени, который к тому же не использует закрытых API системы, и некоторые приложения используют этот вариант. Мы в компании DENIVIP Media также разработали и опробовали в опытной эксплуатации эту функциональность.

Интересные примеры

Рассмотрим некоторые интересные примеры использования описанных выше возможностей SDK iOS.

Пример RosyWriter

Проект RosyWriter доступен как часть официальной документации Apple для разработчиков. Он показывает, как реализовать продвинутый вариант съемки и записи видеофайла, включающий одним из этапов покадровую обработку получаемых с камеры изображений. В данном случае обработка заключается в обнулении зеленой составляющей цвета изображения, но в вашем приложении это может быть все, что угодно. Кроме того, в примере показано, как сделать собственный слой предпросмотра средствами OpenGL.

Пример GLCameraRipple

GLCameraRipple — еще один аналогичный пример из документации iOS SDK. Он демонстрирует, как применить к видеопотоку, получаемому с камеры, эффект волн с помощью шейдеров OpenGL.

Для обработки кадров на GPU используется структура

1
CVOpenGLESTextureCache

, появившаяся в iOS 5, которая позволяет значительно ускорить работу с OpenGL, исключая избыточные операции пересылки данных картинки на GPU и обратно.