Мультимедийное туннелирование

Мультимедийное туннелирование позволяет передавать сжатые видеоданные через аппаратный видеодекодер непосредственно на дисплей, минуя обработку кодом приложения или фреймворка Android. Код, специфичный для устройства, расположенный ниже стека Android, определяет, какие видеокадры и когда отправлять на дисплей, сравнивая временные метки отображения видеокадров с одним из следующих типов внутренних часов:

  • Для воспроизведения видео по запросу в Android 5 и выше часы AudioTrack синхронизируются с временными метками аудиопрезентации , передаваемыми приложением.

  • Для воспроизведения прямой трансляции в Android 11 или более поздней версии требуется программный опорный тактовый генератор (PCR) или системный тактовый генератор (STC), управляемый тюнером .

Фон

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

Поскольку туннелированное воспроизведение видео обходит код приложения и сокращает количество процессов, работающих с видео, оно может обеспечить более эффективный рендеринг видео в зависимости от реализации OEM. Оно также может обеспечить более точную каденцию видео и синхронизацию с выбранным тактовым генератором (PRC, STC или аудио), избегая проблем с синхронизацией, возникающих из-за возможного расхождения между синхронизацией запросов Android на рендеринг видео и синхронизацией аппаратных vsync. Однако туннелирование также может ухудшить поддержку графических эффектов, таких как размытие или скругление углов в окнах «картинка в картинке» (PiP), поскольку буферы обходят графический стек Android.

На следующей диаграмме показано, как туннелирование упрощает процесс воспроизведения видео.

сравнение традиционного и туннельного режимов

Рисунок 1. Сравнение традиционных и туннелированных процессов воспроизведения видео

Для разработчиков приложений

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

Для воспроизведения видео по запросу в Android 5 и выше:

  1. Создайте экземпляр SurfaceView .

  2. Создайте экземпляр audioSessionId .

  3. Создайте экземпляры AudioTrack и MediaCodec с экземпляром audioSessionId , созданным на шаге 2.

  4. Поместить аудиоданные в AudioTrack с меткой времени представления для первого аудиокадра в аудиоданных.

Для воспроизведения прямой трансляции на устройствах Android 11 и выше:

  1. Создайте экземпляр SurfaceView .

  2. Получите экземпляр avSyncHwId из Tuner .

  3. Создайте экземпляры 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 .

    • Если этот параметр не установлен, поведение устройства определяет производитель оригинального оборудования.

  • Если аудиоданные не поступают в AudioTrack и буферы пусты (недозаполнение аудио), воспроизведение видео останавливается до тех пор, пока не будут записаны дополнительные аудиоданные, поскольку аудиосинхронизация больше не осуществляется.

  • Во время воспроизведения во временных метках аудиопрезентации могут появляться разрывы, которые приложение не может исправить. В этом случае производитель исправляет отрицательные паузы, останавливая текущий видеокадр, а положительные — либо пропуская видеокадры, либо вставляя аудиокадры тишины (в зависимости от реализации производителя). Позиция кадра 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, &params);
if (c2err == C2_OK && params.size() == 1u) {
    C2PortTunnelHandleTuning::output *videoTunnelSideband =
            C2PortTunnelHandleTuning::output::From(params[0].get());
    return OK;
}

Если компонент поддерживает эту конфигурацию, он должен выделить дескриптор боковой полосы этому кодеку и передать его обратно через C2PortTunnelHandlingTuning чтобы HWC мог идентифицировать связанный кодек.

Аудио HAL

Для воспроизведения видео по запросу 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 в потоке воспроизведения (аналогично разгрузке).

Предложения по реализации

Аудио 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)

Если на дисплее присутствует туннелируемый слой (слой с compositionType HWC_SIDEBAND ), то sidebandStream слоя — это дескриптор боковой полосы, выделенный видеокомпонентом OMX.

HWC синхронизирует декодированные видеокадры (из туннелируемого OMX-компонента) с соответствующей звуковой дорожкой (с идентификатором audio-hw-sync ). Когда новый видеокадр становится текущим, HWC компонует его с текущим содержимым всех слоёв, полученных во время последнего вызова функции подготовки или установки, и отображает полученное изображение. Вызовы функции подготовки или установки происходят только при изменении других слоёв или свойств слоя боковой полосы (например, положения или размера).

На следующем рисунке представлена ​​работа HWC с синхронизатором оборудования (или ядра, или драйвера) для объединения видеокадров (7b) с последней композицией (7a) для отображения в нужное время на основе звука (7c).

HWC объединяет видеокадры на основе звука

Рисунок 2. Синхронизатор оборудования HWC (или ядра, или драйвера)