react-native-audio-api 0.13.4 → 0.13.5

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.
@@ -57,7 +57,6 @@ class RecorderAdapterNode : public AudioNode {
57
57
  AudioBuffer overflowBuffer_;
58
58
  size_t overflowSize_ = 0;
59
59
 
60
- std::atomic<bool> isInitialized_{false};
61
60
  /// Incremented around each processNode() call; adapterCleanup waits for quiescence.
62
61
  std::atomic<uint32_t> currentProcesses_{0};
63
62
  };
@@ -2,6 +2,7 @@
2
2
  #import <AudioEngine.h>
3
3
  #import <AudioSessionManager.h>
4
4
  #import <Foundation/Foundation.h>
5
+ #include <algorithm>
5
6
  #include <mutex>
6
7
  #include <unordered_map>
7
8
  #include <vector>
@@ -93,7 +94,9 @@ void IOSAudioRecorder::runSideEffects(const AudioBufferList *inputBuffer, int nu
93
94
 
94
95
  if (isConnected()) {
95
96
  if (auto lock = Locker::tryLock(adapterNodeMutex_)) {
96
- for (size_t channel = 0; channel < adapterNode_->getChannelCount(); ++channel) {
97
+ const size_t channelCount = std::min(
98
+ adapterNode_->getChannelCount(), static_cast<size_t>(inputBuffer->mNumberBuffers));
99
+ for (size_t channel = 0; channel < channelCount; ++channel) {
97
100
  auto *data = static_cast<float *>(inputBuffer->mBuffers[channel].mData);
98
101
  adapterNode_->buff_[channel]->write(data, numFrames);
99
102
  }
@@ -204,7 +207,6 @@ Result<NoneType, std::string> IOSAudioRecorder::reprepareFileWriter(
204
207
  }
205
208
 
206
209
  filePath_ = result.unwrap();
207
- recordingSegmentPaths_.push_back(filePath_);
208
210
  fileOutputConfigured_.store(true, std::memory_order_release);
209
211
  return Result<NoneType, std::string>::Ok(None);
210
212
  }
@@ -237,6 +239,8 @@ void IOSAudioRecorder::reprepareAdapter(AVAudioFormat *inputFormat, int maxInput
237
239
  return;
238
240
  }
239
241
 
242
+ // init() is a no-op on an initialized adapter, so the old format has to be torn down first.
243
+ adapterNode_->adapterCleanup();
240
244
  adapterNode_->init(
241
245
  static_cast<size_t>(maxInputBufferLength),
242
246
  recorderFormatChannelCount(inputFormat),
@@ -407,16 +411,16 @@ AudioRecorder::StopResult IOSAudioRecorder::stop()
407
411
  std::shared_ptr<RecorderAdapterNode> adapterNode;
408
412
  std::vector<std::string> outputPaths;
409
413
 
410
- {
411
- std::scoped_lock stopLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
414
+ if (isIdle()) {
415
+ return StopResult::Err("Recorder is not in recording state.");
416
+ }
412
417
 
413
- if (isIdle()) {
414
- return StopResult::Err("Recorder is not in recording state.");
415
- }
418
+ state_.store(RecorderState::Idle, std::memory_order_release);
419
+ [nativeRecorder_ setInputArmed:false];
420
+ [nativeRecorder_ stop];
416
421
 
417
- [nativeRecorder_ setInputArmed:false];
418
- state_.store(RecorderState::Idle, std::memory_order_release);
419
- [nativeRecorder_ stop];
422
+ {
423
+ std::scoped_lock stopLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
420
424
 
421
425
  if (usesFileOutput()) {
422
426
  fileOutputConfigured_.store(false, std::memory_order_release);
@@ -565,8 +569,11 @@ void IOSAudioRecorder::resume()
565
569
  return;
566
570
  }
567
571
 
568
- [nativeRecorder_ resume];
569
572
  state_.store(RecorderState::Recording, std::memory_order_release);
573
+
574
+ if (![nativeRecorder_ resume]) {
575
+ state_.store(RecorderState::Paused, std::memory_order_release);
576
+ }
570
577
  }
571
578
 
572
579
  /// @brief Checks if the recorder is currently recording.
@@ -26,7 +26,7 @@ typedef void (^AudioReceiverBlock)(const AudioBufferList *inputBuffer, int numFr
26
26
 
27
27
  - (void)pause;
28
28
 
29
- - (void)resume;
29
+ - (BOOL)resume;
30
30
 
31
31
  - (void)cleanup;
32
32
 
@@ -164,18 +164,20 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
164
164
  [audioEngine pauseIfNecessary];
165
165
  }
166
166
 
167
- - (void)resume
167
+ - (BOOL)resume
168
168
  {
169
169
  AudioEngine *audioEngine = [AudioEngine sharedInstance];
170
170
  assert(audioEngine != nil);
171
171
 
172
- if ([audioEngine startIfNecessary]) {
173
- if (self.onInputConfigurationChange != nil) {
174
- self.onInputConfigurationChange();
175
- } else {
176
- self.inputArmed = YES;
177
- }
172
+ if (![audioEngine startIfNecessary]) {
173
+ return NO;
178
174
  }
175
+
176
+ if (self.onInputConfigurationChange != nil) {
177
+ self.onInputConfigurationChange();
178
+ }
179
+
180
+ return YES;
179
181
  }
180
182
 
181
183
  - (void)cleanup
@@ -166,13 +166,26 @@ NSURL *getFileURL(
166
166
  NSString *timestamp = getTimestampString();
167
167
  NSString *fileExtension = getFileExtension(properties);
168
168
 
169
- NSString *fileName = fileNameOverride.length() > 0 && properties->rotateIntervalBytes == 0
170
- ? [NSString stringWithFormat:@"%@.%@",
169
+ if (fileNameOverride.length() > 0 && properties->rotateIntervalBytes == 0) {
170
+ NSString *fileName =
171
+ [NSString stringWithFormat:@"%@.%@",
171
172
  [NSString stringWithUTF8String:fileNameOverride.c_str()],
172
- fileExtension]
173
- : [NSString stringWithFormat:@"%@_%@.%@", fileNamePrefix, timestamp, fileExtension];
173
+ fileExtension];
174
+ return [directoryURL URLByAppendingPathComponent:fileName];
175
+ }
176
+
177
+ NSString *fileName =
178
+ [NSString stringWithFormat:@"%@_%@.%@", fileNamePrefix, timestamp, fileExtension];
179
+ NSURL *fileURL = [directoryURL URLByAppendingPathComponent:fileName];
180
+
181
+ for (NSUInteger suffix = 1; [[NSFileManager defaultManager] fileExistsAtPath:fileURL.path];
182
+ ++suffix) {
183
+ fileName = [NSString
184
+ stringWithFormat:@"%@_%@_%lu.%@", fileNamePrefix, timestamp, (unsigned long)suffix, fileExtension];
185
+ fileURL = [directoryURL URLByAppendingPathComponent:fileName];
186
+ }
174
187
 
175
- return [directoryURL URLByAppendingPathComponent:fileName];
188
+ return fileURL;
176
189
  }
177
190
 
178
191
  NSSearchPathDirectory getDirectory(const std::shared_ptr<AudioFileProperties> &properties)
@@ -67,6 +67,11 @@ class IOSFileWriter : public AudioFileWriter {
67
67
  private:
68
68
  using FreeList = slots::SlotFreeList<FILE_WRITER_POOL_SIZE>;
69
69
 
70
+ Result<NoneType, std::string> prepareConversionPipeline(
71
+ AVAudioFormat *bufferFormat,
72
+ size_t maxInputBufferLength);
73
+ void releaseConversionPipeline();
74
+
70
75
  std::vector<ios::OwnedAudioBufferListPtr> inputBufferPool_;
71
76
  size_t inputBufferBytesPerBuffer_{0};
72
77
  std::unique_ptr<FreeList> freeSlots_;
@@ -83,6 +83,24 @@ OpenFileResult IOSFileWriter::openFile(
83
83
  [[error debugDescription] UTF8String]);
84
84
  }
85
85
 
86
+ auto pipelineResult = prepareConversionPipeline(bufferFormat, maxInputBufferLength);
87
+
88
+ if (pipelineResult.is_err()) {
89
+ rollbackFailedOpen();
90
+ return OpenFileResult::Err(pipelineResult.unwrap_err());
91
+ }
92
+
93
+ isFileOpen_.store(true, std::memory_order_release);
94
+ return OpenFileResult::Ok([[fileURL_ path] UTF8String]);
95
+ }
96
+ }
97
+
98
+ /// @brief Builds the converter, conversion buffers and offloader for the given input format.
99
+ Result<NoneType, std::string> IOSFileWriter::prepareConversionPipeline(
100
+ AVAudioFormat *bufferFormat,
101
+ size_t maxInputBufferLength)
102
+ {
103
+ @autoreleasepool {
86
104
  converter_ = [[AVAudioConverter alloc] initFromFormat:bufferFormat
87
105
  toFormat:[audioFile_ processingFormat]];
88
106
  converter_.sampleRateConverterAlgorithm = AVSampleRateConverterAlgorithm_Normal;
@@ -103,8 +121,7 @@ OpenFileResult IOSFileWriter::openFile(
103
121
 
104
122
  if (converterInputBuffer_ == nil || converterOutputBuffer_ == nil || audioFile_ == nil ||
105
123
  converter_ == nil) {
106
- rollbackFailedOpen();
107
- return OpenFileResult::Err("Error creating converter buffers");
124
+ return Result<NoneType, std::string>::Err("Error creating converter buffers");
108
125
  }
109
126
 
110
127
  auto offloaderLambda = [this](WriterData data) {
@@ -126,8 +143,7 @@ OpenFileResult IOSFileWriter::openFile(
126
143
  for (size_t i = 0; i < FILE_WRITER_POOL_SIZE; ++i) {
127
144
  OwnedAudioBufferListPtr buffer(allocateOwnedAudioBufferList(bufferCount, bytesPerBuffer));
128
145
  if (buffer == nullptr) {
129
- rollbackFailedOpen();
130
- return OpenFileResult::Err("Failed to preallocate iOS file writer buffers");
146
+ return Result<NoneType, std::string>::Err("Failed to preallocate iOS file writer buffers");
131
147
  }
132
148
  inputBufferPool_.push_back(std::move(buffer));
133
149
  }
@@ -135,39 +151,53 @@ OpenFileResult IOSFileWriter::openFile(
135
151
  freeSlots_ = std::make_unique<FreeList>();
136
152
  freeSlots_->seed();
137
153
 
138
- isFileOpen_.store(true, std::memory_order_release);
139
- return OpenFileResult::Ok([[fileURL_ path] UTF8String]);
154
+ return Result<NoneType, std::string>::Ok(None);
140
155
  }
141
156
  }
142
157
 
158
+ /// @brief Tears down the converter, conversion buffers and offloader, draining pending writes.
159
+ void IOSFileWriter::releaseConversionPipeline()
160
+ {
161
+ offloader_.reset();
162
+ freeSlots_.reset();
163
+ inputBufferPool_.clear();
164
+ inputBufferBytesPerBuffer_ = 0;
165
+
166
+ converter_ = nil;
167
+ converterInputBuffer_ = nil;
168
+ converterOutputBuffer_ = nil;
169
+ }
170
+
143
171
  OpenFileResult IOSFileWriter::reopenForInputFormatChange(
144
172
  AVAudioFormat *bufferFormat,
145
173
  size_t maxInputBufferLength)
146
174
  {
147
175
  @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
- }
176
+ if (!isFileOpen() || audioFile_ == nil) {
177
+ return openFile(bufferFormat, maxInputBufferLength, "");
154
178
  }
155
179
 
156
- return openFile(bufferFormat, maxInputBufferLength, "");
180
+ // The file settings come from the recorder's file properties, not from the input, so the file
181
+ // stays open across a configuration change and the recording is not split. Only the conversion
182
+ // pipeline, which is built for the input format, is rebuilt.
183
+ releaseConversionPipeline();
184
+ bufferFormat_ = bufferFormat;
185
+
186
+ auto pipelineResult = prepareConversionPipeline(bufferFormat, maxInputBufferLength);
187
+
188
+ if (pipelineResult.is_err()) {
189
+ return OpenFileResult::Err(pipelineResult.unwrap_err());
190
+ }
191
+
192
+ return OpenFileResult::Ok([[fileURL_ path] UTF8String]);
157
193
  }
158
194
  }
159
195
 
160
196
  void IOSFileWriter::rollbackFailedOpen()
161
197
  {
162
- offloader_.reset();
163
- freeSlots_.reset();
164
- inputBufferPool_.clear();
165
- inputBufferBytesPerBuffer_ = 0;
198
+ releaseConversionPipeline();
166
199
 
167
200
  audioFile_ = nil;
168
- converter_ = nil;
169
- converterInputBuffer_ = nil;
170
- converterOutputBuffer_ = nil;
171
201
  bufferFormat_ = nil;
172
202
 
173
203
  if (fileURL_ != nil) {
@@ -58,13 +58,8 @@ OpenFileResult IOSRotatingFileWriter::reprepareStreamFormat(
58
58
  return openInnerWriter();
59
59
  }
60
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());
61
+ auto inner = std::static_pointer_cast<IOSFileWriter>(currentWriter_);
62
+ return inner->reopenForInputFormatChange(streamFormat_, streamMaxBufferSizeInFrames_);
68
63
  }
69
64
 
70
65
  void IOSRotatingFileWriter::writeAudioData(AudioDataType data, int numFrames)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-audio-api",
3
- "version": "0.13.4",
3
+ "version": "0.13.5",
4
4
  "description": "react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification",
5
5
  "bin": {
6
6
  "setup-rn-audio-api-web": "./scripts/setup-rn-audio-api-web.js"