Tunelización multimedia

Puede implementar túneles multimedia en el marco de Android 5.0 y superior. Aunque no se requiere túnel multimedia para Android TV, proporciona la mejor experiencia para contenido de ultra alta definición (4K).

Para Android 11 o superior, puede implementar túneles multimedia con contenido de audio y video alimentado directamente desde Tuner. Codec2 y AudioTrack pueden usar la ID de sincronización HW de Tuner, que podría corresponder al canal de referencia del reloj del programa (PCR) o al canal del reloj de tiempo del sistema (STC).

Fondo

El marco de medios de Android maneja el contenido de audio/video de cuatro maneras:

  • Software puro (descodificación local): el procesador de aplicaciones (AP) decodifica localmente audio a modulación de código de pulso (PCM) sin aceleración especial. Siempre se usa para Ogg Vorbis y se usa para MP3 y AAC cuando no hay soporte de descarga comprimida.
  • La descarga de audio comprimido envía datos de audio comprimidos directamente al procesador de señal digital (DSP) y mantiene el AP apagado tanto como sea posible. Úselo para reproducir archivos de música con la pantalla apagada.
  • La transferencia de audio comprimido envía audio comprimido (específicamente AC3 y E-AC3) directamente a través de HDMI a un televisor o receptor de audio externo, sin decodificarlo en el dispositivo Android TV. La porción de video se maneja por separado.
  • La tunelización multimedia envía datos de audio y video comprimidos juntos. Cuando los decodificadores de video y audio reciben el flujo codificado, no vuelve al marco. Idealmente, la transmisión no interrumpe el AP.
  • El paso multimedia envía datos de audio y video comprimidos juntos desde Tuner a decodificadores de video y audio sin involucrar el marco.
Diagrama de flujo de tunelización multimedia
Figura 1. Flujo de tunelización multimedia

Comparación de enfoques

software puro Descarga de audio comprimido Transferencia de audio comprimido tunelización multimedia Transferencia multimedia
Decodificar ubicación punto de acceso DSP TV o receptor de audio/video (AVR) TV o AVR TV o AVR
Maneja audio no
vídeo de las manijas no no no

Para desarrolladores de aplicaciones

Cree una instancia de SurfaceView , obtenga una ID de sesión de audio, luego cree las instancias de AudioTrack y MediaCodec para proporcionar la sincronización y las configuraciones necesarias para la reproducción y la decodificación de cuadros de video.

Para Android 11 o superior, como alternativa para la identificación de la sesión de audio, la aplicación puede obtener la identificación de sincronización HW de Tuner y proporcionarla a las instancias de AudioTrack y MediaCodec para la sincronización A/V.

sincronización audiovisual

En el modo de tunelización multimedia, el audio y el video se sincronizan en un reloj maestro.

  • Para Android 11 o superior, PCR o STC de Tuner pueden ser el reloj maestro para la sincronización A/V.
  • Para Android 10 o inferior, el reloj de audio es el reloj maestro que se usa para la reproducción de A/V.

Si la instancia de MediaCodec de video tunelizado y las instancias de AudioTrack están vinculadas a la instancia HW_AV_SYNC en AudioTrack , el reloj implícito derivado de HW_AV_SYNC restringe cuándo se presenta cada cuadro de video y muestra de audio, según la marca de tiempo de presentación (PTS) del cuadro de audio o video real.

Flujo de llamadas a la API

Para Android 11 o superior, el cliente puede usar la ID de sincronización HW de Tuner.

  1. Cree una instancia de SurfaceView .
    SurfaceView sv = new SurfaceView(mContext);
  2. Obtenga una ID de sesión de audio. Esta ID única se utiliza para crear la pista de audio ( AudioTrack ). Se pasa al códec de medios ( MediaCodec ) y lo utiliza el marco de medios para vincular las rutas de audio y video.
    AudioManager am = mContext.getSystemService(AUDIO_SERVICE);
    int audioSessionId = am.generateAudioSessionId()
    // or, for Android 11 or higher
    int avSyncId = tuner.getAvSyncHwId();
  3. Cree AudioTrack con HW A/V sync AudioAttributes .

    El administrador de políticas de audio solicita a la capa de abstracción de hardware (HAL) una salida de dispositivo que admita FLAG_HW_AV_SYNC y crea una pista de audio conectada directamente a esta salida sin un mezclador intermedio.

    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. Cree una instancia de MediaCodec de video y configúrela para la reproducción de video tunelizado.
    // 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. Decodifica los cuadros de video.
    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: Puede cambiar el orden de los pasos 3 y 4 en este proceso, como se ve en las dos figuras a continuación.

Diagrama de la pista de audio creada antes de configurar el códec
Figura 2. Pista de audio creada antes de configurar el códec
Diagrama de la pista de audio creada después de configurar el códec
Figura 3. Pista de audio creada después de configurar el códec

Para fabricantes de dispositivos

Los OEM deben crear un componente OpenMAX IL (OMX) decodificador de video separado para admitir la reproducción de video tunelizado. Este componente OMX debe anunciar que es capaz de reproducción en túnel (en media_codecs.xml ).

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

El componente también debe admitir un parámetro extendido OMX OMX.google.android.index.configureVideoTunnelMode que usa una estructura 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
};

Cuando se realiza una solicitud de creación de MediaCodec tunelizado, el marco configura el componente OMX en modo tunelizado (configurando bTunneled en OMX_TRUE ) y pasa el dispositivo de salida de audio asociado creado con un indicador AUDIO_HW_AV_SYNC al componente OMX (en nAudioHwSync ).

Si el componente admite esta configuración, debe asignar un identificador de banda lateral a este códec y devolverlo a través del miembro pSidebandWindow . Un identificador de banda lateral es una etiqueta de identificación para la capa tunelada que permite que Hardware Composer (HW Composer) la identifique. Si el componente no admite esta configuración, debe establecer bTunneled en OMX_FALSE .

El marco recupera la capa tunelada (el identificador de banda lateral) asignada por el componente OMX y la pasa a HW Composer. El tipo de compositionType de esta capa se establece en HWC_SIDEBAND . (Consulte hardware/libhardware/include/hardware/hwcomposer.h ).

HW Composer es responsable de recibir nuevos búferes de imágenes del flujo en el momento adecuado (por ejemplo, sincronizados con el dispositivo de salida de audio asociado), componerlos con el contenido actual de otras capas y mostrar la imagen resultante. Esto sucede independientemente del ciclo normal de preparación/programación. Las llamadas de preparación/establecimiento ocurren solo cuando cambian otras capas, o cuando cambian las propiedades de la capa de banda lateral (como la posición o el tamaño).

Configuración

frameworks/av/services/audioflinger/AudioFlinger.cpp

HAL devuelve el ID de HW_AV_SYNC como una representación decimal de cadena de caracteres de un entero de 64 bits. (Consulte 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

El marco de trabajo de audio debe encontrar el flujo de salida HAL al que corresponde este ID de sesión y consultar el HAL para el hwAVSyncId usando set_parameters .

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

Configuración del decodificador OMX

MediaCodec.java

El marco de trabajo de audio encuentra el flujo de salida HAL correspondiente para este ID de sesión y recupera el ID audio-hw-sync consultando HAL para el indicador 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);
}

// ...

Este ID de sincronización HW se pasa al decodificador de video tunelizado OMX mediante el parámetro personalizado OMX.google.android.index.configureVideoTunnelMode .

ACodec.cpp

Después de obtener el ID de sincronización del hardware de audio, ACodec lo usa para configurar el decodificador de video tunelizado para que el decodificador de video tunelizado sepa qué pista de audio 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

El componente OMX se configura mediante el método configureVideoTunnelMode anterior.

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

Una vez que el componente OMX se configura en modo de túnel, el identificador de la banda lateral se asocia con la superficie de representación.

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

Luego, la sugerencia de resolución máxima, si está presente, se envía al 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 soporte

Android 5.0 y versiones anteriores no incluyen compatibilidad con pausas. Puede pausar la reproducción tunelada solo por agotamiento de A/V, pero si el búfer interno para video es grande (por ejemplo, hay 1 segundo de datos en el componente OMX), hace que la pausa parezca no responder.

En Android 5.1 y superior, AudioFlinger admite pausa y reanudación para salidas de audio directas (tunelizadas). Si la HAL implementa pausa/reanudación, la pausa/reanudación de la pista se reenvía a la HAL.

La secuencia de llamadas de pausa, vaciado y reanudación se respeta mediante la ejecución de las llamadas HAL en el subproceso de reproducción (igual que la descarga).

Compatibilidad con códec2

Para Android 11 o superior, Codec2 admite la reproducción en túnel.

CCodec.cpp

Para admitir la reproducción en túnel, Codec2 funciona de manera similar a OMX. Para admitir la identificación de sincronización HW de Tuner, Codec2 busca la identificación de sincronización de las fuentes a continuación.

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

Sugerencias de implementación

HAL de audio

Para Android 11, la ID de sincronización HW de PCR o STC se puede usar para la sincronización A/V, por lo que solo se admite la transmisión de video.

Para Android 10 o versiones anteriores, los dispositivos que admitan la reproducción de video en túnel deben tener al menos un perfil de flujo de salida de audio con las FLAG_HW_AV_SYNC y AUDIO_OUTPUT_FLAG_DIRECT en su archivo audio_policy.conf . Estos indicadores se utilizan para configurar el reloj del sistema desde el reloj de audio.

OMX

Los fabricantes de dispositivos deben tener un componente OMX separado para la reproducción de video tunelizado. Los fabricantes pueden tener componentes OMX adicionales para otros tipos de reproducción de audio y video, como la reproducción segura.

Este componente debe especificar 0 búferes ( nBufferCountMin , nBufferCountActual ) en su puerto de salida.

El componente tunelizado también debe implementar la extensión OMX.google.android.index.prepareForAdaptivePlayback setParameter .

El componente tunelizado debe especificar sus capacidades en el archivo media_codecs.xml y declarar la característica de reproducción tunelizada. También debe aclarar cualquier limitación en el tamaño del cuadro, la alineación o la tasa 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>

Si se utiliza el mismo componente OMX para admitir la decodificación con túnel y sin túnel, entonces debería dejar la función de reproducción con túnel como no requerida. Tanto los decodificadores tunelizados como los no tunelizados tienen las mismas limitaciones de capacidad.

    <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

Cuando hay una capa tunelada (una capa con HWC_SIDEBAND compositionType en una pantalla, el sidebandStream de la capa es el identificador de banda lateral asignado por el componente de video OMX.

HW Composer sincroniza fotogramas de vídeo decodificados (del componente OMX tunelizado) con la pista de audio asociada (con el ID audio-hw-sync ). Cuando un nuevo cuadro de video se vuelve actual, HW Composer lo combina con el contenido actual de todas las capas recibidas durante la última llamada de preparación/configuración y muestra la imagen resultante. Las llamadas de preparación/establecimiento ocurren solo cuando cambian otras capas, o cuando cambian las propiedades de la capa de banda lateral (como la posición o el tamaño).

La figura 4 representa el HW Composer trabajando con el sincronizador HW (o kernel/driver) para combinar fotogramas de video (7b) con la última composición (7a) para mostrarlos en el momento correcto, según el audio (7c).

Diagrama del compositor de hardware que combina fotogramas de video basados ​​en el audio
Figura 4. HW Composer trabajando con el sincronizador HW (o kernel/driver)