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.
Files changed (110) hide show
  1. package/README.md +8 -3
  2. package/RNAudioAPI.podspec +0 -2
  3. package/android/src/main/cpp/audioapi/CMakeLists.txt +8 -2
  4. package/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +7 -29
  5. package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.cpp +83 -0
  6. package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.h +14 -0
  7. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +5 -4
  8. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +1 -0
  9. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +37 -21
  10. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +1 -1
  11. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +21 -0
  12. package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp +26 -4
  13. package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h +1 -0
  14. package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.cpp +2 -2
  15. package/common/cpp/audioapi/HostObjects/utils/AudioDecoderHostObject.cpp +3 -3
  16. package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.cpp +60 -0
  17. package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.h +24 -0
  18. package/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +2 -2
  19. package/common/cpp/audioapi/core/OfflineAudioContext.cpp +0 -1
  20. package/common/cpp/audioapi/core/OfflineAudioContext.h +0 -1
  21. package/common/cpp/audioapi/core/effects/ConvolverNode.cpp +4 -10
  22. package/common/cpp/audioapi/core/effects/ConvolverNode.h +0 -4
  23. package/common/cpp/audioapi/core/effects/WorkletProcessingNode.cpp +1 -1
  24. package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp +13 -11
  25. package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h +5 -9
  26. package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp +47 -139
  27. package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h +11 -8
  28. package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +29 -114
  29. package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h +6 -8
  30. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +9 -11
  31. package/common/cpp/audioapi/core/sources/AudioFileSourceNode.h +1 -1
  32. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +2 -2
  33. package/common/cpp/audioapi/core/types/AudioFormat.h +3 -1
  34. package/common/cpp/audioapi/core/utils/AudioDecoder.cpp +120 -91
  35. package/common/cpp/audioapi/core/utils/AudioDecoder.h +24 -101
  36. package/common/cpp/audioapi/core/utils/AudioFileConcatenator.cpp +862 -0
  37. package/common/cpp/audioapi/core/utils/AudioFileConcatenator.h +164 -0
  38. package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +2 -4
  39. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +7 -12
  40. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +2 -0
  41. package/common/cpp/audioapi/core/utils/buffer/BufferProcessingDirection.h +6 -0
  42. package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.cpp +110 -0
  43. package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.h +75 -0
  44. package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.cpp +129 -0
  45. package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.h +55 -0
  46. package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.cpp +95 -0
  47. package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.h +52 -0
  48. package/common/cpp/audioapi/core/utils/param/ParamControlQueue.cpp +12 -8
  49. package/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp +65 -157
  50. package/common/cpp/audioapi/events/AudioEventHandlerRegistry.h +52 -33
  51. package/common/cpp/audioapi/events/AudioEventPayload.h +87 -0
  52. package/common/cpp/audioapi/events/IAudioEventHandlerRegistry.h +12 -12
  53. package/common/cpp/audioapi/libs/decoding/IncrementalAudioDecoder.h +12 -10
  54. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +152 -78
  55. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +6 -6
  56. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.cpp +34 -20
  57. package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.h +4 -4
  58. package/common/cpp/audioapi/utils/AudioArray.hpp +6 -1
  59. package/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp +1 -1
  60. package/common/cpp/audioapi/utils/TaskOffloader.hpp +3 -5
  61. package/ios/audioapi/ios/AudioAPIModule.h +2 -1
  62. package/ios/audioapi/ios/AudioAPIModule.mm +4 -25
  63. package/ios/audioapi/ios/core/IOSAudioPlayer.h +16 -2
  64. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +90 -24
  65. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +1 -1
  66. package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +64 -20
  67. package/ios/audioapi/ios/system/AudioSessionManager.mm +18 -7
  68. package/ios/audioapi/ios/system/SystemNotificationManager.mm +22 -22
  69. package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +10 -13
  70. package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +1 -1
  71. package/lib/commonjs/AudioAPIModule/AudioAPIModule.js.map +1 -1
  72. package/lib/commonjs/api.js +8 -0
  73. package/lib/commonjs/api.js.map +1 -1
  74. package/lib/commonjs/api.web.js +6 -1
  75. package/lib/commonjs/api.web.js.map +1 -1
  76. package/lib/commonjs/core/AudioFileUtils.js +35 -0
  77. package/lib/commonjs/core/AudioFileUtils.js.map +1 -0
  78. package/lib/commonjs/mock/index.js +15 -1
  79. package/lib/commonjs/mock/index.js.map +1 -1
  80. package/lib/module/AudioAPIModule/AudioAPIModule.js +1 -1
  81. package/lib/module/AudioAPIModule/AudioAPIModule.js.map +1 -1
  82. package/lib/module/api.js +1 -0
  83. package/lib/module/api.js.map +1 -1
  84. package/lib/module/api.web.js +4 -1
  85. package/lib/module/api.web.js.map +1 -1
  86. package/lib/module/core/AudioFileUtils.js +31 -0
  87. package/lib/module/core/AudioFileUtils.js.map +1 -0
  88. package/lib/module/mock/index.js +14 -1
  89. package/lib/module/mock/index.js.map +1 -1
  90. package/lib/typescript/AudioAPIModule/AudioAPIModule.d.ts.map +1 -1
  91. package/lib/typescript/api.d.ts +2 -0
  92. package/lib/typescript/api.d.ts.map +1 -1
  93. package/lib/typescript/api.web.d.ts +3 -1
  94. package/lib/typescript/api.web.d.ts.map +1 -1
  95. package/lib/typescript/core/AudioFileUtils.d.ts +2 -0
  96. package/lib/typescript/core/AudioFileUtils.d.ts.map +1 -0
  97. package/lib/typescript/interfaces.d.ts +3 -0
  98. package/lib/typescript/interfaces.d.ts.map +1 -1
  99. package/lib/typescript/mock/index.d.ts +3 -1
  100. package/lib/typescript/mock/index.d.ts.map +1 -1
  101. package/package.json +10 -4
  102. package/scripts/download-prebuilt-binaries.sh +34 -1
  103. package/scripts/validate-worklets-version.js +1 -1
  104. package/src/AudioAPIModule/AudioAPIModule.ts +1 -0
  105. package/src/AudioAPIModule/globals.d.ts +3 -0
  106. package/src/api.ts +2 -0
  107. package/src/api.web.ts +10 -2
  108. package/src/core/AudioFileUtils.ts +49 -0
  109. package/src/interfaces.ts +7 -0
  110. package/src/mock/index.ts +29 -0
package/README.md CHANGED
@@ -28,9 +28,6 @@ check out the [Getting Started](https://docs.swmansion.com/react-native-audio-ap
28
28
  - **DynamicCompressorNode 〽️**<br />
29
29
  Reduce the volume of loud sounds and boost quieter nodes to balance the audio signal, avoid clipping or distorted sounds
30
30
 
31
- - **Audio tag 🏷️**<br />
32
- Simple ability to play and buffer audio, with all of the most commonly used functions, same as on the web, without the need to create and manipulate an audio graph.
33
-
34
31
  - **MIDI support 🎸**<br />
35
32
  Complementary lib for react-native-audio-api, that will allow to communicate with MIDI devices or read/write MIDI files.
36
33
 
@@ -40,6 +37,14 @@ check out the [Getting Started](https://docs.swmansion.com/react-native-audio-ap
40
37
  - **Noise Cancellation 🦇**<br />
41
38
  System-based active noise and echo cancellation support
42
39
 
40
+ ### <a href="https://github.com/software-mansion/react-native-audio-api/releases/tag/0.12.0"><img src="https://img.shields.io/badge/Released_in-0.12.0-green" /></a>
41
+
42
+ - **Audio tag 🏷️**<br />
43
+ Simple ability to play and buffer audio, with all of the most commonly used functions, same as on the web, without the need to create and manipulate an audio graph.
44
+
45
+ - **Recording rotation 🎤**<br />
46
+ Ability to chunk your recording into smaller files, increasing resilience to unpredictable events.
47
+
43
48
  ### <a href="https://github.com/software-mansion/react-native-audio-api/releases/tag/0.11.0"><img src="https://img.shields.io/badge/Released_in-0.11.0-green" /></a>
44
49
 
45
50
  - **Recording to file 📼**<br />
@@ -131,8 +131,6 @@ Pod::Spec.new do |s|
131
131
  -force_load #{lib_dir}/libvorbis.a
132
132
  -force_load #{lib_dir}/libvorbisenc.a
133
133
  -force_load #{lib_dir}/libvorbisfile.a
134
- -force_load #{lib_dir}/libssl.a
135
- -force_load #{lib_dir}/libcrypto.a
136
134
  ].join(" "),
137
135
  }
138
136
  # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
@@ -16,6 +16,14 @@ set_source_files_properties(
16
16
  COMPILE_FLAGS "-O3"
17
17
  )
18
18
 
19
+ if(CMAKE_C_COMPILER_ID MATCHES "Clang")
20
+ set_source_files_properties(
21
+ "${COMMON_CPP_DIR}/audioapi/dsp/r8brain/fft/pffft_double.c"
22
+ PROPERTIES
23
+ COMPILE_FLAGS "-Wno-#pragma-messages"
24
+ )
25
+ endif()
26
+
19
27
  set(INCLUDE_DIR ${COMMON_CPP_DIR}/audioapi/external/include)
20
28
  set(FFMPEG_INCLUDE_DIR ${COMMON_CPP_DIR}/audioapi/external/include_ffmpeg)
21
29
  set(EXTERNAL_DIR ${COMMON_CPP_DIR}/audioapi/external/android)
@@ -132,8 +140,6 @@ target_link_libraries(react-native-audio-api
132
140
  vorbis
133
141
  vorbisenc
134
142
  vorbisfile
135
- crypto
136
- ssl
137
143
  z
138
144
  )
139
145
 
@@ -1,7 +1,6 @@
1
1
  #include <audioapi/android/AudioAPIModule.h>
2
+ #include <audioapi/android/JniEventPayloadParser.h>
2
3
  #include <memory>
3
- #include <string>
4
- #include <unordered_map>
5
4
 
6
5
  namespace audioapi {
7
6
 
@@ -66,34 +65,13 @@ void AudioAPIModule::injectJSIBindings() {
66
65
  void AudioAPIModule::invokeHandlerWithEventNameAndEventBody(
67
66
  jint eventOrdinal,
68
67
  jni::alias_ref<jni::JMap<jstring, jobject>> eventBody) {
69
- std::unordered_map<std::string, EventValue> body = {};
70
-
71
- for (const auto &entry : *eventBody) {
72
- std::string name = entry.first->toStdString();
73
- auto value = entry.second;
74
-
75
- if (value->isInstanceOf(jni::JString::javaClassStatic())) {
76
- body[name] = jni::static_ref_cast<jni::JString>(value)->toStdString();
77
- } else if (value->isInstanceOf(jni::JInteger::javaClassStatic())) {
78
- body[name] = jni::static_ref_cast<jni::JInteger>(value)->value();
79
- } else if (value->isInstanceOf(jni::JDouble::javaClassStatic())) {
80
- body[name] = jni::static_ref_cast<jni::JDouble>(value)->value();
81
- } else if (value->isInstanceOf(jni::JFloat::javaClassStatic())) {
82
- body[name] = jni::static_ref_cast<jni::JFloat>(value)->value();
83
- } else if (value->isInstanceOf(jni::JBoolean::javaClassStatic())) {
84
- auto booleanValue = jni::static_ref_cast<jni::JBoolean>(value)->value();
85
-
86
- if (booleanValue) {
87
- body[name] = true;
88
- } else {
89
- body[name] = false;
90
- }
91
- }
68
+ if (audioEventHandlerRegistry_ == nullptr) {
69
+ return;
92
70
  }
93
71
 
94
- if (audioEventHandlerRegistry_ != nullptr) {
95
- audioEventHandlerRegistry_->invokeHandlerWithEventBody(
96
- static_cast<audioapi::AudioEvent>(eventOrdinal), body);
97
- }
72
+ auto event = static_cast<AudioEvent>(eventOrdinal);
73
+ audioEventHandlerRegistry_->dispatchEvent(
74
+ event, kBroadcastListenerId, buildPayloadFromJniMap(event, eventBody));
98
75
  }
76
+
99
77
  } // namespace audioapi
@@ -0,0 +1,83 @@
1
+ #include <audioapi/android/JniEventPayloadParser.h>
2
+ #include <string>
3
+
4
+ namespace audioapi {
5
+
6
+ using namespace facebook;
7
+
8
+ jni::local_ref<jobject> jniMapGet(
9
+ const jni::alias_ref<jni::JMap<jstring, jobject>> &map,
10
+ const char *key) {
11
+ if (!map) {
12
+ return nullptr;
13
+ }
14
+
15
+ static auto getMethod =
16
+ jni::JMap<jstring, jobject>::javaClassStatic()->getMethod<jobject(jobject)>("get");
17
+
18
+ auto jKey = jni::make_jstring(key);
19
+ return getMethod(map, jKey.get());
20
+ }
21
+
22
+ double jniGetDouble(const jni::alias_ref<jni::JMap<jstring, jobject>> &map, const char *key) {
23
+ auto val = jniMapGet(map, key);
24
+ if (!val) {
25
+ return 0.0;
26
+ }
27
+
28
+ if (val->isInstanceOf(jni::JDouble::javaClassStatic())) {
29
+ return jni::static_ref_cast<jni::JDouble>(val)->value();
30
+ }
31
+
32
+ if (val->isInstanceOf(jni::JFloat::javaClassStatic())) {
33
+ return static_cast<double>(jni::static_ref_cast<jni::JFloat>(val)->value());
34
+ }
35
+
36
+ if (val->isInstanceOf(jni::JInteger::javaClassStatic())) {
37
+ return static_cast<double>(jni::static_ref_cast<jni::JInteger>(val)->value());
38
+ }
39
+
40
+ if (val->isInstanceOf(jni::JLong::javaClassStatic())) {
41
+ return static_cast<double>(jni::static_ref_cast<jni::JLong>(val)->value());
42
+ }
43
+
44
+ return 0.0;
45
+ }
46
+
47
+ std::string jniGetString(const jni::alias_ref<jni::JMap<jstring, jobject>> &map, const char *key) {
48
+ auto val = jniMapGet(map, key);
49
+ if (val && val->isInstanceOf(jni::JString::javaClassStatic())) {
50
+ return jni::static_ref_cast<jni::JString>(val)->toStdString();
51
+ }
52
+
53
+ return {};
54
+ }
55
+
56
+ bool jniGetBool(const jni::alias_ref<jni::JMap<jstring, jobject>> &map, const char *key) {
57
+ auto val = jniMapGet(map, key);
58
+ if (val && val->isInstanceOf(jni::JBoolean::javaClassStatic())) {
59
+ return static_cast<bool>(jni::static_ref_cast<jni::JBoolean>(val)->value());
60
+ }
61
+ return false;
62
+ }
63
+
64
+ AudioEventPayload buildPayloadFromJniMap(
65
+ AudioEvent event,
66
+ const jni::alias_ref<jni::JMap<jstring, jobject>> &map) {
67
+ switch (event) {
68
+ case AudioEvent::INTERRUPTION:
69
+ return InterruptionPayload{
70
+ .type = jniGetString(map, "type"), .shouldResume = jniGetBool(map, "shouldResume")};
71
+
72
+ case AudioEvent::VOLUME_CHANGE:
73
+ case AudioEvent::PLAYBACK_NOTIFICATION_SKIP_FORWARD:
74
+ case AudioEvent::PLAYBACK_NOTIFICATION_SKIP_BACKWARD:
75
+ case AudioEvent::PLAYBACK_NOTIFICATION_SEEK_TO:
76
+ return DoubleValuePayload{.value = jniGetDouble(map, "value")};
77
+
78
+ default:
79
+ return EmptyPayload{};
80
+ }
81
+ }
82
+
83
+ } // namespace audioapi
@@ -0,0 +1,14 @@
1
+ #pragma once
2
+
3
+ #include <audioapi/events/AudioEvent.h>
4
+ #include <audioapi/events/AudioEventPayload.h>
5
+ #include <fbjni/fbjni.h>
6
+
7
+ namespace audioapi {
8
+
9
+ /// @brief Builds AudioEventPayload from a JNI map passed from Java/Kotlin.
10
+ AudioEventPayload buildPayloadFromJniMap(
11
+ AudioEvent event,
12
+ const facebook::jni::alias_ref<facebook::jni::JMap<jstring, jobject>> &map);
13
+
14
+ } // namespace audioapi
@@ -321,7 +321,7 @@ Result<NoneType, std::string> AndroidAudioRecorder::setupFileWriter(
321
321
  const std::string &fileNameOverride) {
322
322
  #if RN_AUDIO_API_FFMPEG_DISABLED
323
323
  if (properties->format != AudioFileProperties::Format::WAV) {
324
- return Result<std::string, std::string>::Err(
324
+ return Result<NoneType, std::string>::Err(
325
325
  "FFmpeg backend is disabled. Cannot create file writer for the requested format. Use WAV format instead.");
326
326
  }
327
327
  #endif
@@ -575,9 +575,10 @@ void AndroidAudioRecorder::onErrorAfterClose(oboe::AudioStream *stream, oboe::Re
575
575
  }
576
576
 
577
577
  std::string message = "Android recorder error: " + streamResult.unwrap_err();
578
- std::unordered_map<std::string, EventValue> eventPayload{{"message", std::move(message)}};
579
- audioEventHandlerRegistry_->invokeHandlerWithEventBody(
580
- AudioEvent::RECORDER_ERROR, callbackId, eventPayload);
578
+ audioEventHandlerRegistry_->dispatchEvent(
579
+ AudioEvent::RECORDER_ERROR,
580
+ callbackId,
581
+ StringPayload{.name = "message", .reason = std::move(message)});
581
582
  return;
582
583
  }
583
584
 
@@ -31,6 +31,7 @@ bool AudioPlayer::openAudioStream() {
31
31
  ->setPerformanceMode(PerformanceMode::None)
32
32
  ->setChannelCount(channelCount_)
33
33
  ->setSampleRateConversionQuality(SampleRateConversionQuality::Medium)
34
+ ->setFramesPerDataCallback(RENDER_QUANTUM_SIZE)
34
35
  ->setDataCallback(this)
35
36
  ->setSampleRate(static_cast<int>(sampleRate_))
36
37
  ->setErrorCallback(this);
@@ -7,6 +7,7 @@
7
7
  #include <audioapi/utils/CircularArray.hpp>
8
8
 
9
9
  #include <algorithm>
10
+ #include <cstring>
10
11
  #include <memory>
11
12
  #include <string>
12
13
  #include <unordered_map>
@@ -35,20 +36,7 @@ AndroidRecorderCallback::AndroidRecorderCallback(
35
36
  callbackId) {}
36
37
 
37
38
  AndroidRecorderCallback::~AndroidRecorderCallback() {
38
- if (converter_ != nullptr) {
39
- ma_data_converter_uninit(converter_.get(), nullptr);
40
- converter_.reset();
41
- }
42
-
43
- if (processingBuffer_ != nullptr) {
44
- ma_free(processingBuffer_, nullptr);
45
- processingBuffer_ = nullptr;
46
- processingBufferLength_ = 0;
47
- }
48
-
49
- for (size_t i = 0; i < circularBuffer_.size(); ++i) {
50
- circularBuffer_[i]->zero();
51
- }
39
+ cleanup();
52
40
  }
53
41
 
54
42
  /// @brief Prepares the recorder callback by initializing the data converter and allocating necessary buffers.
@@ -111,6 +99,10 @@ Result<NoneType, std::string> AndroidRecorderCallback::prepare(
111
99
  }
112
100
 
113
101
  void AndroidRecorderCallback::cleanup() {
102
+ std::scoped_lock audioLock(destructionAudioGuard_);
103
+ // join the worker
104
+ offloader_.reset();
105
+
114
106
  if (circularBuffer_[0]->getNumberOfAvailableFrames() > 0) {
115
107
  emitAudioData(true);
116
108
  }
@@ -126,10 +118,9 @@ void AndroidRecorderCallback::cleanup() {
126
118
  processingBufferLength_ = 0;
127
119
  }
128
120
 
129
- for (size_t i = 0; i < circularBuffer_.size(); ++i) {
130
- circularBuffer_[i]->zero();
121
+ for (const auto &arr : circularBuffer_) {
122
+ arr->zero();
131
123
  }
132
- offloader_.reset();
133
124
  }
134
125
 
135
126
  /// @brief Receives audio data from the recorder, processes it (resampling and deinterleaving if necessary),
@@ -137,11 +128,27 @@ void AndroidRecorderCallback::cleanup() {
137
128
  /// @param data Pointer to the incoming audio data.
138
129
  /// @param numFrames Number of frames in the incoming audio data.
139
130
  void AndroidRecorderCallback::receiveAudioData(void *data, int numFrames) {
131
+ // if we wait here, we are in the middle of the destruction
132
+ std::scoped_lock lock(destructionAudioGuard_);
133
+ if (offloader_ == nullptr) {
134
+ return;
135
+ }
140
136
  if (!isInitialized_.load(std::memory_order_acquire)) {
141
137
  return;
142
138
  }
143
139
 
144
- offloader_->getSender()->send({data, numFrames});
140
+ // Oboe owns `data` only for the duration of this synchronous callback.
141
+ // Copy into an owned buffer before handing off to the worker thread; the
142
+ // consumer in taskOffloaderFunction frees it.
143
+ size_t bytes =
144
+ static_cast<size_t>(numFrames) * streamChannelCount_ * ma_get_bytes_per_sample(ma_format_f32);
145
+ void *owned = ma_malloc(bytes, nullptr);
146
+ if (owned == nullptr) {
147
+ return;
148
+ }
149
+ std::memcpy(owned, data, bytes);
150
+
151
+ offloader_->getSender()->send({.data = owned, .numFrames = numFrames});
145
152
  }
146
153
 
147
154
  /// @brief Deinterleaves the audio data and pushes it into the circular buffer.
@@ -151,7 +158,7 @@ void AndroidRecorderCallback::deinterleaveAndPushAudioData(void *data, int numFr
151
158
  auto *inputData = static_cast<float *>(data);
152
159
  deinterleavingBuffer_->deinterleaveFrom(inputData, numFrames);
153
160
 
154
- for (size_t ch = 0; ch < channelCount_; ++ch) {
161
+ for (int ch = 0; ch < channelCount_; ++ch) {
155
162
  circularBuffer_[ch]->push_back(*deinterleavingBuffer_->getChannel(ch), numFrames);
156
163
  }
157
164
  }
@@ -160,16 +167,23 @@ void AndroidRecorderCallback::deinterleaveAndPushAudioData(void *data, int numFr
160
167
  /// processes it (resampling and deinterleaving if necessary), and pushes it into the circular buffer.
161
168
  void AndroidRecorderCallback::taskOffloaderFunction(CallbackData callbackData) {
162
169
  auto [data, numFrames] = callbackData;
170
+
171
+ // The TaskOffloader destructor sends a default-constructed CallbackData
172
+ // (data == nullptr) to unblock the receiver; ignore it here.
173
+ if (data == nullptr) {
174
+ return;
175
+ }
176
+
163
177
  ma_uint64 inputFrameCount = numFrames;
164
178
  ma_uint64 outputFrameCount = 0;
165
179
 
166
- if (static_cast<float>(streamSampleRate_) == sampleRate_ &&
167
- streamChannelCount_ == channelCount_) {
180
+ if (streamSampleRate_ == sampleRate_ && streamChannelCount_ == channelCount_) {
168
181
  deinterleaveAndPushAudioData(data, numFrames);
169
182
 
170
183
  if (circularBuffer_[0]->getNumberOfAvailableFrames() >= bufferLength_) {
171
184
  emitAudioData();
172
185
  }
186
+ ma_free(data, nullptr);
173
187
  return;
174
188
  }
175
189
 
@@ -184,6 +198,8 @@ void AndroidRecorderCallback::taskOffloaderFunction(CallbackData callbackData) {
184
198
  if (circularBuffer_[0]->getNumberOfAvailableFrames() >= bufferLength_) {
185
199
  emitAudioData();
186
200
  }
201
+
202
+ ma_free(data, nullptr);
187
203
  }
188
204
 
189
205
  } // namespace audioapi
@@ -29,7 +29,7 @@ class AndroidRecorderCallback : public AudioRecorderCallback {
29
29
 
30
30
  Result<NoneType, std::string>
31
31
  prepare(float streamSampleRate, int streamChannelCount, size_t maxInputBufferLength);
32
- void cleanup() override;
32
+ void cleanup() final;
33
33
 
34
34
  void receiveAudioData(void *data, int numFrames);
35
35
 
@@ -5,6 +5,7 @@
5
5
  #include <audioapi/HostObjects/inputs/AudioRecorderHostObject.h>
6
6
  #include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
7
7
  #include <audioapi/HostObjects/utils/AudioDecoderHostObject.h>
8
+ #include <audioapi/HostObjects/utils/AudioFileUtilsHostObject.h>
8
9
  #include <audioapi/HostObjects/utils/AudioStretcherHostObject.h>
9
10
  #include <audioapi/core/AudioContext.h>
10
11
  #include <audioapi/core/OfflineAudioContext.h>
@@ -37,6 +38,7 @@ class AudioAPIModuleInstaller {
37
38
  jsiRuntime, jsCallInvoker, audioEventHandlerRegistry, uiRuntime);
38
39
  auto createAudioBuffer = getCreateAudioBufferFunction(jsiRuntime);
39
40
  auto createAudioDecoder = getCreateAudioDecoderFunction(jsiRuntime, jsCallInvoker);
41
+ auto createAudioFileUtils = getCreateAudioFileUtilsFunction(jsiRuntime, jsCallInvoker);
40
42
  auto createAudioStretcher = getCreateAudioStretcherFunction(jsiRuntime, jsCallInvoker);
41
43
 
42
44
  jsiRuntime->global().setProperty(*jsiRuntime, "createAudioContext", createAudioContext);
@@ -45,6 +47,7 @@ class AudioAPIModuleInstaller {
45
47
  *jsiRuntime, "createOfflineAudioContext", createOfflineAudioContext);
46
48
  jsiRuntime->global().setProperty(*jsiRuntime, "createAudioBuffer", createAudioBuffer);
47
49
  jsiRuntime->global().setProperty(*jsiRuntime, "createAudioDecoder", createAudioDecoder);
50
+ jsiRuntime->global().setProperty(*jsiRuntime, "createAudioFileUtils", createAudioFileUtils);
48
51
  jsiRuntime->global().setProperty(*jsiRuntime, "createAudioStretcher", createAudioStretcher);
49
52
 
50
53
  auto audioEventHandlerRegistryHostObject =
@@ -185,6 +188,24 @@ class AudioAPIModuleInstaller {
185
188
  });
186
189
  }
187
190
 
191
+ static jsi::Function getCreateAudioFileUtilsFunction(
192
+ jsi::Runtime *jsiRuntime,
193
+ const std::shared_ptr<react::CallInvoker> &jsCallInvoker) {
194
+ return jsi::Function::createFromHostFunction(
195
+ *jsiRuntime,
196
+ jsi::PropNameID::forAscii(*jsiRuntime, "createAudioFileUtils"),
197
+ 0,
198
+ [jsCallInvoker](
199
+ jsi::Runtime &runtime,
200
+ const jsi::Value &thisValue,
201
+ const jsi::Value *args,
202
+ size_t count) -> jsi::Value {
203
+ auto audioFileUtilsHostObject =
204
+ std::make_shared<AudioFileUtilsHostObject>(&runtime, jsCallInvoker);
205
+ return jsi::Object::createFromHostObject(runtime, audioFileUtilsHostObject);
206
+ });
207
+ }
208
+
188
209
  static jsi::Function getCreateAudioBufferFunction(jsi::Runtime *jsiRuntime) {
189
210
  return jsi::Function::createFromHostFunction(
190
211
  *jsiRuntime,
@@ -68,9 +68,24 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, enqueueBuffer) {
68
68
  auto audioBufferHostObject =
69
69
  args[0].getObject(runtime).asHostObject<AudioBufferHostObject>(runtime);
70
70
  // TODO: add optimized memory management for buffer changes, e.g.
71
- // when the same buffer is reused across threads and
71
+ // when the same buffer is reused across threads and
72
72
  // buffer modification is not allowed on JS thread
73
- auto copiedBuffer = std::make_shared<AudioBuffer>(*audioBufferHostObject->audioBuffer_);
73
+
74
+ auto swapBuffer = false; // whether to swap internal node buffer with the new buffer
75
+ if (!channelCountSet_) {
76
+ channelCount_ = static_cast<int>(audioBufferHostObject->audioBuffer_->getNumberOfChannels());
77
+ channelCountSet_ = true;
78
+ swapBuffer = true;
79
+ }
80
+
81
+ // first buffer defines channel count, rest of them is mixed to channel count of the first buffer
82
+ auto copiedBuffer = std::make_shared<AudioBuffer>(
83
+ audioBufferHostObject->audioBuffer_->getSize(),
84
+ channelCount_,
85
+ audioBufferHostObject->audioBuffer_->getSampleRate());
86
+
87
+ copiedBuffer->sum(*audioBufferHostObject->audioBuffer_);
88
+
74
89
  std::shared_ptr<AudioBuffer> tailBuffer = nullptr;
75
90
 
76
91
  if (pitchCorrection_ && !stretchHasBeenInit_) {
@@ -84,8 +99,15 @@ JSI_HOST_FUNCTION_IMPL(AudioBufferQueueSourceNodeHostObject, enqueueBuffer) {
84
99
  stretchHasBeenInit_ = true;
85
100
  }
86
101
 
87
- auto event = [audioBufferQueueSourceNode, copiedBuffer, bufferId = bufferId_, tailBuffer](
88
- BaseAudioContext &) {
102
+ auto event = [audioBufferQueueSourceNode,
103
+ copiedBuffer,
104
+ bufferId = bufferId_,
105
+ tailBuffer,
106
+ swapBuffer,
107
+ channelCount = channelCount_](BaseAudioContext &) {
108
+ if (swapBuffer) {
109
+ audioBufferQueueSourceNode->setChannelCount(static_cast<int>(channelCount));
110
+ }
89
111
  audioBufferQueueSourceNode->enqueueBuffer(copiedBuffer, bufferId, tailBuffer);
90
112
  };
91
113
  audioBufferQueueSourceNode->scheduleAudioEvent(std::move(event));
@@ -30,6 +30,7 @@ class AudioBufferQueueSourceNodeHostObject : public AudioBufferBaseSourceNodeHos
30
30
  size_t bufferId_ = 0;
31
31
  uint64_t onBufferEndedCallbackId_ = 0;
32
32
  bool stretchHasBeenInit_ = false;
33
+ bool channelCountSet_ = false;
33
34
 
34
35
  void setOnBufferEndedCallbackId(uint64_t callbackId);
35
36
  };
@@ -14,8 +14,8 @@ AudioFileSourceNodeHostObject::AudioFileSourceNodeHostObject(
14
14
  const AudioFileSourceOptions &options)
15
15
  : AudioScheduledSourceNodeHostObject(context->createFileSource(options), options),
16
16
  loop_(options.loop),
17
- volume_(options.volume),
18
- duration_(std::static_pointer_cast<AudioFileSourceNode>(node_)->getDuration()) {
17
+ duration_(std::static_pointer_cast<AudioFileSourceNode>(node_)->getDuration()),
18
+ volume_(options.volume) {
19
19
  addGetters(
20
20
  JSI_EXPORT_PROPERTY_GETTER(AudioFileSourceNodeHostObject, volume),
21
21
  JSI_EXPORT_PROPERTY_GETTER(AudioFileSourceNodeHostObject, loop),
@@ -28,7 +28,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithMemoryBlock) {
28
28
  auto sampleRate = static_cast<float>(args[1].getNumber());
29
29
 
30
30
  auto promise = promiseVendor_->createAsyncPromise([data, size, sampleRate]() -> PromiseResolver {
31
- auto result = AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);
31
+ auto result = audiodecoder::decodeWithMemoryBlock(data, size, sampleRate);
32
32
 
33
33
  if (result.is_err()) {
34
34
  return [result = std::move(result)](
@@ -54,7 +54,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithFilePath) {
54
54
  auto sampleRate = static_cast<float>(args[1].getNumber());
55
55
 
56
56
  auto promise = promiseVendor_->createAsyncPromise([sourcePath, sampleRate]() -> PromiseResolver {
57
- auto result = AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);
57
+ auto result = audiodecoder::decodeWithFilePath(sourcePath, sampleRate);
58
58
 
59
59
  if (result.is_err()) {
60
60
  return [result = std::move(result)](
@@ -84,7 +84,7 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {
84
84
 
85
85
  auto promise = promiseVendor_->createAsyncPromise(
86
86
  [b64, inputSampleRate, inputChannelCount, interleaved]() -> PromiseResolver {
87
- auto result = AudioDecoder::decodeWithPCMInBase64(
87
+ auto result = audiodecoder::decodeWithPCMInBase64(
88
88
  b64, inputSampleRate, inputChannelCount, interleaved);
89
89
 
90
90
  if (result.is_err()) {
@@ -0,0 +1,60 @@
1
+ #include <audioapi/HostObjects/utils/AudioFileUtilsHostObject.h>
2
+ #include <audioapi/core/utils/AudioFileConcatenator.h>
3
+ #include <audioapi/jsi/JsiPromise.h>
4
+
5
+ #include <jsi/jsi.h>
6
+ #include <memory>
7
+ #include <string>
8
+ #include <utility>
9
+ #include <vector>
10
+
11
+ namespace audioapi {
12
+
13
+ AudioFileUtilsHostObject::AudioFileUtilsHostObject(
14
+ jsi::Runtime *runtime,
15
+ const std::shared_ptr<react::CallInvoker> &callInvoker) {
16
+ promiseVendor_ = std::make_shared<PromiseVendor>(runtime, callInvoker);
17
+ addFunctions(JSI_EXPORT_FUNCTION(AudioFileUtilsHostObject, concatAudioFiles));
18
+ }
19
+
20
+ JSI_HOST_FUNCTION_IMPL(AudioFileUtilsHostObject, concatAudioFiles) {
21
+ if (count < 2 || !args[0].isObject() || !args[1].isString()) {
22
+ throw jsi::JSError(runtime, "concatAudioFiles expects input paths and an output path.");
23
+ }
24
+
25
+ auto inputPathArray = args[0].asObject(runtime).asArray(runtime);
26
+ const auto inputPathCount = inputPathArray.size(runtime);
27
+ std::vector<std::string> inputPaths;
28
+ inputPaths.reserve(inputPathCount);
29
+
30
+ for (size_t i = 0; i < inputPathCount; ++i) {
31
+ auto value = inputPathArray.getValueAtIndex(runtime, i);
32
+ if (!value.isString()) {
33
+ throw jsi::JSError(runtime, "concatAudioFiles input paths must be strings.");
34
+ }
35
+ inputPaths.push_back(value.asString(runtime).utf8(runtime));
36
+ }
37
+
38
+ auto outputPath = args[1].asString(runtime).utf8(runtime);
39
+
40
+ auto promise = promiseVendor_->createAsyncPromise(
41
+ [inputPaths = std::move(inputPaths), outputPath]() -> PromiseResolver {
42
+ auto result = audioapi::concatAudioFiles(inputPaths, outputPath);
43
+
44
+ if (result.is_err()) {
45
+ return [result = std::move(result)](
46
+ jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
47
+ return result.unwrap_err();
48
+ };
49
+ }
50
+
51
+ return [outputPath = std::move(outputPath)](
52
+ jsi::Runtime &runtime) -> std::variant<jsi::Value, std::string> {
53
+ return jsi::String::createFromUtf8(runtime, outputPath);
54
+ };
55
+ });
56
+
57
+ return promise;
58
+ }
59
+
60
+ } // namespace audioapi
@@ -0,0 +1,24 @@
1
+ #pragma once
2
+
3
+ #include <audioapi/jsi/JsiHostObject.h>
4
+ #include <audioapi/jsi/JsiPromise.h>
5
+
6
+ #include <jsi/jsi.h>
7
+ #include <memory>
8
+
9
+ namespace audioapi {
10
+ using namespace facebook;
11
+
12
+ class AudioFileUtilsHostObject : public JsiHostObject {
13
+ public:
14
+ explicit AudioFileUtilsHostObject(
15
+ jsi::Runtime *runtime,
16
+ const std::shared_ptr<react::CallInvoker> &callInvoker);
17
+
18
+ JSI_HOST_FUNCTION_DECL(concatAudioFiles);
19
+
20
+ private:
21
+ std::shared_ptr<PromiseVendor> promiseVendor_;
22
+ };
23
+
24
+ } // namespace audioapi
@@ -309,14 +309,14 @@ inline AudioFileSourceOptions parseAudioFileSourceOptions(
309
309
  if (sourceValue.isString()) {
310
310
  options.filePath = sourceValue.asString(runtime).utf8(runtime);
311
311
  options.requiresFFmpeg =
312
- AudioDecoder::pathHasExtension(options.filePath, {".mp4", ".m4a", ".aac"});
312
+ audiodecoder::pathHasExtension(options.filePath, {".mp4", ".m4a", ".aac"});
313
313
  } else if (sourceValue.isObject()) {
314
314
  auto sourceObj = sourceValue.asObject(runtime);
315
315
  if (sourceObj.isArrayBuffer(runtime)) {
316
316
  auto arrayBuffer = sourceObj.getArrayBuffer(runtime);
317
317
  auto *data = arrayBuffer.data(runtime);
318
318
  auto size = arrayBuffer.size(runtime);
319
- auto format = AudioDecoder::detectAudioFormat(data, size);
319
+ auto format = audiodecoder::detectAudioFormat(data, size);
320
320
  options.requiresFFmpeg =
321
321
  format == AudioFormat::MP4 || format == AudioFormat::M4A || format == AudioFormat::AAC;
322
322
  options.data = std::vector<uint8_t>(data, data + size);
@@ -23,7 +23,6 @@ OfflineAudioContext::OfflineAudioContext(
23
23
  const RuntimeRegistry &runtimeRegistry)
24
24
  : BaseAudioContext(sampleRate, audioEventHandlerRegistry, runtimeRegistry),
25
25
  length_(length),
26
- numberOfChannels_(numberOfChannels),
27
26
  currentSampleFrame_(0),
28
27
  audioBuffer_(
29
28
  std::make_shared<DSPAudioBuffer>(RENDER_QUANTUM_SIZE, numberOfChannels, sampleRate)),
@@ -41,7 +41,6 @@ class OfflineAudioContext : public BaseAudioContext {
41
41
  OfflineAudioContextResultCallback resultCallback_;
42
42
 
43
43
  const size_t length_;
44
- const int numberOfChannels_;
45
44
  size_t currentSampleFrame_;
46
45
 
47
46
  std::shared_ptr<DSPAudioBuffer> audioBuffer_;
@@ -88,21 +88,15 @@ void ConvolverNode::onInputDisabled() {
88
88
  }
89
89
  }
90
90
 
91
- std::shared_ptr<DSPAudioBuffer> ConvolverNode::processInputs(
92
- const std::shared_ptr<DSPAudioBuffer> &outputBuffer,
93
- int framesToProcess,
94
- bool checkIsAlreadyProcessed) {
95
- if (internalBufferIndex_ < framesToProcess) {
96
- return AudioNode::processInputs(outputBuffer, RENDER_QUANTUM_SIZE, false);
97
- }
98
- return AudioNode::processInputs(outputBuffer, 0, false);
99
- }
100
-
101
91
  // processing pipeline: processingBuffer -> intermediateBuffer_ -> audioBuffer_ (mixing
102
92
  // with intermediateBuffer_)
103
93
  std::shared_ptr<DSPAudioBuffer> ConvolverNode::processNode(
104
94
  const std::shared_ptr<DSPAudioBuffer> &processingBuffer,
105
95
  int framesToProcess) {
96
+ if (processingBuffer->getSize() != RENDER_QUANTUM_SIZE) {
97
+ printf(
98
+ "[AUDIOAPI WARN] convolver requires 128 buffer size for each render quantum, otherwise quality of convolution is very poor\n");
99
+ }
106
100
  if (signalledToStop_) {
107
101
  if (remainingSegments_ > 0) {
108
102
  remainingSegments_--;