@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,154 +0,0 @@
1
- #!/usr/bin/env swift
2
-
3
- import AVFoundation
4
- import Foundation
5
-
6
- // Test script to verify if AVAudioRecorder actually supports Opus encoding
7
- // This version is macOS-compatible for testing purposes
8
-
9
- func testOpusSupport() {
10
- print("Testing AVAudioRecorder Opus Support (macOS test)...")
11
- print("--------------------------------------------------")
12
-
13
- // Test 1: Check if kAudioFormatOpus is defined
14
- let opusFormat = kAudioFormatOpus
15
- print("✓ kAudioFormatOpus is defined: \(opusFormat) (0x\(String(opusFormat, radix: 16)))")
16
-
17
- // Convert to FourCC string
18
- let fourCC = String(format: "%c%c%c%c",
19
- (opusFormat >> 24) & 0xff,
20
- (opusFormat >> 16) & 0xff,
21
- (opusFormat >> 8) & 0xff,
22
- opusFormat & 0xff)
23
- print(" FourCC: '\(fourCC)'")
24
- print()
25
-
26
- // Test 2: Try to create AVAudioRecorder with Opus settings
27
- print("Testing AVAudioRecorder with Opus settings...")
28
-
29
- let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
30
- let opusURL = documentsPath.appendingPathComponent("test_opus.opus")
31
- let aacURL = documentsPath.appendingPathComponent("test_aac.m4a")
32
-
33
- // Opus settings
34
- let opusSettings: [String: Any] = [
35
- AVFormatIDKey: kAudioFormatOpus,
36
- AVSampleRateKey: 48000,
37
- AVNumberOfChannelsKey: 1,
38
- AVEncoderBitRateKey: 64000
39
- ]
40
-
41
- // AAC settings for comparison
42
- let aacSettings: [String: Any] = [
43
- AVFormatIDKey: kAudioFormatMPEG4AAC,
44
- AVSampleRateKey: 48000,
45
- AVNumberOfChannelsKey: 1,
46
- AVEncoderBitRateKey: 64000
47
- ]
48
-
49
- // Test Opus recorder
50
- do {
51
- let opusRecorder = try AVAudioRecorder(url: opusURL, settings: opusSettings)
52
- print("✓ Opus recorder created successfully")
53
- print(" URL: \(opusURL.lastPathComponent)")
54
- print(" Settings provided: \(opusSettings)")
55
- print(" Settings after init: \(opusRecorder.settings)")
56
- print(" Format: \(opusRecorder.format)")
57
-
58
- // Check if recorder can prepare
59
- if opusRecorder.prepareToRecord() {
60
- print("✓ Opus recorder prepared successfully")
61
-
62
- // Try to record for a brief moment
63
- if opusRecorder.record() {
64
- print("✓ Opus recorder started recording")
65
- Thread.sleep(forTimeInterval: 0.5)
66
- opusRecorder.stop()
67
- print("✓ Opus recorder stopped")
68
-
69
- // Check if file was created
70
- if FileManager.default.fileExists(atPath: opusURL.path) {
71
- let attributes = try FileManager.default.attributesOfItem(atPath: opusURL.path)
72
- let fileSize = attributes[.size] as? Int64 ?? 0
73
- print("✓ Opus file created: \(fileSize) bytes")
74
-
75
- // Check file format by reading header
76
- if fileSize > 0 {
77
- let fileHandle = try FileHandle(forReadingFrom: opusURL)
78
- let headerData = fileHandle.readData(ofLength: 32)
79
- fileHandle.closeFile()
80
-
81
- print(" File header (hex): \(headerData.map { String(format: "%02X", $0) }.prefix(16).joined(separator: " "))")
82
-
83
- // Check for common audio file signatures
84
- if headerData.count >= 4 {
85
- let signature = headerData.prefix(4)
86
- if signature.starts(with: "OggS".data(using: .ascii)!) {
87
- print(" ✓ File has OGG container signature")
88
- } else if signature.starts(with: [0x00, 0x00, 0x00]) {
89
- print(" File might be MP4/M4A container")
90
- } else {
91
- print(" Unknown file signature")
92
- }
93
- }
94
- }
95
-
96
- // Clean up
97
- try FileManager.default.removeItem(at: opusURL)
98
- } else {
99
- print("✗ No Opus file was created")
100
- }
101
- } else {
102
- print("✗ Opus recorder failed to start recording")
103
- }
104
- } else {
105
- print("✗ Opus recorder failed to prepare")
106
- }
107
- } catch {
108
- print("✗ Failed to create Opus recorder: \(error)")
109
- print(" Error details: \(error.localizedDescription)")
110
- }
111
-
112
- print()
113
-
114
- // Test AAC recorder for comparison
115
- print("Testing AVAudioRecorder with AAC settings (for comparison)...")
116
- do {
117
- let aacRecorder = try AVAudioRecorder(url: aacURL, settings: aacSettings)
118
- print("✓ AAC recorder created successfully")
119
- print(" Settings after init: \(aacRecorder.settings)")
120
- print(" Format: \(aacRecorder.format)")
121
-
122
- if aacRecorder.prepareToRecord() && aacRecorder.record() {
123
- print("✓ AAC recorder working normally")
124
- Thread.sleep(forTimeInterval: 0.5)
125
- aacRecorder.stop()
126
-
127
- if FileManager.default.fileExists(atPath: aacURL.path) {
128
- let attributes = try FileManager.default.attributesOfItem(atPath: aacURL.path)
129
- let fileSize = attributes[.size] as? Int64 ?? 0
130
- print("✓ AAC file created: \(fileSize) bytes")
131
-
132
- // Check file header
133
- let fileHandle = try FileHandle(forReadingFrom: aacURL)
134
- let headerData = fileHandle.readData(ofLength: 16)
135
- fileHandle.closeFile()
136
-
137
- print(" File header (hex): \(headerData.map { String(format: "%02X", $0) }.joined(separator: " "))")
138
-
139
- try FileManager.default.removeItem(at: aacURL)
140
- }
141
- }
142
- } catch {
143
- print("✗ Failed to create AAC recorder: \(error)")
144
- }
145
-
146
- print()
147
- print("Test complete!")
148
- print()
149
- print("Note: This test runs on macOS which may have different codec support than iOS.")
150
- print("The results should be validated on an actual iOS device or simulator.")
151
- }
152
-
153
- // Run the test
154
- testOpusSupport()
@@ -1,144 +0,0 @@
1
- #!/usr/bin/env swift
2
-
3
- import Foundation
4
- import AVFoundation
5
- import Accelerate
6
-
7
- // Simple test framework
8
- struct TestResult {
9
- let name: String
10
- let passed: Bool
11
- let message: String
12
- }
13
-
14
- class AudioProcessingTest {
15
- var results: [TestResult] = []
16
-
17
- func assert(_ condition: Bool, _ message: String, file: String = #file, line: Int = #line) {
18
- let testName = "\(file.split(separator: "/").last ?? ""):\(line)"
19
- results.append(TestResult(name: testName, passed: condition, message: message))
20
- if !condition {
21
- print("❌ FAILED: \(message) at \(testName)")
22
- }
23
- }
24
-
25
- func assertEqual<T: Equatable>(_ a: T, _ b: T, _ message: String = "", file: String = #file, line: Int = #line) {
26
- let passed = a == b
27
- let msg = message.isEmpty ? "\(a) should equal \(b)" : message
28
- assert(passed, msg, file: file, line: line)
29
- }
30
-
31
- func assertClose(_ a: Float, _ b: Float, tolerance: Float = 0.001, _ message: String = "", file: String = #file, line: Int = #line) {
32
- let passed = abs(a - b) < tolerance
33
- let msg = message.isEmpty ? "\(a) should be close to \(b)" : message
34
- assert(passed, msg, file: file, line: line)
35
- }
36
-
37
- func run() {
38
- print("🧪 Running iOS Audio Processing Tests...\n")
39
-
40
- testRMSCalculation()
41
- testZeroCrossingRate()
42
- testChannelConversion()
43
- testBitDepthConversion()
44
-
45
- // Print summary
46
- let passed = results.filter { $0.passed }.count
47
- let total = results.count
48
-
49
- print("\n📊 Test Summary:")
50
- print(" Total: \(total)")
51
- print(" Passed: \(passed)")
52
- print(" Failed: \(total - passed)")
53
-
54
- if passed == total {
55
- print("\n✅ All tests passed!")
56
- } else {
57
- print("\n❌ Some tests failed!")
58
- exit(1)
59
- }
60
- }
61
-
62
- func testRMSCalculation() {
63
- print("Testing RMS calculation...")
64
-
65
- // Create a simple sine wave
66
- let sampleCount = 1024
67
- var samples = [Float](repeating: 0, count: sampleCount)
68
-
69
- // Generate 1.0 amplitude sine wave
70
- for i in 0..<sampleCount {
71
- samples[i] = sin(Float(i) * 2.0 * .pi / 64.0)
72
- }
73
-
74
- // Calculate RMS
75
- var rms: Float = 0
76
- vDSP_rmsqv(samples, 1, &rms, vDSP_Length(sampleCount))
77
-
78
- // For a sine wave, RMS should be approximately 1/sqrt(2) ≈ 0.707
79
- assertClose(rms, 0.707, tolerance: 0.01, "RMS of sine wave should be ~0.707")
80
-
81
- print("✓ RMS calculation test completed")
82
- }
83
-
84
- func testZeroCrossingRate() {
85
- print("\nTesting zero crossing rate...")
86
-
87
- // Create a signal that crosses zero 10 times
88
- let samples: [Float] = [1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1]
89
-
90
- var zcr = 0
91
- for i in 1..<samples.count {
92
- if (samples[i] >= 0 && samples[i-1] < 0) || (samples[i] < 0 && samples[i-1] >= 0) {
93
- zcr += 1
94
- }
95
- }
96
-
97
- assertEqual(zcr, 10, "Should have 10 zero crossings")
98
-
99
- print("✓ Zero crossing rate test completed")
100
- }
101
-
102
- func testChannelConversion() {
103
- print("\nTesting channel conversion...")
104
-
105
- // Mono to stereo
106
- let monoSamples: [Float] = [0.5, -0.5, 0.3, -0.3]
107
- var stereoSamples = [Float](repeating: 0, count: monoSamples.count * 2)
108
-
109
- // Simple duplication for mono to stereo
110
- for i in 0..<monoSamples.count {
111
- stereoSamples[i * 2] = monoSamples[i]
112
- stereoSamples[i * 2 + 1] = monoSamples[i]
113
- }
114
-
115
- assertEqual(stereoSamples.count, 8, "Stereo should have double the samples")
116
- assertEqual(stereoSamples[0], monoSamples[0], "Left channel should match mono")
117
- assertEqual(stereoSamples[1], monoSamples[0], "Right channel should match mono")
118
-
119
- print("✓ Channel conversion test completed")
120
- }
121
-
122
- func testBitDepthConversion() {
123
- print("\nTesting bit depth conversion...")
124
-
125
- // 16-bit to float conversion
126
- let int16Samples: [Int16] = [Int16.max, 0, Int16.min]
127
- var floatSamples = [Float](repeating: 0, count: int16Samples.count)
128
-
129
- // Convert
130
- for i in 0..<int16Samples.count {
131
- floatSamples[i] = Float(int16Samples[i]) / Float(Int16.max)
132
- }
133
-
134
- assertClose(floatSamples[0], 1.0, "Max int16 should convert to ~1.0")
135
- assertClose(floatSamples[1], 0.0, "Zero should remain zero")
136
- assertClose(floatSamples[2], -1.0, tolerance: 0.01, "Min int16 should convert to ~-1.0")
137
-
138
- print("✓ Bit depth conversion test completed")
139
- }
140
- }
141
-
142
- // Run the tests
143
- let test = AudioProcessingTest()
144
- test.run()
@@ -1,277 +0,0 @@
1
- #!/usr/bin/env swift
2
-
3
- import Foundation
4
- import AVFoundation
5
-
6
- // Simple test framework
7
- struct TestResult {
8
- let name: String
9
- let passed: Bool
10
- let message: String
11
- }
12
-
13
- class AudioRecordingTest {
14
- var results: [TestResult] = []
15
- let testDir: URL
16
- var audioRecorder: AVAudioRecorder?
17
-
18
- init() {
19
- // Create a temporary directory for test files
20
- let tempDir = FileManager.default.temporaryDirectory
21
- testDir = tempDir.appendingPathComponent("audio_recording_test_\(UUID().uuidString)")
22
- try? FileManager.default.createDirectory(at: testDir, withIntermediateDirectories: true)
23
- }
24
-
25
- deinit {
26
- // Clean up test directory
27
- try? FileManager.default.removeItem(at: testDir)
28
- }
29
-
30
- func assert(_ condition: Bool, _ message: String, file: String = #file, line: Int = #line) {
31
- let testName = "\(file.split(separator: "/").last ?? ""):\(line)"
32
- results.append(TestResult(name: testName, passed: condition, message: message))
33
- if !condition {
34
- print("❌ FAILED: \(message) at \(testName)")
35
- }
36
- }
37
-
38
- func assertEqual<T: Equatable>(_ a: T, _ b: T, _ message: String = "", file: String = #file, line: Int = #line) {
39
- let passed = a == b
40
- let msg = message.isEmpty ? "\(a) should equal \(b)" : message
41
- assert(passed, msg, file: file, line: line)
42
- }
43
-
44
- func run() {
45
- print("🧪 Running iOS Audio Recording Tests...\n")
46
-
47
- // Request permission first (in a real app)
48
- setupAudioSession()
49
-
50
- testBasicWAVRecording()
51
- testCompressedRecording()
52
- testRecordingSettings()
53
- testFileValidation()
54
-
55
- // Print summary
56
- let passed = results.filter { $0.passed }.count
57
- let total = results.count
58
-
59
- print("\n📊 Test Summary:")
60
- print(" Total: \(total)")
61
- print(" Passed: \(passed)")
62
- print(" Failed: \(total - passed)")
63
-
64
- if passed == total {
65
- print("\n✅ All tests passed!")
66
- } else {
67
- print("\n❌ Some tests failed!")
68
- exit(1)
69
- }
70
- }
71
-
72
- func setupAudioSession() {
73
- #if os(iOS)
74
- let session = AVAudioSession.sharedInstance()
75
- do {
76
- try session.setCategory(.playAndRecord, mode: .default)
77
- try session.setActive(true)
78
- print("✓ Audio session configured")
79
- } catch {
80
- print("⚠️ Failed to setup audio session: \(error)")
81
- }
82
- #else
83
- print("✓ Audio session setup skipped (macOS)")
84
- #endif
85
- }
86
-
87
- func testBasicWAVRecording() {
88
- print("\nTesting basic WAV recording...")
89
-
90
- let wavURL = testDir.appendingPathComponent("test_recording.wav")
91
-
92
- // Configure recording settings for WAV
93
- let settings: [String: Any] = [
94
- AVFormatIDKey: Int(kAudioFormatLinearPCM),
95
- AVSampleRateKey: 44100.0,
96
- AVNumberOfChannelsKey: 2,
97
- AVLinearPCMBitDepthKey: 16,
98
- AVLinearPCMIsBigEndianKey: false,
99
- AVLinearPCMIsFloatKey: false
100
- ]
101
-
102
- do {
103
- // Create recorder
104
- audioRecorder = try AVAudioRecorder(url: wavURL, settings: settings)
105
- assert(audioRecorder != nil, "Recorder should be created")
106
-
107
- // Prepare and record
108
- let prepared = audioRecorder!.prepareToRecord()
109
- assert(prepared, "Recorder should prepare successfully")
110
-
111
- let started = audioRecorder!.record()
112
- assert(started, "Recording should start")
113
-
114
- // Record for a short time
115
- Thread.sleep(forTimeInterval: 0.5)
116
-
117
- audioRecorder!.stop()
118
-
119
- // Verify file exists and has content
120
- assert(FileManager.default.fileExists(atPath: wavURL.path), "WAV file should exist")
121
-
122
- let attributes = try FileManager.default.attributesOfItem(atPath: wavURL.path)
123
- let fileSize = attributes[.size] as? Int64 ?? 0
124
- assert(fileSize > 44, "WAV file should have content beyond header")
125
-
126
- // Verify WAV header
127
- let data = try Data(contentsOf: wavURL)
128
- let riffHeader = String(data: data[0..<4], encoding: .ascii)
129
- assertEqual(riffHeader, "RIFF", "Should have RIFF header")
130
-
131
- let waveFormat = String(data: data[8..<12], encoding: .ascii)
132
- assertEqual(waveFormat, "WAVE", "Should have WAVE format")
133
-
134
- print("✓ Basic WAV recording test completed")
135
-
136
- } catch {
137
- assert(false, "Recording failed: \(error)")
138
- }
139
- }
140
-
141
- func testCompressedRecording() {
142
- print("\nTesting compressed recording (AAC)...")
143
-
144
- let aacURL = testDir.appendingPathComponent("test_recording.m4a")
145
-
146
- // Configure recording settings for AAC
147
- let settings: [String: Any] = [
148
- AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
149
- AVSampleRateKey: 44100.0,
150
- AVNumberOfChannelsKey: 2,
151
- AVEncoderBitRateKey: 128000,
152
- AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
153
- ]
154
-
155
- do {
156
- // Create recorder
157
- audioRecorder = try AVAudioRecorder(url: aacURL, settings: settings)
158
- assert(audioRecorder != nil, "AAC recorder should be created")
159
-
160
- // Record
161
- let started = audioRecorder!.record()
162
- assert(started, "AAC recording should start")
163
-
164
- Thread.sleep(forTimeInterval: 0.5)
165
- audioRecorder!.stop()
166
-
167
- // Verify file
168
- assert(FileManager.default.fileExists(atPath: aacURL.path), "AAC file should exist")
169
-
170
- let attributes = try FileManager.default.attributesOfItem(atPath: aacURL.path)
171
- let fileSize = attributes[.size] as? Int64 ?? 0
172
- assert(fileSize > 0, "AAC file should have content")
173
-
174
- // Verify it's a valid audio file by loading it
175
- let audioFile = try AVAudioFile(forReading: aacURL)
176
- assert(audioFile.length > 0, "AAC file should have audio frames")
177
- assertEqual(Int(audioFile.fileFormat.sampleRate), 44100, "Sample rate should match")
178
-
179
- print("✓ Compressed recording test completed")
180
-
181
- } catch {
182
- assert(false, "AAC recording failed: \(error)")
183
- }
184
- }
185
-
186
- func testRecordingSettings() {
187
- print("\nTesting various recording settings...")
188
-
189
- // Test different sample rates
190
- let sampleRates = [8000.0, 16000.0, 44100.0, 48000.0]
191
-
192
- for sampleRate in sampleRates {
193
- let url = testDir.appendingPathComponent("test_\(Int(sampleRate))hz.wav")
194
-
195
- let settings: [String: Any] = [
196
- AVFormatIDKey: Int(kAudioFormatLinearPCM),
197
- AVSampleRateKey: sampleRate,
198
- AVNumberOfChannelsKey: 1,
199
- AVLinearPCMBitDepthKey: 16,
200
- AVLinearPCMIsBigEndianKey: false,
201
- AVLinearPCMIsFloatKey: false
202
- ]
203
-
204
- do {
205
- let recorder = try AVAudioRecorder(url: url, settings: settings)
206
- assert(recorder.prepareToRecord(), "Should prepare at \(sampleRate)Hz")
207
-
208
- // Verify settings were applied
209
- let appliedSettings = recorder.settings
210
- let appliedRate = appliedSettings[AVSampleRateKey] as? Double ?? 0
211
- assertEqual(appliedRate, sampleRate, "Sample rate should be \(sampleRate)")
212
-
213
- } catch {
214
- assert(false, "Failed to create recorder at \(sampleRate)Hz: \(error)")
215
- }
216
- }
217
-
218
- print("✓ Recording settings test completed")
219
- }
220
-
221
- func testFileValidation() {
222
- print("\nTesting file validation and properties...")
223
-
224
- // Create a test recording
225
- let url = testDir.appendingPathComponent("validation_test.wav")
226
- let duration = 1.0 // 1 second
227
-
228
- let settings: [String: Any] = [
229
- AVFormatIDKey: Int(kAudioFormatLinearPCM),
230
- AVSampleRateKey: 16000.0,
231
- AVNumberOfChannelsKey: 1,
232
- AVLinearPCMBitDepthKey: 16,
233
- AVLinearPCMIsBigEndianKey: false,
234
- AVLinearPCMIsFloatKey: false
235
- ]
236
-
237
- do {
238
- audioRecorder = try AVAudioRecorder(url: url, settings: settings)
239
- audioRecorder!.record()
240
-
241
- // Record for the specified duration
242
- Thread.sleep(forTimeInterval: duration)
243
- audioRecorder!.stop()
244
-
245
- // Load and validate the file
246
- let audioFile = try AVAudioFile(forReading: url)
247
-
248
- // Check duration (should be close to 1 second)
249
- let recordedDuration = Double(audioFile.length) / audioFile.fileFormat.sampleRate
250
- assert(abs(recordedDuration - duration) < 0.5, "Duration should be close to \(duration)s (got \(recordedDuration)s)")
251
-
252
- // Check file format
253
- assertEqual(Int(audioFile.fileFormat.sampleRate), 16000, "Sample rate should be 16kHz")
254
- assertEqual(Int(audioFile.fileFormat.channelCount), 1, "Should be mono")
255
-
256
- // Calculate expected file size
257
- let expectedDataSize = Int(16000 * duration * 2) // 16kHz * 1s * 2 bytes per sample
258
- let expectedFileSize = expectedDataSize + 44 // Plus WAV header
259
-
260
- let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
261
- let actualFileSize = attributes[.size] as? Int64 ?? 0
262
-
263
- // Allow some tolerance (macOS may add extra metadata)
264
- assert(abs(Int(actualFileSize) - expectedFileSize) < 5000,
265
- "File size should be close to expected (\(actualFileSize) vs \(expectedFileSize))")
266
-
267
- print("✓ File validation test completed")
268
-
269
- } catch {
270
- assert(false, "File validation failed: \(error)")
271
- }
272
- }
273
- }
274
-
275
- // Run the tests
276
- let test = AudioRecordingTest()
277
- test.run()