Туннелирование мультимедиа позволяет передавать сжатые видеоданные через аппаратный видеодекодер непосредственно на дисплей без обработки кодом приложения или кодом платформы Android. Код конкретного устройства под стеком Android определяет, какие видеокадры отправлять на дисплей и когда их отправлять, сравнивая временные метки представления видеокадров с одним из следующих типов внутренних часов:
Для воспроизведения видео по требованию в Android 5 или более поздней версии часы
AudioTrack
синхронизируются с временными метками аудиопрезентации , передаваемыми приложением.Для воспроизведения прямой трансляции в Android 11 или более поздней версии используются программные эталонные часы (PCR) или системные часы (STC), управляемые тюнером.
Фон
Традиционное воспроизведение видео на Android уведомляет приложение о декодировании сжатого видеокадра. Затем приложение выводит декодированный видеокадр на дисплей для визуализации в то же время системных часов, что и соответствующий аудиокадр, получая исторические экземпляры AudioTimestamps
для расчета правильного времени.
Поскольку туннельное воспроизведение видео обходит код приложения и уменьшает количество процессов, воздействующих на видео, оно может обеспечить более эффективный рендеринг видео в зависимости от реализации OEM. Он также может обеспечить более точную частоту видео и синхронизацию с выбранными часами (PRC, STC или аудио), избегая проблем с синхронизацией, возникающих из-за потенциального несоответствия между временем запросов Android на рендеринг видео и временем истинной аппаратной вертикальной синхронизации. Однако туннелирование также может уменьшить поддержку эффектов графического процессора, таких как размытие или закругленные углы в окнах «картинка в картинке» (PiP), поскольку буферы обходят графический стек Android.
На следующей диаграмме показано, как туннелирование упрощает процесс воспроизведения видео.
Рисунок 1. Сравнение традиционного и туннелированного процессов воспроизведения видео
Для разработчиков приложений
Поскольку большинство разработчиков приложений интегрируются с библиотекой для реализации воспроизведения, в большинстве случаев реализация требует только перенастройки этой библиотеки для туннелированного воспроизведения. Для низкоуровневой реализации туннелированного видеоплеера используйте следующие инструкции.
Для воспроизведения видео по требованию в Android 5 или более поздней версии:
Создайте экземпляр
SurfaceView
.Создайте экземпляр
audioSessionId
.Создайте экземпляры
AudioTrack
иMediaCodec
с помощью экземпляраaudioSessionId
, созданного на шаге 2.Поместите аудиоданные в очередь в
AudioTrack
с отметкой времени представления для первого аудиокадра в аудиоданных.
Для воспроизведения прямой трансляции в Android 11 или более поздней версии:
Создайте экземпляр
SurfaceView
.Получите экземпляр
avSyncHwId
отTuner
.Создайте экземпляры
AudioTrack
иMediaCodec
с помощью экземпляраavSyncHwId
, созданного на шаге 2.
Поток вызовов API показан в следующих фрагментах кода:
aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);
// configure for audio clock sync
aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
// or, for tuner clock sync (Android 11 or higher)
new tunerConfig = TunerConfiguration(0, avSyncId);
aab.setTunerConfiguration(tunerConfig);
if (codecName == null) {
return FAILURE;
}
// configure for audio clock sync
mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
// or, for tuner clock sync (Android 11 or higher)
mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);
Поведение воспроизведения видео по требованию
Поскольку туннелированное воспроизведение видео по запросу неявно связано с воспроизведением AudioTrack
, поведение туннелированного воспроизведения видео может зависеть от поведения воспроизведения звука.
На большинстве устройств по умолчанию видеокадр не отображается до тех пор, пока не начнется воспроизведение звука. Однако приложению может потребоваться визуализировать видеокадр перед началом воспроизведения звука, например, чтобы показать пользователю текущую позицию видео во время поиска.
Чтобы сигнализировать о том, что первый видеокадр в очереди должен быть отображен сразу после его декодирования, установите для параметра
PARAMETER_KEY_TUNNEL_PEEK
значение1
. Когда сжатые видеокадры переупорядочиваются в очереди (например, при наличии B-кадров ), это означает, что первый отображаемый видеокадр всегда должен быть I-кадром.Если вы не хотите, чтобы первый видеокадр в очереди отображался до начала воспроизведения звука, установите для этого параметра значение
0
.Если этот параметр не установлен, поведение устройства определяет OEM-производитель.
Если аудиоданные не передаются в
AudioTrack
и буферы пусты (недополнение звука), воспроизведение видео останавливается до тех пор, пока не будет записано больше аудиоданных, поскольку тактовый сигнал аудио больше не движется вперед.Во время воспроизведения в временных метках аудиопрезентации могут появляться разрывы, которые приложение не может исправить. Когда это происходит, OEM исправляет отрицательные пробелы, останавливая текущий видеокадр, а положительные пробелы либо удаляя видеокадры, либо вставляя беззвучные аудиокадры (в зависимости от реализации OEM). Положение кадра
AudioTimestamp
не увеличивается для вставленных тихих аудиокадров.
Для производителей устройств
Конфигурация
OEM-производителям следует создать отдельный видеодекодер для поддержки туннельного воспроизведения видео. Этот декодер должен объявить о возможности туннелированного воспроизведения в файле media_codecs.xml
:
<Feature name="tunneled-playback" required="true"/>
Когда туннелированный экземпляр MediaCodec
настроен с идентификатором аудиосеанса, он запрашивает у AudioFlinger
этот идентификатор HW_AV_SYNC
:
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);
}
Во время этого запроса AudioFlinger
извлекает идентификатор HW_AV_SYNC
из основного аудиоустройства и внутренне связывает его с идентификатором аудиосеанса:
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);
Если экземпляр AudioTrack
уже создан, идентификатор HW_AV_SYNC
передается в выходной поток с тем же идентификатором аудиосеанса. Если он еще не создан, идентификатор HW_AV_SYNC
передается в выходной поток во время создания AudioTrack
. Это делается потоком воспроизведения :
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);
Идентификатор HW_AV_SYNC
, независимо от того, соответствует ли он выходному аудиопотоку или конфигурации Tuner
, передается в компонент OMX или Codec2, чтобы OEM-код мог связать кодек с соответствующим выходным потоком аудио или потоком тюнера.
Во время настройки компонента компонент OMX или Codec2 должен возвращать дескриптор боковой полосы, который можно использовать для связи кодека с уровнем Hardware Composer (HWC). Когда приложение связывает поверхность с MediaCodec
, этот дескриптор боковой полосы передается в HWC через SurfaceFlinger
, который настраивает слой как слой боковой полосы.
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;
}
HWC отвечает за получение новых буферов изображения с выхода кодека в соответствующее время, синхронизированное либо с соответствующим выходным потоком аудио, либо с эталонными тактовыми импульсами программы тюнера, объединение буферов с текущим содержимым других слоев и отображение результирующего изображения. Это происходит независимо от обычного цикла подготовки и установки. Вызовы подготовки и установки происходят только тогда, когда изменяются другие слои или когда изменяются свойства слоя боковой полосы (например, положение или размер).
ОМХ
Компонент туннелированного декодера должен поддерживать следующее:
Установка расширенного параметра
OMX.google.android.index.configureVideoTunnelMode
, который использует структуруConfigureVideoTunnelModeParams
для передачи идентификатораHW_AV_SYNC
, связанного с устройством вывода звука.Настройка параметра
OMX_IndexConfigAndroidTunnelPeek
, который сообщает кодеку отображать или не отображать первый декодированный видеокадр, независимо от того, началось ли воспроизведение звука.Отправка события
OMX_EventOnFirstTunnelFrameReady
, когда первый туннелированный видеокадр декодирован и готов к визуализации.
Реализация AOSP настраивает туннельный режим в ACodec
через OMXNodeInstance
, как показано в следующем фрагменте кода:
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;
Если компонент поддерживает эту конфигурацию, он должен выделить этому кодеку дескриптор боковой полосы и передать его обратно через элемент pSidebandWindow
, чтобы HWC мог идентифицировать связанный кодек. Если компонент не поддерживает эту конфигурацию, ему следует установить для bTunneled
значение OMX_FALSE
.
Кодек2
В Android 11 или более поздней версии Codec2
поддерживает туннелированное воспроизведение. Компонент декодера должен поддерживать следующее:
Настройка
C2PortTunneledModeTuning
, который настраивает туннельный режим и передаетHW_AV_SYNC
, полученный либо от устройства вывода звука, либо от конфигурации тюнера.Запрос
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE
для выделения и получения дескриптора боковой полосы для HWC.Обработка
C2_PARAMKEY_TUNNEL_HOLD_RENDER
при подключении кC2Work
, который инструктирует кодек декодировать и сигнализировать о завершении работы, но не визуализировать выходной буфер до тех пор, пока 1) кодек позже не получит указание визуализировать его, или 2) не начнется воспроизведение звука.Обработка
C2_PARAMKEY_TUNNEL_START_RENDER
, который предписывает кодеку немедленно визуализировать кадр, отмеченныйC2_PARAMKEY_TUNNEL_HOLD_RENDER
, даже если воспроизведение звука еще не началось.Оставьте
debug.stagefright.ccodec_delayed_params
ненастроенным (рекомендуется). Если вы его настроили, установите значениеfalse
.
Реализация AOSP настраивает туннельный режим в CCodec
через C2PortTunnelModeTuning
, как показано в следующем фрагменте кода:
if (msg->findInt32("audio-hw-sync", &tunneledPlayback->m.syncId[0])) {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::AUDIO_HW_SYNC;
} else if (msg->findInt32("hw-av-sync-id", &tunneledPlayback->m.syncId[0])) {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::HW_AV_SYNC;
} else {
tunneledPlayback->m.syncType =
C2PortTunneledModeTuning::Struct::sync_type_t::REALTIME;
tunneledPlayback->setFlexCount(0);
}
c2_status_t c2err = comp->config({ tunneledPlayback.get() }, C2_MAY_BLOCK,
failures);
std::vector<std::unique_ptr<C2Param>> params;
c2err = comp->query({}, {C2PortTunnelHandleTuning::output::PARAM_TYPE},
C2_DONT_BLOCK, ¶ms);
if (c2err == C2_OK && params.size() == 1u) {
C2PortTunnelHandleTuning::output *videoTunnelSideband =
C2PortTunnelHandleTuning::output::From(params[0].get());
return OK;
}
Если компонент поддерживает эту конфигурацию, он должен выделить этому кодеку дескриптор боковой полосы и передать его обратно через C2PortTunnelHandlingTuning
, чтобы HWC мог идентифицировать связанный кодек.
Аудио ХАЛ
Для воспроизведения видео по требованию Audio HAL получает временные метки аудиопрезентации вместе с аудиоданными в формате с обратным порядком байтов внутри заголовка, который находится в начале каждого блока аудиоданных, записываемых приложением:
struct TunnelModeSyncHeader {
// The 32-bit data to identify the sync header (0x55550002)
int32 syncWord;
// The size of the audio data following the sync header before the next sync
// header might be found.
int32 sizeInBytes;
// The presentation timestamp of the first audio sample following the sync
// header.
int64 presentationTimestamp;
// The number of bytes to skip after the beginning of the sync header to find the
// first audio sample (20 bytes for compressed audio, or larger for PCM, aligned
// to the channel count and sample size).
int32 offset;
}
Чтобы HWC синхронизировал видеокадры с соответствующими аудиокадрами, Audio HAL должен проанализировать заголовок синхронизации и использовать временную метку представления для повторной синхронизации часов воспроизведения с рендерингом звука. Для повторной синхронизации при воспроизведении сжатого звука Audio HAL может потребоваться проанализировать метаданные внутри сжатых аудиоданных, чтобы определить продолжительность его воспроизведения.
Приостановить поддержку
Android 5 или более ранней версии не поддерживает паузу. Вы можете приостановить туннелированное воспроизведение только из-за нехватки аудио/видео, но если внутренний буфер для видео велик (например, в компоненте OMX имеется одна секунда данных), пауза будет выглядеть нереагирующей.
В Android 5.1 или выше AudioFlinger
поддерживает паузу и возобновление прямого (туннельного) аудиовыхода. Если HAL реализует паузу и возобновление, дорожка паузы и возобновления пересылается в HAL.
Последовательность вызовов паузы, сброса и возобновления соблюдается при выполнении вызовов HAL в потоке воспроизведения (так же, как и при разгрузке).
Предложения по реализации
Аудио ХАЛ
В 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>
Аппаратный композитор (HWC)
Когда на дисплее имеется туннельный слой (слой с HWC_SIDEBAND
compositionType
), sidebandStream
слоя — это дескриптор боковой полосы, выделенный видеокомпонентом OMX.
HWC синхронизирует декодированные видеокадры (из туннелированного компонента OMX) с соответствующей звуковой дорожкой (с идентификатором audio-hw-sync
). Когда новый видеокадр становится текущим, HWC объединяет его с текущим содержимым всех слоев, полученных во время последнего вызова подготовки или установки, и отображает полученное изображение. Вызовы подготовки или установки происходят только тогда, когда изменяются другие слои или когда изменяются свойства слоя боковой полосы (например, положение или размер).
На следующем рисунке показан HWC, работающий с аппаратным синхронизатором (или ядром, или драйвером) для объединения видеокадров (7b) с последней композицией (7a) для отображения в нужное время на основе звука (7c).
Рисунок 2. Аппаратный синхронизатор HWC (или ядра, или драйвера)