El túnel multimedia permite que los datos de video comprimidos pasen por un decodificador de video de hardware directamente a una pantalla, sin que los procese el código de la app ni el código del framework de Android. El código específico del dispositivo que se encuentra debajo de la pila de Android determina qué fotogramas de video se envían a la pantalla y cuándo se envían comparando las marcas de tiempo de presentación de los fotogramas de video con uno de los siguientes tipos de reloj interno:
Para la reproducción de video a pedido en Android 5 o versiones posteriores, un reloj
AudioTrack
sincronizado con las marcas de tiempo de la presentación de audio que la app pasaPara la reproducción de transmisiones en vivo en Android 11 o versiones posteriores, un reloj de referencia del programa (PCR) o un reloj del sistema (STC) controlado por un sintonizador
Información general
La reproducción de video tradicional en Android notifica a la app cuando se decodificó un fotograma de video comprimido. Luego, la app libera el fotograma de video decodificado para que se renderice en la pantalla al mismo tiempo del reloj del sistema que el fotograma de audio correspondiente, recuperando instancias históricas de AudioTimestamps
para calcular la sincronización correcta.
Dado que la reproducción de video en túnel omite el código de la app y reduce la cantidad de procesos que actúan sobre 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), ya que evita problemas de sincronización que pueden surgir por una posible desviación entre la sincronización de las solicitudes de Android para renderizar video y la sincronización de las sincronizaciones verticales de hardware reales. Sin embargo, el tunneling también puede reducir la compatibilidad con efectos de GPU, como el desenfoque o las esquinas redondeadas en ventanas de imagen en imagen (PiP), ya que los búferes omiten la pila de gráficos de Android.
En el siguiente diagrama, se muestra cómo el túnel simplifica el proceso de reproducción de video.
Figura 1: Comparación de los procesos de reproducción de video tradicionales y con túnel
Para desarrolladores de apps
Dado que la mayoría de los desarrolladores de apps se integran con una biblioteca para implementar 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 en túnel, usa las siguientes instrucciones.
Para la reproducción de video a pedido en Android 5 o versiones posteriores, haz lo siguiente:
Crea una instancia de
SurfaceView
.Crea una instancia de
audioSessionId
.Crea instancias de
AudioTrack
yMediaCodec
con la instancia deaudioSessionId
creada en el paso 2.Pone en cola los datos de audio en
AudioTrack
con la marca de tiempo de presentación del primer fotograma de audio en los datos de audio.
Para reproducir transmisiones en vivo en Android 11 o versiones posteriores, haz lo siguiente:
Crea una instancia de
SurfaceView
.Obtén una instancia de
avSyncHwId
a partir deTuner
.Crea instancias de
AudioTrack
yMediaCodec
con la instancia deavSyncHwId
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 on demand
Dado que la reproducción de video on demand a través de un túnel se vincula de forma implícita a la reproducción de AudioTrack
, el comportamiento de la reproducción de video a través de un 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 necesite 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 busca.
Para indicar que el primer fotograma de video en cola se debe renderizar en cuanto se decodifique, establece el parámetro
PARAMETER_KEY_TUNNEL_PEEK
en1
. Cuando los fotogramas de video comprimidos se reordenan en la cola (por ejemplo, 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 del video en cola 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 (subejecución de audio), la reproducción de video se detiene hasta que se escriben más datos de audio, ya que 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 silencioso (según la implementación del OEM). La posición del fotograma
AudioTimestamp
no aumenta para los fotogramas de audio silencioso insertados.
Para fabricantes de dispositivos
Configuración
Los OEM 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 realizar la reproducción en 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 AudioFlinger
para obtener 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 de HW_AV_SYNC
del dispositivo de audio principal y lo asocia internamente 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 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 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 con la transmisión del sintonizador.
Durante la configuración del componente, el componente OMX o Codec2 debe devolver un identificador de banda lateral que se pueda usar para asociar el códec con una capa de Hardware Composer (HWC). Cuando la app asocia una superficie con MediaCodec
, este identificador de banda lateral se pasa al 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;
}
El HWC es responsable de recibir nuevos búferes de imagen del resultado 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 del sintonizador, componer los búferes con el contenido actual de otras capas y mostrar 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 producen 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 de decodificador en túnel debe admitir lo siguiente:
Establece el 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 indica al códec si debe renderizar el primer fotograma de video decodificado o no, independientemente de si se inició la reproducción de audio.Envía el evento
OMX_EventOnFirstTunnelFrameReady
cuando se decodificó el primer fotograma de video canalizado y está listo para renderizarse.
La implementación del 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
.
Codec2
En Android 11 y versiones posteriores, Codec2
admite la reproducción en túnel. El componente del decodificador debe admitir lo siguiente:
Configurar
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 sintonizadorConsultar
C2_PARAMKEY_OUTPUT_TUNNEL_HANDLE
para asignar y recuperar el identificador de banda lateral para HWCControla
C2_PARAMKEY_TUNNEL_HOLD_RENDER
cuando se adjunta a unC2Work
, que indica al códec que decodifique y señale la finalización del trabajo, pero no que renderice el búfer de salida hasta que 1) se le indique al códec que lo renderice más adelante o 2) comience la reproducción de audio.Controla
C2_PARAMKEY_TUNNEL_START_RENDER
, que indica al códec que renderice de inmediato el fotograma marcado conC2_PARAMKEY_TUNNEL_START_RENDER
, incluso si no se inició la reproducción de audio.C2_PARAMKEY_TUNNEL_HOLD_RENDER
Deja
debug.stagefright.ccodec_delayed_params
sin configurar (recomendado). Si lo configuras, establécelo enfalse
.
La implementación del 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 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 a pedido, 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 los fotogramas de video en sincronía 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 resincronizar el reloj de reproducción con el procesamiento de audio. Para resincronizar cuando se reproduce audio comprimido, es posible que el HAL de audio deba analizar los metadatos dentro de los datos de audio comprimido para determinar su duración de reproducción.
Compatibilidad con la pausa
Android 5 o versiones anteriores no incluyen compatibilidad con la pausa. Solo puedes pausar la reproducción en túnel por falta de datos de A/V, pero si el búfer interno para el video es grande (por ejemplo, hay un segundo de datos en el componente OMX), la pausa parece no responder.
En Android 5.1 y versiones posteriores, AudioFlinger
admite la pausa y la reanudación de las salidas de audio directas (en túnel). Si el HAL implementa la pausa y la reanudación, la pausa y la reanudación del seguimiento se reenvían al HAL.
La secuencia de llamadas de pausa, vaciado y reanudación se respeta ejecutando las llamadas de HAL en el subproceso de reproducción (igual que en 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 solo de video.
En Android 10 y versiones anteriores, los dispositivos que admiten la reproducción de video en túnel deben tener al menos un perfil de transmisión 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 a partir del 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 canalizado 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 las limitaciones de tamaño, alineación o 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 de OMX para admitir la decodificación con túnel y sin túnel, se debe dejar la función de reproducción con túnel como no obligatoria. Por lo tanto, los decodificadores con túnel 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 asignado por 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 de banda lateral (como la posición o el tamaño).
En la siguiente figura, se representa el HWC trabajando con el sincronizador de hardware (o kernel o controlador) para combinar los fotogramas de video (7b) con la composición más reciente (7a) para mostrarlos en el momento correcto, según el audio (7c).
Figura 2: Sincronizador de hardware (o kernel o controlador) de HWC