Tunelización de contenido multimedia

El tunelización multimedia permite que los datos de video comprimidos se tunelicen a través de un decodificador de video de hardware directamente a una pantalla, sin que el código de la app o el código del framework de Android los procesen. El código específico del dispositivo debajo de la pila de Android determina qué fotogramas de video enviar a la pantalla y cuándo enviarlos comparando las marcas de tiempo de presentación de fotogramas de video con uno de los siguientes tipos de reloj interno:

  • Para la reproducción de video on demand en Android 5 o versiones posteriores, un reloj AudioTrack sincronizado con las marcas de tiempo de presentación de audio que pasa la app

  • Para la reproducción de transmisiones en vivo en Android 11 o versiones posteriores, un reloj de referencia de programa (PCR) o un reloj de tiempo del sistema (STC) controlado por un sintonizador

Información general

La reproducción de video tradicional en Android notifica a la app cuando se decodifica un fotograma de video comprimido. Luego, la app libera el fotograma de video decodificado a la pantalla para que se renderice en la misma hora del reloj del sistema que el fotograma de audio correspondiente, recuperando instancias históricas de AudioTimestamps para calcular el tiempo correcto.

Debido a que la reproducción de video en túneles pasa por alto el código de la app y reduce la cantidad de procesos que actúan en el video, puede proporcionar una renderización de video más eficiente según la implementación del OEM. También puede proporcionar una cadencia y sincronización de video más precisas con el reloj elegido (PRC, STC o audio) evitando problemas de sincronización que introduce un posible sesgo entre los tiempos de las solicitudes de Android para renderizar videos y los tiempos de las vsync de hardware reales. Sin embargo, el tunelización también puede reducir la compatibilidad con los efectos de la GPU, como el desenfoque o las esquinas redondeadas en las ventanas de pantalla en pantalla (PiP), ya que los búferes omiten la pila de gráficos de Android.

En el siguiente diagrama, se muestra cómo el tunelización simplifica el proceso de reproducción de video.

comparación de los modos tradicional y de túnel

Figura 1: Comparación de los procesos de reproducción de video tradicionales y con túneles

Para desarrolladores de apps

Debido a que la mayoría de los desarrolladores de apps integran una biblioteca para la implementación de la reproducción, en la mayoría de los casos, la implementación solo requiere la reconfiguración de esa biblioteca para la reproducción en túnel. Para la implementación de bajo nivel de un reproductor de video en túnel, usa las siguientes instrucciones.

Para la reproducción de videos a pedido en Android 5 o versiones posteriores, haz lo siguiente:

  1. Crea una instancia de SurfaceView.

  2. Crea una instancia de audioSessionId.

  3. Crea instancias de AudioTrack y MediaCodec con la instancia de audioSessionId que creaste en el paso 2.

  4. Agrega datos de audio a una fila en AudioTrack con la marca de tiempo de presentación para el primer fotograma de audio en los datos de audio.

Para reproducir transmisiones en vivo en Android 11 o versiones posteriores, haz lo siguiente:

  1. Crea una instancia de SurfaceView.

  2. Obtén una instancia de avSyncHwId desde Tuner.

  3. Crea instancias de AudioTrack y MediaCodec con la instancia de avSyncHwId que creaste en el paso 2.

El flujo de llamadas a la API se muestra en los siguientes fragmentos 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);

Comportamiento de la reproducción de video on demand

Debido a que la reproducción de video on demand en túnel está vinculada de forma implícita a la reproducción de AudioTrack, el comportamiento de la reproducción de video en túnel puede depender del comportamiento de la reproducción de audio.

  • En la mayoría de los dispositivos, de forma predeterminada, no se renderiza un fotograma de video hasta que comienza la reproducción de audio. Sin embargo, es posible que la app deba renderizar un fotograma de video antes de iniciar la reproducción de audio, por ejemplo, para mostrarle al usuario la posición actual del video mientras se busca.

    • Para indicar que el primer fotograma de video en fila debe renderizarse en cuanto se decodifica, establece el parámetro PARAMETER_KEY_TUNNEL_PEEK en 1. Cuando los fotogramas de video comprimidos se vuelven a ordenar en la fila (como cuando hay fotogramas B), esto significa que el primer fotograma de video que se muestra siempre debe ser un fotograma I.

    • Si no quieres que se renderice el primer fotograma de video en fila hasta que comience la reproducción de audio, establece este parámetro en 0.

    • Si no se establece este parámetro, el OEM determina el comportamiento del dispositivo.

  • Cuando no se proporcionan datos de audio a AudioTrack y los búferes están vacíos (subdesbordamiento de audio), la reproducción de video se detiene hasta que se escriben más datos de audio porque el reloj de audio ya no avanza.

  • Durante la reproducción, es posible que aparezcan discontinuidades que la app no puede corregir en las marcas de tiempo de la presentación de audio. Cuando esto sucede, el OEM corrige las brechas negativas deteniendo el fotograma de video actual y las brechas positivas descartando fotogramas de video o insertando fotogramas de audio silenciosos (según la implementación del OEM). La posición de fotogramas AudioTimestamp no aumenta para los fotogramas de audio silenciosos insertados.

Para fabricantes de dispositivos

Configuración

Los OEMs deben crear un decodificador de video independiente para admitir la reproducción de video en túnel. Este decodificador debe anunciar que es capaz de reproducir contenido a través de un túnel en el archivo media_codecs.xml:

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

Cuando se configura una instancia de MediaCodec con túnel con un ID de sesión de audio, consulta a AudioFlinger por este 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 esta consulta, AudioFlinger recupera el ID HW_AV_SYNC del dispositivo de audio principal y lo asocia de forma interna con el ID de sesión de audio:

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

Si ya se creó una instancia de AudioTrack, el ID de HW_AV_SYNC se pasa al flujo de salida con el mismo ID de sesión de audio. Si aún no se creó, el ID de HW_AV_SYNC se pasa al flujo de salida durante la creación de AudioTrack. Esto se realiza mediante el subproceso de reproducción:

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

El ID HW_AV_SYNC, ya sea que corresponda a una transmisión de salida de audio o a una configuración de Tuner, se pasa al componente OMX o Codec2 para que el código del OEM pueda asociar el códec con la transmisión de salida de audio correspondiente o la transmisión del sintonizador.

Durante la configuración del componente, el componente OMX o Codec2 debe mostrar un control de banda lateral que se puede usar para asociar el códec con una capa de compositor de hardware (HWC). Cuando la app asocia una superficie con MediaCodec, este control lateral se pasa a HWC a través de SurfaceFlinger, que configura la capa como una capa 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;
}

HWC es responsable de recibir nuevos búferes de imagen del resultado del códec en el momento adecuado, ya sea sincronizado con el flujo de salida de audio asociado o con el reloj de referencia del programa del sintonizador, combinando los búferes con el contenido actual de otras capas y mostrando la imagen resultante. Esto sucede independientemente del ciclo normal de preparación y configuración. Las llamadas de preparación y configuración solo se realizan cuando cambian otras capas o cuando cambian las propiedades de la capa lateral (como la posición o el tamaño).

OMX

Un componente de decodificador con túnel debe admitir lo siguiente:

  • Configurar el parámetro extendido OMX.google.android.index.configureVideoTunnelMode, que usa la estructura ConfigureVideoTunnelModeParams para pasar el ID HW_AV_SYNC asociado con el dispositivo de salida de audio

  • Configurar el parámetro OMX_IndexConfigAndroidTunnelPeek que le indica al codificador que renderice o no el primer fotograma de video decodificado, independientemente de si comenzó la reproducción de audio

  • Enviar el evento OMX_EventOnFirstTunnelFrameReady cuando se decodificó el primer fotograma de video en túnel y está listo para renderizarse

La implementación de AOSP configura el modo de túnel en ACodec a través de OMXNodeInstance, como se muestra en el siguiente fragmento de código:

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;

Si el componente admite esta configuración, debe asignar un identificador de banda lateral a este códec y pasarlo a través del miembro pSidebandWindow para que el HWC pueda identificar el códec asociado. Si el componente no admite esta configuración, debe establecer bTunneled en OMX_FALSE.

Codec2

En Android 11 o versiones posteriores, Codec2 admite la reproducción en túnel. El componente del decodificador debe admitir lo siguiente:

  • Configura C2PortTunneledModeTuning, que configura el modo de túnel y pasa el HW_AV_SYNC recuperado del dispositivo de salida de audio o de la configuración del sintonizador.

  • Consulta C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE para asignar y recuperar el control lateral para HWC.

  • Controla C2_PARAMKEY_TUNNEL_HOLD_RENDER cuando se adjunta a un C2Work, que le indica al códec que decodifique y señale la finalización del trabajo, pero que no renderice el búfer de salida hasta que 1) se le indique más adelante al códec que lo renderice o 2) comience la reproducción de audio.

  • Controla C2_PARAMKEY_TUNNEL_START_RENDER, que le indica al códec que renderice de inmediato la trama que se marcó con C2_PARAMKEY_TUNNEL_HOLD_RENDER, incluso si no se inició la reproducción de audio.

  • Deja debug.stagefright.ccodec_delayed_params sin configurar (recomendado). Si lo configuras, configúralo como false.

La implementación de AOSP configura el modo de túnel en CCodec a través de C2PortTunnelModeTuning, como se muestra en el siguiente fragmento de código:

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

Si el componente admite esta configuración, debe asignar un identificador de banda lateral a este códec y pasarlo a través de C2PortTunnelHandlingTuning para que el HWC pueda identificar el códec asociado.

HAL de audio

Para la reproducción de video on demand, el HAL de audio recibe las marcas de tiempo de presentación de audio intercaladas con los datos de audio en formato big-endian dentro de un encabezado que se encuentra al comienzo de cada bloque de datos de audio que escribe la 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 HWC renderice fotogramas de video sincronizados con los fotogramas de audio correspondientes, el HAL de audio debe analizar el encabezado de sincronización y usar la marca de tiempo de presentación para volver a sincronizar el reloj de reproducción con la renderización de audio. Para volver a sincronizar cuando se reproduce audio comprimido, es posible que el HAL de audio deba analizar los metadatos dentro de los datos de audio comprimidos para determinar su duración de reproducción.

Detener la asistencia

Android 5 o versiones anteriores no incluyen compatibilidad con la pausa. Puedes pausar la reproducción en túnel solo por falta de A/V, pero si el búfer interno para video es grande (por ejemplo, hay un segundo de datos en el componente OMX), la pausa parece no responder.

En Android 5.1 o versiones posteriores, AudioFlinger admite pausar y reanudar las salidas de audio directas (con tunelización). Si el HAL implementa la pausa y la reanudación, la pausa y la reanudación de la pista se reenvían al HAL.

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

Sugerencias de implementación

HAL de audio

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

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

OMX

Los fabricantes de dispositivos deben tener un componente OMX independiente para la reproducción de video en túnel (los fabricantes pueden tener componentes OMX adicionales para otros tipos de reproducción de audio y video, como la reproducción segura). El componente con túneles debe hacer lo siguiente:

  • Especifica 0 búferes (nBufferCountMin, nBufferCountActual) en su puerto de salida.

  • Implementa la extensión OMX.google.android.index.prepareForAdaptivePlayback setParameter.

  • Especifica sus capacidades en el archivo media_codecs.xml y declara la función de reproducción en túnel. También debe aclarar cualquier limitación en el tamaño, la alineación o la tasa de bits de los fotogramas. A continuación, se muestra un ejemplo:

    <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 usa el mismo componente OMX para admitir la decodificación con y sin túnel, la función de reproducción con túnel debe dejarse como no obligatoria. Los decodificadores con y sin túnel tienen las mismas limitaciones de capacidad. A continuación, se muestra un ejemplo:

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

Cuando hay una capa en túnel (una capa con HWC_SIDEBAND compositionType) en una pantalla, el sidebandStream de la capa es el identificador de banda lateral que asigna el componente de video OMX.

El HWC sincroniza los fotogramas de video decodificados (del componente OMX en túnel) con la pista de audio asociada (con el ID audio-hw-sync). Cuando un nuevo fotograma de video se vuelve actual, el HWC lo compone con el contenido actual de todas las capas recibidas durante la última llamada de preparación o configuración y muestra la imagen resultante. Las llamadas de preparación o configuración solo se producen cuando cambian otras capas o cuando cambian las propiedades de la capa lateral (como la posición o el tamaño).

En la siguiente imagen, se representa el HWC que funciona con el sincronizador de hardware (o kernel o controlador) para combinar fotogramas de video (7b) con la composición más reciente (7a) para mostrarlos en el momento correcto, según el audio (7c).

HWC combina fotogramas de video en función del audio

Figura 2: Sincronizador de hardware (o kernel o controlador) de HWC