O encapsulamento multimídia permite que dados de vídeo compactados sejam encapsulados por um decodificador de vídeo de hardware diretamente em uma tela, sem ser processado 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 da apresentação do frame de vídeo com um dos tipos de relógio interno a seguir:
Para a reprodução de vídeo sob demanda no Android 5 ou mais recente, um
AudioTrack
relógio sincronizado com as marcações de data e hora da apresentação de áudio transmitidas pelo appPara a reprodução de transmissões ao vivo no Android 11 ou versões mais recentes, use um relógio de referência do programa (PCR, na sigla em inglês) ou relógio de horário do sistema (STC, na sigla em inglês) acionado por um sintonizador.
Contexto
A reprodução de vídeo tradicional no Android notifica
o app quando um frame de vídeo compactado é decodificado. Em seguida, o app
libera
o frame de vídeo decodificado na tela para que ele seja renderizado no mesmo horário
do relógio do sistema que o frame de áudio correspondente,
recuperando instâncias
AudioTimestamps
históricas para calcular o tempo correto.
Como a reprodução de vídeo encapsulada 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. Ele também pode fornecer uma cadência e sincronização de vídeo mais precisa para o relógio escolhido (PRC, STC ou áudio), evitando problemas de tempo introduzidos por possíveis distorções entre o tempo de solicitações do Android para renderizar vídeos e o tempo de vsyncs de hardware verdadeiro. No entanto, o encapsulamento 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 encapsulamento simplifica o processo de reprodução de vídeo.
Figura 1. Comparação entre os processos de reprodução de vídeo tradicionais e com encapsulamento
Para desenvolvedores de apps
Como a maioria dos desenvolvedores de apps faz a integração com uma biblioteca para implementação de reprodução, na maioria dos casos, ela exige apenas a reconfiguração dessa biblioteca para reprodução encapsulada. Para a implementação de baixo nível de um player de vídeo em túnel, use as instruções a seguir.
Para assistir vídeos sob demanda no Android 5 ou mais recente:
Crie uma instância
SurfaceView
.Crie uma instância
audioSessionId
.Crie instâncias
AudioTrack
eMediaCodec
com a instânciaaudioSessionId
criada na etapa 2.Coloque os dados de áudio na fila de
AudioTrack
com o carimbo de data/hora da apresentação do primeiro frame de áudio nos dados de áudio.
Para reprodução de transmissão ao vivo no Android 11 ou versões mais recentes:
Crie uma instância
SurfaceView
.Receba uma instância de
avSyncHwId
doTuner
.Crie as instâncias
AudioTrack
eMediaCodec
com a instânciaavSyncHwId
criada na etapa 2.
O fluxo de chamadas de API é mostrado nos seguintes snippets de código:
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 em túnel está implicitamente vinculada à reprodução de AudioTrack
, o comportamento da reprodução de vídeo em túnel 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 do á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 indicar que o primeiro frame de vídeo na fila precisa ser renderizado assim que for decodificado, defina o parâmetro
PARAMETER_KEY_TUNNEL_PEEK
como1
. Quando os frames de vídeo compactados são reordenados na fila (por exemplo, quando 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 da fila 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 vai determinar o comportamento do dispositivo.
Quando dados de áudio não são fornecidos para
AudioTrack
e os buffers estão vazios (underrun) a reprodução de vídeo, 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, as descontinuidades que o app não pode corrigir podem aparecer nos carimbos de data/hora da apresentação de áudio. Quando isso acontece, o OEM corrige lacunas negativas suspendendo 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
AudioTimestamp
não aumenta para frames de áudio silenciosos inseridos.
Para fabricantes de dispositivos
Configuração
Os OEMs precisam criar um decodificador de vídeo separado para oferecer suporte à reprodução de vídeo em túnel.
Esse decodificador precisa anunciar que é capaz de reprodução em túnel no
arquivo media_codecs.xml
:
<Feature name="tunneled-playback" required="true"/>
Quando uma instância MediaCodec
em túnel é 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 ID da HW_AV_SYNC
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 ao stream de saída com o mesmo ID da sessão de áudio. Se ele ainda não tiver sido
criado, o ID do HW_AV_SYNC
será transmitido para o stream de saída durante a
criação do 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 do HW_AV_SYNC
, correspondendo a um stream de saída de áudio ou a uma configuração Tuner
, é transmitido ao 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
gerenciador de sideband que possa ser usado para associar o codec a uma camada
de compositor de hardware (HWC, na sigla em inglês). Quando o app associa uma superfície a MediaCodec
, esse handle de sideband
é transmitido para o HWC por SurfaceFlinger
, que configura a
camada como uma
sideband.
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 adequado, seja sincronizado com o fluxo de saída de áudio associado ou com o relógio de referência do programa sintonizador, compondo os buffers com o conteúdo atual de outras camadas e mostrando a imagem resultante. Isso acontece independentemente do ciclo normal de preparação e definição. As chamadas de preparação e definição acontecem somente quando outras camadas mudam ou quando as propriedades da camada de sideband (como posição ou tamanho) mudam.
OMX
Um componente decodificador encapsulado precisa ser compatível com o seguinte:
Definir o parâmetro estendido
OMX.google.android.index.configureVideoTunnelMode
, que usa a estruturaConfigureVideoTunnelModeParams
para transmitir o IDHW_AV_SYNC
associado ao dispositivo de saída de áudio.Configurar o parâmetro
OMX_IndexConfigAndroidTunnelPeek
, que informa ao codec se ele deve ou não renderizar o primeiro frame de vídeo decodificado, independentemente de a reprodução de áudio ter começado.Enviar o evento
OMX_EventOnFirstTunnelFrameReady
quando o primeiro frame de vídeo em túnel for decodificado e estiver pronto para ser renderizado.
A implementação do AOSP configura o modo de túnel em
ACodec
usando
OMXNodeInstance
,
conforme mostrado no snippet de código abaixo:
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 sideband 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 em versões mais recentes, o Codec2
oferece suporte à reprodução em túnel. O componente
decodificador precisa ser compatível com o seguinte:
Configurando
C2PortTunneledModeTuning
, que configura o modo de túnel e transmite oHW_AV_SYNC
extraído do dispositivo de saída de áudio ou da configuração do sintonizador.Consulta de
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE
para alocar e extrair o gerenciador de sideband para HWC.Gerenciar
C2_PARAMKEY_TUNNEL_HOLD_RENDER
quando 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é 1) o codec ser instruído a renderizá-lo ou 2) a reprodução de áudio começar.processamento de
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 sido iniciada.Deixe a
debug.stagefright.ccodec_delayed_params
sem configuração (recomendado). Se você configurar, defina comofalse
.
A implementação do AOSP configura o modo de túnel em
CCodec
por C2PortTunnelModeTuning
, conforme mostrado no snippet de código abaixo:
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 vai alocar um identificador
de sideband para esse codec e transmiti-lo novamente por C2PortTunnelHandlingTuning
para
que o HWC possa identificar o codec associado.
HAL de áudio
Para reprodução de vídeo sob demanda, a HAL de áudio recebe os carimbos de data/hora da apresentação de áudio inline 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 gravado 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, a HAL de áudio precisa analisar o cabeçalho de sincronização e usar o carimbo de data/hora da apresentação para sincronizar novamente o relógio de reprodução com a renderização de áudio. Para sincronizar novamente quando o áudio compactado estiver sendo reproduzido, o HAL de áudio talvez precise analisar metadados dentro dos dados de áudio compactado para determinar a duração da reprodução.
Pausar suporte
O Android 5 ou versões anteriores não incluem suporte à pausa. É possível pausar a reprodução encaminhada apenas por falta de A/V, mas se o buffer interno para vídeo for grande (por exemplo, se houver um segundo de dados no componente OMX), a pausa não vai responder.
No Android 5.1 ou versões mais recentes, o AudioFlinger
oferece suporte à pausa e retomada de saídas de áudio
diretas (encapsuladas). Se o HAL implementar pausa e retomada, a pausa
e a retomada da faixa serão encaminhadas para o HAL.
A sequência de chamadas de pausa, limpeza e retomada é respeitada ao executar as chamadas de HAL na linha de execução de reprodução (igual ao desligamento).
Sugestões de implementação
HAL de áudio
No Android 11, o ID de sincronização de HW do PCR ou STC pode ser usado para sincronização A/V. Portanto, o streaming somente de vídeo é compatível.
Para o Android 10 ou versões anteriores, os dispositivos compatíveis com a reprodução de vídeo encapsulada precisam ter
pelo menos um perfil de stream de saída de áudio com as sinalizações 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 a partir do relógio de áudio.
OMX
Os fabricantes de dispositivos precisam ter um componente OMX separado para a reprodução de vídeo em túnel. Os fabricantes podem ter outros componentes OMX para outros tipos de reprodução de áudio e vídeo, como a reprodução segura. O componente encapsulado precisa:
Especifique 0 buffers (
nBufferCountMin
,nBufferCountActual
) na porta de saída.Implemente a extensão
OMX.google.android.index.prepareForAdaptivePlayback setParameter
.Especifique os recursos no arquivo
media_codecs.xml
e declare o recurso de reprodução em túnel. Ela também precisa esclarecer todas as limitações de tamanho do frame, alinhamento ou taxa de bits. Veja 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 com e sem encapsulamento, ele vai deixar o recurso de reprodução com encapsulamento como não obrigatório. Os decodificadores com e sem túnel 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 encapsulada (uma camada com HWC_SIDEBAND
compositionType
) em
uma tela, o sidebandStream
da camada é o identificador de sideband alocado pelo
componente de vídeo OMX.
O HWC sincroniza frames de vídeo decodificados (do componente OMX encapsulado) 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
recebido durante a última chamada de preparação ou configuraçã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 2. Sincronizador de hardware (ou kernel ou driver) de HWC