实时视频俱乐部

实时时间戳

MediaPipe 计算器图表通常用于处理交互式应用的视频或音频帧流。MediaPipe 框架只要求为连续数据包分配单调递增的时间戳。按照惯例,实时计算器和图表将每帧的记录时间或呈现时间用作其时间戳,每个时间戳表示自 Jan/1/1970:00:00:00 起的微秒数。这允许以全局一致的序列处理来自各种来源的数据包。

实时安排

通常,每个计算器只要给定时间戳的所有输入数据包都可用,就会立即运行。通常,当计算器处理完上一帧,并且生成其输入的每个计算器都已处理完当前帧时,就会发生这种情况。只要满足这些条件,MediaPipe 调度器就会立即调用每个计算器。如需了解详情,请参阅同步

时间戳边界

如果计算器没有针对给定的时间戳生成任何输出数据包,则可以改为输出“时间戳边界”,表明不会为该时间戳生成任何数据包。即使该时间戳的某些数据流未到达数据包,下游计算器也必须用以下指示来表示该时间戳。这对于交互式应用中的实时图表尤为重要,因为必须尽快开始处理每个计算器。

请参考以下图表:

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

假设:在时间戳 T 时,节点 A 在其输出流 alpha 中未发送数据包。节点 Bfoo 的时间戳为 T 处获取数据包,并正在等待 alpha 中的数据包的时间戳为 T。如果 A 未向 B 发送 alpha 的时间戳边界更新,B 会继续等待数据包到达 alpha。同时,foo 的数据包队列会在 TT+1 等位置累积数据包。

为了将数据包输出到数据流中,计算器会使用 API 函数 CalculatorContext::OutputsOutputStream::Add。如需改为输出在数据流上绑定的时间戳,计算器可以使用 API 函数 CalculatorContext::OutputsCalculatorContext::SetNextTimestampBound。指定边界是指定输出流上下一个数据包所允许的最低时间戳。如果没有输出数据包,计算器通常会执行如下操作:

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

函数 Timestamp::NextAllowedInStream 会返回连续时间戳。 例如 Timestamp(1).NextAllowedInStream() == Timestamp(2)

传播时间戳边界

实时图表中使用的计算器需要根据输入时间戳边界定义输出时间戳边界,以便及时安排下游计算器。一种常见的模式是,计算器输出时间戳与其输入数据包相同的数据包。在这种情况下,只要在每次调用 Calculator::Process 时都输出数据包,就足以定义输出时间戳边界。

不过,计算器无需遵循这种常用的输出时间戳模式,只需选择单调递增的输出时间戳即可。因此,某些计算器必须明确计算时间戳边界。MediaPipe 提供了多种工具,用于为每个计算器计算适当的时间戳边界。

1. SetNextTimestampBound() 可用于为输出流指定时间戳边界 t + 1

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

或者,您也可以生成一个具有时间戳 t 的空数据包,以指定时间戳边界 t + 1

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

输入流的时间戳边界由输入流上的数据包或空数据包指示。

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

2. 可以指定 TimestampOffset(),以将输入流中的时间戳边界自动复制到输出流。

cc->SetTimestampOffset(0);

此设置的优点是可以自动传播时间戳边界,即使只有时间戳边界到达并且未调用 Calculator::Process。

3. 可以指定 ProcessTimestampBounds(),以便为每个新的“已确定的时间戳”调用 Calculator::Process,其中“已解决的时间戳”是当前时间戳边界以下的新最高时间戳。如果没有 ProcessTimestampBounds(),系统只会在收到一个或多个数据包时调用 Calculator::Process

cc->SetProcessTimestampBounds(true);

此设置允许计算器自身执行时间戳边界计算和传播,即使仅更新输入时间戳也是如此。它可用于复制 TimestampOffset() 的效果,但也可用于计算考虑其他因素的时间戳边界。

例如,为了复制 SetTimestampOffset(0),计算器可以执行以下操作:

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

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

计算器的时间安排::打开和计算器::关闭

生成所有必需的输入旁数据包后,系统会调用 Calculator::Open。输入旁包可由封装应用或图中的“旁包计算器”提供。您可以使用 API 的 CalculatorGraph::InitializeCalculatorGraph::StartRun 从图表外部指定旁数据包。图中的计算器可以使用 CalculatorGraphConfig::OutputSidePacketsOutputSidePacket::Set 来指定辅助数据包。

计算器:当所有输入流都因关闭或达到时间戳边界 Timestamp::Done 而变为 Done 时,调用关闭。

注意:如果图在某些流变为 Done 之前完成所有待处理的计算器执行并变为 Done,则 MediaPipe 将调用对 Calculator::Close 的其余调用,以便每个计算器都可以生成其最终输出。

使用 TimestampOffset 会对 Calculator::Close 有一些影响。指定 SetTimestampOffset(0) 的计算器会在设计上指示其所有输出流在其所有输入流都已达到 Timestamp::Done 时均已达到 Timestamp::Done,因此不会再有输出。这样可以防止此类计算器在 Calculator::Close 期间发出任何数据包。如果计算器需要在 Calculator::Close 期间生成摘要数据包,Calculator::Process 必须指定时间戳边界,以便在 Calculator::Close 期间至少有一个时间戳(例如 Timestamp::Max)可用。这意味着,此类计算器通常不能依赖于 SetTimestampOffset(0),而必须使用 SetNextTimestampBounds() 明确指定时间戳边界。