react-native-audio-api 0.11.0-nightly-95f9c99-20251215 → 0.11.0-nightly-dd83923-20251216

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +382 -39
  2. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +45 -18
  3. package/android/src/main/cpp/audioapi/android/core/NativeAudioRecorder.hpp +9 -9
  4. package/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h +33 -0
  5. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +170 -0
  6. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +46 -0
  7. package/android/src/main/cpp/audioapi/android/core/utils/AudioDecoder.cpp +0 -1
  8. package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.cpp +83 -0
  9. package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.h +22 -0
  10. package/android/src/main/cpp/audioapi/android/core/utils/MiniaudioImplementation.cpp +8 -0
  11. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +493 -0
  12. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h +70 -0
  13. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/ptrs.hpp +56 -0
  14. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.cpp +114 -0
  15. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.h +34 -0
  16. package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp +296 -0
  17. package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +40 -0
  18. package/android/src/main/cpp/audioapi/android/system/NativeFileInfo.hpp +32 -0
  19. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +2 -0
  20. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +7 -3
  21. package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +1 -0
  22. package/android/src/main/java/com/swmansion/audioapi/system/NativeFileInfo.kt +18 -0
  23. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +1 -0
  24. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +2 -0
  25. package/android/src/oldarch/NativeAudioAPIModuleSpec.java +100 -80
  26. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +3 -11
  27. package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp +145 -16
  28. package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.h +21 -6
  29. package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +43 -60
  30. package/common/cpp/audioapi/core/inputs/AudioRecorder.h +53 -33
  31. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +42 -14
  32. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +12 -9
  33. package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +41 -0
  34. package/common/cpp/audioapi/core/utils/AudioFileWriter.h +44 -0
  35. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +101 -0
  36. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +52 -0
  37. package/common/cpp/audioapi/utils/AudioFileProperties.cpp +92 -0
  38. package/common/cpp/audioapi/utils/AudioFileProperties.h +76 -0
  39. package/common/cpp/audioapi/utils/Result.hpp +323 -0
  40. package/common/cpp/audioapi/utils/UnitConversion.h +9 -0
  41. package/ios/audioapi/ios/AudioAPIModule.mm +9 -14
  42. package/ios/audioapi/ios/core/IOSAudioPlayer.h +1 -1
  43. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +7 -6
  44. package/ios/audioapi/ios/core/IOSAudioRecorder.h +39 -13
  45. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +302 -26
  46. package/ios/audioapi/ios/core/NativeAudioPlayer.m +7 -11
  47. package/ios/audioapi/ios/core/NativeAudioRecorder.h +8 -9
  48. package/ios/audioapi/ios/core/NativeAudioRecorder.m +70 -76
  49. package/ios/audioapi/ios/core/utils/AudioDecoder.mm +1 -0
  50. package/ios/audioapi/ios/core/utils/FileOptions.h +33 -0
  51. package/ios/audioapi/ios/core/utils/FileOptions.mm +195 -0
  52. package/ios/audioapi/ios/core/utils/IOSFileWriter.h +53 -0
  53. package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +239 -0
  54. package/ios/audioapi/ios/core/utils/IOSRecorderCallback.h +47 -0
  55. package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +185 -0
  56. package/ios/audioapi/ios/system/AudioEngine.h +21 -16
  57. package/ios/audioapi/ios/system/AudioEngine.mm +138 -130
  58. package/ios/audioapi/ios/system/AudioSessionManager.h +19 -9
  59. package/ios/audioapi/ios/system/AudioSessionManager.mm +250 -215
  60. package/ios/audioapi/ios/system/NotificationManager.mm +24 -42
  61. package/lib/commonjs/api.js +82 -109
  62. package/lib/commonjs/api.js.map +1 -1
  63. package/lib/commonjs/core/AudioRecorder.js +159 -13
  64. package/lib/commonjs/core/AudioRecorder.js.map +1 -1
  65. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  66. package/lib/commonjs/system/notification/PlaybackNotificationManager.js +17 -14
  67. package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -1
  68. package/lib/commonjs/system/notification/RecordingNotificationManager.js +22 -19
  69. package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -1
  70. package/lib/commonjs/system/notification/SimpleNotificationManager.js +16 -13
  71. package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -1
  72. package/lib/commonjs/types.js +39 -0
  73. package/lib/commonjs/types.js.map +1 -1
  74. package/lib/commonjs/utils/filePresets.js +43 -0
  75. package/lib/commonjs/utils/filePresets.js.map +1 -0
  76. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +6 -3
  77. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -1
  78. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +6 -3
  79. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -1
  80. package/lib/module/api.js +5 -4
  81. package/lib/module/api.js.map +1 -1
  82. package/lib/module/core/AudioRecorder.js +159 -13
  83. package/lib/module/core/AudioRecorder.js.map +1 -1
  84. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  85. package/lib/module/system/notification/PlaybackNotificationManager.js +17 -14
  86. package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -1
  87. package/lib/module/system/notification/RecordingNotificationManager.js +22 -19
  88. package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -1
  89. package/lib/module/system/notification/SimpleNotificationManager.js +16 -13
  90. package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -1
  91. package/lib/module/types.js +38 -1
  92. package/lib/module/types.js.map +1 -1
  93. package/lib/module/utils/filePresets.js +39 -0
  94. package/lib/module/utils/filePresets.js.map +1 -0
  95. package/lib/module/web-system/notification/PlaybackNotificationManager.js +6 -3
  96. package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -1
  97. package/lib/module/web-system/notification/RecordingNotificationManager.js +6 -3
  98. package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -1
  99. package/lib/typescript/api.d.ts +5 -4
  100. package/lib/typescript/api.d.ts.map +1 -1
  101. package/lib/typescript/core/AudioRecorder.d.ts +69 -7
  102. package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
  103. package/lib/typescript/events/types.d.ts +36 -2
  104. package/lib/typescript/events/types.d.ts.map +1 -1
  105. package/lib/typescript/interfaces.d.ts +24 -4
  106. package/lib/typescript/interfaces.d.ts.map +1 -1
  107. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +1 -1
  108. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  109. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +4 -3
  110. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -1
  111. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +4 -3
  112. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -1
  113. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +3 -2
  114. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -1
  115. package/lib/typescript/system/notification/types.d.ts.map +1 -1
  116. package/lib/typescript/types.d.ts +79 -3
  117. package/lib/typescript/types.d.ts.map +1 -1
  118. package/lib/typescript/utils/filePresets.d.ts +9 -0
  119. package/lib/typescript/utils/filePresets.d.ts.map +1 -0
  120. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +4 -3
  121. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -1
  122. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +4 -3
  123. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -1
  124. package/package.json +4 -4
  125. package/src/AudioAPIModule/globals.d.ts +1 -2
  126. package/src/api.ts +8 -29
  127. package/src/core/AudioRecorder.ts +195 -23
  128. package/src/events/types.ts +40 -2
  129. package/src/interfaces.ts +34 -5
  130. package/src/specs/NativeAudioAPIModule.ts +2 -2
  131. package/src/system/notification/PlaybackNotificationManager.ts +20 -16
  132. package/src/system/notification/RecordingNotificationManager.ts +26 -21
  133. package/src/system/notification/SimpleNotificationManager.ts +18 -13
  134. package/src/system/notification/types.ts +1 -0
  135. package/src/types.ts +89 -3
  136. package/src/utils/filePresets.ts +47 -0
  137. package/src/web-system/notification/PlaybackNotificationManager.ts +9 -5
  138. package/src/web-system/notification/RecordingNotificationManager.ts +9 -5
@@ -0,0 +1,493 @@
1
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
2
+
3
+ extern "C" {
4
+ #include <libavcodec/avcodec.h>
5
+ #include <libavformat/avformat.h>
6
+ #include <libavutil/audio_fifo.h>
7
+ #include <libavutil/opt.h>
8
+ #include <libswresample/swresample.h>
9
+ }
10
+
11
+ #include <audioapi/android/core/utils/AndroidFileWriterBackend.h>
12
+ #include <audioapi/android/core/utils/FileOptions.h>
13
+ #include <audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h>
14
+ #include <audioapi/android/core/utils/ffmpegBackend/ptrs.hpp>
15
+ #include <audioapi/android/core/utils/ffmpegBackend/utils.h>
16
+ #include <audioapi/utils/AudioFileProperties.h>
17
+ #include <audioapi/utils/UnitConversion.h>
18
+
19
+ #include <algorithm>
20
+ #include <memory>
21
+ #include <string>
22
+ #include <utility>
23
+
24
+ constexpr int defaultFrameRatio = 4;
25
+ constexpr int fallbackFIFOSize = 8192;
26
+ constexpr int defaultFlushInterval = 100;
27
+
28
+ namespace audioapi::android::ffmpeg {
29
+
30
+ FFmpegAudioFileWriter::FFmpegAudioFileWriter(
31
+ const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
32
+ const std::shared_ptr<AudioFileProperties> &fileProperties)
33
+ : AndroidFileWriterBackend(audioEventHandlerRegistry, fileProperties) {
34
+ // Set flush interval from properties, limit minimum to 100ms
35
+ // to avoid people hurting themselves too much
36
+ flushIntervalMs_ = std::min(fileProperties_->androidFlushIntervalMs, defaultFlushInterval);
37
+ }
38
+
39
+ FFmpegAudioFileWriter::~FFmpegAudioFileWriter() {
40
+ if (isFileOpen()) {
41
+ closeFile();
42
+ }
43
+ }
44
+
45
+ /// @brief Opens a specified audio file for writing and prepares any necessary resources.
46
+ /// such as codecs, conversion buffers or circular AVIO FIFO.
47
+ /// This method should be called from the JS thread only.
48
+ /// @param streamSampleRate The sample rate of the incoming audio stream (aka microphone).
49
+ /// @param streamChannelCount The number of channels in the incoming audio stream.
50
+ /// @param streamMaxBufferSize The estimated maximum buffer size for the incoming audio stream.
51
+ /// @returns Success status with file path or Error status with message.
52
+ OpenFileResult FFmpegAudioFileWriter::openFile(
53
+ float streamSampleRate,
54
+ int32_t streamChannelCount,
55
+ int32_t streamMaxBufferSize) {
56
+ streamSampleRate_ = streamSampleRate;
57
+ streamChannelCount_ = streamChannelCount;
58
+ streamMaxBufferSize_ = streamMaxBufferSize;
59
+ framesWritten_.store(0, std::memory_order_release);
60
+ nextPts_ = 0;
61
+ Result<NoneType, std::string> result = Result<NoneType, std::string>::Ok(None);
62
+ Result<std::string, std::string> filePathResult = fileoptions::getFilePath(fileProperties_);
63
+
64
+ if (!filePathResult.is_ok()) {
65
+ return OpenFileResult::Err(filePathResult.unwrap_err());
66
+ }
67
+
68
+ filePath_ = filePathResult.unwrap();
69
+
70
+ const AVCodec *codec = getCodec(fileProperties_);
71
+
72
+ if (!codec) {
73
+ return OpenFileResult::Err("Unsupported codec for the given file format");
74
+ }
75
+
76
+ return initializeFormatContext(codec)
77
+ .and_then([this, codec](auto) { return configureAndOpenCodec(codec); })
78
+ .and_then([this](auto) { return initializeStream(); })
79
+ .and_then([this](auto) { return openIOAndWriteHeader(); })
80
+ .and_then([this, streamSampleRate, streamChannelCount](auto) {
81
+ return initializeResampler(streamSampleRate, streamChannelCount);
82
+ })
83
+ .and_then([this, streamMaxBufferSize, filePath = std::move(filePath_)](auto) {
84
+ initializeBuffers(streamMaxBufferSize);
85
+ isFileOpen_.store(true, std::memory_order_release);
86
+ return OpenFileResult::Ok(filePath);
87
+ });
88
+ }
89
+
90
+ /// @brief Closes the currently opened audio file, flushing any remaining data and finalizing the file.
91
+ /// This method should called from the JS thread only.
92
+ /// @returns CloseFileStatus indicating success with file path, size and duration, or error with message.
93
+ CloseFileResult FFmpegAudioFileWriter::closeFile() {
94
+ int result = 0;
95
+
96
+ if (!isFileOpen()) {
97
+ return CloseFileResult::Err("File is not open");
98
+ }
99
+
100
+ result = processFifo(true);
101
+
102
+ if (result < 0) {
103
+ auto finalStatus = finalizeOutput();
104
+
105
+ return CloseFileResult::Err(
106
+ "Failed to flush FIFO to encoder. error code: " + parseErrorCode(result) +
107
+ ", finalization status: " + (finalStatus.is_ok() ? "success" : finalStatus.unwrap_err()));
108
+ }
109
+
110
+ result = avcodec_send_frame(encoderCtx_.get(), nullptr);
111
+
112
+ if (result < 0) {
113
+ return CloseFileResult::Err("Failed to send EOF to encoder");
114
+ }
115
+
116
+ if (writeEncodedPackets() < 0) {
117
+ return CloseFileResult::Err("Failed to drain encoder packets");
118
+ }
119
+
120
+ return finalizeOutput();
121
+ }
122
+
123
+ /// @brief Writes audio data to the currently opened file.
124
+ /// This method should be called only from the audio thread (or audio side-effect thread in the future).
125
+ /// @param data Pointer to the audio data buffer (interleaved float samples) as returned by Oboe stream.
126
+ /// @param numFrames Number of audio frames in the data buffer.
127
+ /// @returns True if the data was written successfully, false otherwise.
128
+ bool FFmpegAudioFileWriter::writeAudioData(void *data, int numFrames) {
129
+ if (!isFileOpen()) {
130
+ return false;
131
+ }
132
+
133
+ if (!resampleAndPushToFifo(data, numFrames)) {
134
+ return false;
135
+ }
136
+
137
+ framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel);
138
+
139
+ if (processFifo(false) < 0) {
140
+ return false;
141
+ }
142
+
143
+ return true;
144
+ }
145
+
146
+ /// @brief Initializes the FFmpeg format context for the output file.
147
+ /// @param codec The codec to be used for encoding.
148
+ /// @returns Success status or Error status with message.
149
+ Result<NoneType, std::string> FFmpegAudioFileWriter::initializeFormatContext(const AVCodec *codec) {
150
+ AVFormatContext *rawFormatCtx = nullptr;
151
+
152
+ int result = avformat_alloc_output_context2(
153
+ &rawFormatCtx, nullptr, getMuxerName(fileProperties_).c_str(), filePath_.c_str());
154
+
155
+ if (result < 0 || !rawFormatCtx) {
156
+ return Result<NoneType, std::string>::Err(
157
+ "Failed to allocate FFmpeg format context with error: " + parseErrorCode(result));
158
+ }
159
+
160
+ formatCtx_ = av_unique_ptr<AVFormatContext>(rawFormatCtx);
161
+ return Result<NoneType, std::string>::Ok(None);
162
+ }
163
+
164
+ /// @brief Configures and opens the codec context for encoding.
165
+ /// @param codec The codec to be used for encoding.
166
+ /// @returns Success status or Error status with message.
167
+ Result<NoneType, std::string> FFmpegAudioFileWriter::configureAndOpenCodec(const AVCodec *codec) {
168
+ encoderCtx_ = av_unique_ptr<AVCodecContext>(avcodec_alloc_context3(codec));
169
+
170
+ if (!encoderCtx_) {
171
+ return Result<NoneType, std::string>::Err("Failed to allocate FFmpeg codec context");
172
+ }
173
+
174
+ av_channel_layout_default(&encoderCtx_->ch_layout, fileProperties_->channelCount);
175
+ encoderCtx_->sample_rate = static_cast<int>(fileProperties_->sampleRate);
176
+ encoderCtx_->sample_fmt = getSampleFormat(fileProperties_);
177
+
178
+ if (fileProperties_->bitRate > 0) {
179
+ encoderCtx_->bit_rate = fileProperties_->bitRate;
180
+ }
181
+
182
+ AVDictionary *codecOptions = nullptr;
183
+
184
+ if (fileProperties_->flacCompressionLevel >= 0) {
185
+ av_dict_set_int(&codecOptions, "compression_level", fileProperties_->flacCompressionLevel, 0);
186
+ }
187
+
188
+ int result = avcodec_open2(encoderCtx_.get(), codec, &codecOptions);
189
+ av_dict_free(&codecOptions);
190
+
191
+ if (result < 0) {
192
+ return Result<NoneType, std::string>::Err(
193
+ "Failed to open FFmpeg codec with error: " + parseErrorCode(result));
194
+ }
195
+
196
+ return Result<NoneType, std::string>::Ok(None);
197
+ }
198
+
199
+ /// @brief Initializes a new stream in the format context.
200
+ /// @returns Success status or Error status with message.
201
+ Result<NoneType, std::string> FFmpegAudioFileWriter::initializeStream() {
202
+ stream_ = avformat_new_stream(formatCtx_.get(), nullptr);
203
+
204
+ if (!stream_) {
205
+ return Result<NoneType, std::string>::Err("Failed to create new stream in format context");
206
+ }
207
+
208
+ int result = avcodec_parameters_from_context(stream_->codecpar, encoderCtx_.get());
209
+
210
+ if (result < 0) {
211
+ return Result<NoneType, std::string>::Err(
212
+ "Failed to copy codec parameters to stream with error: " + parseErrorCode(result));
213
+ }
214
+
215
+ stream_->time_base = AVRational{1, static_cast<int>(encoderCtx_->sample_rate)};
216
+ return Result<NoneType, std::string>::Ok(None);
217
+ }
218
+
219
+ /// @brief Opens the file and writes the basic header (depends on the codec/format used).
220
+ /// @returns Success status or Error status with message.
221
+ Result<NoneType, std::string> FFmpegAudioFileWriter::openIOAndWriteHeader() {
222
+ int result = 0;
223
+
224
+ if (!(formatCtx_->oformat->flags & AVFMT_NOFILE)) {
225
+ result = avio_open(&formatCtx_->pb, filePath_.c_str(), AVIO_FLAG_WRITE);
226
+
227
+ if (result < 0) {
228
+ return Result<NoneType, std::string>::Err(
229
+ "Failed to open output file with error: " + parseErrorCode(result));
230
+ }
231
+ }
232
+
233
+ result = avformat_write_header(formatCtx_.get(), nullptr);
234
+
235
+ if (result < 0) {
236
+ return Result<NoneType, std::string>::Err("Failed to write header to file: " + filePath_);
237
+ }
238
+
239
+ return Result<NoneType, std::string>::Ok(None);
240
+ }
241
+
242
+ /// @brief Initializes the resampler context for audio conversion.
243
+ /// @param inputRate The sample rate of the input audio.
244
+ /// @param inputChannels The number of channels in the input audio.
245
+ /// @returns Success status or Error status with message.
246
+ Result<NoneType, std::string> FFmpegAudioFileWriter::initializeResampler(
247
+ float inputRate,
248
+ int inputChannels) {
249
+ resampleCtx_ = av_unique_ptr<SwrContext>(swr_alloc());
250
+
251
+ if (!resampleCtx_) {
252
+ return Result<NoneType, std::string>::Err("Failed to allocate resampler context");
253
+ }
254
+
255
+ AVChannelLayout inChannelLayout;
256
+ av_channel_layout_default(&inChannelLayout, inputChannels);
257
+
258
+ av_opt_set_chlayout(resampleCtx_.get(), "in_chlayout", &inChannelLayout, 0);
259
+ av_opt_set_chlayout(resampleCtx_.get(), "out_chlayout", &encoderCtx_->ch_layout, 0);
260
+
261
+ av_opt_set_int(resampleCtx_.get(), "in_sample_rate", static_cast<int64_t>(inputRate), 0);
262
+ av_opt_set_int(resampleCtx_.get(), "out_sample_rate", encoderCtx_->sample_rate, 0);
263
+
264
+ av_opt_set_sample_fmt(resampleCtx_.get(), "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
265
+ av_opt_set_sample_fmt(resampleCtx_.get(), "out_sample_fmt", encoderCtx_->sample_fmt, 0);
266
+
267
+ int result = swr_init(resampleCtx_.get());
268
+
269
+ if (result < 0) {
270
+ return Result<NoneType, std::string>::Err(
271
+ "Failed to initialize resampler for file: " + parseErrorCode(result));
272
+ }
273
+
274
+ return Result<NoneType, std::string>::Ok(None);
275
+ }
276
+
277
+ /// @brief Initializes frame and packet buffers as well as the audio FIFO,
278
+ /// that might be needed for storing intermediate audio data or buffering before encoding.
279
+ /// @param maxBufferSize The maximum buffer size to allocate.
280
+ void FFmpegAudioFileWriter::initializeBuffers(int32_t maxBufferSize) {
281
+ frame_ = av_unique_ptr<AVFrame>(av_frame_alloc());
282
+ packet_ = av_unique_ptr<AVPacket>(av_packet_alloc());
283
+
284
+ int frameRatio = defaultFrameRatio;
285
+ if (encoderCtx_->frame_size > 0) {
286
+ frameRatio = static_cast<int>(std::ceil(
287
+ static_cast<double>(maxBufferSize) / static_cast<double>(encoderCtx_->frame_size)));
288
+ }
289
+
290
+ int calculatedSize =
291
+ (encoderCtx_->frame_size > 0 ? encoderCtx_->frame_size * frameRatio
292
+ : maxBufferSize * frameRatio);
293
+
294
+ int fifoSize = std::max(calculatedSize, fallbackFIFOSize);
295
+
296
+ audioFifo_ = av_unique_ptr<AVAudioFifo>(
297
+ av_audio_fifo_alloc(encoderCtx_->sample_fmt, encoderCtx_->ch_layout.nb_channels, fifoSize));
298
+ }
299
+
300
+ /// @brief Resamples input audio data and pushes it to the audio FIFO.
301
+ /// @param inputData Pointer to the input audio data.
302
+ /// @param inputFrameCount Number of input frames.
303
+ /// @returns True if successful, false otherwise.
304
+ bool FFmpegAudioFileWriter::resampleAndPushToFifo(void *inputData, int inputFrameCount) {
305
+ int result = 0;
306
+ int64_t outputLength = av_rescale_rnd(
307
+ inputFrameCount, encoderCtx_->sample_rate, static_cast<int>(streamSampleRate_), AV_ROUND_UP);
308
+
309
+ result = prepareFrameForEncoding(outputLength);
310
+
311
+ if (result < 0) {
312
+ invokeOnErrorCallback("Failed to prepare frame for resampling: " + parseErrorCode(result));
313
+ return false;
314
+ }
315
+
316
+ const uint8_t *inputs[1] = {reinterpret_cast<const uint8_t *>(inputData)};
317
+
318
+ int convertedSamples = swr_convert(
319
+ resampleCtx_.get(), frame_->data, static_cast<int>(outputLength), inputs, inputFrameCount);
320
+
321
+ if (convertedSamples < 0) {
322
+ invokeOnErrorCallback("Failed to convert audio samples: " + parseErrorCode(convertedSamples));
323
+ av_frame_unref(frame_.get());
324
+ return false;
325
+ }
326
+
327
+ int written = av_audio_fifo_write(
328
+ audioFifo_.get(), reinterpret_cast<void **>(frame_->data), convertedSamples);
329
+
330
+ if (written < convertedSamples) {
331
+ invokeOnErrorCallback("Failed to write all samples to FIFO");
332
+ av_frame_unref(frame_.get());
333
+ return false;
334
+ }
335
+
336
+ return true;
337
+ }
338
+
339
+ /// @brief pushes the audio data from FIFO to the encoder in chunks,
340
+ // defined by the encoder (512 samples by default) or flushes the FIFO if requested.
341
+ /// Note: flush might be called only when writing the final data batch, otherwise
342
+ // the codec will crash (especially in case of defined size frames like AAC).
343
+ /// @param flush Indicates whether to flush the FIFO.
344
+ /// @returns 0 on success, -1 or AV_ERROR code on failure
345
+ int FFmpegAudioFileWriter::processFifo(bool flush) {
346
+ int result = 0;
347
+ int frameSize = encoderCtx_->frame_size > 0 ? encoderCtx_->frame_size : 512;
348
+
349
+ while (av_audio_fifo_size(audioFifo_.get()) >= (flush ? 1 : frameSize)) {
350
+ const int chunkSize = std::min(av_audio_fifo_size(audioFifo_.get()), frameSize);
351
+
352
+ if (prepareFrameForEncoding(chunkSize) < 0) {
353
+ invokeOnErrorCallback("Failed to prepare frame for encoding");
354
+ return -1;
355
+ }
356
+
357
+ if (av_audio_fifo_read(audioFifo_.get(), reinterpret_cast<void **>(frame_->data), chunkSize) !=
358
+ chunkSize) {
359
+ invokeOnErrorCallback("Failed to read data from FIFO");
360
+ return -1;
361
+ }
362
+
363
+ frame_->pts = nextPts_;
364
+ nextPts_ += chunkSize;
365
+
366
+ result = avcodec_send_frame(encoderCtx_.get(), frame_.get());
367
+
368
+ if (result < 0) {
369
+ invokeOnErrorCallback("Failed to send frame to encoder: " + parseErrorCode(result));
370
+ return result;
371
+ }
372
+
373
+ result = writeEncodedPackets();
374
+
375
+ if (result < 0) {
376
+ invokeOnErrorCallback("Failed to write encoded packets: " + parseErrorCode(result));
377
+ return result;
378
+ }
379
+ }
380
+
381
+ return 0;
382
+ }
383
+
384
+ /// @brief Takes ready encoded packets from the encoder and writes them to the output file.
385
+ /// Also in order to optimize file writing vs file resilience from crashes, it periodically
386
+ /// forces the AVIO buffer to flush data to disk, by default every 0,5 second.
387
+ /// @returns 0 on success, AV_ERROR code on failure
388
+ int FFmpegAudioFileWriter::writeEncodedPackets() {
389
+ int result = 0;
390
+
391
+ while (true) {
392
+ result = avcodec_receive_packet(encoderCtx_.get(), packet_.get());
393
+
394
+ if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
395
+ return 0;
396
+ } else if (result < 0) {
397
+ invokeOnErrorCallback("Failed to receive packet from encoder: " + parseErrorCode(result));
398
+ return result;
399
+ }
400
+
401
+ av_packet_rescale_ts(packet_.get(), encoderCtx_->time_base, stream_->time_base);
402
+ packet_->stream_index = stream_->index;
403
+
404
+ result = av_interleaved_write_frame(formatCtx_.get(), packet_.get());
405
+
406
+ auto now = std::chrono::steady_clock::now();
407
+ auto elapsedMs =
408
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - lastFlushTime_).count();
409
+
410
+ if (formatCtx_->pb && elapsedMs >= flushIntervalMs_) {
411
+ avio_flush(formatCtx_->pb);
412
+ lastFlushTime_ = now;
413
+ }
414
+
415
+ if (result < 0) {
416
+ return result;
417
+ }
418
+ }
419
+ }
420
+
421
+ /// @brief Prepares the frame for next encoding phase,
422
+ /// if frame is same size as previously used one (99.9% cases) try to reuse it.
423
+ /// Otherwise resize the frame and in the worst case allocate new frame to use.
424
+ /// @param samplesToRead Number of samples to prepare the frame for.
425
+ /// @returns 0 on success, AV_ERROR code on failure
426
+ int FFmpegAudioFileWriter::prepareFrameForEncoding(int64_t samplesToRead) {
427
+ int result = 0;
428
+
429
+ if (frame_->data[0] && frame_->nb_samples == samplesToRead &&
430
+ av_frame_is_writable(frame_.get())) {
431
+ return 0;
432
+ }
433
+
434
+ frame_->nb_samples = static_cast<int>(samplesToRead);
435
+ frame_->format = encoderCtx_->sample_fmt;
436
+ frame_->sample_rate = encoderCtx_->sample_rate;
437
+
438
+ if (av_channel_layout_compare(&frame_->ch_layout, &encoderCtx_->ch_layout) != 0) {
439
+ av_channel_layout_uninit(&frame_->ch_layout);
440
+
441
+ result = av_channel_layout_copy(&frame_->ch_layout, &encoderCtx_->ch_layout);
442
+
443
+ if (result < 0) {
444
+ invokeOnErrorCallback("Failed to copy channel layout: " + parseErrorCode(result));
445
+ return result;
446
+ }
447
+ }
448
+
449
+ result = av_frame_make_writable(frame_.get());
450
+
451
+ if (result < 0) {
452
+ av_frame_unref(frame_.get());
453
+
454
+ frame_->nb_samples = static_cast<int>(samplesToRead);
455
+ ;
456
+ frame_->format = encoderCtx_->sample_fmt;
457
+ frame_->sample_rate = encoderCtx_->sample_rate;
458
+ av_channel_layout_copy(&frame_->ch_layout, &encoderCtx_->ch_layout);
459
+
460
+ result = av_frame_get_buffer(frame_.get(), 0);
461
+ }
462
+
463
+ return result;
464
+ }
465
+
466
+ /// @brief Closes the currently opened audio file, flushing any remaining data and finalizing the file.
467
+ /// Method checks the file size and duration for convenience.
468
+ /// @returns CloseFileResult indicating success or error details
469
+ CloseFileResult FFmpegAudioFileWriter::finalizeOutput() {
470
+ int result = av_write_trailer(formatCtx_.get());
471
+
472
+ if (result < 0) {
473
+ return CloseFileResult::Err("Failed to write trailer: " + parseErrorCode(result));
474
+ }
475
+
476
+ double fileSizeInMB = 0;
477
+
478
+ if (formatCtx_->pb) {
479
+ fileSizeInMB = static_cast<double>(avio_size(formatCtx_->pb)) / MB_IN_BYTES;
480
+ avio_closep(&formatCtx_->pb);
481
+ }
482
+
483
+ double durationInSeconds = getCurrentDuration();
484
+
485
+ filePath_ = "";
486
+ isFileOpen_.store(false, std::memory_order_release);
487
+
488
+ return CloseFileResult::Ok({fileSizeInMB, durationInSeconds});
489
+ }
490
+
491
+ } // namespace audioapi::android::ffmpeg
492
+
493
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
@@ -0,0 +1,70 @@
1
+ #pragma once
2
+
3
+ #include <audioapi/android/core/utils/AndroidFileWriterBackend.h>
4
+ #include <audioapi/android/core/utils/ffmpegBackend/utils.h>
5
+ #include <string>
6
+ #include <memory>
7
+ #include <tuple>
8
+ #include <chrono>
9
+ #include <audioapi/utils/Result.hpp>
10
+
11
+ struct AVCodecContext;
12
+ struct AVFormatContext;
13
+ struct AVFrame;
14
+ struct AVPacket;
15
+ struct AVAudioFifo;
16
+ struct SwrContext;
17
+ struct AVStream;
18
+
19
+ namespace audioapi {
20
+
21
+ class AudioFileProperties;
22
+
23
+ namespace android::ffmpeg {
24
+
25
+ class FFmpegAudioFileWriter : public AndroidFileWriterBackend {
26
+ public:
27
+ explicit FFmpegAudioFileWriter(
28
+ const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
29
+ const std::shared_ptr<AudioFileProperties> &fileProperties);
30
+ ~FFmpegAudioFileWriter();
31
+
32
+ OpenFileResult openFile(float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize) override;
33
+ CloseFileResult closeFile() override;
34
+
35
+ bool writeAudioData(void *data, int numFrames) override;
36
+
37
+ private:
38
+ av_unique_ptr<AVCodecContext> encoderCtx_{nullptr};
39
+ av_unique_ptr<AVFormatContext> formatCtx_{nullptr};
40
+ av_unique_ptr<SwrContext> resampleCtx_{nullptr};
41
+ av_unique_ptr<AVAudioFifo> audioFifo_{nullptr};
42
+ av_unique_ptr<AVPacket> packet_{nullptr};
43
+ av_unique_ptr<AVFrame> frame_{nullptr};
44
+ AVStream* stream_{nullptr};
45
+ unsigned int nextPts_{0};
46
+
47
+ std::chrono::steady_clock::time_point lastFlushTime_ = std::chrono::steady_clock::now();
48
+ int flushIntervalMs_;
49
+
50
+ // Initialization helper methods
51
+ Result<NoneType, std::string> initializeFormatContext(const AVCodec* codec);
52
+ Result<NoneType, std::string> configureAndOpenCodec(const AVCodec* codec);
53
+ Result<NoneType, std::string> initializeStream();
54
+ Result<NoneType, std::string> openIOAndWriteHeader();
55
+ Result<NoneType, std::string> initializeResampler(float inputRate, int inputChannels);
56
+ void initializeBuffers(int32_t maxBufferSize);
57
+
58
+ // Processing helper methods
59
+ bool resampleAndPushToFifo(void *data, int numFrames);
60
+ int processFifo(bool flush);
61
+ int writeEncodedPackets();
62
+ int prepareFrameForEncoding(int64_t samplesToRead);
63
+
64
+ // Finalization helper methods
65
+ CloseFileResult finalizeOutput();
66
+ };
67
+
68
+ } // namespace android::ffmpeg
69
+
70
+ } // namespace audioapi
@@ -0,0 +1,56 @@
1
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
2
+
3
+ #pragma once
4
+
5
+ extern "C" {
6
+ #include <libavcodec/avcodec.h>
7
+ #include <libavformat/avformat.h>
8
+ #include <libavutil/audio_fifo.h>
9
+ #include <libswresample/swresample.h>
10
+ }
11
+
12
+ #include <audioapi/android/core/utils/ffmpegBackend/utils.h>
13
+
14
+ namespace audioapi::android::ffmpeg {
15
+
16
+ template<> inline void AvDtor<AVCodecContext>::operator()(AVCodecContext* ctx) const {
17
+ if (ctx != nullptr) {
18
+ avcodec_free_context(&ctx);
19
+ }
20
+ }
21
+
22
+ template<> inline void AvDtor<AVFormatContext>::operator()(AVFormatContext* ctx) const {
23
+ if (ctx != nullptr) {
24
+ avformat_free_context(ctx);
25
+ }
26
+ }
27
+
28
+ template<> inline void AvDtor<AVFrame>::operator()(AVFrame* frame) const {
29
+ if (frame != nullptr) {
30
+ av_frame_free(&frame);
31
+ }
32
+ }
33
+
34
+ template<> inline void AvDtor<AVPacket>::operator()(AVPacket* packet) const {
35
+ if (packet != nullptr) {
36
+ av_packet_free(&packet);
37
+ }
38
+ }
39
+
40
+ template<> inline void AvDtor<SwrContext>::operator()(SwrContext* ctx) const {
41
+ if (ctx != nullptr) {
42
+ swr_free(&ctx);
43
+ }
44
+ }
45
+
46
+ template<> inline void AvDtor<AVAudioFifo>::operator()(AVAudioFifo* fifo) const {
47
+ if (fifo != nullptr) {
48
+ av_audio_fifo_free(fifo);
49
+ }
50
+ }
51
+
52
+ } // namespace audioapi::android::ffmpeg
53
+
54
+ #else
55
+
56
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED