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.
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +0 -1
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +18 -11
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +1 -1
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +9 -7
- package/ios/audioapi/ios/core/utils/FileOptions.mm +18 -5
- package/ios/audioapi/ios/core/utils/IOSFileWriter.h +5 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +50 -20
- package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.mm +2 -7
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
414
|
+
if (isIdle()) {
|
|
415
|
+
return StopResult::Err("Recorder is not in recording state.");
|
|
416
|
+
}
|
|
412
417
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
418
|
+
state_.store(RecorderState::Idle, std::memory_order_release);
|
|
419
|
+
[nativeRecorder_ setInputArmed:false];
|
|
420
|
+
[nativeRecorder_ stop];
|
|
416
421
|
|
|
417
|
-
|
|
418
|
-
|
|
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.
|
|
@@ -164,18 +164,20 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
|
|
|
164
164
|
[audioEngine pauseIfNecessary];
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
- (
|
|
167
|
+
- (BOOL)resume
|
|
168
168
|
{
|
|
169
169
|
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
170
170
|
assert(audioEngine != nil);
|
|
171
171
|
|
|
172
|
-
if ([audioEngine startIfNecessary]) {
|
|
173
|
-
|
|
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
|
-
|
|
170
|
-
|
|
169
|
+
if (fileNameOverride.length() > 0 && properties->rotateIntervalBytes == 0) {
|
|
170
|
+
NSString *fileName =
|
|
171
|
+
[NSString stringWithFormat:@"%@.%@",
|
|
171
172
|
[NSString stringWithUTF8String:fileNameOverride.c_str()],
|
|
172
|
-
fileExtension]
|
|
173
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|