統計情報¶
Sora iOS SDK では、Sora を利用した WebRTC セッション中に iOS クライアントが送信している音声・映像・データパケットの状態を確認できます。 ビットレートや RTT、パケットロスといった統計情報を、W3C WebRTC Stats 仕様の getStats に準拠した形式で取得します(https://www.w3.org/TR/webrtc-stats/)。
統計情報の取得¶
Sora iOS SDK では、Sora からの送信要求に応じて統計情報を定期的に自動で送信していますが、
MediaChannel.getStats() メソッドを利用することでクライアントの任意のタイミングで統計情報を取得することができます。
取得した統計情報は通信状況の確認・可視化等に利用することができます。
統計情報を取得して統計要素を抽出する実装サンプル¶
// 画面中のボタンをタップすることにより統計情報を取得するサンプル実装です
// 統計情報取得に必要な処理以外は省略しています
import Sora
import UIKit
class ViewController: UIViewController {
// 接続完了後に設定される MediaChannelです
// 統計情報の取得には Sora へ接続済みである必要があります
var mediaChannel: MediaChannel?
// 統計情報取得アクションです。ボタン等にアクションとして紐付けます
@IBAction func getStats(_ sender: AnyObject) {
// 接続していない状態で実行した場合は処理を抜けます
guard let mediaChannel else {
return
}
// getStats 実行により統計情報を取得します。
// getStats は libwebrtc の統計情報取得メソッド(クロージャ)に、
// 接続状況のチェック等を追加したラッパー関数のためクロージャ形式となっています
mediaChannel.getStats { [weak self] result in
// ViewController が解放済みの場合は処理を抜けます
guard let self else { return }
// getStats 結果の評価
switch result {
case .success(let stats):
// stats は entries プロパティで StatisticsEntry のリストを持っています
// ここでは例として、entries の中から type=outbound-rtp の要素を抽出します
let outboundEntries = stats.entries.filter { $0.type == "outbound-rtp" }
// StatisticsEntry は id, type(統計種別), timestamp(測定時刻[μs]), values を要素として持ちます
// values は type ごとに異なる統計要素です
for entry in outboundEntries {
print("id: \(entry.id), timestamp: \(entry.timestamp), values: \(entry.values)")
}
case .failure(let error):
// MediaChannel が切断済み、または WebRTC 側で統計取得に失敗した場合
print("Stats Error:", error)
}
}
}
}
統計情報の構造¶
統計情報は RTCStatsReport 型で取得されます。 [{ "type": "...", ... }] のように type ごとのレコードが並ぶイメージで、
ID をキーにしたレコードの集合となっています。
Tip
詳細な RTCStatsReport の構造については https://www.w3.org/TR/webrtc-stats/ をご確認ください
統計情報を取得して内容を表示する
統計情報サンプル¶
実際に取得される統計情報のサンプルを掲載します。
[
{
"ssrc" : 1853431412,
"id" : "RIV1853431412",
"localId" : "OTaudio_OhfC2B1V1853431412",
"codecId" : "COTaudio_OhfC2B1_120_profile-id=0;x-google-start-bitrate=500",
"transportId" : "Taudio_OhfC2B1",
"kind" : "video",
"jitter" : 0.0078770000000000003,
"totalRoundTripTime" : 0.034118999999999997,
"roundTripTime" : 0.034118999999999997,
"fractionLost" : 0,
"type" : "remote-inbound-rtp",
"roundTripTimeMeasurements" : 1,
"timestamp" : 1762916916047921,
"packetsLost" : 0
},
{
"id" : "P",
"type" : "peer-connection",
"dataChannelsClosed" : 0,
"dataChannelsOpened" : 0,
"timestamp" : 1762916916592191
},
{
"type" : "media-source",
"totalAudioEnergy" : 5.311656835890106e-06,
"timestamp" : 1762916916592191,
"kind" : "audio",
"audioLevel" : 0.00048829615161595508,
"trackIdentifier" : "mainAudio",
"totalSamplesDuration" : 2.1699999999999977,
"id" : "SA3"
},
{
"payloadType" : 109,
"clockRate" : 48000,
"sdpFmtpLine" : "maxplaybackrate=48000;minptime=10;sprop-stereo=1;stereo=1;usedtx=0;useinbandfec=1",
"id" : "COTaudio_OhfC2B1_109_maxplaybackrate=48000;minptime=10;sprop-stereo=1;stereo=1;usedtx=0;useinbandfec=1",
"type" : "codec",
"timestamp" : 1762916916592191,
"transportId" : "Taudio_OhfC2B1",
"mimeType" : "audio\/opus",
"channels" : 2
},
{
"mediaSourceId" : "SA3",
"id" : "OTaudio_OhfC2B1A3036164645",
"type" : "outbound-rtp",
"kind" : "audio",
"active" : true,
"retransmittedPacketsSent" : 0,
"targetBitrate" : 64000,
"retransmittedBytesSent" : 0,
"transportId" : "Taudio_OhfC2B1",
"packetsSent" : 108,
"mid" : "audio_OhfC2B",
"totalPacketSendDelay" : 0,
"codecId" : "COTaudio_OhfC2B1_109_maxplaybackrate=48000;minptime=10;sprop-stereo=1;stereo=1;usedtx=0;useinbandfec=1",
"timestamp" : 1762916916592191,
"headerBytesSent" : 2592,
"nackCount" : 0,
"ssrc" : 3036164645,
"bytesSent" : 17388
},
{
"...": "省略"
}
]