La tunelización multimedia permite que los datos de video comprimidos pasen a través de un decodificador de video de hardware directamente a una pantalla, sin ser procesados por el código de la aplicación o el código del marco de Android. 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 a pedido en Android 5 o superior, un reloj
AudioTrack
sincronizado con las marcas de tiempo de la presentación de audio pasadas por la aplicaciónPara la reproducción de transmisiones en vivo en Android 11 o superior, un reloj de referencia de programa (PCR) o un reloj de tiempo del sistema (STC) controlado por un sintonizador
Fondo
La reproducción de video tradicional en Android notifica a la aplicación cuando se ha decodificado un cuadro de video comprimido. Luego, la aplicación libera el cuadro de video decodificado en la pantalla para que se reproduzca a la misma hora del reloj del sistema que el cuadro de audio correspondiente, recuperando instancias históricas de AudioTimestamps
de tiempo de audio para calcular el tiempo correcto.
Debido a que la reproducción de video tunelizado omite el código de la aplicación y reduce la cantidad de procesos que actúan en el video, puede proporcionar una representación de video más eficiente según la implementación del OEM. También puede proporcionar una cadencia de video y una sincronización más precisas con el reloj elegido (PRC, STC o audio) al evitar los problemas de temporización introducidos por un posible sesgo entre la temporización de las solicitudes de Android para renderizar video y la temporización de las verdaderas sincronizaciones de hardware. Sin embargo, la 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 imagen en imagen (PiP), porque los búferes omiten la pila de gráficos de Android.
El siguiente diagrama muestra cómo la tunelización simplifica el proceso de reproducción de video.
Figura 1. Comparación de los procesos de reproducción de video tradicionales y tunelizados
Para desarrolladores de aplicaciones
Debido a que la mayoría de los desarrolladores de aplicaciones se integran con una biblioteca para la implementación de la reproducción, en la mayoría de los casos, la implementación solo requiere reconfigurar esa biblioteca para la reproducción en túnel. Para la implementación de bajo nivel de un reproductor de video tunelizado, use las siguientes instrucciones.
Para reproducción de video a pedido en Android 5 o superior:
Cree una instancia de
SurfaceView
.Cree una instancia de
audioSessionId
.Cree instancias de
AudioTrack
yMediaCodec
con la instancia deaudioSessionId
creada en el paso 2.Ponga en cola datos de audio en
AudioTrack
con la marca de tiempo de presentación para el primer cuadro de audio en los datos de audio.
Para la reproducción de transmisiones en vivo en Android 11 o superior:
Cree una instancia de
SurfaceView
.Obtenga una instancia de
avSyncHwId
deTuner
.Cree instancias de
AudioTrack
yMediaCodec
con la instanciaavSyncHwId
creada 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 bajo demanda
Debido a que la reproducción de video a pedido tunelizado está ligada implícitamente a la reproducción de AudioTrack
, el comportamiento de la reproducción de video tunelizado puede depender del comportamiento de la reproducción de audio.
En la mayoría de los dispositivos, de manera predeterminada, un cuadro de video no se procesa hasta que comienza la reproducción de audio. Sin embargo, es posible que la aplicación deba procesar un cuadro de video antes de iniciar la reproducción de audio, por ejemplo, para mostrar al usuario la posición actual del video mientras busca.
Para señalar que el primer cuadro de video en cola debe procesarse tan pronto como se decodifique, establezca el parámetro
PARAMETER_KEY_TUNNEL_PEEK
en1
. Cuando los cuadros de video comprimidos se reordenan en la cola (como cuando hay cuadros B presentes), esto significa que el primer cuadro de video que se muestra siempre debe ser un cuadro I.Si no desea que se procese el primer cuadro de video en cola hasta que comience la reproducción de audio, establezca este parámetro en
0
.Si este parámetro no está configurado, el OEM determina el comportamiento del dispositivo.
Cuando no se proporcionan datos de audio a
AudioTrack
y los búferes están vacíos (insuficiencia 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, las discontinuidades que la aplicación no puede corregir pueden aparecer en las marcas de tiempo de la presentación de audio. Cuando esto sucede, el OEM corrige las brechas negativas deteniendo el cuadro de video actual y las brechas positivas eliminando cuadros de video o insertando cuadros de audio silenciosos (según la implementación del OEM). La posición del marco
AudioTimestamp
no aumenta para los marcos de audio silenciosos insertados.
Para fabricantes de dispositivos
Configuración
Los OEM deben crear un decodificador de video separado para admitir la reproducción de video tunelizado. Este decodificador debería anunciar que es capaz de reproducir en túnel en el archivo media_codecs.xml
:
<Feature name="tunneled-playback" required="true"/>
Cuando una instancia de MediaCodec
tunelizada se configura con una ID de sesión de audio, consulta a AudioFlinger
por esta 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 de HW_AV_SYNC
del dispositivo de audio principal y lo asocia internamente con el ID de la 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 ha creado 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 ha creado, el ID de HW_AV_SYNC
se pasa al flujo de salida durante la creación de AudioTrack
. Esto lo hace el hilo de reproducción :
mOutput->stream->common.set_parameters(&mOutput->stream->common, AUDIO_PARAMETER_STREAM_HW_AV_SYNC, hwAVSyncId);
El ID de HW_AV_SYNC
, ya sea que corresponda a un flujo de salida de audio o una configuración de Tuner
, se pasa al componente OMX o Codec2 para que el código OEM pueda asociar el códec con el flujo de salida de audio correspondiente o el flujo de sintonizador.
Durante la configuración del componente, el componente OMX o Codec2 debe devolver un identificador de banda lateral que se puede usar para asociar el códec con una capa de Hardware Composer (HWC). Cuando la aplicación asocia una superficie con MediaCodec
, este identificador de banda lateral se transmite a HWC a través de SurfaceFlinger
, que configura la capa como una capa 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;
}
HWC es responsable de recibir nuevos búferes de imagen de la salida del códec en el momento adecuado, ya sea sincronizados con el flujo de salida de audio asociado o con el reloj de referencia del programa sintonizador, componiendo 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 fraguado. Las llamadas de preparación y configuración 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).
OMX
Un componente decodificador tunelizado debe admitir lo siguiente:
Configuración del parámetro extendido
OMX.google.android.index.configureVideoTunnelMode
, que usa la estructuraConfigureVideoTunnelModeParams
para pasar el ID deHW_AV_SYNC
asociado con el dispositivo de salida de audio.Configurar el parámetro
OMX_IndexConfigAndroidTunnelPeek
que le dice al códec que procese o no el primer cuadro de video decodificado, independientemente de si la reproducción de audio ha comenzado.Enviar el evento
OMX_EventOnFirstTunnelFrameReady
cuando el primer cuadro de video tunelizado se haya decodificado 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 devolverlo 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
.
Códec2
En Android 11 o superior, Codec2
admite la reproducción en túnel. El componente decodificador debe admitir lo siguiente:
Configuración de
C2PortTunneledModeTuning
, que configura el modo de túnel y pasa elHW_AV_SYNC
recuperado del dispositivo de salida de audio o de la configuración del sintonizador.Consultar
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE
para asignar y recuperar el identificador de banda lateral para HWC.Manejar
C2_PARAMKEY_TUNNEL_HOLD_RENDER
cuando se adjunta a unC2Work
, que le indica al códec que decodifique y señale la finalización del trabajo, pero que no procese el búfer de salida hasta que 1) se le indique al códec que lo procese o 2) comience la reproducción de audio.Manejo de
C2_PARAMKEY_TUNNEL_START_RENDER
, que le indica al códec que reproduzca inmediatamente el cuadro marcado conC2_PARAMKEY_TUNNEL_HOLD_RENDER
, incluso si la reproducción de audio no ha comenzado.Deje
debug.stagefright.ccodec_delayed_params
configurar (recomendado). Si lo configura, configúrelo enfalse
.
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, ¶ms);
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 devolverlo a través de C2PortTunnelHandlingTuning
para que el HWC pueda identificar el códec asociado.
HAL de audio
Para la reproducción de video a pedido, Audio HAL recibe las marcas de tiempo de la presentación de audio en línea 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 aplicación:
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 reproduzca cuadros de video sincronizados con los cuadros de audio correspondientes, Audio HAL debe analizar el encabezado de sincronización y usar la marca de tiempo de la presentación para volver a sincronizar el reloj de reproducción con la reproducción de audio. Para volver a sincronizar cuando se reproduce audio comprimido, es posible que Audio HAL necesite analizar los metadatos dentro de los datos de audio comprimido para determinar la duración de la reproducción.
Pausar soporte
Android 5 o inferior no incluye soporte de pausa. 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 un segundo de datos en el componente OMX), hace que la pausa parezca no responder.
En Android 5.1 o superior, AudioFlinger
admite pausa y reanudación para salidas de audio directas (tunelizadas). Si la HAL implementa la pausa y la reanudación, la pausa y la reanudación de la pista se reenvían 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).
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 tunelizado deben tener al menos un perfil de flujo de salida de audio con los 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). El componente tunelizado debe:
Especifique 0 búferes (
nBufferCountMin
,nBufferCountActual
) en su puerto de salida.Implemente la extensión
OMX.google.android.index.prepareForAdaptivePlayback setParameter
.Especifique sus capacidades en el archivo
media_codecs.xml
y declare la función de reproducción en túnel. También debe aclarar cualquier limitación en el tamaño del cuadro, la alineación o la tasa de bits. 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 túnel y sin túnel, 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. 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>
Compositor de hardware (HWC)
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.
El HWC sincroniza cuadros de video decodificados (desde el componente OMX tunelizado) con la pista de audio asociada (con el ID audio-hw-sync
). Cuando un nuevo cuadro de video se vuelve actual, el HWC lo combina 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 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 siguiente figura representa el HWC trabajando con el sincronizador de hardware (o kernel o controlador), para combinar cuadros de video (7b) con la última composición (7a) para mostrar en el momento correcto, según el audio (7c).
Figura 2. Sincronizador de hardware HWC (o kernel o controlador)