44#include < audioapi/core/utils/Constants.h>
55#include < audioapi/utils/AudioArray.h>
66#include < audioapi/utils/AudioBuffer.h>
7+
8+ #include < algorithm>
9+ #include < cmath>
10+ #include < cstring>
711#include < memory>
12+ #include < vector>
813
914namespace audioapi {
1015
@@ -15,7 +20,7 @@ RecorderAdapterNode::RecorderAdapterNode(const std::shared_ptr<BaseAudioContext>
1520 isInitialized_ = false ;
1621}
1722
18- void RecorderAdapterNode::init (size_t bufferSize, int channelCount) {
23+ void RecorderAdapterNode::init (size_t bufferSize, int channelCount, float sampleRate ) {
1924 std::shared_ptr<BaseAudioContext> context = context_.lock ();
2025 if (isInitialized_ || context == nullptr ) {
2126 return ;
@@ -29,26 +34,37 @@ void RecorderAdapterNode::init(size_t bufferSize, int channelCount) {
2934 buff_[i] = std::make_shared<CircularOverflowableAudioArray>(bufferSize);
3035 }
3136
32- // This assumes that the sample rate is the same in audio context and recorder.
33- // (recorder is not enforcing any sample rate on the system*). This means that only
34- // channel mixing might be required. To do so, we create an output buffer with
35- // the desired channel count and will take advantage of the AudioBuffer sum method.
36- //
37- // * any allocations required by the recorder (including this method) are during recording start
38- // or after, which means that audio context has already setup the system in 99% of sane cases.
39- // But if we would like to support cases when context is created on the fly during recording,
40- // we would need to add sample rate conversion as well or other weird bullshit like resampling
41- // context output and not enforcing anything on the system output/input configuration.
42- // A lot of words for a couple of lines of implementation :shrug:
37+ float contextSampleRate = context->getSampleRate ();
38+ needsResampling_ = static_cast <int >(sampleRate) != static_cast <int >(contextSampleRate);
39+
4340 adapterOutputBuffer_ =
44- std::make_shared<AudioBuffer>(RENDER_QUANTUM_SIZE, channelCount_, context->getSampleRate ());
41+ std::make_shared<AudioBuffer>(RENDER_QUANTUM_SIZE, channelCount_, contextSampleRate);
42+
43+ if (needsResampling_) {
44+ inputChunkSize_ =
45+ static_cast <size_t >(std::ceil (RENDER_QUANTUM_SIZE * sampleRate / contextSampleRate)) + 4 ;
46+
47+ resampler_ = std::make_unique<r8b::MultiChannelResampler>(
48+ sampleRate, contextSampleRate, channelCount_, static_cast <int >(inputChunkSize_));
49+
50+ const int maxOutLen = resampler_->getMaxOutLen ();
51+
52+ resamplerInputBuffer_ = AudioBuffer (inputChunkSize_, channelCount_, sampleRate);
53+ resamplerOutputBuffer_ =
54+ AudioBuffer (static_cast <size_t >(maxOutLen), channelCount_, contextSampleRate);
55+ overflowBuffer_ = AudioBuffer (2 * maxOutLen, channelCount_, contextSampleRate);
56+ overflowSize_ = 0 ;
57+ }
58+
4559 isInitialized_ = true ;
4660}
4761
4862void RecorderAdapterNode::cleanup () {
4963 isInitialized_ = false ;
64+ needsResampling_ = false ;
5065 buff_.clear ();
51- adapterOutputBuffer_.reset ();
66+ resampler_.reset ();
67+ overflowSize_ = 0 ;
5268}
5369
5470std::shared_ptr<AudioBuffer> RecorderAdapterNode::processNode (
@@ -59,17 +75,67 @@ std::shared_ptr<AudioBuffer> RecorderAdapterNode::processNode(
5975 return processingBuffer;
6076 }
6177
62- readFrames (framesToProcess);
78+ if (needsResampling_) {
79+ processResampled (framesToProcess);
80+ } else {
81+ readFrames (*adapterOutputBuffer_, framesToProcess);
82+ }
6383
6484 processingBuffer->sum (*adapterOutputBuffer_, ChannelInterpretation::SPEAKERS);
6585 return processingBuffer;
6686}
6787
68- void RecorderAdapterNode::readFrames ( const size_t framesToRead ) {
88+ void RecorderAdapterNode::processResampled ( int framesToProcess ) {
6989 adapterOutputBuffer_->zero ();
7090
91+ size_t outputWritten = 0 ;
92+ const size_t needed = static_cast <size_t >(framesToProcess);
93+
94+ // Drain leftover resampled samples from the previous call
95+ if (overflowSize_ > 0 ) {
96+ const size_t toCopy = std::min (overflowSize_, needed);
97+
98+ adapterOutputBuffer_->copy (overflowBuffer_, 0 , 0 , toCopy);
99+ outputWritten = toCopy;
100+
101+ if (toCopy < overflowSize_) {
102+ const size_t remaining = overflowSize_ - toCopy;
103+ for (int ch = 0 ; ch < channelCount_; ++ch) {
104+ overflowBuffer_[ch].copyWithin (toCopy, 0 , remaining);
105+ }
106+ }
107+ overflowSize_ -= toCopy;
108+ }
109+
110+ // Feed the resampler until we have enough output frames
111+ while (outputWritten < needed) {
112+ readFrames (resamplerInputBuffer_, inputChunkSize_);
113+
114+ const int outLen = resampler_->process (
115+ resamplerInputBuffer_, static_cast <int >(inputChunkSize_), resamplerOutputBuffer_);
116+
117+ const size_t remaining = needed - outputWritten;
118+ const size_t toCopy = std::min (static_cast <size_t >(outLen), remaining);
119+
120+ // Write resampled frames into the output buffer
121+ adapterOutputBuffer_->copy (resamplerOutputBuffer_, 0 , outputWritten, toCopy);
122+ outputWritten += toCopy;
123+
124+ // Stash excess for the next processNode call
125+ const int excess = outLen - static_cast <int >(toCopy);
126+ if (excess > 0 ) {
127+ overflowBuffer_.copy (
128+ resamplerOutputBuffer_, toCopy, overflowSize_, static_cast <size_t >(excess));
129+ overflowSize_ += static_cast <size_t >(excess);
130+ }
131+ }
132+ }
133+
134+ void RecorderAdapterNode::readFrames (AudioBuffer &target, const size_t framesToRead) {
135+ target.zero ();
136+
71137 for (size_t channel = 0 ; channel < channelCount_; ++channel) {
72- buff_[channel]->read (*adapterOutputBuffer_-> getChannel (channel), framesToRead);
138+ buff_[channel]->read (*target. getChannel (channel), framesToRead);
73139 }
74140}
75141
0 commit comments