統計情報¶
概要¶
Sora Flutter SDK では、Sora との WebRTC 接続中にクライアントが送受信している音声・映像・データの状況(ビットレート、RTT、パケットロスなど)を統計情報として取得できます。
用語¶
WebRTC 統計情報¶
WebRTC 統計情報とは W3C Identifiers for WebRTC's Statistics API に定義されている標準仕様です。
Sora Flutter SDK が返す統計情報の type とメンバー名も、この仕様に基づいています。
ただし、SDK は RTCStatsReport を Dart のオブジェクトへ変換しているのではなく、libwebrtc の RTCStatsReport::ToJson() が生成した JSON 文字列を FFI で返しています。
そのため、JSON のルート構造、出力される type 、各メンバーの有無は、libwebrtc のバージョン、接続状態、メディア構成によって変わります。
ブラウザーの RTCPeerConnection.getStats() が返す RTCStatsReport の Map 構造を、そのまま前提にしないでください。
統計情報取得方法¶
統計情報の取得には SoraConnection.getStats() を利用します。
以下のコード例では、次の import を共通で使用します。
import 'dart:async';
import 'dart:convert';
import 'package:sora_sdk/sora_sdk.dart';
Future<String?> getInitialStats() async {
final config = SoraConnectionConfig(
signalingUrls: <String>['wss://sora.example.com/signaling'],
channelId: 'example-channel',
role: SoraRole.sendrecv,
audio: true,
video: true,
);
final stream = await MediaDevices.getUserMedia(
GetUserMediaOptions(audio: true, video: true),
);
SoraConnection? conn;
try {
conn = await Sora.createConnection(config);
await conn.connect(stream);
// WebRTC 統計情報を JSON 文字列として取得する
return await conn.getStats();
} finally {
await conn?.dispose();
for (final track in stream.getTracks()) {
await track.dispose();
}
await stream.dispose();
}
}
getStats()¶
- 戻り値:
Future<String?>
接続中の PeerConnection から WebRTC 統計情報を取得し、ネイティブの Stats レポートを JSON 文字列に変換して返します。
通常、JSON のルートは Stats レポートを要素とする配列です。
レポートが存在しない場合は null が返り、レポートが存在しても統計情報が空の場合は [] が返ることがあります。
互換性のため、解析側では null と空文字列の両方を先に処理してください。
同時呼び出し¶
同時に取得したい処理が複数ある場合は、各呼び出しをそのまま待つことができます。
Future<void> getStatsConcurrently(SoraConnection conn) async {
final first = conn.getStats();
final second = conn.getStats();
final results = await Future.wait<String?>(<Future<String?>>[
first,
second,
]);
}
results には同じ統計取得処理の結果が 2 件入ります。定期取得では、前回の取得完了後に次の getStats() を呼ぶ構成にすると、取得結果を順番に処理できます。
状態ごとの戻り値と例外¶
PeerConnectionがまだ生成されていない場合や、切断後に取得した場合はnullが返ります- 取得中に
disconnect()が実行されると、進行中のgetStats()はStateErrorで完了します SoraConnection.dispose()後にgetStats()を呼ぶとStateErrorが送出されます- ネイティブ側のコールバックが到着しないまま孤立した
getStats()が上限 (10 件) に達すると、StateErrorが送出されます。コールバックが到着して孤立が解消されるまで新しいgetStats()は失敗し、解消されない場合は接続を再生成する必要があります - ネイティブ側の応答が 5 秒以内に届かない場合は
TimeoutExceptionが送出されます
戻り値の JSON 構造¶
戻り値の JSON 文字列を dart:convert の jsonDecode() でデコードすると、Stats レポートの List<dynamic> が得られます。
各要素は Map<String, dynamic> 相当の JSON オブジェクトです。
libwebrtc 内部ではレポートを id で検索できる Map として管理していますが、ToJson() の出力は Map の値を並べた配列です。
配列の順序は契約されていないため、id を配列の位置の代わりに使用してください。
各 Stats レポートは、レポートを識別する id 、レポートの種類を表す type 、取得時刻を表す timestamp を持ちます。
その他のメンバーはレポートの種類や実装によって異なり、該当しないメンバーは出力されません。
List<Map<String, dynamic>> decodeStats(String? raw) {
if (raw == null || raw.isEmpty) {
return const <Map<String, dynamic>>[];
}
final decoded = jsonDecode(raw);
if (decoded is! List) {
throw const FormatException('Stats JSON のルートが配列ではありません');
}
return <Map<String, dynamic>>[
for (final value in decoded)
if (value is Map) Map<String, dynamic>.from(value),
];
}
Future<void> printStats(SoraConnection conn) async {
final reports = decodeStats(await conn.getStats());
for (final report in reports) {
print('${report['id']}: ${report['type']}');
}
}
例えば、ネイティブの ToJson() は次のような配列を返します。
[
{
"type": "peer-connection",
"id": "P",
"timestamp": 1787550022589501,
"dataChannelsOpened": 0,
"dataChannelsClosed": 0
}
]
timestamp はネイティブの JSON シリアライザーが出力する値です。
ブラウザーの RTCStatsReport と同じ単位や数値型になるとは限らないため、時間差の計算では SDK と libwebrtc のバージョンを確認してください。
標準仕様に定義されている代表的な type:
type |
|
|---|---|
|
|
|
接続トランスポートの統計。 |
|
接続に使用する証明書の統計。 |
|
利用中のコーデック情報。 |
|
受信中の RTP 統計。 |
|
送信中の RTP 統計。 |
|
リモート側の受信 RTP 統計。 |
|
リモート側の送信 RTP 統計。 |
|
ICE candidate pair の接続状態。 |
|
ICE candidate の統計。 |
|
送信元のメディア統計。 |
|
音声の再生統計。 |
|
|
track は現行の W3C RTCStatsType に定義されておらず、現行 libwebrtc の Stats 型にも対応するレポート型はありません。
メディアトラックに関する情報は、inbound-rtp や media-source の trackIdentifier などのメンバーとして出力されます。
古い libwebrtc や別の実装が未知の type を返す可能性はあるため、track を含め、一覧にない type をエラーとして扱わないでください。
各 type が持つメンバーの詳細は W3C WebRTC Stats 仕様 をご確認ください。
標準仕様に定義された type でも、メンバーは接続状態やメディアの種類によって省略されます。
また、libwebrtc のバージョンによって、標準仕様にないメンバーが含まれることがあります。
使用例¶
映像送信統計を取得する例:
Future<void> printVideoStats(SoraConnection conn) async {
final raw = await conn.getStats();
if (raw == null || raw.isEmpty) {
return;
}
final reports = decodeStats(raw);
// type が outbound-rtp かつ kind が video のレポートを収集する
for (final report in reports) {
if (report['type'] != 'outbound-rtp') continue;
if (report['kind'] != 'video') continue;
print('codec: ${report['mimeType']}');
print('resolution: ${report['frameWidth']}x${report['frameHeight']}');
print('bytesSent: ${report['bytesSent']}');
print('packetsSent: ${report['packetsSent']}');
print('framesEncoded: ${report['framesEncoded']}');
}
}
コーデック情報を取得する例:
Future<void> printCodecStats(SoraConnection conn) async {
final raw = await conn.getStats();
if (raw == null || raw.isEmpty) {
return;
}
final reports = decodeStats(raw);
for (final report in reports) {
if (report['type'] != 'codec') continue;
print('mimeType: ${report['mimeType']}');
print('payloadType: ${report['payloadType']}');
}
}
タイムアウト¶
getStats() は内部で 5 秒のタイムアウトを持ちます。ネイティブ側のコールバックが応答しない場合に TimeoutException が送出されます。
Future<void> getStatsWithTimeout(SoraConnection conn) async {
try {
final stats = await conn.getStats();
print(stats);
} on TimeoutException {
print('Stats retrieval timed out');
}
}