Tunelamento multimídia

O encapsulamento multimídia permite que os dados de vídeo compactados sejam encapsulados por meio de um decodificador de vídeo de hardware diretamente para uma tela, sem serem processados ​​pelo código do aplicativo ou pelo código da estrutura do Android. O código específico do dispositivo abaixo da pilha do Android determina quais quadros de vídeo enviar para a tela e quando enviá-los, comparando os carimbos de data e hora de apresentação do quadro de vídeo com um dos seguintes tipos de relógio interno:

  • Para reprodução de vídeo sob demanda no Android 5 ou superior, um relógio AudioTrack sincronizado com os carimbos de data e hora da apresentação de áudio transmitidos pelo aplicativo

  • Para reprodução de transmissão ao vivo no Android 11 ou superior, um relógio de referência de programa (PCR) ou relógio de tempo do sistema (STC) acionado por um sintonizador

Fundo

A reprodução de vídeo tradicional no Android notifica o aplicativo quando um quadro de vídeo compactado foi decodificado. O aplicativo então libera o quadro de vídeo decodificado para a exibição para ser renderizado no mesmo horário do sistema que o quadro de áudio correspondente, recuperando instâncias históricas de AudioTimestamps para calcular o tempo correto.

Como a reprodução de vídeo em túnel ignora o código do aplicativo 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 de vídeo e sincronização mais precisas para o relógio escolhido (PRC, STC ou áudio), evitando problemas de tempo introduzidos por uma possível distorção entre o tempo das solicitações do Android para renderizar vídeo e o tempo das verdadeiras vsyncs de hardware. No entanto, o tunelamento também pode reduzir o suporte para 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.

comparação de modos de tradição e túnel

Figura 1. Comparação dos processos de reprodução de vídeo tradicionais e encapsulados

Para desenvolvedores de aplicativos

Como a maioria dos desenvolvedores de aplicativos se integra a uma biblioteca para implementação de reprodução, na maioria dos casos a implementação requer apenas a reconfiguração dessa biblioteca para reprodução em túnel. Para 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 sob demanda no Android 5 ou superior:

  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. Enfileirar dados de áudio para AudioTrack com o carimbo de data/hora de apresentação para o primeiro quadro de áudio nos dados de áudio.

Para reprodução de transmissão ao vivo no Android 11 ou superior:

  1. Crie uma instância SurfaceView .

  2. Obtenha uma instância avSyncHwId do Tuner .

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

O fluxo de chamadas da 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á vinculada implicitamente à reprodução do 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 quadro de vídeo não é renderizado até que a reprodução de áudio comece. No entanto, o aplicativo pode precisar renderizar um quadro 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 quadro de vídeo na fila deve ser renderizado assim que for decodificado, defina o parâmetro PARAMETER_KEY_TUNNEL_PEEK como 1 . Quando os quadros de vídeo compactados são reordenados na fila (como quando os quadros B estão presentes), isso significa que o primeiro quadro de vídeo exibido deve ser sempre um quadro I.

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

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

  • Quando os dados de áudio não são fornecidos ao AudioTrack e os buffers estão vazios (áudio insuficiente), a reprodução de vídeo para 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 para as quais o aplicativo não pode corrigir podem aparecer nos carimbos de data/hora da apresentação de áudio. Quando isso acontece, o OEM corrige as lacunas negativas paralisando o quadro de vídeo atual e as lacunas positivas descartando quadros de vídeo ou inserindo quadros de áudio silenciosos (dependendo da implementação do OEM). A posição do quadro AudioTimestamp não aumenta para quadros de áudio silenciosos inseridos.

Para fabricantes de dispositivos

Configuração

Os OEMs devem criar um decodificador de vídeo separado para dar suporte à reprodução de vídeo em túnel. Este decodificador deve anunciar que é capaz de reproduzir em túnel no arquivo media_codecs.xml :

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

Quando uma instância de MediaCodec encapsulada é configurada com um ID de sessão de áudio, ele consulta AudioFlinger para este 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 esta consulta, AudioFlinger recupera o ID HW_AV_SYNC do dispositivo de áudio primário 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 é passado para o fluxo de saída com o mesmo ID de sessão de áudio. Se ainda não foi criado, o ID HW_AV_SYNC é passado para o fluxo de saída durante a criação do AudioTrack . Isso é feito pelo thread de reprodução :

mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);

O ID HW_AV_SYNC , se corresponde a um fluxo de saída de áudio ou a uma configuração de Tuner , é passado para o componente OMX ou Codec2 para que o código OEM possa associar o codec ao fluxo de saída de áudio correspondente ou ao fluxo de sintonizador.

Durante a configuração do componente, o componente OMX ou Codec2 deve retornar um identificador de banda lateral que pode ser usado para associar o codec a uma camada do Hardware Composer (HWC). Quando o aplicativo associa uma superfície a MediaCodec , esse identificador de banda lateral é passado para HWC por meio de 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 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 exibindo a imagem resultante. Isso acontece independentemente do ciclo normal de preparação e configuração. As chamadas de preparação e configuração ocorrem apenas quando outras camadas são alteradas ou quando as propriedades da camada de banda lateral (como posição ou tamanho) são alteradas.

OMX

Um componente de decodificador encapsulado deve suportar o seguinte:

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

  • Configurando o parâmetro OMX_IndexConfigAndroidTunnelPeek que informa ao codec para renderizar ou não o primeiro quadro de vídeo decodificado, independentemente de a reprodução de áudio ter sido iniciada.

  • Enviando o evento OMX_EventOnFirstTunnelFrameReady quando o primeiro quadro de vídeo encapsulado foi decodificado e está pronto para ser renderizado.

A implementação do AOSP configura o modo de túnel no ACodec por meio OMXNodeInstance , conforme mostrado no trecho 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 deverá alocar um identificador de banda lateral para esse codec e passá-lo de volta pelo membro pSidebandWindow para que o HWC possa identificar o codec associado. Se o componente não suportar esta configuração, ele deve definir bTunneled para OMX_FALSE .

Codec2

No Android 11 ou superior, Codec2 é compatível com a reprodução em túnel. O componente decodificador deve suportar o seguinte:

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

  • Consultando C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE , para alocar e recuperar o identificador de banda lateral para HWC.

  • Manipulando 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 renderizá-lo ou 2) a reprodução de áudio comece.

  • Manipulação de C2_PARAMKEY_TUNNEL_START_RENDER , que instrui o codec a renderizar imediatamente o quadro que foi marcado com C2_PARAMKEY_TUNNEL_HOLD_RENDER , mesmo que a reprodução de áudio não tenha sido iniciada.

  • Deixe debug.stagefright.ccodec_delayed_params desconfigurado (recomendado). Se você configurá-lo, defina como false .

A implementação do AOSP configura o modo de túnel no CCodec por meio de C2PortTunnelModeTuning , conforme mostrado no trecho 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 suportar essa configuração, ele deve alocar um identificador de banda lateral para esse codec e passá-lo de volta pelo C2PortTunnelHandlingTuning para que o HWC possa identificar o codec associado.

Áudio HAL

Para reprodução de vídeo sob demanda, o Audio HAL recebe os timestamps de apresentação de áudio alinhados 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 aplicativo 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 quadros de vídeo em sincronia com os quadros de áudio correspondentes, o Audio HAL deve analisar o cabeçalho de sincronização e usar o carimbo de hora da apresentação para ressincronizar o relógio de reprodução com a renderização de áudio. Para ressincronizar quando o áudio compactado estiver sendo reproduzido, o Audio HAL pode precisar analisar metadados dentro dos dados de áudio compactados para determinar sua duração de reprodução.

Pausar suporte

O Android 5 ou inferior não inclui suporte a pausa. Você pode pausar a reprodução em túnel apenas por inanição de A/V, mas se o buffer interno para vídeo for grande (por exemplo, há um segundo de dados no componente OMX), a pausa parecerá sem resposta.

No Android 5.1 ou superior, AudioFlinger suporta pausar e retomar para saídas de áudio diretas (em túnel). Se o HAL implementar pausa e retomada, a pausa e a retomada da faixa serão encaminhadas ao HAL.

A sequência de chamada de pausa, liberação e retomada é respeitada executando as chamadas HAL no thread de reprodução (o mesmo que descarregamento).

Sugestões de implementação

Áudio HAL

Para o Android 11, o ID de sincronização de HW do PCR ou STC pode ser usado para sincronização A/V, portanto, o fluxo somente de vídeo é compatível.

Para Android 10 ou inferior, os dispositivos compatíveis com a reprodução de vídeo em túnel devem ter pelo menos um perfil de fluxo de saída de áudio com os FLAG_HW_AV_SYNC e AUDIO_OUTPUT_FLAG_DIRECT em seu arquivo audio_policy.conf . Esses sinalizadores são usados ​​para definir o relógio do sistema a partir do relógio de áudio.

OMX

Os fabricantes de dispositivos devem ter um componente OMX separado para reprodução de vídeo em túnel (os fabricantes podem ter componentes OMX adicionais para outros tipos de reprodução de áudio e vídeo, como reprodução segura). O componente encapsulado deve:

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

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

  • Especifique seus recursos no arquivo media_codecs.xml e declare o recurso de reprodução em túnel. Ele também deve esclarecer quaisquer limitações no tamanho do quadro, alinhamento ou taxa de bits. Um exemplo é mostrado 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 dar suporte à decodificação encapsulada e não encapsulada, ele deverá deixar o recurso de reprodução encapsulada como não obrigatório. Ambos os decodificadores encapsulados e não encapsulados têm as mesmas limitações de capacidade. Um exemplo é mostrado 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>

Compositor de Hardware (HWC)

Quando há uma camada encapsulada (uma camada com HWC_SIDEBAND compositionType ) em uma exibição, o sidebandStream da camada é o identificador de banda lateral alocado pelo componente de vídeo OMX.

O HWC sincroniza quadros de vídeo decodificados (do componente OMX encapsulado) com a faixa de áudio associada (com o ID audio-hw-sync ). Quando um novo quadro 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 configuração e exibe a imagem resultante. As chamadas de preparação ou configuração ocorrem 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 quadros de vídeo (7b) com a composição mais recente (7a) para exibição na hora correta, com base no áudio (7c).

HWC combinando quadros de vídeo com base em áudio

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