Monday, October 1, 2012

Android Media API

Вместе с выходом Android 4.1 был представлен новый Меdia API Поначалу данное событие мною воспринялось как многообещающее, но после попыток раскурить его, энтузиазма поубавилось. И так попорядку.

Видео

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

На первых парах API выглядит достаточно простым и стройным. Полистав слайды, кажется, что демо можно накидать за пару часов. Это почти правда. Как выяснилось потом, на слайдах нет самых интересных кусков кода. Поэтому к некоторым вещам приходилось идти империческим путем т.е. методом проб и ошибок. Загуглив, я лишь нашел javadoc и один юнит тест :). Негусто. Качнул исходники Android 4.1 и "грепнул" их - толку тоже мало. В общем самым полезным из всего найденного оказался юнит тест.

Абстрактный декодер

Напишим три класса: абстрактный декодер и два наследника. Один для видео другой для аудио. В наших примерах мы будем играть либо видео, либо аудио. Позже я объясню почему. А сейчас код:

Класс представляет собой тред, который "молотит" до момента пока в очереди есть декодированные данные(иначе говоря есть что отрисовывать/проигрывать). Важный момент - это инициализации входного/выходного буфера, и их заполнение/вычитка - методы dequeueInput()/dequeueOutput(). На начальном этапе трудности и вопросы возникает именно здесь.

Видео декодер

Класс выглядить достаточно просто. Нам нужно передать ссылку на канву и декодер отрисует все за нас. Порадовало то, что не нужно заморачиваться со всякого рода мелочами: формат пиксела и т.д. Собственно код: Второй параметр метода releaseOutputBuffer(..., true) "говорит", что мы рендерим картинку средствами декодера.

Аудио декодер

Кода на пару строк больше - создаем аудио трэк, а в качестве канвы передаем null(ибо для звука в ней нет необходимости).

Проблема номер один - синхронизация

Если честно, не нашел вменяемого способа, как синхронизировать аудио/видео в рамках данной задачи. Опробовав разные методы, пришел к очень примитивному, но в то же время простому - синхронизация по внешнему таймеру. Есть некий тред, который "посылает сигнал" декодеру, передавая текущую временную метку. Декодер получив уведомление, "проверяет" есть ли в очереди пакет PTS(presentation time stamp) которого равен либо меньше текущего значения метки. Если есть, то дергаем processDecodedData(). Повторюсь, способ убогий и не обеспечивает должной точности синхронизации, но он работает и для простенького демо сойдет. Пример синхронизации аудио(для видео абсолютно тоже самое за одним лишь отличием - вмето AudioTrackDecoder создать экземпляр VideoTrackDecoder с доп. параметром surface):

Проблема номер два - MediaExtractor

Как говорит дока - сие есть демультиплексор. Задача которого извлекать сырые данные. Если мы хотим работать по отдельности только с аудио, либо только с видео, то вопросов нет. А вот если одновременно, например, проиграть видео файл, то парочку есть. Перед чтением необходимо вызвать метод selectTrack, указать номер трэка в контейнере с которым мы собираемся работать. Допустим есть два трэда - один аудио декодер, другой видео декодер. Есть экземпляр медиа экстрактора расшаренный между этими тредами. При такой схеме у меня дико падала производительность, видео было куцее, звук прерывистый. Второй вариант - создать отдельный медиа экстрактор для каждого трэда. Показал гораздо лучшие результаты чем первый, но с точки зрения здравого смысла он не верный. Т.к. ресурс один, а соединений два(одно соединение для видео, другое - для аудио).

Выводы

Плюсы:
  • Pure java - не нужно париться с NDK
  • Относительно простой API
Минусы:
  • Нет примеров

No comments:

Post a Comment