react-native-audio-api 0.13.3 → 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/android/src/main/java/com/swmansion/audioapi/system/AudioEvent.kt +1 -0
- package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.cpp +8 -2
- package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.h +1 -0
- package/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp +2 -0
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +15 -1
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.h +19 -0
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +12 -1
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +6 -0
- package/common/cpp/audioapi/core/utils/param/ParamQueueBase.hpp +1 -1
- package/common/cpp/audioapi/core/utils/param/ParamRenderQueue.cpp +35 -12
- package/common/cpp/audioapi/core/utils/param/ParamRenderQueue.h +9 -0
- package/common/cpp/audioapi/core/utils/worklets/SafeIncludes.h +3 -3
- package/common/cpp/audioapi/core/utils/worklets/WorkletsRunner.cpp +2 -2
- package/common/cpp/audioapi/events/AudioEvent.h +1 -0
- package/common/cpp/audioapi/events/AudioEventPayload.h +11 -0
- package/common/cpp/audioapi/events/AudioEventPayloadMapping.h +1 -0
- package/common/cpp/audioapi/utils/events/BufferingStateDispatcher.cpp +49 -0
- package/common/cpp/audioapi/utils/events/BufferingStateDispatcher.h +42 -0
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +12 -1
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +195 -72
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +3 -1
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +41 -14
- package/ios/audioapi/ios/core/utils/FileOptions.mm +18 -5
- package/ios/audioapi/ios/core/utils/IOSFileWriter.h +8 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +55 -8
- package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.h +3 -0
- package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.mm +16 -0
- package/ios/audioapi/ios/system/AudioEngine.h +2 -1
- package/ios/audioapi/ios/system/AudioEngine.mm +49 -23
- package/ios/audioapi/ios/system/SystemNotificationManager.mm +10 -0
- package/lib/commonjs/Audio/Audio.js +30 -3
- package/lib/commonjs/Audio/Audio.js.map +1 -1
- package/lib/commonjs/Audio/Audio.web.js +21 -3
- package/lib/commonjs/Audio/Audio.web.js.map +1 -1
- package/lib/commonjs/Audio/AudioFileSourceNode.js +20 -0
- package/lib/commonjs/Audio/AudioFileSourceNode.js.map +1 -1
- package/lib/commonjs/Audio/controls/AudioControls.js +90 -94
- package/lib/commonjs/Audio/controls/AudioControls.js.map +1 -1
- package/lib/commonjs/Audio/controls/audioControlUtils.js +48 -18
- package/lib/commonjs/Audio/controls/audioControlUtils.js.map +1 -1
- package/lib/commonjs/Audio/index.js +7 -0
- package/lib/commonjs/Audio/index.js.map +1 -1
- package/lib/commonjs/Audio/utils.js +16 -5
- package/lib/commonjs/Audio/utils.js.map +1 -1
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/commonjs/core/AudioDecoder.js +4 -1
- package/lib/commonjs/core/AudioDecoder.js.map +1 -1
- package/lib/module/Audio/Audio.js +30 -3
- package/lib/module/Audio/Audio.js.map +1 -1
- package/lib/module/Audio/Audio.web.js +21 -3
- package/lib/module/Audio/Audio.web.js.map +1 -1
- package/lib/module/Audio/AudioFileSourceNode.js +20 -0
- package/lib/module/Audio/AudioFileSourceNode.js.map +1 -1
- package/lib/module/Audio/controls/AudioControls.js +92 -96
- package/lib/module/Audio/controls/AudioControls.js.map +1 -1
- package/lib/module/Audio/controls/audioControlUtils.js +48 -19
- package/lib/module/Audio/controls/audioControlUtils.js.map +1 -1
- package/lib/module/Audio/index.js +1 -0
- package/lib/module/Audio/index.js.map +1 -1
- package/lib/module/Audio/utils.js +15 -5
- package/lib/module/Audio/utils.js.map +1 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/module/core/AudioDecoder.js +4 -1
- package/lib/module/core/AudioDecoder.js.map +1 -1
- package/lib/typescript/Audio/Audio.d.ts.map +1 -1
- package/lib/typescript/Audio/Audio.web.d.ts.map +1 -1
- package/lib/typescript/Audio/AudioFileSourceNode.d.ts +2 -0
- package/lib/typescript/Audio/AudioFileSourceNode.d.ts.map +1 -1
- package/lib/typescript/Audio/controls/AudioControls.d.ts.map +1 -1
- package/lib/typescript/Audio/controls/audioControlUtils.d.ts +11 -4
- package/lib/typescript/Audio/controls/audioControlUtils.d.ts.map +1 -1
- package/lib/typescript/Audio/index.d.ts +1 -0
- package/lib/typescript/Audio/index.d.ts.map +1 -1
- package/lib/typescript/Audio/types.d.ts +15 -1
- package/lib/typescript/Audio/types.d.ts.map +1 -1
- package/lib/typescript/Audio/utils.d.ts +2 -1
- package/lib/typescript/Audio/utils.d.ts.map +1 -1
- package/lib/typescript/core/AudioDecoder.d.ts.map +1 -1
- package/lib/typescript/events/types.d.ts +5 -0
- package/lib/typescript/events/types.d.ts.map +1 -1
- package/lib/typescript/jsi-interfaces.d.ts +1 -0
- package/lib/typescript/jsi-interfaces.d.ts.map +1 -1
- package/package.json +8 -8
- package/scripts/validate-worklets-version.js +1 -1
- package/src/Audio/Audio.tsx +38 -2
- package/src/Audio/Audio.web.tsx +26 -2
- package/src/Audio/AudioFileSourceNode.ts +28 -0
- package/src/Audio/controls/AudioControls.tsx +117 -116
- package/src/Audio/controls/audioControlUtils.ts +63 -31
- package/src/Audio/index.ts +1 -0
- package/src/Audio/types.ts +15 -1
- package/src/Audio/utils.ts +20 -2
- package/src/AudioAPIModule/AudioAPIModule.ts +1 -1
- package/src/core/AudioDecoder.ts +5 -3
- package/src/events/types.ts +6 -0
- package/src/jsi-interfaces.ts +1 -0
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
#import <AudioEngine.h>
|
|
3
3
|
#import <AudioSessionManager.h>
|
|
4
4
|
#import <Foundation/Foundation.h>
|
|
5
|
-
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <unordered_map>
|
|
6
8
|
#include <vector>
|
|
7
9
|
|
|
8
10
|
#include <audioapi/core/sources/RecorderAdapterNode.h>
|
|
@@ -19,62 +21,26 @@
|
|
|
19
21
|
|
|
20
22
|
namespace audioapi {
|
|
21
23
|
|
|
22
|
-
static
|
|
24
|
+
static double recorderFormatSampleRate(AVAudioFormat *format)
|
|
23
25
|
{
|
|
24
|
-
|
|
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
|
-
}
|
|
26
|
+
return format.sampleRate;
|
|
50
27
|
}
|
|
51
28
|
|
|
52
|
-
static
|
|
29
|
+
static AVAudioChannelCount recorderFormatChannelCount(AVAudioFormat *format)
|
|
53
30
|
{
|
|
54
|
-
return
|
|
31
|
+
return format.channelCount;
|
|
55
32
|
}
|
|
56
33
|
|
|
57
|
-
static
|
|
34
|
+
static bool hasUsableRecorderFormat(AVAudioFormat *format)
|
|
58
35
|
{
|
|
59
|
-
return
|
|
36
|
+
return format != nil && format.sampleRate > 0 && format.channelCount > 0;
|
|
60
37
|
}
|
|
61
38
|
|
|
62
|
-
static
|
|
39
|
+
static std::string describeRecorderFormat(AVAudioFormat *format)
|
|
63
40
|
{
|
|
64
|
-
return
|
|
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") + "}";
|
|
41
|
+
return "engineFormat={sampleRate=" + std::to_string(format.sampleRate) +
|
|
42
|
+
", channelCount=" + std::to_string(format.channelCount) +
|
|
43
|
+
", interleaved=" + (format.interleaved ? "true" : "false") + "}";
|
|
78
44
|
}
|
|
79
45
|
|
|
80
46
|
/// @brief Appends the shared input diagnostics (and, on the simulator, the host-mic hint)
|
|
@@ -103,31 +69,183 @@ IOSAudioRecorder::IOSAudioRecorder(
|
|
|
103
69
|
: AudioRecorder(audioEventHandlerRegistry)
|
|
104
70
|
{
|
|
105
71
|
AudioReceiverBlock receiverBlock = ^(const AudioBufferList *inputBuffer, int numFrames) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
72
|
+
runSideEffects(inputBuffer, numFrames);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
nativeRecorder_ = [[NativeAudioRecorder alloc] initWithReceiverBlock:receiverBlock];
|
|
76
|
+
|
|
77
|
+
nativeRecorder_.onInputConfigurationChange = ^{ this->handleInputConfigurationChange(); };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void IOSAudioRecorder::runSideEffects(const AudioBufferList *inputBuffer, int numFrames)
|
|
81
|
+
{
|
|
82
|
+
if (usesFileOutput()) {
|
|
83
|
+
if (auto lock = Locker::tryLock(fileWriterMutex_)) {
|
|
84
|
+
fileWriter_->writeAudioData(inputBuffer, numFrames);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (usesCallback()) {
|
|
89
|
+
if (auto lock = Locker::tryLock(callbackMutex_)) {
|
|
90
|
+
std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
|
|
91
|
+
->receiveAudioData(inputBuffer, numFrames);
|
|
110
92
|
}
|
|
93
|
+
}
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
95
|
+
if (isConnected()) {
|
|
96
|
+
if (auto lock = Locker::tryLock(adapterNodeMutex_)) {
|
|
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) {
|
|
100
|
+
auto *data = static_cast<float *>(inputBuffer->mBuffers[channel].mData);
|
|
101
|
+
adapterNode_->buff_[channel]->write(data, numFrames);
|
|
116
102
|
}
|
|
117
103
|
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
118
106
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
107
|
+
void IOSAudioRecorder::handleInputConfigurationChange()
|
|
108
|
+
{
|
|
109
|
+
if (isIdle()) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
123
112
|
|
|
124
|
-
|
|
125
|
-
|
|
113
|
+
BOOL formatChanged = NO;
|
|
114
|
+
if (![nativeRecorder_ refreshResolvedInputFormatReturningChanged:&formatChanged]) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!formatChanged) {
|
|
119
|
+
if (state_.load(std::memory_order_acquire) == RecorderState::Recording) {
|
|
120
|
+
[nativeRecorder_ setInputArmed:true];
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
reprepareForLiveInput();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Result<NoneType, std::string> IOSAudioRecorder::reprepareForLiveInput()
|
|
129
|
+
{
|
|
130
|
+
if (isIdle()) {
|
|
131
|
+
return Result<NoneType, std::string>::Ok(None);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// those values are resolved earlier with actual correct values
|
|
135
|
+
AVAudioFormat *inputFormat = [nativeRecorder_ getResolvedInputFormat];
|
|
136
|
+
int maxInputBufferLength = [nativeRecorder_ getResolvedBufferSize];
|
|
137
|
+
|
|
138
|
+
if (!hasUsableRecorderFormat(inputFormat) || maxInputBufferLength <= 0) {
|
|
139
|
+
return Result<NoneType, std::string>::Err("Recorder input format is unavailable");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const bool shouldArmInput = state_.load(std::memory_order_acquire) == RecorderState::Recording;
|
|
143
|
+
[nativeRecorder_ setInputArmed:false];
|
|
144
|
+
|
|
145
|
+
if (usesFileOutput()) {
|
|
146
|
+
auto fileResult = reprepareFileWriter(inputFormat, maxInputBufferLength);
|
|
147
|
+
if (fileResult.is_err()) {
|
|
148
|
+
if (shouldArmInput) {
|
|
149
|
+
[nativeRecorder_ setInputArmed:true];
|
|
126
150
|
}
|
|
151
|
+
return fileResult;
|
|
127
152
|
}
|
|
128
|
-
}
|
|
153
|
+
}
|
|
129
154
|
|
|
130
|
-
|
|
155
|
+
if (usesCallback()) {
|
|
156
|
+
auto callbackResult = reprepareCallback(inputFormat, maxInputBufferLength);
|
|
157
|
+
if (callbackResult.is_err()) {
|
|
158
|
+
if (shouldArmInput) {
|
|
159
|
+
[nativeRecorder_ setInputArmed:true];
|
|
160
|
+
}
|
|
161
|
+
return callbackResult;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isConnected() && adapterNode_ != nullptr) {
|
|
166
|
+
reprepareAdapter(inputFormat, maxInputBufferLength);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (shouldArmInput) {
|
|
170
|
+
[nativeRecorder_ setInputArmed:true];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return Result<NoneType, std::string>::Ok(None);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Result<NoneType, std::string> IOSAudioRecorder::reprepareFileWriter(
|
|
177
|
+
AVAudioFormat *inputFormat,
|
|
178
|
+
int maxInputBufferLength)
|
|
179
|
+
{
|
|
180
|
+
std::scoped_lock lock(fileWriterMutex_);
|
|
181
|
+
|
|
182
|
+
if (fileWriter_ == nullptr) {
|
|
183
|
+
return Result<NoneType, std::string>::Err("File writer is unavailable");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (auto rotatingWriter = std::dynamic_pointer_cast<IOSRotatingFileWriter>(fileWriter_)) {
|
|
187
|
+
auto result = rotatingWriter->reprepareStreamFormat(
|
|
188
|
+
inputFormat, static_cast<size_t>(maxInputBufferLength));
|
|
189
|
+
if (result.is_err()) {
|
|
190
|
+
fileOutputConfigured_.store(false, std::memory_order_release);
|
|
191
|
+
return Result<NoneType, std::string>::Err(
|
|
192
|
+
"Failed to reopen file for writing: " + result.unwrap_err());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
filePath_ = result.unwrap();
|
|
196
|
+
fileOutputConfigured_.store(true, std::memory_order_release);
|
|
197
|
+
return Result<NoneType, std::string>::Ok(None);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
auto iosWriter = std::static_pointer_cast<IOSFileWriter>(fileWriter_);
|
|
201
|
+
auto result =
|
|
202
|
+
iosWriter->reopenForInputFormatChange(inputFormat, static_cast<size_t>(maxInputBufferLength));
|
|
203
|
+
if (result.is_err()) {
|
|
204
|
+
fileOutputConfigured_.store(false, std::memory_order_release);
|
|
205
|
+
return Result<NoneType, std::string>::Err(
|
|
206
|
+
"Failed to reopen file for writing: " + result.unwrap_err());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
filePath_ = result.unwrap();
|
|
210
|
+
fileOutputConfigured_.store(true, std::memory_order_release);
|
|
211
|
+
return Result<NoneType, std::string>::Ok(None);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Result<NoneType, std::string> IOSAudioRecorder::reprepareCallback(
|
|
215
|
+
AVAudioFormat *inputFormat,
|
|
216
|
+
int maxInputBufferLength)
|
|
217
|
+
{
|
|
218
|
+
std::scoped_lock lock(callbackMutex_);
|
|
219
|
+
|
|
220
|
+
if (dataCallback_ == nullptr) {
|
|
221
|
+
return Result<NoneType, std::string>::Err("Callback is unavailable");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
auto result = std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
|
|
225
|
+
->prepare(inputFormat, static_cast<size_t>(maxInputBufferLength));
|
|
226
|
+
if (result.is_err()) {
|
|
227
|
+
callbackOutputConfigured_.store(false, std::memory_order_release);
|
|
228
|
+
return Result<NoneType, std::string>::Err("Failed to prepare callback: " + result.unwrap_err());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
callbackOutputConfigured_.store(true, std::memory_order_release);
|
|
232
|
+
return Result<NoneType, std::string>::Ok(None);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
void IOSAudioRecorder::reprepareAdapter(AVAudioFormat *inputFormat, int maxInputBufferLength)
|
|
236
|
+
{
|
|
237
|
+
std::scoped_lock lock(adapterNodeMutex_);
|
|
238
|
+
if (adapterNode_ == nullptr) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// init() is a no-op on an initialized adapter, so the old format has to be torn down first.
|
|
243
|
+
adapterNode_->adapterCleanup();
|
|
244
|
+
adapterNode_->init(
|
|
245
|
+
static_cast<size_t>(maxInputBufferLength),
|
|
246
|
+
recorderFormatChannelCount(inputFormat),
|
|
247
|
+
recorderFormatSampleRate(inputFormat));
|
|
248
|
+
connectedConfigured_.store(true, std::memory_order_release);
|
|
131
249
|
}
|
|
132
250
|
|
|
133
251
|
IOSAudioRecorder::~IOSAudioRecorder()
|
|
@@ -136,6 +254,8 @@ IOSAudioRecorder::~IOSAudioRecorder()
|
|
|
136
254
|
stop();
|
|
137
255
|
}
|
|
138
256
|
|
|
257
|
+
nativeRecorder_.onInputConfigurationChange = nil;
|
|
258
|
+
|
|
139
259
|
{
|
|
140
260
|
std::scoped_lock lock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
|
|
141
261
|
callbackOutputConfigured_.store(false, std::memory_order_release);
|
|
@@ -291,16 +411,16 @@ AudioRecorder::StopResult IOSAudioRecorder::stop()
|
|
|
291
411
|
std::shared_ptr<RecorderAdapterNode> adapterNode;
|
|
292
412
|
std::vector<std::string> outputPaths;
|
|
293
413
|
|
|
294
|
-
{
|
|
295
|
-
|
|
414
|
+
if (isIdle()) {
|
|
415
|
+
return StopResult::Err("Recorder is not in recording state.");
|
|
416
|
+
}
|
|
296
417
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
418
|
+
state_.store(RecorderState::Idle, std::memory_order_release);
|
|
419
|
+
[nativeRecorder_ setInputArmed:false];
|
|
420
|
+
[nativeRecorder_ stop];
|
|
300
421
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
[nativeRecorder_ stop];
|
|
422
|
+
{
|
|
423
|
+
std::scoped_lock stopLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
|
|
304
424
|
|
|
305
425
|
if (usesFileOutput()) {
|
|
306
426
|
fileOutputConfigured_.store(false, std::memory_order_release);
|
|
@@ -449,8 +569,11 @@ void IOSAudioRecorder::resume()
|
|
|
449
569
|
return;
|
|
450
570
|
}
|
|
451
571
|
|
|
452
|
-
[nativeRecorder_ resume];
|
|
453
572
|
state_.store(RecorderState::Recording, std::memory_order_release);
|
|
573
|
+
|
|
574
|
+
if (![nativeRecorder_ resume]) {
|
|
575
|
+
state_.store(RecorderState::Paused, std::memory_order_release);
|
|
576
|
+
}
|
|
454
577
|
}
|
|
455
578
|
|
|
456
579
|
/// @brief Checks if the recorder is currently recording.
|
|
@@ -12,19 +12,21 @@ 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;
|
|
24
26
|
|
|
25
27
|
- (void)pause;
|
|
26
28
|
|
|
27
|
-
- (
|
|
29
|
+
- (BOOL)resume;
|
|
28
30
|
|
|
29
31
|
- (void)cleanup;
|
|
30
32
|
|
|
@@ -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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
return nextPowerOfTwo(ceil(bufferDuration *
|
|
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;
|
|
@@ -144,14 +164,20 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
|
|
|
144
164
|
[audioEngine pauseIfNecessary];
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
- (
|
|
167
|
+
- (BOOL)resume
|
|
148
168
|
{
|
|
149
169
|
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
150
170
|
assert(audioEngine != nil);
|
|
151
171
|
|
|
152
|
-
if ([audioEngine startIfNecessary]) {
|
|
153
|
-
|
|
172
|
+
if (![audioEngine startIfNecessary]) {
|
|
173
|
+
return NO;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (self.onInputConfigurationChange != nil) {
|
|
177
|
+
self.onInputConfigurationChange();
|
|
154
178
|
}
|
|
179
|
+
|
|
180
|
+
return YES;
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
- (void)cleanup
|
|
@@ -161,6 +187,7 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
|
|
|
161
187
|
self.resolvedBufferSize = 0;
|
|
162
188
|
self.receiverBlock = nil;
|
|
163
189
|
self.receiverSinkBlock = nil;
|
|
190
|
+
self.onInputConfigurationChange = nil;
|
|
164
191
|
}
|
|
165
192
|
|
|
166
193
|
@end
|
|
@@ -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)
|
|
@@ -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;
|
|
@@ -64,6 +67,11 @@ class IOSFileWriter : public AudioFileWriter {
|
|
|
64
67
|
private:
|
|
65
68
|
using FreeList = slots::SlotFreeList<FILE_WRITER_POOL_SIZE>;
|
|
66
69
|
|
|
70
|
+
Result<NoneType, std::string> prepareConversionPipeline(
|
|
71
|
+
AVAudioFormat *bufferFormat,
|
|
72
|
+
size_t maxInputBufferLength);
|
|
73
|
+
void releaseConversionPipeline();
|
|
74
|
+
|
|
67
75
|
std::vector<ios::OwnedAudioBufferListPtr> inputBufferPool_;
|
|
68
76
|
size_t inputBufferBytesPerBuffer_{0};
|
|
69
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,22 +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
|
|
|
143
|
-
|
|
158
|
+
/// @brief Tears down the converter, conversion buffers and offloader, draining pending writes.
|
|
159
|
+
void IOSFileWriter::releaseConversionPipeline()
|
|
144
160
|
{
|
|
145
161
|
offloader_.reset();
|
|
146
162
|
freeSlots_.reset();
|
|
147
163
|
inputBufferPool_.clear();
|
|
148
164
|
inputBufferBytesPerBuffer_ = 0;
|
|
149
165
|
|
|
150
|
-
audioFile_ = nil;
|
|
151
166
|
converter_ = nil;
|
|
152
167
|
converterInputBuffer_ = nil;
|
|
153
168
|
converterOutputBuffer_ = nil;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
OpenFileResult IOSFileWriter::reopenForInputFormatChange(
|
|
172
|
+
AVAudioFormat *bufferFormat,
|
|
173
|
+
size_t maxInputBufferLength)
|
|
174
|
+
{
|
|
175
|
+
@autoreleasepool {
|
|
176
|
+
if (!isFileOpen() || audioFile_ == nil) {
|
|
177
|
+
return openFile(bufferFormat, maxInputBufferLength, "");
|
|
178
|
+
}
|
|
179
|
+
|
|
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]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void IOSFileWriter::rollbackFailedOpen()
|
|
197
|
+
{
|
|
198
|
+
releaseConversionPipeline();
|
|
199
|
+
|
|
200
|
+
audioFile_ = nil;
|
|
154
201
|
bufferFormat_ = nil;
|
|
155
202
|
|
|
156
203
|
if (fileURL_ != nil) {
|
|
@@ -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,22 @@ 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
|
+
auto inner = std::static_pointer_cast<IOSFileWriter>(currentWriter_);
|
|
62
|
+
return inner->reopenForInputFormatChange(streamFormat_, streamMaxBufferSizeInFrames_);
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
void IOSRotatingFileWriter::writeAudioData(AudioDataType data, int numFrames)
|
|
50
66
|
{
|
|
51
67
|
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
|
|