| /* | |
| * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "test/pc/e2e/echo/echo_emulation.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <limits> | |
| #include <memory> | |
| #include <span> | |
| #include <utility> | |
| #include "api/sequence_checker.h" | |
| #include "api/test/pclf/media_configuration.h" | |
| #include "modules/audio_device/include/test_audio_device.h" | |
| #include "rtc_base/buffer.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/logging.h" | |
| namespace webrtc { | |
| namespace webrtc_pc_e2e { | |
| namespace { | |
| constexpr int kSingleBufferDurationMs = 10; | |
| } // namespace | |
| EchoEmulatingCapturer::EchoEmulatingCapturer( | |
| std::unique_ptr<TestAudioDeviceModule::Capturer> capturer, | |
| EchoEmulationConfig config) | |
| : delegate_(std::move(capturer)), | |
| config_(config), | |
| renderer_queue_(2 * config_.echo_delay.ms() / kSingleBufferDurationMs), | |
| queue_input_(TestAudioDeviceModule::SamplesPerFrame( | |
| delegate_->SamplingFrequency()) * | |
| delegate_->NumChannels()), | |
| queue_output_(TestAudioDeviceModule::SamplesPerFrame( | |
| delegate_->SamplingFrequency()) * | |
| delegate_->NumChannels()) { | |
| renderer_thread_.Detach(); | |
| capturer_thread_.Detach(); | |
| } | |
| void EchoEmulatingCapturer::OnAudioRendered(std::span<const int16_t> data) { | |
| RTC_DCHECK_RUN_ON(&renderer_thread_); | |
| if (!recording_started_) { | |
| // Because rendering can start before capturing in the beginning we can have | |
| // a set of empty audio data frames. So we will skip them and will start | |
| // fill the queue only after 1st non-empty audio data frame will arrive. | |
| bool is_empty = true; | |
| for (auto d : data) { | |
| if (d != 0) { | |
| is_empty = false; | |
| break; | |
| } | |
| } | |
| if (is_empty) { | |
| return; | |
| } | |
| recording_started_ = true; | |
| } | |
| queue_input_.assign(data.begin(), data.end()); | |
| if (!renderer_queue_.Insert(&queue_input_)) { | |
| RTC_LOG(LS_WARNING) << "Echo queue is full"; | |
| } | |
| } | |
| bool EchoEmulatingCapturer::Capture(BufferT<int16_t>* buffer) { | |
| RTC_DCHECK_RUN_ON(&capturer_thread_); | |
| bool result = delegate_->Capture(buffer); | |
| // Now we have to reduce input signal to avoid saturation when mixing in the | |
| // fake echo. | |
| for (size_t i = 0; i < buffer->size(); ++i) { | |
| (*buffer)[i] /= 2; | |
| } | |
| // When we accumulated enough delay in the echo buffer we will pop from | |
| // that buffer on each ::Capture(...) call. If the buffer become empty it | |
| // will mean some bug, so we will crash during removing item from the queue. | |
| if (!delay_accumulated_) { | |
| delay_accumulated_ = | |
| renderer_queue_.SizeAtLeast() >= | |
| static_cast<size_t>(config_.echo_delay.ms() / kSingleBufferDurationMs); | |
| } | |
| if (delay_accumulated_) { | |
| RTC_CHECK(renderer_queue_.Remove(&queue_output_)); | |
| for (size_t i = 0; i < buffer->size() && i < queue_output_.size(); ++i) { | |
| int32_t res = (*buffer)[i] + queue_output_[i]; | |
| if (res < std::numeric_limits<int16_t>::min()) { | |
| res = std::numeric_limits<int16_t>::min(); | |
| } | |
| if (res > std::numeric_limits<int16_t>::max()) { | |
| res = std::numeric_limits<int16_t>::max(); | |
| } | |
| (*buffer)[i] = static_cast<int16_t>(res); | |
| } | |
| } | |
| return result; | |
| } | |
| EchoEmulatingRenderer::EchoEmulatingRenderer( | |
| std::unique_ptr<TestAudioDeviceModule::Renderer> renderer, | |
| EchoEmulatingCapturer* echo_emulating_capturer) | |
| : delegate_(std::move(renderer)), | |
| echo_emulating_capturer_(echo_emulating_capturer) { | |
| RTC_DCHECK(echo_emulating_capturer_); | |
| } | |
| bool EchoEmulatingRenderer::Render(std::span<const int16_t> data) { | |
| if (!data.empty()) { | |
| echo_emulating_capturer_->OnAudioRendered(data); | |
| } | |
| return delegate_->Render(data); | |
| } | |
| } // namespace webrtc_pc_e2e | |
| } // namespace webrtc |