Transmisiones en tiempo real

Marcas de tiempo en tiempo real

Los gráficos de la calculadora de MediaPipe suelen usarse para procesar transmisiones de fotogramas de audio o video en aplicaciones interactivas. El framework de MediaPipe solo requiere que a los paquetes sucesivos se les asignen marcas de tiempo que aumenten monótonamente. Por convención, las calculadoras y los gráficos en tiempo real usan el tiempo de registro o el tiempo de presentación de cada fotograma como su marca de tiempo, y cada marca de tiempo indica los microsegundos desde Jan/1/1970:00:00:00. Esto permite que los paquetes de varias fuentes se procesen en una secuencia coherente a nivel global.

Programación en tiempo real

Por lo general, cada calculadora se ejecuta en cuanto todos sus paquetes de entrada para una marca de tiempo determinada están disponibles. Por lo general, esto sucede cuando la calculadora termina de procesar el fotograma anterior y cada una de las calculadoras que producen sus entradas terminó de procesar el fotograma actual. El programador de MediaPipe invoca cada calculadora en cuanto se cumplen estas condiciones. Consulta Sincronización para obtener más detalles.

Límites de marcas de tiempo

Cuando una calculadora no produce paquetes de salida para una marca de tiempo determinada, puede generar un “límite de marca de tiempo” en el que se indique que no se producirán paquetes para esa marca de tiempo. Esta indicación es necesaria para permitir que las calculadoras descendentes se ejecuten en esa marca de tiempo, aunque no hayan llegado paquetes para ciertas transmisiones para esa marca de tiempo. Esto es muy importante para los gráficos en tiempo real en aplicaciones interactivas, ya que es fundamental que cada calculadora comience a procesar lo antes posible.

Considera un gráfico como el siguiente:

node {
   calculator: "A"
   input_stream: "alpha_in"
   output_stream: "alpha"
}
node {
   calculator: "B"
   input_stream: "alpha"
   input_stream: "foo"
   output_stream: "beta"
}

Supongamos que, en la marca de tiempo T, el nodo A no envía un paquete en su flujo de salida alpha. El nodo B obtiene un paquete en foo en la marca de tiempo T y espera un paquete en alpha en la marca de tiempo T. Si A no envía a B una actualización vinculada a la marca de tiempo para alpha, B seguirá esperando que llegue un paquete en alpha. Mientras tanto, la cola de paquetes de foo acumulará paquetes en T, T+1, etc.

Para generar un paquete en una transmisión, una calculadora usa las funciones de la API CalculatorContext::Outputs y OutputStream::Add. Si, en cambio, quieres generar una marca de tiempo limitada en una transmisión, una calculadora puede usar las funciones de la API CalculatorContext::Outputs y CalculatorContext::SetNextTimestampBound. El límite especificado es la marca de tiempo más baja permitida para el siguiente paquete en el flujo de salida especificado. Cuando no se genera ningún paquete, la calculadora suele hacer lo siguiente:

cc->Outputs().Tag("output_frame").SetNextTimestampBound(
  cc->InputTimestamp().NextAllowedInStream());

La función Timestamp::NextAllowedInStream muestra la marca de tiempo sucesiva. Por ejemplo, Timestamp(1).NextAllowedInStream() == Timestamp(2)

Propaga límites de marca de tiempo

Las calculadoras que se usarán en grafos en tiempo real deben definir los límites de la marca de tiempo de salida según los límites de la marca de tiempo de entrada para permitir que las calculadoras descendentes se programen con rapidez. Un patrón común consiste en que las calculadoras generen paquetes con las mismas marcas de tiempo que sus paquetes de entrada. En este caso, basta con enviar un paquete en cada llamada a Calculator::Process para definir los límites de marca de tiempo de salida.

Sin embargo, no es necesario que las calculadoras sigan este patrón común para las marcas de tiempo de salida; solo deben elegir marcas de tiempo de salida que aumenten monótonamente. Como resultado, algunas calculadoras deben calcular los límites de marca de tiempo de forma explícita. MediaPipe proporciona varias herramientas para calcular el límite de marca de tiempo apropiado para cada calculadora.

1. SetNextTimestampBound() se puede usar para especificar el límite de marca de tiempo, t + 1, para una transmisión de salida.

cc->Outputs.Tag("OUT").SetNextTimestampBound(t.NextAllowedInStream());

Como alternativa, se puede producir un paquete vacío con la marca de tiempo t para especificar el t + 1 vinculado a la marca de tiempo.

cc->Outputs.Tag("OUT").Add(Packet(), t);

El límite de la marca de tiempo de una transmisión de entrada se indica mediante el paquete o el paquete vacío en la transmisión de entrada.

Timestamp bound = cc->Inputs().Tag("IN").Value().Timestamp();

2. Se puede especificar TimestampOffset() para copiar automáticamente el límite de la marca de tiempo de las transmisiones de entrada a las de salida.

cc->SetTimestampOffset(0);

Esta configuración tiene la ventaja de propagar los límites de la marca de tiempo automáticamente, incluso cuando solo llegan los límites de la marca de tiempo y no se invoca Calculator::Process.

3. Se puede especificar ProcessTimestampBounds() para invocar a Calculator::Process por cada "marca de tiempo establecida" nueva, en la que la "marca de tiempo establecida" es la nueva marca de tiempo más alta que está debajo de los límites de la marca de tiempo actual. Sin ProcessTimestampBounds(), Calculator::Process solo se invoca con uno o más paquetes entrantes.

cc->SetProcessTimestampBounds(true);

Este parámetro de configuración permite que una calculadora realice su propio cálculo y propagación de los límites de la marca de tiempo, incluso cuando solo se actualizan las marcas de tiempo de entrada. Se puede usar para replicar el efecto de TimestampOffset(), pero también se puede usar a fin de calcular un límite de marca de tiempo que tenga en cuenta factores adicionales.

Por ejemplo, para replicar SetTimestampOffset(0), una calculadora podría hacer lo siguiente:

absl::Status Open(CalculatorContext* cc) {
  cc->SetProcessTimestampBounds(true);
}

absl::Status Process(CalculatorContext* cc) {
  cc->Outputs.Tag("OUT").SetNextTimestampBound(
      cc->InputTimestamp().NextAllowedInStream());
}

Programación de la Calculadora::Abrir y Calculadora::Cerrar

Calculator::Open se invoca cuando se producen todos los paquetes laterales de entrada necesarios. Los paquetes laterales de entrada se pueden proporcionar a través de la aplicación adjunta o las "calculadoras de paquetes laterales" dentro del gráfico. Los paquetes laterales se pueden especificar desde fuera del gráfico con CalculatorGraph::Initialize y CalculatorGraph::StartRun de la API. Las calculadoras dentro del gráfico pueden especificar los paquetes laterales con CalculatorGraphConfig::OutputSidePackets y OutputSidePacket::Set.

Se invoca Calculator::Close cuando todas las transmisiones de entrada se convierten en Done, ya que se cierran o alcanzan el límite de marca de tiempo Timestamp::Done.

Nota: Si el grafo finaliza toda la ejecución pendiente de la calculadora y se convierte en Done, antes de que algunas transmisiones se conviertan en Done, MediaPipe invocará las llamadas restantes a Calculator::Close para que cada calculadora pueda producir sus resultados finales.

El uso de TimestampOffset tiene algunas implicaciones para Calculator::Close. Una calculadora que especifique SetTimestampOffset(0) indicará por diseño que todas sus transmisiones de salida alcanzaron Timestamp::Done cuando todas sus transmisiones de entrada alcanzaron Timestamp::Done y, por lo tanto, no son posibles más salidas. Esto evita que esa calculadora emita cualquier paquete durante Calculator::Close. Si una calculadora necesita producir un paquete de resumen durante Calculator::Close, Calculator::Process debe especificar límites de marca de tiempo para que al menos una marca de tiempo (como Timestamp::Max) permanezca disponible durante Calculator::Close. Esto significa que esa calculadora normalmente no puede depender de SetTimestampOffset(0) y, en su lugar, debe especificar los límites de marca de tiempo explícitamente usando SetNextTimestampBounds().