From 153056b0595cc058afa28e26d28c261d0174ebb0 Mon Sep 17 00:00:00 2001 From: Artem Titov Date: Tue, 16 Apr 2019 16:49:32 +0200 Subject: [PATCH] Add ability to play audio in circle for TestAudioDevice wav file capturer Also use this ability in PC smoke test. Bug: webrtc:10138 Change-Id: I83d526344f203082a19377d9642c9e453454f7ad Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/133163 Reviewed-by: Niels Moller Reviewed-by: Mirko Bonadei Reviewed-by: Henrik Andreassson Commit-Queue: Artem Titov Cr-Commit-Position: refs/heads/master@{#27649} --- common_audio/wav_file.cc | 8 ++ common_audio/wav_file.h | 4 + common_audio/wav_file_unittest.cc | 75 +++++++++++++++++++ .../audio_device/include/test_audio_device.cc | 46 ++++++++---- .../audio_device/include/test_audio_device.h | 10 ++- .../include/test_audio_device_unittest.cc | 40 ++++++++++ test/pc/e2e/peer_connection_e2e_smoke_test.cc | 6 ++ test/pc/e2e/test_peer.cc | 2 +- 8 files changed, 174 insertions(+), 17 deletions(-) diff --git a/common_audio/wav_file.cc b/common_audio/wav_file.cc index 1b8bcbd923..77b46e72e1 100644 --- a/common_audio/wav_file.cc +++ b/common_audio/wav_file.cc @@ -74,12 +74,20 @@ WavReader::WavReader(rtc::PlatformFile file) { num_samples_remaining_ = num_samples_; RTC_CHECK_EQ(kWavFormat, format); RTC_CHECK_EQ(kBytesPerSample, bytes_per_sample); + RTC_CHECK_EQ(0, fgetpos(file_handle_, &data_start_pos_)) + << "Failed to get WAV data position from file"; } WavReader::~WavReader() { Close(); } +void WavReader::Reset() { + RTC_CHECK_EQ(0, fsetpos(file_handle_, &data_start_pos_)) + << "Failed to set position in the file to WAV data start position"; + num_samples_remaining_ = num_samples_; +} + int WavReader::sample_rate() const { return sample_rate_; } diff --git a/common_audio/wav_file.h b/common_audio/wav_file.h index 7e790e0fc7..d7aa2c1c03 100644 --- a/common_audio/wav_file.h +++ b/common_audio/wav_file.h @@ -77,6 +77,9 @@ class WavReader final : public WavFile { // Close the WAV file. ~WavReader() override; + // Resets position to the beginning of the file. + void Reset(); + // Returns the number of samples read. If this is less than requested, // verifies that the end of the file was reached. size_t ReadSamples(size_t num_samples, float* samples); @@ -93,6 +96,7 @@ class WavReader final : public WavFile { size_t num_samples_; // Total number of samples in the file. size_t num_samples_remaining_; FILE* file_handle_; // Input file, owned by this class. + fpos_t data_start_pos_; // Position in the file immediately after WAV header. RTC_DISALLOW_COPY_AND_ASSIGN(WavReader); }; diff --git a/common_audio/wav_file_unittest.cc b/common_audio/wav_file_unittest.cc index b7e5d3fa3e..fef87c8169 100644 --- a/common_audio/wav_file_unittest.cc +++ b/common_audio/wav_file_unittest.cc @@ -24,9 +24,11 @@ #if defined(WEBRTC_MAC) #define MAYBE_CPP DISABLED_CPP #define MAYBE_CPPFileDescriptor DISABLED_CPPFileDescriptor +#define MAYBE_CPPReset DISABLED_CPPReset #else #define MAYBE_CPP CPP #define MAYBE_CPPFileDescriptor CPPFileDescriptor +#define MAYBE_CPPReset CPPReset #endif namespace webrtc { @@ -259,4 +261,77 @@ TEST(WavWriterTest, MAYBE_CPPFileDescriptor) { } } +// Write a tiny WAV file with the C++ interface then read-reset-read. +TEST(WavReaderTest, MAYBE_CPPReset) { + const std::string outfile = test::OutputPath() + "wavtest4.wav"; + static const size_t kNumSamples = 3; + { + WavWriter w(outfile, 14099, 1); + EXPECT_EQ(14099, w.sample_rate()); + EXPECT_EQ(1u, w.num_channels()); + EXPECT_EQ(0u, w.num_samples()); + w.WriteSamples(kSamples, kNumSamples); + EXPECT_EQ(kNumSamples, w.num_samples()); + } + // Write some extra "metadata" to the file that should be silently ignored + // by WavReader. We don't use WavWriter directly for this because it doesn't + // support metadata. + static const uint8_t kMetadata[] = {101, 202}; + { + FILE* f = fopen(outfile.c_str(), "ab"); + ASSERT_TRUE(f); + ASSERT_EQ(1u, fwrite(kMetadata, sizeof(kMetadata), 1, f)); + fclose(f); + } + static const uint8_t kExpectedContents[] = { + // clang-format off + // clang formatting doesn't respect inline comments. + 'R', 'I', 'F', 'F', + 42, 0, 0, 0, // size of whole file - 8: 6 + 44 - 8 + 'W', 'A', 'V', 'E', + 'f', 'm', 't', ' ', + 16, 0, 0, 0, // size of fmt block - 8: 24 - 8 + 1, 0, // format: PCM (1) + 1, 0, // channels: 1 + 0x13, 0x37, 0, 0, // sample rate: 14099 + 0x26, 0x6e, 0, 0, // byte rate: 2 * 14099 + 2, 0, // block align: NumChannels * BytesPerSample + 16, 0, // bits per sample: 2 * 8 + 'd', 'a', 't', 'a', + 6, 0, 0, 0, // size of payload: 6 + 0, 0, // first sample: 0.0 + 10, 0, // second sample: 10.0 + 0xff, 0x7f, // third sample: 4e4 (saturated) + kMetadata[0], kMetadata[1], + // clang-format on + }; + static const size_t kContentSize = + kWavHeaderSize + kNumSamples * sizeof(int16_t) + sizeof(kMetadata); + static_assert(sizeof(kExpectedContents) == kContentSize, "content size"); + EXPECT_EQ(kContentSize, test::GetFileSize(outfile)); + FILE* f = fopen(outfile.c_str(), "rb"); + ASSERT_TRUE(f); + uint8_t contents[kContentSize]; + ASSERT_EQ(1u, fread(contents, kContentSize, 1, f)); + EXPECT_EQ(0, fclose(f)); + EXPECT_EQ(0, memcmp(kExpectedContents, contents, kContentSize)); + + { + WavReader r(outfile); + EXPECT_EQ(14099, r.sample_rate()); + EXPECT_EQ(1u, r.num_channels()); + EXPECT_EQ(kNumSamples, r.num_samples()); + static const float kTruncatedSamples[] = {0.0, 10.0, 32767.0}; + float samples[kNumSamples]; + EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, samples)); + EXPECT_EQ(0, memcmp(kTruncatedSamples, samples, sizeof(samples))); + EXPECT_EQ(0u, r.ReadSamples(kNumSamples, samples)); + + r.Reset(); + EXPECT_EQ(kNumSamples, r.ReadSamples(kNumSamples, samples)); + EXPECT_EQ(0, memcmp(kTruncatedSamples, samples, sizeof(samples))); + EXPECT_EQ(0u, r.ReadSamples(kNumSamples, samples)); + } +} + } // namespace webrtc diff --git a/modules/audio_device/include/test_audio_device.cc b/modules/audio_device/include/test_audio_device.cc index 8e439e148e..5ff5387287 100644 --- a/modules/audio_device/include/test_audio_device.cc +++ b/modules/audio_device/include/test_audio_device.cc @@ -269,17 +269,21 @@ class WavFileReader final : public TestAudioDeviceModule::Capturer { public: WavFileReader(std::string filename, int sampling_frequency_in_hz, - int num_channels) + int num_channels, + bool repeat) : WavFileReader(absl::make_unique(filename), sampling_frequency_in_hz, - num_channels) {} + num_channels, + repeat) {} WavFileReader(rtc::PlatformFile file, int sampling_frequency_in_hz, - int num_channels) + int num_channels, + bool repeat) : WavFileReader(absl::make_unique(file), sampling_frequency_in_hz, - num_channels) {} + num_channels, + repeat) {} int SamplingFrequency() const override { return sampling_frequency_in_hz_; } @@ -290,7 +294,17 @@ class WavFileReader final : public TestAudioDeviceModule::Capturer { TestAudioDeviceModule::SamplesPerFrame(sampling_frequency_in_hz_) * num_channels_, [&](rtc::ArrayView data) { - return wav_reader_->ReadSamples(data.size(), data.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.subview(read).data()); + RTC_CHECK_GT(delta, 0) << "No new data read from file"; + read += delta; + } while (read < data.size()); + } + return read; }); return buffer->size() > 0; } @@ -298,17 +312,20 @@ class WavFileReader final : public TestAudioDeviceModule::Capturer { private: WavFileReader(std::unique_ptr wav_reader, int sampling_frequency_in_hz, - int num_channels) + int num_channels, + bool repeat) : sampling_frequency_in_hz_(sampling_frequency_in_hz), num_channels_(num_channels), - wav_reader_(std::move(wav_reader)) { + 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); } - int sampling_frequency_in_hz_; + const int sampling_frequency_in_hz_; const int num_channels_; std::unique_ptr wav_reader_; + const bool repeat_; }; class WavFileWriter final : public TestAudioDeviceModule::Renderer { @@ -495,16 +512,16 @@ TestAudioDeviceModule::CreateWavFileReader(std::string filename, int sampling_frequency_in_hz, int num_channels) { return absl::make_unique(filename, sampling_frequency_in_hz, - num_channels); + num_channels, false); } std::unique_ptr -TestAudioDeviceModule::CreateWavFileReader(std::string filename) { +TestAudioDeviceModule::CreateWavFileReader(std::string filename, bool repeat) { WavReader reader(filename); int sampling_frequency_in_hz = reader.sample_rate(); int num_channels = rtc::checked_cast(reader.num_channels()); return absl::make_unique(filename, sampling_frequency_in_hz, - num_channels); + num_channels, repeat); } std::unique_ptr @@ -528,16 +545,17 @@ TestAudioDeviceModule::CreateWavFileReader(rtc::PlatformFile file, int sampling_frequency_in_hz, int num_channels) { return absl::make_unique(file, sampling_frequency_in_hz, - num_channels); + num_channels, false); } std::unique_ptr -TestAudioDeviceModule::CreateWavFileReader(rtc::PlatformFile file) { +TestAudioDeviceModule::CreateWavFileReader(rtc::PlatformFile file, + bool repeat) { WavReader reader(file); int sampling_frequency_in_hz = reader.sample_rate(); int num_channels = rtc::checked_cast(reader.num_channels()); return absl::make_unique(file, sampling_frequency_in_hz, - num_channels); + num_channels, repeat); } std::unique_ptr diff --git a/modules/audio_device/include/test_audio_device.h b/modules/audio_device/include/test_audio_device.h index c7ef7687c3..586996ee89 100644 --- a/modules/audio_device/include/test_audio_device.h +++ b/modules/audio_device/include/test_audio_device.h @@ -113,7 +113,10 @@ class TestAudioDeviceModule : public AudioDeviceModule { // Returns a Capturer instance that gets its data from a file. // Automatically detects sample rate and num of channels. - static std::unique_ptr CreateWavFileReader(std::string filename); + // |repeat| - if true, the file will be replayed from the start when we reach + // the end of file. + static std::unique_ptr CreateWavFileReader(std::string filename, + bool repeat = false); // Returns a Renderer instance that writes its data to a file. static std::unique_ptr CreateWavFileWriter( @@ -140,7 +143,10 @@ class TestAudioDeviceModule : public AudioDeviceModule { // Returns a Capturer instance that gets its data from a file. // Automatically detects sample rate and num of channels. - static std::unique_ptr CreateWavFileReader(rtc::PlatformFile file); + // |repeat| - if true, the file will be replayed from the start when we reach + // the end of file. + static std::unique_ptr CreateWavFileReader(rtc::PlatformFile file, + bool repeat = false); // Returns a Renderer instance that writes its data to a file. static std::unique_ptr CreateWavFileWriter( diff --git a/modules/audio_device/include/test_audio_device_unittest.cc b/modules/audio_device/include/test_audio_device_unittest.cc index db3260e214..8038b95350 100644 --- a/modules/audio_device/include/test_audio_device_unittest.cc +++ b/modules/audio_device/include/test_audio_device_unittest.cc @@ -173,6 +173,46 @@ TEST(BoundedWavFileWriterTest, EndSilenceCutoff) { RunTest(kInputSamples, kExpectedSamples, 8); } +TEST(WavFileReaderTest, RepeatedTrueWithSingleFrameFileReadTwice) { + static const std::vector kInputSamples = {75, 1234, 243, -1231, + -22222, 0, 3, 88}; + static const rtc::BufferT kExpectedSamples(kInputSamples.data(), + kInputSamples.size()); + + const std::string output_filename = test::OutputPath() + + "WavFileReaderTest_RepeatedTrue_" + + std::to_string(std::rand()) + ".wav"; + + static const size_t kSamplesPerFrame = 8; + static const int kSampleRate = kSamplesPerFrame * 100; + EXPECT_EQ(TestAudioDeviceModule::SamplesPerFrame(kSampleRate), + kSamplesPerFrame); + + // Create wav file to read. + { + std::unique_ptr writer = + TestAudioDeviceModule::CreateWavFileWriter(output_filename, 800); + + for (size_t i = 0; i < kInputSamples.size(); i += kSamplesPerFrame) { + EXPECT_TRUE(writer->Render(rtc::ArrayView( + &kInputSamples[i], + std::min(kSamplesPerFrame, kInputSamples.size() - i)))); + } + } + + { + std::unique_ptr reader = + TestAudioDeviceModule::CreateWavFileReader(output_filename, true); + rtc::BufferT buffer(kExpectedSamples.size()); + EXPECT_TRUE(reader->Capture(&buffer)); + EXPECT_EQ(kExpectedSamples, buffer); + EXPECT_TRUE(reader->Capture(&buffer)); + EXPECT_EQ(kExpectedSamples, buffer); + } + + remove(output_filename.c_str()); +} + TEST(PulsedNoiseCapturerTest, SetMaxAmplitude) { const int16_t kAmplitude = 50; std::unique_ptr capturer = diff --git a/test/pc/e2e/peer_connection_e2e_smoke_test.cc b/test/pc/e2e/peer_connection_e2e_smoke_test.cc index 37388a4f70..f32e9725d3 100644 --- a/test/pc/e2e/peer_connection_e2e_smoke_test.cc +++ b/test/pc/e2e/peer_connection_e2e_smoke_test.cc @@ -85,6 +85,9 @@ TEST(PeerConnectionE2EQualityTestSmokeTest, RunWithEmulatedNetwork) { alice->AddVideoConfig(std::move(video_config)); AudioConfig audio_config; audio_config.stream_label = "alice-audio"; + audio_config.mode = AudioConfig::Mode::kFile; + audio_config.input_file_name = test::ResourcePath( + "pc_quality_smoke_test_alice_source", "wav"); alice->SetAudioConfig(std::move(audio_config)); }); @@ -98,6 +101,9 @@ TEST(PeerConnectionE2EQualityTestSmokeTest, RunWithEmulatedNetwork) { bob->AddVideoConfig(std::move(video_config)); AudioConfig audio_config; audio_config.stream_label = "bob-audio"; + audio_config.mode = AudioConfig::Mode::kFile; + audio_config.input_file_name = test::ResourcePath( + "pc_quality_smoke_test_bob_source", "wav"); bob->SetAudioConfig(std::move(audio_config)); }); diff --git a/test/pc/e2e/test_peer.cc b/test/pc/e2e/test_peer.cc index d5e43799b8..33c1036a8c 100644 --- a/test/pc/e2e/test_peer.cc +++ b/test/pc/e2e/test_peer.cc @@ -112,7 +112,7 @@ struct TestPeerComponents { if (audio_config.mode == AudioConfig::Mode::kFile) { RTC_DCHECK(audio_config.input_file_name); return TestAudioDeviceModule::CreateWavFileReader( - audio_config.input_file_name.value()); + audio_config.input_file_name.value(), /*repeat=*/true); } RTC_NOTREACHED() << "Unknown audio_config->mode"; return nullptr;