react-native-audio-api 0.11.0-nightly-95f9c99-20251215 → 0.11.0-nightly-52d0b79-20251216
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/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +382 -39
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +45 -18
- package/android/src/main/cpp/audioapi/android/core/NativeAudioRecorder.hpp +9 -9
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h +33 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +170 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +46 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AudioDecoder.cpp +0 -1
- package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.cpp +83 -0
- package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.h +22 -0
- package/android/src/main/cpp/audioapi/android/core/utils/MiniaudioImplementation.cpp +8 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +493 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h +70 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/ptrs.hpp +56 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.cpp +114 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.h +34 -0
- package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp +296 -0
- package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +40 -0
- package/android/src/main/cpp/audioapi/android/system/NativeFileInfo.hpp +32 -0
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +2 -0
- package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +7 -3
- package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +1 -0
- package/android/src/main/java/com/swmansion/audioapi/system/NativeFileInfo.kt +18 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +1 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +2 -0
- package/common/cpp/audioapi/AudioAPIModuleInstaller.h +3 -11
- package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp +145 -16
- package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.h +21 -6
- package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +43 -60
- package/common/cpp/audioapi/core/inputs/AudioRecorder.h +53 -33
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +42 -14
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +12 -9
- package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +41 -0
- package/common/cpp/audioapi/core/utils/AudioFileWriter.h +44 -0
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +101 -0
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +52 -0
- package/common/cpp/audioapi/utils/AudioFileProperties.cpp +92 -0
- package/common/cpp/audioapi/utils/AudioFileProperties.h +76 -0
- package/common/cpp/audioapi/utils/Result.hpp +323 -0
- package/common/cpp/audioapi/utils/UnitConversion.h +9 -0
- package/ios/audioapi/ios/AudioAPIModule.mm +9 -14
- package/ios/audioapi/ios/core/IOSAudioPlayer.h +1 -1
- package/ios/audioapi/ios/core/IOSAudioPlayer.mm +7 -6
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +39 -13
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +302 -26
- package/ios/audioapi/ios/core/NativeAudioPlayer.m +7 -11
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +8 -9
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +70 -76
- package/ios/audioapi/ios/core/utils/AudioDecoder.mm +1 -0
- package/ios/audioapi/ios/core/utils/FileOptions.h +31 -0
- package/ios/audioapi/ios/core/utils/FileOptions.mm +195 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.h +53 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +239 -0
- package/ios/audioapi/ios/core/utils/IOSRecorderCallback.h +47 -0
- package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +185 -0
- package/ios/audioapi/ios/system/AudioEngine.h +21 -16
- package/ios/audioapi/ios/system/AudioEngine.mm +138 -130
- package/ios/audioapi/ios/system/AudioSessionManager.h +19 -9
- package/ios/audioapi/ios/system/AudioSessionManager.mm +250 -215
- package/ios/audioapi/ios/system/NotificationManager.mm +24 -42
- package/lib/commonjs/api.js +82 -109
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/core/AudioRecorder.js +159 -13
- package/lib/commonjs/core/AudioRecorder.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js +17 -14
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/commonjs/system/notification/RecordingNotificationManager.js +22 -19
- package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/commonjs/system/notification/SimpleNotificationManager.js +16 -13
- package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -1
- package/lib/commonjs/types.js +39 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/filePresets.js +43 -0
- package/lib/commonjs/utils/filePresets.js.map +1 -0
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +6 -3
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +6 -3
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/module/api.js +5 -4
- package/lib/module/api.js.map +1 -1
- package/lib/module/core/AudioRecorder.js +159 -13
- package/lib/module/core/AudioRecorder.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/module/system/notification/PlaybackNotificationManager.js +17 -14
- package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/module/system/notification/RecordingNotificationManager.js +22 -19
- package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/module/system/notification/SimpleNotificationManager.js +16 -13
- package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -1
- package/lib/module/types.js +38 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/filePresets.js +39 -0
- package/lib/module/utils/filePresets.js.map +1 -0
- package/lib/module/web-system/notification/PlaybackNotificationManager.js +6 -3
- package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/module/web-system/notification/RecordingNotificationManager.js +6 -3
- package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/typescript/api.d.ts +5 -4
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/core/AudioRecorder.d.ts +69 -7
- package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
- package/lib/typescript/events/types.d.ts +36 -2
- package/lib/typescript/events/types.d.ts.map +1 -1
- package/lib/typescript/interfaces.d.ts +24 -4
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +4 -3
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +4 -3
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +3 -2
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/types.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +79 -3
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/utils/filePresets.d.ts +9 -0
- package/lib/typescript/utils/filePresets.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +4 -3
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -1
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +4 -3
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/AudioAPIModule/globals.d.ts +1 -2
- package/src/api.ts +8 -29
- package/src/core/AudioRecorder.ts +195 -23
- package/src/events/types.ts +40 -2
- package/src/interfaces.ts +34 -5
- package/src/specs/NativeAudioAPIModule.ts +2 -2
- package/src/system/notification/PlaybackNotificationManager.ts +20 -16
- package/src/system/notification/RecordingNotificationManager.ts +26 -21
- package/src/system/notification/SimpleNotificationManager.ts +18 -13
- package/src/system/notification/types.ts +1 -0
- package/src/types.ts +89 -3
- package/src/utils/filePresets.ts +47 -0
- package/src/web-system/notification/PlaybackNotificationManager.ts +9 -5
- package/src/web-system/notification/RecordingNotificationManager.ts +9 -5
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#import <AVFoundation/AVFoundation.h>
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
|
|
4
|
+
#include <audioapi/events/AudioEventHandlerRegistry.h>
|
|
5
|
+
#include <audioapi/ios/core/utils/FileOptions.h>
|
|
6
|
+
#include <audioapi/ios/core/utils/IOSFileWriter.h>
|
|
7
|
+
#include <audioapi/utils/AudioFileProperties.h>
|
|
8
|
+
#include <audioapi/utils/Result.hpp>
|
|
9
|
+
#include <audioapi/utils/UnitConversion.h>
|
|
10
|
+
|
|
11
|
+
namespace audioapi {
|
|
12
|
+
IOSFileWriter::IOSFileWriter(
|
|
13
|
+
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
|
|
14
|
+
const std::shared_ptr<AudioFileProperties> &fileProperties)
|
|
15
|
+
: AudioFileWriter(audioEventHandlerRegistry, fileProperties)
|
|
16
|
+
{
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
IOSFileWriter::~IOSFileWriter()
|
|
20
|
+
{
|
|
21
|
+
@autoreleasepool {
|
|
22
|
+
fileURL_ = nil;
|
|
23
|
+
audioFile_ = nil;
|
|
24
|
+
converter_ = nil;
|
|
25
|
+
bufferFormat_ = nil;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// @brief Opens an audio file for writing with the specified buffer format and maximum input buffer length.
|
|
30
|
+
/// This method initializes the AVAudioFile and AVAudioConverter for audio data writing and conversion.
|
|
31
|
+
/// This method should be called from the JS thread only.
|
|
32
|
+
/// @param bufferFormat The audio format of the input buffer.
|
|
33
|
+
/// @param maxInputBufferLength The maximum length of the input buffer in frames.
|
|
34
|
+
/// @returns An OpenFileResult indicating success with the file path or an error message.
|
|
35
|
+
OpenFileResult IOSFileWriter::openFile(AVAudioFormat *bufferFormat, size_t maxInputBufferLength)
|
|
36
|
+
{
|
|
37
|
+
@autoreleasepool {
|
|
38
|
+
if (audioFile_ != nil) {
|
|
39
|
+
return OpenFileResult::Err("file already open");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
framesWritten_.store(0, std::memory_order_release);
|
|
43
|
+
bufferFormat_ = bufferFormat;
|
|
44
|
+
|
|
45
|
+
NSError *error = nil;
|
|
46
|
+
NSDictionary *settings = ios::fileoptions::getFileSettings(fileProperties_);
|
|
47
|
+
fileURL_ = ios::fileoptions::getFileURL(fileProperties_);
|
|
48
|
+
|
|
49
|
+
if (fileProperties_->sampleRate == 0 || fileProperties_->channelCount == 0) {
|
|
50
|
+
return OpenFileResult::Err(
|
|
51
|
+
"Invalid file properties: sampleRate and channelCount must be greater than 0");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (bufferFormat.sampleRate == 0 || bufferFormat.channelCount == 0) {
|
|
55
|
+
return OpenFileResult::Err(
|
|
56
|
+
"Invalid input format: sampleRate and channelCount must be greater than 0");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
audioFile_ = [[AVAudioFile alloc] initForWriting:fileURL_
|
|
60
|
+
settings:settings
|
|
61
|
+
commonFormat:AVAudioPCMFormatFloat32
|
|
62
|
+
interleaved:bufferFormat.interleaved
|
|
63
|
+
error:&error];
|
|
64
|
+
|
|
65
|
+
if (error != nil) {
|
|
66
|
+
return OpenFileResult::Err(
|
|
67
|
+
std::string("Error creating audio file for writing: ") +
|
|
68
|
+
[[error debugDescription] UTF8String]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
converter_ = [[AVAudioConverter alloc] initFromFormat:bufferFormat
|
|
72
|
+
toFormat:[audioFile_ processingFormat]];
|
|
73
|
+
converter_.sampleRateConverterAlgorithm = AVSampleRateConverterAlgorithm_Normal;
|
|
74
|
+
converter_.sampleRateConverterQuality = AVAudioQualityMax;
|
|
75
|
+
converter_.primeMethod = AVAudioConverterPrimeMethod_None;
|
|
76
|
+
|
|
77
|
+
converterInputBufferSize_ = maxInputBufferLength;
|
|
78
|
+
converterOutputBufferSize_ = std::max(
|
|
79
|
+
(double)maxInputBufferLength,
|
|
80
|
+
fileProperties_->sampleRate / bufferFormat.sampleRate * maxInputBufferLength);
|
|
81
|
+
|
|
82
|
+
converterInputBuffer_ =
|
|
83
|
+
[[AVAudioPCMBuffer alloc] initWithPCMFormat:bufferFormat
|
|
84
|
+
frameCapacity:(AVAudioFrameCount)maxInputBufferLength];
|
|
85
|
+
converterOutputBuffer_ =
|
|
86
|
+
[[AVAudioPCMBuffer alloc] initWithPCMFormat:[audioFile_ processingFormat]
|
|
87
|
+
frameCapacity:(AVAudioFrameCount)converterOutputBufferSize_];
|
|
88
|
+
|
|
89
|
+
if (converterInputBuffer_ == nil || converterOutputBuffer_ == nil || audioFile_ == nil ||
|
|
90
|
+
converter_ == nil) {
|
|
91
|
+
audioFile_ = nil;
|
|
92
|
+
converter_ = nil;
|
|
93
|
+
converterInputBuffer_ = nil;
|
|
94
|
+
converterOutputBuffer_ = nil;
|
|
95
|
+
|
|
96
|
+
return OpenFileResult::Err("Error creating converter buffers");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return OpenFileResult::Ok([[fileURL_ path] UTF8String]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// @brief Closes the currently open audio file and finalizes writing.
|
|
104
|
+
/// This method retrieves the final file duration and size before closing.
|
|
105
|
+
/// This method should be called from the JS thread only.
|
|
106
|
+
/// @returns A CloseFileResult indicating success with file duration and size or an error message.
|
|
107
|
+
CloseFileResult IOSFileWriter::closeFile()
|
|
108
|
+
{
|
|
109
|
+
@autoreleasepool {
|
|
110
|
+
NSError *error;
|
|
111
|
+
std::string filePath = [[fileURL_ path] UTF8String];
|
|
112
|
+
|
|
113
|
+
if (audioFile_ == nil) {
|
|
114
|
+
return CloseFileResult::Err("file is not open: " + filePath);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// AVAudioFile automatically finalizes the file when deallocated
|
|
118
|
+
audioFile_ = nil;
|
|
119
|
+
|
|
120
|
+
double fileDuration = CMTimeGetSeconds([[AVURLAsset URLAssetWithURL:fileURL_
|
|
121
|
+
options:nil] duration]);
|
|
122
|
+
double fileSizeBytesMb = static_cast<double>([[[NSFileManager defaultManager]
|
|
123
|
+
attributesOfItemAtPath:fileURL_.path
|
|
124
|
+
error:&error] fileSize]) /
|
|
125
|
+
MB_IN_BYTES;
|
|
126
|
+
|
|
127
|
+
if (error != nil) {
|
|
128
|
+
NSLog(@"⚠️ closeFile: error while retrieving file size");
|
|
129
|
+
fileSizeBytesMb = 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fileURL_ = nil;
|
|
133
|
+
framesWritten_.store(0, std::memory_order_release);
|
|
134
|
+
|
|
135
|
+
return CloseFileResult::Ok(std::make_tuple(fileDuration, fileSizeBytesMb));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// @brief Writes audio data to the open audio file, performing format conversion if necessary.
|
|
140
|
+
/// This method should be called from the audio thread.
|
|
141
|
+
/// @param audioBufferList Pointer to the AudioBufferList containing the audio data to write.
|
|
142
|
+
/// @param numFrames Number of audio frames in the audioBufferList.
|
|
143
|
+
/// @returns True if the write operation was successful, false otherwise.
|
|
144
|
+
bool IOSFileWriter::writeAudioData(const AudioBufferList *audioBufferList, int numFrames)
|
|
145
|
+
{
|
|
146
|
+
if (audioFile_ == nil) {
|
|
147
|
+
invokeOnErrorCallback("Attempted to write audio data when file is not open");
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@autoreleasepool {
|
|
152
|
+
NSError *error = nil;
|
|
153
|
+
AVAudioFormat *fileFormat = [audioFile_ processingFormat];
|
|
154
|
+
|
|
155
|
+
if (bufferFormat_.sampleRate == fileFormat.sampleRate &&
|
|
156
|
+
bufferFormat_.channelCount == fileFormat.channelCount &&
|
|
157
|
+
bufferFormat_.isInterleaved == fileFormat.isInterleaved) {
|
|
158
|
+
// We can use the converter input buffer as a "transport" layer to the file
|
|
159
|
+
for (size_t i = 0; i < bufferFormat_.channelCount; ++i) {
|
|
160
|
+
memcpy(
|
|
161
|
+
converterInputBuffer_.mutableAudioBufferList->mBuffers[i].mData,
|
|
162
|
+
audioBufferList->mBuffers[i].mData,
|
|
163
|
+
audioBufferList->mBuffers[i].mDataByteSize);
|
|
164
|
+
}
|
|
165
|
+
converterInputBuffer_.frameLength = numFrames;
|
|
166
|
+
|
|
167
|
+
[audioFile_ writeFromBuffer:converterInputBuffer_ error:&error];
|
|
168
|
+
|
|
169
|
+
if (error != nil) {
|
|
170
|
+
invokeOnErrorCallback(
|
|
171
|
+
std::string("Error writing audio data to file, native error: ") +
|
|
172
|
+
[[error debugDescription] UTF8String]);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (size_t i = 0; i < bufferFormat_.channelCount; ++i) {
|
|
181
|
+
memcpy(
|
|
182
|
+
converterInputBuffer_.mutableAudioBufferList->mBuffers[i].mData,
|
|
183
|
+
audioBufferList->mBuffers[i].mData,
|
|
184
|
+
audioBufferList->mBuffers[i].mDataByteSize);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
converterInputBuffer_.frameLength = numFrames;
|
|
188
|
+
|
|
189
|
+
__block BOOL handedOff = false;
|
|
190
|
+
AVAudioConverterInputBlock inputBlock = ^AVAudioBuffer *_Nullable(
|
|
191
|
+
AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus *outStatus)
|
|
192
|
+
{
|
|
193
|
+
if (handedOff) {
|
|
194
|
+
*outStatus = AVAudioConverterInputStatus_NoDataNow;
|
|
195
|
+
return nil;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
handedOff = true;
|
|
199
|
+
*outStatus = AVAudioConverterInputStatus_HaveData;
|
|
200
|
+
return converterInputBuffer_;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
[converter_ convertToBuffer:converterOutputBuffer_ error:&error withInputFromBlock:inputBlock];
|
|
204
|
+
converterOutputBuffer_.frameLength =
|
|
205
|
+
fileProperties_->sampleRate / bufferFormat_.sampleRate * numFrames;
|
|
206
|
+
|
|
207
|
+
if (error != nil) {
|
|
208
|
+
invokeOnErrorCallback(
|
|
209
|
+
std::string("Error during audio conversion, native error: ") +
|
|
210
|
+
[[error debugDescription] UTF8String]);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
[audioFile_ writeFromBuffer:converterOutputBuffer_ error:&error];
|
|
215
|
+
|
|
216
|
+
if (error != nil) {
|
|
217
|
+
invokeOnErrorCallback(
|
|
218
|
+
std::string("Error writing audio data to file, native error: ") +
|
|
219
|
+
[[error debugDescription] UTF8String]);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel);
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
double IOSFileWriter::getCurrentDuration() const
|
|
229
|
+
{
|
|
230
|
+
return static_cast<double>(framesWritten_.load(std::memory_order_acquire)) /
|
|
231
|
+
bufferFormat_.sampleRate;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
std::string IOSFileWriter::getFilePath() const
|
|
235
|
+
{
|
|
236
|
+
return [[fileURL_ path] UTF8String];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
} // namespace audioapi
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#ifndef __OBJC__ // when compiled as C++
|
|
4
|
+
typedef struct objc_object AVAudioFormat;
|
|
5
|
+
typedef struct objc_object AudioBufferList;
|
|
6
|
+
typedef struct objc_object AVAudioConverter;
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
#include <audioapi/core/utils/AudioRecorderCallback.h>
|
|
10
|
+
#include <audioapi/utils/Result.hpp>
|
|
11
|
+
#include <memory>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
namespace audioapi {
|
|
15
|
+
|
|
16
|
+
class AudioBus;
|
|
17
|
+
class CircularAudioArray;
|
|
18
|
+
class AudioEventHandlerRegistry;
|
|
19
|
+
|
|
20
|
+
class IOSRecorderCallback : public AudioRecorderCallback {
|
|
21
|
+
public:
|
|
22
|
+
IOSRecorderCallback(
|
|
23
|
+
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
|
|
24
|
+
float sampleRate,
|
|
25
|
+
size_t bufferLength,
|
|
26
|
+
int channelCount,
|
|
27
|
+
uint64_t callbackId);
|
|
28
|
+
~IOSRecorderCallback();
|
|
29
|
+
|
|
30
|
+
Result<NoneType, std::string> prepare(AVAudioFormat *bufferFormat, size_t maxInputBufferLength);
|
|
31
|
+
void cleanup() override;
|
|
32
|
+
|
|
33
|
+
void receiveAudioData(const AudioBufferList *audioBufferList, int numFrames);
|
|
34
|
+
|
|
35
|
+
protected:
|
|
36
|
+
size_t converterInputBufferSize_;
|
|
37
|
+
size_t converterOutputBufferSize_;
|
|
38
|
+
|
|
39
|
+
AVAudioFormat *bufferFormat_;
|
|
40
|
+
AVAudioFormat *callbackFormat_;
|
|
41
|
+
AVAudioConverter *converter_;
|
|
42
|
+
|
|
43
|
+
AVAudioPCMBuffer *converterInputBuffer_;
|
|
44
|
+
AVAudioPCMBuffer *converterOutputBuffer_;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
} // namespace audioapi
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#import <AVFoundation/AVFoundation.h>
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
|
|
4
|
+
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
|
|
5
|
+
#include <audioapi/core/utils/Constants.h>
|
|
6
|
+
#include <audioapi/dsp/VectorMath.h>
|
|
7
|
+
#include <audioapi/events/AudioEventHandlerRegistry.h>
|
|
8
|
+
#include <audioapi/ios/core/utils/IOSRecorderCallback.h>
|
|
9
|
+
#include <audioapi/utils/AudioArray.h>
|
|
10
|
+
#include <audioapi/utils/AudioBus.h>
|
|
11
|
+
#include <audioapi/utils/CircularAudioArray.h>
|
|
12
|
+
#include <audioapi/utils/Result.hpp>
|
|
13
|
+
#include <algorithm>
|
|
14
|
+
|
|
15
|
+
namespace audioapi {
|
|
16
|
+
|
|
17
|
+
IOSRecorderCallback::IOSRecorderCallback(
|
|
18
|
+
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
|
|
19
|
+
float sampleRate,
|
|
20
|
+
size_t bufferLength,
|
|
21
|
+
int channelCount,
|
|
22
|
+
uint64_t callbackId)
|
|
23
|
+
: AudioRecorderCallback(
|
|
24
|
+
audioEventHandlerRegistry,
|
|
25
|
+
sampleRate,
|
|
26
|
+
bufferLength,
|
|
27
|
+
channelCount,
|
|
28
|
+
callbackId)
|
|
29
|
+
{
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
IOSRecorderCallback::~IOSRecorderCallback()
|
|
33
|
+
{
|
|
34
|
+
@autoreleasepool {
|
|
35
|
+
converter_ = nil;
|
|
36
|
+
bufferFormat_ = nil;
|
|
37
|
+
callbackFormat_ = nil;
|
|
38
|
+
converterInputBuffer_ = nil;
|
|
39
|
+
converterOutputBuffer_ = nil;
|
|
40
|
+
|
|
41
|
+
for (int i = 0; i < channelCount_; ++i) {
|
|
42
|
+
circularBus_[i]->zero();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// @brief Prepares the IOSRecorderCallback for receiving audio data.
|
|
48
|
+
/// This involves setting up the audio converter and buffers based on the provided input format.
|
|
49
|
+
/// This method should be called from the JS thread only.
|
|
50
|
+
/// @param bufferFormat The format of the incoming audio data.
|
|
51
|
+
/// @param maxInputBufferLength The maximum length of the input buffer in frames.
|
|
52
|
+
/// @returns Result indicating success or error with message.
|
|
53
|
+
Result<NoneType, std::string> IOSRecorderCallback::prepare(
|
|
54
|
+
AVAudioFormat *bufferFormat,
|
|
55
|
+
size_t maxInputBufferLength)
|
|
56
|
+
{
|
|
57
|
+
@autoreleasepool {
|
|
58
|
+
bufferFormat_ = bufferFormat;
|
|
59
|
+
converterInputBufferSize_ = maxInputBufferLength;
|
|
60
|
+
|
|
61
|
+
if (bufferFormat.sampleRate <= 0 || bufferFormat.channelCount == 0) {
|
|
62
|
+
return Result<NoneType, std::string>::Err(
|
|
63
|
+
"Invalid input format: sampleRate and channelCount must be greater than 0");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (sampleRate_ <= 0 || channelCount_ == 0) {
|
|
67
|
+
return Result<NoneType, std::string>::Err(
|
|
68
|
+
"Invalid callback format: sampleRate and channelCount must be greater than 0");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
converterOutputBufferSize_ = std::max(
|
|
72
|
+
(double)maxInputBufferLength, sampleRate_ / bufferFormat.sampleRate * maxInputBufferLength);
|
|
73
|
+
|
|
74
|
+
callbackFormat_ = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
|
|
75
|
+
sampleRate:sampleRate_
|
|
76
|
+
channels:channelCount_
|
|
77
|
+
interleaved:NO];
|
|
78
|
+
|
|
79
|
+
converter_ = [[AVAudioConverter alloc] initFromFormat:bufferFormat toFormat:callbackFormat_];
|
|
80
|
+
converter_.sampleRateConverterAlgorithm = AVSampleRateConverterAlgorithm_Normal;
|
|
81
|
+
converter_.sampleRateConverterQuality = AVAudioQualityMax;
|
|
82
|
+
converter_.primeMethod = AVAudioConverterPrimeMethod_None;
|
|
83
|
+
|
|
84
|
+
converterInputBuffer_ =
|
|
85
|
+
[[AVAudioPCMBuffer alloc] initWithPCMFormat:bufferFormat_
|
|
86
|
+
frameCapacity:(AVAudioFrameCount)converterInputBufferSize_];
|
|
87
|
+
converterOutputBuffer_ =
|
|
88
|
+
[[AVAudioPCMBuffer alloc] initWithPCMFormat:callbackFormat_
|
|
89
|
+
frameCapacity:(AVAudioFrameCount)converterOutputBufferSize_];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Result<NoneType, std::string>::Ok(None);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// @brief Cleans up resources used by the IOSRecorderCallback.
|
|
96
|
+
/// This method should be called from the JS thread only.
|
|
97
|
+
void IOSRecorderCallback::cleanup()
|
|
98
|
+
{
|
|
99
|
+
@autoreleasepool {
|
|
100
|
+
emitAudioData(true);
|
|
101
|
+
|
|
102
|
+
converter_ = nil;
|
|
103
|
+
bufferFormat_ = nil;
|
|
104
|
+
callbackFormat_ = nil;
|
|
105
|
+
converterInputBuffer_ = nil;
|
|
106
|
+
converterOutputBuffer_ = nil;
|
|
107
|
+
|
|
108
|
+
for (int i = 0; i < channelCount_; ++i) {
|
|
109
|
+
circularBus_[i]->zero();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// @brief Receives audio data from the recorder, processes it, and stores it in the circular buffer.
|
|
115
|
+
/// The data is converted using AVAudioConverter if the input format differs from the user desired callback format.
|
|
116
|
+
/// This method runs on the audio thread.
|
|
117
|
+
/// @param inputBuffer Pointer to the AudioBufferList containing the incoming audio data.
|
|
118
|
+
/// @param numFrames Number of frames in the input buffer.
|
|
119
|
+
void IOSRecorderCallback::receiveAudioData(const AudioBufferList *inputBuffer, int numFrames)
|
|
120
|
+
{
|
|
121
|
+
if (!isInitialized_.load(std::memory_order_acquire)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@autoreleasepool {
|
|
126
|
+
NSError *error = nil;
|
|
127
|
+
|
|
128
|
+
if (bufferFormat_.sampleRate == sampleRate_ && bufferFormat_.channelCount == channelCount_ &&
|
|
129
|
+
!bufferFormat_.isInterleaved) {
|
|
130
|
+
// Directly write to circular buffer
|
|
131
|
+
for (int i = 0; i < channelCount_; ++i) {
|
|
132
|
+
auto *inputChannel = static_cast<float *>(inputBuffer->mBuffers[i].mData);
|
|
133
|
+
circularBus_[i]->push_back(inputChannel, numFrames);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
emitAudioData();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
size_t outputFrameCount = ceil(numFrames * (sampleRate_ / bufferFormat_.sampleRate));
|
|
141
|
+
|
|
142
|
+
for (size_t i = 0; i < bufferFormat_.channelCount; ++i) {
|
|
143
|
+
memcpy(
|
|
144
|
+
converterInputBuffer_.mutableAudioBufferList->mBuffers[i].mData,
|
|
145
|
+
inputBuffer->mBuffers[i].mData,
|
|
146
|
+
inputBuffer->mBuffers[i].mDataByteSize);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
converterInputBuffer_.frameLength = numFrames;
|
|
150
|
+
|
|
151
|
+
__block BOOL handedOff = false;
|
|
152
|
+
AVAudioConverterInputBlock inputBlock = ^AVAudioBuffer *_Nullable(
|
|
153
|
+
AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus *outStatus)
|
|
154
|
+
{
|
|
155
|
+
if (handedOff) {
|
|
156
|
+
*outStatus = AVAudioConverterInputStatus_NoDataNow;
|
|
157
|
+
return nil;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
handedOff = true;
|
|
161
|
+
*outStatus = AVAudioConverterInputStatus_HaveData;
|
|
162
|
+
return converterInputBuffer_;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
[converter_ convertToBuffer:converterOutputBuffer_ error:&error withInputFromBlock:inputBlock];
|
|
166
|
+
converterOutputBuffer_.frameLength = sampleRate_ / bufferFormat_.sampleRate * numFrames;
|
|
167
|
+
|
|
168
|
+
if (error != nil) {
|
|
169
|
+
invokeOnErrorCallback(
|
|
170
|
+
std::string("Error during audio conversion, native error: ") +
|
|
171
|
+
[[error debugDescription] UTF8String]);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (int i = 0; i < channelCount_; ++i) {
|
|
176
|
+
auto *inputChannel =
|
|
177
|
+
static_cast<float *>(converterOutputBuffer_.audioBufferList->mBuffers[i].mData);
|
|
178
|
+
circularBus_[i]->push_back(inputChannel, outputFrameCount);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
emitAudioData();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
} // namespace audioapi
|
|
@@ -5,30 +5,26 @@
|
|
|
5
5
|
|
|
6
6
|
@class AudioSessionManager;
|
|
7
7
|
|
|
8
|
+
typedef NS_ENUM(NSInteger, AudioEngineState) {
|
|
9
|
+
AudioEngineStateIdle = 0,
|
|
10
|
+
AudioEngineStateRunning,
|
|
11
|
+
AudioEngineStatePaused,
|
|
12
|
+
AudioEngineStateInterrupted
|
|
13
|
+
};
|
|
14
|
+
|
|
8
15
|
@interface AudioEngine : NSObject
|
|
9
16
|
|
|
10
|
-
@property (nonatomic, assign)
|
|
11
|
-
@property (nonatomic, assign) bool isSupposedToBeRunning;
|
|
17
|
+
@property (nonatomic, assign) AudioEngineState state;
|
|
12
18
|
@property (nonatomic, strong) AVAudioEngine *audioEngine;
|
|
13
19
|
@property (nonatomic, strong) NSMutableDictionary *sourceNodes;
|
|
14
20
|
@property (nonatomic, strong) NSMutableDictionary *sourceFormats;
|
|
15
|
-
@property (nonatomic, strong) NSMutableDictionary *sourceStates;
|
|
16
21
|
@property (nonatomic, strong) AVAudioSinkNode *inputNode;
|
|
17
22
|
@property (nonatomic, weak) AudioSessionManager *sessionManager;
|
|
18
23
|
|
|
19
|
-
- (instancetype)
|
|
20
|
-
|
|
24
|
+
- (instancetype)init;
|
|
21
25
|
+ (instancetype)sharedInstance;
|
|
26
|
+
|
|
22
27
|
- (void)cleanup;
|
|
23
|
-
- (bool)rebuildAudioEngineAndStartIfNecessary;
|
|
24
|
-
- (bool)restartAudioEngine;
|
|
25
|
-
- (bool)startEngine;
|
|
26
|
-
- (void)stopEngine;
|
|
27
|
-
- (void)pauseEngine:(NSString *)sourceNodeId;
|
|
28
|
-
- (bool)isRunning;
|
|
29
|
-
- (void)markAsInterrupted;
|
|
30
|
-
- (void)unmarkAsInterrupted;
|
|
31
|
-
- (bool)isSupposedToRun;
|
|
32
28
|
|
|
33
29
|
- (NSString *)attachSourceNode:(AVAudioSourceNode *)sourceNode format:(AVAudioFormat *)format;
|
|
34
30
|
- (void)detachSourceNodeWithId:(NSString *)sourceNodeId;
|
|
@@ -36,10 +32,19 @@
|
|
|
36
32
|
- (void)attachInputNode:(AVAudioSinkNode *)inputNode;
|
|
37
33
|
- (void)detachInputNode;
|
|
38
34
|
|
|
39
|
-
- (void)
|
|
35
|
+
- (void)onInterruptionBegin;
|
|
36
|
+
- (void)onInterruptionEnd:(bool)shouldResume;
|
|
37
|
+
|
|
38
|
+
- (AudioEngineState)getState;
|
|
40
39
|
|
|
41
40
|
- (bool)startIfNecessary;
|
|
42
|
-
- (void)stopIfNecessary;
|
|
43
41
|
- (void)pauseIfNecessary;
|
|
42
|
+
- (void)stopIfNecessary;
|
|
43
|
+
|
|
44
|
+
- (void)stopIfPossible;
|
|
45
|
+
|
|
46
|
+
- (void)restartAudioEngine;
|
|
47
|
+
|
|
48
|
+
- (void)logAudioEngineState;
|
|
44
49
|
|
|
45
50
|
@end
|