Вы можете реализовать мультимедийное туннелирование в Android framework 5.0 и выше. Хотя мультимедийное туннелирование не требуется для Android TV, оно обеспечивает наилучшие возможности для контента сверхвысокой четкости (4K).
Для Android 11 или более поздней версии вы можете реализовать мультимедийное туннелирование с аудио- и видеоконтентом, поступающим непосредственно из Tuner. Codec2
и AudioTrack
могут использовать идентификатор синхронизации HW от Tuner, который может соответствовать эталону часов программы (PCR) или каналу часов системного времени (STC).
Фон
Платформа мультимедиа Android обрабатывает аудио/видео контент четырьмя способами:
- Чистое программное обеспечение (локальное декодирование): процессор приложений (AP) локально декодирует звук в импульсно-кодовую модуляцию (ИКМ) без специального ускорения. Всегда используется для Ogg Vorbis и используется для MP3 и AAC, когда нет поддержки сжатой разгрузки.
- Разгрузка сжатого аудио отправляет сжатые аудиоданные непосредственно на процессор цифровых сигналов (DSP) и максимально удерживает точку доступа отключенной. Используйте для воспроизведения музыкальных файлов с выключенным экраном.
- Сквозная передача сжатого звука отправляет сжатый звук (в частности, AC3 и E-AC3) напрямую через HDMI на внешний телевизор или аудиоприемник без декодирования на устройстве Android TV. Часть видео обрабатывается отдельно.
- Мультимедийное туннелирование отправляет сжатые аудио- и видеоданные вместе. Когда закодированный поток принимается видео- и аудиодекодерами, он не возвращается в фреймворк. В идеале поток не прерывает точку доступа.
- Сквозная передача мультимедиа отправляет сжатые аудио- и видеоданные вместе из тюнера в видео- и аудиодекодеры без участия фреймворка.
![Блок-схема мультимедийного туннелирования](https://source.android.com/static/devices/tv/images/multimedia_tunneling_flo_diag.png?authuser=9&hl=ru)
Сравнение подходов
Чистое программное обеспечение | Разгрузка сжатого аудио | Передача сжатого аудио | Мультимедийное туннелирование | Прохождение мультимедиа | |
---|---|---|---|---|---|
Расшифровать местоположение | АП | DSP | Телевизор или аудио/видео ресивер (AVR) | телевизор или ресивер | телевизор или ресивер |
Обрабатывает аудио | да | да | да | да | нет |
Обрабатывает видео | да | нет | нет | да | нет |
Для разработчиков приложений
Создайте экземпляр SurfaceView
, получите идентификатор аудиосеанса, а затем создайте экземпляры AudioTrack
и MediaCodec
, чтобы обеспечить необходимую синхронизацию и конфигурации для воспроизведения и декодирования видеокадров.
Для Android 11 или более поздней версии в качестве альтернативы идентификатору аудиосеанса приложение может получить идентификатор синхронизации HW от Tuner и предоставить его экземплярам AudioTrack
и MediaCodec
для синхронизации аудио/видео.
Аудио/видео синхронизация
В режиме мультимедийного туннелирования аудио и видео синхронизируются по основным часам.
- Для Android 11 или более поздней версии PCR или STC от Tuner могут быть основными часами для синхронизации аудио/видео.
- Для Android 10 или более ранней версии аудиочасы являются основными часами, используемыми для воспроизведения аудио/видео.
Если туннелированный экземпляр видео MediaCodec
и экземпляры AudioTrack
связаны с экземпляром HW_AV_SYNC
в AudioTrack
, неявные часы, полученные из HW_AV_SYNC
ограничивают представление каждого видеокадра и аудиосэмпла на основе фактической метки времени представления аудио- или видеокадра (PTS).
Поток вызовов API
Для Android 11 или более поздней версии клиент может использовать идентификатор синхронизации HW от Tuner.
- Создайте экземпляр
SurfaceView
.SurfaceView sv = new SurfaceView(mContext);
- Получите идентификатор аудиосессии. Этот уникальный идентификатор используется при создании звуковой дорожки (
AudioTrack
). Он передается кодеку мультимедиа (MediaCodec
) и используется инфраструктурой мультимедиа для связывания путей аудио и видео.AudioManager am = mContext.getSystemService(AUDIO_SERVICE); int audioSessionId = am.generateAudioSessionId() // or, for Android 11 or higher int avSyncId = tuner.getAvSyncHwId();
- Создайте
AudioTrack
с аппаратной синхронизацией аудио/видеоAudioAttributes
.Диспетчер политики аудио запрашивает уровень аппаратной абстракции (HAL) для выхода устройства, который поддерживает
FLAG_HW_AV_SYNC
, и создает звуковую дорожку, напрямую подключенную к этому выходу без промежуточного микшера.AudioAttributes.Builder aab = new AudioAttributes.Builder(); aab.setUsage(AudioAttributes.USAGE_MEDIA); aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE); aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC); // or, for Android 11 or higher new tunerConfig = TunerConfiguration(0, avSyncId); aab.setTunerConfiguration(tunerConfig); AudioAttributes aa = aab.build(); AudioTrack at = new AudioTrack(aa);
- Создайте экземпляр видео
MediaCodec
и настройте его для туннелированного воспроизведения видео.// retrieve codec with tunneled video playback feature MediaFormat mf = MediaFormat.createVideoFormat(“video/hevc”, 3840, 2160); mf.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS); String codecName = mcl.findDecoderForFormat(mf); if (codecName == null) { return FAILURE; } // create codec and configure it mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId); // or, for Android 11 or higher mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId); MediaCodec mc = MediaCodec.createCodecByName(codecName); mc.configure(mf, sv.getSurfaceHolder().getSurface(), null, 0);
- Декодируйте видеокадры.
mc.start(); for (;;) { int ibi = mc.dequeueInputBuffer(timeoutUs); if (ibi >= 0) { ByteBuffer ib = mc.getInputBuffer(ibi); // fill input buffer (ib) with valid data ... mc.queueInputBuffer(ibi, ...); } // no need to dequeue explicitly output buffers. The codec // does this directly to the sideband layer. } mc.stop(); mc.release(); mc = null;
Примечание. Вы можете изменить порядок шагов 3 и 4 в этом процессе, как показано на двух рисунках ниже.
![Диаграмма звуковой дорожки, созданная до настройки кодека](https://source.android.com/static/devices/tv/images/audio_track_created_before_codec_configure.png?authuser=9&hl=ru)
![Схема звуковой дорожки, созданной после настройки кодека](https://source.android.com/static/devices/tv/images/audio_track_created_after_codec_configure.png?authuser=9&hl=ru)
Для производителей устройств
OEM-производителям следует создать отдельный компонент видеодекодера OpenMAX IL (OMX) для поддержки воспроизведения туннелированного видео. Этот компонент OMX должен объявить, что он способен к туннелированному воспроизведению (в media_codecs.xml
).
<Feature name=”tunneled-playback” required=”true” />
Компонент также должен поддерживать расширенный параметр OMX OMX.google.android.index.configureVideoTunnelMode
, который использует структуру ConfigureVideoTunnelModeParams
.
struct ConfigureVideoTunnelModeParams { OMX_U32 nSize; // IN OMX_VERSIONTYPE nVersion; // IN OMX_U32 nPortIndex; // IN OMX_BOOL bTunneled; // IN/OUT OMX_U32 nAudioHwSync; // IN OMX_PTR pSidebandWindow; // OUT };
Когда делается запрос на создание туннелированного MediaCodec
, платформа настраивает компонент OMX в туннелированном режиме (устанавливая для bTunneled
значение OMX_TRUE
) и передает связанное устройство вывода звука, созданное с флагом AUDIO_HW_AV_SYNC
, компоненту OMX (в nAudioHwSync
).
Если компонент поддерживает эту конфигурацию, он должен выделить дескриптор боковой полосы для этого кодека и передать его обратно через pSidebandWindow
. Дескриптор боковой полосы — это идентификационный тег для туннелируемого уровня, который позволяет аппаратному компоновщику (HW Composer) идентифицировать его. Если компонент не поддерживает эту конфигурацию, он должен установить для bTunneled
значение OMX_FALSE
.
Платформа извлекает туннелированный уровень (дескриптор боковой полосы), выделенный компонентом OMX, и передает его HW Composer. compositionType
тип этого слоя устанавливается в HWC_SIDEBAND
. (См. hardware/libhardware/include/hardware/hwcomposer.h
.)
HW Composer отвечает за получение новых буферов изображений из потока в соответствующее время (например, синхронизированных с соответствующим устройством вывода звука), компоновку их с текущим содержимым других слоев и отображение результирующего изображения. Это происходит независимо от обычного цикла подготовки/установки. Вызовы prepare/set происходят только при изменении других слоев или при изменении свойств слоя боковой полосы (таких как положение или размер).
Конфигурация
frameworks/av/services/audioflinger/AudioFlinger.cpp
HAL возвращает идентификатор HW_AV_SYNC
как десятичное представление строки символов 64-битного целого числа. (См. frameworks/av/services/audioflinger/AudioFlinger.cpp
.)
audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice(); char *reply = dev->get_parameters(dev, AUDIO_PARAMETER_HW_AV_SYNC); AudioParameter param = AudioParameter(String8(reply)); int hwAVSyncId; param.getInt(String8(AUDIO_PARAMETER_HW_AV_SYNC), hwAVSyncId);
рамки/AV/услуги/audioflinger/Threads.cpp
Платформа аудио должна найти выходной поток HAL, которому соответствует этот идентификатор сеанса, и запросить у HAL hwAVSyncId
с помощью set_parameters
.
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC=hwAVSyncId);
Конфигурация декодера OMX
MediaCodec.java
Платформа аудио находит соответствующий выходной поток HAL для этого идентификатора сеанса и извлекает идентификатор audio-hw-sync
, запрашивая у HAL флаг AUDIO_PARAMETER_STREAM_HW_AV_SYNC
с помощью get_parameters
.
// Retrieve HW AV sync audio output device from Audio Service // in MediaCodec.configure() if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) { int sessionId = 0; try { sessionId = (Integer)entry.getValue(); } catch (Exception e) { throw new IllegalArgumentException("Wrong Session ID Parameter!"); } keys[i] = "audio-hw-sync"; values[i] = AudioSystem.getAudioHwSyncForSession(sessionId); } // ...
Этот идентификатор синхронизации HW передается туннелируемому видеодекодеру OMX с помощью пользовательского параметра OMX.google.android.index.configureVideoTunnelMode
.
ACodec.cpp
После того, как вы получите идентификатор синхронизации аудиооборудования, ACodec использует его для настройки туннелированного видеодекодера, чтобы туннелированный видеодекодер знал, какую звуковую дорожку синхронизировать.
// Assume you're going to use tunneled video rendering. // Configure OMX component in tunneled mode and grab sideband handle (sidebandHandle) from OMX // component. native_handle_t* sidebandHandle; // Configure OMX component in tunneled mode status_t err = mOMX->configureVideoTunnelMode(mNode, kPortIndexOutput, OMX_TRUE, audioHwSync, &sidebandHandle);
OMXNodeInstance.cpp
Компонент OMX настраивается описанным выше методом configureVideoTunnelMode
.
// paraphrased OMX_INDEXTYPE index; OMX_STRING name = const_cast<OMX_STRING>( "OMX.google.android.index.configureVideoTunnelMode"); OMX_ERRORTYPE err = OMX_GetExtensionIndex(mHandle, name, &index); ConfigureVideoTunnelModeParams tunnelParams; InitOMXParams(&tunnelParams); tunnelParams.nPortIndex = portIndex; tunnelParams.bTunneled = tunneled; tunnelParams.nAudioHwSync = audioHwSync; err = OMX_SetParameter(mHandle, index, &tunnelParams); err = OMX_GetParameter(mHandle, index, &tunnelParams); sidebandHandle = (native_handle_t*)tunnelParams.pSidebandWindow;
ACodec.cpp
После настройки компонента OMX в туннельном режиме дескриптор боковой полосы связывается с поверхностью рендеринга.
err = native_window_set_sideband_stream(nativeWindow.get(), sidebandHandle); if (err != OK) { ALOGE("native_window_set_sideband_stream(%p) failed! (err %d).", sidebandHandle, err); return err; }
Затем компоненту отправляется подсказка о максимальном разрешении, если она присутствует.
// Configure max adaptive playback resolution - as for any other video decoder int32_t maxWidth = 0, maxHeight = 0; if (msg->findInt32("max-width", &maxWidth) && msg->findInt32("max-height", &maxHeight)) { err = mOMX->prepareForAdaptivePlayback( mNode, kPortIndexOutput, OMX_TRUE, maxWidth, maxHeight); }
Приостановить поддержку
Android 5.0 и более ранние версии не поддерживают паузу. Вы можете приостановить туннелированное воспроизведение только с помощью A/V голодания, но если внутренний буфер для видео велик (например, в компоненте OMX есть 1 секунда данных), пауза выглядит невосприимчивой.
В Android 5.1 и более поздних версиях AudioFlinger поддерживает паузу и возобновление для прямых (туннелированных) аудиовыходов. Если HAL реализует паузу/возобновление, пауза/возобновление дорожки перенаправляется в HAL.
Последовательность вызовов паузы, сброса и возобновления соблюдается путем выполнения вызовов HAL в потоке воспроизведения (аналогично разгрузке).
Поддержка кодека 2
Для Android 11 или более поздней версии Codec2 поддерживает туннелированное воспроизведение.
CCodec.cpp
Для поддержки туннельного воспроизведения Codec2 работает аналогично OMX. Чтобы поддерживать идентификатор синхронизации HW от Tuner, Codec2 ищет идентификатор синхронизации из источников, указанных ниже.
sp<ANativeWindow> nativeWindow = static_cast<ANativeWindow *>(surface.get()); int32_t audioHwSync = 0; if (!msg->findInt32("hw-av-sync-id", &audioHwSync)) { if (!msg->findInt32("audio-hw-sync", &audioHwSync)) { } } err = configureTunneledVideoPlayback(comp, audioHwSync, nativeWindow);
Предложения по реализации
Аудио ХАЛ
Для Android 11 идентификатор синхронизации HW из PCR или STC можно использовать для синхронизации аудио/видео, поэтому поддерживается только поток видео.
Для Android 10 или более ранней версии устройства, поддерживающие туннелированное воспроизведение видео, должны иметь по крайней мере один профиль потока вывода звука с флагами FLAG_HW_AV_SYNC
и AUDIO_OUTPUT_FLAG_DIRECT
в файле audio_policy.conf
. Эти флаги используются для установки системных часов по звуковым часам.
ОМКС
Производители устройств должны иметь отдельный компонент OMX для воспроизведения туннелированного видео. У производителей могут быть дополнительные компоненты OMX для других типов воспроизведения аудио и видео, например безопасного воспроизведения.
Этот компонент должен указать 0 буферов ( nBufferCountMin
, nBufferCountActual
) на своем выходном порту.
Туннельный компонент также должен реализовывать расширение OMX.google.android.index.prepareForAdaptivePlayback setParameter
.
Туннелируемый компонент должен указать свои возможности в файле media_codecs.xml
и объявить функцию туннелированного воспроизведения. Также следует разъяснить любые ограничения на размер кадра, выравнивание или битрейт.
<MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC.tunneled"
type="video/avc" >
<Feature name="adaptive-playback" />
<Feature name="tunneled-playback" required=”true” />
<Limit name="size" min="32x32" max="3840x2160" />
<Limit name="alignment" value="2x2" />
<Limit name="bitrate" range="1-20000000" />
...
</MediaCodec>
Если для поддержки туннелированного и нетуннельного декодирования используется один и тот же компонент OMX, то функция туннелированного воспроизведения должна быть необязательной. И туннелированные, и нетуннелированные декодеры имеют одинаковые ограничения возможностей.
<MediaCodec name="OMX.OEM_NAME.VIDEO.DECODER.AVC" type="video/avc" > <Feature name="adaptive-playback" /> <Feature name="tunneled-playback" /> <Limit name="size" min="32x32" max="3840x2160" /> <Limit name="alignment" value="2x2" /> <Limit name="bitrate" range="1-20000000" /> ... </MediaCodec>
Аппаратный композитор
При наличии на дисплее туннелированного слоя (слоя с типом compositionType
HWC_SIDEBAND
) sidebandStream
слоя является дескриптором боковой полосы, выделенным видеокомпонентом OMX.
HW Composer синхронизирует декодированные видеокадры (из туннелированного компонента OMX) с соответствующей звуковой дорожкой (с идентификатором audio-hw-sync
). Когда новый видеокадр становится текущим, HW Composer объединяет его с текущим содержимым всех слоев, полученных во время последнего вызова подготовки/установки, и отображает результирующее изображение. Вызовы prepare/set происходят только при изменении других слоев или при изменении свойств слоя боковой полосы (таких как положение или размер).
На рисунке 4 представлен HW Composer, работающий с аппаратным синхронизатором (или ядром/драйвером) для объединения видеокадров (7b) с последней композицией (7a) для отображения в нужное время на основе аудио (7c).
![Схема аппаратного компоновщика, объединяющего видеокадры на основе аудио](https://source.android.com/static/devices/tv/images/hardware_composer_and_synchronizer.png?authuser=9&hl=ru)