react-native-audio-api 0.13.3 → 0.13.4

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 (95) hide show
  1. package/android/src/main/java/com/swmansion/audioapi/system/AudioEvent.kt +1 -0
  2. package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.cpp +8 -2
  3. package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.h +1 -0
  4. package/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp +2 -0
  5. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +15 -1
  6. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.h +19 -0
  7. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +12 -1
  8. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +7 -0
  9. package/common/cpp/audioapi/core/utils/param/ParamQueueBase.hpp +1 -1
  10. package/common/cpp/audioapi/core/utils/param/ParamRenderQueue.cpp +35 -12
  11. package/common/cpp/audioapi/core/utils/param/ParamRenderQueue.h +9 -0
  12. package/common/cpp/audioapi/core/utils/worklets/SafeIncludes.h +3 -3
  13. package/common/cpp/audioapi/core/utils/worklets/WorkletsRunner.cpp +2 -2
  14. package/common/cpp/audioapi/events/AudioEvent.h +1 -0
  15. package/common/cpp/audioapi/events/AudioEventPayload.h +11 -0
  16. package/common/cpp/audioapi/events/AudioEventPayloadMapping.h +1 -0
  17. package/common/cpp/audioapi/utils/events/BufferingStateDispatcher.cpp +49 -0
  18. package/common/cpp/audioapi/utils/events/BufferingStateDispatcher.h +42 -0
  19. package/ios/audioapi/ios/core/IOSAudioRecorder.h +12 -1
  20. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +179 -63
  21. package/ios/audioapi/ios/core/NativeAudioRecorder.h +2 -0
  22. package/ios/audioapi/ios/core/NativeAudioRecorder.m +37 -12
  23. package/ios/audioapi/ios/core/utils/IOSFileWriter.h +3 -0
  24. package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +17 -0
  25. package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.h +3 -0
  26. package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.mm +21 -0
  27. package/ios/audioapi/ios/system/AudioEngine.h +2 -1
  28. package/ios/audioapi/ios/system/AudioEngine.mm +49 -23
  29. package/ios/audioapi/ios/system/SystemNotificationManager.mm +10 -0
  30. package/lib/commonjs/Audio/Audio.js +30 -3
  31. package/lib/commonjs/Audio/Audio.js.map +1 -1
  32. package/lib/commonjs/Audio/Audio.web.js +21 -3
  33. package/lib/commonjs/Audio/Audio.web.js.map +1 -1
  34. package/lib/commonjs/Audio/AudioFileSourceNode.js +20 -0
  35. package/lib/commonjs/Audio/AudioFileSourceNode.js.map +1 -1
  36. package/lib/commonjs/Audio/controls/AudioControls.js +90 -94
  37. package/lib/commonjs/Audio/controls/AudioControls.js.map +1 -1
  38. package/lib/commonjs/Audio/controls/audioControlUtils.js +48 -18
  39. package/lib/commonjs/Audio/controls/audioControlUtils.js.map +1 -1
  40. package/lib/commonjs/Audio/index.js +7 -0
  41. package/lib/commonjs/Audio/index.js.map +1 -1
  42. package/lib/commonjs/Audio/utils.js +16 -5
  43. package/lib/commonjs/Audio/utils.js.map +1 -1
  44. package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +1 -1
  45. package/lib/commonjs/core/AudioDecoder.js +4 -1
  46. package/lib/commonjs/core/AudioDecoder.js.map +1 -1
  47. package/lib/module/Audio/Audio.js +30 -3
  48. package/lib/module/Audio/Audio.js.map +1 -1
  49. package/lib/module/Audio/Audio.web.js +21 -3
  50. package/lib/module/Audio/Audio.web.js.map +1 -1
  51. package/lib/module/Audio/AudioFileSourceNode.js +20 -0
  52. package/lib/module/Audio/AudioFileSourceNode.js.map +1 -1
  53. package/lib/module/Audio/controls/AudioControls.js +92 -96
  54. package/lib/module/Audio/controls/AudioControls.js.map +1 -1
  55. package/lib/module/Audio/controls/audioControlUtils.js +48 -19
  56. package/lib/module/Audio/controls/audioControlUtils.js.map +1 -1
  57. package/lib/module/Audio/index.js +1 -0
  58. package/lib/module/Audio/index.js.map +1 -1
  59. package/lib/module/Audio/utils.js +15 -5
  60. package/lib/module/Audio/utils.js.map +1 -1
  61. package/lib/module/AudioAPIModule/AudioAPIModule.js +1 -1
  62. package/lib/module/core/AudioDecoder.js +4 -1
  63. package/lib/module/core/AudioDecoder.js.map +1 -1
  64. package/lib/typescript/Audio/Audio.d.ts.map +1 -1
  65. package/lib/typescript/Audio/Audio.web.d.ts.map +1 -1
  66. package/lib/typescript/Audio/AudioFileSourceNode.d.ts +2 -0
  67. package/lib/typescript/Audio/AudioFileSourceNode.d.ts.map +1 -1
  68. package/lib/typescript/Audio/controls/AudioControls.d.ts.map +1 -1
  69. package/lib/typescript/Audio/controls/audioControlUtils.d.ts +11 -4
  70. package/lib/typescript/Audio/controls/audioControlUtils.d.ts.map +1 -1
  71. package/lib/typescript/Audio/index.d.ts +1 -0
  72. package/lib/typescript/Audio/index.d.ts.map +1 -1
  73. package/lib/typescript/Audio/types.d.ts +15 -1
  74. package/lib/typescript/Audio/types.d.ts.map +1 -1
  75. package/lib/typescript/Audio/utils.d.ts +2 -1
  76. package/lib/typescript/Audio/utils.d.ts.map +1 -1
  77. package/lib/typescript/core/AudioDecoder.d.ts.map +1 -1
  78. package/lib/typescript/events/types.d.ts +5 -0
  79. package/lib/typescript/events/types.d.ts.map +1 -1
  80. package/lib/typescript/jsi-interfaces.d.ts +1 -0
  81. package/lib/typescript/jsi-interfaces.d.ts.map +1 -1
  82. package/package.json +8 -8
  83. package/scripts/validate-worklets-version.js +1 -1
  84. package/src/Audio/Audio.tsx +38 -2
  85. package/src/Audio/Audio.web.tsx +26 -2
  86. package/src/Audio/AudioFileSourceNode.ts +28 -0
  87. package/src/Audio/controls/AudioControls.tsx +117 -116
  88. package/src/Audio/controls/audioControlUtils.ts +63 -31
  89. package/src/Audio/index.ts +1 -0
  90. package/src/Audio/types.ts +15 -1
  91. package/src/Audio/utils.ts +20 -2
  92. package/src/AudioAPIModule/AudioAPIModule.ts +1 -1
  93. package/src/core/AudioDecoder.ts +5 -3
  94. package/src/events/types.ts +6 -0
  95. package/src/jsi-interfaces.ts +1 -0
@@ -2,7 +2,8 @@
2
2
  #import <AudioEngine.h>
3
3
  #import <AudioSessionManager.h>
4
4
  #import <Foundation/Foundation.h>
5
-
5
+ #include <mutex>
6
+ #include <unordered_map>
6
7
  #include <vector>
7
8
 
8
9
  #include <audioapi/core/sources/RecorderAdapterNode.h>
@@ -19,62 +20,26 @@
19
20
 
20
21
  namespace audioapi {
21
22
 
22
- static inline NSNumber *recorderFormatNumber(id format, NSString *key)
23
+ static double recorderFormatSampleRate(AVAudioFormat *format)
23
24
  {
24
- if (format == nil) {
25
- return nil;
26
- }
27
-
28
- if ([format isKindOfClass:[AVAudioFormat class]]) {
29
- AVAudioFormat *audioFormat = (AVAudioFormat *)format;
30
-
31
- if ([key isEqualToString:@"sampleRate"]) {
32
- return @(audioFormat.sampleRate);
33
- }
34
-
35
- if ([key isEqualToString:@"channelCount"]) {
36
- return @(audioFormat.channelCount);
37
- }
38
-
39
- if ([key isEqualToString:@"interleaved"]) {
40
- return @(audioFormat.interleaved);
41
- }
42
- }
43
-
44
- @try {
45
- id value = [format valueForKey:key];
46
- return [value isKindOfClass:[NSNumber class]] ? value : nil;
47
- } @catch (__unused NSException *exception) {
48
- return nil;
49
- }
25
+ return format.sampleRate;
50
26
  }
51
27
 
52
- static inline double recorderFormatSampleRate(id format)
28
+ static AVAudioChannelCount recorderFormatChannelCount(AVAudioFormat *format)
53
29
  {
54
- return [recorderFormatNumber(format, @"sampleRate") doubleValue];
30
+ return format.channelCount;
55
31
  }
56
32
 
57
- static inline AVAudioChannelCount recorderFormatChannelCount(id format)
33
+ static bool hasUsableRecorderFormat(AVAudioFormat *format)
58
34
  {
59
- return (AVAudioChannelCount)[recorderFormatNumber(format, @"channelCount") unsignedIntegerValue];
35
+ return format != nil && format.sampleRate > 0 && format.channelCount > 0;
60
36
  }
61
37
 
62
- static inline bool recorderFormatInterleaved(id format)
38
+ static std::string describeRecorderFormat(AVAudioFormat *format)
63
39
  {
64
- return [recorderFormatNumber(format, @"interleaved") boolValue];
65
- }
66
-
67
- static inline bool hasUsableRecorderFormat(id format)
68
- {
69
- return format != nil && recorderFormatSampleRate(format) > 0 &&
70
- recorderFormatChannelCount(format) > 0;
71
- }
72
-
73
- static std::string describeRecorderFormat(id format)
74
- {
75
- return "engineFormat={sampleRate=" + std::to_string(recorderFormatSampleRate(format)) +
76
- ", channelCount=" + std::to_string(recorderFormatChannelCount(format)) +
77
- ", interleaved=" + std::string(recorderFormatInterleaved(format) ? "true" : "false") + "}";
40
+ return "engineFormat={sampleRate=" + std::to_string(format.sampleRate) +
41
+ ", channelCount=" + std::to_string(format.channelCount) +
42
+ ", interleaved=" + (format.interleaved ? "true" : "false") + "}";
78
43
  }
79
44
 
80
45
  /// @brief Appends the shared input diagnostics (and, on the simulator, the host-mic hint)
@@ -103,31 +68,180 @@ IOSAudioRecorder::IOSAudioRecorder(
103
68
  : AudioRecorder(audioEventHandlerRegistry)
104
69
  {
105
70
  AudioReceiverBlock receiverBlock = ^(const AudioBufferList *inputBuffer, int numFrames) {
106
- if (usesFileOutput()) {
107
- if (auto lock = Locker::tryLock(fileWriterMutex_)) {
108
- fileWriter_->writeAudioData(inputBuffer, numFrames);
109
- }
71
+ runSideEffects(inputBuffer, numFrames);
72
+ };
73
+
74
+ nativeRecorder_ = [[NativeAudioRecorder alloc] initWithReceiverBlock:receiverBlock];
75
+
76
+ nativeRecorder_.onInputConfigurationChange = ^{ this->handleInputConfigurationChange(); };
77
+ }
78
+
79
+ void IOSAudioRecorder::runSideEffects(const AudioBufferList *inputBuffer, int numFrames)
80
+ {
81
+ if (usesFileOutput()) {
82
+ if (auto lock = Locker::tryLock(fileWriterMutex_)) {
83
+ fileWriter_->writeAudioData(inputBuffer, numFrames);
110
84
  }
85
+ }
111
86
 
112
- if (usesCallback()) {
113
- if (auto lock = Locker::tryLock(callbackMutex_)) {
114
- std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
115
- ->receiveAudioData(inputBuffer, numFrames);
87
+ if (usesCallback()) {
88
+ if (auto lock = Locker::tryLock(callbackMutex_)) {
89
+ std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
90
+ ->receiveAudioData(inputBuffer, numFrames);
91
+ }
92
+ }
93
+
94
+ if (isConnected()) {
95
+ if (auto lock = Locker::tryLock(adapterNodeMutex_)) {
96
+ for (size_t channel = 0; channel < adapterNode_->getChannelCount(); ++channel) {
97
+ auto *data = static_cast<float *>(inputBuffer->mBuffers[channel].mData);
98
+ adapterNode_->buff_[channel]->write(data, numFrames);
116
99
  }
117
100
  }
101
+ }
102
+ }
118
103
 
119
- if (isConnected()) {
120
- if (auto lock = Locker::tryLock(adapterNodeMutex_)) {
121
- for (size_t channel = 0; channel < adapterNode_->getChannelCount(); ++channel) {
122
- auto data = (float *)inputBuffer->mBuffers[channel].mData;
104
+ void IOSAudioRecorder::handleInputConfigurationChange()
105
+ {
106
+ if (isIdle()) {
107
+ return;
108
+ }
109
+
110
+ BOOL formatChanged = NO;
111
+ if (![nativeRecorder_ refreshResolvedInputFormatReturningChanged:&formatChanged]) {
112
+ return;
113
+ }
114
+
115
+ if (!formatChanged) {
116
+ if (state_.load(std::memory_order_acquire) == RecorderState::Recording) {
117
+ [nativeRecorder_ setInputArmed:true];
118
+ }
119
+ return;
120
+ }
121
+
122
+ reprepareForLiveInput();
123
+ }
124
+
125
+ Result<NoneType, std::string> IOSAudioRecorder::reprepareForLiveInput()
126
+ {
127
+ if (isIdle()) {
128
+ return Result<NoneType, std::string>::Ok(None);
129
+ }
130
+
131
+ // those values are resolved earlier with actual correct values
132
+ AVAudioFormat *inputFormat = [nativeRecorder_ getResolvedInputFormat];
133
+ int maxInputBufferLength = [nativeRecorder_ getResolvedBufferSize];
123
134
 
124
- adapterNode_->buff_[channel]->write(data, numFrames);
125
- }
135
+ if (!hasUsableRecorderFormat(inputFormat) || maxInputBufferLength <= 0) {
136
+ return Result<NoneType, std::string>::Err("Recorder input format is unavailable");
137
+ }
138
+
139
+ const bool shouldArmInput = state_.load(std::memory_order_acquire) == RecorderState::Recording;
140
+ [nativeRecorder_ setInputArmed:false];
141
+
142
+ if (usesFileOutput()) {
143
+ auto fileResult = reprepareFileWriter(inputFormat, maxInputBufferLength);
144
+ if (fileResult.is_err()) {
145
+ if (shouldArmInput) {
146
+ [nativeRecorder_ setInputArmed:true];
126
147
  }
148
+ return fileResult;
127
149
  }
128
- };
150
+ }
129
151
 
130
- nativeRecorder_ = [[NativeAudioRecorder alloc] initWithReceiverBlock:receiverBlock];
152
+ if (usesCallback()) {
153
+ auto callbackResult = reprepareCallback(inputFormat, maxInputBufferLength);
154
+ if (callbackResult.is_err()) {
155
+ if (shouldArmInput) {
156
+ [nativeRecorder_ setInputArmed:true];
157
+ }
158
+ return callbackResult;
159
+ }
160
+ }
161
+
162
+ if (isConnected() && adapterNode_ != nullptr) {
163
+ reprepareAdapter(inputFormat, maxInputBufferLength);
164
+ }
165
+
166
+ if (shouldArmInput) {
167
+ [nativeRecorder_ setInputArmed:true];
168
+ }
169
+
170
+ return Result<NoneType, std::string>::Ok(None);
171
+ }
172
+
173
+ Result<NoneType, std::string> IOSAudioRecorder::reprepareFileWriter(
174
+ AVAudioFormat *inputFormat,
175
+ int maxInputBufferLength)
176
+ {
177
+ std::scoped_lock lock(fileWriterMutex_);
178
+
179
+ if (fileWriter_ == nullptr) {
180
+ return Result<NoneType, std::string>::Err("File writer is unavailable");
181
+ }
182
+
183
+ if (auto rotatingWriter = std::dynamic_pointer_cast<IOSRotatingFileWriter>(fileWriter_)) {
184
+ auto result = rotatingWriter->reprepareStreamFormat(
185
+ inputFormat, static_cast<size_t>(maxInputBufferLength));
186
+ if (result.is_err()) {
187
+ fileOutputConfigured_.store(false, std::memory_order_release);
188
+ return Result<NoneType, std::string>::Err(
189
+ "Failed to reopen file for writing: " + result.unwrap_err());
190
+ }
191
+
192
+ filePath_ = result.unwrap();
193
+ fileOutputConfigured_.store(true, std::memory_order_release);
194
+ return Result<NoneType, std::string>::Ok(None);
195
+ }
196
+
197
+ auto iosWriter = std::static_pointer_cast<IOSFileWriter>(fileWriter_);
198
+ auto result =
199
+ iosWriter->reopenForInputFormatChange(inputFormat, static_cast<size_t>(maxInputBufferLength));
200
+ if (result.is_err()) {
201
+ fileOutputConfigured_.store(false, std::memory_order_release);
202
+ return Result<NoneType, std::string>::Err(
203
+ "Failed to reopen file for writing: " + result.unwrap_err());
204
+ }
205
+
206
+ filePath_ = result.unwrap();
207
+ recordingSegmentPaths_.push_back(filePath_);
208
+ fileOutputConfigured_.store(true, std::memory_order_release);
209
+ return Result<NoneType, std::string>::Ok(None);
210
+ }
211
+
212
+ Result<NoneType, std::string> IOSAudioRecorder::reprepareCallback(
213
+ AVAudioFormat *inputFormat,
214
+ int maxInputBufferLength)
215
+ {
216
+ std::scoped_lock lock(callbackMutex_);
217
+
218
+ if (dataCallback_ == nullptr) {
219
+ return Result<NoneType, std::string>::Err("Callback is unavailable");
220
+ }
221
+
222
+ auto result = std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
223
+ ->prepare(inputFormat, static_cast<size_t>(maxInputBufferLength));
224
+ if (result.is_err()) {
225
+ callbackOutputConfigured_.store(false, std::memory_order_release);
226
+ return Result<NoneType, std::string>::Err("Failed to prepare callback: " + result.unwrap_err());
227
+ }
228
+
229
+ callbackOutputConfigured_.store(true, std::memory_order_release);
230
+ return Result<NoneType, std::string>::Ok(None);
231
+ }
232
+
233
+ void IOSAudioRecorder::reprepareAdapter(AVAudioFormat *inputFormat, int maxInputBufferLength)
234
+ {
235
+ std::scoped_lock lock(adapterNodeMutex_);
236
+ if (adapterNode_ == nullptr) {
237
+ return;
238
+ }
239
+
240
+ adapterNode_->init(
241
+ static_cast<size_t>(maxInputBufferLength),
242
+ recorderFormatChannelCount(inputFormat),
243
+ recorderFormatSampleRate(inputFormat));
244
+ connectedConfigured_.store(true, std::memory_order_release);
131
245
  }
132
246
 
133
247
  IOSAudioRecorder::~IOSAudioRecorder()
@@ -136,6 +250,8 @@ IOSAudioRecorder::~IOSAudioRecorder()
136
250
  stop();
137
251
  }
138
252
 
253
+ nativeRecorder_.onInputConfigurationChange = nil;
254
+
139
255
  {
140
256
  std::scoped_lock lock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
141
257
  callbackOutputConfigured_.store(false, std::memory_order_release);
@@ -12,12 +12,14 @@ typedef void (^AudioReceiverBlock)(const AudioBufferList *inputBuffer, int numFr
12
12
  @property (nonatomic, strong) AVAudioFormat *resolvedInputFormat;
13
13
  @property (nonatomic, assign) int resolvedBufferSize;
14
14
  @property (atomic, assign) BOOL inputArmed;
15
+ @property (nonatomic, copy) void (^onInputConfigurationChange)(void);
15
16
 
16
17
  - (instancetype)initWithReceiverBlock:(AudioReceiverBlock)receiverBlock;
17
18
 
18
19
  - (int)getBufferSize;
19
20
  - (AVAudioFormat *)getResolvedInputFormat;
20
21
  - (int)getResolvedBufferSize;
22
+ - (BOOL)refreshResolvedInputFormatReturningChanged:(BOOL *)formatChanged;
21
23
  - (BOOL)start:(NSError **)error;
22
24
 
23
25
  - (void)stop;
@@ -57,17 +57,15 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
57
57
 
58
58
  - (int)getBufferSize
59
59
  {
60
- // NOTE: this method should be called only after the session is activated
61
60
  AVAudioSession *audioSession = [AVAudioSession sharedInstance];
61
+ double sampleRate = audioSession.sampleRate;
62
62
 
63
- // TMPfix: it seems that buffer duration in some cases (background/device change) can switch
64
- // to longer values, exceeding buffer size predicted after session start
65
- // since it is just a couple of buffers we can set min value of 200ms
66
- // to enforce we always have enough frames allocated to pass further down the pipeline
67
- float bufferDuration = MAX(audioSession.IOBufferDuration, 0.2);
63
+ if (self.resolvedInputFormat != nil && self.resolvedInputFormat.sampleRate > 0) {
64
+ sampleRate = self.resolvedInputFormat.sampleRate;
65
+ }
68
66
 
69
- // IOS returns buffer duration rounded, but expects the buffer size to be power of two in runtime
70
- return nextPowerOfTwo(ceil(bufferDuration * audioSession.sampleRate));
67
+ float bufferDuration = MAX(audioSession.IOBufferDuration, 0.2);
68
+ return nextPowerOfTwo(ceil(bufferDuration * sampleRate));
71
69
  }
72
70
 
73
71
  - (int)getResolvedBufferSize
@@ -75,6 +73,29 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
75
73
  return self.resolvedBufferSize;
76
74
  }
77
75
 
76
+ - (BOOL)refreshResolvedInputFormatReturningChanged:(BOOL *)formatChanged
77
+ {
78
+ AVAudioFormat *liveFormat = [self readLiveInputFormat];
79
+
80
+ if (liveFormat == nil || liveFormat.sampleRate <= 0 || liveFormat.channelCount == 0) {
81
+ return NO;
82
+ }
83
+
84
+ AVAudioFormat *previousFormat = self.resolvedInputFormat;
85
+ BOOL changed = previousFormat == nil || previousFormat.sampleRate != liveFormat.sampleRate ||
86
+ previousFormat.channelCount != liveFormat.channelCount ||
87
+ previousFormat.isInterleaved != liveFormat.isInterleaved;
88
+
89
+ self.resolvedInputFormat = liveFormat;
90
+ self.resolvedBufferSize = [self getBufferSize];
91
+
92
+ if (formatChanged != nil) {
93
+ *formatChanged = changed;
94
+ }
95
+
96
+ return YES;
97
+ }
98
+
78
99
  - (BOOL)start:(NSError **)error
79
100
  {
80
101
  AudioEngine *audioEngine = [AudioEngine sharedInstance];
@@ -93,7 +114,8 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
93
114
  self.resolvedBufferSize = 0;
94
115
 
95
116
  [audioEngine stopIfNecessary];
96
- [audioEngine attachInputNodeWithReceiverBlock:self.receiverSinkBlock];
117
+ [audioEngine attachInputNodeWithReceiverBlock:self.receiverSinkBlock
118
+ onInputConfigurationChange:self.onInputConfigurationChange];
97
119
 
98
120
  if (![audioEngine startIfNecessary]) {
99
121
  [audioEngine detachInputNode];
@@ -128,8 +150,6 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
128
150
  self.inputArmed = NO;
129
151
  [audioEngine detachInputNode];
130
152
  [audioEngine stopIfPossible];
131
- // This makes sure that the engine releases the input properly when we no longer need it
132
- // (i.e. no more misleading dot)
133
153
  [audioEngine restartAudioEngine];
134
154
  self.resolvedInputFormat = nil;
135
155
  self.resolvedBufferSize = 0;
@@ -150,7 +170,11 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
150
170
  assert(audioEngine != nil);
151
171
 
152
172
  if ([audioEngine startIfNecessary]) {
153
- self.inputArmed = YES;
173
+ if (self.onInputConfigurationChange != nil) {
174
+ self.onInputConfigurationChange();
175
+ } else {
176
+ self.inputArmed = YES;
177
+ }
154
178
  }
155
179
  }
156
180
 
@@ -161,6 +185,7 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
161
185
  self.resolvedBufferSize = 0;
162
186
  self.receiverBlock = nil;
163
187
  self.receiverSinkBlock = nil;
188
+ self.onInputConfigurationChange = nil;
164
189
  }
165
190
 
166
191
  @end
@@ -41,6 +41,9 @@ class IOSFileWriter : public AudioFileWriter {
41
41
  AVAudioFormat *bufferFormat,
42
42
  size_t maxInputBufferLength,
43
43
  const std::string &fileNameOverride);
44
+ OpenFileResult reopenForInputFormatChange(
45
+ AVAudioFormat *bufferFormat,
46
+ size_t maxInputBufferLength);
44
47
  CloseFileResult closeFile() override;
45
48
 
46
49
  void writeAudioData(const AudioBufferList *audioBufferList, int numFrames) override;
@@ -140,6 +140,23 @@ OpenFileResult IOSFileWriter::openFile(
140
140
  }
141
141
  }
142
142
 
143
+ OpenFileResult IOSFileWriter::reopenForInputFormatChange(
144
+ AVAudioFormat *bufferFormat,
145
+ size_t maxInputBufferLength)
146
+ {
147
+ @autoreleasepool {
148
+ if (isFileOpen()) {
149
+ auto closeResult = closeFile();
150
+ if (closeResult.is_err()) {
151
+ return OpenFileResult::Err(
152
+ "Failed to finalize recording segment: " + closeResult.unwrap_err());
153
+ }
154
+ }
155
+
156
+ return openFile(bufferFormat, maxInputBufferLength, "");
157
+ }
158
+ }
159
+
143
160
  void IOSFileWriter::rollbackFailedOpen()
144
161
  {
145
162
  offloader_.reset();
@@ -30,6 +30,9 @@ class IOSRotatingFileWriter : public IOSFileWriter, public RotatingFileWriter {
30
30
  AVAudioFormat *streamFormat,
31
31
  size_t streamMaxBufferSizeInFrames,
32
32
  const std::string &fileNameOverride) override;
33
+ OpenFileResult reprepareStreamFormat(
34
+ AVAudioFormat *streamFormat,
35
+ size_t streamMaxBufferSizeInFrames);
33
36
  CloseFileResult closeFile() override;
34
37
  [[nodiscard]] double getCurrentDuration() const override;
35
38
 
@@ -46,6 +46,27 @@ OpenFileResult IOSRotatingFileWriter::openFile(
46
46
  return openInnerWriter();
47
47
  }
48
48
 
49
+ OpenFileResult IOSRotatingFileWriter::reprepareStreamFormat(
50
+ AVAudioFormat *streamFormat,
51
+ size_t streamMaxBufferSizeInFrames)
52
+ {
53
+ streamFormat_ = streamFormat;
54
+ streamMaxBufferSizeInFrames_ = streamMaxBufferSizeInFrames;
55
+
56
+ if (currentWriter_ == nullptr) {
57
+ currentWriter_ = writerFactory_(fileProperties_);
58
+ return openInnerWriter();
59
+ }
60
+
61
+ rotateFiles();
62
+
63
+ if (currentWriter_ == nullptr) {
64
+ return OpenFileResult::Err("Failed to reopen file for writing after input format change");
65
+ }
66
+
67
+ return OpenFileResult::Ok(currentWriter_->getFilePath());
68
+ }
69
+
49
70
  void IOSRotatingFileWriter::writeAudioData(AudioDataType data, int numFrames)
50
71
  {
51
72
  if (currentWriter_ == nullptr) {
@@ -33,7 +33,8 @@ typedef NS_ENUM(NSInteger, AudioEngineState) {
33
33
  channelCount:(AVAudioChannelCount)channelCount;
34
34
  - (void)detachSourceNodeWithId:(NSString *)sourceNodeId;
35
35
 
36
- - (void)attachInputNodeWithReceiverBlock:(AVAudioSinkNodeReceiverBlock)receiverBlock;
36
+ - (void)attachInputNodeWithReceiverBlock:(AVAudioSinkNodeReceiverBlock)receiverBlock
37
+ onInputConfigurationChange:(void (^)(void))onInputConfigurationChange;
37
38
  - (void)detachInputNode;
38
39
  - (AVAudioFormat *)getLiveInputFormat;
39
40
 
@@ -17,6 +17,7 @@
17
17
  @interface AudioEngineInputRegistration : NSObject
18
18
 
19
19
  @property (nonatomic, copy) AVAudioSinkNodeReceiverBlock receiverBlock;
20
+ @property (nonatomic, copy) void (^onInputConfigurationChange)(void);
20
21
 
21
22
  @end
22
23
 
@@ -24,7 +25,7 @@
24
25
  @end
25
26
 
26
27
  @interface AudioEngine () {
27
- std::mutex _engineLock;
28
+ std::recursive_mutex _engineLock;
28
29
  BOOL _isRebuildingAudioEngine;
29
30
  }
30
31
 
@@ -43,6 +44,7 @@
43
44
  - (AVAudioFormat *)liveInputFormat;
44
45
  - (void)resetInputNode;
45
46
  - (void)rebuildAudioEngineAndResumeIfNeeded;
47
+ - (void)notifyConfigurationChanges;
46
48
 
47
49
  @end
48
50
 
@@ -161,6 +163,29 @@ static AudioEngine *_sharedInstance = nil;
161
163
  [self.audioEngine connect:sourceNode to:self.audioEngine.mainMixerNode format:format];
162
164
  }
163
165
 
166
+ - (AVAudioFormat *)liveInputFormat
167
+ {
168
+ std::scoped_lock lock(_engineLock);
169
+
170
+ if (self.audioEngine == nil) {
171
+ return nil;
172
+ }
173
+
174
+ AVAudioInputNode *engineInputNode = self.audioEngine.inputNode;
175
+
176
+ if (engineInputNode == nil) {
177
+ return nil;
178
+ }
179
+
180
+ AVAudioFormat *inputFormat = [engineInputNode outputFormatForBus:0];
181
+
182
+ if (inputFormat == nil || inputFormat.sampleRate <= 0 || inputFormat.channelCount == 0) {
183
+ return nil;
184
+ }
185
+
186
+ return inputFormat;
187
+ }
188
+
164
189
  - (AVAudioFormat *)currentInputConnectionFormat
165
190
  {
166
191
  AVAudioFormat *inputFormat = [self liveInputFormat];
@@ -186,6 +211,14 @@ static AudioEngine *_sharedInstance = nil;
186
211
  return YES;
187
212
  }
188
213
 
214
+ NSError *sessionError = nil;
215
+ if (![self.sessionManager ensureActive:true error:&sessionError]) {
216
+ NSLog(
217
+ @"Error while activating audio session before input materialization: %@",
218
+ [sessionError debugDescription]);
219
+ return NO;
220
+ }
221
+
189
222
  AVAudioFormat *inputFormat = [self currentInputConnectionFormat];
190
223
 
191
224
  if (inputFormat == nil) {
@@ -253,6 +286,7 @@ static AudioEngine *_sharedInstance = nil;
253
286
  }
254
287
 
255
288
  - (void)attachInputNodeWithReceiverBlock:(AVAudioSinkNodeReceiverBlock)receiverBlock
289
+ onInputConfigurationChange:(void (^)(void))onInputConfigurationChange
256
290
  {
257
291
  std::scoped_lock lock(_engineLock);
258
292
  [self createAudioEngineIfNeeded];
@@ -263,6 +297,7 @@ static AudioEngine *_sharedInstance = nil;
263
297
 
264
298
  AudioEngineInputRegistration *registration = [[AudioEngineInputRegistration alloc] init];
265
299
  registration.receiverBlock = receiverBlock;
300
+ registration.onInputConfigurationChange = onInputConfigurationChange;
266
301
  self.inputRegistration = registration;
267
302
 
268
303
  [self materializeInputNodeIfNeeded];
@@ -292,30 +327,8 @@ static AudioEngine *_sharedInstance = nil;
292
327
  [self resetInputNode];
293
328
  }
294
329
 
295
- - (AVAudioFormat *)liveInputFormat
296
- {
297
- if (self.audioEngine == nil) {
298
- return nil;
299
- }
300
-
301
- AVAudioInputNode *engineInputNode = self.audioEngine.inputNode;
302
-
303
- if (engineInputNode == nil) {
304
- return nil;
305
- }
306
-
307
- AVAudioFormat *inputFormat = [engineInputNode outputFormatForBus:0];
308
-
309
- if (inputFormat == nil || inputFormat.sampleRate <= 0 || inputFormat.channelCount == 0) {
310
- return nil;
311
- }
312
-
313
- return inputFormat;
314
- }
315
-
316
330
  - (AVAudioFormat *)getLiveInputFormat
317
331
  {
318
- std::scoped_lock lock(_engineLock);
319
332
  return [self liveInputFormat];
320
333
  }
321
334
 
@@ -377,6 +390,7 @@ static AudioEngine *_sharedInstance = nil;
377
390
 
378
391
  if (!shouldResume) {
379
392
  self.state = AudioEngineState::AudioEngineStatePaused;
393
+ [self notifyConfigurationChanges];
380
394
  return;
381
395
  }
382
396
 
@@ -388,11 +402,20 @@ static AudioEngine *_sharedInstance = nil;
388
402
  @"Error while restarting the audio engine after interruption: %@",
389
403
  [error debugDescription]);
390
404
  self.state = AudioEngineState::AudioEngineStateIdle;
405
+ [self notifyConfigurationChanges];
391
406
  return;
392
407
  }
393
408
 
394
409
  self.state = AudioEngineState::AudioEngineStateRunning;
395
410
  self.sessionDeactivationInvalidatedGraph = false;
411
+ [self notifyConfigurationChanges];
412
+ }
413
+
414
+ - (void)notifyConfigurationChanges
415
+ {
416
+ if (self.inputRegistration != nil && self.inputRegistration.onInputConfigurationChange != nil) {
417
+ self.inputRegistration.onInputConfigurationChange();
418
+ }
396
419
  }
397
420
 
398
421
  - (AudioEngineState)getState
@@ -426,6 +449,8 @@ static AudioEngine *_sharedInstance = nil;
426
449
  [self startEngine];
427
450
  }
428
451
 
452
+ [self notifyConfigurationChanges];
453
+
429
454
  _isRebuildingAudioEngine = NO;
430
455
  }
431
456
 
@@ -497,6 +522,7 @@ static AudioEngine *_sharedInstance = nil;
497
522
  std::scoped_lock lock(_engineLock);
498
523
  if (self.state == AudioEngineState::AudioEngineStateRunning && self.audioEngine != nil &&
499
524
  [self.audioEngine isRunning]) {
525
+ [self materializeTrackedNodesIfNeeded];
500
526
  return true;
501
527
  }
502
528
 
@@ -218,6 +218,16 @@ static NSString *NotificationManagerContext = @"SystemNotificationManagerContext
218
218
  invokeHandlerWithEventName:audioapi::AudioEvent::ROUTE_CHANGE
219
219
  payload:audioapi::StringPayload{
220
220
  .name = "reason", .reason = [reasonStr UTF8String]}];
221
+
222
+ switch (routeChangeReason) {
223
+ case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
224
+ case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
225
+ case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
226
+ [self handleEngineConfigurationChange:nil];
227
+ break;
228
+ default:
229
+ break;
230
+ }
221
231
  }
222
232
 
223
233
  - (void)handleMediaServicesReset:(NSNotification *)notification