@siteed/audio-studio 3.2.0-beta.1 → 3.2.0
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/CHANGELOG.md +356 -5
- package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +306 -94
- package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +39 -6
- package/build/cjs/errors/AudioStreamError.js +9 -0
- package/build/cjs/errors/AudioStreamError.js.map +1 -1
- package/build/cjs/errors/AudioStreamError.test.js +22 -1
- package/build/cjs/errors/AudioStreamError.test.js.map +1 -1
- package/build/cjs/streamAudioData.js +99 -32
- package/build/cjs/streamAudioData.js.map +1 -1
- package/build/cjs/utils/audioProcessing.js +14 -10
- package/build/cjs/utils/audioProcessing.js.map +1 -1
- package/build/esm/errors/AudioStreamError.js +9 -0
- package/build/esm/errors/AudioStreamError.js.map +1 -1
- package/build/esm/errors/AudioStreamError.test.js +22 -1
- package/build/esm/errors/AudioStreamError.test.js.map +1 -1
- package/build/esm/streamAudioData.js +99 -32
- package/build/esm/streamAudioData.js.map +1 -1
- package/build/esm/utils/audioProcessing.js +14 -10
- package/build/esm/utils/audioProcessing.js.map +1 -1
- package/build/types/errors/AudioStreamError.d.ts.map +1 -1
- package/build/types/streamAudioData.d.ts +5 -0
- package/build/types/streamAudioData.d.ts.map +1 -1
- package/build/types/utils/audioProcessing.d.ts +2 -2
- package/build/types/utils/audioProcessing.d.ts.map +1 -1
- package/ios/AudioStreamDecoder.swift +191 -100
- package/ios/AudioStudioModule.swift +48 -9
- package/package.json +163 -146
- package/scripts/README.md +58 -0
- package/src/errors/AudioStreamError.test.ts +29 -2
- package/src/errors/AudioStreamError.ts +14 -0
- package/src/streamAudioData.ts +146 -42
- package/src/utils/audioProcessing.ts +25 -14
- package/android/src/androidTest/assets/chorus.wav +0 -0
- package/android/src/androidTest/assets/jfk.wav +0 -0
- package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
- package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +0 -190
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +0 -197
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +0 -487
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +0 -250
- package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +0 -186
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +0 -324
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +0 -253
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +0 -218
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +0 -120
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +0 -345
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +0 -340
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +0 -252
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +0 -95
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +0 -43
- package/android/src/test/java/net/siteed/audiostudio/AndroidCallStateTest.kt +0 -37
- package/android/src/test/java/net/siteed/audiostudio/AndroidEventEmitterTest.kt +0 -28
- package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +0 -279
- package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +0 -249
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +0 -151
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +0 -273
- package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +0 -140
- package/android/src/test/java/net/siteed/audiostudio/InterruptionAutoResumePolicyTest.kt +0 -49
- package/android/src/test/resources/chorus.wav +0 -0
- package/android/src/test/resources/generate_test_audio.py +0 -94
- package/android/src/test/resources/jfk.wav +0 -0
- package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
- package/android/src/test/resources/recorder_hello_world.wav +0 -0
- package/ios/AudioStudioTests/AudioFileHandlerTests.swift +0 -338
- package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +0 -331
- package/ios/AudioStudioTests/AudioStreamDecoderTests.swift +0 -128
- package/ios/AudioStudioTests/AudioTestHelpers.swift +0 -130
- package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +0 -334
- package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +0 -105
- package/ios/AudioStudioTests/Info.plist +0 -22
- package/ios/AudioStudioTests/README.md +0 -39
- package/ios/AudioStudioTests/SimpleAudioTest.swift +0 -98
- package/ios/AudioStudioTests/TestAudioGenerator.swift +0 -75
- package/ios/tests/README.md +0 -41
- package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
- package/ios/tests/integration/buffer_duration_test.swift +0 -185
- package/ios/tests/integration/compressed_only_output_test.swift +0 -271
- package/ios/tests/integration/output_control_test.swift +0 -322
- package/ios/tests/integration/run_integration_tests.sh +0 -37
- package/ios/tests/opus_support_test_macos.swift +0 -154
- package/ios/tests/standalone/audio_processing_test.swift +0 -144
- package/ios/tests/standalone/audio_recording_test.swift +0 -277
- package/ios/tests/standalone/audio_streaming_test.swift +0 -249
- package/ios/tests/standalone/standalone_test.swift +0 -144
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio.integration
|
|
2
|
-
|
|
3
|
-
import android.media.AudioManager
|
|
4
|
-
import android.content.Context
|
|
5
|
-
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
6
|
-
import androidx.test.platform.app.InstrumentationRegistry
|
|
7
|
-
import org.junit.Test
|
|
8
|
-
import org.junit.Assert.*
|
|
9
|
-
import org.junit.Before
|
|
10
|
-
import org.junit.After
|
|
11
|
-
import org.junit.runner.RunWith
|
|
12
|
-
import net.siteed.audiostudio.RecordingConfig
|
|
13
|
-
import java.io.File
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Integration tests for audio focus strategy functionality.
|
|
17
|
-
* These tests run on actual Android devices/emulators to validate that
|
|
18
|
-
* audio focus strategies work correctly in real scenarios.
|
|
19
|
-
*/
|
|
20
|
-
@RunWith(AndroidJUnit4::class)
|
|
21
|
-
class AudioFocusStrategyIntegrationTest {
|
|
22
|
-
|
|
23
|
-
private lateinit var context: Context
|
|
24
|
-
private lateinit var audioManager: AudioManager
|
|
25
|
-
private lateinit var filesDir: File
|
|
26
|
-
|
|
27
|
-
@Before
|
|
28
|
-
fun setUp() {
|
|
29
|
-
context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
30
|
-
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
31
|
-
filesDir = context.filesDir
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@After
|
|
35
|
-
fun tearDown() {
|
|
36
|
-
// Clean up any test files
|
|
37
|
-
val testFiles = filesDir.listFiles { _, name ->
|
|
38
|
-
name.startsWith("test_audio_focus_")
|
|
39
|
-
}
|
|
40
|
-
testFiles?.forEach { it.delete() }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@Test
|
|
44
|
-
fun testRecordingConfigWithBackgroundStrategy() {
|
|
45
|
-
val options = mapOf(
|
|
46
|
-
"sampleRate" to 44100,
|
|
47
|
-
"channels" to 1,
|
|
48
|
-
"encoding" to "pcm_16bit",
|
|
49
|
-
"keepAwake" to true,
|
|
50
|
-
"autoResumeAfterInterruption" to true,
|
|
51
|
-
"android" to mapOf(
|
|
52
|
-
"audioFocusStrategy" to "background"
|
|
53
|
-
)
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
val result = RecordingConfig.fromMap(options)
|
|
57
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
58
|
-
|
|
59
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
60
|
-
|
|
61
|
-
// Verify audio focus strategy configuration
|
|
62
|
-
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
63
|
-
assertTrue("keepAwake should be true for background recording", config.keepAwake)
|
|
64
|
-
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
65
|
-
|
|
66
|
-
// Verify audio format is properly configured
|
|
67
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
68
|
-
assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@Test
|
|
72
|
-
fun testRecordingConfigWithInteractiveStrategy() {
|
|
73
|
-
val options = mapOf(
|
|
74
|
-
"sampleRate" to 44100,
|
|
75
|
-
"channels" to 1,
|
|
76
|
-
"encoding" to "pcm_16bit",
|
|
77
|
-
"keepAwake" to false,
|
|
78
|
-
"autoResumeAfterInterruption" to true,
|
|
79
|
-
"android" to mapOf(
|
|
80
|
-
"audioFocusStrategy" to "interactive"
|
|
81
|
-
)
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
val result = RecordingConfig.fromMap(options)
|
|
85
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
86
|
-
|
|
87
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
88
|
-
|
|
89
|
-
// Verify audio focus strategy configuration
|
|
90
|
-
assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
|
|
91
|
-
assertFalse("keepAwake should be false for interactive recording", config.keepAwake)
|
|
92
|
-
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
93
|
-
|
|
94
|
-
// Verify audio format is properly configured
|
|
95
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
96
|
-
assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@Test
|
|
100
|
-
fun testRecordingConfigWithCommunicationStrategy() {
|
|
101
|
-
val options = mapOf(
|
|
102
|
-
"sampleRate" to 16000, // Common speech sample rate
|
|
103
|
-
"channels" to 1,
|
|
104
|
-
"encoding" to "pcm_16bit",
|
|
105
|
-
"keepAwake" to false,
|
|
106
|
-
"autoResumeAfterInterruption" to true,
|
|
107
|
-
"android" to mapOf(
|
|
108
|
-
"audioFocusStrategy" to "communication"
|
|
109
|
-
)
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
val result = RecordingConfig.fromMap(options)
|
|
113
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
114
|
-
|
|
115
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
116
|
-
|
|
117
|
-
// Verify audio focus strategy configuration
|
|
118
|
-
assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
|
|
119
|
-
assertEquals("Sample rate should be 16000 for speech", 16000, config.sampleRate)
|
|
120
|
-
assertFalse("keepAwake should be false", config.keepAwake)
|
|
121
|
-
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
122
|
-
|
|
123
|
-
// Verify audio format is properly configured
|
|
124
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
125
|
-
assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
@Test
|
|
129
|
-
fun testRecordingConfigWithNoneStrategy() {
|
|
130
|
-
val options = mapOf(
|
|
131
|
-
"sampleRate" to 44100,
|
|
132
|
-
"channels" to 1,
|
|
133
|
-
"encoding" to "pcm_16bit",
|
|
134
|
-
"keepAwake" to false,
|
|
135
|
-
"android" to mapOf(
|
|
136
|
-
"audioFocusStrategy" to "none"
|
|
137
|
-
)
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
val result = RecordingConfig.fromMap(options)
|
|
141
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
142
|
-
|
|
143
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
144
|
-
|
|
145
|
-
// Verify audio focus strategy configuration
|
|
146
|
-
assertEquals("Audio focus strategy should be none", "none", config.audioFocusStrategy)
|
|
147
|
-
|
|
148
|
-
// Verify audio format is properly configured
|
|
149
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
150
|
-
assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
@Test
|
|
154
|
-
fun testStrategyOverrideBehavior() {
|
|
155
|
-
val options = mapOf(
|
|
156
|
-
"sampleRate" to 44100,
|
|
157
|
-
"channels" to 1,
|
|
158
|
-
"encoding" to "pcm_16bit",
|
|
159
|
-
"keepAwake" to true, // This would normally default to background
|
|
160
|
-
"android" to mapOf(
|
|
161
|
-
"audioFocusStrategy" to "communication" // But we override to communication
|
|
162
|
-
)
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
val result = RecordingConfig.fromMap(options)
|
|
166
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
167
|
-
|
|
168
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
169
|
-
|
|
170
|
-
// Verify that explicit strategy overrides keepAwake defaults
|
|
171
|
-
assertEquals("Audio focus strategy should be communication (overriding keepAwake default)", "communication", config.audioFocusStrategy)
|
|
172
|
-
assertTrue("keepAwake should still be true", config.keepAwake)
|
|
173
|
-
|
|
174
|
-
// Verify audio format is properly configured
|
|
175
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@Test
|
|
179
|
-
fun testAudioFocusStrategyWithCompression() {
|
|
180
|
-
val options = mapOf(
|
|
181
|
-
"sampleRate" to 44100,
|
|
182
|
-
"channels" to 1,
|
|
183
|
-
"encoding" to "pcm_16bit",
|
|
184
|
-
"android" to mapOf(
|
|
185
|
-
"audioFocusStrategy" to "background"
|
|
186
|
-
),
|
|
187
|
-
"output" to mapOf(
|
|
188
|
-
"compressed" to mapOf(
|
|
189
|
-
"enabled" to true,
|
|
190
|
-
"format" to "aac",
|
|
191
|
-
"bitrate" to 128000
|
|
192
|
-
)
|
|
193
|
-
)
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
val result = RecordingConfig.fromMap(options)
|
|
197
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
198
|
-
|
|
199
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
200
|
-
|
|
201
|
-
// Verify audio focus strategy works with compression
|
|
202
|
-
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
203
|
-
assertTrue("Compressed output should be enabled", config.output.compressed.enabled)
|
|
204
|
-
assertEquals("Compression format should be aac", "aac", config.output.compressed.format)
|
|
205
|
-
assertEquals("Bitrate should be 128000", 128000, config.output.compressed.bitrate)
|
|
206
|
-
|
|
207
|
-
// Verify audio format is properly configured
|
|
208
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
209
|
-
assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
@Test
|
|
213
|
-
fun testAudioFocusStrategyWithNotifications() {
|
|
214
|
-
val options = mapOf(
|
|
215
|
-
"sampleRate" to 44100,
|
|
216
|
-
"channels" to 1,
|
|
217
|
-
"encoding" to "pcm_16bit",
|
|
218
|
-
"showNotification" to true,
|
|
219
|
-
"showWaveformInNotification" to true,
|
|
220
|
-
"android" to mapOf(
|
|
221
|
-
"audioFocusStrategy" to "background"
|
|
222
|
-
),
|
|
223
|
-
"notification" to mapOf(
|
|
224
|
-
"title" to "Recording Audio",
|
|
225
|
-
"text" to "Background recording in progress",
|
|
226
|
-
"icon" to "ic_mic"
|
|
227
|
-
)
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
val result = RecordingConfig.fromMap(options)
|
|
231
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
232
|
-
|
|
233
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
234
|
-
|
|
235
|
-
// Verify audio focus strategy works with notifications
|
|
236
|
-
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
237
|
-
assertTrue("Notifications should be enabled", config.showNotification)
|
|
238
|
-
assertTrue("Waveform in notification should be enabled", config.showWaveformInNotification)
|
|
239
|
-
assertEquals("Notification title should match", "Recording Audio", config.notification.title)
|
|
240
|
-
assertEquals("Notification text should match", "Background recording in progress", config.notification.text)
|
|
241
|
-
assertEquals("Notification icon should match", "ic_mic", config.notification.icon)
|
|
242
|
-
|
|
243
|
-
// Verify audio format is properly configured
|
|
244
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
245
|
-
assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
@Test
|
|
249
|
-
fun testAudioFocusStrategyValidation() {
|
|
250
|
-
// Test all valid strategies
|
|
251
|
-
val strategies = listOf("background", "interactive", "communication", "none")
|
|
252
|
-
|
|
253
|
-
for (strategy in strategies) {
|
|
254
|
-
val options = mapOf(
|
|
255
|
-
"sampleRate" to 44100,
|
|
256
|
-
"channels" to 1,
|
|
257
|
-
"encoding" to "pcm_16bit",
|
|
258
|
-
"android" to mapOf(
|
|
259
|
-
"audioFocusStrategy" to strategy
|
|
260
|
-
)
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
val result = RecordingConfig.fromMap(options)
|
|
264
|
-
assertTrue("Config creation should succeed for strategy: $strategy", result.isSuccess)
|
|
265
|
-
|
|
266
|
-
val (config, _) = result.getOrThrow()
|
|
267
|
-
assertEquals("Audio focus strategy should be $strategy", strategy, config.audioFocusStrategy)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
@Test
|
|
272
|
-
fun testInvalidAudioFocusStrategyHandling() {
|
|
273
|
-
val options = mapOf(
|
|
274
|
-
"sampleRate" to 44100,
|
|
275
|
-
"channels" to 1,
|
|
276
|
-
"encoding" to "pcm_16bit",
|
|
277
|
-
"android" to mapOf(
|
|
278
|
-
"audioFocusStrategy" to "invalid_strategy"
|
|
279
|
-
)
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
val result = RecordingConfig.fromMap(options)
|
|
283
|
-
assertTrue("Config creation should succeed even with invalid strategy", result.isSuccess)
|
|
284
|
-
|
|
285
|
-
val (config, _) = result.getOrThrow()
|
|
286
|
-
assertEquals("Invalid strategy should be preserved", "invalid_strategy", config.audioFocusStrategy)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
@Test
|
|
290
|
-
fun testCompleteAudioFocusConfiguration() {
|
|
291
|
-
val options = mapOf(
|
|
292
|
-
"sampleRate" to 44100,
|
|
293
|
-
"channels" to 1,
|
|
294
|
-
"encoding" to "pcm_16bit",
|
|
295
|
-
"keepAwake" to true,
|
|
296
|
-
"autoResumeAfterInterruption" to true,
|
|
297
|
-
"showNotification" to true,
|
|
298
|
-
"showWaveformInNotification" to false,
|
|
299
|
-
"enableProcessing" to false,
|
|
300
|
-
"android" to mapOf(
|
|
301
|
-
"audioFocusStrategy" to "communication"
|
|
302
|
-
),
|
|
303
|
-
"notification" to mapOf(
|
|
304
|
-
"title" to "Voice Call Recording",
|
|
305
|
-
"text" to "Call in progress",
|
|
306
|
-
"icon" to "ic_call"
|
|
307
|
-
)
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
val result = RecordingConfig.fromMap(options)
|
|
311
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
312
|
-
|
|
313
|
-
val (config, audioFormat) = result.getOrThrow()
|
|
314
|
-
|
|
315
|
-
// Verify complete configuration
|
|
316
|
-
assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
|
|
317
|
-
assertEquals("Sample rate should be 44100", 44100, config.sampleRate)
|
|
318
|
-
assertEquals("Channels should be 1", 1, config.channels)
|
|
319
|
-
assertEquals("Encoding should be pcm_16bit", "pcm_16bit", config.encoding)
|
|
320
|
-
assertTrue("keepAwake should be true", config.keepAwake)
|
|
321
|
-
assertFalse("showWaveformInNotification should be false", config.showWaveformInNotification)
|
|
322
|
-
assertTrue("showNotification should be true", config.showNotification)
|
|
323
|
-
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
324
|
-
assertFalse("enableProcessing should be false", config.enableProcessing)
|
|
325
|
-
assertEquals("Notification title should match", "Voice Call Recording", config.notification.title)
|
|
326
|
-
assertEquals("Notification text should match", "Call in progress", config.notification.text)
|
|
327
|
-
|
|
328
|
-
// Verify audio format is properly configured
|
|
329
|
-
assertNotNull("Audio format should be created", audioFormat)
|
|
330
|
-
assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio.integration
|
|
2
|
-
|
|
3
|
-
import android.media.AudioFormat
|
|
4
|
-
import android.media.AudioRecord
|
|
5
|
-
import android.media.MediaRecorder
|
|
6
|
-
import android.os.Build
|
|
7
|
-
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
8
|
-
import androidx.test.platform.app.InstrumentationRegistry
|
|
9
|
-
import org.junit.After
|
|
10
|
-
import org.junit.Before
|
|
11
|
-
import org.junit.Test
|
|
12
|
-
import org.junit.runner.RunWith
|
|
13
|
-
import java.util.concurrent.CountDownLatch
|
|
14
|
-
import java.util.concurrent.TimeUnit
|
|
15
|
-
import kotlin.math.abs
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Integration test for Buffer Duration feature
|
|
19
|
-
* This tests the ACTUAL behavior of Android AudioRecord with different buffer sizes
|
|
20
|
-
*/
|
|
21
|
-
@RunWith(AndroidJUnit4::class)
|
|
22
|
-
class BufferDurationIntegrationTest {
|
|
23
|
-
|
|
24
|
-
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
25
|
-
private val results = mutableListOf<TestResult>()
|
|
26
|
-
private var audioRecord: AudioRecord? = null
|
|
27
|
-
|
|
28
|
-
data class TestResult(
|
|
29
|
-
val name: String,
|
|
30
|
-
val passed: Boolean,
|
|
31
|
-
val message: String
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
@Before
|
|
35
|
-
fun setup() {
|
|
36
|
-
println("🧪 Buffer Duration Integration Test")
|
|
37
|
-
println("===================================\n")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@After
|
|
41
|
-
fun tearDown() {
|
|
42
|
-
audioRecord?.release()
|
|
43
|
-
printResults()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@Test
|
|
47
|
-
fun testDefaultBufferSize() {
|
|
48
|
-
println("Test 1: Default Buffer Size")
|
|
49
|
-
println("---------------------------")
|
|
50
|
-
|
|
51
|
-
val sampleRate = 48000
|
|
52
|
-
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
|
53
|
-
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
|
54
|
-
|
|
55
|
-
// Get minimum buffer size
|
|
56
|
-
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
|
57
|
-
println("✓ Android minimum buffer size: $minBufferSize bytes")
|
|
58
|
-
|
|
59
|
-
// Calculate frames from bytes (16-bit = 2 bytes per sample)
|
|
60
|
-
val minFrames = minBufferSize / 2
|
|
61
|
-
println("✓ Minimum frames: $minFrames")
|
|
62
|
-
|
|
63
|
-
// Test with default 1024 frames (2048 bytes)
|
|
64
|
-
val requestedBytes = 1024 * 2
|
|
65
|
-
val actualBufferSize = if (requestedBytes < minBufferSize) minBufferSize else requestedBytes
|
|
66
|
-
|
|
67
|
-
audioRecord = AudioRecord(
|
|
68
|
-
MediaRecorder.AudioSource.MIC,
|
|
69
|
-
sampleRate,
|
|
70
|
-
channelConfig,
|
|
71
|
-
audioFormat,
|
|
72
|
-
actualBufferSize
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
val state = audioRecord?.state
|
|
76
|
-
val passed = state == AudioRecord.STATE_INITIALIZED
|
|
77
|
-
|
|
78
|
-
results.add(TestResult(
|
|
79
|
-
name = "Default Buffer Size",
|
|
80
|
-
passed = passed,
|
|
81
|
-
message = "Requested: 1024 frames, Min required: $minFrames frames, State: ${if (passed) "INITIALIZED" else "UNINITIALIZED"}"
|
|
82
|
-
))
|
|
83
|
-
|
|
84
|
-
println("✓ Requested: 1024 frames (${requestedBytes} bytes)")
|
|
85
|
-
println("✓ Actual buffer: ${actualBufferSize / 2} frames ($actualBufferSize bytes)")
|
|
86
|
-
println("✓ Initialization: ${if (passed) "SUCCESS" else "FAILED"}\n")
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@Test
|
|
90
|
-
fun testCustomBufferSizes() {
|
|
91
|
-
println("Test 2: Custom Buffer Sizes")
|
|
92
|
-
println("---------------------------")
|
|
93
|
-
|
|
94
|
-
val sampleRate = 48000
|
|
95
|
-
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
|
96
|
-
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
|
97
|
-
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
|
98
|
-
|
|
99
|
-
val testCases = listOf(
|
|
100
|
-
0.01 to "10ms",
|
|
101
|
-
0.05 to "50ms",
|
|
102
|
-
0.1 to "100ms",
|
|
103
|
-
0.2 to "200ms",
|
|
104
|
-
0.5 to "500ms"
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
for ((duration, name) in testCases) {
|
|
108
|
-
val requestedFrames = (duration * sampleRate).toInt()
|
|
109
|
-
val requestedBytes = requestedFrames * 2 // 16-bit = 2 bytes
|
|
110
|
-
val actualBufferSize = if (requestedBytes < minBufferSize) minBufferSize else requestedBytes
|
|
111
|
-
|
|
112
|
-
audioRecord?.release()
|
|
113
|
-
audioRecord = AudioRecord(
|
|
114
|
-
MediaRecorder.AudioSource.MIC,
|
|
115
|
-
sampleRate,
|
|
116
|
-
channelConfig,
|
|
117
|
-
audioFormat,
|
|
118
|
-
actualBufferSize
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
val state = audioRecord?.state
|
|
122
|
-
val passed = state == AudioRecord.STATE_INITIALIZED
|
|
123
|
-
|
|
124
|
-
// Test actual read behavior
|
|
125
|
-
if (passed) {
|
|
126
|
-
audioRecord?.startRecording()
|
|
127
|
-
val buffer = ByteArray(requestedBytes)
|
|
128
|
-
val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: -1
|
|
129
|
-
audioRecord?.stop()
|
|
130
|
-
|
|
131
|
-
val framesRead = if (bytesRead > 0) bytesRead / 2 else 0
|
|
132
|
-
|
|
133
|
-
results.add(TestResult(
|
|
134
|
-
name = "Buffer $name",
|
|
135
|
-
passed = bytesRead > 0,
|
|
136
|
-
message = "Requested: $requestedFrames frames, Read: $framesRead frames"
|
|
137
|
-
))
|
|
138
|
-
|
|
139
|
-
println(" $name: Requested $requestedFrames → Read $framesRead frames")
|
|
140
|
-
} else {
|
|
141
|
-
results.add(TestResult(
|
|
142
|
-
name = "Buffer $name",
|
|
143
|
-
passed = false,
|
|
144
|
-
message = "Failed to initialize AudioRecord"
|
|
145
|
-
))
|
|
146
|
-
println(" $name: Failed to initialize")
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
println()
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
@Test
|
|
153
|
-
fun testBufferSizeLimits() {
|
|
154
|
-
println("Test 3: Buffer Size Limits")
|
|
155
|
-
println("--------------------------")
|
|
156
|
-
|
|
157
|
-
val sampleRate = 48000
|
|
158
|
-
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
|
159
|
-
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
|
160
|
-
|
|
161
|
-
val extremeCases = listOf(
|
|
162
|
-
100 to "Very small (100 frames)",
|
|
163
|
-
50000 to "Very large (50000 frames)"
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
for ((frames, name) in extremeCases) {
|
|
167
|
-
val requestedBytes = frames * 2
|
|
168
|
-
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
|
169
|
-
val actualBufferSize = if (requestedBytes < minBufferSize) minBufferSize else requestedBytes
|
|
170
|
-
|
|
171
|
-
audioRecord?.release()
|
|
172
|
-
audioRecord = AudioRecord(
|
|
173
|
-
MediaRecorder.AudioSource.MIC,
|
|
174
|
-
sampleRate,
|
|
175
|
-
channelConfig,
|
|
176
|
-
audioFormat,
|
|
177
|
-
actualBufferSize
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
val state = audioRecord?.state
|
|
181
|
-
val passed = state == AudioRecord.STATE_INITIALIZED
|
|
182
|
-
|
|
183
|
-
results.add(TestResult(
|
|
184
|
-
name = name,
|
|
185
|
-
passed = passed,
|
|
186
|
-
message = "Requested: $frames frames, Buffer size: ${actualBufferSize / 2} frames"
|
|
187
|
-
))
|
|
188
|
-
|
|
189
|
-
println(" $name: $frames → ${actualBufferSize / 2} frames")
|
|
190
|
-
}
|
|
191
|
-
println()
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
@Test
|
|
195
|
-
fun testBufferAccumulation() {
|
|
196
|
-
println("Test 4: Buffer Accumulation for Small Durations")
|
|
197
|
-
println("-----------------------------------------------")
|
|
198
|
-
|
|
199
|
-
val sampleRate = 48000
|
|
200
|
-
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
|
201
|
-
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
|
202
|
-
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
|
203
|
-
|
|
204
|
-
// Test very small buffer (20ms = 960 frames)
|
|
205
|
-
val targetDuration = 0.02 // 20ms
|
|
206
|
-
val targetFrames = (targetDuration * sampleRate).toInt()
|
|
207
|
-
val targetBytes = targetFrames * 2
|
|
208
|
-
|
|
209
|
-
audioRecord?.release()
|
|
210
|
-
audioRecord = AudioRecord(
|
|
211
|
-
MediaRecorder.AudioSource.MIC,
|
|
212
|
-
sampleRate,
|
|
213
|
-
channelConfig,
|
|
214
|
-
audioFormat,
|
|
215
|
-
minBufferSize // Use minimum buffer size
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
if (audioRecord?.state == AudioRecord.STATE_INITIALIZED) {
|
|
219
|
-
audioRecord?.startRecording()
|
|
220
|
-
|
|
221
|
-
// Accumulate small chunks
|
|
222
|
-
val accumulator = mutableListOf<ByteArray>()
|
|
223
|
-
var totalFrames = 0
|
|
224
|
-
val smallBuffer = ByteArray(targetBytes)
|
|
225
|
-
|
|
226
|
-
// Read multiple times to accumulate
|
|
227
|
-
repeat(5) {
|
|
228
|
-
val bytesRead = audioRecord?.read(smallBuffer, 0, smallBuffer.size) ?: -1
|
|
229
|
-
if (bytesRead > 0) {
|
|
230
|
-
accumulator.add(smallBuffer.copyOf(bytesRead))
|
|
231
|
-
totalFrames += bytesRead / 2
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
audioRecord?.stop()
|
|
236
|
-
|
|
237
|
-
val passed = totalFrames >= targetFrames
|
|
238
|
-
results.add(TestResult(
|
|
239
|
-
name = "Buffer Accumulation",
|
|
240
|
-
passed = passed,
|
|
241
|
-
message = "Target: $targetFrames frames, Accumulated: $totalFrames frames over ${accumulator.size} reads"
|
|
242
|
-
))
|
|
243
|
-
|
|
244
|
-
println("✓ Target frames: $targetFrames")
|
|
245
|
-
println("✓ Accumulated: $totalFrames frames")
|
|
246
|
-
println("✓ Number of reads: ${accumulator.size}")
|
|
247
|
-
} else {
|
|
248
|
-
results.add(TestResult(
|
|
249
|
-
name = "Buffer Accumulation",
|
|
250
|
-
passed = false,
|
|
251
|
-
message = "Failed to initialize AudioRecord"
|
|
252
|
-
))
|
|
253
|
-
}
|
|
254
|
-
println()
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
@Test
|
|
258
|
-
fun testDifferentSampleRates() {
|
|
259
|
-
println("Test 5: Different Sample Rates")
|
|
260
|
-
println("------------------------------")
|
|
261
|
-
|
|
262
|
-
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
|
263
|
-
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
|
264
|
-
val bufferDuration = 0.1 // 100ms
|
|
265
|
-
|
|
266
|
-
val sampleRates = listOf(16000, 44100, 48000)
|
|
267
|
-
|
|
268
|
-
for (sampleRate in sampleRates) {
|
|
269
|
-
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
|
270
|
-
val targetFrames = (bufferDuration * sampleRate).toInt()
|
|
271
|
-
val targetBytes = targetFrames * 2
|
|
272
|
-
val actualBufferSize = if (targetBytes < minBufferSize) minBufferSize else targetBytes
|
|
273
|
-
|
|
274
|
-
audioRecord?.release()
|
|
275
|
-
audioRecord = AudioRecord(
|
|
276
|
-
MediaRecorder.AudioSource.MIC,
|
|
277
|
-
sampleRate,
|
|
278
|
-
channelConfig,
|
|
279
|
-
audioFormat,
|
|
280
|
-
actualBufferSize
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
val state = audioRecord?.state
|
|
284
|
-
val passed = state == AudioRecord.STATE_INITIALIZED
|
|
285
|
-
|
|
286
|
-
results.add(TestResult(
|
|
287
|
-
name = "Sample Rate ${sampleRate}Hz",
|
|
288
|
-
passed = passed,
|
|
289
|
-
message = "Buffer duration: ${bufferDuration}s, Frames: ${actualBufferSize / 2}"
|
|
290
|
-
))
|
|
291
|
-
|
|
292
|
-
println(" ${sampleRate}Hz: ${if (passed) "SUCCESS" else "FAILED"} - ${actualBufferSize / 2} frames")
|
|
293
|
-
}
|
|
294
|
-
println()
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
private fun printResults() {
|
|
298
|
-
println("📊 Test Results")
|
|
299
|
-
println("===============")
|
|
300
|
-
|
|
301
|
-
val passed = results.count { it.passed }
|
|
302
|
-
val total = results.size
|
|
303
|
-
|
|
304
|
-
for (result in results) {
|
|
305
|
-
val status = if (result.passed) "✅" else "❌"
|
|
306
|
-
println("$status ${result.name}")
|
|
307
|
-
println(" ${result.message}")
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
println("\nSummary: $passed/$total tests passed")
|
|
311
|
-
|
|
312
|
-
if (passed == total) {
|
|
313
|
-
println("🎉 All tests passed!")
|
|
314
|
-
} else {
|
|
315
|
-
println("⚠️ Some tests failed")
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
println("\n📝 Key Findings:")
|
|
319
|
-
println("- Android enforces minimum buffer size via getMinBufferSize()")
|
|
320
|
-
println("- Minimum varies by device and sample rate")
|
|
321
|
-
println("- Small buffers require accumulation strategy")
|
|
322
|
-
println("- AudioRecord handles buffer sizing more flexibly than iOS")
|
|
323
|
-
}
|
|
324
|
-
}
|