| 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/resonance_audio_api_impl.h" | 
| 18 |  | 
| 19 | #include <algorithm> | 
| 20 | #include <numeric> | 
| 21 |  | 
| 22 | #include "ambisonics/utils.h" | 
| 23 | #include "base/constants_and_types.h" | 
| 24 | #include "base/logging.h" | 
| 25 | #include "base/misc_math.h" | 
| 26 | #include "base/source_parameters.h" | 
| 27 |  | 
| 28 | #include "base/unique_ptr_wrapper.h" | 
| 29 | #include "config/source_config.h" | 
| 30 | #include "dsp/channel_converter.h" | 
| 31 | #include "dsp/distance_attenuation.h" | 
| 32 | #include "graph/source_parameters_manager.h" | 
| 33 | #include "utils/planar_interleaved_conversion.h" | 
| 34 | #include "utils/sample_type_conversion.h" | 
| 35 |  | 
| 36 | namespace vraudio { | 
| 37 |  | 
| 38 | namespace { | 
| 39 |  | 
| 40 | // Support 50 setter calls for 512 sources. | 
| 41 | const size_t kMaxNumTasksOnTaskQueue = 50 * 512; | 
| 42 |  | 
| 43 | // User warning/notification messages. | 
| 44 | static const char* kBadInputPointerMessage = "Ignoring nullptr buffer" ; | 
| 45 | static const char* kBufferSizeMustMatchNumFramesMessage = | 
| 46 |     "Number of frames must match the frames per buffer specified during "  | 
| 47 |     "construction - ignoring buffer" ; | 
| 48 |  | 
| 49 | // Helper method to fetch |SourceGraphConfig| from |RenderingMode|. | 
| 50 | SourceGraphConfig GetSourceGraphConfigFromRenderingMode( | 
| 51 |     RenderingMode rendering_mode) { | 
| 52 |   switch (rendering_mode) { | 
| 53 |     case RenderingMode::kStereoPanning: | 
| 54 |       return StereoPanningConfig(); | 
| 55 |     case RenderingMode::kBinauralLowQuality: | 
| 56 |       return BinauralLowQualityConfig(); | 
| 57 |     case RenderingMode::kBinauralMediumQuality: | 
| 58 |       return BinauralMediumQualityConfig(); | 
| 59 |     case RenderingMode::kBinauralHighQuality: | 
| 60 |       return BinauralHighQualityConfig(); | 
| 61 |     case RenderingMode::kRoomEffectsOnly: | 
| 62 |       return RoomEffectsOnlyConfig(); | 
| 63 |     default: | 
| 64 |       LOG(FATAL) << "Unknown rendering mode" ; | 
| 65 |       break; | 
| 66 |   } | 
| 67 |   return BinauralHighQualityConfig(); | 
| 68 | } | 
| 69 |  | 
| 70 | }  // namespace | 
| 71 |  | 
| 72 | ResonanceAudioApiImpl::ResonanceAudioApiImpl(size_t num_channels, | 
| 73 |                                              size_t frames_per_buffer, | 
| 74 |                                              int sample_rate_hz) | 
| 75 |     : system_settings_(num_channels, frames_per_buffer, sample_rate_hz), | 
| 76 |       task_queue_(kMaxNumTasksOnTaskQueue), | 
| 77 |       source_id_counter_(0) { | 
| 78 |   if (num_channels != kNumStereoChannels) { | 
| 79 |     LOG(FATAL) << "Only stereo output is supported" ; | 
| 80 |     return; | 
| 81 |   } | 
| 82 |  | 
| 83 |   if (frames_per_buffer > kMaxSupportedNumFrames) { | 
| 84 |     LOG(FATAL) << "Only frame lengths up to "  << kMaxSupportedNumFrames | 
| 85 |                << " are supported." ; | 
| 86 |     return; | 
| 87 |   } | 
| 88 |  | 
| 89 |   // The pffft library requires a minimum buffer size of 32 samples. | 
| 90 |   if (frames_per_buffer < FftManager::kMinFftSize) { | 
| 91 |     LOG(FATAL) << "The minimum number of frames per buffer is "  | 
| 92 |                << FftManager::kMinFftSize << " samples" ; | 
| 93 |     return; | 
| 94 |   } | 
| 95 |   graph_manager_.reset(p: new GraphManager(system_settings_)); | 
| 96 | } | 
| 97 |  | 
| 98 | ResonanceAudioApiImpl::~ResonanceAudioApiImpl() { | 
| 99 |   // Clear task queue before shutting down. | 
| 100 |   task_queue_.Execute(); | 
| 101 | } | 
| 102 |  | 
| 103 | bool ResonanceAudioApiImpl::FillInterleavedOutputBuffer(size_t num_channels, | 
| 104 |                                                         size_t num_frames, | 
| 105 |                                                         float* buffer_ptr) { | 
| 106 |   DCHECK(buffer_ptr); | 
| 107 |   return FillOutputBuffer<float*>(num_channels, num_frames, buffer_ptr); | 
| 108 | } | 
| 109 |  | 
| 110 | bool ResonanceAudioApiImpl::FillInterleavedOutputBuffer(size_t num_channels, | 
| 111 |                                                         size_t num_frames, | 
| 112 |                                                         int16* buffer_ptr) { | 
| 113 |   DCHECK(buffer_ptr); | 
| 114 |   return FillOutputBuffer<int16*>(num_channels, num_frames, buffer_ptr); | 
| 115 | } | 
| 116 |  | 
| 117 | bool ResonanceAudioApiImpl::FillPlanarOutputBuffer(size_t num_channels, | 
| 118 |                                                    size_t num_frames, | 
| 119 |                                                    float* const* buffer_ptr) { | 
| 120 |   DCHECK(buffer_ptr); | 
| 121 |   return FillOutputBuffer<float* const*>(num_channels, num_frames, buffer_ptr); | 
| 122 | } | 
| 123 |  | 
| 124 | bool ResonanceAudioApiImpl::FillPlanarOutputBuffer(size_t num_channels, | 
| 125 |                                                    size_t num_frames, | 
| 126 |                                                    int16* const* buffer_ptr) { | 
| 127 |   DCHECK(buffer_ptr); | 
| 128 |   return FillOutputBuffer<int16* const*>(num_channels, num_frames, buffer_ptr); | 
| 129 | } | 
| 130 |  | 
| 131 | void ResonanceAudioApiImpl::SetHeadPosition(float x, float y, float z) { | 
| 132 |   auto task = [this, x, y, z]() { | 
| 133 |     const WorldPosition head_position(x, y, z); | 
| 134 |     system_settings_.SetHeadPosition(head_position); | 
| 135 |   }; | 
| 136 |   task_queue_.Post(task); | 
| 137 | } | 
| 138 |  | 
| 139 | void ResonanceAudioApiImpl::SetHeadRotation(float x, float y, float z, | 
| 140 |                                             float w) { | 
| 141 |   auto task = [this, w, x, y, z]() { | 
| 142 |     const WorldRotation head_rotation(w, x, y, z); | 
| 143 |     system_settings_.SetHeadRotation(head_rotation); | 
| 144 |   }; | 
| 145 |   task_queue_.Post(task); | 
| 146 | } | 
| 147 |  | 
| 148 | void ResonanceAudioApiImpl::SetMasterVolume(float volume) { | 
| 149 |   auto task = [this, volume]() { system_settings_.SetMasterGain(volume); }; | 
| 150 |   task_queue_.Post(task); | 
| 151 | } | 
| 152 |  | 
| 153 | int ResonanceAudioApiImpl::CreateAmbisonicSource(size_t num_channels) { | 
| 154 |   if (num_channels < kNumFirstOrderAmbisonicChannels || | 
| 155 |       !IsValidAmbisonicOrder(num_channels)) { | 
| 156 |     // Invalid number of input channels, don't create the ambisonic source. | 
| 157 |     LOG(ERROR) << "Invalid number of channels for the ambisonic source: "  | 
| 158 |                << num_channels; | 
| 159 |     return kInvalidSourceId; | 
| 160 |   } | 
| 161 |  | 
| 162 |   const int ambisonic_source_id = source_id_counter_.fetch_add(i: 1); | 
| 163 |  | 
| 164 |   const size_t num_valid_channels = | 
| 165 |       std::min(a: num_channels, b: graph_manager_->GetNumMaxAmbisonicChannels()); | 
| 166 |   if (num_valid_channels < num_channels) { | 
| 167 |     LOG(WARNING) << "Number of ambisonic channels will be diminished to "  | 
| 168 |                  << num_valid_channels; | 
| 169 |   } | 
| 170 |  | 
| 171 |   auto task = [this, ambisonic_source_id, num_valid_channels]() { | 
| 172 |     graph_manager_->CreateAmbisonicSource(ambisonic_source_id, | 
| 173 |                                           num_channels: num_valid_channels); | 
| 174 |     system_settings_.GetSourceParametersManager()->Register( | 
| 175 |         source_id: ambisonic_source_id); | 
| 176 |     // Overwrite default source parameters for ambisonic source. | 
| 177 |     auto source_parameters = | 
| 178 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 179 |             source_id: ambisonic_source_id); | 
| 180 |     source_parameters->room_effects_gain = 0.0f; | 
| 181 |     source_parameters->distance_rolloff_model = DistanceRolloffModel::kNone; | 
| 182 |     source_parameters->distance_attenuation = 1.0f; | 
| 183 |   }; | 
| 184 |   task_queue_.Post(task); | 
| 185 |   return ambisonic_source_id; | 
| 186 | } | 
| 187 |  | 
| 188 | int ResonanceAudioApiImpl::CreateStereoSource(size_t num_channels) { | 
| 189 |   if (num_channels > kNumStereoChannels) { | 
| 190 |     LOG(ERROR) << "Unsupported number of input channels" ; | 
| 191 |     return kInvalidSourceId; | 
| 192 |   } | 
| 193 |   const int stereo_source_id = source_id_counter_.fetch_add(i: 1); | 
| 194 |  | 
| 195 |   auto task = [this, stereo_source_id]() { | 
| 196 |     graph_manager_->CreateStereoSource(stereo_source_id); | 
| 197 |     system_settings_.GetSourceParametersManager()->Register(source_id: stereo_source_id); | 
| 198 |     auto source_parameters = | 
| 199 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 200 |             source_id: stereo_source_id); | 
| 201 |     source_parameters->enable_hrtf = false; | 
| 202 |   }; | 
| 203 |   task_queue_.Post(task); | 
| 204 |   return stereo_source_id; | 
| 205 | } | 
| 206 |  | 
| 207 | int ResonanceAudioApiImpl::CreateSoundObjectSource( | 
| 208 |     RenderingMode rendering_mode) { | 
| 209 |   const int sound_object_source_id = source_id_counter_.fetch_add(i: 1); | 
| 210 |  | 
| 211 |   const auto config = GetSourceGraphConfigFromRenderingMode(rendering_mode); | 
| 212 |   auto task = [this, sound_object_source_id, config]() { | 
| 213 |     graph_manager_->CreateSoundObjectSource( | 
| 214 |         sound_object_source_id, ambisonic_order: config.ambisonic_order, enable_hrtf: config.enable_hrtf, | 
| 215 |         enable_direct_rendering: config.enable_direct_rendering); | 
| 216 |     system_settings_.GetSourceParametersManager()->Register( | 
| 217 |         source_id: sound_object_source_id); | 
| 218 |     auto source_parameters = | 
| 219 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 220 |             source_id: sound_object_source_id); | 
| 221 |     source_parameters->enable_hrtf = config.enable_hrtf; | 
| 222 |   }; | 
| 223 |   task_queue_.Post(task); | 
| 224 |   return sound_object_source_id; | 
| 225 | } | 
| 226 |  | 
| 227 | void ResonanceAudioApiImpl::DestroySource(SourceId source_id) { | 
| 228 |   auto task = [this, source_id]() { | 
| 229 |     graph_manager_->DestroySource(source_id); | 
| 230 |     system_settings_.GetSourceParametersManager()->Unregister(source_id); | 
| 231 |   }; | 
| 232 |   task_queue_.Post(task); | 
| 233 | } | 
| 234 |  | 
| 235 | void ResonanceAudioApiImpl::SetInterleavedBuffer(SourceId source_id, | 
| 236 |                                                  const float* audio_buffer_ptr, | 
| 237 |                                                  size_t num_channels, | 
| 238 |                                                  size_t num_frames) { | 
| 239 |   SetSourceBuffer<const float*>(source_id, audio_buffer_ptr, num_input_channels: num_channels, | 
| 240 |                                 num_frames); | 
| 241 | } | 
| 242 |  | 
| 243 | void ResonanceAudioApiImpl::SetInterleavedBuffer(SourceId source_id, | 
| 244 |                                                  const int16* audio_buffer_ptr, | 
| 245 |                                                  size_t num_channels, | 
| 246 |                                                  size_t num_frames) { | 
| 247 |   SetSourceBuffer<const int16*>(source_id, audio_buffer_ptr, num_input_channels: num_channels, | 
| 248 |                                 num_frames); | 
| 249 | } | 
| 250 |  | 
| 251 | void ResonanceAudioApiImpl::SetPlanarBuffer( | 
| 252 |     SourceId source_id, const float* const* audio_buffer_ptr, | 
| 253 |     size_t num_channels, size_t num_frames) { | 
| 254 |   SetSourceBuffer<const float* const*>(source_id, audio_buffer_ptr, | 
| 255 |                                        num_input_channels: num_channels, num_frames); | 
| 256 | } | 
| 257 |  | 
| 258 | void ResonanceAudioApiImpl::SetPlanarBuffer( | 
| 259 |     SourceId source_id, const int16* const* audio_buffer_ptr, | 
| 260 |     size_t num_channels, size_t num_frames) { | 
| 261 |   SetSourceBuffer<const int16* const*>(source_id, audio_buffer_ptr, | 
| 262 |                                        num_input_channels: num_channels, num_frames); | 
| 263 | } | 
| 264 |  | 
| 265 | void ResonanceAudioApiImpl::SetSourceDistanceAttenuation( | 
| 266 |     SourceId source_id, float distance_attenuation) { | 
| 267 |   auto task = [this, source_id, distance_attenuation]() { | 
| 268 |     auto source_parameters = | 
| 269 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 270 |             source_id); | 
| 271 |     if (source_parameters != nullptr) { | 
| 272 |       const auto& rolloff_model = source_parameters->distance_rolloff_model; | 
| 273 |       DCHECK_EQ(rolloff_model, DistanceRolloffModel::kNone); | 
| 274 |       if (rolloff_model != DistanceRolloffModel::kNone) { | 
| 275 |         LOG(WARNING) << "Implicit distance rolloff model is set. The value "  | 
| 276 |                         "will be overwritten." ; | 
| 277 |       } | 
| 278 |       source_parameters->distance_attenuation = distance_attenuation; | 
| 279 |     } | 
| 280 |   }; | 
| 281 |   task_queue_.Post(task); | 
| 282 | } | 
| 283 |  | 
| 284 | void ResonanceAudioApiImpl::SetSourceDistanceModel(SourceId source_id, | 
| 285 |                                                    DistanceRolloffModel rolloff, | 
| 286 |                                                    float min_distance, | 
| 287 |                                                    float max_distance) { | 
| 288 |   if (max_distance < min_distance && rolloff != DistanceRolloffModel::kNone) { | 
| 289 |     LOG(WARNING) << "max_distance must be larger than min_distance" ; | 
| 290 |     return; | 
| 291 |   } | 
| 292 |   auto task = [this, source_id, rolloff, min_distance, max_distance]() { | 
| 293 |     auto source_parameters = | 
| 294 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 295 |             source_id); | 
| 296 |     if (source_parameters != nullptr) { | 
| 297 |       source_parameters->distance_rolloff_model = rolloff; | 
| 298 |       source_parameters->minimum_distance = min_distance; | 
| 299 |       source_parameters->maximum_distance = max_distance; | 
| 300 |     } | 
| 301 |   }; | 
| 302 |   task_queue_.Post(task); | 
| 303 | } | 
| 304 |  | 
| 305 | void ResonanceAudioApiImpl::SetSourcePosition(SourceId source_id, float x, | 
| 306 |                                               float y, float z) { | 
| 307 |   const WorldPosition position(x, y, z); | 
| 308 |   auto task = [this, source_id, position]() { | 
| 309 |     auto source_parameters = | 
| 310 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 311 |             source_id); | 
| 312 |     if (source_parameters != nullptr) { | 
| 313 |       source_parameters->object_transform.position = position; | 
| 314 |     } | 
| 315 |   }; | 
| 316 |   task_queue_.Post(task); | 
| 317 | } | 
| 318 |  | 
| 319 | void ResonanceAudioApiImpl::SetSourceRoomEffectsGain(SourceId source_id, | 
| 320 |                                                      float room_effects_gain) { | 
| 321 |   auto task = [this, source_id, room_effects_gain]() { | 
| 322 |     auto source_parameters = | 
| 323 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 324 |             source_id); | 
| 325 |     if (source_parameters != nullptr) { | 
| 326 |       source_parameters->room_effects_gain = room_effects_gain; | 
| 327 |     } | 
| 328 |   }; | 
| 329 |   task_queue_.Post(task); | 
| 330 | } | 
| 331 |  | 
| 332 | void ResonanceAudioApiImpl::SetSourceRotation(SourceId source_id, float x, | 
| 333 |                                               float y, float z, float w) { | 
| 334 |   const WorldRotation rotation(w, x, y, z); | 
| 335 |   auto task = [this, source_id, rotation]() { | 
| 336 |     auto source_parameters = | 
| 337 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 338 |             source_id); | 
| 339 |     if (source_parameters != nullptr) { | 
| 340 |       source_parameters->object_transform.rotation = rotation; | 
| 341 |     } | 
| 342 |   }; | 
| 343 |   task_queue_.Post(task); | 
| 344 | } | 
| 345 |  | 
| 346 | void ResonanceAudioApiImpl::SetSourceVolume(SourceId source_id, float volume) { | 
| 347 |   auto task = [this, source_id, volume]() { | 
| 348 |     auto source_parameters = | 
| 349 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 350 |             source_id); | 
| 351 |     if (source_parameters != nullptr) { | 
| 352 |       source_parameters->gain = volume; | 
| 353 |     } | 
| 354 |   }; | 
| 355 |   task_queue_.Post(task); | 
| 356 | } | 
| 357 |  | 
| 358 | void ResonanceAudioApiImpl::SetSoundObjectDirectivity( | 
| 359 |     SourceId sound_object_source_id, float alpha, float order) { | 
| 360 |   auto task = [this, sound_object_source_id, alpha, order]() { | 
| 361 |     auto source_parameters = | 
| 362 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 363 |             source_id: sound_object_source_id); | 
| 364 |     if (source_parameters != nullptr) { | 
| 365 |       source_parameters->directivity_alpha = alpha; | 
| 366 |       source_parameters->directivity_order = order; | 
| 367 |     } | 
| 368 |   }; | 
| 369 |   task_queue_.Post(task); | 
| 370 | } | 
| 371 |  | 
| 372 | void ResonanceAudioApiImpl::SetSoundObjectListenerDirectivity( | 
| 373 |     SourceId sound_object_source_id, float alpha, float order) { | 
| 374 |   auto task = [this, sound_object_source_id, alpha, order]() { | 
| 375 |     auto source_parameters = | 
| 376 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 377 |             source_id: sound_object_source_id); | 
| 378 |     if (source_parameters != nullptr) { | 
| 379 |       source_parameters->listener_directivity_alpha = alpha; | 
| 380 |       source_parameters->listener_directivity_order = order; | 
| 381 |     } | 
| 382 |   }; | 
| 383 |   task_queue_.Post(task); | 
| 384 | } | 
| 385 |  | 
| 386 | void ResonanceAudioApiImpl::SetSoundObjectNearFieldEffectGain( | 
| 387 |     SourceId sound_object_source_id, float gain) { | 
| 388 |   auto task = [this, sound_object_source_id, gain]() { | 
| 389 |     auto source_parameters = | 
| 390 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 391 |             source_id: sound_object_source_id); | 
| 392 |     if (source_parameters != nullptr) { | 
| 393 |       source_parameters->near_field_gain = gain; | 
| 394 |     } | 
| 395 |   }; | 
| 396 |   task_queue_.Post(task); | 
| 397 | } | 
| 398 |  | 
| 399 | void ResonanceAudioApiImpl::SetSoundObjectOcclusionIntensity( | 
| 400 |     SourceId sound_object_source_id, float intensity) { | 
| 401 |   auto task = [this, sound_object_source_id, intensity]() { | 
| 402 |     auto source_parameters = | 
| 403 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 404 |             source_id: sound_object_source_id); | 
| 405 |     if (source_parameters != nullptr) { | 
| 406 |       source_parameters->occlusion_intensity = intensity; | 
| 407 |     } | 
| 408 |   }; | 
| 409 |   task_queue_.Post(task); | 
| 410 | } | 
| 411 |  | 
| 412 | void ResonanceAudioApiImpl::SetSoundObjectSpread( | 
| 413 |     SourceId sound_object_source_id, float spread_deg) { | 
| 414 |   auto task = [this, sound_object_source_id, spread_deg]() { | 
| 415 |     auto source_parameters = | 
| 416 |         system_settings_.GetSourceParametersManager()->GetMutableParameters( | 
| 417 |             source_id: sound_object_source_id); | 
| 418 |     if (source_parameters != nullptr) { | 
| 419 |       source_parameters->spread_deg = spread_deg; | 
| 420 |     } | 
| 421 |   }; | 
| 422 |   task_queue_.Post(task); | 
| 423 | } | 
| 424 |  | 
| 425 | void ResonanceAudioApiImpl::EnableRoomEffects(bool enable) { | 
| 426 |   auto task = [this, enable]() { graph_manager_->EnableRoomEffects(enable); }; | 
| 427 |   task_queue_.Post(task); | 
| 428 | } | 
| 429 |  | 
| 430 | void ResonanceAudioApiImpl::SetReflectionProperties( | 
| 431 |     const ReflectionProperties& reflection_properties) { | 
| 432 |   auto task = [this, reflection_properties]() { | 
| 433 |     system_settings_.SetReflectionProperties(reflection_properties); | 
| 434 |   }; | 
| 435 |   task_queue_.Post(task); | 
| 436 | } | 
| 437 |  | 
| 438 | void ResonanceAudioApiImpl::SetReverbProperties( | 
| 439 |     const ReverbProperties& reverb_properties) { | 
| 440 |   auto task = [this, reverb_properties]() { | 
| 441 |     system_settings_.SetReverbProperties(reverb_properties); | 
| 442 |   }; | 
| 443 |   task_queue_.Post(task); | 
| 444 | } | 
| 445 |  | 
| 446 | const AudioBuffer* ResonanceAudioApiImpl::GetAmbisonicOutputBuffer() const { | 
| 447 |   return graph_manager_->GetAmbisonicBuffer(); | 
| 448 | } | 
| 449 |  | 
| 450 | const AudioBuffer* ResonanceAudioApiImpl::GetStereoOutputBuffer() const { | 
| 451 |     return graph_manager_->GetStereoBuffer(); | 
| 452 | } | 
| 453 |  | 
| 454 | const AudioBuffer *ResonanceAudioApiImpl::GetReverbBuffer() const { | 
| 455 |     return graph_manager_->GetReverbBuffer(); | 
| 456 | } | 
| 457 |  | 
| 458 | void ResonanceAudioApiImpl::ProcessNextBuffer() { | 
| 459 | #if defined(ENABLE_TRACING) && !ION_PRODUCTION | 
| 460 |   // This enables tracing on the audio thread. | 
| 461 |   auto task = []() { ENABLE_TRACING_ON_CURRENT_THREAD("AudioThread" ); }; | 
| 462 |   task_queue_.Post(task); | 
| 463 | #endif  // defined(ENABLE_TRACING) && !ION_PRODUCTION | 
| 464 |  | 
| 465 |  | 
| 466 |   task_queue_.Execute(); | 
| 467 |  | 
| 468 |   // Update room effects only if the pipeline is initialized. | 
| 469 |   if (graph_manager_->GetRoomEffectsEnabled()) { | 
| 470 |     graph_manager_->UpdateRoomReflections(); | 
| 471 |     graph_manager_->UpdateRoomReverb(); | 
| 472 |   } | 
| 473 |   // Update source attenuation parameters. | 
| 474 |   const auto process = [this](SourceParameters* parameters) { | 
| 475 |     const float master_gain = system_settings_.GetMasterGain(); | 
| 476 |     const auto& listener_position = system_settings_.GetHeadPosition(); | 
| 477 |     const auto& reflection_properties = | 
| 478 |         system_settings_.GetReflectionProperties(); | 
| 479 |     const auto& reverb_properties = system_settings_.GetReverbProperties(); | 
| 480 |     UpdateAttenuationParameters(master_gain, reflections_gain: reflection_properties.gain, | 
| 481 |                                 reverb_gain: reverb_properties.gain, listener_position, | 
| 482 |                                 parameters); | 
| 483 |   }; | 
| 484 |   system_settings_.GetSourceParametersManager()->ProcessAllParameters(process); | 
| 485 |  | 
| 486 |   graph_manager_->Process(); | 
| 487 | } | 
| 488 |  | 
| 489 | void ResonanceAudioApiImpl::SetStereoSpeakerMode(bool enabled) { | 
| 490 |   auto task = [this, enabled]() { | 
| 491 |     system_settings_.SetStereoSpeakerMode(enabled); | 
| 492 |   }; | 
| 493 |   task_queue_.Post(task); | 
| 494 | } | 
| 495 |  | 
| 496 | template <typename OutputType> | 
| 497 | bool ResonanceAudioApiImpl::FillOutputBuffer(size_t num_channels, | 
| 498 |                                              size_t num_frames, | 
| 499 |                                              OutputType buffer_ptr) { | 
| 500 |  | 
| 501 |  | 
| 502 |   if (buffer_ptr == nullptr) { | 
| 503 |     LOG(WARNING) << kBadInputPointerMessage; | 
| 504 |     return false; | 
| 505 |   } | 
| 506 |   if (num_channels != kNumStereoChannels) { | 
| 507 |     LOG(WARNING) << "Output buffer must be stereo" ; | 
| 508 |     return false; | 
| 509 |   } | 
| 510 |   const size_t num_input_samples = num_frames * num_channels; | 
| 511 |   const size_t num_expected_output_samples = | 
| 512 |       system_settings_.GetFramesPerBuffer() * system_settings_.GetNumChannels(); | 
| 513 |   if (num_input_samples != num_expected_output_samples) { | 
| 514 |     LOG(WARNING) << "Output buffer size must be "  << num_expected_output_samples | 
| 515 |                  << " samples" ; | 
| 516 |     return false; | 
| 517 |   } | 
| 518 |  | 
| 519 |   // Get the processed output buffer. | 
| 520 |   ProcessNextBuffer(); | 
| 521 |   const AudioBuffer* output_buffer = GetStereoOutputBuffer(); | 
| 522 |   if (output_buffer == nullptr) { | 
| 523 |     // This indicates that the graph processing is triggered without having any | 
| 524 |     // connected sources. | 
| 525 |     return false; | 
| 526 |   } | 
| 527 |  | 
| 528 |   FillExternalBuffer(*output_buffer, buffer_ptr, num_frames, num_channels); | 
| 529 |   return true; | 
| 530 | } | 
| 531 |  | 
| 532 | template <typename SampleType> | 
| 533 | void ResonanceAudioApiImpl::SetSourceBuffer(SourceId source_id, | 
| 534 |                                             SampleType audio_buffer_ptr, | 
| 535 |                                             size_t num_input_channels, | 
| 536 |                                             size_t num_frames) { | 
| 537 |   // Execute task queue to ensure newly created sound sources are initialized. | 
| 538 |   task_queue_.Execute(); | 
| 539 |  | 
| 540 |   if (audio_buffer_ptr == nullptr) { | 
| 541 |     LOG(WARNING) << kBadInputPointerMessage; | 
| 542 |     return; | 
| 543 |   } | 
| 544 |   if (num_frames != system_settings_.GetFramesPerBuffer()) { | 
| 545 |     LOG(WARNING) << kBufferSizeMustMatchNumFramesMessage; | 
| 546 |     return; | 
| 547 |   } | 
| 548 |  | 
| 549 |   AudioBuffer* const output_buffer = | 
| 550 |       graph_manager_->GetMutableAudioBuffer(source_id); | 
| 551 |   if (output_buffer == nullptr) { | 
| 552 |     LOG(WARNING) << "Source audio buffer not found" ; | 
| 553 |     return; | 
| 554 |   } | 
| 555 |   const size_t num_output_channels = output_buffer->num_channels(); | 
| 556 |  | 
| 557 |   if (num_input_channels == num_output_channels) { | 
| 558 |     FillAudioBuffer(audio_buffer_ptr, num_frames, num_input_channels, | 
| 559 |                     output_buffer); | 
| 560 |  | 
| 561 |     return; | 
| 562 |   } | 
| 563 |  | 
| 564 |   if ((num_input_channels == kNumMonoChannels) && | 
| 565 |       (num_output_channels == kNumStereoChannels)) { | 
| 566 |     FillAudioBufferWithChannelRemapping( | 
| 567 |         audio_buffer_ptr, num_frames, num_input_channels, | 
| 568 |         {0, 0} /* channel_map */, output_buffer); | 
| 569 |     return; | 
| 570 |   } | 
| 571 |  | 
| 572 |   if (num_input_channels > num_output_channels) { | 
| 573 |     std::vector<size_t> channel_map(num_output_channels); | 
| 574 |     // Fill channel map with increasing indices. | 
| 575 |     std::iota(first: std::begin(cont&: channel_map), last: std::end(cont&: channel_map), value: 0); | 
| 576 |     FillAudioBufferWithChannelRemapping(audio_buffer_ptr, num_frames, | 
| 577 |                                         num_input_channels, channel_map, | 
| 578 |                                         output_buffer); | 
| 579 |     return; | 
| 580 |   } | 
| 581 |  | 
| 582 |   LOG(WARNING) << "Number of input channels does not match the number of "  | 
| 583 |                   "output channels" ; | 
| 584 | } | 
| 585 |  | 
| 586 | }  // namespace vraudio | 
| 587 |  |