Encapsulamento de multimídia

O tunelamento multimídia 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 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 dos frames 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 relógio AudioTrack sincronizado com os carimbos de data/hora da apresentação de áudio transmitidos pelo app

  • Para reprodução de transmissões 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 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 para a tela, que será 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 em túnel ignora o código do app e reduz o número de processos que atuam no vídeo, ela pode oferecer uma renderização de vídeo mais eficiente dependendo da implementação do OEM. Ele também pode fornecer cadência e sincronização de vídeo mais precisas com o relógio escolhido (PRC, STC ou áudio), evitando problemas de tempo introduzidos por um possível desalinhamento entre o tempo das solicitações do Android para renderizar vídeo e o tempo dos vsyncs de hardware verdadeiros. No entanto, o encapsulamento também pode reduzir o suporte a efeitos de GPU, como desfoque ou cantos arredondados em janelas picture-in-picture (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.

Comparação entre os modos tradicional e de túnel

Figura 1. Comparação entre processos tradicionais e tunelados de reprodução de vídeo

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 em túnel. Para uma implementação de baixo nível de um player de vídeo em túnel, use as instruções a seguir.

Para reprodução de vídeo on demand no Android 5 ou mais recente:

  1. Crie uma instância SurfaceView.

  2. Crie uma instância audioSessionId.

  3. Crie instâncias AudioTrack e MediaCodec com a instância audioSessionId criada na etapa 2.

  4. Enfileire os dados de áudio para AudioTrack com o carimbo de data/hora da apresentação do primeiro frame de áudio nos dados de áudio.

Para reproduzir transmissões ao vivo no Android 11 ou em versões mais recentes:

  1. Crie uma instância SurfaceView.

  2. Receba uma instância avSyncHwId de Tuner.

  3. Crie instâncias AudioTrack e MediaCodec com a instância avSyncHwId criada 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 on demand 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 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 deve ser renderizado assim que for decodificado, defina o parâmetro PARAMETER_KEY_TUNNEL_PEEK como 1. Quando os frames de vídeo compactados são reordenados na fila (como quando há B-frames), isso significa que o primeiro frame de vídeo exibido deve sempre ser um I-frame.

    • Se você não quiser que o primeiro frame do vídeo na fila seja renderizado até que a reprodução de áudio comece, defina esse parâmetro como 0.

    • Se esse parâmetro não for definido, o OEM vai determinar o comportamento do dispositivo.

  • Quando os dados de áudio não são fornecidos a AudioTrack e os buffers estão vazios (subfluxo 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 avança mais.

  • 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 as falhas negativas interrompendo o frame de vídeo atual e as falhas 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. O decodificador precisa anunciar que é capaz de fazer reprodução em túnel no arquivo media_codecs.xml:

<Feature name="tunneled-playback" required="true"/>

Quando uma instância de MediaCodec em túnel é configurada com um ID de sessão de áudio, ela consulta AudioFlinger para esse ID de 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 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 para o fluxo de saída com o mesmo ID de sessão de áudio. Se ele ainda não tiver sido criado, o ID HW_AV_SYNC será transmitido para o fluxo de saída durante a criação do AudioTrack. Isso é feito pela thread 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 ao componente OMX ou Codec2 para que o código OEM possa associar o codec ao stream de saída de áudio ou ao stream do sintonizador correspondente.

Durante a configuração do componente, o OMX ou o Codec2 precisa retornar um handle de sideband 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 por 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 adequado, sincronizados com o fluxo de saída de áudio associado ou o relógio de referência do programa do sintonizador, combinando os buffers com o conteúdo atual de outras camadas e mostrando a imagem resultante. Isso acontece independente 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 em túnel precisa ser compatível com o seguinte:

  • Definir o parâmetro OMX.google.android.index.configureVideoTunnelMode extended que usa a estrutura ConfigureVideoTunnelModeParams para transmitir o ID HW_AV_SYNC associado ao dispositivo de saída de áudio.

  • Configurar o parâmetro OMX_IndexConfigAndroidTunnelPeek, que informa ao codec se o primeiro frame de vídeo decodificado deve ser renderizado ou não, independente de a reprodução de áudio ter começado.

  • Envia o evento OMX_EventOnFirstTunnelFrameReady quando o primeiro frame de vídeo em túnel é decodificado e está pronto para ser renderizado.

A implementação do AOSP configura o modo de túnel em ACodec por 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 vai 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 vai definir bTunneled como OMX_FALSE.

Codec2

No Android 11 ou versões mais recentes, o Codec2 oferece suporte à reprodução em túnel. O componente decodificador precisa oferecer suporte ao seguinte:

  • Configurar C2PortTunneledModeTuning, que configura o modo de túnel e transmite o HW_AV_SYNC recuperado do dispositivo de saída de áudio ou da configuração do sintonizador.

  • Consultar C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE para alocar e recuperar o descritor de sideband para HWC.

  • Processamento de C2_PARAMKEY_TUNNEL_HOLD_RENDER quando anexado a um C2Work, 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 a renderizar mais tarde ou 2) a reprodução de áudio comece.

  • Processamento de C2_PARAMKEY_TUNNEL_START_RENDER, que instrui o codec a renderizar imediatamente o frame marcado com C2_PARAMKEY_TUNNEL_HOLD_RENDER, mesmo que a reprodução de áudio não tenha começado.

  • Deixe debug.stagefright.ccodec_delayed_params sem configuração (recomendado). Se você configurar, defina como false.

A implementação do AOSP configura o modo de túnel em CCodec usando 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, &params);
if (c2err == C2_OK && params.size() == 1u) {
    C2PortTunnelHandleTuning::output *videoTunnelSideband =
            C2PortTunnelHandleTuning::output::From(params[0].get());
    return OK;
}

Se o componente for compatível com essa configuração, ele vai alocar um identificador de banda lateral para esse codec e transmiti-lo de volta por 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 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 que o app grava:

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 ressincronizar o relógio de reprodução com a renderização de áudio. Para ressincronizar quando um áudio compactado está sendo reproduzido, a HAL de áudio pode precisar analisar os metadados nos dados de áudio compactados para determinar a duração da reprodução.

Pausar suporte

O Android 5 ou versões anteriores não incluem suporte para pausa. É possível pausar a reprodução em túnel apenas por falta 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 de saídas de áudio diretas (em túnel). Se a HAL implementar pausa e retomada, o rastreamento de pausa e retomada será encaminhado para a HAL.

A sequência de chamadas de pausa, limpeza e retomada é respeitada executando as 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 da PCR ou do STC pode ser usado para sincronização de A/V. Portanto, o fluxo somente de vídeo é compatível.

Para o Android 10 ou versões anteriores, os dispositivos que oferecem suporte à reprodução de vídeo em túnel precisam ter pelo menos um perfil de fluxo 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 com base no relógio de áudio.

OMX

Os fabricantes de dispositivos precisam ter um componente OMX separado para reprodução de vídeo em túnel. Eles também podem ter outros componentes OMX para outros tipos de reprodução de áudio e vídeo, como a reprodução segura. O componente tunelado precisa:

  • Especifique 0 buffers (nBufferCountMin, nBufferCountActual) na porta de saída.

  • Implemente a extensão OMX.google.android.index.prepareForAdaptivePlayback setParameter.

  • Especifique os recursos dele no arquivo media_codecs.xml e declare o recurso de reprodução em túnel. Também é preciso esclarecer as limitações de tamanho, alinhamento ou taxa de bits do frame. 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 em túnel e sem túnel, ele vai deixar o recurso de reprodução em túnel como não obrigatório. Os decodificadores tunelados e não tunelados têm as mesmas limitações de capacidade. Veja 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 em túnel (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 em túnel) 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 combina com o conteúdo atual de todas as camadas recebidas durante a última chamada de preparação ou definição e mostra 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) são alteradas.

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

HWC que combina frames de vídeo com base no áudio

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