react-native-audio-api 0.13.2 → 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.
- package/RNAudioAPI.podspec +53 -16
- 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 +7 -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 +179 -63
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +2 -0
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +37 -12
- package/ios/audioapi/ios/core/utils/IOSFileWriter.h +3 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +17 -0
- package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.h +3 -0
- package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.mm +21 -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/commonjs/core/AudioParam.js +1 -0
- package/lib/commonjs/core/AudioParam.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/module/core/AudioParam.js +1 -0
- package/lib/module/core/AudioParam.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/core/AudioParam.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/download-prebuilt-binaries.sh +94 -6
- 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/core/AudioParam.ts +1 -0
- package/src/events/types.ts +6 -0
- 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
|
|
23
|
+
static double recorderFormatSampleRate(AVAudioFormat *format)
|
|
23
24
|
{
|
|
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
|
-
}
|
|
25
|
+
return format.sampleRate;
|
|
50
26
|
}
|
|
51
27
|
|
|
52
|
-
static
|
|
28
|
+
static AVAudioChannelCount recorderFormatChannelCount(AVAudioFormat *format)
|
|
53
29
|
{
|
|
54
|
-
return
|
|
30
|
+
return format.channelCount;
|
|
55
31
|
}
|
|
56
32
|
|
|
57
|
-
static
|
|
33
|
+
static bool hasUsableRecorderFormat(AVAudioFormat *format)
|
|
58
34
|
{
|
|
59
|
-
return
|
|
35
|
+
return format != nil && format.sampleRate > 0 && format.channelCount > 0;
|
|
60
36
|
}
|
|
61
37
|
|
|
62
|
-
static
|
|
38
|
+
static std::string describeRecorderFormat(AVAudioFormat *format)
|
|
63
39
|
{
|
|
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") + "}";
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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.
|
|
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::
|
|
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
|