Введение
На рынке электроники доступен широчайший ассортимент устройств Android* с различными версиями ОС и разным оборудованием. В результате поставщикам интернет-видео приходится сталкиваться с определенными трудностями при распространении своих видеоматериалов. Поскольку уже в ближайшем будущем устройства с Android 4.1 и более поздней версии будут преобладать на рынке, в этой статье рассказывается о том, как интегрировать FFmpegв MediaCodec для поддержки различных форматов видео, а также о том, как создать аппаратный декодер для Android на базе платформ Intel® Atom™.
Базовые сведения
Для управления воспроизведением звука и видео Google создал проигрыватель MediaPlayer. Однако функциональность MediaPlayer ограничена ввиду поддержки всего трех форматов мультимедиа: mp4, 3gpp и mkv (начиная с Android 4.0). При этом большинство поставщиков интернет-видео используют другие форматы, такие как FLV и т. д. Для воспроизведения неподдерживаемых форматов и сохранения совместимости с как можно большим количеством доступных на рынке устройств Android разработчики интернет-видео применяют FFmpeg в качестве предпочитаемого программного декодера. Существует и другое решение: некоторые разработчики интегрируют OpenMAX* (хорошо известный стандарт в отрасли мультимедиа) для доступа к низкоуровневому аппаратному декодеру на устройствах с Android 2.3, кодек для поддержки устройств с Android 4.0 и класс MediaCodec для поддержки устройств с Android 4.1 и более поздних версий. Им приходилось покупать большинство самых популярных на рынке устройств с Android для тестирования. Но и в этом случае все равно оставались проблемы совместимости, поэтому приходилось использовать FFmpeg в качестве резервного решения.
Анализ решения FFmpeg с MediaCodec
FFmpeg [1] — это ведущая платформа мультимедиа, способная декодировать, кодировать, перекодировать, распаковывать, передавать потоком, фильтровать и воспроизводить практически любое содержимое, созданное человеком и компьютерами. FFmpeg также может выполнять разбор потока видео.
Класс MediaCodec впервые появился в Android 4.1, он основан на API Java* и предоставляет интерфейс для доступа к низкоуровневым системным кодекам; это могут быть либо аппаратные кодеки, либо, в случае со звуком, программные кодеки с высоким уровнем оптимизации. Использование MediaCodec помогает добиться ощутимого прироста производительности и экономии электроэнергии.
Помимо форматов mp4, 3gpp и mkv, поддерживаемых в Android MediaPlayer напрямую, многие другие популярные форматы видео, такие как flv, mov, rm, rmvb и пр., не имеют встроенной поддержки. Для этих неподдерживаемых форматов поставщики интернет-видео обычно используют FFmpeg в качестве программного декодера, из-за чего возрастает нагрузка на ЦП и потребление электроэнергии.
Эту проблему можно решить путем интеграции FFmpeg с MediaCodec: при этом будут поддерживаться самые разные форматы видео, а платформы Intel Atom под управлением Android получат аппаратный декодер.
Решение показано на следующей схеме.
Рисунок 1..Интеграция FFmpeg с решением MediaCodec.
В этом решении FFmpeg распаковывает контейнер с видео как необработанные видеоданные и аудиоданные на уровне собственного кода. Затем необработанное видео передается в API MediaCodec на уровне Java для аппаратного декодера. Звуковые данные декодируются напрямую в FFmpeg, на это не тратится слишком много ресурсов ЦП. И наконец, FFmpeg синхронизирует звук и видео по штампу времени.
Это решение поддерживает некоторые другие популярные форматы видео, которые не поддерживаются в MediaPlayer, и независимые поставщики интернет-видео считают его полезным.
Сборка FFmpeg для Android на платформе x86
FFmpeg поддерживает Android начиная с выпуска 2.1, но пакет FFmpeg не имел сценария сборки для Android на платформе x86. Ниже приведены действия для сборки FFmpeg для Android на платформе x86:
- На веб-сайте FFmpeg загрузите последнюю версию FFmpeg — ffmpeg-2.2.4.tar.bz2 [2].
- Скопируйте пакет FFmpeg на компьютер с Ubuntu* и извлеките содержимое пакета с помощью следующей команды tar:
wangsy@ubuntu:~/Desktop$ tar xvf ffmpeg-2.2.4.tar.bz2
- Задайте среду ANDROID_NDK_HOME с помощью команды export:
export ANDROID_NDK_HOME= $ ANDROID_NDK_HOME :/~/android-ndk-r9c
- Скопируйте следующий файл конфигурации в папку ~/ffmpeg-2.2.4 и добавьте права на исполнение:
wangsy@ubuntu:~/Desktop$ cp config_build_x86.sh ~/ffmpeg-2.2.4 wangsy@ubuntu:~/Desktop$ sudo chmod a+x ~/ffmpeg-2.2.4/config_build_x86.sh
Запустите сценарий конфигурации и соберите программу с помощью команд make и make instal:
wangsy@ubuntu:~/Desktop/ffmpeg-2.2.4$ make wangsy@ubuntu:~/Desktop/ffmpeg-2.2.4$ make install
Созданные LIB-файлы Android для x86 находятся в папке ~/Desktop/ffmpeg-2.2.4/android/x86/lib$.
Разработчики могут скопировать эти библиотеки для разработки. Поскольку компилятор ассемблера YASM [3] и потоковые расширения Intel® SIMD (Intel® SSE) [4] включены в config_build_x86.sh, созданные LIB-файлы Android для x86 оптимизированы для очень высокой производительности на платформах Intel Atom под управлением Android.
Интеграция API Java MediaCodec из уровня собственного кода FFmpeg
Класс MediaCodec можно использовать для доступа к низкоуровневому медиакодеку, то есть к компонентам кодирования и декодирования. Его можно без труда вызвать из уровня Java. Но в этом решении FFmpeg распаковывает необработанные видеоданные на уровне собственного кода, поэтому необходимо разбирать функции MediaCodec из этого уровня и передавать необработанные видеоданные этим API.
Демонстрационный код: доступ к классу MediaCodec из уровня собственного кода:
struct classname { const char *name; int offset; }; static const struct classname classes[] = { { "android/media/MediaCodecList", OFF(media_codec_list_class) }, { "android/media/MediaCodec", OFF(media_codec_class) }, { "android/media/MediaFormat", OFF(media_format_class) }, { "android/media/MediaFormat", OFF(media_format_class) }, { "android/media/MediaCodec$BufferInfo", OFF(buffer_info_class) }, { "java/nio/ByteBuffer", OFF(byte_buffer_class) }, { NULL, 0 }, }; JNIEnv* env = NULL; ATTACH_THREAD; for (int i = 0; classes[i].name; i++) { *(jclass*)((uint8_t*)p_sys + classes[i].offset) = (*env)->FindClass(env, classes[i].name); if ((*env)->ExceptionOccurred(env)) { msg_Warn(p_dec, "Unable to find class %s", classes[i].name); (*env)->ExceptionClear(env); goto error; } }
Демонстрационный код: доступ к функциям MediaCodec из уровня собственного кода:
struct member { const char *name; const char *sig; const char *class; int offset; int type; }; static const struct member members[] = { { "toString", "()Ljava/lang/String;", "java/lang/Object", OFF(tostring), METHOD }, { "getCodecCount", "()I", "android/media/MediaCodecList", OFF(get_codec_count), STATIC_METHOD }, { "getCodecInfoAt", "(I)Landroid/media/MediaCodecInfo;", "android/media/MediaCodecList", OFF(get_codec_info_at), STATIC_METHOD }, { "isEncoder", "()Z", "android/media/MediaCodecInfo", OFF(is_encoder), METHOD }, { "getSupportedTypes", "()[Ljava/lang/String;", "android/media/MediaCodecInfo", OFF(get_supported_types), METHOD }, { "getName", "()Ljava/lang/String;", "android/media/MediaCodecInfo", OFF(get_name), METHOD }, { "createByCodecName", "(Ljava/lang/String;)Landroid/media/MediaCodec;", "android/media/MediaCodec", OFF(create_by_codec_name), STATIC_METHOD }, { "configure", "(Landroid/media/MediaFormat;Landroid/view/Surface;Landroid/media/MediaCrypto;I)V", "android/media/MediaCodec", OFF(configure), METHOD }, { "start", "()V", "android/media/MediaCodec", OFF(start), METHOD }, { "stop", "()V", "android/media/MediaCodec", OFF(stop), METHOD }, { "flush", "()V", "android/media/MediaCodec", OFF(flush), METHOD }, { "release", "()V", "android/media/MediaCodec", OFF(release), METHOD }, { "getOutputFormat", "()Landroid/media/MediaFormat;", "android/media/MediaCodec", OFF(get_output_format), METHOD }, { "getInputBuffers", "()[Ljava/nio/ByteBuffer;", "android/media/MediaCodec", OFF(get_input_buffers), METHOD }, { "getOutputBuffers", "()[Ljava/nio/ByteBuffer;", "android/media/MediaCodec", OFF(get_output_buffers), METHOD }, { "dequeueInputBuffer", "(J)I", "android/media/MediaCodec", OFF(dequeue_input_buffer), METHOD }, { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", "android/media/MediaCodec", OFF(dequeue_output_buffer), METHOD }, { "queueInputBuffer", "(IIIJI)V", "android/media/MediaCodec", OFF(queue_input_buffer), METHOD }, { "releaseOutputBuffer", "(IZ)V", "android/media/MediaCodec", OFF(release_output_buffer), METHOD }, { "createVideoFormat", "(Ljava/lang/String;II)Landroid/media/MediaFormat;", "android/media/MediaFormat", OFF(create_video_format), STATIC_METHOD }, { "setInteger", "(Ljava/lang/String;I)V", "android/media/MediaFormat", OFF(set_integer), METHOD }, { "getInteger", "(Ljava/lang/String;)I", "android/media/MediaFormat", OFF(get_integer), METHOD }, { "setByteBuffer", "(Ljava/lang/String;Ljava/nio/ByteBuffer;)V", "android/media/MediaFormat", OFF(set_bytebuffer), METHOD }, { "<init>", "()V", "android/media/MediaCodec$BufferInfo", OFF(buffer_info_ctor), METHOD }, { "size", "I", "android/media/MediaCodec$BufferInfo", OFF(size_field), FIELD }, { "offset", "I", "android/media/MediaCodec$BufferInfo", OFF(offset_field), FIELD }, { "presentationTimeUs", "J", "android/media/MediaCodec$BufferInfo", OFF(pts_field), FIELD }, { "allocateDirect", "(I)Ljava/nio/ByteBuffer;", "java/nio/ByteBuffer", OFF(allocate_direct), STATIC_METHOD }, { "limit", "(I)Ljava/nio/Buffer;", "java/nio/ByteBuffer", OFF(limit), METHOD }, { NULL, NULL, NULL, 0, 0 }, }; JNIEnv* env = NULL; jclass last_class; for (int i = 0; members[i].name; i++) { if (i == 0 || strcmp(members[i].class, members[i - 1].class)) last_class = (*env)->FindClass(env, members[i].class); if ((*env)->ExceptionOccurred(env)) { msg_Warn(p_dec, "Unable to find class %s", members[i].class); (*env)->ExceptionClear(env); goto error; }
С помощью описанного выше метода разработчики могут разобрать класс и функции MediaCodec из уровня собственного кода. Дополнительные сведения об использовании см. по адресу http://developer.android.com/reference/android/media/MediaCodec.html.
Заключение
Метод оптимизации Intel был создан для помощи разработчикам в интеграции FFmpeg с MediaCodec: при этом поддерживаются самые разные форматы видео, а платформы Intel Atom под управлением Android получают аппаратный декодер.
Статьи по теме
Высококачественное сжатие видео: интеграция решения H.265/HEVC для платформ на базе Intel® Atom™ — Аndroid*: https://software.intel.com/en-us/android/articles/high-quality-video-compression-integrating-an-h265hevc-solution-for-intel-atom-based-android-platforms
Аппаратный кодек Android* — MediaCodec: https://software.intel.com/en-us/android/articles/android-hardware-codec-mediacodec
Справочные материалы
[1] FFmpeg: http://www.ffmpeg.org/index.html
[2] Загрузка FFmpeg: http://www.ffmpeg.org/olddownload.html
[3] Проект Yasm Modular Assembler: http://yasm.tortall.net/
[4] Потоковые расширения Intel® SIMD (Intel® SSE): https://software.intel.com/en-us/articles/performance-tools-for-software-developers-intel-compiler-options-for-sse-generation-and-processor-specific-optimizations.
Об авторе
Сонью Вань (Songyue Wang) — старший инженер по разработке приложений в отделе Intel® Software and Solutions Group (SSG), в подразделении Developer Relations Division, в составе группы Intel® Atom™ Processor Mobile Enabling Team. Сонью отвечает за поддержку приложений для Android на устройствах с процессорами Intel Atom. Среди его основных задач — оптимизация производительности мультимедиа на платформе Bay Trail, тесное сотрудничество с популярными поставщиками видео в КНР для поддержки кодировщика и декодера H.265/HEVC, а также реализация функциональности Intel® Wireless Display в решениях Android для платформ x86.