@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +356 -5
  2. package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +306 -94
  3. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +39 -6
  4. package/build/cjs/errors/AudioStreamError.js +9 -0
  5. package/build/cjs/errors/AudioStreamError.js.map +1 -1
  6. package/build/cjs/errors/AudioStreamError.test.js +22 -1
  7. package/build/cjs/errors/AudioStreamError.test.js.map +1 -1
  8. package/build/cjs/streamAudioData.js +99 -32
  9. package/build/cjs/streamAudioData.js.map +1 -1
  10. package/build/cjs/utils/audioProcessing.js +14 -10
  11. package/build/cjs/utils/audioProcessing.js.map +1 -1
  12. package/build/esm/errors/AudioStreamError.js +9 -0
  13. package/build/esm/errors/AudioStreamError.js.map +1 -1
  14. package/build/esm/errors/AudioStreamError.test.js +22 -1
  15. package/build/esm/errors/AudioStreamError.test.js.map +1 -1
  16. package/build/esm/streamAudioData.js +99 -32
  17. package/build/esm/streamAudioData.js.map +1 -1
  18. package/build/esm/utils/audioProcessing.js +14 -10
  19. package/build/esm/utils/audioProcessing.js.map +1 -1
  20. package/build/types/errors/AudioStreamError.d.ts.map +1 -1
  21. package/build/types/streamAudioData.d.ts +5 -0
  22. package/build/types/streamAudioData.d.ts.map +1 -1
  23. package/build/types/utils/audioProcessing.d.ts +2 -2
  24. package/build/types/utils/audioProcessing.d.ts.map +1 -1
  25. package/ios/AudioStreamDecoder.swift +191 -100
  26. package/ios/AudioStudioModule.swift +48 -9
  27. package/package.json +163 -146
  28. package/scripts/README.md +58 -0
  29. package/src/errors/AudioStreamError.test.ts +29 -2
  30. package/src/errors/AudioStreamError.ts +14 -0
  31. package/src/streamAudioData.ts +146 -42
  32. package/src/utils/audioProcessing.ts +25 -14
  33. package/android/src/androidTest/assets/chorus.wav +0 -0
  34. package/android/src/androidTest/assets/jfk.wav +0 -0
  35. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  36. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  37. package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +0 -190
  38. package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +0 -197
  39. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +0 -487
  40. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +0 -250
  41. package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +0 -186
  42. package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
  43. package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +0 -324
  44. package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +0 -253
  45. package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +0 -218
  46. package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +0 -120
  47. package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +0 -345
  48. package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +0 -340
  49. package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +0 -252
  50. package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +0 -95
  51. package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +0 -43
  52. package/android/src/test/java/net/siteed/audiostudio/AndroidCallStateTest.kt +0 -37
  53. package/android/src/test/java/net/siteed/audiostudio/AndroidEventEmitterTest.kt +0 -28
  54. package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +0 -279
  55. package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +0 -249
  56. package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +0 -151
  57. package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +0 -273
  58. package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +0 -140
  59. package/android/src/test/java/net/siteed/audiostudio/InterruptionAutoResumePolicyTest.kt +0 -49
  60. package/android/src/test/resources/chorus.wav +0 -0
  61. package/android/src/test/resources/generate_test_audio.py +0 -94
  62. package/android/src/test/resources/jfk.wav +0 -0
  63. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  64. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  65. package/ios/AudioStudioTests/AudioFileHandlerTests.swift +0 -338
  66. package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +0 -331
  67. package/ios/AudioStudioTests/AudioStreamDecoderTests.swift +0 -128
  68. package/ios/AudioStudioTests/AudioTestHelpers.swift +0 -130
  69. package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +0 -334
  70. package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +0 -105
  71. package/ios/AudioStudioTests/Info.plist +0 -22
  72. package/ios/AudioStudioTests/README.md +0 -39
  73. package/ios/AudioStudioTests/SimpleAudioTest.swift +0 -98
  74. package/ios/AudioStudioTests/TestAudioGenerator.swift +0 -75
  75. package/ios/tests/README.md +0 -41
  76. package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
  77. package/ios/tests/integration/buffer_duration_test.swift +0 -185
  78. package/ios/tests/integration/compressed_only_output_test.swift +0 -271
  79. package/ios/tests/integration/output_control_test.swift +0 -322
  80. package/ios/tests/integration/run_integration_tests.sh +0 -37
  81. package/ios/tests/opus_support_test_macos.swift +0 -154
  82. package/ios/tests/standalone/audio_processing_test.swift +0 -144
  83. package/ios/tests/standalone/audio_recording_test.swift +0 -277
  84. package/ios/tests/standalone/audio_streaming_test.swift +0 -249
  85. package/ios/tests/standalone/standalone_test.swift +0 -144
@@ -1,22 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>CFBundleDevelopmentRegion</key>
6
- <string>$(DEVELOPMENT_LANGUAGE)</string>
7
- <key>CFBundleExecutable</key>
8
- <string>$(EXECUTABLE_NAME)</string>
9
- <key>CFBundleIdentifier</key>
10
- <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
- <key>CFBundleInfoDictionaryVersion</key>
12
- <string>6.0</string>
13
- <key>CFBundleName</key>
14
- <string>$(PRODUCT_NAME)</string>
15
- <key>CFBundlePackageType</key>
16
- <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
17
- <key>CFBundleShortVersionString</key>
18
- <string>1.0</string>
19
- <key>CFBundleVersion</key>
20
- <string>1</string>
21
- </dict>
22
- </plist>
@@ -1,39 +0,0 @@
1
- # AudioStudio iOS Unit Tests
2
-
3
- This directory contains unit tests for the AudioStudio iOS module.
4
-
5
- ## Test Files
6
-
7
- - `AudioTestHelpers.swift` - Common test utilities and extensions
8
- - `AudioFormatUtilsTests.swift` - Tests for audio format utilities
9
- - `AudioFileHandlerTests.swift` - Tests for file handling
10
- - `SimpleAudioTest.swift` - Basic audio functionality tests
11
- - `TestAudioGenerator.swift` - Audio generation utilities for testing
12
- - `CompressedOnlyOutputTests.swift` - Tests for compressed-only output feature (Issue #244)
13
-
14
- ## Running Tests
15
-
16
- ### In Xcode
17
- 1. Open the workspace/project containing AudioStudio
18
- 2. Select the test target
19
- 3. Press `Cmd+U` to run all tests or click on individual test methods
20
-
21
- ### From Command Line
22
- ```bash
23
- # Run all tests
24
- xcodebuild test -scheme AudioStudioTests -destination 'platform=iOS Simulator,name=iPhone 15'
25
-
26
- # Run specific test class
27
- xcodebuild test -scheme AudioStudioTests -destination 'platform=iOS Simulator,name=iPhone 15' -only-testing:AudioStudioTests/CompressedOnlyOutputTests
28
- ```
29
-
30
- ## Compressed-Only Output Tests
31
-
32
- The `CompressedOnlyOutputTests.swift` file tests the fix for Issue #244, ensuring that:
33
- - Compression info is properly returned when primary output is disabled
34
- - AAC format works correctly
35
- - Opus format falls back to AAC on iOS
36
- - Compressed file URIs are accessible
37
- - File sizes and metadata are correctly reported
38
-
39
- These tests verify that users can access compressed audio files even when primary WAV output is disabled.
@@ -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()