@mentra/acs-meeting 3.2.0-dev.222 → 3.2.0-dev.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +14 -3
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingModule.kt +10 -10
  4. package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingSession.kt +37 -37
  5. package/android/src/main/java/com/mentra/acsmeeting/audio/UplinkSender.kt +4 -4
  6. package/android/src/main/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpec.kt +3 -0
  7. package/android/src/main/java/com/mentra/acsmeeting/video/AcsFrameSender.kt +12 -10
  8. package/android/src/main/java/com/mentra/acsmeeting/video/I420FormatSpec.kt +2 -0
  9. package/android/src/main/java/com/mentra/acsmeeting/video/Nv12Packer.kt +2 -0
  10. package/android/src/test/java/com/mentra/acsmeeting/source/{AcsInvestigationTest.kt → MediaDiagnosticsTest.kt} +10 -10
  11. package/android/src/test/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpecTest.kt +3 -0
  12. package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoQualityTest.kt +2 -2
  13. package/android/src/test/java/com/mentra/acsmeeting/video/I420FormatSpecTest.kt +2 -0
  14. package/android/src/test/java/com/mentra/acsmeeting/video/VideoProfileTest.kt +2 -2
  15. package/build/AcsMeeting.types.d.ts +10 -1
  16. package/build/AcsMeeting.types.d.ts.map +1 -1
  17. package/build/AcsMeeting.types.js.map +1 -1
  18. package/build/AcsMeetingModule.d.ts +2 -2
  19. package/build/AcsMeetingModule.d.ts.map +1 -1
  20. package/build/AcsMeetingModule.js.map +1 -1
  21. package/ios/AcsMeeting.podspec +1 -1
  22. package/ios/AcsMeetingModule.swift +284 -148
  23. package/ios/PolicyKit/Package.swift +2 -1
  24. package/ios/PolicyKit/Sources/AcsAudioPolicy/CallbackOperation.swift +60 -0
  25. package/ios/PolicyKit/Sources/AcsAudioPolicy/CapturePolicy.swift +5 -0
  26. package/ios/PolicyKit/Tests/AcsAudioPolicyTests/AcsAudioPolicyTests.swift +1 -0
  27. package/ios/PolicyKit/Tests/AcsAudioPolicyTests/CallAgentRetirementTests.swift +113 -0
  28. package/package.json +3 -2
  29. package/src/AcsMeeting.types.ts +4 -1
  30. package/src/AcsMeetingModule.ts +2 -2
  31. package/android/src/androidTest/java/com/mentra/acsmeeting/network/SoftApFeasibilityGateTest.kt +0 -291
  32. package/android/src/main/java/com/mentra/acsmeeting/network/InternetHold.kt +0 -501
  33. package/android/src/main/java/com/mentra/acsmeeting/network/Ipv4Prefix.kt +0 -51
  34. package/android/src/main/java/com/mentra/acsmeeting/network/NetworkFacts.kt +0 -81
  35. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetector.kt +0 -355
  36. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkError.kt +0 -109
  37. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkObserver.kt +0 -160
  38. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkReadiness.kt +0 -103
  39. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpec.kt +0 -53
  40. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkState.kt +0 -127
  41. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedSoftApNetwork.kt +0 -573
  42. package/android/src/main/java/com/mentra/acsmeeting/source/BitmapFont.kt +0 -99
  43. package/android/src/main/java/com/mentra/acsmeeting/source/CloudflareWhepSource.kt +0 -487
  44. package/android/src/main/java/com/mentra/acsmeeting/source/DecodedTrackRelay.kt +0 -147
  45. package/android/src/main/java/com/mentra/acsmeeting/source/FirstFrameGate.kt +0 -45
  46. package/android/src/main/java/com/mentra/acsmeeting/source/GlassesMediaSource.kt +0 -143
  47. package/android/src/main/java/com/mentra/acsmeeting/source/LocalWhipIngestSource.kt +0 -772
  48. package/android/src/main/java/com/mentra/acsmeeting/source/MediaListeners.kt +0 -57
  49. package/android/src/main/java/com/mentra/acsmeeting/source/SdpAdapter.kt +0 -17
  50. package/android/src/main/java/com/mentra/acsmeeting/source/SelectedIcePair.kt +0 -139
  51. package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIceDiagnosis.kt +0 -144
  52. package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIcePolicy.kt +0 -46
  53. package/android/src/main/java/com/mentra/acsmeeting/source/SoftApSdpGuard.kt +0 -160
  54. package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticFrameFactory.kt +0 -205
  55. package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticI420Source.kt +0 -214
  56. package/android/src/main/java/com/mentra/acsmeeting/source/TrackRegistry.kt +0 -33
  57. package/android/src/main/java/com/mentra/acsmeeting/source/VideoSourceArm.kt +0 -110
  58. package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestProtocol.kt +0 -207
  59. package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestServer.kt +0 -356
  60. package/android/src/main/java/com/mentra/acsmeeting/telemetry/AvSyncProbe.kt +0 -155
  61. package/android/src/main/java/com/mentra/acsmeeting/telemetry/ChromaProbe.kt +0 -78
  62. package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineStats.kt +0 -372
  63. package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineTicker.kt +0 -79
  64. package/android/src/main/java/com/mentra/acsmeeting/telemetry/ProcessCpu.kt +0 -15
  65. package/android/src/main/java/com/mentra/acsmeeting/telemetry/RingPercentile.kt +0 -48
  66. package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoQuality.kt +0 -178
  67. package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoRateVerdict.kt +0 -145
  68. package/android/src/main/java/com/mentra/acsmeeting/trace/SoftApTrace.kt +0 -143
  69. package/android/src/main/java/com/mentra/acsmeeting/video/FrameGeometry.kt +0 -22
  70. package/android/src/main/java/com/mentra/acsmeeting/video/I420Packer.kt +0 -72
  71. package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldLifecycleTest.kt +0 -102
  72. package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldTest.kt +0 -148
  73. package/android/src/test/java/com/mentra/acsmeeting/network/Ipv4PrefixTest.kt +0 -59
  74. package/android/src/test/java/com/mentra/acsmeeting/network/NetworkFactsTest.kt +0 -59
  75. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedLocalNetworkPermissionTest.kt +0 -35
  76. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetectorTest.kt +0 -165
  77. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkErrorTest.kt +0 -96
  78. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkLifecycleTest.kt +0 -187
  79. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkObserverTest.kt +0 -172
  80. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkReadinessTest.kt +0 -141
  81. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpecTest.kt +0 -69
  82. package/android/src/test/java/com/mentra/acsmeeting/source/BitmapFontTest.kt +0 -22
  83. package/android/src/test/java/com/mentra/acsmeeting/source/DecodedTrackRelayTest.kt +0 -41
  84. package/android/src/test/java/com/mentra/acsmeeting/source/FirstFrameGateTest.kt +0 -60
  85. package/android/src/test/java/com/mentra/acsmeeting/source/GlassesMediaSourceTest.kt +0 -151
  86. package/android/src/test/java/com/mentra/acsmeeting/source/I420PlanesTest.kt +0 -67
  87. package/android/src/test/java/com/mentra/acsmeeting/source/SelectedIcePairTest.kt +0 -165
  88. package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIceDiagnosisTest.kt +0 -148
  89. package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIcePolicyTest.kt +0 -76
  90. package/android/src/test/java/com/mentra/acsmeeting/source/SoftApSdpGuardTest.kt +0 -275
  91. package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticFrameFactoryTest.kt +0 -193
  92. package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticI420SourceTest.kt +0 -176
  93. package/android/src/test/java/com/mentra/acsmeeting/source/TrackRegistryTest.kt +0 -45
  94. package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestProtocolTest.kt +0 -356
  95. package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestServerLoopbackTest.kt +0 -411
  96. package/android/src/test/java/com/mentra/acsmeeting/telemetry/AvSyncProbeTest.kt +0 -83
  97. package/android/src/test/java/com/mentra/acsmeeting/telemetry/ChromaProbeTest.kt +0 -61
  98. package/android/src/test/java/com/mentra/acsmeeting/telemetry/PipelineStatsTest.kt +0 -322
  99. package/android/src/test/java/com/mentra/acsmeeting/telemetry/ProcessCpuTest.kt +0 -18
  100. package/android/src/test/java/com/mentra/acsmeeting/telemetry/RingPercentileTest.kt +0 -49
  101. package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoRateVerdictTest.kt +0 -149
  102. package/android/src/test/java/com/mentra/acsmeeting/trace/SoftApTraceTest.kt +0 -97
  103. package/android/src/test/java/com/mentra/acsmeeting/video/FrameGeometryTest.kt +0 -33
  104. package/android/src/test/java/com/mentra/acsmeeting/video/I420PackerTest.kt +0 -126
  105. package/ios/PolicyKit/Sources/AcsAudioPolicy/GlassesMediaSource.swift +0 -68
  106. package/ios/WhepVideoSource.swift +0 -356
@@ -1,322 +0,0 @@
1
- package com.mentra.acsmeeting.telemetry
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
-
6
- class PipelineStatsTest {
7
- @Test
8
- fun conservationHoldsAcrossEveryDisposition() {
9
- val stats = PipelineStats()
10
- repeat(5) { stats.onSink() }
11
- stats.onQueued(); stats.onSub()
12
- stats.onDropSize()
13
- stats.onDropBusy()
14
- stats.onDropNullI420()
15
- stats.onDropPaced()
16
- assertThat(stats.conserved()).isTrue()
17
- assertThat(stats.sinkCount()).isEqualTo(5)
18
- assertThat(stats.subCount()).isEqualTo(1)
19
- assertThat(stats.dropCount()).isEqualTo(4)
20
- assertThat(stats.inFlightCount()).isEqualTo(0)
21
- }
22
-
23
- @Test
24
- fun latencyStraddleIsOneHundredPercentOverTenTicks() {
25
- var clock = 0L
26
- val stats = PipelineStats { clock }
27
- val ticks = ArrayList<String>()
28
- stats.onSink()
29
- stats.onQueued()
30
- clock += 1000
31
- ticks.add(stats.tick())
32
- stats.onSub()
33
- repeat(9) {
34
- clock += 1000
35
- ticks.add(stats.tick())
36
- }
37
- assertThat(stats.subCount()).isEqualTo(stats.sinkCount())
38
- assertThat(stats.conserved()).isTrue()
39
- val last = ticks.last()
40
- assertThat(last).contains("cum{sink=1 sub=1 drop=0 inFlight=0}")
41
- assertThat(last).doesNotContain("CONSERVE_FAIL")
42
- }
43
-
44
- @Test
45
- fun cumulativeCountersNeverDecrease() {
46
- var clock = 0L
47
- val stats = PipelineStats { clock }
48
- stats.onSink(); stats.onQueued(); stats.onSub()
49
- clock += 1000
50
- val first = stats.tick()
51
- clock += 1000
52
- val idle = stats.tick()
53
- assertThat(extractCum(first)).isEqualTo(extractCum(idle))
54
- assertThat(stats.sinkCount()).isEqualTo(1)
55
- assertThat(stats.subCount()).isEqualTo(1)
56
- }
57
-
58
- @Test
59
- fun idleTickStillEmitsSinkZero() {
60
- var clock = 0L
61
- val stats = PipelineStats { clock }
62
- clock += 1000
63
- val line = stats.tick()
64
- assertThat(line).startsWith("P6 ladder")
65
- assertThat(line).contains("sink=0.0")
66
- assertThat(line).contains("dec=na")
67
- assertThat(line).contains("wire=0x0@na kbps=na codec=na")
68
- assertThat(line).contains("drop{")
69
- assertThat(line).contains("pace=0")
70
- assertThat(line).contains("ms{")
71
- assertThat(line).contains("chroma{")
72
- assertThat(line).contains("cum{sink=0 sub=0 drop=0 inFlight=0}")
73
- }
74
-
75
- @Test
76
- fun queuedFrameCountsAsInFlightUntilSettled() {
77
- val stats = PipelineStats()
78
- stats.onSink()
79
- stats.onQueued()
80
- assertThat(stats.conserved()).isTrue()
81
- assertThat(stats.inFlightCount()).isEqualTo(1)
82
- stats.onSub()
83
- assertThat(stats.inFlightCount()).isEqualTo(0)
84
- assertThat(stats.conserved()).isTrue()
85
- }
86
-
87
- /**
88
- * The device logged 14 CONSERVE_FAIL lines on a pipeline with zero drops.
89
- * Cause was a tracked inFlight counter read mid-settle, not lost frames.
90
- */
91
- @Test
92
- fun concurrentSendsNeverReportConserveFail() {
93
- val stats = PipelineStats { 0L }
94
- val sender = Thread {
95
- repeat(20_000) {
96
- stats.onSink()
97
- stats.onQueued()
98
- stats.onSub()
99
- }
100
- }
101
- val failures = ArrayList<String>()
102
- val reader = Thread {
103
- repeat(20_000) {
104
- if (!stats.conserved()) failures.add("inFlight=${stats.inFlightCount()}")
105
- }
106
- }
107
- sender.start(); reader.start()
108
- sender.join(); reader.join()
109
- assertThat(failures).isEmpty()
110
- assertThat(stats.inFlightCount()).isEqualTo(0)
111
- assertThat(stats.tick()).doesNotContain("CONSERVE_FAIL")
112
- }
113
-
114
- @Test
115
- fun recvHealthReportsDeltasNotAbsolutes() {
116
- val stats = PipelineStats { 0L }
117
- assertThat(stats.recvLabel()).isEqualTo("recv{na}")
118
- stats.setRecvHealth(
119
- PipelineStats.RecvHealth(assembled = 100, dropped = 5, packetsLost = 40, nack = 7, pli = 2, freezes = 1),
120
- )
121
- assertThat(stats.recvLabel()).contains("drop=5", "lost=40", "nack=7", "pli=2", "freeze=1")
122
- stats.setRecvHealth(
123
- PipelineStats.RecvHealth(assembled = 115, dropped = 5, packetsLost = 52, nack = 9, pli = 2, freezes = 3),
124
- )
125
- assertThat(stats.recvLabel()).contains("drop=0", "lost=12", "nack=2", "pli=0", "freeze=2")
126
- }
127
-
128
- @Test
129
- fun recvHealthAveragesPerFrameTimings() {
130
- val stats = PipelineStats { 0L }
131
- stats.setRecvHealth(
132
- PipelineStats.RecvHealth(
133
- assembled = 200,
134
- decodeSec = 0.8,
135
- jitterBufferSec = 12.0,
136
- jitterBufferEmits = 200,
137
- jitter = 0.015,
138
- ),
139
- )
140
- val label = stats.recvLabel()
141
- assertThat(label).contains("decMs=4.0")
142
- assertThat(label).contains("jbMs=60.0")
143
- assertThat(label).contains("jit=15.0")
144
- }
145
-
146
- @Test
147
- fun recvHealthToleratesMissingMembers() {
148
- val stats = PipelineStats { 0L }
149
- stats.setRecvHealth(PipelineStats.RecvHealth())
150
- val label = stats.recvLabel()
151
- assertThat(label).contains("drop=na", "lost=na", "decMs=na", "jbMs=na", "decImpl=na")
152
- assertThat(stats.tick()).contains("recv{")
153
- }
154
-
155
- @Test
156
- fun recvHealthPrintsDecoderImplementation() {
157
- val stats = PipelineStats { 0L }
158
- stats.setRecvHealth(PipelineStats.RecvHealth(decImpl = "OMX.qcom.video.decoder.avc"))
159
- assertThat(stats.recvLabel()).contains("decImpl=OMX.qcom.video.decoder.avc")
160
- }
161
-
162
- @Test
163
- fun ladderIncludesArmLabel() {
164
- var clock = 0L
165
- val stats = PipelineStats { clock }
166
- clock += 1000
167
- assertThat(stats.tick()).contains("arm=whep")
168
- stats.arm = "synthetic"
169
- clock += 1000
170
- assertThat(stats.tick()).contains("arm=synthetic")
171
- }
172
-
173
- @Test
174
- fun ladderExposesCpuAttributionFields() {
175
- var clock = 0L
176
- val stats = PipelineStats { clock }
177
- stats.toI420.record(4_000_000)
178
- stats.sinkCb.record(8_000_000)
179
- stats.split.record(1_000_000)
180
- stats.onDestAlloc()
181
- stats.onPlaneAlloc()
182
- stats.onPlaneAlloc()
183
- clock += 1000
184
- val line = stats.tick()
185
- assertThat(line).contains("i420P95=4.0")
186
- assertThat(line).contains("sinkCbP95=8.0")
187
- assertThat(line).contains("splitP95=1.0")
188
- assertThat(line).contains("alloc{dest=1 plane=2}")
189
- }
190
-
191
- @Test
192
- fun ladderCarriesPathBufStrideZcAndCopyP95Na() {
193
- var clock = 0L
194
- val stats = PipelineStats { clock }
195
- clock += 1000
196
- val idle = stats.tick()
197
- assertThat(idle).contains("path{mode=texture copy=packsplit pix=i420}")
198
- assertThat(idle).contains("wire=0x0@na kbps=na codec=na")
199
- assertThat(idle).contains("buf{tex=0 i420=0 other=0}")
200
- assertThat(idle).contains("stride{y=0 u=0 v=0 tight=0 padded=0}")
201
- assertThat(idle).contains("zc{on=0 used=0 fell=0 padded=0 heldMax=0 timeout=0}")
202
- assertThat(idle).contains("copyP95=na")
203
-
204
- stats.pathMode = "bytebuf"
205
- stats.pathCopy = "nv12"
206
- stats.pix = "nv12"
207
- stats.codecName = "h264 sw"
208
- stats.onFrameBuffer("tex")
209
- stats.onFrameBuffer("i420")
210
- stats.onFrameBuffer("i420")
211
- stats.onFrameBuffer("nv12")
212
- stats.onStrides(1280, 640, 640, 1280)
213
- stats.onStrides(1280, 768, 768, 1280)
214
- stats.zcOn = 1
215
- stats.onZcUsed(1)
216
- stats.onZcUsed(2)
217
- stats.onZcFell()
218
- stats.onZcPadded()
219
- stats.onZcTimeout()
220
- stats.copy.record(3_000_000)
221
- clock += 1000
222
- val line = stats.tick()
223
- assertThat(line).contains("path{mode=bytebuf copy=nv12 pix=nv12}")
224
- assertThat(line).contains("codec=h264_sw")
225
- assertThat(line).contains("buf{tex=1 i420=2 other=1}")
226
- assertThat(line).contains("stride{y=1280 u=768 v=768 tight=1 padded=1}")
227
- assertThat(line).contains("zc{on=1 used=2 fell=1 padded=1 heldMax=2 timeout=1}")
228
- assertThat(line).contains("copyP95=3.0")
229
- }
230
-
231
- @Test
232
- fun ladderPrintsAcsWireGeometryAndBitrate() {
233
- var clock = 0L
234
- val stats = PipelineStats { clock }
235
- stats.wireWidth = 960
236
- stats.wireHeight = 540
237
- stats.wireFps = 29.8
238
- stats.wireBitrateBps = 1_480_000
239
- clock += 1000
240
- stats.codecName = "H264SkypeEncoder_HW"
241
- assertThat(stats.tick()).contains("wire=960x540@29.8 kbps=1480 codec=H264SkypeEncoder_HW")
242
- }
243
-
244
- @Test
245
- fun ladderExposesRecvRateSeparateFromDecode() {
246
- var clock = 0L
247
- val stats = PipelineStats { clock }
248
- stats.recvFps = 14.8
249
- stats.decodedFps = 8.1
250
- clock += 1000
251
- val line = stats.tick()
252
- assertThat(line).contains("recv=14.8")
253
- assertThat(line).contains("dec=8.1")
254
- }
255
-
256
- @Test
257
- fun ladderReportsFrameAgeAndEndToEndLatency() {
258
- var clock = 0L
259
- val stats = PipelineStats { clock }
260
- stats.age.record(40_000_000)
261
- stats.e2e.record(95_000_000)
262
- clock += 1000
263
- val line = stats.tick()
264
- assertThat(line).contains("ageP50=40.0")
265
- assertThat(line).contains("e2eP50=95.0")
266
- }
267
-
268
- /**
269
- * A backed-up ACS send thread has no other symptom on this ladder: `sendP95` times the ACS call
270
- * itself, so the wait in front of it has to be its own measurement.
271
- */
272
- @Test
273
- fun ladderSeparatesSenderQueueWaitFromTheAcsCall() {
274
- var clock = 0L
275
- val stats = PipelineStats { clock }
276
- stats.queue.record(30_000_000)
277
- stats.send.record(2_000_000)
278
- clock += 1000
279
- val line = stats.tick()
280
- assertThat(line).contains("queueP95=30.0")
281
- assertThat(line).contains("sendP95=2.0")
282
- }
283
-
284
- @Test
285
- fun slowestStageNamesTheWorstPhoneSideStage() {
286
- val stats = PipelineStats { 0 }
287
- stats.scale.record(2_000_000)
288
- stats.toI420.record(9_000_000)
289
- stats.copy.record(1_000_000)
290
- stats.send.record(12_000_000)
291
- assertThat(stats.slowestStage()).isEqualTo("send=12.0")
292
- }
293
-
294
- /** Ranking must compare numbers: as strings "9.0" sorts above "12.0". */
295
- @Test
296
- fun slowestStageRanksNumericallyNotLexically() {
297
- val stats = PipelineStats { 0 }
298
- stats.toI420.record(9_000_000)
299
- stats.copy.record(12_000_000)
300
- assertThat(stats.slowestStage()).isEqualTo("copy=12.0")
301
- }
302
-
303
- /**
304
- * Frame age is dominated by capture, encode and the network, so blaming a phone-side stage for it
305
- * would make every healthy call accuse a stage this device does not run.
306
- */
307
- @Test
308
- fun slowestStageIgnoresUpstreamFrameAge() {
309
- val stats = PipelineStats { 0 }
310
- stats.age.record(400_000_000)
311
- stats.copy.record(3_000_000)
312
- assertThat(stats.slowestStage()).isEqualTo("copy=3.0")
313
- }
314
-
315
- @Test
316
- fun slowestStageIsNotAvailableBeforeAnySamples() {
317
- assertThat(PipelineStats { 0 }.slowestStage()).isEqualTo("na")
318
- }
319
-
320
- private fun extractCum(line: String): String =
321
- Regex("""cum\{[^}]+\}""").find(line)?.value ?: line
322
- }
@@ -1,18 +0,0 @@
1
- package com.mentra.acsmeeting.telemetry
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
-
6
- class ProcessCpuTest {
7
- @Test
8
- fun percentMatchesTopStyleOverOneSecond() {
9
- assertThat(ProcessCpu.percent(cpuDeltaMs = 1120, wallDeltaMs = 1000)).isEqualTo(112.0)
10
- assertThat(ProcessCpu.percent(cpuDeltaMs = 200, wallDeltaMs = 1000)).isEqualTo(20.0)
11
- }
12
-
13
- @Test
14
- fun labelFormatsNaUntilFirstSample() {
15
- assertThat(ProcessCpu.label(null, 8)).isEqualTo("cpu{proc=na cores=8}")
16
- assertThat(ProcessCpu.label(112.4, 8)).isEqualTo("cpu{proc=112.4 cores=8}")
17
- }
18
- }
@@ -1,49 +0,0 @@
1
- package com.mentra.acsmeeting.telemetry
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
- import java.util.concurrent.CountDownLatch
6
- import java.util.concurrent.Executors
7
- import java.util.concurrent.TimeUnit
8
-
9
- class RingPercentileTest {
10
- @Test
11
- fun emptyIsNa() {
12
- assertThat(RingPercentile().p50()).isEqualTo("na")
13
- assertThat(RingPercentile().p95()).isEqualTo("na")
14
- }
15
-
16
- @Test
17
- fun knownInputs() {
18
- val ring = RingPercentile(8)
19
- ring.record(10_000_000)
20
- ring.record(20_000_000)
21
- ring.record(40_000_000)
22
- assertThat(ring.p50()).isEqualTo("20.0")
23
- }
24
-
25
- @Test
26
- fun wraparoundKeepsNewest() {
27
- val ring = RingPercentile(3)
28
- repeat(6) { index -> ring.record((index + 1) * 1_000_000L) }
29
- assertThat(ring.size()).isEqualTo(3)
30
- assertThat(ring.p50()).isEqualTo("5.0")
31
- }
32
-
33
- @Test
34
- fun concurrentRecordDoesNotCorruptCount() {
35
- val ring = RingPercentile(32)
36
- val pool = Executors.newFixedThreadPool(4)
37
- val latch = CountDownLatch(4)
38
- repeat(4) {
39
- pool.execute {
40
- repeat(50) { ring.record(1_000_000) }
41
- latch.countDown()
42
- }
43
- }
44
- assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue()
45
- pool.shutdown()
46
- assertThat(ring.size()).isEqualTo(32)
47
- assertThat(ring.p50()).isNotEqualTo("na")
48
- }
49
- }
@@ -1,149 +0,0 @@
1
- package com.mentra.acsmeeting.telemetry
2
-
3
- import com.mentra.acsmeeting.telemetry.VideoRateVerdict.Bound
4
- import org.assertj.core.api.Assertions.assertThat
5
- import org.junit.Test
6
-
7
- class VideoRateVerdictTest {
8
- /** 540p15 against a starved 1.5 Mbps budget — the grant Mentra Call used to send. */
9
- private fun verdict(
10
- advertisedFps: Double = 15.0,
11
- sinkFps: Double = 15.0,
12
- admittedFps: Double = 15.0,
13
- wireFps: Double? = 15.0,
14
- wireBitrateBps: Long? = 1_400_000,
15
- budgetBps: Int = 1_500_000,
16
- cpuPercent: Double? = 55.0,
17
- ) = VideoRateVerdict.of(
18
- advertisedFps = advertisedFps,
19
- sinkFps = sinkFps,
20
- admittedFps = admittedFps,
21
- wireFps = wireFps,
22
- wireBitrateBps = wireBitrateBps,
23
- budgetBps = budgetBps,
24
- cpuPercent = cpuPercent,
25
- )
26
-
27
- @Test
28
- fun aWireHoldingTheAdvertisedRateIsOk() {
29
- assertThat(verdict()).isEqualTo(Bound.OK)
30
- }
31
-
32
- /**
33
- * The verdict that decides whether "the call quality is low" is answered by raising the grant.
34
- * Every frame is on the wire, so nothing here is short — but the bits are pressed against the
35
- * ceiling, which is the one condition under which a bigger number changes the picture. Without
36
- * this the same tick reads OK and the ceiling never enters the conversation.
37
- */
38
- @Test
39
- fun aFullRateRidingTheCeilingIsNamedAsCapBound() {
40
- assertThat(verdict(wireBitrateBps = 1_480_000)).isEqualTo(Bound.CAP_BOUND)
41
- // The controller routinely overshoots the grant; that is still the ceiling binding.
42
- assertThat(verdict(wireBitrateBps = 1_623_000)).isEqualTo(Bound.CAP_BOUND)
43
- }
44
-
45
- /** Headroom under the grant at a full rate is a healthy stream, not a ceiling problem. */
46
- @Test
47
- fun aFullRateWithBitsToSpareIsStillPlainOk() {
48
- assertThat(verdict(wireBitrateBps = 1_100_000)).isEqualTo(Bound.OK)
49
- assertThat(verdict(wireBitrateBps = null)).isEqualTo(Bound.OK)
50
- }
51
-
52
- /**
53
- * Cap-bound is about the ceiling, and a short frame rate means something else is wrong first.
54
- * Raising the grant for a stream that cannot fill the one it has buys nothing.
55
- */
56
- @Test
57
- fun aShortRateIsDiagnosedBeforeTheCeilingIsConsidered() {
58
- assertThat(verdict(wireFps = 8.0, wireBitrateBps = 1_480_000, cpuPercent = 92.0))
59
- .isEqualTo(Bound.ENCODER_SHORT)
60
- }
61
-
62
- /**
63
- * The exact shape of the greedy-gate bug: the glasses supplied 14.6 fps, the
64
- * pacer admitted 7.3 against an advertised 8, and every counter downstream
65
- * looked like an encoder problem. It was ours.
66
- */
67
- @Test
68
- fun aGateThatDropsFramesTheSourceSuppliedIsBlamedOnThePacer() {
69
- assertThat(verdict(advertisedFps = 8.0, sinkFps = 14.6, admittedFps = 7.3, wireFps = 7.0))
70
- .isEqualTo(Bound.PACER_SHORT)
71
- }
72
-
73
- @Test
74
- fun aShortSourceIsNotBlamedOnAnythingDownstream() {
75
- assertThat(verdict(sinkFps = 6.0, admittedFps = 6.0, wireFps = 6.0))
76
- .isEqualTo(Bound.SOURCE_SHORT)
77
- }
78
-
79
- /**
80
- * A source a little under the advertise is not the pacer's fault. Scoring the
81
- * gate against the advertise alone would read every slightly slow glasses leg
82
- * as our own bug and send the next fix to the wrong place.
83
- */
84
- @Test
85
- fun thePacerIsNotBlamedForFramesTheSourceNeverSent() {
86
- assertThat(verdict(sinkFps = 13.0, admittedFps = 13.0, wireFps = 13.0))
87
- .isEqualTo(Bound.OK)
88
- }
89
-
90
- /**
91
- * The two verdicts that matter, because they want opposite fixes. Same short
92
- * wire rate, same delivered rate; only CPU and the bitrate headroom differ.
93
- */
94
- @Test
95
- fun aBusyCpuMeansTheEncoderIsTheCeiling() {
96
- assertThat(verdict(wireFps = 8.0, cpuPercent = 92.0)).isEqualTo(Bound.ENCODER_SHORT)
97
- }
98
-
99
- @Test
100
- fun sparecpuAndACollapsedBitrateMeansTheRateControllerIsThrottling() {
101
- assertThat(verdict(wireFps = 8.0, wireBitrateBps = 496_000, cpuPercent = 50.0))
102
- .isEqualTo(Bound.BITRATE_STARVED)
103
- }
104
-
105
- /** Spare CPU but the budget is being spent: the encoder really is the limit. */
106
- @Test
107
- fun sparecpuWithTheBudgetSpentIsStillTheEncoder() {
108
- assertThat(verdict(wireFps = 8.0, wireBitrateBps = 1_450_000, cpuPercent = 50.0))
109
- .isEqualTo(Bound.ENCODER_SHORT)
110
- }
111
-
112
- @Test
113
- fun aMissingWireReportIsNotAVerdict() {
114
- assertThat(verdict(wireFps = null)).isEqualTo(Bound.UNKNOWN)
115
- assertThat(verdict(advertisedFps = 0.0)).isEqualTo(Bound.UNKNOWN)
116
- }
117
-
118
- /** Every bound carries an action, or the line makes the reader open this file. */
119
- @Test
120
- fun everyBoundHasANonEmptyHint() {
121
- for (bound in Bound.entries) {
122
- assertThat(VideoRateVerdict.hint(bound)).isNotBlank()
123
- }
124
- }
125
-
126
- @Test
127
- fun theLinePrintsTheInputsBesideTheVerdict() {
128
- val line = VideoRateVerdict.line(
129
- advertisedFps = 15.0,
130
- sinkFps = 14.6,
131
- admittedFps = 7.3,
132
- wireFps = 7.0,
133
- wireBitrateBps = 496_000,
134
- budgetBps = 1_500_000,
135
- cpuPercent = 61.3,
136
- codecName = "h264 sw",
137
- )
138
- assertThat(line).startsWith("P7 rate bound=PACER_SHORT")
139
- assertThat(line).contains("advertised=15.0")
140
- assertThat(line).contains("sink=14.6")
141
- assertThat(line).contains("admitted=7.3")
142
- assertThat(line).contains("wire=7.0")
143
- assertThat(line).contains("kbps=496")
144
- assertThat(line).contains("budgetKbps=1500")
145
- assertThat(line).contains("cpu=61.3")
146
- assertThat(line).contains("codec=h264_sw")
147
- assertThat(line).contains(VideoRateVerdict.hint(Bound.PACER_SHORT))
148
- }
149
- }
@@ -1,97 +0,0 @@
1
- package com.mentra.acsmeeting.trace
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
-
6
- /**
7
- * Covers the pure formatter. The `Log.i` wrapper is a one-liner and is exercised on device.
8
- */
9
- class SoftApTraceTest {
10
-
11
- @Test
12
- fun `format carries the grep marker`() {
13
- assertThat(SoftApTrace.format("t1", "scoped_network_available", 12))
14
- .contains("SOFTAP_TRACE")
15
- }
16
-
17
- @Test
18
- fun `format includes traceId stage and elapsed`() {
19
- assertThat(SoftApTrace.format("abc123", "ice_selected_pair", 450))
20
- .isEqualTo("[SOFTAP_TRACE] traceId=abc123 stage=ice_selected_pair elapsedMs=450")
21
- }
22
-
23
- @Test
24
- fun `format omits traceId when absent`() {
25
- assertThat(SoftApTrace.format("", "boot", 0))
26
- .isEqualTo("[SOFTAP_TRACE] stage=boot elapsedMs=0")
27
- }
28
-
29
- @Test
30
- fun `format appends fields in order`() {
31
- assertThat(SoftApTrace.format("t", "whip_request", 7, "method" to "POST", "status" to 201))
32
- .endsWith("method=POST status=201")
33
- }
34
-
35
- @Test
36
- fun `sanitize redacts the hotspot password`() {
37
- assertThat(SoftApTrace.sanitize("password", "hunter2")).isEqualTo("<redacted>")
38
- assertThat(SoftApTrace.sanitize("hotspotPassword", "hunter2")).isEqualTo("<redacted>")
39
- assertThat(SoftApTrace.sanitize("psk", "hunter2")).isEqualTo("<redacted>")
40
- assertThat(SoftApTrace.sanitize("passphrase", "hunter2")).isEqualTo("<redacted>")
41
- }
42
-
43
- @Test
44
- fun `sanitize redacts tokens and credentials`() {
45
- assertThat(SoftApTrace.sanitize("token", "eyJhbGciOi")).isEqualTo("<redacted>")
46
- assertThat(SoftApTrace.sanitize("acsToken", "eyJhbGciOi")).isEqualTo("<redacted>")
47
- assertThat(SoftApTrace.sanitize("Authorization", "Bearer x")).isEqualTo("<redacted>")
48
- assertThat(SoftApTrace.sanitize("meetingUrl", "https://teams.microsoft.com/l/x"))
49
- .isEqualTo("<redacted>")
50
- }
51
-
52
- @Test
53
- fun `format never leaks a secret value into the line`() {
54
- val line = SoftApTrace.format(
55
- "t", "hotspot_started", 5,
56
- "ssid" to "MentraLive", "password" to "hunter2",
57
- )
58
- assertThat(line).doesNotContain("hunter2")
59
- assertThat(line).contains("ssid=MentraLive")
60
- assertThat(line).contains("password=<redacted>")
61
- }
62
-
63
- @Test
64
- fun `sanitize keeps the local whip url which is diagnostic`() {
65
- assertThat(SoftApTrace.sanitize("whipUrl", "http://192.168.43.20:8790/whip/abc"))
66
- .isEqualTo("http://192.168.43.20:8790/whip/abc")
67
- }
68
-
69
- @Test
70
- fun `sanitize strips query strings that may carry tokens`() {
71
- assertThat(SoftApTrace.sanitize("playbackUrl", "https://example.com/live?token=secret123"))
72
- .isEqualTo("https://example.com/live?<redacted>")
73
- }
74
-
75
- @Test
76
- fun `sanitize strips userinfo from urls`() {
77
- val result = SoftApTrace.sanitize("endpoint", "https://user:pw@example.com/x")
78
- assertThat(result).doesNotContain("pw@")
79
- assertThat(result).contains("<redacted>@example.com/x")
80
- }
81
-
82
- @Test
83
- fun `sanitize quotes values containing spaces`() {
84
- assertThat(SoftApTrace.sanitize("reason", "two words")).isEqualTo("\"two words\"")
85
- }
86
-
87
- @Test
88
- fun `sanitize renders null and empty`() {
89
- assertThat(SoftApTrace.sanitize("reason", null)).isEqualTo("null")
90
- assertThat(SoftApTrace.sanitize("reason", "")).isEqualTo("\"\"")
91
- }
92
-
93
- @Test
94
- fun `newTraceId produces distinct ids`() {
95
- assertThat(SoftApTrace.newTraceId()).isNotEqualTo(SoftApTrace.newTraceId())
96
- }
97
- }
@@ -1,33 +0,0 @@
1
- package com.mentra.acsmeeting.video
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
-
6
- class FrameGeometryTest {
7
- @Test
8
- fun rotationZeroUsesBufferSize() {
9
- val size = FrameGeometry.packSize(1280, 720, 0)
10
- assertThat(size.width).isEqualTo(1280)
11
- assertThat(size.height).isEqualTo(720)
12
- assertThat(size.rotationNonZero).isFalse()
13
- }
14
-
15
- @Test
16
- fun rotation90KeepsBufferCoordinatesNotRotatedDisplaySize() {
17
- val bufferW = 1280
18
- val bufferH = 720
19
- val rotatedWidth = bufferH
20
- val rotatedHeight = bufferW
21
- val size = FrameGeometry.packSize(bufferW, bufferH, 90)
22
- assertThat(size.width).isEqualTo(bufferW)
23
- assertThat(size.height).isEqualTo(bufferH)
24
- assertThat(size.rotationNonZero).isTrue()
25
- assertThat(size.width).isNotEqualTo(rotatedWidth)
26
- assertThat(size.height).isNotEqualTo(rotatedHeight)
27
- val overrun = I420Packer.packedSize(rotatedWidth, rotatedHeight)
28
- val actual = I420Packer.packedSize(size.width, size.height)
29
- assertThat(overrun).isEqualTo(actual)
30
- val planeIfRotated = rotatedHeight * 1280
31
- assertThat(planeIfRotated).isGreaterThan(bufferW * bufferH)
32
- }
33
- }