Túnel multimídia

Você pode implementar o encapsulamento multimídia na estrutura do Android 5.0 e superior. Embora o encapsulamento de multimídia não seja necessário para o Android TV, ele oferece a melhor experiência para conteúdo de ultra-alta definição (4K).

Para Android 11 ou superior, você pode implementar o encapsulamento multimídia com conteúdo de áudio e vídeo alimentado diretamente do Tuner. Codec2 e AudioTrack podem usar o ID de sincronização HW do Tuner, que pode corresponder ao canal de referência do relógio do programa (PCR) ou do relógio do sistema (STC).

Fundo

A estrutura de mídia do Android lida com conteúdo de áudio/vídeo de quatro maneiras:

  • Software puro (decodificação local): O processador de aplicativos (AP) decodifica localmente o áudio para modulação por código de pulso (PCM) sem aceleração especial. Sempre usado para Ogg Vorbis e usado para MP3 e AAC quando não há suporte para descarregamento compactado.
  • O descarregamento de áudio compactado envia dados de áudio compactados diretamente para o processador de sinal digital (DSP) e mantém o AP desligado o máximo possível. Use para reproduzir arquivos de música com a tela desligada.
  • A passagem de áudio compactado envia áudio compactado (especificamente AC3 e E-AC3) diretamente por HDMI para uma TV externa ou receptor de áudio, sem decodificá-lo no dispositivo Android TV. A parte de vídeo é tratada separadamente.
  • O encapsulamento multimídia envia dados de áudio e vídeo compactados juntos. Quando o fluxo codificado é recebido pelos decodificadores de vídeo e áudio, ele não volta para a estrutura. Idealmente, o fluxo não interrompe o AP.
  • A passagem multimídia envia dados compactados de áudio e vídeo do Tuner para os decodificadores de vídeo e áudio sem envolver a estrutura.
Diagrama de fluxo de encapsulamento multimídia
Figura 1. Fluxo de encapsulamento multimídia

Comparação de abordagem

Software puro Descarregamento de áudio compactado Passagem de áudio compactado Tunelamento multimídia Passagem multimídia
Local de decodificação PA DSP TV ou receptor de áudio/vídeo (AVR) TV ou AVR TV ou AVR
Lida com áudio sim sim sim sim não
Lida com vídeo sim não não sim não

Para desenvolvedores de aplicativos

Crie uma instância SurfaceView , obtenha uma ID de sessão de áudio e, em seguida, crie as instâncias AudioTrack e MediaCodec para fornecer o tempo e as configurações necessárias para reprodução e decodificação de quadros de vídeo.

Para Android 11 ou superior, como alternativa para o ID da sessão de áudio, o aplicativo pode obter o ID de sincronização de HW do Tuner e fornecê-lo às instâncias AudioTrack e MediaCodec para sincronização A/V.

Sincronização A/V

No modo de encapsulamento multimídia, áudio e vídeo são sincronizados em um relógio mestre.

  • Para Android 11 ou superior, PCR ou STC do Tuner pode ser o relógio mestre para sincronização A/V.
  • Para Android 10 ou inferior, o relógio de áudio é o relógio mestre usado para reprodução A/V.

Se a instância MediaCodec de vídeo encapsulada e as instâncias AudioTrack estiverem vinculadas à instância HW_AV_SYNC em AudioTrack , o relógio implícito derivado de HW_AV_SYNC restringirá quando cada quadro de vídeo e amostra de áudio for apresentado, com base no carimbo de hora de apresentação (PTS) de áudio ou quadro de vídeo real.

Fluxo de chamadas da API

Para Android 11 ou superior, o cliente pode usar o ID de sincronização de HW do Tuner.

  1. Crie uma instância SurfaceView .
    SurfaceView sv = new SurfaceView(mContext);
  2. Obtenha um ID de sessão de áudio. Esse ID exclusivo é usado na criação da faixa de áudio ( AudioTrack ). Ele é passado para o codec de mídia ( MediaCodec ) e usado pela estrutura de mídia para vincular os caminhos de áudio e vídeo.
    AudioManager am = mContext.getSystemService(AUDIO_SERVICE);
    int audioSessionId = am.generateAudioSessionId()
    // or, for Android 11 or higher
    int avSyncId = tuner.getAvSyncHwId();
  3. Crie AudioTrack com HW A/V sync AudioAttributes .

    O gerenciador de política de áudio solicita à camada de abstração de hardware (HAL) uma saída de dispositivo que suporte FLAG_HW_AV_SYNC e cria uma trilha de áudio diretamente conectada a essa saída sem mixer intermediário.

    AudioAttributes.Builder aab = new AudioAttributes.Builder();
    aab.setUsage(AudioAttributes.USAGE_MEDIA);
    aab.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE);
    aab.setFlag(AudioAttributes.FLAG_HW_AV_SYNC);
    
    // or, for Android 11 or higher
    new tunerConfig = TunerConfiguration(0, avSyncId);
    aab.setTunerConfiguration(tunerConfig);
    
    AudioAttributes aa = aab.build();
    AudioTrack at = new AudioTrack(aa);
    
  4. Crie uma instância MediaCodec de vídeo e configure-a para reprodução de vídeo em túnel.
    // retrieve codec with tunneled video playback feature
    MediaFormat mf = MediaFormat.createVideoFormat(“video/hevc”, 3840, 2160);
    mf.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
    MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
    String codecName = mcl.findDecoderForFormat(mf);
    if (codecName == null) {
      return FAILURE;
    }
    // create codec and configure it
    mf.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId);
    
    // or, for Android 11 or higher
    mf.setInteger(MediaFormat.KEY_HARDWARE_AV_SYNC_ID, avSyncId);
    
    MediaCodec mc = MediaCodec.createCodecByName(codecName);
    mc.configure(mf, sv.getSurfaceHolder().getSurface(), null, 0);
    
  5. Decodifique os quadros de vídeo.
    mc.start();
     for (;;) {
       int ibi = mc.dequeueInputBuffer(timeoutUs);
       if (ibi >= 0) {
         ByteBuffer ib = mc.getInputBuffer(ibi);
         // fill input buffer (ib) with valid data
         ...
         mc.queueInputBuffer(ibi, ...);
       }
       // no need to dequeue explicitly output buffers. The codec
       // does this directly to the sideband layer.
     }
     mc.stop();
     mc.release();
     mc = null;

Nota: Você pode alternar a ordem das etapas 3 e 4 neste processo, conforme visto nas duas figuras abaixo.

Diagrama da faixa de áudio criada antes da configuração do codec
Figura 2. Faixa de áudio criada antes da configuração do codec
Diagrama da faixa de áudio criada após a configuração do codec
Figura 3. Faixa de áudio criada após a configuração do codec

Para fabricantes de dispositivos

Os OEMs devem criar um componente decodificador de vídeo OpenMAX IL (OMX) separado para dar suporte à reprodução de vídeo em túnel. Este componente OMX deve anunciar que é capaz de reprodução em túnel (em media_codecs.xml ).

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

O componente também deve oferecer suporte a um parâmetro estendido OMX OMX.google.android.index.configureVideoTunnelMode que usa uma estrutura ConfigureVideoTunnelModeParams .

struct ConfigureVideoTunnelModeParams {
    OMX_U32 nSize;              // IN
    OMX_VERSIONTYPE nVersion;   // IN
    OMX_U32 nPortIndex;         // IN
    OMX_BOOL bTunneled;         // IN/OUT
    OMX_U32 nAudioHwSync;       // IN
    OMX_PTR pSidebandWindow;    // OUT
};

Quando uma solicitação de criação de MediaCodec encapsulada é feita, a estrutura configura o componente OMX no modo encapsulado (definindo bTunneled como OMX_TRUE ) e passa o dispositivo de saída de áudio associado criado com um sinalizador AUDIO_HW_AV_SYNC para o componente OMX (em nAudioHwSync ).

Se o componente oferecer suporte a essa configuração, ele deve alocar um identificador de banda lateral para esse codec e passá-lo de volta pelo membro pSidebandWindow . Um identificador de banda lateral é uma etiqueta de identificação para a camada encapsulada que permite que o Hardware Composer (HW Composer) a identifique. Se o componente não suportar esta configuração, ele deve definir bTunneled para OMX_FALSE .

A estrutura recupera a camada encapsulada (o identificador de banda lateral) alocada pelo componente OMX e a passa para o HW Composer. O tipo de compositionType dessa camada é definido como HWC_SIDEBAND . (Consulte hardware/libhardware/include/hardware/hwcomposer.h .)

O HW Composer é responsável por receber novos buffers de imagem do stream no momento apropriado (por exemplo, sincronizado com o dispositivo de saída de áudio associado), compondo-os com o conteúdo atual de outras camadas e exibindo a imagem resultante. Isso acontece independentemente do ciclo normal de preparação/configuração. As chamadas de preparação/configuração ocorrem somente quando outras camadas são alteradas ou quando as propriedades da camada de banda lateral (como posição ou tamanho) são alteradas.

Configuração

frameworks/av/services/audioflinger/AudioFlinger.cpp

O HAL retorna o ID HW_AV_SYNC como uma representação decimal de cadeia de caracteres de um inteiro de 64 bits. (Veja frameworks/av/services/audioflinger/AudioFlinger.cpp .)

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

frameworks/av/services/audioflinger/Threads.cpp

A estrutura de áudio deve encontrar o fluxo de saída HAL ao qual essa ID de sessão corresponde e consultar o HAL para o hwAVSyncId usando set_parameters .

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

Configuração do decodificador OMX

MediaCodec.java

A estrutura de áudio encontra o fluxo de saída HAL correspondente para esta ID de sessão e recupera a ID audio-hw-sync consultando a HAL para o sinalizador AUDIO_PARAMETER_STREAM_HW_AV_SYNC usando get_parameters .

// Retrieve HW AV sync audio output device from Audio Service
// in MediaCodec.configure()
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);
}

// ...

Esse ID de sincronização de HW é passado para o decodificador de vídeo em túnel OMX usando o parâmetro personalizado OMX.google.android.index.configureVideoTunnelMode .

ACodec.cpp

Depois de obter o ID de sincronização de hardware de áudio, o ACodec o usa para configurar o decodificador de vídeo encapsulado para que o decodificador de vídeo encapsulado saiba qual faixa de áudio sincronizar.

// Assume you're going to use tunneled video rendering.
// Configure OMX component in tunneled mode and grab sideband handle (sidebandHandle) from OMX
// component.

native_handle_t* sidebandHandle;

// Configure OMX component in tunneled mode
status_t err = mOMX->configureVideoTunnelMode(mNode, kPortIndexOutput,
        OMX_TRUE, audioHwSync, &sidebandHandle);

OMXNodeInstance.cpp

O componente OMX é configurado pelo método configureVideoTunnelMode acima.

// paraphrased

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;

ACodec.cpp

Depois que o componente OMX é configurado no modo encapsulado, o identificador de banda lateral é associado à superfície de renderização.

  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; }

Em seguida, a dica de resolução máxima, se presente, é enviada ao componente.

// Configure max adaptive playback resolution - as for any other video decoder
int32_t maxWidth = 0, maxHeight = 0;
if (msg->findInt32("max-width", &maxWidth) &&
    msg->findInt32("max-height", &maxHeight)) {
    err = mOMX->prepareForAdaptivePlayback(
              mNode, kPortIndexOutput, OMX_TRUE, maxWidth, maxHeight);
}

Pausar suporte

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

No Android 5.1 e superior, o AudioFlinger suporta pausar e retomar para saídas de áudio diretas (em túnel). Se o HAL implementar pausa/retomada, a pausa/retomada da faixa será encaminhada para o 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).

Suporte a Codec2

Para Android 11 ou superior, o Codec2 é compatível com a reprodução em túnel.

CCodec.cpp

Para suportar a reprodução em túnel, o Codec2 funciona de forma semelhante ao OMX. Para suportar o ID de sincronização de HW do Tuner, o Codec2 procura o ID de sincronização nas fontes abaixo.

sp<ANativeWindow> nativeWindow = static_cast<ANativeWindow *>(surface.get());
int32_t audioHwSync = 0;
if (!msg->findInt32("hw-av-sync-id", &audioHwSync)) {
       if (!msg->findInt32("audio-hw-sync", &audioHwSync)) {
       }
}
err = configureTunneledVideoPlayback(comp, audioHwSync, nativeWindow);

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

Este componente deve especificar 0 buffers ( nBufferCountMin , nBufferCountActual ) em sua porta de saída.

O componente encapsulado também deve implementar a extensão OMX.google.android.index.prepareForAdaptivePlayback setParameter .

O componente encapsulado deve especificar seus recursos no arquivo media_codecs.xml e declarar o recurso de reprodução encapsulada. Ele também deve esclarecer quaisquer limitações no tamanho do quadro, alinhamento ou taxa de bits.


    <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 em túnel e não em túnel, ele deverá deixar o recurso de reprodução em túnel como não necessário. Ambos os decodificadores encapsulados e não encapsulados têm as mesmas limitações de capacidade.

    <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 HW

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 HW Composer 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 HW Composer o compõe com o conteúdo atual de todas as camadas recebidas durante a última chamada de preparação/definição e exibe a imagem resultante. As chamadas prepare/set acontecem apenas quando outras camadas mudam, ou quando as propriedades da camada de banda lateral (como posição ou tamanho) mudam.

A Figura 4 representa o HW Composer trabalhando com o sincronizador HW (ou kernel/driver), para combinar quadros de vídeo (7b) com a última composição (7a) para exibição no momento correto, com base no áudio (7c).

Diagrama do compositor de hardware combinando quadros de vídeo com base no áudio
Figura 4. HW Composer trabalhando com o sincronizador HW (ou kernel/driver)