Luồng thời gian thực

Dấu thời gian theo thời gian thực

Biểu đồ tính toán MediaPipe thường dùng để xử lý luồng khung hình video hoặc âm thanh cho các ứng dụng tương tác. Khung MediaPipe chỉ yêu cầu các gói liên tiếp được chỉ định dấu thời gian tăng dần đơn điệu. Theo quy ước, trình tính toán và biểu đồ theo thời gian thực sử dụng thời gian ghi hoặc thời gian trình bày của từng khung làm dấu thời gian, với mỗi dấu thời gian chỉ ra micrô giây kể từ Jan/1/1970:00:00:00. Điều này cho phép các gói từ nhiều nguồn được xử lý theo trình tự nhất quán trên toàn cầu.

Lên lịch theo thời gian thực

Thông thường, mỗi Máy tính sẽ chạy ngay khi có sẵn tất cả các gói đầu vào cho một dấu thời gian nhất định. Thông thường, điều này xảy ra khi máy tính đã xử lý xong khung trước đó và mỗi máy tính tạo ra dữ liệu đầu vào đều đã xử lý xong khung hiện tại. Trình lập lịch MediaPipe gọi từng công cụ tính ngay khi đáp ứng các điều kiện này. Xem phần Đồng bộ hoá để biết thêm thông tin chi tiết.

Giới hạn dấu thời gian

Khi không tạo ra gói đầu ra nào cho một dấu thời gian nhất định, máy tính có thể đưa ra "giới hạn dấu thời gian" cho biết sẽ không có gói nào được tạo cho dấu thời gian đó. Chỉ báo này là cần thiết để cho phép các công cụ tính hạ nguồn chạy tại dấu thời gian đó, mặc dù không có gói dữ liệu nào đến đối với một số luồng nhất định cho dấu thời gian đó. Điều này đặc biệt quan trọng đối với các biểu đồ theo thời gian thực trong các ứng dụng tương tác, trong đó mỗi máy tính bắt đầu xử lý càng sớm càng tốt.

Hãy xem xét một biểu đồ như sau:

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

Giả sử: tại dấu thời gian T, nút A không gửi gói trong luồng đầu ra alpha. Nút B nhận một gói trong foo tại dấu thời gian T và đang chờ một gói trong alpha tại dấu thời gian T. Nếu A không gửi cho B bản cập nhật ràng buộc về dấu thời gian của alpha, thì B sẽ tiếp tục chờ một gói đến trong alpha. Trong khi đó, hàng đợi gói foo sẽ tích luỹ các gói tại T, T+1, v.v.

Để xuất một gói trên luồng, máy tính sẽ sử dụng các hàm API CalculatorContext::OutputsOutputStream::Add. Để xuất ra một dấu thời gian được liên kết trên một luồng, máy tính có thể sử dụng các hàm API CalculatorContext::OutputsCalculatorContext::SetNextTimestampBound. Giới hạn đã chỉ định là dấu thời gian cho phép thấp nhất đối với gói tiếp theo trên luồng đầu ra đã chỉ định. Khi không có gói nào được xuất kết quả, máy tính thường sẽ làm những việc như sau:

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

Hàm Timestamp::NextAllowedInStream trả về dấu thời gian liên tiếp. Ví dụ: Timestamp(1).NextAllowedInStream() == Timestamp(2).

Giới hạn dấu thời gian lan truyền

Máy tính sẽ được sử dụng trong biểu đồ theo thời gian thực cần xác định giới hạn dấu thời gian đầu ra dựa trên giới hạn dấu thời gian đầu vào để giúp lên lịch nhanh chóng cho phép tính toán theo thời gian thực. Một mẫu phổ biến là để máy tính đưa ra các gói có cùng dấu thời gian với gói đầu vào. Trong trường hợp này, bạn chỉ cần xuất một gói vào mỗi lệnh gọi đến Calculator::Process là đủ để xác định các giới hạn dấu thời gian đầu ra.

Tuy nhiên, máy tính không bắt buộc phải tuân theo mẫu phổ biến này cho dấu thời gian đầu ra mà chỉ bắt buộc phải chọn dấu thời gian đầu ra tăng đơn điệu. Do đó, một số máy tính nhất định phải tính toán giới hạn dấu thời gian một cách rõ ràng. MediaPipe cung cấp một số công cụ để tính toán giới hạn dấu thời gian phù hợp cho từng công cụ tính.

1. Bạn có thể sử dụng SetNextTimestampBound() để chỉ định giới hạn dấu thời gian, t + 1, cho một luồng đầu ra.

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

Ngoài ra, bạn có thể tạo một gói trống có dấu thời gian t để chỉ định t + 1 liên kết với dấu thời gian.

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

Giới hạn về dấu thời gian của luồng đầu vào được biểu thị bằng gói hoặc gói trống trên luồng đầu vào.

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

2. Bạn có thể chỉ định TimestampOffset() để tự động sao chép dấu thời gian được liên kết từ luồng đầu vào sang luồng đầu ra.

cc->SetTimestampOffset(0);

Chế độ cài đặt này có lợi thế là tự động truyền tải giới hạn dấu thời gian, ngay cả khi chỉ giới hạn dấu thời gian xuất hiện và Calculator::Process không được gọi.

3. Bạn có thể chỉ định ProcessTimestampBounds() để gọi Calculator::Process cho mỗi "dấu thời gian đã giải quyết" mới, trong đó "dấu thời gian đã giải quyết" là dấu thời gian mới cao nhất nằm dưới giới hạn dấu thời gian hiện tại. Nếu không có ProcessTimestampBounds(), Calculator::Process chỉ được gọi khi có một hoặc nhiều gói đến.

cc->SetProcessTimestampBounds(true);

Chế độ cài đặt này cho phép máy tính thực hiện việc tính toán và truyền tải giới hạn dấu thời gian riêng, ngay cả khi chỉ cập nhật dấu thời gian đầu vào. Bạn có thể sử dụng đối số này để tái tạo hiệu ứng của TimestampOffset(), nhưng cũng có thể dùng để tính toán giới hạn dấu thời gian có tính đến các yếu tố bổ sung.

Ví dụ: để sao chép SetTimestampOffset(0), máy tính có thể làm như sau:

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

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

Lập lịch máy tính::Mở và Máy tính::Đóng

Calculator::Open được gọi khi tất cả các gói bên đầu vào bắt buộc đã được tạo. Các gói bên đầu vào có thể được cung cấp bằng ứng dụng đóng gói hoặc bằng "máy tính gói cạnh" bên trong biểu đồ. Bạn có thể chỉ định các gói bên từ bên ngoài biểu đồ bằng cách sử dụng CalculatorGraph::InitializeCalculatorGraph::StartRun của API. Các gói bên có thể được chỉ định bằng máy tính trong biểu đồ bằng cách sử dụng CalculatorGraphConfig::OutputSidePacketsOutputSidePacket::Set.

Máy tính::Đóng được gọi khi tất cả luồng đầu vào đã trở thành Done bằng cách đóng hoặc đạt đến dấu thời gian được liên kết với Timestamp::Done.

Lưu ý: Nếu biểu đồ hoàn tất tất cả quá trình thực thi tính toán đang chờ xử lý và trở thành Done, trước khi một số luồng trở thành Done, thì MediaPipe sẽ gọi các lệnh gọi còn lại đến Calculator::Close, để mọi máy tính đều có thể đưa ra kết quả cuối cùng.

Việc sử dụng TimestampOffset có một số ảnh hưởng đối với Calculator::Close. Một máy tính chỉ định SetTimestampOffset(0) sẽ theo thiết kế tín hiệu rằng tất cả các luồng đầu ra của máy tính đó đã đạt Timestamp::Done khi tất cả luồng đầu vào đã đạt đến Timestamp::Done và do đó không thể có thêm kết quả nào nữa. Điều này ngăn một máy tính như vậy phát đi bất kỳ gói nào trong Calculator::Close. Nếu máy tính cần tạo gói tóm tắt trong Calculator::Close, thì Calculator::Process phải chỉ định giới hạn về dấu thời gian để có ít nhất một dấu thời gian (chẳng hạn như Timestamp::Max) trong Calculator::Close. Điều này có nghĩa là một trình tính toán như vậy thường không thể dựa vào SetTimestampOffset(0) mà phải chỉ định các giới hạn dấu thời gian một cách rõ ràng bằng SetNextTimestampBounds().