react-native-audio-api 0.11.0-alpha.0 → 0.11.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +77 -29
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +17 -4
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h +6 -2
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +187 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +57 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +22 -24
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h +3 -2
- package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp +40 -8
- package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +3 -2
- package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp +24 -7
- package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.h +3 -0
- package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +16 -115
- package/common/cpp/audioapi/core/inputs/AudioRecorder.h +35 -32
- package/ios/audioapi/ios/core/IOSAudioFileOptions.h +1 -0
- package/ios/audioapi/ios/core/IOSAudioFileOptions.mm +5 -0
- package/ios/audioapi/ios/core/IOSAudioFileWriter.h +16 -3
- package/ios/audioapi/ios/core/IOSAudioFileWriter.mm +86 -50
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +11 -2
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +76 -31
- package/ios/audioapi/ios/core/IOSRecorderCallback.h +57 -0
- package/ios/audioapi/ios/core/IOSRecorderCallback.mm +189 -0
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +2 -3
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +26 -0
- package/ios/audioapi/ios/core/utils/AudioDecoder.mm +1 -0
- package/lib/commonjs/core/AudioRecorder.js +14 -1
- package/lib/commonjs/core/AudioRecorder.js.map +1 -1
- package/lib/module/core/AudioRecorder.js +14 -1
- package/lib/module/core/AudioRecorder.js.map +1 -1
- package/lib/typescript/core/AudioRecorder.d.ts +6 -3
- package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
- package/lib/typescript/interfaces.d.ts +5 -3
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +5 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/AudioRecorder.ts +27 -10
- package/src/interfaces.ts +6 -2
- package/src/types.ts +6 -0
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
#include <audioapi/ios/core/IOSAudioFileOptions.h>
|
|
5
5
|
#include <audioapi/ios/core/IOSAudioFileWriter.h>
|
|
6
6
|
|
|
7
|
+
constexpr double BYTES_TO_MB = 1024.0 * 1024.0;
|
|
8
|
+
|
|
7
9
|
namespace audioapi {
|
|
8
10
|
IOSAudioFileWriter::IOSAudioFileWriter(float sampleRate, size_t channelCount, size_t bitRate, size_t iosFlags)
|
|
9
11
|
{
|
|
@@ -18,13 +20,14 @@ IOSAudioFileWriter::~IOSAudioFileWriter()
|
|
|
18
20
|
bufferFormat_ = nil;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
std::string IOSAudioFileWriter::openFile(AVAudioFormat *bufferFormat, size_t maxInputBufferLength)
|
|
22
24
|
{
|
|
23
25
|
@autoreleasepool {
|
|
24
26
|
if (audioFile_ != nil) {
|
|
25
27
|
NSLog(@"⚠️ createFileForWriting: currentAudioFile_ already exists");
|
|
26
|
-
return;
|
|
28
|
+
return "";
|
|
27
29
|
}
|
|
30
|
+
framesWritten_.store(0);
|
|
28
31
|
|
|
29
32
|
bufferFormat_ = bufferFormat;
|
|
30
33
|
|
|
@@ -44,28 +47,59 @@ void IOSAudioFileWriter::openFile(AVAudioFormat *bufferFormat)
|
|
|
44
47
|
converter_.sampleRateConverterQuality = AVAudioQualityMax;
|
|
45
48
|
converter_.primeMethod = AVAudioConverterPrimeMethod_None;
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
converterInputBufferSize_ = maxInputBufferLength;
|
|
51
|
+
converterOutputBufferSize_ = std::max(
|
|
52
|
+
(double)maxInputBufferLength, fileOptions_->getSampleRate() / bufferFormat.sampleRate * maxInputBufferLength);
|
|
53
|
+
|
|
54
|
+
converterInputBuffer_ = [[AVAudioPCMBuffer alloc] initWithPCMFormat:bufferFormat
|
|
55
|
+
frameCapacity:(AVAudioFrameCount)maxInputBufferLength];
|
|
56
|
+
converterOutputBuffer_ = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[audioFile_ processingFormat]
|
|
57
|
+
frameCapacity:(AVAudioFrameCount)maxInputBufferLength];
|
|
48
58
|
|
|
49
59
|
if (error != nil || audioFile_ == nil) {
|
|
50
60
|
NSLog(@"Error creating audio file for writing: %@", [error debugDescription]);
|
|
51
61
|
audioFile_ = nil;
|
|
62
|
+
|
|
63
|
+
return "";
|
|
52
64
|
}
|
|
65
|
+
|
|
66
|
+
return [[fileURL_ path] UTF8String];
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
std::
|
|
70
|
+
std::tuple<double, double> IOSAudioFileWriter::closeFile()
|
|
57
71
|
{
|
|
58
72
|
@autoreleasepool {
|
|
73
|
+
NSError *error;
|
|
59
74
|
std::string filePath = [[fileURL_ path] UTF8String];
|
|
60
75
|
|
|
61
76
|
if (audioFile_ == nil) {
|
|
62
|
-
return
|
|
77
|
+
return {0, 0};
|
|
63
78
|
}
|
|
64
79
|
|
|
65
80
|
// AVAudioFile automatically finalizes the file when deallocated
|
|
66
81
|
audioFile_ = nil;
|
|
82
|
+
|
|
83
|
+
double fileDuration = CMTimeGetSeconds([[AVURLAsset URLAssetWithURL:fileURL_ options:nil] duration]);
|
|
84
|
+
double fileSizeBytesMb =
|
|
85
|
+
static_cast<double>([[[NSFileManager defaultManager] attributesOfItemAtPath:fileURL_.path
|
|
86
|
+
error:&error] fileSize]) /
|
|
87
|
+
BYTES_TO_MB;
|
|
88
|
+
|
|
89
|
+
NSLog(
|
|
90
|
+
@"ℹ️ Closed audio file at path: %s, duration: %.2f sec, size: %.2f MB",
|
|
91
|
+
filePath.c_str(),
|
|
92
|
+
fileDuration,
|
|
93
|
+
fileSizeBytesMb);
|
|
94
|
+
|
|
95
|
+
if (error != nil) {
|
|
96
|
+
NSLog(@"⚠️ closeFile: error while retrieving file size");
|
|
97
|
+
fileSizeBytesMb = 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
67
100
|
fileURL_ = nil;
|
|
68
|
-
|
|
101
|
+
|
|
102
|
+
return {fileSizeBytesMb, fileDuration};
|
|
69
103
|
}
|
|
70
104
|
}
|
|
71
105
|
|
|
@@ -76,66 +110,63 @@ bool IOSAudioFileWriter::writeAudioData(const AudioBufferList *audioBufferList,
|
|
|
76
110
|
return false;
|
|
77
111
|
}
|
|
78
112
|
|
|
79
|
-
// TODO: not sure if this is necessary
|
|
80
113
|
@autoreleasepool {
|
|
81
114
|
NSError *error = nil;
|
|
82
|
-
AVAudioFormat *
|
|
115
|
+
AVAudioFormat *fileFormat = [audioFile_ processingFormat];
|
|
83
116
|
|
|
84
|
-
if (
|
|
85
|
-
bufferFormat_.
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
if (bufferFormat_.sampleRate == fileFormat.sampleRate && bufferFormat_.channelCount == fileFormat.channelCount &&
|
|
118
|
+
bufferFormat_.isInterleaved == fileFormat.isInterleaved) {
|
|
119
|
+
AVAudioPCMBuffer *processingBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:fileFormat
|
|
120
|
+
bufferListNoCopy:audioBufferList
|
|
121
|
+
deallocator:NULL];
|
|
122
|
+
processingBuffer.frameLength = (AVAudioFrameCount)numFrames;
|
|
88
123
|
|
|
89
|
-
|
|
90
|
-
frameCapacity:(AVAudioFrameCount)numFrames];
|
|
124
|
+
[audioFile_ writeFromBuffer:processingBuffer error:&error];
|
|
91
125
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
audioBufferList->mBuffers[i].mData,
|
|
96
|
-
audioBufferList->mBuffers[i].mDataByteSize);
|
|
126
|
+
if (error != nil) {
|
|
127
|
+
NSLog(@"Error writing audio data to file: %@", [error debugDescription]);
|
|
128
|
+
return false;
|
|
97
129
|
}
|
|
98
130
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
131
|
+
framesWritten_.fetch_add(numFrames);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
size_t outputFrameCount = ceil(numFrames * fileFormat.sampleRate / bufferFormat_.sampleRate);
|
|
103
136
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return inputBuffer;
|
|
111
|
-
};
|
|
137
|
+
for (size_t i = 0; i < bufferFormat_.channelCount; ++i) {
|
|
138
|
+
memcpy(
|
|
139
|
+
converterInputBuffer_.mutableAudioBufferList->mBuffers[i].mData,
|
|
140
|
+
audioBufferList->mBuffers[i].mData,
|
|
141
|
+
audioBufferList->mBuffers[i].mDataByteSize);
|
|
142
|
+
}
|
|
112
143
|
|
|
113
|
-
|
|
144
|
+
converterInputBuffer_.frameLength = numFrames;
|
|
114
145
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
146
|
+
AVAudioConverterInputBlock inputBlock =
|
|
147
|
+
^AVAudioBuffer *_Nullable(AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus *outStatus)
|
|
148
|
+
{
|
|
149
|
+
// this line is probably an delusion, but for my sanity lets keep it
|
|
150
|
+
inNumberOfPackets = numFrames;
|
|
151
|
+
*outStatus = AVAudioConverterInputStatus_HaveData;
|
|
152
|
+
return converterInputBuffer_;
|
|
153
|
+
};
|
|
119
154
|
|
|
120
|
-
|
|
155
|
+
[converter_ convertToBuffer:converterOutputBuffer_ error:&error withInputFromBlock:inputBlock];
|
|
121
156
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
} else {
|
|
127
|
-
AVAudioPCMBuffer *processingBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:filePCMFormat
|
|
128
|
-
bufferListNoCopy:audioBufferList
|
|
129
|
-
deallocator:NULL];
|
|
157
|
+
if (error != nil) {
|
|
158
|
+
NSLog(@"Error during audio conversion: %@", [error debugDescription]);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
130
161
|
|
|
131
|
-
|
|
162
|
+
[audioFile_ writeFromBuffer:converterOutputBuffer_ error:&error];
|
|
132
163
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
164
|
+
if (error != nil) {
|
|
165
|
+
NSLog(@"Error writing audio data to file: %@", [error debugDescription]);
|
|
166
|
+
return false;
|
|
137
167
|
}
|
|
138
168
|
|
|
169
|
+
framesWritten_.fetch_add(numFrames);
|
|
139
170
|
return true;
|
|
140
171
|
}
|
|
141
172
|
}
|
|
@@ -184,4 +215,9 @@ NSURL *IOSAudioFileWriter::getFileURL()
|
|
|
184
215
|
return [dirURL URLByAppendingPathComponent:fileName];
|
|
185
216
|
}
|
|
186
217
|
|
|
218
|
+
double IOSAudioFileWriter::getCurrentDuration() const
|
|
219
|
+
{
|
|
220
|
+
return static_cast<double>(framesWritten_.load()) / bufferFormat_.sampleRate;
|
|
221
|
+
}
|
|
222
|
+
|
|
187
223
|
} // namespace audioapi
|
|
@@ -16,6 +16,7 @@ namespace audioapi {
|
|
|
16
16
|
class AudioBus;
|
|
17
17
|
class CircularAudioArray;
|
|
18
18
|
class IOSAudioFileWriter;
|
|
19
|
+
class IOSRecorderCallback;
|
|
19
20
|
class AudioEventHandlerRegistry;
|
|
20
21
|
|
|
21
22
|
class IOSAudioRecorder : public AudioRecorder {
|
|
@@ -23,8 +24,8 @@ class IOSAudioRecorder : public AudioRecorder {
|
|
|
23
24
|
IOSAudioRecorder(const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
|
|
24
25
|
~IOSAudioRecorder() override;
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
std::string stop() override;
|
|
27
|
+
std::string start() override;
|
|
28
|
+
std::tuple<std::string, double, double> stop() override;
|
|
28
29
|
|
|
29
30
|
void enableFileOutput(float sampleRate, size_t channelCount, size_t bitRate, size_t iosFlags, size_t androidFlags)
|
|
30
31
|
override;
|
|
@@ -33,8 +34,16 @@ class IOSAudioRecorder : public AudioRecorder {
|
|
|
33
34
|
void pause() override;
|
|
34
35
|
void resume() override;
|
|
35
36
|
|
|
37
|
+
void setOnAudioReadyCallback(float sampleRate, size_t bufferLength, size_t channelCount, uint64_t callbackId)
|
|
38
|
+
override;
|
|
39
|
+
void clearOnAudioReadyCallback() override;
|
|
40
|
+
|
|
41
|
+
double getCurrentDuration() const override;
|
|
42
|
+
|
|
36
43
|
private:
|
|
37
44
|
std::shared_ptr<IOSAudioFileWriter> fileWriter_;
|
|
45
|
+
std::shared_ptr<IOSRecorderCallback> callback_;
|
|
46
|
+
std::string filePath_{""};
|
|
38
47
|
NativeAudioRecorder *nativeRecorder_;
|
|
39
48
|
};
|
|
40
49
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include <audioapi/events/AudioEventHandlerRegistry.h>
|
|
8
8
|
#include <audioapi/ios/core/IOSAudioFileWriter.h>
|
|
9
9
|
#include <audioapi/ios/core/IOSAudioRecorder.h>
|
|
10
|
+
#include <audioapi/ios/core/IOSRecorderCallback.h>
|
|
10
11
|
#include <audioapi/utils/AudioArray.h>
|
|
11
12
|
#include <audioapi/utils/AudioBus.h>
|
|
12
13
|
#include <audioapi/utils/CircularAudioArray.h>
|
|
@@ -14,22 +15,6 @@
|
|
|
14
15
|
|
|
15
16
|
namespace audioapi {
|
|
16
17
|
|
|
17
|
-
// AudioReceiverBlock audioReceiverBlock = ^(const AudioBufferList *inputBuffer, int numFrames) {
|
|
18
|
-
// if (isRunning_.load()) {
|
|
19
|
-
// auto *inputChannel = static_cast<float *>(inputBuffer->mBuffers[0].mData);
|
|
20
|
-
// writeToBuffers(inputChannel, numFrames);
|
|
21
|
-
// }
|
|
22
|
-
|
|
23
|
-
// while (circularBuffer_->getNumberOfAvailableFrames() >= bufferLength_) {
|
|
24
|
-
// auto bus = std::make_shared<AudioBus>(bufferLength_, 1, sampleRate_);
|
|
25
|
-
// auto *outputChannel = bus->getChannel(0)->getData();
|
|
26
|
-
|
|
27
|
-
// circularBuffer_->pop_front(outputChannel, bufferLength_);
|
|
28
|
-
|
|
29
|
-
// invokeOnAudioReadyCallback(bus, bufferLength_);
|
|
30
|
-
// }
|
|
31
|
-
// };
|
|
32
|
-
|
|
33
18
|
IOSAudioRecorder::IOSAudioRecorder(const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry)
|
|
34
19
|
: AudioRecorder(audioEventHandlerRegistry), fileWriter_(nullptr)
|
|
35
20
|
{
|
|
@@ -37,6 +22,10 @@ IOSAudioRecorder::IOSAudioRecorder(const std::shared_ptr<AudioEventHandlerRegist
|
|
|
37
22
|
if (usesFileOutput()) {
|
|
38
23
|
fileWriter_->writeAudioData(inputBuffer, numFrames);
|
|
39
24
|
}
|
|
25
|
+
|
|
26
|
+
if (usesCallback()) {
|
|
27
|
+
callback_->receiveAudioData(inputBuffer, numFrames);
|
|
28
|
+
}
|
|
40
29
|
};
|
|
41
30
|
|
|
42
31
|
nativeRecorder_ = [[NativeAudioRecorder alloc] initWithReceiverBlock:receiverBlock];
|
|
@@ -47,19 +36,20 @@ IOSAudioRecorder::~IOSAudioRecorder()
|
|
|
47
36
|
stop();
|
|
48
37
|
[nativeRecorder_ cleanup];
|
|
49
38
|
}
|
|
50
|
-
|
|
51
|
-
void IOSAudioRecorder::start()
|
|
39
|
+
std::string IOSAudioRecorder::start()
|
|
52
40
|
{
|
|
41
|
+
size_t maxInputBufferLength = [nativeRecorder_ getBufferSize];
|
|
42
|
+
|
|
53
43
|
if (isRecording()) {
|
|
54
|
-
return;
|
|
44
|
+
return filePath_;
|
|
55
45
|
}
|
|
56
46
|
|
|
57
47
|
if (usesFileOutput()) {
|
|
58
|
-
fileWriter_->openFile([nativeRecorder_ getInputFormat]);
|
|
48
|
+
filePath_ = fileWriter_->openFile([nativeRecorder_ getInputFormat], maxInputBufferLength);
|
|
59
49
|
}
|
|
60
50
|
|
|
61
51
|
if (usesCallback()) {
|
|
62
|
-
|
|
52
|
+
callback_->prepare([nativeRecorder_ getInputFormat], maxInputBufferLength);
|
|
63
53
|
}
|
|
64
54
|
|
|
65
55
|
if (isConnected()) {
|
|
@@ -67,25 +57,36 @@ void IOSAudioRecorder::start()
|
|
|
67
57
|
}
|
|
68
58
|
|
|
69
59
|
[nativeRecorder_ start];
|
|
70
|
-
|
|
60
|
+
state_.store(RecorderState::Recording);
|
|
61
|
+
|
|
62
|
+
return filePath_;
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
std::string IOSAudioRecorder::stop()
|
|
65
|
+
std::tuple<std::string, double, double> IOSAudioRecorder::stop()
|
|
74
66
|
{
|
|
67
|
+
std::string filePath = filePath_;
|
|
68
|
+
double outputFileSize = 0;
|
|
69
|
+
double outputDuration = 0;
|
|
70
|
+
|
|
75
71
|
if (!isRecording()) {
|
|
76
|
-
return
|
|
72
|
+
return {filePath, 0, 0};
|
|
77
73
|
}
|
|
78
74
|
|
|
79
75
|
[nativeRecorder_ stop];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// TODO: send remaining data?
|
|
76
|
+
state_.store(RecorderState::Idle);
|
|
83
77
|
|
|
84
78
|
if (usesFileOutput()) {
|
|
85
|
-
|
|
79
|
+
auto [size, duration] = fileWriter_->closeFile();
|
|
80
|
+
outputFileSize = size;
|
|
81
|
+
outputDuration = duration;
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
|
|
84
|
+
if (usesCallback()) {
|
|
85
|
+
callback_->cleanup();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
filePath_ = "";
|
|
89
|
+
return {filePath, outputFileSize, outputDuration};
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
void IOSAudioRecorder::enableFileOutput(
|
|
@@ -105,8 +106,52 @@ void IOSAudioRecorder::disableFileOutput()
|
|
|
105
106
|
fileWriter_ = nullptr;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
void IOSAudioRecorder::pause()
|
|
109
|
+
void IOSAudioRecorder::pause()
|
|
110
|
+
{
|
|
111
|
+
if (!isRecording()) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
[nativeRecorder_ stop];
|
|
116
|
+
state_.store(RecorderState::Paused);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void IOSAudioRecorder::resume()
|
|
120
|
+
{
|
|
121
|
+
if (!isPaused()) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
109
124
|
|
|
110
|
-
|
|
125
|
+
[nativeRecorder_ start];
|
|
126
|
+
state_.store(RecorderState::Recording);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void IOSAudioRecorder::setOnAudioReadyCallback(
|
|
130
|
+
float sampleRate,
|
|
131
|
+
size_t bufferLength,
|
|
132
|
+
size_t channelCount,
|
|
133
|
+
uint64_t callbackId)
|
|
134
|
+
{
|
|
135
|
+
callback_ = std::make_shared<IOSRecorderCallback>(
|
|
136
|
+
audioEventHandlerRegistry_, sampleRate, bufferLength, channelCount, callbackId);
|
|
137
|
+
callbackOutputEnabled_.store(true);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void IOSAudioRecorder::clearOnAudioReadyCallback()
|
|
141
|
+
{
|
|
142
|
+
callbackOutputEnabled_.store(false);
|
|
143
|
+
callback_ = nullptr;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
double IOSAudioRecorder::getCurrentDuration() const
|
|
147
|
+
{
|
|
148
|
+
double duration = 0.0;
|
|
149
|
+
|
|
150
|
+
if (usesFileOutput() && fileWriter_) {
|
|
151
|
+
duration = fileWriter_->getCurrentDuration();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return duration;
|
|
155
|
+
}
|
|
111
156
|
|
|
112
157
|
} // namespace audioapi
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#ifndef __OBJC__ // when compiled as C++
|
|
4
|
+
typedef struct objc_object AVAudioFormat;
|
|
5
|
+
typedef struct objc_object AudioBufferList;
|
|
6
|
+
typedef struct objc_object AVAudioConverter;
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
#include <memory>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
namespace audioapi {
|
|
13
|
+
|
|
14
|
+
class AudioBus;
|
|
15
|
+
class CircularAudioArray;
|
|
16
|
+
class AudioEventHandlerRegistry;
|
|
17
|
+
|
|
18
|
+
class IOSRecorderCallback {
|
|
19
|
+
public:
|
|
20
|
+
IOSRecorderCallback(
|
|
21
|
+
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
|
|
22
|
+
float sampleRate,
|
|
23
|
+
size_t bufferLength,
|
|
24
|
+
size_t channelCount,
|
|
25
|
+
uint64_t callbackId);
|
|
26
|
+
~IOSRecorderCallback();
|
|
27
|
+
|
|
28
|
+
void prepare(AVAudioFormat *bufferFormat, size_t maxInputBufferLength);
|
|
29
|
+
void cleanup();
|
|
30
|
+
|
|
31
|
+
void receiveAudioData(const AudioBufferList *audioBufferList, int numFrames);
|
|
32
|
+
void emitAudioData();
|
|
33
|
+
|
|
34
|
+
void invokeCallback(const std::shared_ptr<AudioBus> &bus, int numFrames);
|
|
35
|
+
void sendRemainingData();
|
|
36
|
+
|
|
37
|
+
private:
|
|
38
|
+
float sampleRate_;
|
|
39
|
+
size_t bufferLength_;
|
|
40
|
+
size_t channelCount_;
|
|
41
|
+
uint64_t callbackId_;
|
|
42
|
+
size_t ringBufferSize_;
|
|
43
|
+
size_t converterInputBufferSize_;
|
|
44
|
+
size_t converterOutputBufferSize_;
|
|
45
|
+
|
|
46
|
+
std::shared_ptr<AudioEventHandlerRegistry> audioEventHandlerRegistry_;
|
|
47
|
+
std::vector<std::shared_ptr<CircularAudioArray>> circularBus_;
|
|
48
|
+
|
|
49
|
+
AVAudioFormat *bufferFormat_;
|
|
50
|
+
AVAudioFormat *callbackFormat_;
|
|
51
|
+
AVAudioConverter *converter_;
|
|
52
|
+
|
|
53
|
+
AVAudioPCMBuffer *converterInputBuffer_;
|
|
54
|
+
AVAudioPCMBuffer *converterOutputBuffer_;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
} // namespace audioapi
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#import <AVFoundation/AVFoundation.h>
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
|
|
4
|
+
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
|
|
5
|
+
#include <audioapi/core/utils/Constants.h>
|
|
6
|
+
#include <audioapi/dsp/VectorMath.h>
|
|
7
|
+
#include <audioapi/events/AudioEventHandlerRegistry.h>
|
|
8
|
+
#include <audioapi/ios/core/IOSRecorderCallback.h>
|
|
9
|
+
#include <audioapi/utils/AudioArray.h>
|
|
10
|
+
#include <audioapi/utils/AudioBus.h>
|
|
11
|
+
#include <audioapi/utils/CircularAudioArray.h>
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
|
|
14
|
+
namespace audioapi {
|
|
15
|
+
|
|
16
|
+
IOSRecorderCallback::IOSRecorderCallback(
|
|
17
|
+
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
|
|
18
|
+
float sampleRate,
|
|
19
|
+
size_t bufferLength,
|
|
20
|
+
size_t channelCount,
|
|
21
|
+
uint64_t callbackId)
|
|
22
|
+
: audioEventHandlerRegistry_(audioEventHandlerRegistry),
|
|
23
|
+
sampleRate_(sampleRate),
|
|
24
|
+
bufferLength_(bufferLength),
|
|
25
|
+
channelCount_(channelCount),
|
|
26
|
+
callbackId_(callbackId)
|
|
27
|
+
{
|
|
28
|
+
ringBufferSize_ = std::max((int)bufferLength_ * 2, 8192);
|
|
29
|
+
circularBus_.resize(channelCount_);
|
|
30
|
+
|
|
31
|
+
for (size_t i = 0; i < channelCount_; ++i) {
|
|
32
|
+
auto busAudioArray = std::make_shared<CircularAudioArray>(ringBufferSize_);
|
|
33
|
+
circularBus_[i] = busAudioArray;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
IOSRecorderCallback::~IOSRecorderCallback()
|
|
38
|
+
{
|
|
39
|
+
@autoreleasepool {
|
|
40
|
+
converter_ = nil;
|
|
41
|
+
bufferFormat_ = nil;
|
|
42
|
+
callbackFormat_ = nil;
|
|
43
|
+
converterInputBuffer_ = nil;
|
|
44
|
+
converterOutputBuffer_ = nil;
|
|
45
|
+
|
|
46
|
+
for (size_t i = 0; i < channelCount_; ++i) {
|
|
47
|
+
circularBus_[i].reset();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void IOSRecorderCallback::prepare(AVAudioFormat *bufferFormat, size_t maxInputBufferLength)
|
|
53
|
+
{
|
|
54
|
+
@autoreleasepool {
|
|
55
|
+
bufferFormat_ = bufferFormat;
|
|
56
|
+
converterInputBufferSize_ = maxInputBufferLength;
|
|
57
|
+
|
|
58
|
+
converterOutputBufferSize_ =
|
|
59
|
+
std::max((double)maxInputBufferLength, sampleRate_ / bufferFormat.sampleRate * maxInputBufferLength);
|
|
60
|
+
|
|
61
|
+
callbackFormat_ = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
|
|
62
|
+
sampleRate:sampleRate_
|
|
63
|
+
channels:channelCount_
|
|
64
|
+
interleaved:NO];
|
|
65
|
+
|
|
66
|
+
converter_ = [[AVAudioConverter alloc] initFromFormat:bufferFormat toFormat:callbackFormat_];
|
|
67
|
+
converter_.sampleRateConverterAlgorithm = AVSampleRateConverterAlgorithm_Normal;
|
|
68
|
+
converter_.sampleRateConverterQuality = AVAudioQualityMax;
|
|
69
|
+
converter_.primeMethod = AVAudioConverterPrimeMethod_None;
|
|
70
|
+
|
|
71
|
+
converterInputBuffer_ = [[AVAudioPCMBuffer alloc] initWithPCMFormat:bufferFormat_
|
|
72
|
+
frameCapacity:(AVAudioFrameCount)converterInputBufferSize_];
|
|
73
|
+
converterOutputBuffer_ = [[AVAudioPCMBuffer alloc] initWithPCMFormat:callbackFormat_
|
|
74
|
+
frameCapacity:(AVAudioFrameCount)converterOutputBufferSize_];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void IOSRecorderCallback::cleanup()
|
|
79
|
+
{
|
|
80
|
+
@autoreleasepool {
|
|
81
|
+
sendRemainingData();
|
|
82
|
+
|
|
83
|
+
converter_ = nil;
|
|
84
|
+
bufferFormat_ = nil;
|
|
85
|
+
callbackFormat_ = nil;
|
|
86
|
+
converterInputBuffer_ = nil;
|
|
87
|
+
converterOutputBuffer_ = nil;
|
|
88
|
+
|
|
89
|
+
for (size_t i = 0; i < channelCount_; ++i) {
|
|
90
|
+
circularBus_[i].reset();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void IOSRecorderCallback::receiveAudioData(const AudioBufferList *inputBuffer, int numFrames)
|
|
96
|
+
{
|
|
97
|
+
@autoreleasepool {
|
|
98
|
+
NSError *error = nil;
|
|
99
|
+
|
|
100
|
+
if (bufferFormat_.sampleRate == sampleRate_ && bufferFormat_.channelCount == channelCount_ &&
|
|
101
|
+
!bufferFormat_.isInterleaved) {
|
|
102
|
+
// Directly write to circular buffer
|
|
103
|
+
for (size_t i = 0; i < channelCount_; ++i) {
|
|
104
|
+
auto *inputChannel = static_cast<float *>(inputBuffer->mBuffers[i].mData);
|
|
105
|
+
circularBus_[i]->push_back(inputChannel, numFrames);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
emitAudioData();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
size_t outputFrameCount = ceil(numFrames * (sampleRate_ / bufferFormat_.sampleRate));
|
|
113
|
+
|
|
114
|
+
for (size_t i = 0; i < bufferFormat_.channelCount; ++i) {
|
|
115
|
+
memcpy(
|
|
116
|
+
converterInputBuffer_.mutableAudioBufferList->mBuffers[i].mData,
|
|
117
|
+
inputBuffer->mBuffers[i].mData,
|
|
118
|
+
inputBuffer->mBuffers[i].mDataByteSize);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
converterInputBuffer_.frameLength = numFrames;
|
|
122
|
+
|
|
123
|
+
AVAudioConverterInputBlock inputBlock =
|
|
124
|
+
^AVAudioBuffer *_Nullable(AVAudioPacketCount inNumberOfPackets, AVAudioConverterInputStatus *outStatus)
|
|
125
|
+
{
|
|
126
|
+
// this line is probably an delusion, but for my sanity lets keep it
|
|
127
|
+
inNumberOfPackets = numFrames;
|
|
128
|
+
*outStatus = AVAudioConverterInputStatus_HaveData;
|
|
129
|
+
return converterInputBuffer_;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
[converter_ convertToBuffer:converterOutputBuffer_ error:&error withInputFromBlock:inputBlock];
|
|
133
|
+
|
|
134
|
+
if (error != nil) {
|
|
135
|
+
NSLog(@"Error during audio conversion: %@", [error debugDescription]);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (size_t i = 0; i < channelCount_; ++i) {
|
|
140
|
+
auto *inputChannel = static_cast<float *>(converterOutputBuffer_.audioBufferList->mBuffers[i].mData);
|
|
141
|
+
circularBus_[i]->push_back(inputChannel, outputFrameCount);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
emitAudioData();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
void IOSRecorderCallback::emitAudioData()
|
|
149
|
+
{
|
|
150
|
+
while (circularBus_[0]->getNumberOfAvailableFrames() >= bufferLength_) {
|
|
151
|
+
auto bus = std::make_shared<AudioBus>(bufferLength_, channelCount_, sampleRate_);
|
|
152
|
+
|
|
153
|
+
for (size_t i = 0; i < channelCount_; ++i) {
|
|
154
|
+
auto *outputChannel = bus->getChannel(i)->getData();
|
|
155
|
+
circularBus_[i]->pop_front(outputChannel, bufferLength_);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
invokeCallback(bus, bufferLength_);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
void IOSRecorderCallback::invokeCallback(const std::shared_ptr<AudioBus> &bus, int numFrames)
|
|
163
|
+
{
|
|
164
|
+
auto audioBuffer = std::make_shared<AudioBuffer>(bus);
|
|
165
|
+
auto audioBufferHostObject = std::make_shared<AudioBufferHostObject>(audioBuffer);
|
|
166
|
+
|
|
167
|
+
std::unordered_map<std::string, EventValue> eventPayload = {};
|
|
168
|
+
eventPayload.insert({"buffer", audioBufferHostObject});
|
|
169
|
+
eventPayload.insert({"numFrames", numFrames});
|
|
170
|
+
|
|
171
|
+
if (audioEventHandlerRegistry_) {
|
|
172
|
+
audioEventHandlerRegistry_->invokeHandlerWithEventBody("audioReady", callbackId_, eventPayload);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
void IOSRecorderCallback::sendRemainingData()
|
|
177
|
+
{
|
|
178
|
+
auto numberOfFrames = circularBus_[0]->getNumberOfAvailableFrames();
|
|
179
|
+
auto bus = std::make_shared<AudioBus>(circularBus_[0]->getNumberOfAvailableFrames(), channelCount_, sampleRate_);
|
|
180
|
+
|
|
181
|
+
for (size_t i = 0; i < channelCount_; ++i) {
|
|
182
|
+
auto *outputChannel = bus->getChannel(i)->getData();
|
|
183
|
+
circularBus_[i]->pop_front(outputChannel, numberOfFrames);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
invokeCallback(bus, numberOfFrames);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
} // namespace audioapi
|
|
@@ -7,9 +7,6 @@ typedef void (^AudioReceiverBlock)(const AudioBufferList *inputBuffer, int numFr
|
|
|
7
7
|
|
|
8
8
|
@interface NativeAudioRecorder : NSObject
|
|
9
9
|
|
|
10
|
-
@property (nonatomic, assign) int bufferLength;
|
|
11
|
-
@property (nonatomic, assign) float sampleRate;
|
|
12
|
-
|
|
13
10
|
@property (nonatomic, strong) AVAudioSinkNode *sinkNode;
|
|
14
11
|
@property (nonatomic, copy) AVAudioSinkNodeReceiverBlock receiverSinkBlock;
|
|
15
12
|
@property (nonatomic, copy) AudioReceiverBlock receiverBlock;
|
|
@@ -18,6 +15,8 @@ typedef void (^AudioReceiverBlock)(const AudioBufferList *inputBuffer, int numFr
|
|
|
18
15
|
|
|
19
16
|
- (AVAudioFormat *)getInputFormat;
|
|
20
17
|
|
|
18
|
+
- (int)getBufferSize;
|
|
19
|
+
|
|
21
20
|
- (void)start;
|
|
22
21
|
|
|
23
22
|
- (void)stop;
|