Committed by
GitHub
Add RTC stats for publisher/subscriber peer connection and tracks (#165)
* Add RTC stats for publisher/subscriber peer connection and tracks * Documentation
正在显示
3 个修改的文件
包含
171 行增加
和
0 行删除
| @@ -808,6 +808,14 @@ internal constructor( | @@ -808,6 +808,14 @@ internal constructor( | ||
| 808 | 808 | ||
| 809 | client.sendSyncState(syncState) | 809 | client.sendSyncState(syncState) |
| 810 | } | 810 | } |
| 811 | + | ||
| 812 | + fun getPublisherRTCStats(callback: RTCStatsCollectorCallback) { | ||
| 813 | + _publisher?.peerConnection?.getStats(callback) ?: callback.onStatsDelivered(RTCStatsReport(0, emptyMap())) | ||
| 814 | + } | ||
| 815 | + | ||
| 816 | + fun getSubscriberRTCStats(callback: RTCStatsCollectorCallback) { | ||
| 817 | + _subscriber?.peerConnection?.getStats(callback) ?: callback.onStatsDelivered(RTCStatsReport(0, emptyMap())) | ||
| 818 | + } | ||
| 811 | } | 819 | } |
| 812 | 820 | ||
| 813 | /** | 821 | /** |
| @@ -27,6 +27,7 @@ import io.livekit.android.util.FlowObservable | @@ -27,6 +27,7 @@ import io.livekit.android.util.FlowObservable | ||
| 27 | import io.livekit.android.util.LKLog | 27 | import io.livekit.android.util.LKLog |
| 28 | import io.livekit.android.util.flowDelegate | 28 | import io.livekit.android.util.flowDelegate |
| 29 | import io.livekit.android.util.invoke | 29 | import io.livekit.android.util.invoke |
| 30 | +import io.livekit.android.webrtc.getFilteredStats | ||
| 30 | import kotlinx.coroutines.* | 31 | import kotlinx.coroutines.* |
| 31 | import livekit.LivekitModels | 32 | import livekit.LivekitModels |
| 32 | import livekit.LivekitRtc | 33 | import livekit.LivekitRtc |
| @@ -874,6 +875,22 @@ constructor( | @@ -874,6 +875,22 @@ constructor( | ||
| 874 | } | 875 | } |
| 875 | } | 876 | } |
| 876 | 877 | ||
| 878 | + /** | ||
| 879 | + * Get stats for the publisher peer connection. | ||
| 880 | + * | ||
| 881 | + * @see getSubscriberRTCStats | ||
| 882 | + * @see getFilteredStats | ||
| 883 | + */ | ||
| 884 | + fun getPublisherRTCStats(callback: RTCStatsCollectorCallback) = engine.getPublisherRTCStats(callback) | ||
| 885 | + | ||
| 886 | + /** | ||
| 887 | + * Get stats for the subscriber peer connection. | ||
| 888 | + * | ||
| 889 | + * @see getPublisherRTCStats | ||
| 890 | + * @see getFilteredStats | ||
| 891 | + */ | ||
| 892 | + fun getSubscriberRTCStats(callback: RTCStatsCollectorCallback) = engine.getSubscriberRTCStats(callback) | ||
| 893 | + | ||
| 877 | // Debug options | 894 | // Debug options |
| 878 | 895 | ||
| 879 | /** | 896 | /** |
| 1 | +package io.livekit.android.webrtc | ||
| 2 | + | ||
| 3 | +import io.livekit.android.util.LKLog | ||
| 4 | +import org.webrtc.MediaStreamTrack | ||
| 5 | +import org.webrtc.RTCStats | ||
| 6 | +import org.webrtc.RTCStatsReport | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * Returns an RTCStatsReport with all the relevant information pertaining to a track. | ||
| 10 | + * | ||
| 11 | + * @param trackIdentifier track, sender, or receiver id | ||
| 12 | + */ | ||
| 13 | +fun RTCStatsReport.getFilteredStats(track: MediaStreamTrack): RTCStatsReport { | ||
| 14 | + return getFilteredStats(track.id()) | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * Returns an RTCStatsReport with all the relevant information pertaining to a track identifier. | ||
| 19 | + * | ||
| 20 | + * @param trackIdentifier track, sender, or receiver id | ||
| 21 | + */ | ||
| 22 | +fun RTCStatsReport.getFilteredStats(trackIdentifier: String): RTCStatsReport { | ||
| 23 | + val rtcStatsReport = this | ||
| 24 | + val statsMap = rtcStatsReport.statsMap | ||
| 25 | + val filteredStats = mutableSetOf<RTCStats>() | ||
| 26 | + | ||
| 27 | + // Get track stats | ||
| 28 | + val trackStats = getTrackStats(trackIdentifier, statsMap) | ||
| 29 | + if (trackStats == null) { | ||
| 30 | + LKLog.i { "getStats: couldn't find track stats!" } | ||
| 31 | + return RTCStatsReport(rtcStatsReport.timestampUs.toLong(), HashMap()) | ||
| 32 | + } | ||
| 33 | + filteredStats.add(trackStats) | ||
| 34 | + val trackId = trackStats.id | ||
| 35 | + | ||
| 36 | + // Get stream stats | ||
| 37 | + val streamStats = getStreamStats(trackId, statsMap) | ||
| 38 | + if (streamStats != null) { | ||
| 39 | + filteredStats.add(streamStats) | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + // Get streamType stats and associated information | ||
| 43 | + val ssrcs: MutableSet<Long?> = HashSet() | ||
| 44 | + val codecIds: MutableSet<String?> = HashSet() | ||
| 45 | + | ||
| 46 | + for (stats in statsMap.values) { | ||
| 47 | + if ((stats.type == "inbound-rtp" || stats.type == "outbound-rtp") && trackId == stats.members["trackId"]) { | ||
| 48 | + ssrcs.add(stats.members["ssrc"] as Long?) | ||
| 49 | + codecIds.add(stats.members["codecId"] as String?) | ||
| 50 | + filteredStats.add(stats) | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + // Get nominated candidate information | ||
| 55 | + var candidatePairStats: RTCStats? = null | ||
| 56 | + for (stats in statsMap.values) { | ||
| 57 | + if (stats.type == "candidate-pair" && stats.members["nominated"] == true) { | ||
| 58 | + candidatePairStats = stats | ||
| 59 | + break | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + var localCandidateId: String? = null | ||
| 64 | + var remoteCandidateId: String? = null | ||
| 65 | + if (candidatePairStats != null) { | ||
| 66 | + filteredStats.add(candidatePairStats) | ||
| 67 | + localCandidateId = candidatePairStats.members["localCandidateId"] as String? | ||
| 68 | + remoteCandidateId = candidatePairStats.members["remoteCandidateId"] as String? | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + // Sweep for any remaining stats we want. | ||
| 72 | + filteredStats.addAll( | ||
| 73 | + getExtraStats( | ||
| 74 | + trackIdentifier, | ||
| 75 | + ssrcs, | ||
| 76 | + codecIds, | ||
| 77 | + localCandidateId, | ||
| 78 | + remoteCandidateId, | ||
| 79 | + statsMap | ||
| 80 | + ) | ||
| 81 | + ) | ||
| 82 | + val filteredStatsMap: MutableMap<String, RTCStats> = HashMap() | ||
| 83 | + for (stats in filteredStats) { | ||
| 84 | + filteredStatsMap[stats.id] = stats | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + return RTCStatsReport(rtcStatsReport.timestampUs.toLong(), filteredStatsMap) | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +// Note: trackIdentifier can differ from the internal stats trackId | ||
| 91 | +// trackIdentifier refers to the sender or receiver id | ||
| 92 | +private fun getTrackStats(trackIdentifier: String, statsMap: Map<String, RTCStats>): RTCStats? { | ||
| 93 | + for (stats in statsMap.values) { | ||
| 94 | + if (stats.type == "track" && trackIdentifier == stats.members["trackIdentifier"]) { | ||
| 95 | + return stats | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + return null | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | + | ||
| 102 | +private fun getStreamStats(trackId: String, statsMap: Map<String, RTCStats>): RTCStats? { | ||
| 103 | + for (stats in statsMap.values) { | ||
| 104 | + if (stats.type == "stream") { | ||
| 105 | + val trackIds = (stats.members["trackIds"] as? Array<*>)?.toList() ?: emptyList() | ||
| 106 | + if (trackIds.contains(trackId)) { | ||
| 107 | + return stats | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + return null | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +// Note: trackIdentifier can differ from the internal stats trackId | ||
| 115 | +// trackIdentifier refers to the sender or receiver id | ||
| 116 | +private fun getExtraStats( | ||
| 117 | + trackIdentifier: String, | ||
| 118 | + ssrcs: Set<Long?>, | ||
| 119 | + codecIds: Set<String?>, | ||
| 120 | + localCandidateId: String?, | ||
| 121 | + remoteCandidateId: String?, | ||
| 122 | + statsMap: Map<String, RTCStats> | ||
| 123 | +): Set<RTCStats> { | ||
| 124 | + val extraStats: MutableSet<RTCStats> = HashSet() | ||
| 125 | + for (stats in statsMap.values) { | ||
| 126 | + when (stats.type) { | ||
| 127 | + "certificate", "transport" -> extraStats.add(stats) | ||
| 128 | + } | ||
| 129 | + if (stats.id == localCandidateId || stats.id == remoteCandidateId) { | ||
| 130 | + extraStats.add(stats) | ||
| 131 | + continue | ||
| 132 | + } | ||
| 133 | + if (ssrcs.contains(stats.members["ssrc"])) { | ||
| 134 | + extraStats.add(stats) | ||
| 135 | + continue | ||
| 136 | + } | ||
| 137 | + if (trackIdentifier == stats.members["trackIdentifier"]) { | ||
| 138 | + extraStats.add(stats) | ||
| 139 | + continue | ||
| 140 | + } | ||
| 141 | + if (codecIds.contains(stats.id)) { | ||
| 142 | + extraStats.add(stats) | ||
| 143 | + } | ||
| 144 | + } | ||
| 145 | + return extraStats | ||
| 146 | +} |
-
请 注册 或 登录 后发表评论