WebRTC: מדריך להעברה של getStats() מדור קודם

Henrik Boström
Henrik Boström

ממשק ה-API הקודם של WebRTC ‏getStats() יוסר ב-Chrome 117, ולכן אפליקציות שמשתמשות בו יצטרכו לעבור ל-API הסטנדרטי. במאמר הזה נסביר איך להעביר את הקוד ואיך לפעול אם אתם זקוקים לזמן נוסף לביצוע השינוי.

בעבר היו שתי גרסאות מתחרות של ממשק ה-API getStats() של WebRTC. ממשק ה-API הקודם getStats()‎, שקדם לתהליך הסטנדרטיזציה ומקבל ארגומנט של קריאה חוזרת (callback), וממשק ה-API הסטנדרטי שנתמך באופן נרחב ומחזיר הבטחה (promise).

ה-API הסטנדרטי עשיר יותר בתכונות, ויש לו מדדים מוגדרים היטב שמתועדים באופן ציבורי במפרט W3C מזהי Statistics API של WebRTC. המפרט כולל תיאורים של כל המדדים שמפורטים במדריך הזה ועוד רבים.

החל מגרסה Chrome 117, ממשק ה-API הקודם getStats() יגרום להשלכת חריגה בערוץ הגרסה היציבה (השלכת החריגה תושק בהדרגה). כדי להקל על המעבר ל-API הרגיל, כדאי לפעול לפי המדריך הזה.

סוגי נתונים סטטיסטיים מדור קודם לעומת סוגי נתונים סטטיסטיים רגילים

הרשימה המלאה של סוגי הנתונים הסטנדרטיים מופיעה ב-enum‏ RTCStatsType במפרט. ההגדרה הזו כוללת את ההגדרה של מילון הנתונים הסטטיסטיים שמתארת את המדדים שנאספים לכל סוג.

לכל אובייקטי הנתונים הסטטיסטיים יש מאפיין id שמזהה באופן ייחודי את האובייקט הבסיסי במספר קריאות ל-getStats(). לכל אובייקט יהיה אותו מזהה בכל פעם שמפעילים את השיטה. האפשרות הזו שימושית לחישוב קצב השינוי של מדדים (דוגמה מופיעה בקטע הבא). המזהים יוצרים גם קשרי הפניה. לדוגמה, אובייקט הנתונים הסטטיסטיים outbound-rtp מפנה לאובייקט הנתונים הסטטיסטיים המשויך media-source דרך המאפיין outbound-rtp.mediaSourceId. אם מציירים את כל היחסים מסוג ...Id, מקבלים גרף.

ב-API הקודם יש את סוגי הנתונים הסטטיסטיים הבאים, שתואמים לסוגי הנתונים הסטנדרטיים הבאים:


סוג מדור קודם

סוג רגיל
ssrc
הערך הזה מייצג שידור RTP ומדדים לגבי MediaStreamTrack המשויך.


הסוגים הרגילים לכך הם inbound-rtp (לקבלת שידורי RTP ול-MediaStreamTrack המרוחק המשויך), outbound-rtp (לשליחת שידורי RTP) ו-media-source (למדדי MediaStreamTrack מקומיים שמשויכים לשידור RTP לשליחה). מדדי הסטרימינג של RTP מכילים גם מידע על המקודד או על המפענח שבהם נעשה שימוש בסטרימינג של ה-RTP.
VideoBwe
מדדים של אומדן רוחב הפס, קצב העברת הנתונים היעד, קצב העברת הנתונים של המקודד וקצב העברת הנתונים בפועל. סוגי המדדים האלה הם חלק ממדדי ה-RTP (outbound-rtp ו-inbound-rtp) וממדדי הצמדים של מועמדים ל-ICE (candidate-pair).
googComponent
היא מייצגת את התעבורה (ICE ו-DTLS). הגרסה הרגילה היא transport.
localcandidate and remotecandidate
מייצג מועמד ICE. הגרסה הרגילה היא local-candidate ו-remote-candidate.
googCandidatePair
מייצג זוג מועמדים ל-ICE, שהוא התאמה של מועמד מקומי ומועמד מרוחק. הגרסה הרגילה היא candidate-pair.
googCertificate
השדה מייצג אישור שמשמש את התעבורה ב-DTLS. הגרסה הרגילה היא certificate.
googLibjingleSession
הערך הזה מייצג את RTCPeerConnection. התוכן שלו לא ממופה לאף פריט בתקן, אבל לתקן יש סוג שמשויך ל-RTCPeerConnection: peer-connection.

חסר ב-API הקודם

סוגי הנתונים הסטטיסטיים הבאים נוספו ל-API הסטנדרטי, ואין להם סוג תואם מדור קודם:
  • codec: קודק שמשמש כרגע את סטרימינג ה-RTP, לצורך קידוד או לצורך פענוח. זוהי קבוצת משנה של הקודקים שהוסכם עליהם ב-SDP.
  • remote-inbound-rtp: מקור הנתונים של זרם ה-RTP הנכנס בנקודת קצה מרוחקת, שתואם לזרם ה-RTP היוצא שנקודת הקצה הזו שולחת (outbound-rtp). המדד הזה נמדד בנקודת הקצה המרוחקת ומדווח בדוח של מקלט RTCP‏ (RR) או בדוח מורחב של RTCP‏ (XR).
  • remote-outbound-rtp: שידור RTP יוצא של נקודת קצה מרוחקת שתואם לשידור RTP נכנס שנקודת הקצה הזו מקבלת (inbound-rtp). הוא נמדד בנקודת הקצה המרוחקת ומדווח בדוח של שולח RTCP‏ (SR).
  • media-playout: מדדים לגבי הפעלת MediaStreamTrack מרוחק שמשויך לזרם RTP נכנס (inbound-rtp).
  • data-channel: מייצג RTCDataChannel.

מיפוי מדדים מדור קודם למדדים רגילים

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

// Periodically (e.g. every second or every 10 seconds)...
const currReport = await pc.getStats();
// Calculate bitrate since the last getStats() call.
// Handling of undefined is omitted for clarity.
const currOutboundRtp = currReport.values().find(s => s.type == 'outbound-rtp');
const prevOutboundRtp = prevReport.get(currOutboundRtp.id);
const deltaBits = (currOutboundRtp.bytesSent - prevOutboundRtp.bytesSent) * 8;
const deltaSeconds = (currOutboundRtp.timestamp - prevOutboundRtp.timestamp) / 1000;
logBitrateMeasurement(deltaBits / deltaSeconds);
// Remember the report for next time.
prevReport = currReport;

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

מדד מדור קודם
googCertificate
תכתובת רגילה
certificate
.googFingerprint .fingerprint
.googFingerprintAlgorithm .fingerprintAlgorithm
.googDerBase64 .base64Certificate
מדד מדור קודם
googComponent
תכתובת רגילה
transport
.localCertificateId .localCertificateId
.remoteCertificateId .remoteCertificateId
.selectedCandidatePairId .selectedCandidatePairId
.dtlsCipher .dtlsCipher
.srtpCipher .srtpCipher
מדד מדור קודם
localcandidate
תכתובת רגילה
local-candidate או candidate-pair
.stunKeepaliveRequestsSent candidate-pair.requestsSent (חיפוש 'הפוך' של candidate-pair דרך candidate-pair.localCandidateId)
.portNumber local-candidate.port
.networkType local-candidate.networkType
.ipAddress local-candidate.address
.stunKeepaliveResponsesReceived candidate-pair.responsesReceived
.stunKeepaliveRttTotal candidate-pair.totalRoundTripTime
.transport local-candidate.protocol
.candidateType local-candidate.candidateType
.priority local-candidate.priority
מדד מדור קודם
remotecandidate
תכתובת רגילה
remote-candidate
כנ"ל localcandidate שלמעלה. כנ"ל local-candidate שלמעלה.
מדד מדור קודם
googCandidatePair
תכתובת רגילה
candidate-pair
.responsesSent candidate-pair.responsesSent
.requestsReceived candidate-pair.requestsReceived
.googRemoteCandidateType remote-candidate.candidateType
(lookup remote-candidate via
candidate-pair.remoteCandidateId)
.googReadable googReadable הוא ערך בוליאני שמשקף אם העלינו לאחרונה את הערך של candidate-pair.requestsReceived או candidate-pair.responsesReceived או לא.
.googLocalAddress local-candidate.address
(lookup local-candidate via
candidate-pair.localCandidateId)
.consentRequestsSent candidate-pair.consentRequestsSent
.googTransportType זהה ל-local-candidate.protocol ול-remote-candidate.protocol.
.googChannelId candidate-pair.transportId
.googLocalCandidateType local-candidate.candidateType
.googWritable googWritable הוא ערך בוליאני שמשקף אם העלינו לאחרונה את הערך של candidate-pair.responsesReceived או לא.
.googRemoteAddress remote-candidate.address
.googRtt candidate-pair.currentRoundTripTime
.googActiveConnection החיבור הפעיל מתייחס לזוג המועמדים שנבחר כרגע על ידי התעבורה, למשל כאשר candidate-pair.id == transport.selectedCandidatePairId
.packetsDiscardedOnSend candidate-pair.packetsDiscardedOnSend
.bytesReceived candidate-pair.bytesReceived
.responsesReceived candidate-pair.responsesReceived
.remoteCandidateId candidate-pair.remoteCandidateId
.localCandidateId candidate-pair.localCandidateId
.bytesSent candidate-pair.bytesSent
.packetsSent candidate-pair.packetsSent
.bytesReceived candidate-pair.bytesReceived
.bytesReceived candidate-pair.bytesReceived
מדד מדור קודם
ssrc
תכתובות רגילות
inbound-rtp, outbound-rtp, media-source
.audioInputLevel media-source.audioLevel. המדד הקודם נמצא בטווח [0..32768], אבל המדד הרגיל נמצא בטווח [0..1].
.audioOutputLevel
inbound-rtp.audioLevel. המדד הקודם נמצא בטווח [0..32768], אבל המדד הרגיל נמצא בטווח [0..1].
.packetsLost inbound-rtp.packetsLost
.googTrackId media-source.trackIdentifier עבור MediaStreamTrack מקומיים ו-inbound-rtp.trackIdentifier עבור MediaStreamTrack מרוחקים
.googRtt remote-inbound-rtp.roundTripTime (מידע נוסף זמין במאמר outbound-rtp.remoteId)
.googEchoCancellationReturnLossEnhancement inbound-rtp.echoReturnLossEnhancement
.googCodecName שם הקודק הוא סוג המשנה של סוג ה-MIME 'type/subtype', codec.mimeType (ראו inbound-rtp.codecId ו-outbound-rtp.codecId)
.transportId inbound-rtp.transportId וגם outbound-rtp.transportId
.mediaType inbound-rtp.kind וגם outbound-rtp.kind או media-source.kind
.googEchoCancellationReturnLoss inbound-rtp.echoReturnLoss
.totalAudioEnergy inbound-rtp.totalAudioEnergy וגם media-source.totalAudioEnergy
ssrc.totalSamplesDuration inbound-rtp.totalSamplesDuration וגם media-source.totalSamplesDuration
.ssrc inbound-rtp.ssrc וגם outbound-rtp.ssrc
.googJitterReceived inbound-rtp.jitter
.packetsSent outbound-rtp.packetsSent
.bytesSent outbound-rtp.bytesSent
.googContentType inbound-rtp.contentType וגם outbound-rtp.contentType
.googFrameWidthInput media-source.width
.googFrameHeightInput media-source.height
.googFrameRateInput media-source.framesPerSecond
.googFrameWidthSent outbound-rtp.frameWidth
.googFrameHeightSent outbound-rtp.frameHeight
.googFrameRateSent
קצב הפריימים לשליחה הוא קצב השינוי של outbound-rtp.framesSent, אבל הוא מיושם בפועל כ-outbound-rtp.framesPerSecond, שהוא קידוד של קצב הפריימים.
.googFrameWidthReceived inbound-rtp.frameWidth
.googFrameHeightReceived inbound-rtp.frameHeight
.googFrameRateDecoded
שיעור השינוי של inbound-rtp.framesDecoded
.googFrameRateOutput
שיעור השינוי של inbound-rtp.framesDecoded - inbound-rtp.framesDropped
.hugeFramesSent outbound-rtp.hugeFramesSent
.qpSum

inbound-rtp.qpSum וגם outbound-rtp.qpSum

.framesEncoded outbound-rtp.framesEncoded
.googAvgEncodeMs

outbound-rtp.totalEncodeTime / outbound-rtp.framesEncoded

.codecImplementationName

inbound-rtp.decoderImplementation וגם outbound-rtp.encoderImplementation

.googCpuLimitedResolution
True אם outbound-rtp.qualityLimitationReason == "cpu"
.googBandwidthLimitedResolution
True אם outbound-rtp.qualityLimitationReason == "bandwidth"
.googAdaptationChanges
המדד הקודם סופר את מספר הפעמים שרזולוציה או קצב פריימים השתנו מסיבות שקשורות ל-qualityLimitationReason. אפשר להסיק זאת ממדדים אחרים (למשל, רזולוציית השליחה או קצב הפריימים שונים מרזולוציית המקור או מקצב הפריימים שלו), אבל משך הזמן שבו הוגבלה הפעילות שלנו, outbound-rtp.qualityLimitationDurations, עשוי להיות שימושי יותר מאשר התדירות שבה בוצע שינוי בהגדרות של רזולוציית השליחה או קצב הפריימים.
.googNacksReceived inbound-rtp.nackCount
.googNacksSent inbound-rtp.nackCount
.googPlisReceived inbound-rtp.pliCount
.googPlisSent inbound-rtp.pliCount
.googFirsReceived inbound-rtp.firCount
.googFirsSent inbound-rtp.firCount
.googSecondaryDecodedRate
היחס האחרון של חבילות שמכילות תיקון שגיאות: inbound-rtp.fecPacketsReceivedinbound-rtp.fecPacketsDiscarded
.packetsReceived inbound-rtp.packetsReceived
.googJitterBufferMs inbound-rtp.jitterBufferDelay / inbound-rtp.jitterBufferEmittedCount
.googTargetDelayMs (סרטון) inbound-rtp.jitterBufferTargetDelay / inbound-rtp.jitterBufferEmittedCount
.googPreferredJitterBufferMs (אודיו) inbound-rtp.jitterBufferTargetDelay / inbound-rtp.jitterBufferEmittedCount
.googExpandRate
היחס האחרון של דגימות מוסתרות: inbound-rtp.concealedSamples / inbound-rtp.totalSamplesReceived
.googSpeechExpandRate היחס האחרון של דגימות מוסתרות בזמן שהשידור לא היה שקט: (inbound-rtp.concealedSamples - inbound-rtp.silentConcealedSamples) / inbound-rtp.concealedSamples
.googAccelerateRate היחס האחרון של דגימות שהוחמצו כדי להאיץ את מהירות ההפעלה: inbound-rtp.removedSamplesForAcceleration / inbound-rtp.totalSamplesReceived
.googPreemptiveExpandRate
היחס האחרון של דגימות שסונזרו כדי להאט את מהירות ההפעלה: inbound-rtp.insertedSamplesForDeceleration / inbound-rtp.totalSamplesReceived
.googSecondaryDiscardedRate inbound-rtp.fecPacketsDiscarded
.bytesReceived inbound-rtp.bytesReceived
s.googCurrentDelayMs inbound-rtp.jitterBufferDelay + media-playout.totalPlayoutDelay
.googDecodeMs inbound-rtp.totalDecodeTime / inbound-rtp.framesDecoded
.googTimingFrameInfo
המדד היחיד שנותר ב-goog-metric. inbound-rtp.googTimingFrameInfo
.framesDecoded inbound-rtp.framesDecoded
מדד מדור קודם
VideoBwe
התכתבות רגילה
outbound-rtp ו-candidate-pair
.googTargetEncBitrate
outbound-rtp.targetBitrate כערך מיידי או outbound-rtp.totalEncodedBytesTarget / outbound-rtp.framesEncoded כערך ממוצע
.googActualEncBitrate הבייטים שהמקודד יוצר הם הבייטים של עומס העבודה, לא כולל העברות חוזרות: שיעור השינוי של outbound-rtp.bytesSent - outbound-rtp.retransmittedBytesSent
.googBucketDelay outbound-rtp.totalPacketSendDelay / outbound-rtp.packetsSent
.googTransmitBitrate שיעור השינוי של outbound-rtp.headerBytesSent + outbound-rtp.bytesSent לקצב העברת הנתונים לכל שידור RTP, candidate-pair.bytesSent לקצב העברת הנתונים לכל מועמדת ICE או transport.bytesSent לקצב העברת הנתונים לכל אמצעי העברה
.googRetransmitBitrate טווח השינוי של outbound-rtp.retransmittedBytesSent
.googAvailableSendBandwidth candidate-pair.availableOutgoingBitrate
.googAvailableReceiveBandwidth candidate-pair.availableIncomingBitrate

ממשק ה-API הרגיל מודע לשידור סימולטני

אם אתם משתמשים בשידור סימולטני, יכול להיות שמתם לב ש-API הקודם מדווח רק על SSRC יחיד, גם אם אתם משתמשים בשידור סימולטני כדי לשלוח (לדוגמה) שלושה מקורות נתונים של RTP דרך שלושה SSRC נפרדים.

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

לעומת זאת, שידורי SVC או שידורי RTP עם כמה שכבות מרחביות שהוגדרו דרך ה-API של scalabilityMode עדיין מופיעים כ-outbound-rtp יחיד, כי הם נשלחים דרך SSRC יחיד.

אם אתם זקוקים לזמן נוסף להעברה

כשממשק ה-API הקודם יוסר ב-Chrome 117, השימוש בו יגרום ליצירת חריגה. אם אין לכם אפשרות להעביר את הקוד בזמן, תקופת הניסיון למקור של ממשק ה-API getStats()‎ שמבוסס על קריאה חוזרת (callback) של RTCPeerConnection מעניקה לאתרים רשומים יותר זמן להעברה. באמצעות אסימון לגרסת המקור לניסיון, אפשר להמשיך להשתמש ב-API הקודם getStats()‎ עד Chrome 121.