צ'אטים בזמן אמת

חותמות זמן בזמן אמת

גרפים של מחשבון MediaPipe משמשים בדרך כלל לעיבוד זרמים של מסגרות וידאו או אודיו לאפליקציות אינטראקטיביות. כדי להשתמש ב-framework של MediaPipe, צריך להקצות רק לחבילות רצופות חותמות זמן שגדלות באופן מונוטוני. באופן המקובל, במחשבונים ובגרפים בזמן אמת נעשה שימוש בזמן ההקלטה או בזמן ההצגה של כל פריים כחותמת הזמן, כאשר כל חותמת זמן מציינת את מיליוניות השנייה מאז Jan/1/1970:00:00:00. כך ניתן לעבד מנות ממקורות שונים ברצף גלובלי ועקבי.

תזמון בזמן אמת

בדרך כלל, כל מחשבון פועל ברגע שכל חבילות הקלט שלו לחותמת זמן מסוימת הופכות לזמינות. בדרך כלל, זה קורה כשהמחשבון מסיים לעבד את המסגרת הקודמת, וכל אחד מהמחשבון שיוצרים את הקלט שלו מסיים את העיבוד של המסגרת הנוכחית. מתזמן ה-MediaPipe מפעיל כל מחשבון ברגע שהתנאים האלה מתקיימים. לפרטים נוספים, ראו סנכרון.

טווח הגבולות של חותמת הזמן

כשמחשבון לא מפיק חבילות פלט לחותמת זמן נתונה, הוא יכול להפיק פלט של 'טווח זמן לחותמת הזמן' שמציין שלא תיווצר חבילה לחותמת הזמן הזו. החיווי הזה נחוץ כדי לאפשר למַחְשבים ב-downstream לרוץ בחותמת הזמן הזו, למרות שלא התקבלה חבילה עבור שידורים מסוימים עבור חותמת הזמן הזו. הדבר חשוב במיוחד לתרשימים בזמן אמת באפליקציות אינטראקטיביות, כאשר חשוב מאוד שהעיבוד של כל מחשבון יתחיל בהקדם האפשרי.

מומלץ להשתמש בתרשים כמו זה:

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. הצומת B מקבל חבילה ב-foo בחותמת הזמן T והוא ממתין לחבילה ב-alpha בחותמת הזמן T. אם A לא ישלח ל-B עדכון של מועד חותמת הזמן עבור alpha, B ימשיך להמתין להגעת החבילה ב-alpha. בינתיים, תור החבילות של foo יצבור חבילות ב-T, ב-T+1 וכן הלאה.

כדי ליצור פלט של חבילה בשידור, המחשבון משתמש בפונקציות ה-API CalculatorContext::Outputs ו-OutputStream::Add. כדי להפיק פלט של חותמת זמן עם חותמת זמן של מקור נתונים, מחשבון יכול להשתמש בפונקציות ה-API CalculatorContext::Outputs ו-CalculatorContext::SetNextTimestampBound. הגבול שצוין הוא חותמת הזמן הנמוכה ביותר שמותרת לחבילה הבאה בזרם הפלט שצוין. בדרך כלל, מחשבון לא מקבל פלט של חבילה, למשל:

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

הפונקציה Timestamp::NextAllowedInStream מחזירה את חותמת הזמן של הפעולה העוקבת. לדוגמה: Timestamp(1).NextAllowedInStream() == Timestamp(2).

הפצת הגבולות של חותמת הזמן

מחשבונים שישמשו בתרשימים בזמן אמת צריכים להגדיר את גבולות חותמת הזמן של הפלט על סמך גבולות חותמת הזמן של הקלט כדי לאפשר תזמון מהיר של מחשבונים ב-downstream. דפוס נפוץ הוא מחשבונים שמוציאים חבילות עם אותן חותמות זמן כמו של חבילות הקלט. במקרה הזה, מספיק להפיק פלט של חבילה בכל קריאה ל-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);

היתרון של ההגדרה הזו הוא הפצה אוטומטית של גבולות חותמת הזמן, גם אם רק גבולות חותמת הזמן מגיעים ומחשבון: 'התהליך' לא מופעל.

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 מופעלת אחרי שכל החבילות הצדדיות לקלט נוצרו. ניתן לספק חבילות צדדיות לקלט באמצעות האפליקציה המצורפת או באמצעות "מחשבון צד שלישי" בתוך התרשים. אפשר לציין מנות צד מחוץ לתרשים באמצעות CalculatorGraph::Initialize ו-CalculatorGraph::StartRun של ה-API. אפשר לציין חבילות צד באמצעות מחשבונים בתוך התרשים באמצעות CalculatorGraphConfig::OutputSidePackets ו-OutputSidePacket::Set.

מחשבון::הסגירה מופעלת כשכל מקורות הקלט הופכים ל-Done על ידי סגירתם או הגעה לחותמת הזמן של Timestamp::Done.

הערה: במקרה שהתרשים מסיים את כל הביצוע של המחשבון הממתין והופך ל-Done, לפני שחלק מהשידורים הופכים ל-Done, MediaPipe יפעיל את הקריאות שנותרו ל-Calculator::Close, כדי שכל מחשבון יוכל להפיק את הפלט הסופי שלו.

לשימוש בפונקציה TimestampOffset יש השלכות מסוימות על Calculator::Close. מחשבון שמציין את הערך SetTimestampOffset(0) יאותת לכך שכל מקורות הפלט הגיעו ל-Timestamp::Done כשכל מקורות הקלט הגיעו ל-Timestamp::Done, ולכן אי אפשר יהיה לקבל פלטים נוספים. כך מחשבון כזה לא פולט חבילות במהלך Calculator::Close. אם מחשבון צריך ליצור חבילת סיכום במהלך Calculator::Close, צריך לציין גבולות לחותמת הזמן ב-Calculator::Process, כך שלפחות חותמת זמן אחת (למשל Timestamp::Max) תישאר זמינה במהלך Calculator::Close. כלומר, בדרך כלל מחשבון כזה לא יכול להסתמך על SetTimestampOffset(0), ובמקום זאת צריך לציין את גבולות חותמת הזמן באופן מפורש באמצעות SetNextTimestampBounds().