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

Вы можете реализовать мультимедийное туннелирование в 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. Часть видео обрабатывается отдельно.
  • Мультимедийное туннелирование отправляет сжатые аудио- и видеоданные вместе. Когда закодированный поток принимается видео- и аудиодекодерами, он не возвращается в фреймворк. В идеале поток не прерывает точку доступа.
  • Сквозная передача мультимедиа отправляет сжатые аудио- и видеоданные вместе из тюнера в видео- и аудиодекодеры без участия фреймворка.
Блок-схема мультимедийного туннелирования
Рис. 1. Мультимедийный поток туннелирования

Сравнение подходов

Чистое программное обеспечение Разгрузка сжатого аудио Передача сжатого аудио Мультимедийное туннелирование Прохождение мультимедиа
Расшифровать местоположение АП 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.

  1. Создайте экземпляр SurfaceView .
    SurfaceView sv = new SurfaceView(mContext);
  2. Получите идентификатор аудиосессии. Этот уникальный идентификатор используется при создании звуковой дорожки ( AudioTrack ). Он передается кодеку мультимедиа ( MediaCodec ) и используется инфраструктурой мультимедиа для связывания путей аудио и видео.
    AudioManager am = mContext.getSystemService(AUDIO_SERVICE);
    int audioSessionId = am.generateAudioSessionId()
    // or, for Android 11 or higher
    int avSyncId = tuner.getAvSyncHwId();
  3. Создайте 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);
    
  4. Создайте экземпляр видео 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);
    
  5. Декодируйте видеокадры.
    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 в этом процессе, как показано на двух рисунках ниже.

Диаграмма звуковой дорожки, созданная до настройки кодека
Рис. 2. Аудиодорожка, созданная до настройки кодека
Схема звуковой дорожки, созданной после настройки кодека
Рис. 3. Аудиодорожка, созданная после настройки кодека

Для производителей устройств

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).

Схема аппаратного компоновщика, объединяющего видеокадры на основе аудио
Рисунок 4. HW Composer, работающий с синхронизатором HW (или ядром/драйвером)