В LIVEOP мы сосредоточены на предоставлении службам быстрого реагирования наиболее актуальной информации в краткой форме, в то же время не ставя под угрозу удобство взаимодействия с пользователем. Когда мы начали сотрудничать с Zepcam, ведущим мировым поставщиком беспроводных (нательных) систем камер, мы хотели убедиться, что предоставляем опыт, соответствующий нашим высоким стандартам, без ущерба для производительности или эффективности.
Потоки с камеры, размещенные на Zepcam, представлены в нескольких различных форматах, наиболее важно HTTP Live Streaming (HLS), первоклассный гражданин экосистемы iOS со встроенной поддержкой AVFoundation
, и RTSP, протокол потоковой передачи в реальном времени. Потоки HLS обычно используются для прямых телетрансляций и выпусков новостей. Он ориентирован на беспрепятственный просмотр для зрителя: пропадание кадров запрещено, воспроизведение кадров вне очереди не допускается, а небольшой буфер предстоящих кадров поддерживается для обеспечения плавного воспроизведения. Ситуации, в которых активируются потоки Zepcam, часто опасны для жизни. Офицеры могут вести прямую трансляцию со своей нательной камеры, пытаясь сдержать бунт, или лестничный двигатель с камерой, установленной наверху, может обеспечивать вид с высоты птичьего полета на большой пожар в здании, включая положение пожарных на земле. Наше определение беспрепятственного взаимодействия с пользователем отличается от определения, предписанного HTTP Live Streaming: в нашем случае важно, чтобы кадры, отображаемые для пользователя, отображались как можно в реальном времени. Они могут прибыть не по порядку, и пара кадров может быть отброшена, если это дает преимущество для реального времени потока. Сложив наши требования, мы пришли к использованию RTSP поверх UDP.
Apple не предоставляет поддержку ни в одной из высокоуровневых платформ для воспроизведения потоков RTSP. MPMoviePlayerController
, AVPlayerItem
и AVPlayer
, все системные классы высокого уровня для воспроизведения видеопотоков, не поддерживают потоки RTSP. К счастью, FFMPEG, швейцарский армейский нож для обработки аудио / видео, оснащен необходимыми инструментами для обработки и декодирования потоков RTSP. FFMPEG существует уже 17 лет в сообществе открытого исходного кода и с тех пор зарекомендовал себя как надежная сила, стоящая за множеством приложений для конечных пользователей, таких как VLC, Google Chrome и Chromium¹.
Настройка FFMPEG
Потоки RTSP, обслуживаемые Zepcam, кодируются кодеком H264. Чтобы предотвратить резкое увеличение двоичного размера нашего окончательного файла приложения iOS (.ipa), мы решили скомпилировать последнюю версию FFMPEG с нуля (v4.0.1), включив только те функции, которые мы ожидаем использовать. Мы используем отличный скрипт сборки, найденный здесь, с парой корректировок:
- Измените переменную
FF_VERSION
на 4.0.1 - Измените
DEPLOYMENT_TARGET
на цель развертывания вашего приложения iOS. - Измените
CONFIGURE_FLAGS
, чтобы включить битовый код, и отключите все функции, кроме необходимых для нашего потока:
CONFIGURE_FLAGS="--enable-cross-compile --disable-debug --disable-programs --disable-doc --extra-cflags=-fembed-bitcode --extra-cxxflags=-fembed-bitcode --disable-ffmpeg --disable-ffprobe --disable-avdevice --disable-avfilter --disable-encoders --disable-parsers --disable-decoders --disable-protocols --disable-filters --disable-muxers --disable-bsfs --disable-indevs --disable-outdevs --disable-demuxers --enable-protocol=file --enable-protocol=tcp --enable-protocol=udp --enable-decoder=mjpeg --enable-decoder=h264 --enable-parser=mjpeg --enable-parser=h264 --enable-parser=aac --enable-demuxer=rtsp --enable-videotoolbox"
Кроме того, требуется небольшое изменение в исходном файле FFMPEG libswresample/arm/audio_convert_neon.S
, как описано здесь. Теперь компиляция должна завершиться успешно, в результате получится несколько разных библиотек. Перетащите библиотеки в свой проект Xcode и обязательно свяжите их с целевым объектом приложения (Этапы сборки ›Связывание двоичного файла с библиотеками).
Глобальная настройка, необходимая для воспроизведения видео через FFMPEG, довольно проста. Откройте входной URL-адрес, указывающий на поток RTSP с помощью avformat_open_input
, найдите потоки из входа с помощью avformat_find_stream_info
, выделите контекст кодека с помощью avcodec_alloc_context3
и avcodec_parameters_to_context
и, наконец, откройте кодек с помощью avcodec_open2
. Важно реализовать правильную обработку ошибок и очистку памяти для всех этих методов, поскольку все они могут выйти из строя в зависимости от обстоятельств. В нашем приложении мы также решили реализовать обратный вызов прерывания, чтобы на раннем этапе выйти из методов блокировки в определенных ситуациях, например, при отсутствии подключения к Интернету, о чем свидетельствуют SCNetworkReachability
API-интерфейсы, или после истечения настраиваемого таймера тайм-аута. В частности, связь с API-интерфейсами достижимости позволяет нам обойти встроенные тайм-ауты FFMPEG и выйти из строя раньше, когда не обнаружено подключение к Интернету.
Декодирование кадров
Структура AVCodecContext
предоставляет поле get_format
, которое позволяет нам выбрать выходнойAVPixelFormat
для видеокадров, доставленных декодером, из списка доступных форматов. Если мы оставим это поле пустым, видеокадры будут отформатированы как AV_PIX_FMT_YUV420P
, формат, автоматически определяемый декодером на основе базового потока. Изображения в iOS отформатированы в RGB (A) (AV_PIX_FMT_RGB24
), поэтому потребуется дополнительный шаг для преобразования кадров из AV_PIX_FMT_YUV420P
в AV_PIX_FMT_RGB24
перед отображением. libswscale
предоставляет функцию sws_scale
, которая делает именно это, но, к сожалению, она не реализована в графическом процессоре, что означает снижение производительности при выполнении дополнительного шага преобразования из YUV420P в RGB24.
Из списка доступных форматов пикселей, которые мы получаем с помощью функции get_format
, есть один, который требует особого внимания: AV_PIX_FMT_VIDEOTOOLBOX
. Несмотря на то, что этот формат плохо документирован, этот формат предписывает декодеру передавать входящие кадры на VideoToolbox.framework
Apple. Он будет декодировать каждый входящий кадр на GPU, возвращая CVPixelBufferRef
, содержащий декодированные данные. Это намного предпочтительнее реализации по умолчанию, которая требует дополнительного преобразования из YUV420 в RGB24 на ЦП. Дескриптор нашей функции, переданный в поле get_format
AVCodecContext
, теперь выглядит следующим образом:
static enum AVPixelFormat negotiate_pixel_format(struct AVCodecContext *s, const enum AVPixelFormat *fmt) { while (*fmt != AV_PIX_FMT_NONE) { if (*fmt == AV_PIX_FMT_VIDEOTOOLBOX) { if (s->hwaccel_context == NULL) { int result = av_videotoolbox_default_init(s); if (result < 0) { return s->pix_fmt; } } return *fmt; } ++fmt; } return s->pix_fmt; }
Мы проверяем доступность формата VideoToolbox, прежде чем пытаться его использовать. Если он недоступен или если инициализация интеграции videotoolbox не удалась, мы возвращаемся к формату, изначально найденному декодером. Обратите внимание, что AV_PIX_FMT_VIDEOTOOLBOX
недоступен в симуляторе iOS. В методе teardown нашего класса видеоплеера мы проверяем, не является ли hwaccell_context
контекста кодека NULL, и вызываем av_videotoolbox_default_free
, если это так.
Отдельные видеокадры принимаются с avcodec_receive_frame
. При возврате этой функции выходной параметр AVFrame
будет заполнен информацией, кодирующей один видеокадр, в зависимости от используемого формата пикселей. Если формат VideoToolbox был успешно использован, то CVPixelBufferRef
, содержащий данные кадра, можно найти в AVFrame.data[3]
. Хотя это явно не задокументировано, это очевидно из комментариев, помещенных за определениями других форматов с аппаратным ускорением в pixfmt.h
. CVPixelBufferRef
не может быть отображен сразу. Сначала мы преобразуем его в CIImage
с помощью +[CIImage imageWithCVPixelBuffer:]
, а затем превращаем CIImage
в UIImage
с помощью +[UIImage imageWithCIImage:]
. FinalUIImage
описывает один видеокадр и теперь может отображаться в вашем видеопроигрывателе, например реализован как простой UIImageView
.
В наших тестах между использованием формата пикселей по умолчанию, включая дополнительный шаг преобразования в RGB24, и формата VideoToolbox, мы заметили значительную разницу в производительности. При декодировании CPU мы наблюдали в целом медленное воспроизведение и относительно большое количество пропаданий кадров. При декодировании графическим процессором отбрасывались почти нулевые кадры. Несмотря на то, что это не слишком хорошо документировано, углубление во внутренностях FFMPEG для достижения декодирования видеопотоков с ускорением графического процессора на iOS определенно того стоит.
Нам не терпится увидеть, как наша интеграция с Zepcam улучшит рабочий процесс мужчин и женщин, ежедневно работающих над обеспечением безопасности нашего общества.