@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.
- package/README.md +14 -3
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingModule.kt +10 -10
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingSession.kt +37 -37
- package/android/src/main/java/com/mentra/acsmeeting/audio/UplinkSender.kt +4 -4
- package/android/src/main/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpec.kt +3 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/AcsFrameSender.kt +12 -10
- package/android/src/main/java/com/mentra/acsmeeting/video/I420FormatSpec.kt +2 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/Nv12Packer.kt +2 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/{AcsInvestigationTest.kt → MediaDiagnosticsTest.kt} +10 -10
- package/android/src/test/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpecTest.kt +3 -0
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoQualityTest.kt +2 -2
- package/android/src/test/java/com/mentra/acsmeeting/video/I420FormatSpecTest.kt +2 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/VideoProfileTest.kt +2 -2
- package/build/AcsMeeting.types.d.ts +10 -1
- package/build/AcsMeeting.types.d.ts.map +1 -1
- package/build/AcsMeeting.types.js.map +1 -1
- package/build/AcsMeetingModule.d.ts +2 -2
- package/build/AcsMeetingModule.d.ts.map +1 -1
- package/build/AcsMeetingModule.js.map +1 -1
- package/ios/AcsMeeting.podspec +1 -1
- package/ios/AcsMeetingModule.swift +284 -148
- package/ios/PolicyKit/Package.swift +2 -1
- package/ios/PolicyKit/Sources/AcsAudioPolicy/CallbackOperation.swift +60 -0
- package/ios/PolicyKit/Sources/AcsAudioPolicy/CapturePolicy.swift +5 -0
- package/ios/PolicyKit/Tests/AcsAudioPolicyTests/AcsAudioPolicyTests.swift +1 -0
- package/ios/PolicyKit/Tests/AcsAudioPolicyTests/CallAgentRetirementTests.swift +113 -0
- package/package.json +3 -2
- package/src/AcsMeeting.types.ts +4 -1
- package/src/AcsMeetingModule.ts +2 -2
- package/android/src/androidTest/java/com/mentra/acsmeeting/network/SoftApFeasibilityGateTest.kt +0 -291
- package/android/src/main/java/com/mentra/acsmeeting/network/InternetHold.kt +0 -501
- package/android/src/main/java/com/mentra/acsmeeting/network/Ipv4Prefix.kt +0 -51
- package/android/src/main/java/com/mentra/acsmeeting/network/NetworkFacts.kt +0 -81
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetector.kt +0 -355
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkError.kt +0 -109
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkObserver.kt +0 -160
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkReadiness.kt +0 -103
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpec.kt +0 -53
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkState.kt +0 -127
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedSoftApNetwork.kt +0 -573
- package/android/src/main/java/com/mentra/acsmeeting/source/BitmapFont.kt +0 -99
- package/android/src/main/java/com/mentra/acsmeeting/source/CloudflareWhepSource.kt +0 -487
- package/android/src/main/java/com/mentra/acsmeeting/source/DecodedTrackRelay.kt +0 -147
- package/android/src/main/java/com/mentra/acsmeeting/source/FirstFrameGate.kt +0 -45
- package/android/src/main/java/com/mentra/acsmeeting/source/GlassesMediaSource.kt +0 -143
- package/android/src/main/java/com/mentra/acsmeeting/source/LocalWhipIngestSource.kt +0 -772
- package/android/src/main/java/com/mentra/acsmeeting/source/MediaListeners.kt +0 -57
- package/android/src/main/java/com/mentra/acsmeeting/source/SdpAdapter.kt +0 -17
- package/android/src/main/java/com/mentra/acsmeeting/source/SelectedIcePair.kt +0 -139
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIceDiagnosis.kt +0 -144
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIcePolicy.kt +0 -46
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApSdpGuard.kt +0 -160
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticFrameFactory.kt +0 -205
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticI420Source.kt +0 -214
- package/android/src/main/java/com/mentra/acsmeeting/source/TrackRegistry.kt +0 -33
- package/android/src/main/java/com/mentra/acsmeeting/source/VideoSourceArm.kt +0 -110
- package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestProtocol.kt +0 -207
- package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestServer.kt +0 -356
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/AvSyncProbe.kt +0 -155
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ChromaProbe.kt +0 -78
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineStats.kt +0 -372
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineTicker.kt +0 -79
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ProcessCpu.kt +0 -15
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/RingPercentile.kt +0 -48
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoQuality.kt +0 -178
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoRateVerdict.kt +0 -145
- package/android/src/main/java/com/mentra/acsmeeting/trace/SoftApTrace.kt +0 -143
- package/android/src/main/java/com/mentra/acsmeeting/video/FrameGeometry.kt +0 -22
- package/android/src/main/java/com/mentra/acsmeeting/video/I420Packer.kt +0 -72
- package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldLifecycleTest.kt +0 -102
- package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldTest.kt +0 -148
- package/android/src/test/java/com/mentra/acsmeeting/network/Ipv4PrefixTest.kt +0 -59
- package/android/src/test/java/com/mentra/acsmeeting/network/NetworkFactsTest.kt +0 -59
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedLocalNetworkPermissionTest.kt +0 -35
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetectorTest.kt +0 -165
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkErrorTest.kt +0 -96
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkLifecycleTest.kt +0 -187
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkObserverTest.kt +0 -172
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkReadinessTest.kt +0 -141
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpecTest.kt +0 -69
- package/android/src/test/java/com/mentra/acsmeeting/source/BitmapFontTest.kt +0 -22
- package/android/src/test/java/com/mentra/acsmeeting/source/DecodedTrackRelayTest.kt +0 -41
- package/android/src/test/java/com/mentra/acsmeeting/source/FirstFrameGateTest.kt +0 -60
- package/android/src/test/java/com/mentra/acsmeeting/source/GlassesMediaSourceTest.kt +0 -151
- package/android/src/test/java/com/mentra/acsmeeting/source/I420PlanesTest.kt +0 -67
- package/android/src/test/java/com/mentra/acsmeeting/source/SelectedIcePairTest.kt +0 -165
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIceDiagnosisTest.kt +0 -148
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIcePolicyTest.kt +0 -76
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApSdpGuardTest.kt +0 -275
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticFrameFactoryTest.kt +0 -193
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticI420SourceTest.kt +0 -176
- package/android/src/test/java/com/mentra/acsmeeting/source/TrackRegistryTest.kt +0 -45
- package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestProtocolTest.kt +0 -356
- package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestServerLoopbackTest.kt +0 -411
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/AvSyncProbeTest.kt +0 -83
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ChromaProbeTest.kt +0 -61
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/PipelineStatsTest.kt +0 -322
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ProcessCpuTest.kt +0 -18
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/RingPercentileTest.kt +0 -49
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoRateVerdictTest.kt +0 -149
- package/android/src/test/java/com/mentra/acsmeeting/trace/SoftApTraceTest.kt +0 -97
- package/android/src/test/java/com/mentra/acsmeeting/video/FrameGeometryTest.kt +0 -33
- package/android/src/test/java/com/mentra/acsmeeting/video/I420PackerTest.kt +0 -126
- package/ios/PolicyKit/Sources/AcsAudioPolicy/GlassesMediaSource.swift +0 -68
- package/ios/WhepVideoSource.swift +0 -356
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
package com.mentra.acsmeeting.source
|
|
2
|
-
|
|
3
|
-
import com.mentra.acsmeeting.network.Ipv4Prefix
|
|
4
|
-
import org.assertj.core.api.Assertions.assertThat
|
|
5
|
-
import org.junit.Test
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Pins the candidate classification the SoftAP path depends on.
|
|
9
|
-
*
|
|
10
|
-
* The cases here deliberately mirror
|
|
11
|
-
* `com.mentra.asg_client.io.streaming.config.IcePostPolicyTest` on the glasses. The two
|
|
12
|
-
* implementations cannot share code across the module boundary, and if they disagree the symptom is
|
|
13
|
-
* a call that negotiates and then carries nothing: one side posts an offer it considers ready while
|
|
14
|
-
* the other rejects it, or worse, both accept a candidate pair that cannot actually reach across
|
|
15
|
-
* the hotspot.
|
|
16
|
-
*/
|
|
17
|
-
class SoftApSdpGuardTest {
|
|
18
|
-
|
|
19
|
-
private val hostCandidate =
|
|
20
|
-
"a=candidate:1 1 udp 2122260223 192.168.43.20 51234 typ host generation 0"
|
|
21
|
-
private val srflxCandidate =
|
|
22
|
-
"a=candidate:2 1 udp 1686052607 203.0.113.7 51234 typ srflx raddr 192.168.43.20 rport 51234"
|
|
23
|
-
|
|
24
|
-
private fun sdp(vararg lines: String) =
|
|
25
|
-
(listOf("v=0", "m=video 9 UDP/TLS/RTP/SAVPF 96") + lines).joinToString("\r\n")
|
|
26
|
-
|
|
27
|
-
// -----------------------------------------------------------------
|
|
28
|
-
// Accepting a usable negotiation
|
|
29
|
-
// -----------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
@Test
|
|
32
|
-
fun `an sdp with a hotspot host candidate is accepted`() {
|
|
33
|
-
val verdict = SoftApSdpGuard.inspect(sdp(hostCandidate))
|
|
34
|
-
|
|
35
|
-
assertThat(verdict).isInstanceOf(SoftApSdpGuard.Verdict.Ok::class.java)
|
|
36
|
-
val ok = verdict as SoftApSdpGuard.Verdict.Ok
|
|
37
|
-
assertThat(ok.hostCandidates).hasSize(1)
|
|
38
|
-
assertThat(ok.routableCandidates).isEmpty()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Not a rejection. A stale glasses build may still gather srflx candidates, and dropping the call
|
|
43
|
-
* over it would be worse than a warning when a usable hotspot candidate is also present — but it
|
|
44
|
-
* is reported, because ICE could otherwise select it.
|
|
45
|
-
*/
|
|
46
|
-
@Test
|
|
47
|
-
fun `a stray srflx candidate is reported but does not reject`() {
|
|
48
|
-
val verdict = SoftApSdpGuard.inspect(sdp(hostCandidate, srflxCandidate))
|
|
49
|
-
|
|
50
|
-
val ok = verdict as SoftApSdpGuard.Verdict.Ok
|
|
51
|
-
assertThat(ok.hostCandidates).hasSize(1)
|
|
52
|
-
assertThat(ok.routableCandidates).hasSize(1)
|
|
53
|
-
assertThat(ok.routableCandidates.single()).contains("typ srflx")
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@Test
|
|
57
|
-
fun `any rfc1918 subnet counts, not just the default hotspot range`() {
|
|
58
|
-
// A hotspot on a non-default subnet must not silently fail the gate.
|
|
59
|
-
for (address in listOf("192.168.1.5", "10.0.0.9", "172.16.4.4", "172.31.255.254")) {
|
|
60
|
-
val verdict = SoftApSdpGuard.inspect(
|
|
61
|
-
sdp("a=candidate:1 1 udp 2122260223 $address 51234 typ host generation 0"),
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
assertThat(verdict)
|
|
65
|
-
.describedAs(address)
|
|
66
|
-
.isInstanceOf(SoftApSdpGuard.Verdict.Ok::class.java)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// -----------------------------------------------------------------
|
|
71
|
-
// Rejecting an unusable negotiation
|
|
72
|
-
// -----------------------------------------------------------------
|
|
73
|
-
|
|
74
|
-
@Test
|
|
75
|
-
fun `an sdp with only a srflx candidate is rejected`() {
|
|
76
|
-
val verdict = SoftApSdpGuard.inspect(sdp(srflxCandidate))
|
|
77
|
-
|
|
78
|
-
val rejected = verdict as SoftApSdpGuard.Verdict.Rejected
|
|
79
|
-
assertThat(rejected.code).isEqualTo(SoftApSdpGuard.REASON_ONLY_NON_HOTSPOT)
|
|
80
|
-
assertThat(rejected.detail).contains("typ srflx")
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
@Test
|
|
84
|
-
fun `an sdp with no candidates at all is rejected and says so`() {
|
|
85
|
-
val verdict = SoftApSdpGuard.inspect(sdp())
|
|
86
|
-
|
|
87
|
-
val rejected = verdict as SoftApSdpGuard.Verdict.Rejected
|
|
88
|
-
assertThat(rejected.code).isEqualTo(SoftApSdpGuard.REASON_NO_CANDIDATES)
|
|
89
|
-
assertThat(rejected.detail).isEqualTo("no candidates at all")
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Public and loopback host candidates are unreachable from the glasses. */
|
|
93
|
-
@Test
|
|
94
|
-
fun `a host candidate on a public or loopback address is rejected`() {
|
|
95
|
-
for (address in listOf("203.0.113.7", "127.0.0.1", "8.8.8.8", "172.32.0.1", "192.169.0.1")) {
|
|
96
|
-
val verdict = SoftApSdpGuard.inspect(
|
|
97
|
-
sdp("a=candidate:1 1 udp 2122260223 $address 51234 typ host generation 0"),
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
assertThat(verdict)
|
|
101
|
-
.describedAs(address)
|
|
102
|
-
.isInstanceOf(SoftApSdpGuard.Verdict.Rejected::class.java)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* A link-local address means the interface never got a DHCP lease from the glasses. ICE would
|
|
108
|
-
* gather it and connect to nothing.
|
|
109
|
-
*/
|
|
110
|
-
@Test
|
|
111
|
-
fun `a link local host candidate is rejected`() {
|
|
112
|
-
val verdict = SoftApSdpGuard.inspect(
|
|
113
|
-
sdp("a=candidate:1 1 udp 2122260223 169.254.10.20 51234 typ host generation 0"),
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
assertThat(verdict).isInstanceOf(SoftApSdpGuard.Verdict.Rejected::class.java)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* libwebrtc emits these to hide private addresses from remote peers. The glasses cannot resolve
|
|
121
|
-
* them, so a negotiation carrying only mDNS candidates is broken however healthy it looks.
|
|
122
|
-
*/
|
|
123
|
-
@Test
|
|
124
|
-
fun `an mdns obfuscated candidate is rejected`() {
|
|
125
|
-
val verdict = SoftApSdpGuard.inspect(
|
|
126
|
-
sdp(
|
|
127
|
-
"a=candidate:1 1 udp 2122260223 " +
|
|
128
|
-
"b8e7c1f2-0000-4000-8000-000000000000.local 51234 typ host generation 0",
|
|
129
|
-
),
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
val rejected = verdict as SoftApSdpGuard.Verdict.Rejected
|
|
133
|
-
assertThat(rejected.code).isEqualTo(SoftApSdpGuard.REASON_ONLY_NON_HOTSPOT)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
@Test
|
|
137
|
-
fun `an ipv6 host candidate does not satisfy the gate`() {
|
|
138
|
-
// The SoftAP link is IPv4 only, so an IPv6 candidate cannot carry this media.
|
|
139
|
-
val verdict = SoftApSdpGuard.inspect(
|
|
140
|
-
sdp("a=candidate:1 1 udp 2122260223 fe80::1 51234 typ host generation 0"),
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
assertThat(verdict).isInstanceOf(SoftApSdpGuard.Verdict.Rejected::class.java)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
@Test
|
|
147
|
-
fun `empty and non sdp input is malformed rather than candidate-less`() {
|
|
148
|
-
for (input in listOf(null, "", " ", "v=0\r\no=- 0 0 IN IP4 0.0.0.0")) {
|
|
149
|
-
val verdict = SoftApSdpGuard.inspect(input)
|
|
150
|
-
|
|
151
|
-
assertThat((verdict as SoftApSdpGuard.Verdict.Rejected).code)
|
|
152
|
-
.describedAs(input.orEmpty())
|
|
153
|
-
.isEqualTo(SoftApSdpGuard.REASON_MALFORMED)
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// -----------------------------------------------------------------
|
|
158
|
-
// Parsing details
|
|
159
|
-
// -----------------------------------------------------------------
|
|
160
|
-
|
|
161
|
-
@Test
|
|
162
|
-
fun `candidate extraction strips the attribute prefix and ignores other lines`() {
|
|
163
|
-
val candidates = SoftApSdpGuard.candidateLines(
|
|
164
|
-
sdp("a=mid:0", hostCandidate, "a=sendrecv", srflxCandidate),
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
assertThat(candidates).hasSize(2)
|
|
168
|
-
assertThat(candidates).allSatisfy { assertThat(it).startsWith("candidate:") }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
@Test
|
|
172
|
-
fun `candidate extraction tolerates trailing whitespace and lone newlines`() {
|
|
173
|
-
val candidates = SoftApSdpGuard.candidateLines("m=video 9\n$hostCandidate \n")
|
|
174
|
-
|
|
175
|
-
assertThat(candidates).hasSize(1)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@Test
|
|
179
|
-
fun `private ipv4 boundaries match rfc1918 exactly`() {
|
|
180
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("10.0.0.0")).isTrue()
|
|
181
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("10.255.255.255")).isTrue()
|
|
182
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("172.15.0.1")).isFalse()
|
|
183
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("172.16.0.0")).isTrue()
|
|
184
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("172.31.0.0")).isTrue()
|
|
185
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("172.32.0.0")).isFalse()
|
|
186
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("192.168.0.0")).isTrue()
|
|
187
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4("192.167.0.0")).isFalse()
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
@Test
|
|
191
|
-
fun `malformed addresses are not private`() {
|
|
192
|
-
for (token in listOf("192.168.1", "192.168.1.1.1", "192.168.1.256", "a.b.c.d", "", "host")) {
|
|
193
|
-
assertThat(SoftApSdpGuard.isPrivateIpv4(token)).describedAs(token).isFalse()
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
@Test
|
|
198
|
-
fun `a candidate must be typ host to count`() {
|
|
199
|
-
assertThat(SoftApSdpGuard.isSoftApHostCandidate(hostCandidate.removePrefix("a="))).isTrue()
|
|
200
|
-
assertThat(
|
|
201
|
-
SoftApSdpGuard.isSoftApHostCandidate(
|
|
202
|
-
"candidate:1 1 udp 2122260223 192.168.43.20 51234 typ relay",
|
|
203
|
-
),
|
|
204
|
-
).isFalse()
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
@Test
|
|
208
|
-
fun `the candidate address is read positionally, with or without the a= prefix`() {
|
|
209
|
-
assertThat(SoftApSdpGuard.candidateAddress(hostCandidate)).isEqualTo("192.168.43.20")
|
|
210
|
-
assertThat(
|
|
211
|
-
SoftApSdpGuard.candidateAddress(
|
|
212
|
-
"candidate:3558496604 1 udp 2122260223 10.48.51.202 35664 typ host generation 0",
|
|
213
|
-
),
|
|
214
|
-
).isEqualTo("10.48.51.202")
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** `raddr` would win a scan for anything dotted-quad-shaped and blame the wrong interface. */
|
|
218
|
-
@Test
|
|
219
|
-
fun `a reflexive candidate reports its local address, not its raddr`() {
|
|
220
|
-
assertThat(
|
|
221
|
-
SoftApSdpGuard.candidateAddress(
|
|
222
|
-
"candidate:2 1 udp 1686052607 203.0.113.9 44444 typ srflx raddr 192.168.43.20 rport 51234",
|
|
223
|
-
),
|
|
224
|
-
).isEqualTo("203.0.113.9")
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
@Test
|
|
228
|
-
fun `a line without an address reports none rather than a stray token`() {
|
|
229
|
-
assertThat(SoftApSdpGuard.candidateAddress("a=mid:0")).isNull()
|
|
230
|
-
assertThat(SoftApSdpGuard.candidateAddress("")).isNull()
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// -----------------------------------------------------------------
|
|
234
|
-
// Pinning a cellular-substituted host address back onto the hotspot
|
|
235
|
-
// -----------------------------------------------------------------
|
|
236
|
-
|
|
237
|
-
@Test
|
|
238
|
-
fun `a cellular host address is rewritten to the scoped SoftAP address`() {
|
|
239
|
-
val prefix = Ipv4Prefix("192.168.43.79", 24)
|
|
240
|
-
val original =
|
|
241
|
-
sdp("a=candidate:1 1 udp 2122260223 10.48.51.202 35664 typ host generation 0")
|
|
242
|
-
|
|
243
|
-
val pinned = SoftApSdpGuard.pinHostAddresses(original, "192.168.43.79", prefix)
|
|
244
|
-
|
|
245
|
-
assertThat(pinned.rewritten).containsExactly("10.48.51.202")
|
|
246
|
-
assertThat(pinned.sdp).contains("192.168.43.79 35664 typ host")
|
|
247
|
-
assertThat(pinned.sdp).doesNotContain("10.48.51.202")
|
|
248
|
-
assertThat(SoftApSdpGuard.inspect(pinned.sdp, prefix)).isInstanceOf(SoftApSdpGuard.Verdict.Ok::class.java)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
@Test
|
|
252
|
-
fun `a host address already on the hotspot is left alone`() {
|
|
253
|
-
val prefix = Ipv4Prefix("192.168.43.79", 24)
|
|
254
|
-
val original = sdp(hostCandidate)
|
|
255
|
-
|
|
256
|
-
val pinned = SoftApSdpGuard.pinHostAddresses(original, "192.168.43.79", prefix)
|
|
257
|
-
|
|
258
|
-
assertThat(pinned.rewritten).isEmpty()
|
|
259
|
-
assertThat(pinned.sdp).isEqualTo(original)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
@Test
|
|
263
|
-
fun `a reflexive raddr is not treated as the connection address to pin`() {
|
|
264
|
-
// Field 5 is 203.0.113.7; raddr is already the hotspot. Rewriting raddr would hide a srflx
|
|
265
|
-
// that ICE could still select, and would not fix the address the glasses send to.
|
|
266
|
-
val prefix = Ipv4Prefix("192.168.43.79", 24)
|
|
267
|
-
val original = sdp(srflxCandidate)
|
|
268
|
-
|
|
269
|
-
val pinned = SoftApSdpGuard.pinHostAddresses(original, "192.168.43.79", prefix)
|
|
270
|
-
|
|
271
|
-
assertThat(pinned.rewritten).isEmpty()
|
|
272
|
-
assertThat(pinned.sdp).contains("203.0.113.7")
|
|
273
|
-
assertThat(pinned.sdp).contains("raddr 192.168.43.20")
|
|
274
|
-
}
|
|
275
|
-
}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
package com.mentra.acsmeeting.source
|
|
2
|
-
|
|
3
|
-
import com.mentra.acsmeeting.telemetry.ChromaProbe
|
|
4
|
-
import com.mentra.acsmeeting.video.I420Packer
|
|
5
|
-
import org.assertj.core.api.Assertions.assertThat
|
|
6
|
-
import org.junit.Test
|
|
7
|
-
import java.nio.ByteBuffer
|
|
8
|
-
|
|
9
|
-
class SyntheticFrameFactoryTest {
|
|
10
|
-
@Test
|
|
11
|
-
fun packedLengthMatchesI420() {
|
|
12
|
-
val dest = buffer(WIDTH, HEIGHT)
|
|
13
|
-
SyntheticFrameFactory().write(dest, WIDTH, HEIGHT, 0)
|
|
14
|
-
assertThat(dest.remaining()).isEqualTo(I420Packer.packedSize(WIDTH, HEIGHT))
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Regression for the 1.4 fps wire collapse: a noisy CHEAP background is
|
|
19
|
-
* incompressible and starves the ACS encoder. CHEAP must stay flat.
|
|
20
|
-
*/
|
|
21
|
-
@Test
|
|
22
|
-
fun cheapBackgroundStaysCompressible() {
|
|
23
|
-
val dest = buffer(WIDTH, HEIGHT)
|
|
24
|
-
SyntheticFrameFactory(SyntheticEntropy.CHEAP).write(dest, WIDTH, HEIGHT, 0)
|
|
25
|
-
val y = yBytes(dest)
|
|
26
|
-
var background = 0
|
|
27
|
-
for (row in HEIGHT - 40 until HEIGHT) {
|
|
28
|
-
val rowOff = row * WIDTH
|
|
29
|
-
for (col in 0 until WIDTH) {
|
|
30
|
-
if (y[rowOff + col] == SyntheticFrameFactory.BG_Y) background += 1
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
assertThat(background).isGreaterThan(30 * WIDTH)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@Test
|
|
37
|
-
fun consecutiveCheapFramesDifferInY() {
|
|
38
|
-
val a = buffer(WIDTH, HEIGHT)
|
|
39
|
-
val b = buffer(WIDTH, HEIGHT)
|
|
40
|
-
val factory = SyntheticFrameFactory(SyntheticEntropy.CHEAP)
|
|
41
|
-
factory.write(a, WIDTH, HEIGHT, 0)
|
|
42
|
-
factory.write(b, WIDTH, HEIGHT, 1)
|
|
43
|
-
assertThat(yBytes(a)).isNotEqualTo(yBytes(b))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@Test
|
|
47
|
-
fun cheapIsDeterministicForAGivenIndex() {
|
|
48
|
-
val a = buffer(WIDTH, HEIGHT)
|
|
49
|
-
val b = buffer(WIDTH, HEIGHT)
|
|
50
|
-
SyntheticFrameFactory(SyntheticEntropy.CHEAP).write(a, WIDTH, HEIGHT, 7)
|
|
51
|
-
SyntheticFrameFactory(SyntheticEntropy.CHEAP).write(b, WIDTH, HEIGHT, 7)
|
|
52
|
-
assertThat(copy(a)).isEqualTo(copy(b))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@Test
|
|
56
|
-
fun incrementalCheapMatchesFreshWrite() {
|
|
57
|
-
val stepped = buffer(WIDTH, HEIGHT)
|
|
58
|
-
val fresh = buffer(WIDTH, HEIGHT)
|
|
59
|
-
val factory = SyntheticFrameFactory(SyntheticEntropy.CHEAP)
|
|
60
|
-
factory.write(stepped, WIDTH, HEIGHT, 0)
|
|
61
|
-
factory.write(stepped, WIDTH, HEIGHT, 1)
|
|
62
|
-
SyntheticFrameFactory(SyntheticEntropy.CHEAP).write(fresh, WIDTH, HEIGHT, 1)
|
|
63
|
-
assertThat(copy(stepped)).isEqualTo(copy(fresh))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
@Test
|
|
67
|
-
fun chromaAveragesLandAt128() {
|
|
68
|
-
for (entropy in SyntheticEntropy.entries) {
|
|
69
|
-
val dest = buffer(WIDTH, HEIGHT)
|
|
70
|
-
SyntheticFrameFactory(entropy).write(dest, WIDTH, HEIGHT, 3)
|
|
71
|
-
val sample = ChromaProbe.samplePacked(dest, WIDTH, HEIGHT)
|
|
72
|
-
assertThat(sample.u).isEqualTo(128)
|
|
73
|
-
assertThat(sample.v).isEqualTo(128)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
@Test
|
|
78
|
-
fun motionPansAlmostEveryPixel() {
|
|
79
|
-
val a = buffer(WIDTH, HEIGHT)
|
|
80
|
-
val b = buffer(WIDTH, HEIGHT)
|
|
81
|
-
val factory = SyntheticFrameFactory(SyntheticEntropy.MOTION)
|
|
82
|
-
factory.write(a, WIDTH, HEIGHT, 0)
|
|
83
|
-
factory.write(b, WIDTH, HEIGHT, 1)
|
|
84
|
-
val ya = yBytes(a)
|
|
85
|
-
val yb = yBytes(b)
|
|
86
|
-
var differ = 0
|
|
87
|
-
for (i in ya.indices) if (ya[i] != yb[i]) differ += 1
|
|
88
|
-
assertThat(differ).isGreaterThan(ya.size / 2)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Glasses motion still has neighbors that look alike. A 32×32 patch in the
|
|
93
|
-
* corner must stay a gradient, not snow — that is what lets an I-frame fit
|
|
94
|
-
* the bitrate budget.
|
|
95
|
-
*/
|
|
96
|
-
@Test
|
|
97
|
-
fun motionKeepsSpatialStructure() {
|
|
98
|
-
val dest = buffer(WIDTH, HEIGHT)
|
|
99
|
-
SyntheticFrameFactory(SyntheticEntropy.MOTION).write(dest, WIDTH, HEIGHT, 0)
|
|
100
|
-
val y = yBytes(dest)
|
|
101
|
-
val unique = mutableSetOf<Byte>()
|
|
102
|
-
for (row in HEIGHT - 80 until HEIGHT - 48) {
|
|
103
|
-
val rowOff = row * WIDTH
|
|
104
|
-
for (col in 0 until 32) unique.add(y[rowOff + col])
|
|
105
|
-
}
|
|
106
|
-
assertThat(unique.size).isLessThan(40)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
@Test
|
|
110
|
-
fun motionIsDeterministicForAGivenIndex() {
|
|
111
|
-
val a = buffer(WIDTH, HEIGHT)
|
|
112
|
-
val b = buffer(WIDTH, HEIGHT)
|
|
113
|
-
SyntheticFrameFactory(SyntheticEntropy.MOTION).write(a, WIDTH, HEIGHT, 11)
|
|
114
|
-
SyntheticFrameFactory(SyntheticEntropy.MOTION).write(b, WIDTH, HEIGHT, 11)
|
|
115
|
-
assertThat(copy(a)).isEqualTo(copy(b))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
@Test
|
|
119
|
-
fun noiseRewritesTheFullYPlane() {
|
|
120
|
-
val a = buffer(WIDTH, HEIGHT)
|
|
121
|
-
val b = buffer(WIDTH, HEIGHT)
|
|
122
|
-
val factory = SyntheticFrameFactory(SyntheticEntropy.NOISE)
|
|
123
|
-
factory.write(a, WIDTH, HEIGHT, 0)
|
|
124
|
-
factory.write(b, WIDTH, HEIGHT, 1)
|
|
125
|
-
val ya = yBytes(a)
|
|
126
|
-
val yb = yBytes(b)
|
|
127
|
-
var differ = 0
|
|
128
|
-
for (i in ya.indices) if (ya[i] != yb[i]) differ += 1
|
|
129
|
-
assertThat(differ).isGreaterThan(ya.size / 2)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
@Test
|
|
133
|
-
fun stampPaintsFrameIdAndFpsOnADarkBox() {
|
|
134
|
-
val dest = buffer(WIDTH, HEIGHT)
|
|
135
|
-
SyntheticFrameFactory(SyntheticEntropy.CHEAP).write(dest, WIDTH, HEIGHT, 42, 15.0)
|
|
136
|
-
val y = yBytes(dest)
|
|
137
|
-
val boxX = WIDTH / 16
|
|
138
|
-
val boxY = HEIGHT / 2 - 80
|
|
139
|
-
var dark = 0
|
|
140
|
-
var bright = 0
|
|
141
|
-
for (row in boxY until boxY + 160) {
|
|
142
|
-
val rowOff = row * WIDTH
|
|
143
|
-
for (col in boxX until boxX + 400) {
|
|
144
|
-
val value = y[rowOff + col].toInt() and 0xFF
|
|
145
|
-
if (value <= 20) dark += 1
|
|
146
|
-
if (value >= 220) bright += 1
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
assertThat(dark).isGreaterThan(1_000)
|
|
150
|
-
assertThat(bright).isGreaterThan(200)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
@Test
|
|
154
|
-
fun stampFrameIndexChangesTheOverlay() {
|
|
155
|
-
val a = buffer(WIDTH, HEIGHT)
|
|
156
|
-
val b = buffer(WIDTH, HEIGHT)
|
|
157
|
-
SyntheticFrameFactory(SyntheticEntropy.CHEAP).write(a, WIDTH, HEIGHT, 1, 15.0)
|
|
158
|
-
SyntheticFrameFactory(SyntheticEntropy.CHEAP).write(b, WIDTH, HEIGHT, 2, 15.0)
|
|
159
|
-
assertThat(copy(a)).isNotEqualTo(copy(b))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
@Test
|
|
163
|
-
fun noiseIsDeterministicForAGivenIndex() {
|
|
164
|
-
val a = buffer(WIDTH, HEIGHT)
|
|
165
|
-
val b = buffer(WIDTH, HEIGHT)
|
|
166
|
-
SyntheticFrameFactory(SyntheticEntropy.NOISE).write(a, WIDTH, HEIGHT, 11)
|
|
167
|
-
SyntheticFrameFactory(SyntheticEntropy.NOISE).write(b, WIDTH, HEIGHT, 11)
|
|
168
|
-
assertThat(copy(a)).isEqualTo(copy(b))
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private fun buffer(width: Int, height: Int): ByteBuffer =
|
|
172
|
-
ByteBuffer.allocate(I420Packer.packedSize(width, height))
|
|
173
|
-
|
|
174
|
-
private fun yBytes(src: ByteBuffer): ByteArray {
|
|
175
|
-
val view = src.duplicate()
|
|
176
|
-
view.position(0)
|
|
177
|
-
val y = ByteArray(WIDTH * HEIGHT)
|
|
178
|
-
view.get(y)
|
|
179
|
-
return y
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private fun copy(src: ByteBuffer): ByteArray {
|
|
183
|
-
val view = src.duplicate()
|
|
184
|
-
val out = ByteArray(view.remaining())
|
|
185
|
-
view.get(out)
|
|
186
|
-
return out
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
companion object {
|
|
190
|
-
private const val WIDTH = 1280
|
|
191
|
-
private const val HEIGHT = 720
|
|
192
|
-
}
|
|
193
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
package com.mentra.acsmeeting.source
|
|
2
|
-
|
|
3
|
-
import com.mentra.acsmeeting.telemetry.PipelineStats
|
|
4
|
-
import com.mentra.acsmeeting.video.I420Packer
|
|
5
|
-
import org.assertj.core.api.Assertions.assertThat
|
|
6
|
-
import org.junit.Test
|
|
7
|
-
import java.util.concurrent.atomic.AtomicBoolean
|
|
8
|
-
|
|
9
|
-
class SyntheticI420SourceTest {
|
|
10
|
-
@Test
|
|
11
|
-
fun withheldWhileNotReadyThenEmitsOnceReady() {
|
|
12
|
-
val ready = AtomicBoolean(false)
|
|
13
|
-
val frames = ArrayList<Triple<Int, Int, Int>>()
|
|
14
|
-
val stats = PipelineStats()
|
|
15
|
-
val source = source(stats, ready) { planes ->
|
|
16
|
-
frames.add(Triple(I420Planes.remaining(planes.y) + I420Planes.remaining(planes.u) + I420Planes.remaining(planes.v), planes.width, planes.height))
|
|
17
|
-
}
|
|
18
|
-
source.becomeLive()
|
|
19
|
-
source.setTargetSize(TargetSize(1280, 720))
|
|
20
|
-
|
|
21
|
-
assertThat(source.emitOnce()).isFalse()
|
|
22
|
-
assertThat(frames).isEmpty()
|
|
23
|
-
assertThat(stats.sinkCount()).isEqualTo(0)
|
|
24
|
-
|
|
25
|
-
ready.set(true)
|
|
26
|
-
assertThat(source.emitOnce()).isTrue()
|
|
27
|
-
assertThat(frames).hasSize(1)
|
|
28
|
-
assertThat(frames[0].second).isEqualTo(1280)
|
|
29
|
-
assertThat(frames[0].third).isEqualTo(720)
|
|
30
|
-
assertThat(frames[0].first).isEqualTo(I420Packer.packedSize(1280, 720))
|
|
31
|
-
assertThat(stats.sinkCount()).isEqualTo(1)
|
|
32
|
-
assertThat(source.events.any { it.startsWith("P6 synthetic first-frame") }).isTrue()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@Test
|
|
36
|
-
fun withheldWhileTargetSizeIsNull() {
|
|
37
|
-
val frames = ArrayList<Int>()
|
|
38
|
-
val stats = PipelineStats()
|
|
39
|
-
val source = source(stats, AtomicBoolean(true)) { _ -> frames.add(1) }
|
|
40
|
-
source.becomeLive()
|
|
41
|
-
|
|
42
|
-
assertThat(source.emitOnce()).isFalse()
|
|
43
|
-
assertThat(frames).isEmpty()
|
|
44
|
-
assertThat(stats.sinkCount()).isEqualTo(0)
|
|
45
|
-
|
|
46
|
-
source.setTargetSize(TargetSize(640, 360))
|
|
47
|
-
assertThat(source.emitOnce()).isTrue()
|
|
48
|
-
assertThat(frames).hasSize(1)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@Test
|
|
52
|
-
fun nReadyCallsDeliverNFramesAndHonorResize() {
|
|
53
|
-
val frames = ArrayList<Pair<Int, Int>>()
|
|
54
|
-
val stats = PipelineStats()
|
|
55
|
-
val source = source(stats, AtomicBoolean(true)) { planes -> frames.add(planes.width to planes.height) }
|
|
56
|
-
source.becomeLive()
|
|
57
|
-
source.setTargetSize(TargetSize(1280, 720))
|
|
58
|
-
repeat(4) { source.emitOnce() }
|
|
59
|
-
source.setTargetSize(TargetSize(640, 360))
|
|
60
|
-
source.emitOnce()
|
|
61
|
-
|
|
62
|
-
assertThat(frames).containsExactly(
|
|
63
|
-
1280 to 720,
|
|
64
|
-
1280 to 720,
|
|
65
|
-
1280 to 720,
|
|
66
|
-
1280 to 720,
|
|
67
|
-
640 to 360,
|
|
68
|
-
)
|
|
69
|
-
assertThat(stats.sinkCount()).isEqualTo(5)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@Test
|
|
73
|
-
fun withheldTicksTouchNoCounters() {
|
|
74
|
-
val stats = PipelineStats()
|
|
75
|
-
val source = source(stats, AtomicBoolean(false)) { _ -> }
|
|
76
|
-
source.becomeLive()
|
|
77
|
-
source.setTargetSize(TargetSize(1280, 720))
|
|
78
|
-
repeat(5) { source.emitOnce() }
|
|
79
|
-
assertThat(stats.sinkCount()).isEqualTo(0)
|
|
80
|
-
assertThat(stats.dropCount()).isEqualTo(0)
|
|
81
|
-
assertThat(stats.subCount()).isEqualTo(0)
|
|
82
|
-
assertThat(stats.pack.p95()).isEqualTo("na")
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@Test
|
|
86
|
-
fun readinessFlapProducesOnePauseAndOneResume() {
|
|
87
|
-
val ready = AtomicBoolean(true)
|
|
88
|
-
val source = source(PipelineStats(), ready) { _ -> }
|
|
89
|
-
source.becomeLive()
|
|
90
|
-
source.setTargetSize(TargetSize(1280, 720))
|
|
91
|
-
source.emitOnce()
|
|
92
|
-
ready.set(false)
|
|
93
|
-
source.emitOnce()
|
|
94
|
-
ready.set(true)
|
|
95
|
-
source.emitOnce()
|
|
96
|
-
|
|
97
|
-
val pauses = source.events.filter { it.contains("paused") }
|
|
98
|
-
val resumes = source.events.filter { it.contains("resumed") }
|
|
99
|
-
assertThat(pauses).hasSize(1)
|
|
100
|
-
assertThat(resumes).hasSize(1)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@Test
|
|
104
|
-
fun stopEmitsNothingAndRestartResetsIndex() {
|
|
105
|
-
val indexes = ArrayList<Int>()
|
|
106
|
-
val ready = AtomicBoolean(true)
|
|
107
|
-
val source = source(PipelineStats(), ready) { planes ->
|
|
108
|
-
indexes.add(yCorner(planes))
|
|
109
|
-
}
|
|
110
|
-
source.becomeLive()
|
|
111
|
-
source.setTargetSize(TargetSize(64, 64))
|
|
112
|
-
source.emitOnce()
|
|
113
|
-
source.emitOnce()
|
|
114
|
-
source.stop()
|
|
115
|
-
assertThat(source.state).isEqualTo(SourceState.IDLE)
|
|
116
|
-
assertThat(source.emitOnce()).isFalse()
|
|
117
|
-
|
|
118
|
-
source.becomeLive()
|
|
119
|
-
source.setTargetSize(TargetSize(64, 64))
|
|
120
|
-
source.emitOnce()
|
|
121
|
-
assertThat(indexes).hasSize(3)
|
|
122
|
-
assertThat(indexes[0]).isEqualTo(indexes[2])
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
@Test
|
|
126
|
-
fun restartLeavesSourceLive() {
|
|
127
|
-
val source = source(PipelineStats(), AtomicBoolean(false)) { _ -> }
|
|
128
|
-
source.start(SourceConfig("ignored", SourceKind.DIRECT))
|
|
129
|
-
source.restart(SourceConfig("ignored-2", SourceKind.DIRECT))
|
|
130
|
-
assertThat(source.state).isEqualTo(SourceState.LIVE)
|
|
131
|
-
source.stop()
|
|
132
|
-
assertThat(source.state).isEqualTo(SourceState.IDLE)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
@Test
|
|
136
|
-
fun pcmListenerIsNeverInvoked() {
|
|
137
|
-
val source = SyntheticI420Source(
|
|
138
|
-
video = { _ -> },
|
|
139
|
-
stats = PipelineStats(),
|
|
140
|
-
isReady = { true },
|
|
141
|
-
)
|
|
142
|
-
source.setPcmDeliveryEnabled(true)
|
|
143
|
-
assertThat(source.pcmDeliveryEnabled()).isTrue()
|
|
144
|
-
source.setPcmDeliveryEnabled(false)
|
|
145
|
-
assertThat(source.pcmDeliveryEnabled()).isFalse()
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private fun source(
|
|
149
|
-
stats: PipelineStats,
|
|
150
|
-
ready: AtomicBoolean,
|
|
151
|
-
video: VideoFrameListener,
|
|
152
|
-
): SyntheticI420Source = SyntheticI420Source(
|
|
153
|
-
video = video,
|
|
154
|
-
stats = stats,
|
|
155
|
-
isReady = { ready.get() },
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
@Test
|
|
159
|
-
fun emitsTightStridePlanesAliasingThePackedBuffer() {
|
|
160
|
-
val seen = ArrayList<I420Planes>()
|
|
161
|
-
val source = source(PipelineStats(), AtomicBoolean(true)) { seen.add(it) }
|
|
162
|
-
source.becomeLive()
|
|
163
|
-
source.setTargetSize(TargetSize(64, 64))
|
|
164
|
-
assertThat(source.emitOnce()).isTrue()
|
|
165
|
-
val planes = seen.single()
|
|
166
|
-
assertThat(planes.isTight()).isTrue()
|
|
167
|
-
assertThat(planes.retain == null).isTrue()
|
|
168
|
-
assertThat(planes.release == null).isTrue()
|
|
169
|
-
assertThat(I420Planes.remaining(planes.y)).isEqualTo(64 * 64)
|
|
170
|
-
assertThat(I420Planes.remaining(planes.u)).isEqualTo(32 * 32)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private fun yCorner(planes: I420Planes): Int {
|
|
174
|
-
return planes.y.duplicate().get(0).toInt() and 0xFF
|
|
175
|
-
}
|
|
176
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
package com.mentra.acsmeeting.source
|
|
2
|
-
|
|
3
|
-
import org.assertj.core.api.Assertions.assertThat
|
|
4
|
-
import org.junit.Test
|
|
5
|
-
|
|
6
|
-
class TrackRegistryTest {
|
|
7
|
-
@Test
|
|
8
|
-
fun threeClaimsOfOneIdYieldTrueFalseFalse() {
|
|
9
|
-
val registry = TrackRegistry()
|
|
10
|
-
assertThat(registry.claim("track-a")).isTrue()
|
|
11
|
-
assertThat(registry.claim("track-a")).isFalse()
|
|
12
|
-
assertThat(registry.claim("track-a")).isFalse()
|
|
13
|
-
assertThat(registry.size()).isEqualTo(1)
|
|
14
|
-
assertThat(registry.skipped()).isEqualTo(2)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Test
|
|
18
|
-
fun distinctIdsEachClaimOnce() {
|
|
19
|
-
val registry = TrackRegistry()
|
|
20
|
-
assertThat(registry.claim("video")).isTrue()
|
|
21
|
-
assertThat(registry.claim("audio")).isTrue()
|
|
22
|
-
assertThat(registry.size()).isEqualTo(2)
|
|
23
|
-
assertThat(registry.skipped()).isEqualTo(0)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@Test
|
|
27
|
-
fun resetMakesTheSameIdClaimableAgain() {
|
|
28
|
-
val registry = TrackRegistry()
|
|
29
|
-
assertThat(registry.claim("track-a")).isTrue()
|
|
30
|
-
registry.reset()
|
|
31
|
-
assertThat(registry.size()).isEqualTo(0)
|
|
32
|
-
assertThat(registry.skipped()).isEqualTo(0)
|
|
33
|
-
assertThat(registry.claim("track-a")).isTrue()
|
|
34
|
-
assertThat(registry.size()).isEqualTo(1)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@Test
|
|
38
|
-
fun softapWhipReconnectCanReclaimVideo0() {
|
|
39
|
-
val registry = TrackRegistry()
|
|
40
|
-
assertThat(registry.claim("video0")).isTrue()
|
|
41
|
-
assertThat(registry.claim("video0")).isFalse()
|
|
42
|
-
registry.reset()
|
|
43
|
-
assertThat(registry.claim("video0")).isTrue()
|
|
44
|
-
}
|
|
45
|
-
}
|