@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,279 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio
|
|
2
|
-
|
|
3
|
-
import org.junit.Test
|
|
4
|
-
import org.junit.Assert.*
|
|
5
|
-
import org.junit.Before
|
|
6
|
-
import org.junit.After
|
|
7
|
-
import java.io.ByteArrayOutputStream
|
|
8
|
-
import java.io.File
|
|
9
|
-
import java.nio.ByteBuffer
|
|
10
|
-
import java.nio.ByteOrder
|
|
11
|
-
import kotlin.test.assertNotNull
|
|
12
|
-
|
|
13
|
-
class AudioFileHandlerTest {
|
|
14
|
-
private lateinit var tempDir: File
|
|
15
|
-
private lateinit var audioFileHandler: AudioFileHandler
|
|
16
|
-
|
|
17
|
-
@Before
|
|
18
|
-
fun setUp() {
|
|
19
|
-
// Create a temporary directory for testing
|
|
20
|
-
tempDir = File(System.getProperty("java.io.tmpdir"), "audio_test_${System.currentTimeMillis()}")
|
|
21
|
-
tempDir.mkdirs()
|
|
22
|
-
audioFileHandler = AudioFileHandler(tempDir)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@After
|
|
26
|
-
fun tearDown() {
|
|
27
|
-
// Clean up temporary directory
|
|
28
|
-
tempDir.deleteRecursively()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@Test
|
|
32
|
-
fun testWriteWavHeader_writesCorrectHeaderFormat() {
|
|
33
|
-
// Given
|
|
34
|
-
val outputStream = ByteArrayOutputStream()
|
|
35
|
-
val sampleRate = 44100
|
|
36
|
-
val channels = 2
|
|
37
|
-
val bitDepth = 16
|
|
38
|
-
|
|
39
|
-
// When
|
|
40
|
-
audioFileHandler.writeWavHeader(outputStream, sampleRate, channels, bitDepth)
|
|
41
|
-
val header = outputStream.toByteArray()
|
|
42
|
-
|
|
43
|
-
// Then
|
|
44
|
-
assertEquals("WAV header should be 44 bytes", 44, header.size)
|
|
45
|
-
|
|
46
|
-
// Check RIFF header
|
|
47
|
-
assertEquals("Should start with RIFF", "RIFF", String(header.sliceArray(0..3)))
|
|
48
|
-
assertEquals("Should contain WAVE identifier", "WAVE", String(header.sliceArray(8..11)))
|
|
49
|
-
assertEquals("Should contain fmt chunk", "fmt ", String(header.sliceArray(12..15)))
|
|
50
|
-
assertEquals("Should contain data chunk", "data", String(header.sliceArray(36..39)))
|
|
51
|
-
|
|
52
|
-
// Check audio format (PCM = 1)
|
|
53
|
-
val audioFormat = ByteBuffer.wrap(header.sliceArray(20..21)).order(ByteOrder.LITTLE_ENDIAN).short
|
|
54
|
-
assertEquals("Audio format should be 1 (PCM)", 1, audioFormat.toInt())
|
|
55
|
-
|
|
56
|
-
// Check channels
|
|
57
|
-
val headerChannels = ByteBuffer.wrap(header.sliceArray(22..23)).order(ByteOrder.LITTLE_ENDIAN).short
|
|
58
|
-
assertEquals("Channels should match", channels, headerChannels.toInt())
|
|
59
|
-
|
|
60
|
-
// Check sample rate
|
|
61
|
-
val headerSampleRate = ByteBuffer.wrap(header.sliceArray(24..27)).order(ByteOrder.LITTLE_ENDIAN).int
|
|
62
|
-
assertEquals("Sample rate should match", sampleRate, headerSampleRate)
|
|
63
|
-
|
|
64
|
-
// Check bit depth
|
|
65
|
-
val headerBitDepth = ByteBuffer.wrap(header.sliceArray(34..35)).order(ByteOrder.LITTLE_ENDIAN).short
|
|
66
|
-
assertEquals("Bit depth should match", bitDepth, headerBitDepth.toInt())
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@Test
|
|
70
|
-
fun testWriteWavHeader_calculatesCorrectByteRate() {
|
|
71
|
-
// Given
|
|
72
|
-
val outputStream = ByteArrayOutputStream()
|
|
73
|
-
val sampleRate = 48000
|
|
74
|
-
val channels = 1
|
|
75
|
-
val bitDepth = 24
|
|
76
|
-
val expectedByteRate = sampleRate * channels * bitDepth / 8
|
|
77
|
-
|
|
78
|
-
// When
|
|
79
|
-
audioFileHandler.writeWavHeader(outputStream, sampleRate, channels, bitDepth)
|
|
80
|
-
val header = outputStream.toByteArray()
|
|
81
|
-
|
|
82
|
-
// Then
|
|
83
|
-
val byteRate = ByteBuffer.wrap(header.sliceArray(28..31)).order(ByteOrder.LITTLE_ENDIAN).int
|
|
84
|
-
assertEquals("Byte rate should be correctly calculated", expectedByteRate, byteRate)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@Test
|
|
88
|
-
fun testCreateAudioFile_createsFileWithCorrectExtension() {
|
|
89
|
-
// Given
|
|
90
|
-
val extension = "wav"
|
|
91
|
-
|
|
92
|
-
// When
|
|
93
|
-
val file = audioFileHandler.createAudioFile(extension)
|
|
94
|
-
|
|
95
|
-
// Then
|
|
96
|
-
assertNotNull("File should not be null", file)
|
|
97
|
-
assertTrue("File should exist", file.exists())
|
|
98
|
-
assertTrue("File should have correct extension", file.name.endsWith(".$extension"))
|
|
99
|
-
assertTrue("File should have correct prefix", file.name.startsWith("recording_"))
|
|
100
|
-
|
|
101
|
-
// Clean up
|
|
102
|
-
file.delete()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
@Test
|
|
106
|
-
fun testCreateAudioFile_createsUniqueFiles() {
|
|
107
|
-
// Given
|
|
108
|
-
val extension = "wav"
|
|
109
|
-
|
|
110
|
-
// When
|
|
111
|
-
val file1 = audioFileHandler.createAudioFile(extension)
|
|
112
|
-
Thread.sleep(10) // Small delay to ensure different timestamps
|
|
113
|
-
val file2 = audioFileHandler.createAudioFile(extension)
|
|
114
|
-
|
|
115
|
-
// Then
|
|
116
|
-
assertNotEquals("Files should have unique names", file1.name, file2.name)
|
|
117
|
-
assertTrue("First file should exist", file1.exists())
|
|
118
|
-
assertTrue("Second file should exist", file2.exists())
|
|
119
|
-
|
|
120
|
-
// Clean up
|
|
121
|
-
file1.delete()
|
|
122
|
-
file2.delete()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
@Test
|
|
126
|
-
fun testDeleteFile_deletesExistingFile() {
|
|
127
|
-
// Given
|
|
128
|
-
val file = audioFileHandler.createAudioFile("wav")
|
|
129
|
-
assertTrue("File should exist before deletion", file.exists())
|
|
130
|
-
|
|
131
|
-
// When
|
|
132
|
-
val result = audioFileHandler.deleteFile(file)
|
|
133
|
-
|
|
134
|
-
// Then
|
|
135
|
-
assertTrue("Delete should return true", result)
|
|
136
|
-
assertFalse("File should not exist after deletion", file.exists())
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
@Test
|
|
140
|
-
fun testDeleteFile_handlesNullFile() {
|
|
141
|
-
// When
|
|
142
|
-
val result = audioFileHandler.deleteFile(null)
|
|
143
|
-
|
|
144
|
-
// Then
|
|
145
|
-
assertFalse("Delete should return false for null file", result)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@Test
|
|
149
|
-
fun testDeleteFile_handlesNonExistentFile() {
|
|
150
|
-
// Given
|
|
151
|
-
val nonExistentFile = File(tempDir, "non_existent.wav")
|
|
152
|
-
assertFalse("File should not exist", nonExistentFile.exists())
|
|
153
|
-
|
|
154
|
-
// When
|
|
155
|
-
val result = audioFileHandler.deleteFile(nonExistentFile)
|
|
156
|
-
|
|
157
|
-
// Then
|
|
158
|
-
assertFalse("Delete should return false for non-existent file", result)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
@Test
|
|
162
|
-
fun testClearAudioStorage_deletesAllFiles() {
|
|
163
|
-
// Given
|
|
164
|
-
val file1 = audioFileHandler.createAudioFile("wav")
|
|
165
|
-
val file2 = audioFileHandler.createAudioFile("mp3")
|
|
166
|
-
val file3 = audioFileHandler.createAudioFile("aac")
|
|
167
|
-
|
|
168
|
-
assertTrue("File 1 should exist", file1.exists())
|
|
169
|
-
assertTrue("File 2 should exist", file2.exists())
|
|
170
|
-
assertTrue("File 3 should exist", file3.exists())
|
|
171
|
-
|
|
172
|
-
// When
|
|
173
|
-
audioFileHandler.clearAudioStorage()
|
|
174
|
-
|
|
175
|
-
// Then
|
|
176
|
-
assertFalse("File 1 should be deleted", file1.exists())
|
|
177
|
-
assertFalse("File 2 should be deleted", file2.exists())
|
|
178
|
-
assertFalse("File 3 should be deleted", file3.exists())
|
|
179
|
-
assertEquals("Directory should be empty", 0, tempDir.listFiles()?.size ?: 0)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
@Test
|
|
183
|
-
fun testUpdateWavHeader_updatesFileSizeCorrectly() {
|
|
184
|
-
// Given
|
|
185
|
-
val file = audioFileHandler.createAudioFile("wav")
|
|
186
|
-
val outputStream = file.outputStream()
|
|
187
|
-
|
|
188
|
-
// Write header
|
|
189
|
-
audioFileHandler.writeWavHeader(outputStream, 44100, 2, 16)
|
|
190
|
-
|
|
191
|
-
// Write some audio data (1 second of silence)
|
|
192
|
-
val audioDataSize = 44100 * 2 * 2 // sampleRate * channels * bytesPerSample
|
|
193
|
-
val audioData = ByteArray(audioDataSize)
|
|
194
|
-
outputStream.write(audioData)
|
|
195
|
-
outputStream.close()
|
|
196
|
-
|
|
197
|
-
// When
|
|
198
|
-
audioFileHandler.updateWavHeader(file)
|
|
199
|
-
|
|
200
|
-
// Then
|
|
201
|
-
val updatedHeader = file.inputStream().use { it.readNBytes(44) }
|
|
202
|
-
|
|
203
|
-
// Check file size field (bytes 4-7)
|
|
204
|
-
val fileSize = ByteBuffer.wrap(updatedHeader.sliceArray(4..7)).order(ByteOrder.LITTLE_ENDIAN).int
|
|
205
|
-
assertEquals("File size should be data size + 36", audioDataSize + 36, fileSize)
|
|
206
|
-
|
|
207
|
-
// Check data size field (bytes 40-43)
|
|
208
|
-
val dataSize = ByteBuffer.wrap(updatedHeader.sliceArray(40..43)).order(ByteOrder.LITTLE_ENDIAN).int
|
|
209
|
-
assertEquals("Data size should match audio data size", audioDataSize, dataSize)
|
|
210
|
-
|
|
211
|
-
// Clean up
|
|
212
|
-
file.delete()
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
@Test
|
|
216
|
-
fun testLoadRealWavFile_readsHeaderCorrectly() {
|
|
217
|
-
// Given - Load a real WAV file from test resources
|
|
218
|
-
val resourceStream = javaClass.classLoader?.getResourceAsStream("jfk.wav")
|
|
219
|
-
assertNotNull("Test resource jfk.wav should exist", resourceStream)
|
|
220
|
-
|
|
221
|
-
val testFile = File(tempDir, "test_jfk.wav")
|
|
222
|
-
resourceStream?.use { input ->
|
|
223
|
-
testFile.outputStream().use { output ->
|
|
224
|
-
input.copyTo(output)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// When - Read the WAV header
|
|
229
|
-
val header = testFile.inputStream().use { it.readNBytes(44) }
|
|
230
|
-
|
|
231
|
-
// Then - Validate header structure
|
|
232
|
-
assertEquals("Should start with RIFF", "RIFF", String(header.sliceArray(0..3)))
|
|
233
|
-
assertEquals("Should contain WAVE identifier", "WAVE", String(header.sliceArray(8..11)))
|
|
234
|
-
assertEquals("Should contain fmt chunk", "fmt ", String(header.sliceArray(12..15)))
|
|
235
|
-
|
|
236
|
-
// Extract audio properties
|
|
237
|
-
val channels = ByteBuffer.wrap(header.sliceArray(22..23)).order(ByteOrder.LITTLE_ENDIAN).short
|
|
238
|
-
val sampleRate = ByteBuffer.wrap(header.sliceArray(24..27)).order(ByteOrder.LITTLE_ENDIAN).int
|
|
239
|
-
val bitDepth = ByteBuffer.wrap(header.sliceArray(34..35)).order(ByteOrder.LITTLE_ENDIAN).short
|
|
240
|
-
|
|
241
|
-
// JFK.wav is known to be mono, 16kHz, 16-bit
|
|
242
|
-
assertEquals("JFK audio should be mono", 1, channels.toInt())
|
|
243
|
-
assertEquals("JFK audio should be 16kHz", 16000, sampleRate)
|
|
244
|
-
assertEquals("JFK audio should be 16-bit", 16, bitDepth.toInt())
|
|
245
|
-
|
|
246
|
-
// Clean up
|
|
247
|
-
testFile.delete()
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
@Test
|
|
251
|
-
fun testProcessMultipleRealWavFiles() {
|
|
252
|
-
// Test with different real WAV files to ensure compatibility
|
|
253
|
-
val testFiles = listOf("jfk.wav", "recorder_hello_world.wav", "osr_us_000_0010_8k.wav")
|
|
254
|
-
|
|
255
|
-
for (fileName in testFiles) {
|
|
256
|
-
val resourceStream = javaClass.classLoader?.getResourceAsStream(fileName)
|
|
257
|
-
if (resourceStream != null) {
|
|
258
|
-
val testFile = File(tempDir, "test_$fileName")
|
|
259
|
-
resourceStream.use { input ->
|
|
260
|
-
testFile.outputStream().use { output ->
|
|
261
|
-
input.copyTo(output)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Verify file was created and has content
|
|
266
|
-
assertTrue("$fileName should exist", testFile.exists())
|
|
267
|
-
assertTrue("$fileName should have more than just header", testFile.length() > 44)
|
|
268
|
-
|
|
269
|
-
// Read and validate header
|
|
270
|
-
val header = testFile.inputStream().use { it.readNBytes(44) }
|
|
271
|
-
assertEquals("$fileName should have RIFF header", "RIFF", String(header.sliceArray(0..3)))
|
|
272
|
-
assertEquals("$fileName should have WAVE format", "WAVE", String(header.sliceArray(8..11)))
|
|
273
|
-
|
|
274
|
-
// Clean up
|
|
275
|
-
testFile.delete()
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio
|
|
2
|
-
|
|
3
|
-
import org.junit.Test
|
|
4
|
-
import org.junit.Assert.*
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Unit tests for audio focus strategy configuration and logic.
|
|
8
|
-
* These tests verify that the RecordingConfig correctly handles audioFocusStrategy
|
|
9
|
-
* parameter and that the smart defaults work as expected.
|
|
10
|
-
*/
|
|
11
|
-
class AudioFocusStrategyTest {
|
|
12
|
-
|
|
13
|
-
@Test
|
|
14
|
-
fun testRecordingConfigWithExplicitBackgroundStrategy() {
|
|
15
|
-
val options = mapOf(
|
|
16
|
-
"sampleRate" to 44100,
|
|
17
|
-
"channels" to 1,
|
|
18
|
-
"encoding" to "pcm_16bit",
|
|
19
|
-
"android" to mapOf(
|
|
20
|
-
"audioFocusStrategy" to "background"
|
|
21
|
-
)
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
val result = RecordingConfig.fromMap(options)
|
|
25
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
26
|
-
|
|
27
|
-
val (config, _) = result.getOrThrow()
|
|
28
|
-
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@Test
|
|
32
|
-
fun testRecordingConfigWithExplicitInteractiveStrategy() {
|
|
33
|
-
val options = mapOf(
|
|
34
|
-
"sampleRate" to 44100,
|
|
35
|
-
"channels" to 1,
|
|
36
|
-
"encoding" to "pcm_16bit",
|
|
37
|
-
"android" to mapOf(
|
|
38
|
-
"audioFocusStrategy" to "interactive"
|
|
39
|
-
)
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
val result = RecordingConfig.fromMap(options)
|
|
43
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
44
|
-
|
|
45
|
-
val (config, _) = result.getOrThrow()
|
|
46
|
-
assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@Test
|
|
50
|
-
fun testRecordingConfigWithExplicitCommunicationStrategy() {
|
|
51
|
-
val options = mapOf(
|
|
52
|
-
"sampleRate" to 44100,
|
|
53
|
-
"channels" to 1,
|
|
54
|
-
"encoding" to "pcm_16bit",
|
|
55
|
-
"android" to mapOf(
|
|
56
|
-
"audioFocusStrategy" to "communication"
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
val result = RecordingConfig.fromMap(options)
|
|
61
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
62
|
-
|
|
63
|
-
val (config, _) = result.getOrThrow()
|
|
64
|
-
assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
@Test
|
|
68
|
-
fun testRecordingConfigWithExplicitNoneStrategy() {
|
|
69
|
-
val options = mapOf(
|
|
70
|
-
"sampleRate" to 44100,
|
|
71
|
-
"channels" to 1,
|
|
72
|
-
"encoding" to "pcm_16bit",
|
|
73
|
-
"android" to mapOf(
|
|
74
|
-
"audioFocusStrategy" to "none"
|
|
75
|
-
)
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
val result = RecordingConfig.fromMap(options)
|
|
79
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
80
|
-
|
|
81
|
-
val (config, _) = result.getOrThrow()
|
|
82
|
-
assertEquals("Audio focus strategy should be none", "none", config.audioFocusStrategy)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@Test
|
|
86
|
-
fun testRecordingConfigWithoutAudioFocusStrategy() {
|
|
87
|
-
val options = mapOf(
|
|
88
|
-
"sampleRate" to 44100,
|
|
89
|
-
"channels" to 1,
|
|
90
|
-
"encoding" to "pcm_16bit"
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
val result = RecordingConfig.fromMap(options)
|
|
94
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
95
|
-
|
|
96
|
-
val (config, _) = result.getOrThrow()
|
|
97
|
-
assertNull("Audio focus strategy should be null when not specified", config.audioFocusStrategy)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@Test
|
|
101
|
-
fun testRecordingConfigWithInvalidAudioFocusStrategy() {
|
|
102
|
-
val options = mapOf(
|
|
103
|
-
"sampleRate" to 44100,
|
|
104
|
-
"channels" to 1,
|
|
105
|
-
"encoding" to "pcm_16bit",
|
|
106
|
-
"android" to mapOf(
|
|
107
|
-
"audioFocusStrategy" to "invalid_strategy"
|
|
108
|
-
)
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
val result = RecordingConfig.fromMap(options)
|
|
112
|
-
assertTrue("Config creation should succeed even with invalid strategy", result.isSuccess)
|
|
113
|
-
|
|
114
|
-
val (config, _) = result.getOrThrow()
|
|
115
|
-
assertEquals("Invalid audio focus strategy should be preserved", "invalid_strategy", config.audioFocusStrategy)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
@Test
|
|
119
|
-
fun testRecordingConfigWithNullAudioFocusStrategy() {
|
|
120
|
-
val options = mapOf(
|
|
121
|
-
"sampleRate" to 44100,
|
|
122
|
-
"channels" to 1,
|
|
123
|
-
"encoding" to "pcm_16bit",
|
|
124
|
-
"android" to mapOf(
|
|
125
|
-
"audioFocusStrategy" to null
|
|
126
|
-
)
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
val result = RecordingConfig.fromMap(options)
|
|
130
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
131
|
-
|
|
132
|
-
val (config, _) = result.getOrThrow()
|
|
133
|
-
assertNull("Audio focus strategy should be null", config.audioFocusStrategy)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
@Test
|
|
137
|
-
fun testRecordingConfigKeepAwakeAndBackgroundStrategy() {
|
|
138
|
-
val options = mapOf(
|
|
139
|
-
"sampleRate" to 44100,
|
|
140
|
-
"channels" to 1,
|
|
141
|
-
"encoding" to "pcm_16bit",
|
|
142
|
-
"keepAwake" to true,
|
|
143
|
-
"android" to mapOf(
|
|
144
|
-
"audioFocusStrategy" to "background"
|
|
145
|
-
)
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
val result = RecordingConfig.fromMap(options)
|
|
149
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
150
|
-
|
|
151
|
-
val (config, _) = result.getOrThrow()
|
|
152
|
-
assertTrue("keepAwake should be true", config.keepAwake)
|
|
153
|
-
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
@Test
|
|
157
|
-
fun testRecordingConfigKeepAwakeFalseAndInteractiveStrategy() {
|
|
158
|
-
val options = mapOf(
|
|
159
|
-
"sampleRate" to 44100,
|
|
160
|
-
"channels" to 1,
|
|
161
|
-
"encoding" to "pcm_16bit",
|
|
162
|
-
"keepAwake" to false,
|
|
163
|
-
"android" to mapOf(
|
|
164
|
-
"audioFocusStrategy" to "interactive"
|
|
165
|
-
)
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
val result = RecordingConfig.fromMap(options)
|
|
169
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
170
|
-
|
|
171
|
-
val (config, _) = result.getOrThrow()
|
|
172
|
-
assertFalse("keepAwake should be false", config.keepAwake)
|
|
173
|
-
assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
@Test
|
|
177
|
-
fun testRecordingConfigWithAutoResumeAndBackgroundStrategy() {
|
|
178
|
-
val options = mapOf(
|
|
179
|
-
"sampleRate" to 44100,
|
|
180
|
-
"channels" to 1,
|
|
181
|
-
"encoding" to "pcm_16bit",
|
|
182
|
-
"autoResumeAfterInterruption" to true,
|
|
183
|
-
"android" to mapOf(
|
|
184
|
-
"audioFocusStrategy" to "background"
|
|
185
|
-
)
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
val result = RecordingConfig.fromMap(options)
|
|
189
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
190
|
-
|
|
191
|
-
val (config, _) = result.getOrThrow()
|
|
192
|
-
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
193
|
-
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
@Test
|
|
197
|
-
fun testRecordingConfigWithCommunicationStrategyAndSpeechSampleRate() {
|
|
198
|
-
val options = mapOf(
|
|
199
|
-
"sampleRate" to 16000, // Common speech sample rate
|
|
200
|
-
"channels" to 1,
|
|
201
|
-
"encoding" to "pcm_16bit",
|
|
202
|
-
"android" to mapOf(
|
|
203
|
-
"audioFocusStrategy" to "communication"
|
|
204
|
-
)
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
val result = RecordingConfig.fromMap(options)
|
|
208
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
209
|
-
|
|
210
|
-
val (config, _) = result.getOrThrow()
|
|
211
|
-
assertEquals("Sample rate should be 16000", 16000, config.sampleRate)
|
|
212
|
-
assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
@Test
|
|
216
|
-
fun testDefaultRecordingConfigValues() {
|
|
217
|
-
val result = RecordingConfig.fromMap(null)
|
|
218
|
-
assertTrue("Config creation should succeed with null input", result.isSuccess)
|
|
219
|
-
|
|
220
|
-
val (config, _) = result.getOrThrow()
|
|
221
|
-
assertNull("Default audio focus strategy should be null", config.audioFocusStrategy)
|
|
222
|
-
assertTrue("Default keepAwake should be true", config.keepAwake)
|
|
223
|
-
assertFalse("Default autoResumeAfterInterruption should be false", config.autoResumeAfterInterruption)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
@Test
|
|
227
|
-
fun testRecordingConfigCompleteAudioFocusConfiguration() {
|
|
228
|
-
val options = mapOf(
|
|
229
|
-
"sampleRate" to 44100,
|
|
230
|
-
"channels" to 1,
|
|
231
|
-
"encoding" to "pcm_16bit",
|
|
232
|
-
"keepAwake" to true,
|
|
233
|
-
"autoResumeAfterInterruption" to true,
|
|
234
|
-
"showNotification" to true,
|
|
235
|
-
"android" to mapOf(
|
|
236
|
-
"audioFocusStrategy" to "background"
|
|
237
|
-
)
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
val result = RecordingConfig.fromMap(options)
|
|
241
|
-
assertTrue("Config creation should succeed", result.isSuccess)
|
|
242
|
-
|
|
243
|
-
val (config, _) = result.getOrThrow()
|
|
244
|
-
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
245
|
-
assertTrue("keepAwake should be true", config.keepAwake)
|
|
246
|
-
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
247
|
-
assertTrue("showNotification should be true", config.showNotification)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio
|
|
2
|
-
|
|
3
|
-
import org.junit.Test
|
|
4
|
-
import org.junit.Assert.*
|
|
5
|
-
import org.junit.Before
|
|
6
|
-
import java.io.File
|
|
7
|
-
|
|
8
|
-
class AudioFormatTest {
|
|
9
|
-
private lateinit var tempDir: File
|
|
10
|
-
|
|
11
|
-
@Before
|
|
12
|
-
fun setUp() {
|
|
13
|
-
tempDir = File(System.getProperty("java.io.tmpdir"), "audio_format_test_${System.currentTimeMillis()}")
|
|
14
|
-
tempDir.mkdirs()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Test
|
|
18
|
-
fun testGetFileExtension_aacDefaultsToM4a() {
|
|
19
|
-
// Given
|
|
20
|
-
val config = RecordingConfig(
|
|
21
|
-
output = OutputConfig(
|
|
22
|
-
compressed = OutputConfig.CompressedOutput(
|
|
23
|
-
enabled = true,
|
|
24
|
-
format = "aac",
|
|
25
|
-
preferRawStream = false
|
|
26
|
-
)
|
|
27
|
-
)
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
// When
|
|
31
|
-
val file = createTestFile(config, isCompressed = true)
|
|
32
|
-
|
|
33
|
-
// Then
|
|
34
|
-
assertTrue("AAC without preferRawStream should produce .m4a", file.name.endsWith(".m4a"))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@Test
|
|
38
|
-
fun testGetFileExtension_aacWithPreferRawStreamProducesAac() {
|
|
39
|
-
// Given
|
|
40
|
-
val config = RecordingConfig(
|
|
41
|
-
output = OutputConfig(
|
|
42
|
-
compressed = OutputConfig.CompressedOutput(
|
|
43
|
-
enabled = true,
|
|
44
|
-
format = "aac",
|
|
45
|
-
preferRawStream = true
|
|
46
|
-
)
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
// When
|
|
51
|
-
val file = createTestFile(config, isCompressed = true)
|
|
52
|
-
|
|
53
|
-
// Then
|
|
54
|
-
assertTrue("AAC with preferRawStream should produce .aac", file.name.endsWith(".aac"))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@Test
|
|
58
|
-
fun testGetFileExtension_opusProducesOpus() {
|
|
59
|
-
// Given
|
|
60
|
-
val config = RecordingConfig(
|
|
61
|
-
output = OutputConfig(
|
|
62
|
-
compressed = OutputConfig.CompressedOutput(
|
|
63
|
-
enabled = true,
|
|
64
|
-
format = "opus"
|
|
65
|
-
)
|
|
66
|
-
)
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
// When
|
|
70
|
-
val file = createTestFile(config, isCompressed = true)
|
|
71
|
-
|
|
72
|
-
// Then
|
|
73
|
-
assertTrue("Opus should produce .opus", file.name.endsWith(".opus"))
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
@Test
|
|
77
|
-
fun testGetFileExtension_wavForUncompressed() {
|
|
78
|
-
// Given
|
|
79
|
-
val config = RecordingConfig()
|
|
80
|
-
|
|
81
|
-
// When
|
|
82
|
-
val file = createTestFile(config, isCompressed = false)
|
|
83
|
-
|
|
84
|
-
// Then
|
|
85
|
-
assertTrue("Uncompressed should produce .wav", file.name.endsWith(".wav"))
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@Test
|
|
89
|
-
fun testCompressedOutput_parseFromMap() {
|
|
90
|
-
// Given
|
|
91
|
-
val map = mapOf(
|
|
92
|
-
"enabled" to true,
|
|
93
|
-
"format" to "aac",
|
|
94
|
-
"bitrate" to 192000,
|
|
95
|
-
"preferRawStream" to true
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
// When
|
|
99
|
-
val compressed = OutputConfig.CompressedOutput(
|
|
100
|
-
enabled = map["enabled"] as Boolean,
|
|
101
|
-
format = map["format"] as String,
|
|
102
|
-
bitrate = map["bitrate"] as Int,
|
|
103
|
-
preferRawStream = map["preferRawStream"] as Boolean
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
// Then
|
|
107
|
-
assertTrue("enabled should be true", compressed.enabled)
|
|
108
|
-
assertEquals("format should be aac", "aac", compressed.format)
|
|
109
|
-
assertEquals("bitrate should be 192000", 192000, compressed.bitrate)
|
|
110
|
-
assertTrue("preferRawStream should be true", compressed.preferRawStream)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@Test
|
|
114
|
-
fun testCompressedOutput_defaultValues() {
|
|
115
|
-
// When
|
|
116
|
-
val compressed = OutputConfig.CompressedOutput()
|
|
117
|
-
|
|
118
|
-
// Then
|
|
119
|
-
assertFalse("enabled should default to false", compressed.enabled)
|
|
120
|
-
assertEquals("format should default to aac", "aac", compressed.format)
|
|
121
|
-
assertEquals("bitrate should default to 128000", 128000, compressed.bitrate)
|
|
122
|
-
assertFalse("preferRawStream should default to false", compressed.preferRawStream)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Helper function to simulate file creation logic from AudioRecorderManager
|
|
127
|
-
*/
|
|
128
|
-
private fun createTestFile(config: RecordingConfig, isCompressed: Boolean): File {
|
|
129
|
-
val baseFilename = config.filename?.let {
|
|
130
|
-
it.substringBeforeLast('.', it)
|
|
131
|
-
} ?: "test_recording"
|
|
132
|
-
|
|
133
|
-
val extension = if (isCompressed) {
|
|
134
|
-
when (config.output.compressed.format.lowercase()) {
|
|
135
|
-
"aac" -> {
|
|
136
|
-
if (config.output.compressed.preferRawStream) {
|
|
137
|
-
"aac" // Raw AAC stream
|
|
138
|
-
} else {
|
|
139
|
-
"m4a" // M4A container (new default)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
"opus" -> "opus" // Opus in OGG container
|
|
143
|
-
else -> config.output.compressed.format.lowercase()
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
"wav"
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return File(tempDir, "$baseFilename.$extension")
|
|
150
|
-
}
|
|
151
|
-
}
|