Livestreams in Echtzeit

Echtzeit-Zeitstempel

MediaPipe-Rechnerdiagramme werden häufig verwendet, um Streams von Video- oder Audioframes für interaktive Anwendungen zu verarbeiten. Das MediaPipe-Framework erfordert nur, dass aufeinanderfolgende Pakete kontinuierlich steigende Zeitstempel zugewiesen werden. Konventionsgemäß verwenden Echtzeitrechner und -grafiken die Aufzeichnungszeit oder die Darstellungszeit jedes Frames als Zeitstempel, wobei jeder Zeitstempel die Mikrosekunden seit Jan/1/1970:00:00:00 angibt. Dadurch können Pakete aus verschiedenen Quellen in einer global konsistenten Reihenfolge verarbeitet werden.

Planung in Echtzeit

Normalerweise wird jeder Rechner ausgeführt, sobald alle Eingabepakete für einen bestimmten Zeitstempel verfügbar sind. Normalerweise geschieht dies, wenn der Rechner den vorherigen Frame fertig verarbeitet hat und alle Rechner, von denen die Eingaben stammen, die Verarbeitung des aktuellen Frames abgeschlossen haben. Der MediaPipe-Planer ruft jeden Rechner auf, sobald diese Bedingungen erfüllt sind. Weitere Informationen finden Sie unter Synchronisierung.

Zeitstempelgrenzen

Wenn ein Rechner keine Ausgabepakete für einen bestimmten Zeitstempel erzeugt, kann er stattdessen eine „Zeitstempelgrenze“ ausgeben, die anzeigt, dass für diesen Zeitstempel kein Paket erzeugt wird. Diese Angabe ist erforderlich, damit nachgelagerte Rechner zu diesem Zeitstempel ausgeführt werden können, auch wenn für diesen Zeitstempel kein Paket für bestimmte Streams eingegangen ist. Dies ist besonders wichtig bei Echtzeitgrafiken in interaktiven Anwendungen, bei denen jeder Rechner so schnell wie möglich mit der Verarbeitung beginnen muss.

Betrachten Sie ein Diagramm wie dieses:

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

Beispiel: Bei dem Zeitstempel T sendet Knoten A kein Paket in seinem Ausgabestream alpha. Knoten B ruft in foo ein Paket mit dem Zeitstempel T ab und wartet auf ein Paket in alpha mit dem Zeitstempel T. Wenn A kein zeitstempelgebundenes Update für alpha an B sendet, wartet B weiterhin auf das Eintreffen eines Pakets in alpha. Währenddessen sammelt die Paketwarteschlange von foo Pakete bei T, T+1 usw.

Zur Ausgabe eines Pakets in einen Stream verwendet ein Rechner die API-Funktionen CalculatorContext::Outputs und OutputStream::Add. Damit stattdessen eine Zeitstempelgrenze an einen Stream ausgegeben wird, kann ein Rechner die API-Funktionen CalculatorContext::Outputs und CalculatorContext::SetNextTimestampBound verwenden. Die angegebene Grenze ist der niedrigste zulässige Zeitstempel für das nächste Paket im angegebenen Ausgabestream. Wenn kein Paket ausgegeben wird, führt ein Rechner normalerweise etwa folgende Schritte aus:

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

Die Funktion Timestamp::NextAllowedInStream gibt den aufeinanderfolgenden Zeitstempel zurück. Beispiel: Timestamp(1).NextAllowedInStream() == Timestamp(2).

Zeitstempelgrenzen weitergeben

Rechner, die in Echtzeitgrafiken verwendet werden, müssen die Ausgabezeitstempelgrenzen basierend auf den Grenzen der Eingabezeitstempel definieren, damit nachgelagerte Rechner umgehend geplant werden können. Ein gängiges Muster besteht darin, dass Rechner Pakete mit demselben Zeitstempel wie die Eingabepakete ausgeben. In diesem Fall reicht es aus, einfach bei jedem Aufruf von Calculator::Process ein Paket auszugeben, um Zeitstempelgrenzen für die Ausgabe zu definieren.

Rechner müssen jedoch nicht diesem allgemeinen Muster für Ausgabezeitstempel folgen. Sie müssen nur monoton ansteigende Ausgabezeitstempel auswählen. Infolgedessen müssen bestimmte Taschenrechner Zeitstempelgrenzen explizit berechnen. MediaPipe bietet mehrere Tools für die Berechnung geeigneter Zeitstempelgrenzen für jeden Rechner.

1. Mit SetNextTimestampBound() kann die Zeitstempelgrenze t + 1 für einen Ausgabestream angegeben werden.

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

Alternativ kann ein leeres Paket mit dem Zeitstempel t erzeugt werden, um die Zeitstempelbindung t + 1 anzugeben.

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

Die Zeitstempelgrenze eines Eingabestreams wird durch das Paket oder das leere Paket im Eingabestream angegeben.

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

2. TimestampOffset() kann angegeben werden, um die Zeitstempelgrenze von Eingabestreams in Ausgabestreams automatisch zu kopieren.

cc->SetTimestampOffset(0);

Diese Einstellung hat den Vorteil, dass Zeitstempelgrenzen automatisch weitergegeben werden, auch wenn nur Zeitstempelgrenzen eingehen und Calculator::Process nicht aufgerufen wird.

3. ProcessTimestampBounds() kann angegeben werden, um Calculator::Process für jeden neuen „berechneten Zeitstempel“ aufzurufen, wobei der „besetzte Zeitstempel“ der neue höchste Zeitstempel unter den aktuellen Zeitstempelgrenzen ist. Ohne ProcessTimestampBounds() wird Calculator::Process nur mit einem oder mehreren eingehenden Paketen aufgerufen.

cc->SetProcessTimestampBounds(true);

Mit dieser Einstellung kann ein Taschenrechner seine eigene Berechnung und Weitergabe der Zeitstempelgrenzen durchführen, auch wenn nur Eingabezeitstempel aktualisiert werden. Damit lässt sich der Effekt von TimestampOffset() replizieren, aber auch eine Zeitstempelgrenze berechnen, bei der zusätzliche Faktoren berücksichtigt werden.

So könnte ein Rechner beispielsweise SetTimestampOffset(0) replizieren:

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

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

Planung des Rechners::Öffnen und Rechner::Schließen

Calculator::Open wird aufgerufen, wenn alle erforderlichen Eingabe-Nebenpakete erstellt wurden. Eingabe-Seitenpakete können von der einschließenden Anwendung oder von „Seitenpaketrechnern“ innerhalb der Grafik bereitgestellt werden. Seitenpakete können außerhalb des Diagramms mithilfe der CalculatorGraph::Initialize und CalculatorGraph::StartRun der API angegeben werden. Seitenpakete können in der Grafik mithilfe von CalculatorGraphConfig::OutputSidePackets und OutputSidePacket::Set von Rechnern angegeben werden.

Rechner: „Close“ wird aufgerufen, wenn alle Eingabestreams den Status Done haben, da sie geschlossen sind oder die Zeitstempelgrenze Timestamp::Done erreichen.

Hinweis:Wenn die Grafik die ausstehende Ausführung des Rechners beendet und zu Done wechselt, bevor einige Streams zu Done werden, ruft MediaPipe die verbleibenden Calculator::Close-Aufrufe auf, damit jeder Rechner seine endgültigen Ausgaben erzeugen kann.

Die Verwendung von TimestampOffset hat einige Auswirkungen auf Calculator::Close. Ein Rechner, der SetTimestampOffset(0) angibt, signalisiert, dass alle zugehörigen Ausgabestreams Timestamp::Done erreicht haben, wenn alle Eingabestreams Timestamp::Done erreicht haben. Daher sind keine weiteren Ausgaben möglich. Dadurch wird verhindert, dass ein solcher Rechner während Calculator::Close Pakete ausgibt. Wenn ein Rechner während Calculator::Close ein Zusammenfassungspaket erstellen muss, muss Calculator::Process Zeitstempelgrenzen so angeben, dass mindestens ein Zeitstempel (z. B. Timestamp::Max) während Calculator::Close verfügbar bleibt. Das bedeutet, dass ein solcher Rechner normalerweise nicht auf SetTimestampOffset(0) angewiesen ist und stattdessen Zeitstempelgrenzen explizit mit SetNextTimestampBounds() angeben muss.