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

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

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

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

Фон

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

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

    • Если этот параметр не установлен, 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 должен возвращать дескриптор боковой полосы, который можно использовать для связывания кодека со слоем аппаратного компоновщика (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 отвечает за получение новых буферов изображения с выхода кодека в соответствующее время, либо синхронизированных с соответствующим выходным аудиопотоком, либо с эталонными часами программы тюнера, составление буферов с текущим содержимым других слоев и отображение результирующего изображения. Это происходит независимо от обычного цикла подготовки и установки. Вызовы prepare и set происходят только при изменении других слоев или при изменении свойств слоя боковой полосы (таких как положение или размер).

ОМКС

Компонент туннельного декодера должен поддерживать следующее:

  • Установка расширенного параметра 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 мог идентифицировать связанный кодек.

Аудио ХАЛ

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

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

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

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

HWC, объединяющий видеокадры на основе аудио

Рис. 2. Аппаратный (или ядро, или драйвер) синхронизатор HWC