react-native-audio-api 0.12.0 → 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 +5 -4
- 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/effects/WorkletProcessingNode.cpp +1 -1
- 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/core/utils/param/ParamControlQueue.cpp +12 -8
- 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 +6 -1
- 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 +4 -1
- 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 +2 -0
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/api.web.d.ts +3 -1
- 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 +10 -4
- package/scripts/download-prebuilt-binaries.sh +34 -1
- package/scripts/validate-worklets-version.js +1 -1
- package/src/AudioAPIModule/AudioAPIModule.ts +1 -0
- package/src/AudioAPIModule/globals.d.ts +3 -0
- package/src/api.ts +2 -0
- package/src/api.web.ts +10 -2
- package/src/core/AudioFileUtils.ts +49 -0
- package/src/interfaces.ts +7 -0
- package/src/mock/index.ts +29 -0
|
@@ -39,7 +39,7 @@ struct MemoryIOContext {
|
|
|
39
39
|
* 2) readPcmFrames repeatedly; 0 returned = end of stream
|
|
40
40
|
* 3) close when done
|
|
41
41
|
*/
|
|
42
|
-
class FFmpegDecoder : public decoding::
|
|
42
|
+
class FFmpegDecoder : public decoding::IncrementalAudioDecoder {
|
|
43
43
|
public:
|
|
44
44
|
FFmpegDecoder() = default;
|
|
45
45
|
FFmpegDecoder(const FFmpegDecoder &) = delete;
|
|
@@ -48,11 +48,11 @@ class FFmpegDecoder : public decoding::IIncrementalAudioDecoder {
|
|
|
48
48
|
FFmpegDecoder &operator=(FFmpegDecoder &&other) = delete;
|
|
49
49
|
~FFmpegDecoder() override;
|
|
50
50
|
|
|
51
|
-
[[nodiscard]]
|
|
51
|
+
[[nodiscard]] decoding::DecoderResult openFile(
|
|
52
52
|
int outputSampleRate,
|
|
53
53
|
const std::string &path) override;
|
|
54
54
|
|
|
55
|
-
[[nodiscard]]
|
|
55
|
+
[[nodiscard]] decoding::DecoderResult openMemory(
|
|
56
56
|
int outputSampleRate,
|
|
57
57
|
const void *data,
|
|
58
58
|
size_t size) override;
|
|
@@ -71,11 +71,11 @@ class FFmpegDecoder : public decoding::IIncrementalAudioDecoder {
|
|
|
71
71
|
|
|
72
72
|
[[nodiscard]] float getCurrentPositionInSeconds() const override;
|
|
73
73
|
|
|
74
|
-
[[nodiscard]]
|
|
74
|
+
[[nodiscard]] decoding::DecoderResult seekToTime(double seconds) override;
|
|
75
75
|
|
|
76
76
|
private:
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
[[nodiscard]] decoding::DecoderResult setupSwr();
|
|
78
|
+
[[nodiscard]] decoding::DecoderResult feedPipeline();
|
|
79
79
|
void appendFrameResampled(AVFrame *frame);
|
|
80
80
|
|
|
81
81
|
AVFormatContext *fmt_ctx_ = nullptr;
|
|
@@ -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) {
|