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

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

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

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

Фон

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

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

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 или ниже не включает поддержку паузы. Вы можете приостановить туннелированное воспроизведение только с помощью голодания A/V, но если внутренний буфер для видео большой (например, в компоненте OMX есть одна секунда данных), это делает паузу неотзывчивой.

В Android 5.1 или выше AudioFlinger поддерживает паузу и возобновление для прямых (туннелированных) аудиовыходов. Если HAL реализует паузу и возобновление, пауза и возобновление трека перенаправляется в HAL.

Последовательность вызовов «пауза, очистка, возобновление» соблюдается путем выполнения вызовов HAL в потоке воспроизведения (так же, как и при разгрузке).

Предложения по внедрению

Аудио HAL

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

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

HWC объединение видеокадров на основе аудио

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

,

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

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

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

Фон

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

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

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 или ниже не включает поддержку паузы. Вы можете приостановить туннелированное воспроизведение только с помощью голодания A/V, но если внутренний буфер для видео большой (например, в компоненте OMX есть одна секунда данных), это делает паузу неотзывчивой.

В Android 5.1 или выше AudioFlinger поддерживает паузу и возобновление для прямых (туннелированных) аудиовыходов. Если HAL реализует паузу и возобновление, пауза и возобновление трека перенаправляется в HAL.

Последовательность вызовов «пауза, очистка, возобновление» соблюдается путем выполнения вызовов HAL в потоке воспроизведения (так же, как и при разгрузке).

Предложения по внедрению

Аудио HAL

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

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

HWC объединение видеокадров на основе аудио

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