react-native-audio-api 0.12.1 → 0.12.2
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/README.md +8 -3
- package/RNAudioAPI.podspec +0 -2
- package/android/src/main/cpp/audioapi/CMakeLists.txt +8 -2
- package/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +7 -29
- package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.cpp +83 -0
- package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.h +14 -0
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +4 -3
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +1 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +37 -21
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +1 -1
- package/common/cpp/audioapi/AudioAPIModuleInstaller.h +21 -0
- package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp +26 -4
- package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h +1 -0
- package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.cpp +2 -2
- package/common/cpp/audioapi/HostObjects/utils/AudioDecoderHostObject.cpp +3 -3
- package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.cpp +60 -0
- package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.h +24 -0
- package/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +2 -2
- package/common/cpp/audioapi/core/OfflineAudioContext.cpp +0 -1
- package/common/cpp/audioapi/core/OfflineAudioContext.h +0 -1
- package/common/cpp/audioapi/core/effects/ConvolverNode.cpp +4 -10
- package/common/cpp/audioapi/core/effects/ConvolverNode.h +0 -4
- package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp +13 -11
- package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h +5 -9
- package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp +47 -139
- package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h +11 -8
- package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +29 -114
- package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h +6 -8
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +9 -11
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.h +1 -1
- package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +2 -2
- package/common/cpp/audioapi/core/types/AudioFormat.h +3 -1
- package/common/cpp/audioapi/core/utils/AudioDecoder.cpp +120 -91
- package/common/cpp/audioapi/core/utils/AudioDecoder.h +24 -101
- package/common/cpp/audioapi/core/utils/AudioFileConcatenator.cpp +862 -0
- package/common/cpp/audioapi/core/utils/AudioFileConcatenator.h +164 -0
- package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +2 -4
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +7 -12
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +2 -0
- package/common/cpp/audioapi/core/utils/buffer/BufferProcessingDirection.h +6 -0
- package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.cpp +110 -0
- package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.h +75 -0
- package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.cpp +129 -0
- package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.h +55 -0
- package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.cpp +95 -0
- package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.h +52 -0
- package/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp +65 -157
- package/common/cpp/audioapi/events/AudioEventHandlerRegistry.h +52 -33
- package/common/cpp/audioapi/events/AudioEventPayload.h +87 -0
- package/common/cpp/audioapi/events/IAudioEventHandlerRegistry.h +12 -12
- package/common/cpp/audioapi/libs/decoding/IncrementalAudioDecoder.h +12 -10
- package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +152 -78
- package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +6 -6
- package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.cpp +34 -20
- package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.h +4 -4
- package/common/cpp/audioapi/utils/AudioArray.hpp +6 -1
- package/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp +1 -1
- package/common/cpp/audioapi/utils/TaskOffloader.hpp +3 -5
- package/ios/audioapi/ios/AudioAPIModule.h +2 -1
- package/ios/audioapi/ios/AudioAPIModule.mm +4 -25
- package/ios/audioapi/ios/core/IOSAudioPlayer.h +16 -2
- package/ios/audioapi/ios/core/IOSAudioPlayer.mm +90 -24
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +1 -1
- package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +64 -20
- package/ios/audioapi/ios/system/AudioSessionManager.mm +18 -7
- package/ios/audioapi/ios/system/SystemNotificationManager.mm +22 -22
- package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +10 -13
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js.map +1 -1
- package/lib/commonjs/api.js +8 -0
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/api.web.js +5 -0
- package/lib/commonjs/api.web.js.map +1 -1
- package/lib/commonjs/core/AudioFileUtils.js +35 -0
- package/lib/commonjs/core/AudioFileUtils.js.map +1 -0
- package/lib/commonjs/mock/index.js +15 -1
- package/lib/commonjs/mock/index.js.map +1 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js.map +1 -1
- package/lib/module/api.js +1 -0
- package/lib/module/api.js.map +1 -1
- package/lib/module/api.web.js +3 -0
- package/lib/module/api.web.js.map +1 -1
- package/lib/module/core/AudioFileUtils.js +31 -0
- package/lib/module/core/AudioFileUtils.js.map +1 -0
- package/lib/module/mock/index.js +14 -1
- package/lib/module/mock/index.js.map +1 -1
- package/lib/typescript/AudioAPIModule/AudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/api.d.ts +1 -0
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/api.web.d.ts +1 -0
- package/lib/typescript/api.web.d.ts.map +1 -1
- package/lib/typescript/core/AudioFileUtils.d.ts +2 -0
- package/lib/typescript/core/AudioFileUtils.d.ts.map +1 -0
- package/lib/typescript/interfaces.d.ts +3 -0
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/mock/index.d.ts +3 -1
- package/lib/typescript/mock/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/download-prebuilt-binaries.sh +34 -1
- package/src/AudioAPIModule/AudioAPIModule.ts +1 -0
- package/src/AudioAPIModule/globals.d.ts +3 -0
- package/src/api.ts +1 -0
- package/src/api.web.ts +7 -0
- package/src/core/AudioFileUtils.ts +49 -0
- package/src/interfaces.ts +7 -0
- package/src/mock/index.ts +29 -0
|
@@ -7,20 +7,31 @@
|
|
|
7
7
|
#include <algorithm>
|
|
8
8
|
#include <cmath>
|
|
9
9
|
#include <cstddef>
|
|
10
|
+
#include <string>
|
|
10
11
|
#include <vector>
|
|
11
12
|
|
|
12
13
|
namespace audioapi::miniaudio_decoder {
|
|
13
14
|
|
|
14
15
|
namespace {
|
|
15
16
|
|
|
17
|
+
std::string parseMiniAudioError(ma_result result) {
|
|
18
|
+
const char *description = ma_result_description(result);
|
|
19
|
+
if (description == nullptr) {
|
|
20
|
+
return "Unknown MiniAudio error (" + std::to_string(result) + ")";
|
|
21
|
+
}
|
|
22
|
+
return std::string(description) + " (" + std::to_string(result) + ")";
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
ma_decoder_config makeDecoderConfig(const int outputSampleRate) {
|
|
17
26
|
const ma_uint32 outRate =
|
|
18
27
|
outputSampleRate > 0 ? static_cast<ma_uint32>(outputSampleRate) : 0;
|
|
19
28
|
ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 0, outRate);
|
|
29
|
+
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
|
|
20
30
|
static ma_decoding_backend_vtable *customBackends[] = {
|
|
21
31
|
ma_decoding_backend_libvorbis,
|
|
22
32
|
ma_decoding_backend_libopus,
|
|
23
33
|
};
|
|
34
|
+
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
|
|
24
35
|
config.ppCustomBackendVTables = customBackends;
|
|
25
36
|
config.customBackendCount =
|
|
26
37
|
sizeof(customBackends) / sizeof(customBackends[0]);
|
|
@@ -79,12 +90,12 @@ float MiniAudioDecoder::getCurrentPositionInSeconds() const {
|
|
|
79
90
|
static_cast<double>(outputSampleRate_));
|
|
80
91
|
}
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
decoding::DecoderResult MiniAudioDecoder::openFile(
|
|
83
94
|
int outputSampleRate,
|
|
84
95
|
const std::string &path) {
|
|
85
96
|
close();
|
|
86
97
|
if (path.empty()) {
|
|
87
|
-
return
|
|
98
|
+
return Err("MiniAudioDecoder::openFile failed: path is empty");
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
ma_decoder_config config = makeDecoderConfig(outputSampleRate);
|
|
@@ -93,7 +104,7 @@ bool MiniAudioDecoder::openFile(
|
|
|
93
104
|
ma_decoder_init_file(path.c_str(), &config, decoder_.get());
|
|
94
105
|
if (result != MA_SUCCESS) {
|
|
95
106
|
teardownDecoder();
|
|
96
|
-
return
|
|
107
|
+
return Err("MiniAudioDecoder::openFile failed: " + parseMiniAudioError(result));
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
outputChannels_ = static_cast<int>(decoder_->outputChannels);
|
|
@@ -106,16 +117,16 @@ bool MiniAudioDecoder::openFile(
|
|
|
106
117
|
totalLengthFrames_ = 0;
|
|
107
118
|
}
|
|
108
119
|
totalOutputFrames_ = 0;
|
|
109
|
-
return
|
|
120
|
+
return Ok(None);
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
|
|
123
|
+
decoding::DecoderResult MiniAudioDecoder::openMemory(
|
|
113
124
|
int outputSampleRate,
|
|
114
125
|
const void *data,
|
|
115
126
|
size_t size) {
|
|
116
127
|
close();
|
|
117
128
|
if (data == nullptr || size == 0) {
|
|
118
|
-
return
|
|
129
|
+
return Err("MiniAudioDecoder::openMemory failed: input data is empty");
|
|
119
130
|
}
|
|
120
131
|
memoryCopy_.assign(
|
|
121
132
|
static_cast<const uint8_t *>(data),
|
|
@@ -130,7 +141,7 @@ bool MiniAudioDecoder::openMemory(
|
|
|
130
141
|
decoder_.get());
|
|
131
142
|
if (result != MA_SUCCESS) {
|
|
132
143
|
teardownDecoder();
|
|
133
|
-
return
|
|
144
|
+
return Err("MiniAudioDecoder::openMemory failed: " + parseMiniAudioError(result));
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
outputChannels_ = static_cast<int>(decoder_->outputChannels);
|
|
@@ -143,7 +154,7 @@ bool MiniAudioDecoder::openMemory(
|
|
|
143
154
|
totalLengthFrames_ = 0;
|
|
144
155
|
}
|
|
145
156
|
totalOutputFrames_ = 0;
|
|
146
|
-
return
|
|
157
|
+
return Ok(None);
|
|
147
158
|
}
|
|
148
159
|
|
|
149
160
|
size_t MiniAudioDecoder::readPcmFrames(float *outInterleaved, size_t frameCount) {
|
|
@@ -160,9 +171,9 @@ size_t MiniAudioDecoder::readPcmFrames(float *outInterleaved, size_t frameCount)
|
|
|
160
171
|
return static_cast<size_t>(framesRead);
|
|
161
172
|
}
|
|
162
173
|
|
|
163
|
-
|
|
174
|
+
decoding::DecoderResult MiniAudioDecoder::seekToTime(double seconds) {
|
|
164
175
|
if (!isOpen() || outputSampleRate_ <= 0) {
|
|
165
|
-
return
|
|
176
|
+
return Err("MiniAudioDecoder::seekToTime failed: decoder is not open");
|
|
166
177
|
}
|
|
167
178
|
const float dur = getDurationInSeconds();
|
|
168
179
|
if (dur > 0 && std::isfinite(dur)) {
|
|
@@ -170,17 +181,18 @@ bool MiniAudioDecoder::seekToTime(double seconds) {
|
|
|
170
181
|
} else {
|
|
171
182
|
seconds = std::max(0.0, seconds);
|
|
172
183
|
if (!std::isfinite(seconds)) {
|
|
173
|
-
return
|
|
184
|
+
return Err("MiniAudioDecoder::seekToTime failed: seconds is not finite");
|
|
174
185
|
}
|
|
175
186
|
}
|
|
176
187
|
|
|
177
188
|
const ma_uint64 frame =
|
|
178
189
|
static_cast<ma_uint64>(std::llround(seconds * static_cast<double>(outputSampleRate_)));
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
const ma_result result = ma_decoder_seek_to_pcm_frame(decoder_.get(), frame);
|
|
191
|
+
if (result != MA_SUCCESS) {
|
|
192
|
+
return Err("MiniAudioDecoder::seekToTime failed: " + parseMiniAudioError(result));
|
|
181
193
|
}
|
|
182
194
|
totalOutputFrames_ = static_cast<size_t>(frame);
|
|
183
|
-
return
|
|
195
|
+
return Ok(None);
|
|
184
196
|
}
|
|
185
197
|
|
|
186
198
|
namespace {
|
|
@@ -203,14 +215,15 @@ std::shared_ptr<AudioBuffer> buildAudioBufferFromInterleaved(
|
|
|
203
215
|
|
|
204
216
|
std::shared_ptr<AudioBuffer> decodeWithFilePath(const std::string &path, int sample_rate) {
|
|
205
217
|
MiniAudioDecoder dec;
|
|
206
|
-
|
|
218
|
+
const auto openResult = dec.openFile(sample_rate, path);
|
|
219
|
+
if (openResult.is_err()) {
|
|
207
220
|
return nullptr;
|
|
208
221
|
}
|
|
209
222
|
const int ch = std::max(1, dec.outputChannels());
|
|
210
223
|
std::vector<float> acc;
|
|
211
|
-
std::vector<float> tmp(decoding::
|
|
224
|
+
std::vector<float> tmp(decoding::IncrementalAudioDecoder::CHUNK_SIZE * static_cast<size_t>(ch));
|
|
212
225
|
while (true) {
|
|
213
|
-
const size_t n = dec.readPcmFrames(tmp.data(), decoding::
|
|
226
|
+
const size_t n = dec.readPcmFrames(tmp.data(), decoding::IncrementalAudioDecoder::CHUNK_SIZE);
|
|
214
227
|
if (n == 0) {
|
|
215
228
|
break;
|
|
216
229
|
}
|
|
@@ -224,14 +237,15 @@ std::shared_ptr<AudioBuffer> decodeWithFilePath(const std::string &path, int sam
|
|
|
224
237
|
|
|
225
238
|
std::shared_ptr<AudioBuffer> decodeWithMemoryBlock(const void *data, size_t size, int sample_rate) {
|
|
226
239
|
MiniAudioDecoder dec;
|
|
227
|
-
|
|
240
|
+
const auto openResult = dec.openMemory(sample_rate, data, size);
|
|
241
|
+
if (openResult.is_err()) {
|
|
228
242
|
return nullptr;
|
|
229
243
|
}
|
|
230
244
|
const int ch = std::max(1, dec.outputChannels());
|
|
231
245
|
std::vector<float> acc;
|
|
232
|
-
std::vector<float> tmp(decoding::
|
|
246
|
+
std::vector<float> tmp(decoding::IncrementalAudioDecoder::CHUNK_SIZE * static_cast<size_t>(ch));
|
|
233
247
|
while (true) {
|
|
234
|
-
const size_t n = dec.readPcmFrames(tmp.data(), decoding::
|
|
248
|
+
const size_t n = dec.readPcmFrames(tmp.data(), decoding::IncrementalAudioDecoder::CHUNK_SIZE);
|
|
235
249
|
if (n == 0) {
|
|
236
250
|
break;
|
|
237
251
|
}
|
|
@@ -17,16 +17,16 @@ namespace audioapi::miniaudio_decoder {
|
|
|
17
17
|
* MiniAudio-backed incremental decoder (Vorbis/Opus/WAV, etc. via ma_decoder + custom backends).
|
|
18
18
|
* Same usage contract as ffmpegdecoder::FFmpegDecoder.
|
|
19
19
|
*/
|
|
20
|
-
class MiniAudioDecoder : public decoding::
|
|
20
|
+
class MiniAudioDecoder : public decoding::IncrementalAudioDecoder {
|
|
21
21
|
public:
|
|
22
22
|
MiniAudioDecoder();
|
|
23
23
|
~MiniAudioDecoder() override;
|
|
24
24
|
DELETE_COPY_AND_MOVE(MiniAudioDecoder);
|
|
25
25
|
|
|
26
|
-
[[nodiscard]]
|
|
26
|
+
[[nodiscard]] decoding::DecoderResult openFile(
|
|
27
27
|
int outputSampleRate,
|
|
28
28
|
const std::string &path) override;
|
|
29
|
-
[[nodiscard]]
|
|
29
|
+
[[nodiscard]] decoding::DecoderResult openMemory(
|
|
30
30
|
int outputSampleRate,
|
|
31
31
|
const void *data,
|
|
32
32
|
size_t size) override;
|
|
@@ -37,7 +37,7 @@ class MiniAudioDecoder : public decoding::IIncrementalAudioDecoder {
|
|
|
37
37
|
[[nodiscard]] int outputSampleRate() const override;
|
|
38
38
|
[[nodiscard]] float getDurationInSeconds() const override;
|
|
39
39
|
[[nodiscard]] float getCurrentPositionInSeconds() const override;
|
|
40
|
-
[[nodiscard]]
|
|
40
|
+
[[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override;
|
|
41
41
|
|
|
42
42
|
private:
|
|
43
43
|
void teardownDecoder();
|
|
@@ -195,13 +195,18 @@ class AlignedAudioArray {
|
|
|
195
195
|
|
|
196
196
|
/// @brief Copies source array in reverse order into this array.
|
|
197
197
|
/// @note Assumes source and this are in distinct, non-overlapping memory locations.
|
|
198
|
+
/// @note Precondition: length > 0. Reads source[sourceStart .. sourceStart-length+1].
|
|
198
199
|
template <size_t OtherAlignment>
|
|
199
200
|
void copyReverse(
|
|
200
201
|
const AlignedAudioArray<OtherAlignment> &source,
|
|
201
202
|
size_t sourceStart,
|
|
202
203
|
size_t destinationStart,
|
|
203
204
|
size_t length) {
|
|
204
|
-
|
|
205
|
+
// Reverse read walks from sourceStart down to sourceStart-length+1, so the source
|
|
206
|
+
// precondition is sourceStart < source.size_ on the high end and sourceStart+1 >= length
|
|
207
|
+
// on the low end. Caller must also pass length > 0.
|
|
208
|
+
if (size_ - destinationStart < length || sourceStart + 1 < length ||
|
|
209
|
+
sourceStart >= source.size_) [[unlikely]] {
|
|
205
210
|
throw std::out_of_range("Not enough space to copy to destination or from source.");
|
|
206
211
|
}
|
|
207
212
|
|
|
@@ -23,7 +23,7 @@ using namespace channels::spsc;
|
|
|
23
23
|
/// In this setup no locking happens and modifications can be seen by Audio thread.
|
|
24
24
|
/// @note it is intended to be used for two threads one which schedules events and one which processes them
|
|
25
25
|
/// @note it is not safe to be copied across two threads use std::shared_ptr if you need to share data
|
|
26
|
-
template <typename T, int FunctionSize =
|
|
26
|
+
template <typename T, int FunctionSize = 72>
|
|
27
27
|
class CrossThreadEventScheduler {
|
|
28
28
|
using EventType = FatFunction<FunctionSize, void(T &)>;
|
|
29
29
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include <audioapi/utils/Macros.h>
|
|
3
4
|
#include <audioapi/utils/SpscChannel.hpp>
|
|
4
5
|
#include <cassert>
|
|
5
6
|
#include <concepts>
|
|
@@ -16,7 +17,7 @@ namespace audioapi::task_offloader {
|
|
|
16
17
|
template <std::default_initializable T, OverflowStrategy Strategy, WaitStrategy Wait>
|
|
17
18
|
class TaskOffloader {
|
|
18
19
|
public:
|
|
19
|
-
template <
|
|
20
|
+
template <std::invocable<T> Func>
|
|
20
21
|
explicit TaskOffloader(size_t capacity, Func &&task) : shouldRun_(true) {
|
|
21
22
|
auto [sender, receiver] = channels::spsc::channel<T, Strategy, Wait>(capacity);
|
|
22
23
|
sender_ = std::move(sender);
|
|
@@ -33,10 +34,7 @@ class TaskOffloader {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
// delete other functions
|
|
36
|
-
|
|
37
|
-
TaskOffloader &operator=(const TaskOffloader &) = delete;
|
|
38
|
-
TaskOffloader(TaskOffloader &&other) = delete;
|
|
39
|
-
TaskOffloader &operator=(TaskOffloader &&other) = delete;
|
|
37
|
+
DELETE_COPY_AND_MOVE(TaskOffloader);
|
|
40
38
|
|
|
41
39
|
~TaskOffloader() {
|
|
42
40
|
shouldRun_.store(false, std::memory_order_release);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
#import <React/RCTEventEmitter.h>
|
|
10
10
|
#import <audioapi/events/AudioEvent.h>
|
|
11
|
+
#include <audioapi/events/AudioEventPayload.h>
|
|
11
12
|
|
|
12
13
|
@class AudioEngine;
|
|
13
14
|
@class SystemNotificationManager;
|
|
@@ -27,6 +28,6 @@
|
|
|
27
28
|
@property (nonatomic, strong) NotificationRegistry *notificationRegistry;
|
|
28
29
|
|
|
29
30
|
- (void)invokeHandlerWithEventName:(audioapi::AudioEvent)eventName
|
|
30
|
-
|
|
31
|
+
payload:(audioapi::AudioEventPayload)payload;
|
|
31
32
|
|
|
32
33
|
@end
|
|
@@ -318,34 +318,13 @@ RCT_EXPORT_METHOD(
|
|
|
318
318
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
319
319
|
|
|
320
320
|
- (void)invokeHandlerWithEventName:(audioapi::AudioEvent)eventName
|
|
321
|
-
|
|
321
|
+
payload:(audioapi::AudioEventPayload)payload
|
|
322
322
|
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
for (NSString *key in eventBody) {
|
|
326
|
-
id value = eventBody[key];
|
|
327
|
-
std::string stdKey = [key UTF8String];
|
|
328
|
-
|
|
329
|
-
if ([value isKindOfClass:[NSString class]]) {
|
|
330
|
-
std::string stdValue = [value UTF8String];
|
|
331
|
-
body[stdKey] = EventValue(stdValue);
|
|
332
|
-
} else if ([value isKindOfClass:[NSNumber class]]) {
|
|
333
|
-
const char *type = [value objCType];
|
|
334
|
-
if (strcmp(type, @encode(int)) == 0) {
|
|
335
|
-
body[stdKey] = EventValue([value intValue]);
|
|
336
|
-
} else if (strcmp(type, @encode(double)) == 0) {
|
|
337
|
-
body[stdKey] = EventValue([value doubleValue]);
|
|
338
|
-
} else if (strcmp(type, @encode(float)) == 0) {
|
|
339
|
-
body[stdKey] = EventValue([value floatValue]);
|
|
340
|
-
} else {
|
|
341
|
-
body[stdKey] = EventValue([value boolValue]);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
323
|
+
if (_eventHandler == nullptr) {
|
|
324
|
+
return;
|
|
344
325
|
}
|
|
345
326
|
|
|
346
|
-
|
|
347
|
-
_eventHandler->invokeHandlerWithEventBody(eventName, body);
|
|
348
|
-
}
|
|
327
|
+
_eventHandler->dispatchEvent(eventName, audioapi::kBroadcastListenerId, std::move(payload));
|
|
349
328
|
}
|
|
350
329
|
|
|
351
330
|
@end
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
#import <NativeAudioPlayer.h>
|
|
5
5
|
#else // when compiled as C++
|
|
6
6
|
typedef struct objc_object NativeAudioPlayer;
|
|
7
|
+
typedef struct objc_object AudioBufferList;
|
|
7
8
|
#endif // __OBJC__
|
|
8
9
|
|
|
9
10
|
#include <audioapi/utils/AudioBuffer.hpp>
|
|
10
|
-
#include <functional>
|
|
11
11
|
|
|
12
|
+
#include <atomic>
|
|
13
|
+
#include <cstddef>
|
|
14
|
+
#include <functional>
|
|
12
15
|
namespace audioapi {
|
|
13
16
|
|
|
14
17
|
class AudioContext;
|
|
@@ -29,12 +32,23 @@ class IOSAudioPlayer {
|
|
|
29
32
|
|
|
30
33
|
bool isRunning() const;
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
private:
|
|
36
|
+
void clearPendingSaved();
|
|
37
|
+
/// Audio-thread only. Always pulls the graph in steps of RENDER_QUANTUM_SIZE; if the system
|
|
38
|
+
/// buffer size is not a multiple of 128, the unused tail of the last quantum is kept (max 128
|
|
39
|
+
/// frames) and played at the start of the next callback.
|
|
40
|
+
void deliverOutputBuffers(AudioBufferList *outputData, int numFrames);
|
|
41
|
+
|
|
33
42
|
std::shared_ptr<DSPAudioBuffer> audioBuffer_;
|
|
34
43
|
NativeAudioPlayer *audioPlayer_;
|
|
35
44
|
std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> renderAudio_;
|
|
36
45
|
int channelCount_;
|
|
37
46
|
std::atomic<bool> isRunning_;
|
|
47
|
+
/// Set from main thread on start/resume; consumed on audio thread to drop stale pending audio.
|
|
48
|
+
std::atomic<bool> flushOverflowNextPull_{false};
|
|
49
|
+
/// Frames valid at the front of each `pendingSaved_[ch]` (0 … RENDER_QUANTUM_SIZE).
|
|
50
|
+
int pendingSavedCount_{0};
|
|
51
|
+
DSPAudioBuffer pendingSaved_;
|
|
38
52
|
};
|
|
39
53
|
|
|
40
54
|
} // namespace audioapi
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#import <AVFoundation/AVFoundation.h>
|
|
2
2
|
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cstring>
|
|
5
|
+
|
|
3
6
|
#include <audioapi/core/utils/Constants.h>
|
|
4
|
-
#include <audioapi/dsp/VectorMath.h>
|
|
5
7
|
#include <audioapi/ios/core/IOSAudioPlayer.h>
|
|
6
8
|
#include <audioapi/ios/system/AudioEngine.h>
|
|
7
|
-
#include <audioapi/utils/AudioArray.hpp>
|
|
8
9
|
#include <audioapi/utils/AudioBuffer.hpp>
|
|
9
10
|
|
|
10
11
|
namespace audioapi {
|
|
@@ -13,35 +14,20 @@ IOSAudioPlayer::IOSAudioPlayer(
|
|
|
13
14
|
const std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> &renderAudio,
|
|
14
15
|
float sampleRate,
|
|
15
16
|
int channelCount)
|
|
16
|
-
:
|
|
17
|
+
: audioBuffer_(nullptr),
|
|
18
|
+
audioPlayer_(nullptr),
|
|
19
|
+
renderAudio_(renderAudio),
|
|
20
|
+
channelCount_(channelCount),
|
|
21
|
+
isRunning_(false),
|
|
22
|
+
pendingSaved_(RENDER_QUANTUM_SIZE, channelCount_, sampleRate)
|
|
17
23
|
{
|
|
18
24
|
RenderAudioBlock renderAudioBlock = ^(AudioBufferList *outputData, int numFrames) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
while (processedFrames < numFrames) {
|
|
22
|
-
int framesToProcess = std::min(numFrames - processedFrames, RENDER_QUANTUM_SIZE);
|
|
23
|
-
|
|
24
|
-
if (isRunning_.load(std::memory_order_acquire)) {
|
|
25
|
-
renderAudio_(audioBuffer_, framesToProcess);
|
|
26
|
-
} else {
|
|
27
|
-
audioBuffer_->zero();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
for (size_t channel = 0; channel < channelCount_; channel += 1) {
|
|
31
|
-
float *outputChannel = (float *)outputData->mBuffers[channel].mData;
|
|
32
|
-
|
|
33
|
-
audioBuffer_->getChannel(channel)->copyTo(
|
|
34
|
-
outputChannel, 0, processedFrames, framesToProcess);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
processedFrames += framesToProcess;
|
|
38
|
-
}
|
|
25
|
+
deliverOutputBuffers(outputData, numFrames);
|
|
39
26
|
};
|
|
40
27
|
|
|
41
28
|
audioPlayer_ = [[NativeAudioPlayer alloc] initWithRenderAudio:renderAudioBlock
|
|
42
29
|
sampleRate:sampleRate
|
|
43
30
|
channelCount:channelCount_];
|
|
44
|
-
|
|
45
31
|
audioBuffer_ = std::make_shared<DSPAudioBuffer>(RENDER_QUANTUM_SIZE, channelCount_, sampleRate);
|
|
46
32
|
}
|
|
47
33
|
|
|
@@ -50,6 +36,80 @@ IOSAudioPlayer::~IOSAudioPlayer()
|
|
|
50
36
|
cleanup();
|
|
51
37
|
}
|
|
52
38
|
|
|
39
|
+
void IOSAudioPlayer::clearPendingSaved()
|
|
40
|
+
{
|
|
41
|
+
pendingSavedCount_ = 0;
|
|
42
|
+
pendingSaved_.zero();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void IOSAudioPlayer::deliverOutputBuffers(AudioBufferList *outputData, int numFrames)
|
|
46
|
+
{
|
|
47
|
+
// If requested, clear any saved overflow before continuing normal rendering.
|
|
48
|
+
if (flushOverflowNextPull_.exchange(false, std::memory_order_acq_rel)) {
|
|
49
|
+
clearPendingSaved();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// if not running, set output to 0
|
|
53
|
+
if (!isRunning_.load(std::memory_order_acquire)) {
|
|
54
|
+
for (int channel = 0; channel < channelCount_; ++channel) {
|
|
55
|
+
auto *outputChannel = static_cast<float *>(outputData->mBuffers[channel].mData);
|
|
56
|
+
std::memset(outputChannel, 0, static_cast<size_t>(numFrames) * sizeof(float));
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
int outPos = 0;
|
|
62
|
+
while (outPos < numFrames) {
|
|
63
|
+
const int need = numFrames - outPos;
|
|
64
|
+
|
|
65
|
+
if (pendingSavedCount_ > 0) {
|
|
66
|
+
const int fromPending = std::min(need, pendingSavedCount_);
|
|
67
|
+
|
|
68
|
+
// populate output with pendingSaved
|
|
69
|
+
for (int ch = 0; ch < channelCount_; ++ch) {
|
|
70
|
+
float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
|
|
71
|
+
const float *src = pendingSaved_[ch].begin();
|
|
72
|
+
std::memcpy(dst, src, fromPending * sizeof(float));
|
|
73
|
+
|
|
74
|
+
// move the remaining samples to the beginning of the pendingSaved buffer
|
|
75
|
+
const int remain = pendingSavedCount_ - fromPending;
|
|
76
|
+
if (remain > 0) {
|
|
77
|
+
float *buf = pendingSaved_[ch].begin();
|
|
78
|
+
std::memmove(buf, buf + fromPending, remain * sizeof(float));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pendingSavedCount_ -= fromPending;
|
|
83
|
+
outPos += fromPending;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
renderAudio_(audioBuffer_, RENDER_QUANTUM_SIZE);
|
|
88
|
+
|
|
89
|
+
// normal rendering - take RENDER_QUANTUM_SIZE frames from the graph and copy to output
|
|
90
|
+
const int stillNeed = numFrames - outPos;
|
|
91
|
+
if (stillNeed >= RENDER_QUANTUM_SIZE) {
|
|
92
|
+
for (int ch = 0; ch < channelCount_; ++ch) {
|
|
93
|
+
auto *src = (*audioBuffer_)[ch].begin();
|
|
94
|
+
float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
|
|
95
|
+
std::memcpy(dst, src, RENDER_QUANTUM_SIZE * sizeof(float));
|
|
96
|
+
}
|
|
97
|
+
outPos += RENDER_QUANTUM_SIZE;
|
|
98
|
+
} else {
|
|
99
|
+
// when output will be sliced, copy the remaining frames to pendingSaved
|
|
100
|
+
const int tail = RENDER_QUANTUM_SIZE - stillNeed;
|
|
101
|
+
for (int ch = 0; ch < channelCount_; ++ch) {
|
|
102
|
+
auto *src = (*audioBuffer_)[ch].begin();
|
|
103
|
+
float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
|
|
104
|
+
std::memcpy(dst, src, stillNeed * sizeof(float));
|
|
105
|
+
}
|
|
106
|
+
pendingSaved_.copy(*audioBuffer_, stillNeed, 0, tail);
|
|
107
|
+
pendingSavedCount_ = tail;
|
|
108
|
+
outPos += stillNeed;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
53
113
|
bool IOSAudioPlayer::start()
|
|
54
114
|
{
|
|
55
115
|
if (isRunning()) {
|
|
@@ -57,6 +117,9 @@ bool IOSAudioPlayer::start()
|
|
|
57
117
|
}
|
|
58
118
|
|
|
59
119
|
bool success = [audioPlayer_ start];
|
|
120
|
+
if (success) {
|
|
121
|
+
flushOverflowNextPull_.store(true, std::memory_order_release);
|
|
122
|
+
}
|
|
60
123
|
isRunning_.store(success, std::memory_order_release);
|
|
61
124
|
return success;
|
|
62
125
|
}
|
|
@@ -74,6 +137,9 @@ bool IOSAudioPlayer::resume()
|
|
|
74
137
|
}
|
|
75
138
|
|
|
76
139
|
bool success = [audioPlayer_ resume];
|
|
140
|
+
if (success) {
|
|
141
|
+
flushOverflowNextPull_.store(true, std::memory_order_release);
|
|
142
|
+
}
|
|
77
143
|
isRunning_.store(success, std::memory_order_release);
|
|
78
144
|
return success;
|
|
79
145
|
}
|
|
@@ -322,7 +322,7 @@ Result<std::tuple<std::vector<std::string>, double, double>, std::string> IOSAud
|
|
|
322
322
|
|
|
323
323
|
if (hadCallback) {
|
|
324
324
|
callbackOutputConfigured_.store(false, std::memory_order_release);
|
|
325
|
-
dataCallback =
|
|
325
|
+
dataCallback = dataCallback_;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
if (hadConnection) {
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
#include <audioapi/utils/CircularArray.hpp>
|
|
12
12
|
#include <audioapi/utils/Result.hpp>
|
|
13
13
|
#include <algorithm>
|
|
14
|
+
#include <cstdlib>
|
|
15
|
+
#include <cstring>
|
|
16
|
+
#include <memory>
|
|
14
17
|
#include <utility>
|
|
15
18
|
|
|
16
19
|
namespace audioapi {
|
|
@@ -32,17 +35,7 @@ IOSRecorderCallback::IOSRecorderCallback(
|
|
|
32
35
|
|
|
33
36
|
IOSRecorderCallback::~IOSRecorderCallback()
|
|
34
37
|
{
|
|
35
|
-
|
|
36
|
-
converter_ = nil;
|
|
37
|
-
bufferFormat_ = nil;
|
|
38
|
-
callbackFormat_ = nil;
|
|
39
|
-
converterInputBuffer_ = nil;
|
|
40
|
-
converterOutputBuffer_ = nil;
|
|
41
|
-
|
|
42
|
-
for (size_t i = 0; i < channelCount_; ++i) {
|
|
43
|
-
circularBuffer_[i]->zero();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
38
|
+
cleanup();
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
/// @brief Prepares the IOSRecorderCallback for receiving audio data.
|
|
@@ -101,10 +94,14 @@ Result<NoneType, std::string> IOSRecorderCallback::prepare(
|
|
|
101
94
|
}
|
|
102
95
|
|
|
103
96
|
/// @brief Cleans up resources used by the IOSRecorderCallback.
|
|
104
|
-
/// This method should be called from the JS thread only.
|
|
97
|
+
/// This method should be called from the JS GC thread only.
|
|
105
98
|
void IOSRecorderCallback::cleanup()
|
|
106
99
|
{
|
|
100
|
+
std::scoped_lock audioLock(destructionAudioGuard_);
|
|
107
101
|
@autoreleasepool {
|
|
102
|
+
// join the worker
|
|
103
|
+
offloader_.reset();
|
|
104
|
+
|
|
108
105
|
if (circularBuffer_[0]->getNumberOfAvailableFrames() > 0) {
|
|
109
106
|
emitAudioData(true);
|
|
110
107
|
}
|
|
@@ -115,43 +112,89 @@ void IOSRecorderCallback::cleanup()
|
|
|
115
112
|
converterInputBuffer_ = nil;
|
|
116
113
|
converterOutputBuffer_ = nil;
|
|
117
114
|
|
|
118
|
-
for (
|
|
115
|
+
for (int i = 0; i < channelCount_; ++i) {
|
|
119
116
|
circularBuffer_[i]->zero();
|
|
120
117
|
}
|
|
121
|
-
offloader_.reset();
|
|
122
118
|
}
|
|
123
119
|
}
|
|
124
120
|
|
|
121
|
+
static inline void freeOwnedAudioBufferList(const AudioBufferList *bufferList)
|
|
122
|
+
{
|
|
123
|
+
if (bufferList == nullptr) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for (UInt32 i = 0; i < bufferList->mNumberBuffers; ++i) {
|
|
127
|
+
std::free(bufferList->mBuffers[i].mData);
|
|
128
|
+
}
|
|
129
|
+
std::free(const_cast<AudioBufferList *>(bufferList));
|
|
130
|
+
}
|
|
131
|
+
|
|
125
132
|
/// @brief Receives audio data from the recorder, processes it, and stores it in the circular buffer.
|
|
126
133
|
/// The data is converted using AVAudioConverter if the input format differs from the user desired callback format.
|
|
127
134
|
/// This method runs on the audio thread.
|
|
128
135
|
/// @param inputBuffer Pointer to the AudioBufferList containing the incoming audio data.
|
|
129
136
|
/// @param numFrames Number of frames in the input buffer.
|
|
130
|
-
void IOSRecorderCallback::receiveAudioData(const AudioBufferList *
|
|
137
|
+
void IOSRecorderCallback::receiveAudioData(const AudioBufferList *audioBufferList, int numFrames)
|
|
131
138
|
{
|
|
139
|
+
// if we wait here, we are in the middle of the destruction
|
|
140
|
+
std::scoped_lock lock(destructionAudioGuard_);
|
|
141
|
+
if (offloader_ == nullptr) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
132
144
|
if (!isInitialized_.load(std::memory_order_acquire)) {
|
|
133
145
|
return;
|
|
134
146
|
}
|
|
135
|
-
|
|
147
|
+
|
|
148
|
+
// CoreAudio owns `audioBufferList` only for the duration of this synchronous
|
|
149
|
+
// callback. Copy into an owned AudioBufferList before handing off to the
|
|
150
|
+
// worker thread; the consumer in taskOffloaderFunction frees it.
|
|
151
|
+
UInt32 bufferCount = audioBufferList->mNumberBuffers;
|
|
152
|
+
size_t headerSize = offsetof(AudioBufferList, mBuffers) + sizeof(AudioBuffer) * bufferCount;
|
|
153
|
+
AudioBufferList *owned = static_cast<AudioBufferList *>(std::malloc(headerSize));
|
|
154
|
+
if (owned == nullptr) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
owned->mNumberBuffers = bufferCount;
|
|
158
|
+
for (UInt32 i = 0; i < bufferCount; ++i) {
|
|
159
|
+
UInt32 byteSize = audioBufferList->mBuffers[i].mDataByteSize;
|
|
160
|
+
owned->mBuffers[i].mNumberChannels = audioBufferList->mBuffers[i].mNumberChannels;
|
|
161
|
+
owned->mBuffers[i].mDataByteSize = byteSize;
|
|
162
|
+
void *channelData = std::malloc(byteSize);
|
|
163
|
+
if (channelData == nullptr) {
|
|
164
|
+
for (UInt32 j = 0; j < i; ++j) {
|
|
165
|
+
std::free(owned->mBuffers[j].mData);
|
|
166
|
+
}
|
|
167
|
+
std::free(owned);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
std::memcpy(channelData, audioBufferList->mBuffers[i].mData, byteSize);
|
|
171
|
+
owned->mBuffers[i].mData = channelData;
|
|
172
|
+
}
|
|
173
|
+
offloader_->getSender()->send({.audioBufferList = owned, .numFrames = numFrames});
|
|
136
174
|
}
|
|
137
175
|
|
|
138
176
|
void IOSRecorderCallback::taskOffloaderFunction(CallbackData data)
|
|
139
177
|
{
|
|
140
178
|
auto [inputBuffer, numFrames] = data;
|
|
141
|
-
|
|
142
|
-
|
|
179
|
+
|
|
180
|
+
// The TaskOffloader destructor sends a default-constructed CallbackData
|
|
181
|
+
// (audioBufferList == nullptr) to unblock the receiver; ignore it here.
|
|
182
|
+
if (inputBuffer == nullptr) {
|
|
143
183
|
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
144
186
|
@autoreleasepool {
|
|
145
187
|
NSError *error = nil;
|
|
146
188
|
|
|
147
189
|
if (bufferFormat_.sampleRate == sampleRate_ && bufferFormat_.channelCount == channelCount_ &&
|
|
148
190
|
!bufferFormat_.isInterleaved) {
|
|
149
191
|
// Directly write to circular buffer
|
|
150
|
-
for (
|
|
192
|
+
for (int i = 0; i < channelCount_; ++i) {
|
|
151
193
|
auto *data = static_cast<float *>(inputBuffer->mBuffers[i].mData);
|
|
152
194
|
circularBuffer_[i]->push_back(data, numFrames);
|
|
153
195
|
}
|
|
154
196
|
|
|
197
|
+
freeOwnedAudioBufferList(inputBuffer);
|
|
155
198
|
inputBuffer = nullptr;
|
|
156
199
|
if (circularBuffer_[0]->getNumberOfAvailableFrames() >= bufferLength_) {
|
|
157
200
|
emitAudioData();
|
|
@@ -168,6 +211,7 @@ void IOSRecorderCallback::taskOffloaderFunction(CallbackData data)
|
|
|
168
211
|
inputBuffer->mBuffers[i].mDataByteSize);
|
|
169
212
|
}
|
|
170
213
|
|
|
214
|
+
freeOwnedAudioBufferList(inputBuffer);
|
|
171
215
|
inputBuffer = nullptr;
|
|
172
216
|
converterInputBuffer_.frameLength = numFrames;
|
|
173
217
|
|
|
@@ -195,7 +239,7 @@ void IOSRecorderCallback::taskOffloaderFunction(CallbackData data)
|
|
|
195
239
|
return;
|
|
196
240
|
}
|
|
197
241
|
|
|
198
|
-
for (
|
|
242
|
+
for (int i = 0; i < channelCount_; ++i) {
|
|
199
243
|
auto *data = static_cast<float *>(converterOutputBuffer_.audioBufferList->mBuffers[i].mData);
|
|
200
244
|
circularBuffer_[i]->push_back(data, outputFrameCount);
|
|
201
245
|
}
|