Transmissões em tempo real

Carimbos de data/hora em tempo real

Os gráficos da calculadora do MediaPipe geralmente são usados para processar streams de frames de vídeo ou áudio em aplicativos interativos. O framework MediaPipe exige apenas que pacotes sucessivos sejam atribuídos a carimbos de data/hora monotonicamente crescentes. Por convenção, calculadoras e gráficos em tempo real usam o tempo de gravação ou o tempo de apresentação de cada frame como o respectivo carimbo de data/hora, com cada carimbo de data/hora indicando os microssegundos desde Jan/1/1970:00:00:00. Isso permite que pacotes de várias origens sejam processados em uma sequência globalmente consistente.

Agendamento em tempo real

Normalmente, cada calculadora é executada assim que todos os pacotes de entrada para um determinado carimbo de data/hora ficam disponíveis. Normalmente, isso acontece quando a calculadora termina de processar o frame anterior, e cada uma das calculadoras que produzem as entradas terminou de processar o frame atual. O programador do MediaPipe invoca cada calculadora assim que essas condições são atendidas. Consulte Sincronização para mais detalhes.

Limites de carimbo de data/hora

Quando uma calculadora não produz nenhum pacote de saída para um determinado carimbo de data/hora, ela pode gerar um "limite de carimbo de data/hora", indicando que nenhum pacote será produzido para esse carimbo de data/hora. Essa indicação é necessária para permitir que calculadoras downstream sejam executadas nesse carimbo de data/hora, mesmo que nenhum pacote tenha chegado para determinados fluxos para esse carimbo de data/hora. Isso é especialmente importante para gráficos em tempo real em aplicativos interativos, em que é crucial que cada calculadora comece o processamento o mais rápido possível.

Considere um gráfico como o seguinte:

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

Suponha que no carimbo de data/hora T, o nó A não envie um pacote no stream de saída alpha. O nó B recebe um pacote em foo no carimbo de data/hora T e está aguardando um pacote em alpha no carimbo de data/hora T. Se A não enviar B uma atualização de limite de carimbo de data/hora para alpha, B continuará aguardando a chegada de um pacote em alpha. Enquanto isso, a fila de pacotes de foo acumulará pacotes em T, T+1 e assim por diante.

Para gerar um pacote em um stream, uma calculadora usa as funções CalculatorContext::Outputs e OutputStream::Add da API. Para gerar um limite de carimbo de data/hora em um stream, uma calculadora pode usar as funções CalculatorContext::Outputs e CalculatorContext::SetNextTimestampBound da API. O limite especificado é o menor carimbo de data/hora permitido para o próximo pacote no fluxo de saída especificado. Quando nenhum pacote é enviado, uma calculadora geralmente faz algo como:

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

A função Timestamp::NextAllowedInStream retorna o carimbo de data/hora sucessivo. Por exemplo, Timestamp(1).NextAllowedInStream() == Timestamp(2).

Propagação de limites de carimbo de data/hora

As calculadoras que serão usadas em gráficos em tempo real precisam definir limites de carimbo de data/hora de saída com base nos limites do carimbo de data/hora de entrada para permitir que as calculadoras downstream sejam agendadas rapidamente. Um padrão comum é que as calculadoras gerem pacotes com os mesmos carimbos de data/hora que os pacotes de entrada. Nesse caso, apenas a saída de um pacote em cada chamada para Calculator::Process é suficiente para definir os limites do carimbo de data/hora de saída.

No entanto, as calculadoras não precisam seguir esse padrão comum para carimbos de data/hora de saída. Elas só precisam escolher carimbos de data/hora de saída que aumentam monotonicamente. Como resultado, algumas calculadoras precisam calcular os limites de carimbo de data/hora explicitamente. O MediaPipe fornece várias ferramentas para calcular o limite de carimbo de data/hora apropriado de cada calculadora.

1. SetNextTimestampBound() pode ser usado para especificar o limite de carimbo de data/hora, t + 1, para um stream de saída.

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

Como alternativa, um pacote vazio com o carimbo de data/hora t pode ser produzido para especificar o limite do carimbo de data/hora t + 1.

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

O limite de carimbo de data/hora de um stream de entrada é indicado pelo pacote ou pelo pacote vazio no stream de entrada.

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

2. TimestampOffset() pode ser especificado para copiar automaticamente o limite de carimbo de data/hora dos streams de entrada para os de saída.

cc->SetTimestampOffset(0);

Essa configuração tem a vantagem de propagar os limites de carimbo de data/hora automaticamente, mesmo quando apenas os limites de carimbo de data/hora chegam e Calculator::Process não é invocado.

3. ProcessTimestampBounds() pode ser especificado para invocar Calculator::Process para cada novo "carimbo de data/hora estabelecido", em que o "carimbo de data/hora definido" é o novo carimbo de data/hora mais alto abaixo dos limites do carimbo de data/hora atual. Sem ProcessTimestampBounds(), Calculator::Process é invocado apenas com um ou mais pacotes que chegam.

cc->SetProcessTimestampBounds(true);

Com essa configuração, uma calculadora pode realizar o próprio cálculo e propagação de limites de carimbo de data/hora, mesmo quando apenas os carimbos de data/hora de entrada são atualizados. Ele pode ser usado para replicar o efeito de TimestampOffset(), mas também para calcular um limite de carimbo de data/hora que considera outros fatores.

Por exemplo, para replicar SetTimestampOffset(0), uma calculadora pode fazer o seguinte:

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

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

Programação da Calculadora::Abrir e Calculadora::Fechar

Calculator::Open é invocado quando todos os pacotes secundários de entrada necessários foram produzidos. Os pacotes secundários de entrada podem ser fornecidos pelo aplicativo incluído ou por "calculadoras de pacotes secundários" dentro do gráfico. Os pacotes secundários podem ser especificados de fora do gráfico usando CalculatorGraph::Initialize e CalculatorGraph::StartRun da API. Os pacotes secundários podem ser especificados por calculadoras no gráfico usando CalculatorGraphConfig::OutputSidePackets e OutputSidePacket::Set.

Calculadora::Close é invocado quando todos os streams de entrada se tornam Done ao serem fechados ou ao atingir o limite de carimbo de data/hora Timestamp::Done.

Observação:se o gráfico concluir toda a execução de calculadora pendente e se tornar Done, antes de alguns streams se tornarem Done, o MediaPipe vai invocar as chamadas restantes para Calculator::Close, para que cada calculadora possa produzir as saídas finais.

O uso de TimestampOffset tem algumas implicações para Calculator::Close. Uma calculadora que especifica SetTimestampOffset(0) vai sinalizar, por design, que todos os fluxos de saída atingiram Timestamp::Done quando todos os streams de entrada tiverem atingido Timestamp::Done e, portanto, nenhuma outra saída será possível. Isso impede que essa calculadora emita pacotes durante Calculator::Close. Se uma calculadora precisar produzir um pacote de resumo durante Calculator::Close, Calculator::Process vai precisar especificar limites de carimbo de data/hora de modo que pelo menos um carimbo de data/hora (como Timestamp::Max) permaneça disponível durante Calculator::Close. Isso significa que essa calculadora normalmente não pode confiar em SetTimestampOffset(0) e precisa especificar limites de carimbo de data/hora explicitamente usando SetNextTimestampBounds().