1 | /* |
2 | Copyright 2018 Google Inc. All Rights Reserved. |
3 | |
4 | Licensed under the Apache License, Version 2.0 (the "License"); |
5 | you may not use this file except in compliance with the License. |
6 | You may obtain a copy of the License at |
7 | |
8 | http://www.apache.org/licenses/LICENSE-2.0 |
9 | |
10 | Unless required by applicable law or agreed to in writing, software |
11 | distributed under the License is distributed on an "AS-IS" BASIS, |
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | See the License for the specific language governing permissions and |
14 | limitations under the License. |
15 | */ |
16 | |
17 | #include "graph/binaural_surround_renderer_impl.h" |
18 | |
19 | #include <algorithm> |
20 | #include <functional> |
21 | |
22 | #include "base/misc_math.h" |
23 | #include "base/simd_utils.h" |
24 | #include "base/spherical_angle.h" |
25 | #include "graph/resonance_audio_api_impl.h" |
26 | #include "platforms/common/room_effects_utils.h" |
27 | #include "platforms/common/room_properties.h" |
28 | #include "utils/planar_interleaved_conversion.h" |
29 | |
30 | namespace vraudio { |
31 | |
32 | namespace { |
33 | |
34 | // Maximum number of audio buffers in buffer queue. |
35 | const size_t kNumMaxBuffers = 64; |
36 | |
37 | // Output gain, to avoid clipping of individual virtual speaker channels. |
38 | const float kGain = 0.5f; |
39 | |
40 | } // namespace |
41 | |
42 | BinauralSurroundRendererImpl::BinauralSurroundRendererImpl( |
43 | size_t frames_per_buffer, int sample_rate_hz) |
44 | : |
45 | resonance_audio_api_(nullptr), |
46 | frames_per_buffer_(frames_per_buffer), |
47 | sample_rate_hz_(sample_rate_hz), |
48 | surround_format_(kInvalid), |
49 | num_input_channels_(0), |
50 | output_buffer_(kNumStereoChannels, frames_per_buffer), |
51 | total_frames_buffered_(0), |
52 | num_zero_padded_frames_(0), |
53 | output_gain_(1.0f) { |
54 | } |
55 | |
56 | bool BinauralSurroundRendererImpl::Init(SurroundFormat surround_format) { |
57 | surround_format_ = surround_format; |
58 | num_input_channels_ = |
59 | GetExpectedNumChannelsFromSurroundFormat(surround_format); |
60 | |
61 | temp_planar_buffer_ptrs_.resize(new_size: num_input_channels_); |
62 | |
63 | input_audio_buffer_queue_.reset(p: new ThreadsafeFifo<AudioBuffer>( |
64 | kNumMaxBuffers, AudioBuffer(num_input_channels_, frames_per_buffer_))); |
65 | |
66 | buffer_partitioner_.reset(p: new BufferPartitioner( |
67 | num_input_channels_, frames_per_buffer_, |
68 | std::bind(f: &BinauralSurroundRendererImpl::BufferPartitionerCallback, args: this, |
69 | args: std::placeholders::_1))); |
70 | |
71 | buffer_unpartitioner_.reset(p: new BufferUnpartitioner( |
72 | kNumStereoChannels, frames_per_buffer_, |
73 | std::bind(f: &BinauralSurroundRendererImpl::ProcessBuffer, args: this))); |
74 | |
75 | resonance_audio_api_.reset(p: CreateResonanceAudioApi( |
76 | num_channels: kNumStereoChannels, frames_per_buffer: frames_per_buffer_, sample_rate_hz: sample_rate_hz_)); |
77 | |
78 | if (surround_format == kSurroundMono || surround_format == kSurroundStereo || |
79 | surround_format == kSurroundFiveDotOne || |
80 | surround_format == kSurroundSevenDotOne) { |
81 | InitializeRoomReverb(); |
82 | } |
83 | // Initialize rendering mode. |
84 | switch (surround_format) { |
85 | case kSurroundMono: |
86 | InitializeBinauralMono(); |
87 | break; |
88 | case kSurroundStereo: |
89 | InitializeBinauralStereo(); |
90 | break; |
91 | case kSurroundFiveDotOne: |
92 | InitializeBinauralSurround5dot1(); |
93 | break; |
94 | case kSurroundSevenDotOne: |
95 | InitializeBinauralSurround7dot1(); |
96 | break; |
97 | case kFirstOrderAmbisonics: |
98 | case kSecondOrderAmbisonics: |
99 | case kThirdOrderAmbisonics: |
100 | InitializeAmbisonics(); |
101 | break; |
102 | case kFirstOrderAmbisonicsWithNonDiegeticStereo: |
103 | case kSecondOrderAmbisonicsWithNonDiegeticStereo: |
104 | case kThirdOrderAmbisonicsWithNonDiegeticStereo: |
105 | InitializeAmbisonicsWithNonDiegeticStereo(); |
106 | break; |
107 | default: |
108 | LOG(FATAL) << "Undefined rendering mode" ; |
109 | return false; |
110 | break; |
111 | } |
112 | return true; |
113 | } |
114 | |
115 | BinauralSurroundRendererImpl::BinauralSurroundRendererImpl() |
116 | : |
117 | resonance_audio_api_(nullptr), |
118 | frames_per_buffer_(0), |
119 | sample_rate_hz_(0), |
120 | total_frames_buffered_(0), |
121 | num_zero_padded_frames_(0) { |
122 | } |
123 | |
124 | AudioBuffer* BinauralSurroundRendererImpl::BufferPartitionerCallback( |
125 | AudioBuffer* processed_buffer) { |
126 | if (processed_buffer != nullptr) { |
127 | input_audio_buffer_queue_->ReleaseInputObject(object: processed_buffer); |
128 | } |
129 | DCHECK(!input_audio_buffer_queue_->Full()); |
130 | return input_audio_buffer_queue_->AcquireInputObject(); |
131 | } |
132 | |
133 | void BinauralSurroundRendererImpl::SetStereoSpeakerMode(bool enabled) { |
134 | resonance_audio_api_->SetStereoSpeakerMode(enabled); |
135 | } |
136 | |
137 | size_t BinauralSurroundRendererImpl::GetExpectedNumChannelsFromSurroundFormat( |
138 | SurroundFormat surround_format) { |
139 | switch (surround_format) { |
140 | case kSurroundMono: |
141 | return kNumMonoChannels; |
142 | case kSurroundStereo: |
143 | return kNumStereoChannels; |
144 | case kSurroundFiveDotOne: |
145 | return kNumSurroundFiveDotOneChannels; |
146 | case kSurroundSevenDotOne: |
147 | return kNumSurroundSevenDotOneChannels; |
148 | case kFirstOrderAmbisonics: |
149 | return kNumFirstOrderAmbisonicChannels; |
150 | case kSecondOrderAmbisonics: |
151 | return kNumSecondOrderAmbisonicChannels; |
152 | case kThirdOrderAmbisonics: |
153 | return kNumThirdOrderAmbisonicChannels; |
154 | case kFirstOrderAmbisonicsWithNonDiegeticStereo: |
155 | return kNumFirstOrderAmbisonicChannels + kNumStereoChannels; |
156 | case kSecondOrderAmbisonicsWithNonDiegeticStereo: |
157 | return kNumSecondOrderAmbisonicChannels + kNumStereoChannels; |
158 | case kThirdOrderAmbisonicsWithNonDiegeticStereo: |
159 | return kNumThirdOrderAmbisonicChannels + kNumStereoChannels; |
160 | default: |
161 | LOG(FATAL) << "Undefined surround format mode" ; |
162 | return false; |
163 | break; |
164 | } |
165 | return 0; |
166 | } |
167 | |
168 | void BinauralSurroundRendererImpl::InitializeBinauralMono() { |
169 | source_ids_.resize(new_size: kNumMonoChannels); |
170 | // Front (0 degrees): |
171 | source_ids_[0] = CreateSoundObject(azimuth_deg: 0.0f); |
172 | output_gain_ = kGain; |
173 | } |
174 | |
175 | void BinauralSurroundRendererImpl::InitializeBinauralStereo() { |
176 | |
177 | source_ids_.resize(new_size: kNumStereoChannels); |
178 | // Front left (30 degrees): |
179 | source_ids_[0] = CreateSoundObject(azimuth_deg: 30.0f); |
180 | // Front right (-30 degrees): |
181 | source_ids_[1] = CreateSoundObject(azimuth_deg: -30.0f); |
182 | output_gain_ = kGain; |
183 | } |
184 | |
185 | void BinauralSurroundRendererImpl::InitializeBinauralSurround5dot1() { |
186 | source_ids_.resize(new_size: kNumSurroundFiveDotOneChannels); |
187 | // Left (30 degrees): |
188 | source_ids_[0] = CreateSoundObject(azimuth_deg: 30.0f); |
189 | // Right (-30 degrees): |
190 | source_ids_[1] = CreateSoundObject(azimuth_deg: -30.0f); |
191 | // Center (0 degrees): |
192 | source_ids_[2] = CreateSoundObject(azimuth_deg: 0.0f); |
193 | // Low frequency effects at front center: |
194 | source_ids_[3] = CreateSoundObject(azimuth_deg: 0.0f); |
195 | // Left surround (110 degrees): |
196 | source_ids_[4] = CreateSoundObject(azimuth_deg: 110.0f); |
197 | // Right surround (-110 degrees): |
198 | source_ids_[5] = CreateSoundObject(azimuth_deg: -110.0f); |
199 | output_gain_ = kGain; |
200 | } |
201 | |
202 | void BinauralSurroundRendererImpl::InitializeBinauralSurround7dot1() { |
203 | source_ids_.resize(new_size: kNumSurroundSevenDotOneChannels); |
204 | // Left (30 degrees): |
205 | source_ids_[0] = CreateSoundObject(azimuth_deg: 30.0f); |
206 | // Right (-30 degrees): |
207 | source_ids_[1] = CreateSoundObject(azimuth_deg: -30.0f); |
208 | // Center (0 degrees): |
209 | source_ids_[2] = CreateSoundObject(azimuth_deg: 0.0f); |
210 | // Low frequency effects at front center: |
211 | source_ids_[3] = CreateSoundObject(azimuth_deg: 0.0f); |
212 | // Left surround 1 (90 degrees): |
213 | source_ids_[4] = CreateSoundObject(azimuth_deg: 90.0f); |
214 | // Right surround 1 (-90 degrees): |
215 | source_ids_[5] = CreateSoundObject(azimuth_deg: -90.0f); |
216 | // Left surround 2 (150 degrees): |
217 | source_ids_[6] = CreateSoundObject(azimuth_deg: 150.0f); |
218 | // Right surround 2 (-150 degrees): |
219 | source_ids_[7] = CreateSoundObject(azimuth_deg: -150.0f); |
220 | output_gain_ = kGain; |
221 | } |
222 | |
223 | void BinauralSurroundRendererImpl::InitializeAmbisonics() { |
224 | source_ids_.resize(new_size: 1); |
225 | source_ids_[0] = |
226 | resonance_audio_api_->CreateAmbisonicSource(num_channels: num_input_channels_); |
227 | } |
228 | |
229 | void BinauralSurroundRendererImpl::InitializeAmbisonicsWithNonDiegeticStereo() { |
230 | source_ids_.resize(new_size: 2); |
231 | CHECK_GT(num_input_channels_, kNumStereoChannels); |
232 | source_ids_[0] = resonance_audio_api_->CreateAmbisonicSource( |
233 | num_channels: num_input_channels_ - kNumStereoChannels); |
234 | source_ids_[1] = resonance_audio_api_->CreateStereoSource(num_channels: kNumStereoChannels); |
235 | } |
236 | |
237 | SourceId BinauralSurroundRendererImpl::CreateSoundObject(float azimuth_deg) { |
238 | static const float kZeroElevation = 0.0f; |
239 | auto speaker_position = |
240 | vraudio::SphericalAngle::FromDegrees(azimuth_degrees: azimuth_deg, elevation_degrees: kZeroElevation) |
241 | .GetWorldPositionOnUnitSphere(); |
242 | const SourceId source_id = resonance_audio_api_->CreateSoundObjectSource( |
243 | rendering_mode: RenderingMode::kBinauralHighQuality); |
244 | resonance_audio_api_->SetSourcePosition( |
245 | source_id, x: speaker_position[0], y: speaker_position[1], z: speaker_position[2]); |
246 | return source_id; |
247 | } |
248 | |
249 | void BinauralSurroundRendererImpl::InitializeRoomReverb() { |
250 | // The following settings has been applied based on AESTD1001.1.01-10. |
251 | RoomProperties room_properties; |
252 | room_properties.dimensions[0] = 9.54f; |
253 | room_properties.dimensions[1] = 6.0f; |
254 | room_properties.dimensions[2] = 15.12f; |
255 | room_properties.reverb_brightness = 0.0f; |
256 | room_properties.reflection_scalar = 1.0f; |
257 | // Reduce reverb gain to compensate for virtual speakers gain. |
258 | room_properties.reverb_gain = output_gain_; |
259 | for (size_t i = 0; i < kNumRoomSurfaces; ++i) { |
260 | room_properties.material_names[i] = MaterialName::kUniform; |
261 | } |
262 | resonance_audio_api_->SetReflectionProperties( |
263 | ComputeReflectionProperties(room_properties)); |
264 | resonance_audio_api_->SetReverbProperties( |
265 | ComputeReverbProperties(room_properties)); |
266 | resonance_audio_api_->EnableRoomEffects(enable: true); |
267 | } |
268 | |
269 | size_t BinauralSurroundRendererImpl::GetNumAvailableFramesInInputBuffer() |
270 | const { |
271 | DCHECK_NE(surround_format_, kInvalid); |
272 | if (num_zero_padded_frames_ > 0) { |
273 | // Zero padded output buffers must be consumed prior to |
274 | // |AddInterleavedBuffer| calls; |
275 | return 0; |
276 | } |
277 | if (input_audio_buffer_queue_->Full()) { |
278 | return 0; |
279 | } |
280 | // Subtract two buffers from the available input slots to ensure the buffer |
281 | // partitioner can be flushed at any time while keeping an extra buffer |
282 | // available in the |buffer_partitioner_| callback for the next incoming data. |
283 | const size_t num_frames_available_in_input_slots = |
284 | (kNumMaxBuffers - input_audio_buffer_queue_->Size() - 2) * |
285 | frames_per_buffer_; |
286 | DCHECK_GT(frames_per_buffer_, buffer_partitioner_->GetNumBufferedFrames()); |
287 | const size_t num_frames_available_in_buffer_partitioner = |
288 | frames_per_buffer_ - buffer_partitioner_->GetNumBufferedFrames(); |
289 | return num_frames_available_in_input_slots + |
290 | num_frames_available_in_buffer_partitioner; |
291 | } |
292 | |
293 | size_t BinauralSurroundRendererImpl::AddInterleavedInput( |
294 | const int16* input_buffer_ptr, size_t num_channels, size_t num_frames) { |
295 | return AddInputBufferTemplated<const int16*>(input_buffer_ptr, num_channels, |
296 | num_frames); |
297 | } |
298 | |
299 | size_t BinauralSurroundRendererImpl::AddInterleavedInput( |
300 | const float* input_buffer_ptr, size_t num_channels, size_t num_frames) { |
301 | return AddInputBufferTemplated<const float*>(input_buffer_ptr, num_channels, |
302 | num_frames); |
303 | } |
304 | |
305 | size_t BinauralSurroundRendererImpl::AddPlanarInput( |
306 | const int16* const* input_buffer_ptrs, size_t num_channels, |
307 | size_t num_frames) { |
308 | return AddInputBufferTemplated<const int16* const*>(input_buffer_ptr: input_buffer_ptrs, |
309 | num_channels, num_frames); |
310 | } |
311 | |
312 | size_t BinauralSurroundRendererImpl::AddPlanarInput( |
313 | const float* const* input_buffer_ptrs, size_t num_channels, |
314 | size_t num_frames) { |
315 | return AddInputBufferTemplated<const float* const*>(input_buffer_ptr: input_buffer_ptrs, |
316 | num_channels, num_frames); |
317 | } |
318 | |
319 | template <typename BufferType> |
320 | size_t BinauralSurroundRendererImpl::AddInputBufferTemplated( |
321 | const BufferType input_buffer_ptr, size_t num_channels, size_t num_frames) { |
322 | DCHECK_NE(surround_format_, kInvalid); |
323 | if (num_channels != num_input_channels_) { |
324 | LOG(WARNING) << "Invalid number of input channels" ; |
325 | return 0; |
326 | } |
327 | |
328 | if (num_zero_padded_frames_ > 0) { |
329 | LOG(WARNING) << "Zero padded output buffers must be consumed prior to " |
330 | "|AddInterleavedBuffer| calls" ; |
331 | return 0; |
332 | } |
333 | const size_t num_available_input_frames = |
334 | std::min(a: num_frames, b: GetNumAvailableFramesInInputBuffer()); |
335 | |
336 | buffer_partitioner_->AddBuffer(input_buffer_ptr, num_input_channels_, |
337 | num_available_input_frames); |
338 | total_frames_buffered_ += num_available_input_frames; |
339 | return num_available_input_frames; |
340 | } |
341 | |
342 | size_t BinauralSurroundRendererImpl::GetAvailableFramesInStereoOutputBuffer() |
343 | const { |
344 | const size_t num_available_samples_in_buffers = |
345 | (input_audio_buffer_queue_->Size() * frames_per_buffer_) + |
346 | buffer_unpartitioner_->GetNumBufferedFrames(); |
347 | return std::min(a: total_frames_buffered_, b: num_available_samples_in_buffers); |
348 | } |
349 | |
350 | size_t BinauralSurroundRendererImpl::GetInterleavedStereoOutput( |
351 | int16* output_buffer_ptr, size_t num_frames) { |
352 | return GetStereoOutputBufferTemplated<int16*>(output_buffer_ptr, num_frames); |
353 | } |
354 | |
355 | size_t BinauralSurroundRendererImpl::GetInterleavedStereoOutput( |
356 | float* output_buffer_ptr, size_t num_frames) { |
357 | return GetStereoOutputBufferTemplated<float*>(output_buffer_ptr, num_frames); |
358 | } |
359 | |
360 | size_t BinauralSurroundRendererImpl::GetPlanarStereoOutput( |
361 | int16** output_buffer_ptrs, size_t num_frames) { |
362 | return GetStereoOutputBufferTemplated<int16**>(output_buffer_ptr: output_buffer_ptrs, |
363 | num_frames); |
364 | } |
365 | |
366 | size_t BinauralSurroundRendererImpl::GetPlanarStereoOutput( |
367 | float** output_buffer_ptrs, size_t num_frames) { |
368 | return GetStereoOutputBufferTemplated<float**>(output_buffer_ptr: output_buffer_ptrs, |
369 | num_frames); |
370 | } |
371 | |
372 | template <typename BufferType> |
373 | size_t BinauralSurroundRendererImpl::GetStereoOutputBufferTemplated( |
374 | BufferType output_buffer_ptr, size_t num_frames) { |
375 | DCHECK_NE(surround_format_, kInvalid); |
376 | const size_t num_frames_available = GetAvailableFramesInStereoOutputBuffer(); |
377 | size_t num_frames_to_be_processed = |
378 | std::min(a: num_frames_available, b: num_frames); |
379 | if (num_frames_to_be_processed > total_frames_buffered_) { |
380 | // Avoid outputting zero padded input frames from |TriggerProcessing| |
381 | // calls. |
382 | num_frames_to_be_processed = total_frames_buffered_; |
383 | } |
384 | |
385 | const size_t num_frames_written = buffer_unpartitioner_->GetBuffer( |
386 | output_buffer_ptr, kNumStereoChannels, num_frames_to_be_processed); |
387 | |
388 | DCHECK_GE(total_frames_buffered_, num_frames_written); |
389 | total_frames_buffered_ -= num_frames_written; |
390 | |
391 | if (total_frames_buffered_ == 0) { |
392 | // Clear zero padded frames from |TriggerProcessing| calls. |
393 | buffer_unpartitioner_->Clear(); |
394 | num_zero_padded_frames_ = 0; |
395 | } |
396 | |
397 | return num_frames_written; |
398 | } |
399 | |
400 | void BinauralSurroundRendererImpl::Clear() { |
401 | input_audio_buffer_queue_->Clear(); |
402 | buffer_partitioner_->Clear(); |
403 | buffer_unpartitioner_->Clear(); |
404 | total_frames_buffered_ = 0; |
405 | num_zero_padded_frames_ = 0; |
406 | } |
407 | |
408 | bool BinauralSurroundRendererImpl::TriggerProcessing() { |
409 | if (num_zero_padded_frames_ > 0) { |
410 | LOG(WARNING) << "Zero padded output buffers must be consumed prior to " |
411 | "|TriggerProcessing| calls" ; |
412 | return false; |
413 | } |
414 | num_zero_padded_frames_ = buffer_partitioner_->Flush(); |
415 | return num_zero_padded_frames_ > 0; |
416 | } |
417 | |
418 | void BinauralSurroundRendererImpl::SetHeadRotation(float w, float x, float y, |
419 | float z) { |
420 | resonance_audio_api_->SetHeadRotation(x, y, z, w); |
421 | } |
422 | |
423 | AudioBuffer* BinauralSurroundRendererImpl::ProcessBuffer() { |
424 | if (input_audio_buffer_queue_->Size() == 0) { |
425 | LOG(WARNING) << "Buffer underflow detected" ; |
426 | return nullptr; |
427 | } |
428 | |
429 | const AudioBuffer* input = input_audio_buffer_queue_->AcquireOutputObject(); |
430 | DCHECK_EQ(input->num_frames(), frames_per_buffer_); |
431 | DCHECK_EQ(num_input_channels_, input->num_channels()); |
432 | GetRawChannelDataPointersFromAudioBuffer(audio_buffer: *input, channel_ptr_vector: &temp_planar_buffer_ptrs_); |
433 | // Initialize surround rendering. |
434 | const float* planar_ptr; |
435 | |
436 | switch (surround_format_) { |
437 | case kSurroundMono: |
438 | case kSurroundStereo: |
439 | case kSurroundFiveDotOne: |
440 | case kSurroundSevenDotOne: |
441 | DCHECK_EQ(input->num_channels(), source_ids_.size()); |
442 | for (size_t source_itr = 0; source_itr < source_ids_.size(); |
443 | ++source_itr) { |
444 | planar_ptr = (*input)[source_itr].begin(); |
445 | resonance_audio_api_->SetPlanarBuffer(source_id: source_ids_[source_itr], |
446 | audio_buffer_ptr: &planar_ptr, num_channels: kNumMonoChannels, |
447 | num_frames: input->num_frames()); |
448 | } |
449 | break; |
450 | case kFirstOrderAmbisonics: |
451 | case kSecondOrderAmbisonics: |
452 | case kThirdOrderAmbisonics: |
453 | DCHECK_EQ(source_ids_.size(), 1U); |
454 | resonance_audio_api_->SetPlanarBuffer( |
455 | source_id: source_ids_[0], audio_buffer_ptr: temp_planar_buffer_ptrs_.data(), |
456 | num_channels: input->num_channels(), num_frames: input->num_frames()); |
457 | break; |
458 | case kFirstOrderAmbisonicsWithNonDiegeticStereo: |
459 | case kSecondOrderAmbisonicsWithNonDiegeticStereo: |
460 | case kThirdOrderAmbisonicsWithNonDiegeticStereo: |
461 | DCHECK_EQ(source_ids_.size(), 2U); |
462 | DCHECK_GT(input->num_channels(), kNumStereoChannels); |
463 | static_cast<ResonanceAudioApiImpl*>(resonance_audio_api_.get()) |
464 | ->SetPlanarBuffer(source_id: source_ids_[0], audio_buffer_ptr: temp_planar_buffer_ptrs_.data(), |
465 | num_channels: input->num_channels() - kNumStereoChannels, |
466 | num_frames: input->num_frames()); |
467 | static_cast<ResonanceAudioApiImpl*>(resonance_audio_api_.get()) |
468 | ->SetPlanarBuffer(source_id: source_ids_[1], |
469 | audio_buffer_ptr: temp_planar_buffer_ptrs_.data() + |
470 | (input->num_channels() - kNumStereoChannels), |
471 | num_channels: kNumStereoChannels, num_frames: input->num_frames()); |
472 | break; |
473 | default: |
474 | LOG(FATAL) << "Undefined surround format" ; |
475 | break; |
476 | } |
477 | |
478 | // Create a copy of the processed |AudioBuffer| to pass it to output buffer |
479 | // queue. |
480 | auto* const vraudio_api_impl = |
481 | static_cast<ResonanceAudioApiImpl*>(resonance_audio_api_.get()); |
482 | vraudio_api_impl->ProcessNextBuffer(); |
483 | output_buffer_ = *vraudio_api_impl->GetStereoOutputBuffer(); |
484 | |
485 | if (output_gain_ != 1.0f) { |
486 | for (AudioBuffer::Channel& channel : output_buffer_) { |
487 | ScalarMultiply(length: output_buffer_.num_frames(), gain: output_gain_, input: channel.begin(), |
488 | output: channel.begin()); |
489 | } |
490 | } |
491 | input_audio_buffer_queue_->ReleaseOutputObject(object: input); |
492 | return &output_buffer_; |
493 | } |
494 | |
495 | } // namespace vraudio |
496 | |