@siteed/audio-studio 3.1.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +375 -4
  2. package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +852 -0
  3. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +167 -3
  4. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +4 -0
  5. package/build/cjs/errors/AudioStreamError.js +161 -0
  6. package/build/cjs/errors/AudioStreamError.js.map +1 -0
  7. package/build/cjs/errors/AudioStreamError.test.js +82 -0
  8. package/build/cjs/errors/AudioStreamError.test.js.map +1 -0
  9. package/build/cjs/index.js +7 -1
  10. package/build/cjs/index.js.map +1 -1
  11. package/build/cjs/streamAudioData.js +534 -0
  12. package/build/cjs/streamAudioData.js.map +1 -0
  13. package/build/cjs/utils/audioProcessing.js +14 -10
  14. package/build/cjs/utils/audioProcessing.js.map +1 -1
  15. package/build/esm/errors/AudioStreamError.js +156 -0
  16. package/build/esm/errors/AudioStreamError.js.map +1 -0
  17. package/build/esm/errors/AudioStreamError.test.js +80 -0
  18. package/build/esm/errors/AudioStreamError.test.js.map +1 -0
  19. package/build/esm/index.js +3 -1
  20. package/build/esm/index.js.map +1 -1
  21. package/build/esm/streamAudioData.js +527 -0
  22. package/build/esm/streamAudioData.js.map +1 -0
  23. package/build/esm/utils/audioProcessing.js +14 -10
  24. package/build/esm/utils/audioProcessing.js.map +1 -1
  25. package/build/types/errors/AudioStreamError.d.ts +25 -0
  26. package/build/types/errors/AudioStreamError.d.ts.map +1 -0
  27. package/build/types/errors/AudioStreamError.test.d.ts +2 -0
  28. package/build/types/errors/AudioStreamError.test.d.ts.map +1 -0
  29. package/build/types/index.d.ts +5 -1
  30. package/build/types/index.d.ts.map +1 -1
  31. package/build/types/streamAudioData.d.ts +119 -0
  32. package/build/types/streamAudioData.d.ts.map +1 -0
  33. package/build/types/utils/audioProcessing.d.ts +2 -2
  34. package/build/types/utils/audioProcessing.d.ts.map +1 -1
  35. package/ios/AudioProcessingHelpers.swift +10 -5
  36. package/ios/AudioStreamDecoder.swift +614 -0
  37. package/ios/AudioStudioModule.swift +186 -3
  38. package/package.json +163 -146
  39. package/scripts/README.md +58 -0
  40. package/src/errors/AudioStreamError.test.ts +92 -0
  41. package/src/errors/AudioStreamError.ts +199 -0
  42. package/src/index.ts +24 -0
  43. package/src/streamAudioData.ts +758 -0
  44. package/src/utils/audioProcessing.ts +25 -14
  45. package/android/src/androidTest/assets/chorus.wav +0 -0
  46. package/android/src/androidTest/assets/jfk.wav +0 -0
  47. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  48. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  49. package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +0 -190
  50. package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +0 -197
  51. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +0 -487
  52. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +0 -250
  53. package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +0 -186
  54. package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
  55. package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +0 -324
  56. package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +0 -253
  57. package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +0 -218
  58. package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +0 -120
  59. package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +0 -345
  60. package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +0 -340
  61. package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +0 -252
  62. package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +0 -95
  63. package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +0 -43
  64. package/android/src/test/java/net/siteed/audiostudio/AndroidCallStateTest.kt +0 -37
  65. package/android/src/test/java/net/siteed/audiostudio/AndroidEventEmitterTest.kt +0 -28
  66. package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +0 -279
  67. package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +0 -249
  68. package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +0 -151
  69. package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +0 -273
  70. package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +0 -140
  71. package/android/src/test/java/net/siteed/audiostudio/InterruptionAutoResumePolicyTest.kt +0 -49
  72. package/android/src/test/resources/chorus.wav +0 -0
  73. package/android/src/test/resources/generate_test_audio.py +0 -94
  74. package/android/src/test/resources/jfk.wav +0 -0
  75. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  76. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  77. package/ios/AudioStudioTests/AudioFileHandlerTests.swift +0 -338
  78. package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +0 -331
  79. package/ios/AudioStudioTests/AudioTestHelpers.swift +0 -130
  80. package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +0 -334
  81. package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +0 -105
  82. package/ios/AudioStudioTests/Info.plist +0 -22
  83. package/ios/AudioStudioTests/README.md +0 -39
  84. package/ios/AudioStudioTests/SimpleAudioTest.swift +0 -98
  85. package/ios/AudioStudioTests/TestAudioGenerator.swift +0 -75
  86. package/ios/tests/README.md +0 -41
  87. package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
  88. package/ios/tests/integration/buffer_duration_test.swift +0 -185
  89. package/ios/tests/integration/compressed_only_output_test.swift +0 -271
  90. package/ios/tests/integration/output_control_test.swift +0 -322
  91. package/ios/tests/integration/run_integration_tests.sh +0 -37
  92. package/ios/tests/opus_support_test_macos.swift +0 -154
  93. package/ios/tests/standalone/audio_processing_test.swift +0 -144
  94. package/ios/tests/standalone/audio_recording_test.swift +0 -277
  95. package/ios/tests/standalone/audio_streaming_test.swift +0 -249
  96. package/ios/tests/standalone/standalone_test.swift +0 -144
@@ -1,98 +0,0 @@
1
- import XCTest
2
- import AVFoundation
3
-
4
- class SimpleAudioTest: XCTestCase {
5
-
6
- func testCreateWAVHeader() {
7
- // Test creating a basic WAV header
8
- let sampleRate = 44100
9
- let channels = 2
10
- let bitsPerSample = 16
11
- let dataSize = 1024
12
-
13
- // Calculate expected values
14
- let byteRate = sampleRate * channels * (bitsPerSample / 8)
15
- let blockAlign = channels * (bitsPerSample / 8)
16
-
17
- // Create header data manually (44 bytes)
18
- var header = Data()
19
-
20
- // RIFF chunk
21
- header.append("RIFF".data(using: .ascii)!)
22
- var fileSize = UInt32(dataSize + 36).littleEndian
23
- header.append(Data(bytes: &fileSize, count: 4))
24
- header.append("WAVE".data(using: .ascii)!)
25
-
26
- // fmt chunk
27
- header.append("fmt ".data(using: .ascii)!)
28
- var fmtSize = UInt32(16).littleEndian
29
- header.append(Data(bytes: &fmtSize, count: 4))
30
- var audioFormat = UInt16(1).littleEndian // PCM
31
- header.append(Data(bytes: &audioFormat, count: 2))
32
- var numChannels = UInt16(channels).littleEndian
33
- header.append(Data(bytes: &numChannels, count: 2))
34
- var sampleRateValue = UInt32(sampleRate).littleEndian
35
- header.append(Data(bytes: &sampleRateValue, count: 4))
36
- var byteRateValue = UInt32(byteRate).littleEndian
37
- header.append(Data(bytes: &byteRateValue, count: 4))
38
- var blockAlignValue = UInt16(blockAlign).littleEndian
39
- header.append(Data(bytes: &blockAlignValue, count: 2))
40
- var bitsPerSampleValue = UInt16(bitsPerSample).littleEndian
41
- header.append(Data(bytes: &bitsPerSampleValue, count: 2))
42
-
43
- // data chunk
44
- header.append("data".data(using: .ascii)!)
45
- var dataSizeValue = UInt32(dataSize).littleEndian
46
- header.append(Data(bytes: &dataSizeValue, count: 4))
47
-
48
- // Verify header size
49
- XCTAssertEqual(header.count, 44, "WAV header should be 44 bytes")
50
-
51
- // Verify RIFF header
52
- let riffHeader = String(data: header[0..<4], encoding: .ascii)
53
- XCTAssertEqual(riffHeader, "RIFF")
54
-
55
- // Verify WAVE format
56
- let waveFormat = String(data: header[8..<12], encoding: .ascii)
57
- XCTAssertEqual(waveFormat, "WAVE")
58
-
59
- print("✅ Basic WAV header test passed!")
60
- }
61
-
62
- func testSimpleAudioBuffer() {
63
- // Test creating a simple audio buffer
64
- let sampleRate = 44100.0
65
- let duration = 0.1 // 100ms
66
- let frequency = 440.0 // A4 note
67
-
68
- let frameCount = Int(sampleRate * duration)
69
- let format = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)!
70
-
71
- guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount)) else {
72
- XCTFail("Failed to create audio buffer")
73
- return
74
- }
75
-
76
- buffer.frameLength = AVAudioFrameCount(frameCount)
77
-
78
- // Generate a simple sine wave
79
- let channelData = buffer.floatChannelData![0]
80
- for frame in 0..<frameCount {
81
- let phase = 2.0 * Double.pi * frequency * Double(frame) / sampleRate
82
- channelData[frame] = Float(sin(phase) * 0.5)
83
- }
84
-
85
- // Verify buffer properties
86
- XCTAssertEqual(buffer.frameLength, AVAudioFrameCount(frameCount))
87
- XCTAssertEqual(buffer.format.sampleRate, sampleRate)
88
- XCTAssertEqual(buffer.format.channelCount, 1)
89
-
90
- // Verify we have audio data
91
- let firstSample = channelData[0]
92
- let lastSample = channelData[frameCount - 1]
93
- XCTAssertNotEqual(firstSample, 0.0, accuracy: 0.001)
94
- XCTAssertNotEqual(lastSample, firstSample, accuracy: 0.001)
95
-
96
- print("✅ Simple audio buffer test passed!")
97
- }
98
- }
@@ -1,75 +0,0 @@
1
- import Foundation
2
- import AVFoundation
3
- import Accelerate
4
-
5
- class TestAudioGenerator {
6
-
7
- /// Generate a sine wave tone
8
- static func generateTone(frequency: Double, duration: TimeInterval, sampleRate: Double = 44100) -> AVAudioPCMBuffer? {
9
- let frameCount = AVAudioFrameCount(duration * sampleRate)
10
-
11
- guard let buffer = AVAudioPCMBuffer(pcmFormat: AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)!, frameCapacity: frameCount) else {
12
- return nil
13
- }
14
-
15
- buffer.frameLength = frameCount
16
-
17
- let channelData = buffer.floatChannelData![0]
18
- let angleIncrement = 2.0 * .pi * frequency / sampleRate
19
-
20
- for frame in 0..<Int(frameCount) {
21
- channelData[frame] = Float(sin(Double(frame) * angleIncrement))
22
- }
23
-
24
- return buffer
25
- }
26
-
27
- /// Generate white noise
28
- static func generateWhiteNoise(duration: TimeInterval, sampleRate: Double = 44100) -> AVAudioPCMBuffer? {
29
- let frameCount = AVAudioFrameCount(duration * sampleRate)
30
-
31
- guard let buffer = AVAudioPCMBuffer(pcmFormat: AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)!, frameCapacity: frameCount) else {
32
- return nil
33
- }
34
-
35
- buffer.frameLength = frameCount
36
-
37
- let channelData = buffer.floatChannelData![0]
38
-
39
- for frame in 0..<Int(frameCount) {
40
- channelData[frame] = Float.random(in: -1...1)
41
- }
42
-
43
- return buffer
44
- }
45
-
46
- /// Load test asset from bundle
47
- static func loadTestAsset(named name: String) -> AVAudioFile? {
48
- guard let url = Bundle(for: TestAudioGenerator.self).url(forResource: name, withExtension: "wav") else {
49
- return nil
50
- }
51
-
52
- return try? AVAudioFile(forReading: url)
53
- }
54
-
55
- /// Convert AVAudioPCMBuffer to Data
56
- static func bufferToData(_ buffer: AVAudioPCMBuffer) -> Data? {
57
- guard let channelData = buffer.floatChannelData else { return nil }
58
-
59
- let channelCount = Int(buffer.format.channelCount)
60
- let frameLength = Int(buffer.frameLength)
61
- let bytesPerFrame = 2 * channelCount // 16-bit audio
62
-
63
- var data = Data(capacity: frameLength * bytesPerFrame)
64
-
65
- for frame in 0..<frameLength {
66
- for channel in 0..<channelCount {
67
- let sample = channelData[channel][frame]
68
- let int16Sample = Int16(max(-32768, min(32767, sample * 32767)))
69
- data.append(contentsOf: withUnsafeBytes(of: int16Sample) { Array($0) })
70
- }
71
- }
72
-
73
- return data
74
- }
75
- }
@@ -1,41 +0,0 @@
1
- # iOS Audio Format Tests
2
-
3
- This directory contains test scripts for validating audio format support on iOS/macOS.
4
-
5
- ## Opus Support Test
6
-
7
- The `opus_support_test_macos.swift` script verifies that while `kAudioFormatOpus` is defined in the iOS SDK, AVAudioRecorder cannot actually encode Opus audio.
8
-
9
- ### Running the Test
10
-
11
- ```bash
12
- # On macOS (for quick validation)
13
- swift opus_support_test_macos.swift
14
-
15
- # On iOS device/simulator (requires Xcode)
16
- # Copy the test to an iOS project and run it
17
- ```
18
-
19
- ### Test Results
20
-
21
- - ✅ `kAudioFormatOpus` constant exists (value: 1869641075)
22
- - ✅ AVAudioRecorder accepts Opus settings without errors
23
- - ❌ Recording produces 0-byte files (no actual encoding)
24
- - ✅ AAC format works correctly as fallback
25
-
26
- ### Why This Matters
27
-
28
- This test proves that expo-audio-studio's automatic fallback from Opus to AAC on iOS is necessary and correct. Despite the SDK defining the Opus format constant, the actual encoding functionality is not implemented in AVAudioRecorder.
29
-
30
- ## Format Verification
31
-
32
- To verify actual file formats:
33
-
34
- ```bash
35
- # Check file type
36
- file recording.m4a # Should show: ISO Media, MP4 Base Media
37
- file recording.aac # Should show: ADTS, AAC
38
-
39
- # Get detailed info (requires mediainfo)
40
- mediainfo recording.m4a
41
- ```
@@ -1,178 +0,0 @@
1
- #!/usr/bin/env swift
2
-
3
- import Foundation
4
- import AVFoundation
5
-
6
- // Integration test for validating buffer size calculation and fallback behavior fixes
7
- // Tests issues #246 and #247
8
-
9
- print("🧪 Buffer Size Calculation and Fallback Integration Test")
10
- print("======================================================\n")
11
-
12
- class BufferAndFallbackTest {
13
- let audioEngine = AVAudioEngine()
14
- var results: [(name: String, passed: Bool, message: String)] = []
15
- var emissionCount = 0
16
- var lastEmissionData: Data?
17
-
18
- func runAllTests() {
19
- testBufferSizeCalculation()
20
- testFallbackWithoutDuplication()
21
- printResults()
22
- }
23
-
24
- func testBufferSizeCalculation() {
25
- print("Test 1: Buffer Size Calculation with Target Sample Rate")
26
- print("-------------------------------------------------------")
27
- print("Testing that buffer size is calculated based on target sample rate, not hardware rate")
28
-
29
- let inputNode = audioEngine.inputNode
30
- let hardwareFormat = inputNode.inputFormat(forBus: 0)
31
- let hardwareSampleRate = hardwareFormat.sampleRate
32
-
33
- print("Hardware sample rate: \(hardwareSampleRate) Hz")
34
-
35
- // Test case: 0.02 seconds at 16000 Hz should request 320 frames
36
- let targetSampleRate: Double = 16000
37
- let bufferDuration: Double = 0.02
38
- let expectedRequestedFrames = AVAudioFrameCount(bufferDuration * targetSampleRate)
39
-
40
- print("Target sample rate: \(targetSampleRate) Hz")
41
- print("Buffer duration: \(bufferDuration) seconds")
42
- print("Expected requested frames: \(expectedRequestedFrames)")
43
-
44
- // Since iOS enforces minimum ~4800 frames, we expect either 4800 or our requested size
45
- let _ : AVAudioFrameCount = max(4800, expectedRequestedFrames)
46
-
47
- let expectation = DispatchSemaphore(value: 0)
48
- var receivedFrames: AVAudioFrameCount = 0
49
-
50
- inputNode.installTap(onBus: 0, bufferSize: expectedRequestedFrames, format: hardwareFormat) { buffer, _ in
51
- receivedFrames = buffer.frameLength
52
- expectation.signal()
53
- }
54
-
55
- audioEngine.prepare()
56
- do {
57
- try audioEngine.start()
58
- _ = expectation.wait(timeout: .now() + 2)
59
- audioEngine.stop()
60
- } catch {
61
- print("Error: \(error)")
62
- }
63
-
64
- inputNode.removeTap(onBus: 0)
65
-
66
- // The key test: verify that we calculated based on target rate (320 frames), not hardware rate
67
- let wouldHaveBeenWithHardwareRate = AVAudioFrameCount(bufferDuration * hardwareSampleRate)
68
- let usedTargetRate = expectedRequestedFrames == 320
69
-
70
- results.append((
71
- name: "Buffer Size Calculation",
72
- passed: usedTargetRate,
73
- message: "Used target rate: \(usedTargetRate), Requested: \(expectedRequestedFrames) frames (would be \(wouldHaveBeenWithHardwareRate) with hardware rate)"
74
- ))
75
-
76
- print("✓ Requested frames: \(expectedRequestedFrames) (calculated from target rate)")
77
- print("✓ Would have been: \(wouldHaveBeenWithHardwareRate) frames (if using hardware rate)")
78
- print("✓ Actually received: \(receivedFrames) frames (iOS minimum enforced)\n")
79
- }
80
-
81
- func testFallbackWithoutDuplication() {
82
- print("Test 2: Fallback Without Data Duplication")
83
- print("-----------------------------------------")
84
- print("Simulating device fallback scenario to ensure no duplicate emissions")
85
-
86
- // Reset counters
87
- emissionCount = 0
88
- lastEmissionData = nil
89
-
90
- let inputNode = audioEngine.inputNode
91
- let format = inputNode.inputFormat(forBus: 0)
92
-
93
- // Simulate a tap that counts emissions
94
- var bufferCount = 0
95
- let expectation = DispatchSemaphore(value: 0)
96
-
97
- inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { [weak self] buffer, _ in
98
- guard let self = self else { return }
99
-
100
- bufferCount += 1
101
-
102
- // Simulate emission logic
103
- let audioData = buffer.audioBufferList.pointee.mBuffers
104
- if let bufferData = audioData.mData {
105
- let data = Data(bytes: bufferData, count: Int(audioData.mDataByteSize))
106
-
107
- // Check if this is the same data as last emission
108
- if let lastData = self.lastEmissionData, lastData == data {
109
- print("⚠️ Detected duplicate emission!")
110
- }
111
-
112
- self.lastEmissionData = data
113
- self.emissionCount += 1
114
- }
115
-
116
- if bufferCount >= 10 {
117
- expectation.signal()
118
- }
119
- }
120
-
121
- audioEngine.prepare()
122
- do {
123
- try audioEngine.start()
124
- _ = expectation.wait(timeout: .now() + 3)
125
- audioEngine.stop()
126
- } catch {
127
- print("Error: \(error)")
128
- }
129
-
130
- inputNode.removeTap(onBus: 0)
131
-
132
- // With the fix, emission count should equal buffer count (no duplicates)
133
- let noDuplicates = emissionCount == bufferCount
134
-
135
- results.append((
136
- name: "Fallback No Duplication",
137
- passed: noDuplicates,
138
- message: "Buffers: \(bufferCount), Emissions: \(emissionCount), No duplicates: \(noDuplicates)"
139
- ))
140
-
141
- print("✓ Processed \(bufferCount) buffers")
142
- print("✓ Emitted \(emissionCount) times")
143
- print("✓ No duplicate emissions: \(noDuplicates)\n")
144
- }
145
-
146
- func printResults() {
147
- print("📊 Test Results")
148
- print("===============")
149
-
150
- let passed = results.filter { $0.passed }.count
151
- let total = results.count
152
-
153
- for result in results {
154
- let status = result.passed ? "✅" : "❌"
155
- print("\(status) \(result.name)")
156
- print(" \(result.message)")
157
- }
158
-
159
- print("\nSummary: \(passed)/\(total) tests passed")
160
-
161
- if passed == total {
162
- print("🎉 All tests passed!")
163
- print("\n✅ Issue #247 (Buffer Size Calculation) - FIXED")
164
- print("✅ Issue #246 (Duplicate Emissions) - Validation Ready")
165
- } else {
166
- print("⚠️ Some tests failed")
167
- }
168
-
169
- print("\n📝 Key Validations:")
170
- print("- Buffer size is now calculated using target sample rate")
171
- print("- iOS minimum buffer size (~4800 frames) is properly handled")
172
- print("- Fallback behavior ready for duplicate emission testing")
173
- }
174
- }
175
-
176
- // Run the test
177
- let test = BufferAndFallbackTest()
178
- test.runAllTests()
@@ -1,185 +0,0 @@
1
- #!/usr/bin/env swift
2
-
3
- import Foundation
4
- import AVFoundation
5
-
6
- // Integration test for Buffer Duration feature
7
- // This tests the ACTUAL behavior of AVAudioEngine with different buffer sizes
8
-
9
- print("🧪 Buffer Duration Integration Test")
10
- print("===================================\n")
11
-
12
- class BufferDurationTest {
13
- let audioEngine = AVAudioEngine()
14
- var results: [(name: String, passed: Bool, message: String)] = []
15
-
16
- func runAllTests() {
17
- testDefaultBufferSize()
18
- testCustomBufferSizes()
19
- testBufferSizeLimits()
20
- printResults()
21
- }
22
-
23
- func testDefaultBufferSize() {
24
- print("Test 1: Default Buffer Size (1024 frames requested)")
25
- print("-------------------------------------------------")
26
-
27
- let inputNode = audioEngine.inputNode
28
- let expectation = DispatchSemaphore(value: 0)
29
- var receivedFrames: AVAudioFrameCount = 0
30
-
31
- inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputNode.inputFormat(forBus: 0)) { buffer, _ in
32
- receivedFrames = buffer.frameLength
33
- expectation.signal()
34
- }
35
-
36
- audioEngine.prepare()
37
- do {
38
- try audioEngine.start()
39
- _ = expectation.wait(timeout: .now() + 2)
40
- audioEngine.stop()
41
- } catch {
42
- print("Error: \(error)")
43
- }
44
-
45
- inputNode.removeTap(onBus: 0)
46
-
47
- // iOS enforces minimum of ~4800 frames
48
- let passed = receivedFrames >= 4800
49
- results.append((
50
- name: "Default Buffer Size",
51
- passed: passed,
52
- message: "Requested: 1024, Received: \(receivedFrames) frames (iOS minimum: ~4800)"
53
- ))
54
-
55
- print("✓ Requested: 1024 frames")
56
- print("✓ Received: \(receivedFrames) frames")
57
- print("✓ iOS enforces minimum buffer size\n")
58
- }
59
-
60
- func testCustomBufferSizes() {
61
- print("Test 2: Custom Buffer Sizes")
62
- print("---------------------------")
63
-
64
- let inputNode = audioEngine.inputNode
65
- let sampleRate = inputNode.inputFormat(forBus: 0).sampleRate
66
-
67
- let testCases: [(duration: Double, name: String)] = [
68
- (0.01, "10ms"),
69
- (0.05, "50ms"),
70
- (0.1, "100ms"),
71
- (0.2, "200ms"),
72
- (0.5, "500ms")
73
- ]
74
-
75
- for testCase in testCases {
76
- let requestedFrames = AVAudioFrameCount(testCase.duration * sampleRate)
77
- let expectation = DispatchSemaphore(value: 0)
78
- var receivedFrames: AVAudioFrameCount = 0
79
-
80
- inputNode.removeTap(onBus: 0)
81
- inputNode.installTap(onBus: 0, bufferSize: requestedFrames, format: inputNode.inputFormat(forBus: 0)) { buffer, _ in
82
- receivedFrames = buffer.frameLength
83
- expectation.signal()
84
- }
85
-
86
- do {
87
- try audioEngine.start()
88
- _ = expectation.wait(timeout: .now() + 2)
89
- audioEngine.stop()
90
- } catch {
91
- print("Error: \(error)")
92
- }
93
-
94
- let expectedFrames: AVAudioFrameCount = requestedFrames < 4800 ? 4800 : requestedFrames
95
- let tolerance: AVAudioFrameCount = expectedFrames > 10000 ? AVAudioFrameCount(Double(expectedFrames) * 0.2) : 100
96
- let passed = abs(Int32(receivedFrames) - Int32(expectedFrames)) <= Int32(tolerance)
97
-
98
- results.append((
99
- name: "Buffer \(testCase.name)",
100
- passed: passed,
101
- message: "Requested: \(requestedFrames), Expected: \(expectedFrames), Received: \(receivedFrames)"
102
- ))
103
-
104
- print(" \(testCase.name): Requested \(requestedFrames) → Received \(receivedFrames) frames")
105
- }
106
-
107
- inputNode.removeTap(onBus: 0)
108
- print()
109
- }
110
-
111
- func testBufferSizeLimits() {
112
- print("Test 3: Buffer Size Limits")
113
- print("--------------------------")
114
-
115
- let inputNode = audioEngine.inputNode
116
-
117
- let extremeCases: [(size: AVAudioFrameCount, name: String)] = [
118
- (100, "Very small (100 frames)"),
119
- (50000, "Very large (50000 frames)")
120
- ]
121
-
122
- for testCase in extremeCases {
123
- let expectation = DispatchSemaphore(value: 0)
124
- var receivedFrames: AVAudioFrameCount = 0
125
-
126
- inputNode.removeTap(onBus: 0)
127
- inputNode.installTap(onBus: 0, bufferSize: testCase.size, format: inputNode.inputFormat(forBus: 0)) { buffer, _ in
128
- receivedFrames = buffer.frameLength
129
- expectation.signal()
130
- }
131
-
132
- do {
133
- try audioEngine.start()
134
- _ = expectation.wait(timeout: .now() + 2)
135
- audioEngine.stop()
136
- } catch {
137
- print("Error: \(error)")
138
- }
139
-
140
- let passed = receivedFrames >= 4800 && receivedFrames <= 50000
141
- results.append((
142
- name: testCase.name,
143
- passed: passed,
144
- message: "Requested: \(testCase.size), Received: \(receivedFrames)"
145
- ))
146
-
147
- print(" \(testCase.name): \(testCase.size) → \(receivedFrames) frames")
148
- }
149
-
150
- inputNode.removeTap(onBus: 0)
151
- print()
152
- }
153
-
154
- func printResults() {
155
- print("📊 Test Results")
156
- print("===============")
157
-
158
- let passed = results.filter { $0.passed }.count
159
- let total = results.count
160
-
161
- for result in results {
162
- let status = result.passed ? "✅" : "❌"
163
- print("\(status) \(result.name)")
164
- print(" \(result.message)")
165
- }
166
-
167
- print("\nSummary: \(passed)/\(total) tests passed")
168
-
169
- if passed == total {
170
- print("🎉 All tests passed!")
171
- } else {
172
- print("⚠️ Some tests failed")
173
- }
174
-
175
- print("\n📝 Key Findings:")
176
- print("- iOS AVAudioEngine enforces a minimum buffer size of ~4800 frames")
177
- print("- Requests below 4800 frames are ignored")
178
- print("- Larger buffer sizes generally work as requested")
179
- print("- Buffer accumulation is needed for small buffer durations")
180
- }
181
- }
182
-
183
- // Run the test
184
- let test = BufferDurationTest()
185
- test.runAllTests()