O tunelamento multimídia, também conhecido como modo de túnel, permite que dados de vídeo compactados passem por um decodificador de vídeo de hardware diretamente para uma tela, sem serem processados pelo código do app ou do framework do Android. O código específico do dispositivo abaixo da pilha do Android determina quais frames de vídeo enviar para a tela e quando enviá-los, comparando os carimbos de data/hora de apresentação do frame de vídeo com um dos seguintes tipos de relógio interno:
Para reprodução de vídeo sob demanda no Android 5 ou mais recente, um
AudioTrackrelógio sincronizado com os carimbos de data/hora de apresentação de áudio transmitidos pelo appPara reprodução de transmissão ao vivo no Android 11 ou mais recente, um relógio de referência de programa (PCR) ou um relógio de tempo do sistema (STC) controlado por um sintonizador
Contexto
A reprodução de vídeo no modo não tunelado no Android notifica o app quando um
frame de vídeo compactado é decodificado. Em seguida, o app libera o frame de vídeo decodificado para a tela para ser renderizado no mesmo horário do relógio do sistema que o frame de áudio correspondente, recuperando instâncias AudioTimestamp históricas para calcular o tempo correto.
Como a reprodução de vídeo tunelado ignora o código do app e reduz o número de processos que atuam no vídeo, ela pode fornecer uma renderização de vídeo mais eficiente, dependendo da implementação do OEM. Ela também pode fornecer uma cadência e sincronização de vídeo mais precisas para o relógio escolhido (PRC, STC ou áudio), evitando problemas de tempo introduzidos por um possível desvio entre o tempo das solicitações do Android para renderizar vídeo e o tempo das vsyncs de hardware verdadeiras. No entanto, o tunelamento também pode reduzir o suporte a efeitos de GPU, como desfoque ou cantos arredondados em janelas de imagem em imagem (PiP), porque os buffers ignoram a pilha de gráficos do Android.
O diagrama a seguir mostra como o tunelamento simplifica o processo de reprodução de vídeo.

Figura 1. Comparação de processos de reprodução de vídeo tunelados e não tunelados.
Para desenvolvedores de apps
Como a maioria dos desenvolvedores de apps se integra a uma biblioteca para implementação de reprodução, na maioria dos casos, a implementação exige apenas a reconfiguração dessa biblioteca para reprodução tunelada. Para implementação de baixo nível de um player de vídeo tunelado, use as instruções a seguir.
Para reprodução de vídeo sob demanda no Android 5 ou mais recente:
Crie uma instância
SurfaceView.Crie uma instância
audioSessionId.Crie instâncias
AudioTrackeMediaCodeccom a instânciaaudioSessionIdcriada na etapa 2.Coloque dados de áudio na fila para
AudioTrackcom o carimbo de data/hora de apresentação do primeiro frame de áudio nos dados de áudio.
Para reprodução de transmissão ao vivo no Android 11 ou mais recente:
Crie uma instância
SurfaceView.Receba uma instância
avSyncHwIddeTuner.Crie instâncias
AudioTrackeMediaCodeccom a instânciaavSyncHwIdcriada na etapa 2.
O fluxo de chamadas de API é mostrado nos snippets de código a seguir:
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);
Comportamento da reprodução de vídeo sob demanda
Como a reprodução de vídeo sob demanda tunelada está vinculada implicitamente à reprodução AudioTrack, o comportamento da reprodução de vídeo tunelada pode depender do comportamento da reprodução de áudio.
Na maioria dos dispositivos, por padrão, um frame de vídeo não é renderizado até que a reprodução de áudio comece. No entanto, o app pode precisar renderizar um frame de vídeo antes de iniciar a reprodução de áudio, por exemplo, para mostrar ao usuário a posição atual do vídeo durante a busca.
Para sinalizar que o primeiro frame de vídeo enfileirado precisa ser renderizado assim que for decodificado, defina o
PARAMETER_KEY_TUNNEL_PEEKparâmetro como1. Quando os frames de vídeo compactados são reordenados na fila (como quando os frames B estão presentes), isso significa que o primeiro frame de vídeo exibido sempre precisa ser um frame I.Se você não quiser que o primeiro frame de vídeo enfileirado seja renderizado até que a reprodução de áudio comece, defina esse parâmetro como
0.Se esse parâmetro não estiver definido, o OEM determinará o comportamento do dispositivo.
Quando os dados de áudio não são fornecidos ao
AudioTracke os buffers estão vazios (subexecução de áudio), a reprodução de vídeo é interrompida até que mais dados de áudio sejam gravados, porque o relógio de áudio não está mais avançando.Durante a reprodução, podem aparecer descontinuidades que o app não consegue corrigir nos carimbos de data/hora de apresentação de áudio. Quando isso acontece, o OEM corrige lacunas negativas interrompendo o frame de vídeo atual e lacunas positivas descartando frames de vídeo ou inserindo frames de áudio silenciosos (dependendo da implementação do OEM). A posição do frame
AudioTimestampnão aumenta para frames de áudio silenciosos inseridos.
Fluxo de sequência de busca precisa
A busca precisa permite encontrar um ponto específico em um vídeo. Ao contrário da busca de keyframe, que pula apenas para o frame I mais próximo e pode desviar da posição de destino em vários segundos, a busca precisa renderiza o vídeo no carimbo de data/hora solicitado exato. Aderir a essa sequência de API específica permite que o app execute a pré-rolagem em segundo plano e a sincronização de tempo sem problemas. Isso garante que o frame de destino seja exibido instantaneamente quando a reprodução for retomada.
Para realizar uma busca precisa, siga a ordem de execução ilustrada na Figura 2:
Figura 2. Fluxo de sequência para alcançar uma busca precisa.
Os principais detalhes incluem:
Execução paralela: é possível executar etapas em uma única caixa
parsimultaneamente. Por exemplo, as chamadas de vídeoMediaCodecsão independentes deAudioTrack.Dependências sequenciais: chame todas as operações na primeira caixa
parantes de avançar para a segunda caixapar. Especificamente, o app precisa garantir queAudioTrack.writee os buffers no vídeoMediaCodecsejam enfileirados antes de chamarAudioTrack.play.
Fluxo de sequência de reprodução de velocidade variável
A reprodução de velocidade variável permite reproduzir vídeos em uma taxa mais rápida ou mais lenta do que a velocidade normal. Esse recurso é usado com frequência por apps para permitir que os usuários consumam conteúdo mais rápido (como reproduzir palestras educacionais ou podcasts em 1,5x ou 2,0x para economizar tempo) ou mais lento (como analisar jogadas atléticas ou vídeos instrutivos em 0,5x).
Para definir a velocidade, siga a ordem de execução ilustrada na Figura 3:
Figura 3. Fluxo de sequência para definir a velocidade.
Os comportamentos e requisitos técnicos a seguir não são capturados no diagrama de sequência da Figura 3:
AudioTrack.getTimestampretornaframePositioncom base na frequência de entrada de áudio original. Por exemplo, com entrada de 44.100 Hz e velocidade de reprodução 2,0x, após 2 segundos de reprodução,AudioTrack.getTimestampretornaframePositionde 176.400.Se o app chamar
setSpeed(1.5)e isso for bem-sucedido, e depois o app chamarsetSpeed(30)e isso falhar, a reprodução permanecerá em 1,5x.Se o áudio estiver silenciado (usando
setVolume), o app ainda precisará enviar buffers de áudio, porque os frames de vídeo são renderizados com base na posição do áudio.O tom do áudio é preservado quando a velocidade é alterada.
A velocidade do vídeo não é afetada por outras ações de reprodução.
Exemplo 1: se a velocidade do vídeo for 1,5x e
AudioTrackestiver pausado, a velocidade permanecerá em 1,5x após a retomada deAudioTrack.Exemplo 2: se a velocidade do vídeo for 1,5x e o usuário buscar um PTS diferente, seguindo a Figura 2, a reprodução permanecerá em 1,5x.
Para garantir que todos os frames sejam decodificados a tempo de serem renderizados na velocidade do vídeo escolhida, defina
KEY_OPERATING_RATEpara corresponder ao produto da taxa de frames de vídeo e da velocidade do vídeo. SeKEY_OPERATING_RATEnão estiver definido como alto o suficiente, o codec poderá não decodificar frames com rapidez suficiente, causando quedas de frame não intencionais durante a reprodução.- Exemplo: se o frame rate original do conteúdo for de 60 fps e a velocidade do vídeo for de 2x, defina
KEY_OPERATING_RATEcomo120.
- Exemplo: se o frame rate original do conteúdo for de 60 fps e a velocidade do vídeo for de 2x, defina
Definir a velocidade repetidamente com velocidades diferentes com suporte não deve acionar erros, e o comportamento de reprodução após a última chamada precisa ser o mesmo que se a velocidade for definida apenas uma vez, para a configuração de velocidade mais recente.
Para fabricantes de dispositivos
Configuração
Os OEMs precisam criar um decodificador de vídeo separado para oferecer suporte à reprodução de vídeo tunelado.
Esse decodificador precisa anunciar que é capaz de reprodução tunelada no arquivo media_codecs.xml:
<Feature name="tunneled-playback" required="true"/>
Quando uma instância MediaCodec tunelada é configurada com um ID de sessão de áudio, ela consulta AudioFlinger para esse ID 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);
}
Durante essa consulta,
AudioFlinger recupera o HW_AV_SYNC ID
do dispositivo de áudio principal e o associa internamente ao ID da sessão de áudio:
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);
Se uma instância AudioTrack já tiver sido criada, o ID HW_AV_SYNC será transmitido para o stream de saída com o mesmo ID de sessão de áudio. Se ainda não tiver sido criado, o ID HW_AV_SYNC será transmitido para o stream de saída durante a criação de AudioTrack. Isso é feito pela linha de execução
de reprodução:
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);
O ID HW_AV_SYNC, seja ele correspondente a um stream de saída de áudio ou a uma configuração Tuner, é transmitido para o componente OMX ou Codec2 para que o código do OEM possa associar o codec ao stream de saída de áudio correspondente ou ao stream do sintonizador.
Durante a configuração do componente, o componente OMX ou Codec2 precisa retornar um identificador de banda lateral que pode ser usado para associar o codec a uma camada do Hardware Composer (HWC). Quando o app associa uma superfície ao MediaCodec, esse identificador de banda lateral
é transmitido ao HWC pelo SurfaceFlinger, que configura a
camada como uma
camada de banda lateral.
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;
}
O HWC é responsável por receber novos buffers de imagem da saída do codec no momento apropriado, sincronizados com o stream de saída de áudio associado ou com o relógio de referência do programa do sintonizador, compondo os buffers com o conteúdo atual de outras camadas e exibindo a imagem resultante. Isso acontece independentemente do ciclo normal de preparação e definição. As chamadas de preparação e definição acontecem apenas quando outras camadas mudam ou quando as propriedades da camada de banda lateral (como posição ou tamanho) mudam.
OMX
Um componente de decodificador tunelado precisa oferecer suporte a:
Definir o parâmetro estendido
OMX.google.android.index.configureVideoTunnelMode, que usa a estruturaConfigureVideoTunnelModeParamspara transmitir o IDHW_AV_SYNCassociado ao dispositivo de saída de áudio.Configurar o parâmetro
OMX_IndexConfigAndroidTunnelPeekque informa ao codec para renderizar ou não o primeiro frame de vídeo decodificado, independentemente de a reprodução de áudio ter começado.Enviar o evento
OMX_EventOnFirstTunnelFrameReadyquando o primeiro frame de vídeo tunelado for decodificado e estiver pronto para ser renderizado.
A implementação do AOSP configura o modo de túnel em
ACodec
por meio de
OMXNodeInstance
, conforme mostrado no snippet de código a seguir:
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;
Se o componente oferecer suporte a essa configuração, ele precisará alocar um identificador de banda lateral para esse codec e transmiti-lo de volta pelo membro pSidebandWindow para que o HWC possa identificar o codec associado. Se o componente não oferecer suporte a essa configuração, ele precisará definir bTunneled como OMX_FALSE.
Codec2
No Android 11 ou mais recente, o Codec2 oferece suporte à reprodução tunelada. O componente do decodificador precisa oferecer suporte a:
Configurar
C2PortTunneledModeTuning, que configura o modo de túnel e transmite oHW_AV_SYNCrecuperado do dispositivo de saída de áudio ou da configuração do sintonizador.Consultar
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLEpara alocar e recuperar o identificador de banda lateral para HWC.Processar
C2_PARAMKEY_TUNNEL_HOLD_RENDERquando anexado a umC2Work, que instrui o codec a decodificar e sinalizar a conclusão do trabalho, mas não a renderizar o buffer de saída até que 1) o codec seja instruído posteriormente a renderizá-lo ou 2) a reprodução de áudio comece.Processar
C2_PARAMKEY_TUNNEL_START_RENDER, que instrui o codec a renderizar imediatamente o frame marcado comC2_PARAMKEY_TUNNEL_HOLD_RENDER, mesmo que a reprodução de áudio não tenha começado.Deixe
debug.stagefright.ccodec_delayed_paramsnão configurado (recomendado). Se você o configurar, defina comofalse.
A implementação do AOSP configura o modo de túnel em
CCodec
por meio de C2PortTunnelModeTuning, conforme mostrado no snippet de código a seguir:
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;
}
Se o componente oferecer suporte a essa configuração, ele precisará alocar um identificador de banda lateral para esse codec e transmiti-lo de volta pelo C2PortTunnelHandlingTuning para que o HWC possa identificar o codec associado.
HAL de áudio
Para reprodução de vídeo sob demanda, o HAL de áudio recebe os carimbos de data/hora de apresentação de áudio em linha com os dados de áudio no formato big-endian dentro de um cabeçalho encontrado no início de cada bloco de dados de áudio gravados pelo app:
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;
}
Para que o HWC renderize frames de vídeo em sincronia com os frames de áudio correspondentes, o HAL de áudio precisa analisar o cabeçalho de sincronização e usar o carimbo de data/hora de apresentação para resincronizar o relógio de reprodução com a renderização de áudio. Para resincronizar quando o áudio compactado está sendo reproduzido, o HAL de áudio pode precisar analisar os metadados dentro dos dados de áudio compactados para determinar a duração da reprodução.
Suporte à pausa
O Android 5 ou versões anteriores não incluem suporte à pausa. É possível pausar a reprodução tunelada apenas por privação de A/V, mas se o buffer interno do vídeo for grande (por exemplo, há um segundo de dados no componente OMX), a pausa parecerá não responsiva.
No Android 5.1 ou mais recente, o AudioFlinger oferece suporte à pausa e à retomada para saídas de áudio diretas (tuneladas). Se o HAL implementar a pausa e a retomada, a pausa e a retomada da faixa serão encaminhadas para o HAL.
A sequência de chamadas de pausa, liberação e retomada é respeitada pela execução das chamadas HAL na linha de execução de reprodução (igual ao descarregamento).
Sugestões de implementação
HAL de áudio
No Android 11, o ID de sincronização de hardware do PCR ou STC pode ser usado para sincronização de A/V, então o stream somente de vídeo é compatível.
Para o Android 10 ou versões anteriores, os dispositivos que oferecem suporte à reprodução de vídeo tunelado precisam ter pelo menos um perfil de stream de saída de áudio com as flags FLAG_HW_AV_SYNC e AUDIO_OUTPUT_FLAG_DIRECT no arquivo audio_policy.conf. Essas flags são usadas para definir o relógio do sistema no relógio de áudio.
OMX
Os fabricantes de dispositivos precisam ter um componente OMX separado para reprodução de vídeo tunelado. Os fabricantes podem ter outros componentes OMX para outros tipos de reprodução de áudio e vídeo, como reprodução segura. O componente tunelado precisa:
Especificar 0 buffers (
nBufferCountMin,nBufferCountActual) na porta de saída.Implementar a extensão
OMX.google.android.index.prepareForAdaptivePlayback setParameter.Especificar os recursos no arquivo
media_codecs.xmle declarar o recurso de reprodução tunelada. Ele também precisa esclarecer quaisquer limitações de tamanho do frame, alinhamento ou taxa de bits. Confira um exemplo abaixo:<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>
Se o mesmo componente OMX for usado para oferecer suporte à decodificação tunelada e não tunelada, ele precisará deixar o recurso de reprodução tunelada como não obrigatório. Os decodificadores tunelados e não tunelados têm as mesmas limitações de capacidade. Confira um exemplo abaixo:
<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)
Quando há uma camada tunelada (uma camada com HWC_SIDEBAND compositionType) em uma tela, o sidebandStream da camada é o identificador de banda lateral alocado pelo componente de vídeo OMX.
O HWC sincroniza frames de vídeo decodificados (do componente OMX tunelado) com a faixa de áudio associada (com o ID audio-hw-sync). Quando um novo frame de vídeo se torna atual, o HWC o compõe com o conteúdo atual de todas as camadas recebidas durante a última chamada de preparação ou definição e exibe a imagem resultante.
As chamadas de preparação ou definição acontecem apenas quando outras camadas mudam ou quando as propriedades da camada de banda lateral (como posição ou tamanho) mudam.
A figura a seguir representa o HWC trabalhando com o sincronizador de hardware (ou kernel ou driver) para combinar frames de vídeo (7b) com a composição mais recente (7a) para exibição no momento correto, com base no áudio (7c).

Figura 4. Sincronizador de hardware (ou kernel ou driver) do HWC.