Мультимедийное туннелирование, также известное как туннельный режим, позволяет передавать сжатые видеоданные через аппаратный видеодекодер непосредственно на дисплей, минуя обработку кодом приложения или кодом платформы Android. Специфический для устройства код, находящийся ниже стека Android, определяет, какие видеокадры отправлять на дисплей и когда, сравнивая метки времени отображения видеокадров с одним из следующих типов внутренних часов:
Для воспроизведения видео по запросу в Android 5 и выше используется синхронизация часов
AudioTrackс временными метками аудиопрезентации, передаваемыми приложением.Для воспроизведения прямых трансляций в Android 11 и выше используется программный опорный тактовый генератор (PCR) или системный тактовый генератор (STC), управляемый тюнером.
Фон
Non-tunnel mode video playback on Android notifies the app when a compressed video frame has been decoded. The app then releases the decoded video frame to the display to be rendered at the same system clock time as the corresponding audio frame, retrieving historical AudioTimestamp instances to calculate the correct timing.
Поскольку воспроизведение видео через туннель обходит код приложения и уменьшает количество процессов, обрабатывающих видео, оно может обеспечить более эффективную отрисовку видео в зависимости от реализации производителя. Оно также может обеспечить более точную частоту воспроизведения видео и синхронизацию с выбранным тактовым генератором (PRC, STC или аудио), избегая проблем со временем, возникающих из-за потенциального расхождения между временем запросов Android на рендеринг видео и временем истинной аппаратной вертикальной синхронизации. Однако туннелирование также может снизить поддержку эффектов GPU, таких как размытие или скругленные углы в окнах «картинка в картинке» (PiP), поскольку буферы обходят графический стек Android.
На следующей диаграмме показано, как туннелирование упрощает процесс воспроизведения видео.

Рисунок 1. Сравнение процессов воспроизведения видео без туннелирования и с туннелированием.
Для разработчиков приложений
Because most app developers integrate with a library for playback implementation, in most cases implementation requires only reconfiguring that library for tunneled playback. For low-level implementation of a tunneled video player, use the following instructions.
Для воспроизведения видео по запросу в 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 , поведение воспроизведения видео через туннель может зависеть от поведения воспроизведения звука.
On most devices, by default, a video frame isn't rendered until audio playback begins. However, the app might need to render a video frame before starting audio playback, for example, to show the user the current video position while seeking.
To signal that the first queued video frame should be rendered as soon as it's decoded, set the
PARAMETER_KEY_TUNNEL_PEEKparameter to1. When compressed video frames are reordered in the queue (such as when B-frames are present), this means the first displayed video frame should always be an I-frame.Если вы не хотите, чтобы первый кадр видео из очереди отображался до начала воспроизведения звука, установите этот параметр равным
0.Если этот параметр не задан, поведение устройства определяется производителем оборудования.
Если в
AudioTrackне поступают аудиоданные, а буферы пусты (аудио недополнение), воспроизведение видео приостанавливается до тех пор, пока не будут записаны дополнительные аудиоданные, поскольку тактовая частота аудиосигнала больше не увеличивается.Во время воспроизведения в метках времени аудиозаписи могут появляться разрывы, которые приложение не может исправить. В этом случае производитель исправляет отрицательные промежутки, задерживая текущий видеокадр, а положительные — либо удаляя видеокадры, либо вставляя кадры тишины (в зависимости от реализации производителя). Позиция кадра
AudioTimestampне увеличивается для вставленных кадров тишины.
Последовательность точного поиска
Точный поиск позволяет найти определенную точку в видео. В отличие от поиска по ключевым кадрам, который переходит только к ближайшему I-кадру и может отклоняться от целевой позиции на несколько секунд, точный поиск отображает видео точно в запрошенную временную метку. Соблюдение этой конкретной последовательности API позволяет приложению бесперебойно выполнять фоновую предварительную обработку и синхронизацию по времени. Это гарантирует мгновенное отображение целевого кадра при возобновлении воспроизведения.
Для выполнения точного поиска следуйте порядку выполнения, показанному на рисунке 2:

Рисунок 2. Последовательность действий для достижения точного поиска.
Ключевые детали включают:
Параллельное выполнение: Вы можете выполнять шаги в рамках одного
parблока одновременно. Например, вызовыMediaCodecдля видео не зависят отAudioTrack.Sequential dependencies: Call all operations within the first
parbox before advancing to the secondparbox. Specifically, the app must ensure thatAudioTrack.writeand buffers in videoMediaCodecare queued prior to callingAudioTrack.play.
Последовательность воспроизведения с переменной скоростью
Variable speed playback lets you play video at a faster or slower rate than normal speed. This feature is commonly used by apps to allow users to consume content faster (such as playing educational lectures or podcasts at 1.5x or 2.0x to save time) or slower (such as analyzing athletic plays or instructional videos at 0.5x).
Для установки скорости следуйте порядку выполнения, показанному на рисунке 3:

Рисунок 3. Последовательность действий для установки скорости.
Следующие модели поведения и технические требования не отражены на диаграмме последовательности, представленной на рисунке 3:
AudioTrack.getTimestampreturnsframePositionbased on the original audio input frequency. For example, with 44100 Hz input and play speed 2.0x, after playing 2 seconds,AudioTrack.getTimestampreturnsframePositionof 176400.Если приложение вызывает
setSpeed(1.5)и это проходит успешно, а затем приложение вызываетsetSpeed(30)и это проходит неудачно, воспроизведение остается в режиме 1.5x.Если звук отключен (с помощью
setVolume), приложению все равно необходимо отправлять аудиобуферы, поскольку видеокадры отображаются на основе положения звука.Высота тона звука сохраняется при изменении скорости.
Скорость воспроизведения не зависит от других действий при воспроизведении.
Пример 1: Если скорость воспроизведения составляет 1,5x, а
AudioTrackприостановлено, то после возобновленияAudioTrackскорость останется на уровне 1,5x.Пример 2: Если скорость воспроизведения составляет 1,5x, и пользователь переходит на другой PTS, то, следуя рисунку 2, скорость воспроизведения останется на уровне 1,5x.
To help ensure that all frames are decoded in time to be rendered at the chosen speed, set
KEY_OPERATING_RATEto match the product of the video frame rate and the playback speed. IfKEY_OPERATING_RATEis not set high enough, the codec might not decode frames fast enough, causing unintended frame drops during playback.- Пример: Если исходная частота кадров контента составляет 60 кадров в секунду, а скорость воспроизведения — 2x, то установите
KEY_OPERATING_RATEравным120.
- Пример: Если исходная частота кадров контента составляет 60 кадров в секунду, а скорость воспроизведения — 2x, то установите
Repeatedly setting speed with different supported speeds shouldn't trigger any errors, and the playback behavior after the last call should be the same as if the speed is set only once, to the most recent speed setting.
Для производителей устройств
Конфигурация
Производителям оборудования следует создать отдельный видеодекодер для поддержки туннельного воспроизведения видео. Этот декодер должен указывать в файле 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);
If an AudioTrack instance has already been created, the HW_AV_SYNC ID is passed to the output stream with the same audio session ID. If it hasn't been created yet, then the HW_AV_SYNC ID is passed to the output stream during AudioTrack creation. This is done by the playback thread :
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);
The HW_AV_SYNC ID, whether it corresponds to an audio output stream or a Tuner configuration, is passed into the OMX or Codec2 component so that the OEM code can associate the codec with the corresponding audio output stream or the tuner stream.
During component configuration, the OMX or Codec2 component should return a sideband handle that can be used to associate the codec with a Hardware Composer (HWC) layer. When the app associates a surface with MediaCodec , this sideband handle is passed down to HWC through SurfaceFlinger , which configures the layer as a sideband layer.
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
Компонент туннельного декодера должен поддерживать следующее:
Setting the
OMX.google.android.index.configureVideoTunnelModeextended parameter, which uses theConfigureVideoTunnelModeParamsstructure to pass in theHW_AV_SYNCID associated with the audio output device.Настройка параметра
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;
If the component supports this configuration, it should allocate a sideband handle to this codec and pass it back through the pSidebandWindow member so that the HWC can identify the associated codec. If the component doesn't support this configuration, it should set bTunneled to OMX_FALSE .
Кодек2
В Android 11 и более поздних версиях Codec2 поддерживает туннельное воспроизведение. Компонент декодера должен поддерживать следующее:
Настройка параметра
C2PortTunneledModeTuning, который определяет режим туннелирования и передает значениеHW_AV_SYNCполученное либо от устройства вывода звука, либо из конфигурации тюнера.Для выделения и получения дескриптора боковой полосы для HWC используется запрос к
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE.Handling
C2_PARAMKEY_TUNNEL_HOLD_RENDERwhen attached to aC2Work, which instructs the codec to decode and signal work completion, but not to render the output buffer until either 1) the codec is later instructed to render it or 2) audio playback begins.Handling
C2_PARAMKEY_TUNNEL_START_RENDER, which instructs the codec to immediately render the frame that was marked withC2_PARAMKEY_TUNNEL_HOLD_RENDER, even if audio playback hasn't started.Рекомендуется не настраивать
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;
}
If the component supports this configuration, it should allocate a sideband handle to this codec and pass it back through C2PortTunnelHandlingTuning so that the HWC can identify the associated codec.
Аудио HAL
For on-demand video playback, the Audio HAL receives the audio presentation timestamps inline with the audio data in big-endian format inside a header found at the beginning of each block of audio data the app writes:
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;
}
For HWC to render video frames in sync with the corresponding audio frames, the Audio HAL should parse the sync header and use the presentation timestamp to resynchronize the playback clock with audio rendering. To resynchronize when compressed audio is being played, the Audio HAL might need to parse metadata inside the compressed audio data to determine its playback duration.
Поддержка паузы
Android 5 or lower doesn't include pause support. You can pause tunneled playback only by A/V starvation, but if the internal buffer for video is large (for example, there's one second of data in the OMX component), it makes pause look nonresponsive.
В Android 5.1 и выше AudioFlinger поддерживает паузу и возобновление воспроизведения для прямых (туннелированных) аудиовыходов. Если HAL реализует паузу и возобновление, то сигналы паузы и возобновления воспроизведения передаются в HAL.
Последовательность вызовов pause, flush, resume соблюдается при выполнении вызовов HAL в потоке воспроизведения (аналогично offload).
Предложения по реализации
Аудио HAL
В Android 11 для синхронизации аудио и видео можно использовать идентификатор аппаратной синхронизации из PCR или STC, поэтому поддерживается потоковая передача только видео.
For Android 10 or lower, devices supporting tunneled video playback should have at least one audio output stream profile with the FLAG_HW_AV_SYNC and AUDIO_OUTPUT_FLAG_DIRECT flags in its audio_policy.conf file. These flags are used to set the system clock from the audio clock.
OMX
Device manufacturers should have a separate OMX component for tunneled video playback (manufacturers can have additional OMX components for other types of audio and video playback, such as secure playback). The tunneled component should:
Укажите 0 буферов (
nBufferCountMin,nBufferCountActual) на его выходном порту.Реализуйте расширение
OMX.google.android.index.prepareForAdaptivePlayback setParameter.Specify its capabilities in the
media_codecs.xmlfile and declare the tunneled playback feature. It should also clarify any limitations on frame size, alignment, or bitrate. An example is shown below:<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>
If the same OMX component is used to support tunneled and nontunneled decoding, it should leave the tunneled playback feature as non-required. Both tunneled and nontunneled decoders then have the same capability limitations. An example is shown below:
<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>
Hardware Composer (HWC)
Когда на дисплее присутствует туннелированный слой (слой с compositionType HWC_SIDEBAND ), sidebandStream этого слоя представляет собой дескриптор боковой полосы, выделенный видеокомпонентом OMX.
HWC синхронизирует декодированные видеокадры (из туннелированного компонента OMX) с соответствующей звуковой дорожкой (с идентификатором audio-hw-sync ). Когда новый видеокадр становится актуальным, HWC компонует его с текущим содержимым всех слоев, полученных во время последнего вызова prepare или set, и отображает результирующее изображение. Вызовы prepare или set происходят только при изменении других слоев или при изменении свойств бокового слоя (таких как положение или размер).
The following figure represents the HWC working with the hardware (or kernel or driver) synchronizer, to combine video frames (7b) with the latest composition (7a) for display at the correct time, based on audio (7c).

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