RTT OWD 显示
创建于:2024年11月21日
使用 OpenAI 01 Mini 回答 OpenAI01
创建于:2024年11月21日
使用 OpenAI 01 Mini 回答 OpenAI01
更新UI显示
你正在更新handleSignalingData函数的计算方法,计划将RTT和OWD分别显示在两个并排的列中,用于展示最新、最大、最小和平均值。
了解 RTT 和历史
我正在查看 RTT 状态和历史,以及时钟偏移量。没有独立存储 OWD。
计算和记录
我正在计算RTT和时钟偏移,检测ONAK时延,并将RTT的状态和历史记录更新在视图生命周期中。
要在页面中并排显示往返时延(RTT)和单向时延(OWD),需要对现有的 VideoInitiatorFragment
进行以下修改:
添加用于存储 OWD 的状态变量:
owdState
和 owdHistory
来存储单向时延的最新值和历史记录。在 handleSignalingData
函数中计算并存储 OWD:
time_sync_response
时,除了计算 RTT,还要计算 OWD 并更新相应的状态变量。更新 Compose UI 布局:
WebRTCComposeLayout
,将 RTT 和 OWD 分别显示在两列中,每列包含最新、最大、最小和平均时延。以下是详细的实现步骤和代码示例:
在类的成员变量中,添加 owdState
和 owdHistory
,类似于 RTT 的状态管理:
// State variables for RTT and OWD private val rttState = mutableLongStateOf(0L) private val rttHistory = mutableStateListOf<Long>() private val owdState = mutableLongStateOf(0L) // 新增 private val owdHistory = mutableStateListOf<Long>() // 新增 private var clockOffset: Long = 0L
handleSignalingData
函数中计算并存储 OWD在 handleSignalingData
中处理 time_sync_response
时,计算 OWD 并更新状态:
"时间同步: RTT=$rtt ms, 时钟偏移量= $clockOffset ms" // 计算单向时延 OWD val owd = (t2 - t1) - clockOffset if (owd >= 0) { viewLifecycleOwner.lifecycleScope.launch { // 更新 RTT 状态 rttState.longValue = rtt // 更新 RTT 历史记录 rttHistory.add(rtt) if (rttHistory.size > 60) { rttHistory.removeAt(0) } // 计算 RTT 的最大、最小和平均值 val maxRtt = rttHistory.maxOrNull() ?: 0L val minRtt = rttHistory.minOrNull() ?: 0L val averageRtt = if (rttHistory.isNotEmpty()) { rttHistory.average().toLong() } else { 0L } // 更新 OWD 状态 owdState.longValue = owd // 更新 OWD 历史记录 owdHistory.add(owd) if (owdHistory.size > 60) { owdHistory.removeAt(0) } // 计算 OWD 的最大、最小和平均值 val maxOwd = owdHistory.maxOrNull() ?: 0L val minOwd = owdHistory.minOrNull() ?: 0L val averageOwd = if (owdHistory.isNotEmpty()) { owdHistory.average().toLong() } else { 0L } Log.d(TAG, "RTT - 最新: $rtt ms, 最大: $maxRtt ms, 最小: $minRtt ms, 平均: $averageRtt ms") Log.d(TAG, "单向时延 (OWD) - 最新: $owd ms, 最大: $maxOwd ms, 最小: $minOwd ms, 平均: $averageOwd ms") } } else { Log.e(TAG, "计算出的OWD为负数,可能时钟同步有误。") }
在 WebRTCComposeLayout
中,创建一个横向排列的 Row
,其中包含两个 Column
,分别用于显示 OWD 和 RTT 的数据:
@Composable fun WebRTCComposeLayout() { val context = LocalContext.current lateinit var peerConnectionFactory: PeerConnectionFactory var localVideoTrack: VideoTrack? by remember { mutableStateOf(null) } Surface(color = Color.Black) { Column(modifier = Modifier.fillMaxSize()) { // Local video view AndroidView( factory = { localView = SurfaceViewRenderer(it).apply { setZOrderMediaOverlay(false) } localView!! }, modifier = Modifier .weight(1f) .fillMaxWidth(), update = { if (localEglBase == null) { localEglBase = EglBase.create() it.init(localEglBase!!.eglBaseContext, null) it.setMirror(false) } } ) Spacer(modifier = Modifier.height(8.dp)) // 新增:并排显示 OWD 和 RTT Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), horizontalArrangement = Arrangement.SpaceEvenly ) { // 单向时延 (OWD) 列 Column(modifier = Modifier.weight(1f)) { Text( text = "单向时延 (OWD)", color = Color.Cyan, style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(bottom = 4.dp) ) Text( text = "最新: ${owdState.longValue} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最大: ${owdHistory.maxOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最小: ${owdHistory.minOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) val averageOwd = if (owdHistory.isNotEmpty()) owdHistory.average().toLong() else 0L Text( text = "平均: $averageOwd ms", color = Color.White, style = MaterialTheme.typography.bodyMedium ) } Spacer(modifier = Modifier.width(16.dp)) // 往返时延 (RTT) 列 Column(modifier = Modifier.weight(1f)) { Text( text = "往返时延 (RTT)", color = Color.Cyan, style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(bottom = 4.dp) ) Text( text = "最新: ${rttState.longValue} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最大: ${rttHistory.maxOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最小: ${rttHistory.minOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) val averageRtt = if (rttHistory.isNotEmpty()) rttHistory.average().toLong() else 0L Text( text = "平均: $averageRtt ms", color = Color.White, style = MaterialTheme.typography.bodyMedium ) } } } // ... 保持其他代码不变 } // ... 保持 LaunchedEffect 和其他逻辑不变 }
以下是完整的 VideoInitiatorFragment
类的更新部分,整合了上述所有修改:
class VideoInitiatorFragment : Fragment() { companion object { fun newInstance( room: String, stunUrl: String, turnUrl: String, turnUsername: String, turnPassword: String, signalingServerUrl: String ): VideoInitiatorFragment { val fragment = VideoInitiatorFragment() val args = Bundle().apply { putString("room", room) putString("stunUrl", stunUrl) putString("turnUrl", turnUrl) putString("turnUsername", turnUsername) putString("turnPassword", turnPassword) putString("signalingServerUrl", signalingServerUrl) } fragment.arguments = args return fragment } } // Class member variables remain unchanged private lateinit var socket: Socket private var localPeer: PeerConnection? = null private var localView: SurfaceViewRenderer? = null private var localEglBase: EglBase? = null private val pendingIceCandidates = mutableListOf<IceCandidate>() private var currentRoom: String? = null private lateinit var signalingServerUrl: String private lateinit var stunUrl: String private lateinit var turnUrl: String private lateinit var turnUsername: String private lateinit var turnPassword: String private val TAG: String = "WebRTC-Initiator" // State variables for RTT and OWD private val rttState = mutableLongStateOf(0L) private val rttHistory = mutableStateListOf<Long>() private val owdState = mutableLongStateOf(0L) // 新增 private val owdHistory = mutableStateListOf<Long>() // 新增 private var clockOffset: Long = 0L private var statsJob: Job? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Retrieve data from arguments currentRoom = arguments?.getString("room") ?: "default-room" signalingServerUrl = arguments?.getString("signalingServerUrl") ?: "https://wstszx.us.kg" stunUrl = arguments?.getString("stunUrl") ?: "stun:stun.wstszx.us.kg:3478" turnUrl = arguments?.getString("turnUrl") ?: "turn:turn.wstszx.us.kg:5349" turnUsername = arguments?.getString("turnUsername") ?: "wstszx" turnPassword = arguments?.getString("turnPassword") ?: "930379" Log.d( TAG, "onCreate: Role = Initiator, Room = $currentRoom, Signaling Server = $signalingServerUrl, STUN URL = $stunUrl, TURN URL = $turnUrl" ) } // ... 保持其他代码不变 @Composable fun WebRTCComposeLayout() { val context = LocalContext.current lateinit var peerConnectionFactory: PeerConnectionFactory var localVideoTrack: VideoTrack? by remember { mutableStateOf(null) } Surface(color = Color.Black) { Column(modifier = Modifier.fillMaxSize()) { // Local video view AndroidView( factory = { localView = SurfaceViewRenderer(it).apply { setZOrderMediaOverlay(false) } localView!! }, modifier = Modifier .weight(1f) .fillMaxWidth(), update = { if (localEglBase == null) { localEglBase = EglBase.create() it.init(localEglBase!!.eglBaseContext, null) it.setMirror(false) } } ) Spacer(modifier = Modifier.height(8.dp)) // 并排显示 OWD 和 RTT Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), horizontalArrangement = Arrangement.SpaceEvenly ) { // 单向时延 (OWD) 列 Column(modifier = Modifier.weight(1f)) { Text( text = "单向时延 (OWD)", color = Color.Cyan, style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(bottom = 4.dp) ) Text( text = "最新: ${owdState.longValue} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最大: ${owdHistory.maxOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最小: ${owdHistory.minOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) val averageOwd = if (owdHistory.isNotEmpty()) owdHistory.average().toLong() else 0L Text( text = "平均: $averageOwd ms", color = Color.White, style = MaterialTheme.typography.bodyMedium ) } Spacer(modifier = Modifier.width(16.dp)) // 往返时延 (RTT) 列 Column(modifier = Modifier.weight(1f)) { Text( text = "往返时延 (RTT)", color = Color.Cyan, style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(bottom = 4.dp) ) Text( text = "最新: ${rttState.longValue} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最大: ${rttHistory.maxOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = "最小: ${rttHistory.minOrNull() ?: 0} ms", color = Color.White, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(bottom = 2.dp) ) val averageRtt = if (rttHistory.isNotEmpty()) rttHistory.average().toLong() else 0L Text( text = "平均: $averageRtt ms", color = Color.White, style = MaterialTheme.typography.bodyMedium ) } } } LaunchedEffect(Unit) { val options = PeerConnectionFactory.InitializationOptions.builder(context) .createInitializationOptions() PeerConnectionFactory.initialize(options) val encoderFactory = DefaultVideoEncoderFactory( localEglBase!!.eglBaseContext, true, true ) val decoderFactory = DefaultVideoDecoderFactory(localEglBase!!.eglBaseContext) peerConnectionFactory = PeerConnectionFactory.builder() .setVideoEncoderFactory(encoderFactory) .setVideoDecoderFactory(decoderFactory) .createPeerConnectionFactory() initLocalVideo(context, localView, peerConnectionFactory, localEglBase!!) { localVideoTrack = it } createPeerConnection( context, peerConnectionFactory, localVideoTrack ) { localPeer = it startStatsCollection() // Start collecting statistics } initializeSocketIO() requestPermissionsIfNeeded() startTimeSync() } } } private fun handleSignalingData(data: JSONObject) { Log.d(TAG, "Handling signaling data: $data") when (data.getString("type")) { "offer" -> { // ... 保持不变 } "answer" -> { // ... 保持不变 } "ice" -> { // ... 保持不变 } "time_sync_response" -> { Log.d(TAG, "Received time_sync_response") val t1 = data.getLong("t1") val t2 = data.getLong("t2") val t3 = data.getLong("t3") val t4 = System.currentTimeMillis() // 计算 RTT 和时钟偏移量 val rtt = t4 - t1 - (t3 - t2) clockOffset = ((t2 - t1) + (t3 - t4)) / 2 Log.d(TAG, "时间同步: RTT=$rtt ms, 时钟偏移量= $clockOffset ms") // 计算单向时延 OWD val owd = (t2 - t1) - clockOffset if (owd >= 0) { viewLifecycleOwner.lifecycleScope.launch { // 更新 RTT 状态 rttState.longValue = rtt // 更新 RTT 历史记录 rttHistory.add(rtt) if (rttHistory.size > 60) { rttHistory.removeAt(0) } // 计算 RTT 的最大、最小和平均值 val maxRtt = rttHistory.maxOrNull() ?: 0L val minRtt = rttHistory.minOrNull() ?: 0L val averageRtt = if (rttHistory.isNotEmpty()) { rttHistory.average().toLong() } else { 0L } // 更新 OWD 状态 owdState.longValue = owd // 更新 OWD 历史记录 owdHistory.add(owd) if (owdHistory.size > 60) { owdHistory.removeAt(0) } // 计算 OWD 的最大、最小和平均值 val maxOwd = owdHistory.maxOrNull() ?: 0L val minOwd = owdHistory.minOrNull() ?: 0L val averageOwd = if (owdHistory.isNotEmpty()) { owdHistory.average().toLong() } else { 0L } Log.d(TAG, "RTT - 最新: $rtt ms, 最大: $maxRtt ms, 最小: $minRtt ms, 平均: $averageRtt ms") Log.d(TAG, "单向时延 (OWD) - 最新: $owd ms, 最大: $maxOwd ms, 最小: $minOwd ms, 平均: $averageOwd ms") } } else { Log.e(TAG, "计算出的OWD为负数,可能时钟同步有误。") } } else -> { Log.e(TAG, "Unknown signaling type: ${data.getString("type")}") } } } // ... 保持其他函数不变 override fun onDestroyView() { super.onDestroyView() statsJob?.cancel() socket.disconnect() localPeer?.dispose() localView?.release() localEglBase?.release() } }
经过上述修改后,应用界面将显示两个并排的列:
示例布局:
-------------------------------------------
| Local Video |
-------------------------------------------
| 单向时延 (OWD) | 往返时延 (RTT) |
| 最新: XX ms | 最新: XX ms |
| 最大: XX ms | 最大: XX ms |
| 最小: XX ms | 最小: XX ms |
| 平均: XX ms | 平均: XX ms |
-------------------------------------------
这样,用户可以清晰地看到当前的单向和往返时延情况,有助于分析网络性能和应用的实时性。