| /* | |
| * Copyright (c) 2018 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 "modules/audio_device/include/test_audio_device.h" | |
| #include <algorithm> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <memory> | |
| #include <span> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/string_view.h" | |
| #include "api/audio/audio_device.h" | |
| #include "api/environment/environment.h" | |
| #include "api/make_ref_counted.h" | |
| #include "api/scoped_refptr.h" | |
| #include "common_audio/wav_file.h" | |
| #include "modules/audio_device/audio_device_impl.h" | |
| #include "modules/audio_device/test_audio_device_impl.h" | |
| #include "rtc_base/buffer.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/numerics/safe_conversions.h" | |
| #include "rtc_base/random.h" | |
| #include "rtc_base/synchronization/mutex.h" | |
| #include "rtc_base/system/file_wrapper.h" | |
| #include "rtc_base/thread_annotations.h" | |
| #include "rtc_base/time_utils.h" | |
| namespace webrtc { | |
| namespace { | |
| constexpr int kFrameLengthUs = 10000; | |
| constexpr int kFramesPerSecond = kNumMicrosecsPerSec / kFrameLengthUs; | |
| // A fake capturer that generates pulses with random samples between | |
| // -max_amplitude and +max_amplitude. | |
| class PulsedNoiseCapturerImpl final | |
| : public TestAudioDeviceModule::PulsedNoiseCapturer { | |
| public: | |
| // Assuming 10ms audio packets. | |
| PulsedNoiseCapturerImpl(int16_t max_amplitude, | |
| int sampling_frequency_in_hz, | |
| int num_channels) | |
| : sampling_frequency_in_hz_(sampling_frequency_in_hz), | |
| fill_with_zero_(false), | |
| random_generator_(1), | |
| max_amplitude_(max_amplitude), | |
| num_channels_(num_channels) { | |
| RTC_DCHECK_GT(max_amplitude, 0); | |
| } | |
| int SamplingFrequency() const override { return sampling_frequency_in_hz_; } | |
| int NumChannels() const override { return num_channels_; } | |
| bool Capture(BufferT<int16_t>* buffer) override { | |
| fill_with_zero_ = !fill_with_zero_; | |
| int16_t max_amplitude; | |
| { | |
| MutexLock lock(&lock_); | |
| max_amplitude = max_amplitude_; | |
| } | |
| buffer->SetData( | |
| TestAudioDeviceModule::SamplesPerFrame(sampling_frequency_in_hz_) * | |
| num_channels_, | |
| [&](std::span<int16_t> data) { | |
| if (fill_with_zero_) { | |
| std::fill(data.begin(), data.end(), 0); | |
| } else { | |
| std::generate(data.begin(), data.end(), [&]() { | |
| return random_generator_.Rand(-max_amplitude, max_amplitude); | |
| }); | |
| } | |
| return data.size(); | |
| }); | |
| return true; | |
| } | |
| void SetMaxAmplitude(int16_t amplitude) override { | |
| MutexLock lock(&lock_); | |
| max_amplitude_ = amplitude; | |
| } | |
| private: | |
| int sampling_frequency_in_hz_; | |
| bool fill_with_zero_; | |
| Random random_generator_; | |
| Mutex lock_; | |
| int16_t max_amplitude_ RTC_GUARDED_BY(lock_); | |
| const int num_channels_; | |
| }; | |
| class WavFileReader final : public TestAudioDeviceModule::Capturer { | |
| public: | |
| WavFileReader(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels, | |
| bool repeat) | |
| : WavFileReader(std::make_unique<WavReader>(filename), | |
| sampling_frequency_in_hz, | |
| num_channels, | |
| repeat) {} | |
| int SamplingFrequency() const override { return sampling_frequency_in_hz_; } | |
| int NumChannels() const override { return num_channels_; } | |
| bool Capture(BufferT<int16_t>* buffer) override { | |
| buffer->SetData( | |
| TestAudioDeviceModule::SamplesPerFrame(sampling_frequency_in_hz_) * | |
| num_channels_, | |
| [&](std::span<int16_t> data) { | |
| size_t read = wav_reader_->ReadSamples(data.size(), data.data()); | |
| if (read < data.size() && repeat_) { | |
| do { | |
| wav_reader_->Reset(); | |
| size_t delta = wav_reader_->ReadSamples( | |
| data.size() - read, data.subspan(read).data()); | |
| RTC_CHECK_GT(delta, 0) << "No new data read from file"; | |
| read += delta; | |
| } while (read < data.size()); | |
| } | |
| return read; | |
| }); | |
| return !buffer->empty(); | |
| } | |
| private: | |
| WavFileReader(std::unique_ptr<WavReader> wav_reader, | |
| int sampling_frequency_in_hz, | |
| int num_channels, | |
| bool repeat) | |
| : sampling_frequency_in_hz_(sampling_frequency_in_hz), | |
| num_channels_(num_channels), | |
| wav_reader_(std::move(wav_reader)), | |
| repeat_(repeat) { | |
| RTC_CHECK_EQ(wav_reader_->sample_rate(), sampling_frequency_in_hz); | |
| RTC_CHECK_EQ(wav_reader_->num_channels(), num_channels); | |
| } | |
| const int sampling_frequency_in_hz_; | |
| const int num_channels_; | |
| std::unique_ptr<WavReader> wav_reader_; | |
| const bool repeat_; | |
| }; | |
| class WavFileWriter final : public TestAudioDeviceModule::Renderer { | |
| public: | |
| WavFileWriter(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels) | |
| : WavFileWriter(std::make_unique<WavWriter>(filename, | |
| sampling_frequency_in_hz, | |
| num_channels), | |
| sampling_frequency_in_hz, | |
| num_channels) {} | |
| int SamplingFrequency() const override { return sampling_frequency_in_hz_; } | |
| int NumChannels() const override { return num_channels_; } | |
| bool Render(std::span<const int16_t> data) override { | |
| wav_writer_->WriteSamples(data.data(), data.size()); | |
| return true; | |
| } | |
| private: | |
| WavFileWriter(std::unique_ptr<WavWriter> wav_writer, | |
| int sampling_frequency_in_hz, | |
| int num_channels) | |
| : sampling_frequency_in_hz_(sampling_frequency_in_hz), | |
| wav_writer_(std::move(wav_writer)), | |
| num_channels_(num_channels) {} | |
| int sampling_frequency_in_hz_; | |
| std::unique_ptr<WavWriter> wav_writer_; | |
| const int num_channels_; | |
| }; | |
| class BoundedWavFileWriter : public TestAudioDeviceModule::Renderer { | |
| public: | |
| BoundedWavFileWriter(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels) | |
| : sampling_frequency_in_hz_(sampling_frequency_in_hz), | |
| wav_writer_(filename, sampling_frequency_in_hz, num_channels), | |
| num_channels_(num_channels), | |
| silent_audio_( | |
| TestAudioDeviceModule::SamplesPerFrame(sampling_frequency_in_hz) * | |
| num_channels, | |
| 0), | |
| started_writing_(false), | |
| trailing_zeros_(0) {} | |
| int SamplingFrequency() const override { return sampling_frequency_in_hz_; } | |
| int NumChannels() const override { return num_channels_; } | |
| bool Render(std::span<const int16_t> data) override { | |
| const int16_t kAmplitudeThreshold = 5; | |
| const int16_t* begin = data.data(); | |
| const int16_t* end = data.data() + data.size(); | |
| if (!started_writing_) { | |
| // Cut off silence at the beginning. | |
| while (begin < end) { | |
| if (std::abs(*begin) > kAmplitudeThreshold) { | |
| started_writing_ = true; | |
| break; | |
| } | |
| ++begin; | |
| } | |
| } | |
| if (started_writing_) { | |
| // Cut off silence at the end. | |
| while (begin < end) { | |
| if (std::abs(*(end - 1)) > kAmplitudeThreshold) { | |
| break; | |
| } | |
| --end; | |
| } | |
| if (begin < end) { | |
| // If it turns out that the silence was not final, need to write all the | |
| // skipped zeros and continue writing audio. | |
| while (trailing_zeros_ > 0) { | |
| const size_t zeros_to_write = | |
| std::min(trailing_zeros_, silent_audio_.size()); | |
| wav_writer_.WriteSamples(silent_audio_.data(), zeros_to_write); | |
| trailing_zeros_ -= zeros_to_write; | |
| } | |
| wav_writer_.WriteSamples(begin, end - begin); | |
| } | |
| // Save the number of zeros we skipped in case this needs to be restored. | |
| trailing_zeros_ += (data.data() + data.size()) - end; | |
| } | |
| return true; | |
| } | |
| private: | |
| int sampling_frequency_in_hz_; | |
| WavWriter wav_writer_; | |
| const int num_channels_; | |
| std::vector<int16_t> silent_audio_; | |
| bool started_writing_; | |
| size_t trailing_zeros_; | |
| }; | |
| class DiscardRenderer final : public TestAudioDeviceModule::Renderer { | |
| public: | |
| explicit DiscardRenderer(int sampling_frequency_in_hz, int num_channels) | |
| : sampling_frequency_in_hz_(sampling_frequency_in_hz), | |
| num_channels_(num_channels) {} | |
| int SamplingFrequency() const override { return sampling_frequency_in_hz_; } | |
| int NumChannels() const override { return num_channels_; } | |
| bool Render(std::span<const int16_t> /* data */) override { return true; } | |
| private: | |
| int sampling_frequency_in_hz_; | |
| const int num_channels_; | |
| }; | |
| class RawFileReader final : public TestAudioDeviceModule::Capturer { | |
| public: | |
| RawFileReader(absl::string_view input_file_name, | |
| int sampling_frequency_in_hz, | |
| int num_channels, | |
| bool repeat) | |
| : input_file_name_(input_file_name), | |
| sampling_frequency_in_hz_(sampling_frequency_in_hz), | |
| num_channels_(num_channels), | |
| repeat_(repeat), | |
| read_buffer_( | |
| TestAudioDeviceModule::SamplesPerFrame(sampling_frequency_in_hz) * | |
| num_channels * 2, | |
| 0) { | |
| input_file_ = FileWrapper::OpenReadOnly(input_file_name_); | |
| RTC_CHECK(input_file_.is_open()) | |
| << "Failed to open audio input file: " << input_file_name_; | |
| } | |
| ~RawFileReader() override { input_file_.Close(); } | |
| int SamplingFrequency() const override { return sampling_frequency_in_hz_; } | |
| int NumChannels() const override { return num_channels_; } | |
| bool Capture(BufferT<int16_t>* buffer) override { | |
| buffer->SetData( | |
| TestAudioDeviceModule::SamplesPerFrame(SamplingFrequency()) * | |
| NumChannels(), | |
| [&](std::span<int16_t> data) { | |
| std::span<int8_t> read_buffer_view = ReadBufferView(); | |
| size_t size = data.size() * 2; | |
| size_t read = input_file_.Read(read_buffer_view.data(), size); | |
| if (read < size && repeat_) { | |
| do { | |
| input_file_.Rewind(); | |
| size_t delta = input_file_.Read( | |
| read_buffer_view.subspan(read).data(), size - read); | |
| RTC_CHECK_GT(delta, 0) << "No new data to read from file"; | |
| read += delta; | |
| } while (read < size); | |
| } | |
| memcpy(data.data(), read_buffer_view.data(), size); | |
| return read / 2; | |
| }); | |
| return !buffer->empty(); | |
| } | |
| private: | |
| std::span<int8_t> ReadBufferView() { return read_buffer_; } | |
| const std::string input_file_name_; | |
| const int sampling_frequency_in_hz_; | |
| const int num_channels_; | |
| const bool repeat_; | |
| FileWrapper input_file_; | |
| std::vector<int8_t> read_buffer_; | |
| }; | |
| class RawFileWriter : public TestAudioDeviceModule::Renderer { | |
| public: | |
| RawFileWriter(absl::string_view output_file_name, | |
| int sampling_frequency_in_hz, | |
| int num_channels) | |
| : output_file_name_(output_file_name), | |
| sampling_frequency_in_hz_(sampling_frequency_in_hz), | |
| num_channels_(num_channels), | |
| silent_audio_( | |
| TestAudioDeviceModule::SamplesPerFrame(sampling_frequency_in_hz) * | |
| num_channels * 2, | |
| 0), | |
| write_buffer_( | |
| TestAudioDeviceModule::SamplesPerFrame(sampling_frequency_in_hz) * | |
| num_channels * 2, | |
| 0), | |
| started_writing_(false), | |
| trailing_zeros_(0) { | |
| output_file_ = FileWrapper::OpenWriteOnly(output_file_name_); | |
| RTC_CHECK(output_file_.is_open()) | |
| << "Failed to open playout file" << output_file_name_; | |
| } | |
| ~RawFileWriter() override { output_file_.Close(); } | |
| int SamplingFrequency() const override { return sampling_frequency_in_hz_; } | |
| int NumChannels() const override { return num_channels_; } | |
| bool Render(std::span<const int16_t> data) override { | |
| const int16_t kAmplitudeThreshold = 5; | |
| const int16_t* begin = data.data(); | |
| const int16_t* end = data.data() + data.size(); | |
| if (!started_writing_) { | |
| // Cut off silence at the beginning. | |
| while (begin < end) { | |
| if (std::abs(*begin) > kAmplitudeThreshold) { | |
| started_writing_ = true; | |
| break; | |
| } | |
| ++begin; | |
| } | |
| } | |
| if (started_writing_) { | |
| // Cut off silence at the end. | |
| while (begin < end) { | |
| if (*(end - 1) != 0) { | |
| break; | |
| } | |
| --end; | |
| } | |
| if (begin < end) { | |
| // If it turns out that the silence was not final, need to write all the | |
| // skipped zeros and continue writing audio. | |
| while (trailing_zeros_ > 0) { | |
| const size_t zeros_to_write = | |
| std::min(trailing_zeros_, silent_audio_.size()); | |
| output_file_.Write(silent_audio_.data(), zeros_to_write * 2); | |
| trailing_zeros_ -= zeros_to_write; | |
| } | |
| WriteInt16(begin, end); | |
| } | |
| // Save the number of zeros we skipped in case this needs to be restored. | |
| trailing_zeros_ += (data.data() + data.size()) - end; | |
| } | |
| return true; | |
| } | |
| private: | |
| void WriteInt16(const int16_t* begin, const int16_t* end) { | |
| int size = (end - begin) * sizeof(int16_t); | |
| memcpy(write_buffer_.data(), begin, size); | |
| output_file_.Write(write_buffer_.data(), size); | |
| } | |
| const std::string output_file_name_; | |
| const int sampling_frequency_in_hz_; | |
| const int num_channels_; | |
| FileWrapper output_file_; | |
| std::vector<int8_t> silent_audio_; | |
| std::vector<int8_t> write_buffer_; | |
| bool started_writing_; | |
| size_t trailing_zeros_; | |
| }; | |
| } // namespace | |
| size_t TestAudioDeviceModule::SamplesPerFrame(int sampling_frequency_in_hz) { | |
| return CheckedDivExact(sampling_frequency_in_hz, kFramesPerSecond); | |
| } | |
| scoped_refptr<AudioDeviceModule> TestAudioDeviceModule::Create( | |
| const Environment& env, | |
| std::unique_ptr<TestAudioDeviceModule::Capturer> capturer, | |
| std::unique_ptr<TestAudioDeviceModule::Renderer> renderer, | |
| float speed) { | |
| auto audio_device = make_ref_counted<AudioDeviceModuleImpl>( | |
| env, AudioDeviceModule::AudioLayer::kDummyAudio, | |
| std::make_unique<TestAudioDevice>(env, std::move(capturer), | |
| std::move(renderer), speed), | |
| /*create_detached=*/true); | |
| // Ensure that the current platform is supported. | |
| if (audio_device->CheckPlatform() == -1) { | |
| return nullptr; | |
| } | |
| // Create the platform-dependent implementation. | |
| if (audio_device->CreatePlatformSpecificObjects(env) == -1) { | |
| return nullptr; | |
| } | |
| // Ensure that the generic audio buffer can communicate with the platform | |
| // specific parts. | |
| if (audio_device->AttachAudioBuffer() == -1) { | |
| return nullptr; | |
| } | |
| return audio_device; | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::PulsedNoiseCapturer> | |
| TestAudioDeviceModule::CreatePulsedNoiseCapturer(int16_t max_amplitude, | |
| int sampling_frequency_in_hz, | |
| int num_channels) { | |
| return std::make_unique<PulsedNoiseCapturerImpl>( | |
| max_amplitude, sampling_frequency_in_hz, num_channels); | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::Renderer> | |
| TestAudioDeviceModule::CreateDiscardRenderer(int sampling_frequency_in_hz, | |
| int num_channels) { | |
| return std::make_unique<DiscardRenderer>(sampling_frequency_in_hz, | |
| num_channels); | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::Capturer> | |
| TestAudioDeviceModule::CreateWavFileReader(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels) { | |
| return std::make_unique<WavFileReader>(filename, sampling_frequency_in_hz, | |
| num_channels, false); | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::Capturer> | |
| TestAudioDeviceModule::CreateWavFileReader(absl::string_view filename, | |
| bool repeat) { | |
| WavReader reader(filename); | |
| int sampling_frequency_in_hz = reader.sample_rate(); | |
| int num_channels = checked_cast<int>(reader.num_channels()); | |
| return std::make_unique<WavFileReader>(filename, sampling_frequency_in_hz, | |
| num_channels, repeat); | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::Renderer> | |
| TestAudioDeviceModule::CreateWavFileWriter(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels) { | |
| return std::make_unique<WavFileWriter>(filename, sampling_frequency_in_hz, | |
| num_channels); | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::Renderer> | |
| TestAudioDeviceModule::CreateBoundedWavFileWriter(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels) { | |
| return std::make_unique<BoundedWavFileWriter>( | |
| filename, sampling_frequency_in_hz, num_channels); | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::Capturer> | |
| TestAudioDeviceModule::CreateRawFileReader(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels, | |
| bool repeat) { | |
| return std::make_unique<RawFileReader>(filename, sampling_frequency_in_hz, | |
| num_channels, repeat); | |
| } | |
| std::unique_ptr<TestAudioDeviceModule::Renderer> | |
| TestAudioDeviceModule::CreateRawFileWriter(absl::string_view filename, | |
| int sampling_frequency_in_hz, | |
| int num_channels) { | |
| return std::make_unique<RawFileWriter>(filename, sampling_frequency_in_hz, | |
| num_channels); | |
| } | |
| } // namespace webrtc |