Stream in tempo reale

Timestamp in tempo reale

I grafici della calcolatrice MediaPipe vengono spesso utilizzati per elaborare flussi di frame video o audio per applicazioni interattive. Il framework MediaPipe richiede solo l'assegnazione ai pacchetti successivi in modo monotonico di timestamp crescenti. Per convenzione, le calcolatrici e i grafici in tempo reale utilizzano il tempo di registrazione o il tempo di presentazione di ogni frame come timestamp e ogni timestamp indica i microsecondi a partire dal giorno Jan/1/1970:00:00:00. Ciò consente di elaborare i pacchetti di varie origini in una sequenza coerente a livello globale.

Programmazione in tempo reale

Normalmente, ogni Calcolatrice viene eseguita non appena sono disponibili tutti i pacchetti di input per un determinato timestamp. Normalmente, ciò si verifica quando la calcolatrice ha terminato l'elaborazione del frame precedente e ognuna delle calcolatrici che producono i suoi input ha terminato l'elaborazione del frame corrente. Lo scheduler MediaPipe richiama ogni calcolatore non appena vengono soddisfatte queste condizioni. Per ulteriori dettagli, consulta Sincronizzazione.

Limiti di timestamp

Quando una calcolatrice non produce pacchetti di output per un determinato timestamp, può generare un "timestamp bound" che indica che non verrà prodotto alcun pacchetto per quel timestamp. Questa indicazione è necessaria per consentire l'esecuzione delle calcolatrici downstream a quel timestamp, anche se non è arrivato nessun pacchetto per determinati flussi per quel timestamp. Questo è particolarmente importante per i grafici in tempo reale nelle applicazioni interattive, dove è fondamentale che ogni calcolatrice inizi l'elaborazione il prima possibile.

Considera un grafico simile al seguente:

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

Supponiamo: al timestamp T, il nodo A non invia un pacchetto nel suo flusso di output alpha. Il nodo B riceve un pacchetto in foo al timestamp T ed è in attesa di un pacchetto in alpha al timestamp T. Se A non invia a B un aggiornamento associato al timestamp per alpha, B continuerà ad attendere l'arrivo di un pacchetto tra alpha. Nel frattempo, la coda di pacchetti di foo accumulerà pacchetti alle ore T, T+1 e così via.

Per generare un pacchetto in un flusso, una calcolatrice utilizza le funzioni API CalculatorContext::Outputs e OutputStream::Add. Per generare un timestamp associato a uno stream, una calcolatrice può utilizzare le funzioni API CalculatorContext::Outputs e CalculatorContext::SetNextTimestampBound. Il limite specificato è il timestamp minimo consentito per il pacchetto successivo nel flusso di output specificato. Quando non viene generato alcun pacchetto, una calcolatrice solitamente funziona come:

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

La funzione Timestamp::NextAllowedInStream restituisce il timestamp successivo. Ad esempio, Timestamp(1).NextAllowedInStream() == Timestamp(2).

Propagazione dei limiti del timestamp

I calcolatori che verranno utilizzati nei grafici in tempo reale devono definire i limiti del timestamp di output in base ai limiti del timestamp di input per consentire la pianificazione immediata delle calcolatrici downstream. Uno schema comune consiste nel far restituire alle calcolatrici pacchetti con gli stessi timestamp dei pacchetti di input. In questo caso, è sufficiente inviare un pacchetto a ogni chiamata a Calculator::Process per definire i limiti del timestamp di output.

Tuttavia, non è necessario che le calcolatrici seguano questo modello comune per i timestamp di output, ma devono scegliere solo i timestamp di output che aumentano monotonicamente. Di conseguenza, alcune calcolatrici devono calcolare in modo esplicito i limiti del timestamp. MediaPipe offre diversi strumenti per calcolare il timestamp appropriato per ogni calcolatore.

1. Puoi utilizzare SetNextTimestampBound() per specificare il vincolo al timestamp t + 1 per uno stream di output.

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

In alternativa, è possibile generare un pacchetto vuoto con timestamp t per specificare il limite al timestamp t + 1.

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

Il timestamp associato di un flusso di input è indicato dal pacchetto o dal pacchetto vuoto nel flusso di input.

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

2. TimestampOffset() può essere specificato per copiare automaticamente il timestamp associato dai flussi di input ai flussi di output.

cc->SetTimestampOffset(0);

Questa impostazione ha il vantaggio di propagare automaticamente i limiti di timestamp, anche quando arrivano solo i limiti di timestamp e Calculator::Process non viene richiamato.

3. ProcessTimestampBounds() può essere specificato per richiamare Calculator::Process per ogni nuovo "timestamp regolato", in cui il "timestamp settled" è il nuovo timestamp più alto sotto i limiti del timestamp attuali. Senza ProcessTimestampBounds(), Calculator::Process viene richiamato solo con uno o più pacchetti in arrivo.

cc->SetProcessTimestampBounds(true);

Questa impostazione consente a una calcolatrice di eseguire il calcolo e la propagazione dei limiti dei timestamp, anche quando vengono aggiornati solo i timestamp di input. Può essere utilizzata per replicare l'effetto di TimestampOffset(), ma anche per calcolare un vincolo al timestamp che tenga conto di altri fattori.

Ad esempio, per replicare SetTimestampOffset(0), una calcolatrice potrebbe procedere nel seguente modo:

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

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

Pianificazione della Calcolatrice::Apri e Calcolatrice::Chiudi

Calculator::Open viene richiamato quando sono stati prodotti tutti i pacchetti laterali di input richiesti. I pacchetti laterali di input possono essere forniti dall'applicazione che include i pacchetti o da "calcolatori di pacchetti laterali" all'interno del grafico. I pacchetti laterali possono essere specificati dall'esterno del grafico utilizzando i valori CalculatorGraph::Initialize e CalculatorGraph::StartRun dell'API. I pacchetti laterali possono essere specificati da calcolatrici all'interno del grafico utilizzando CalculatorGraphConfig::OutputSidePackets e OutputSidePacket::Set.

Calcolatrice::la chiusura viene richiamata quando tutti gli stream di input sono diventati Done perché sono stati chiusi o hanno raggiunto il limite del timestamp Timestamp::Done.

Nota: se il grafico termina tutte le esecuzioni della calcolatrice in attesa e diventa Done, prima che alcuni flussi diventino Done, MediaPipe richiama le chiamate rimanenti a Calculator::Close in modo che ogni calcolatore possa produrre gli output finali.

L'utilizzo di TimestampOffset ha alcune implicazioni per Calculator::Close. Una calcolatrice che specifica SetTimestampOffset(0) indica per progettazione che tutti i flussi di output hanno raggiunto Timestamp::Done quando tutti i flussi di input hanno raggiunto Timestamp::Done e quindi non sono possibili ulteriori output. Ciò impedisce alla calcolatrice di emettere pacchetti durante il periodo Calculator::Close. Se una calcolatrice deve generare un pacchetto di riepilogo durante il periodo Calculator::Close, Calculator::Process deve specificare limiti di timestamp in modo che durante il periodo Calculator::Close rimanga disponibile almeno un timestamp (ad esempio Timestamp::Max). Ciò significa che una calcolatrice di solito non può fare affidamento su SetTimestampOffset(0), ma deve invece specificare limiti di timestamp esplicitamente utilizzando SetNextTimestampBounds().