| /* |
| * Copyright (c) 2012 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 <stdio.h> |
| |
| #include <string> |
| |
| #include "webrtc/test/testsupport/fileutils.h" |
| #include "webrtc/voice_engine/test/auto_test/fixtures/after_initialization_fixture.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| const int16_t kLimiterHeadroom = 29204; // == -1 dbFS |
| const int16_t kInt16Max = 0x7fff; |
| const int kSampleRateHz = 16000; |
| const int kTestDurationMs = 3000; |
| const int kSkipOutputMs = 500; |
| |
| } // namespace |
| |
| class MixingTest : public AfterInitializationFixture { |
| protected: |
| MixingTest() |
| : input_filename_(test::OutputPath() + "mixing_test_input.pcm"), |
| output_filename_(test::OutputPath() + "mixing_test_output.pcm") { |
| } |
| void SetUp() { |
| transport_ = new LoopBackTransport(voe_network_); |
| } |
| void TearDown() { |
| delete transport_; |
| } |
| |
| // Creates and mixes |num_remote_streams| which play a file "as microphone" |
| // with |num_local_streams| which play a file "locally", using a constant |
| // amplitude of |input_value|. The local streams manifest as "anonymous" |
| // mixing participants, meaning they will be mixed regardless of the number |
| // of participants. (A stream is a VoiceEngine "channel"). |
| // |
| // The mixed output is verified to always fall between |max_output_value| and |
| // |min_output_value|, after a startup phase. |
| // |
| // |num_remote_streams_using_mono| of the remote streams use mono, with the |
| // remainder using stereo. |
| void RunMixingTest(int num_remote_streams, |
| int num_local_streams, |
| int num_remote_streams_using_mono, |
| int16_t input_value, |
| int16_t max_output_value, |
| int16_t min_output_value) { |
| ASSERT_LE(num_remote_streams_using_mono, num_remote_streams); |
| |
| GenerateInputFile(input_value); |
| |
| std::vector<int> local_streams(num_local_streams); |
| for (size_t i = 0; i < local_streams.size(); ++i) { |
| local_streams[i] = voe_base_->CreateChannel(); |
| EXPECT_NE(-1, local_streams[i]); |
| } |
| StartLocalStreams(local_streams); |
| TEST_LOG("Playing %d local streams.\n", num_local_streams); |
| |
| std::vector<int> remote_streams(num_remote_streams); |
| for (size_t i = 0; i < remote_streams.size(); ++i) { |
| remote_streams[i] = voe_base_->CreateChannel(); |
| EXPECT_NE(-1, remote_streams[i]); |
| } |
| StartRemoteStreams(remote_streams, num_remote_streams_using_mono); |
| TEST_LOG("Playing %d remote streams.\n", num_remote_streams); |
| |
| // Start recording the mixed output and wait. |
| EXPECT_EQ(0, voe_file_->StartRecordingPlayout(-1 /* record meeting */, |
| output_filename_.c_str())); |
| Sleep(kTestDurationMs); |
| EXPECT_EQ(0, voe_file_->StopRecordingPlayout(-1)); |
| |
| StopLocalStreams(local_streams); |
| StopRemoteStreams(remote_streams); |
| |
| VerifyMixedOutput(max_output_value, min_output_value); |
| } |
| |
| private: |
| // Generate input file with constant values equal to |input_value|. The file |
| // will be one second longer than the duration of the test. |
| void GenerateInputFile(int16_t input_value) { |
| FILE* input_file = fopen(input_filename_.c_str(), "wb"); |
| ASSERT_TRUE(input_file != NULL); |
| for (int i = 0; i < kSampleRateHz / 1000 * (kTestDurationMs + 1000); i++) { |
| ASSERT_EQ(1u, fwrite(&input_value, sizeof(input_value), 1, input_file)); |
| } |
| ASSERT_EQ(0, fclose(input_file)); |
| } |
| |
| void VerifyMixedOutput(int16_t max_output_value, int16_t min_output_value) { |
| // Verify the mixed output. |
| FILE* output_file = fopen(output_filename_.c_str(), "rb"); |
| ASSERT_TRUE(output_file != NULL); |
| int16_t output_value = 0; |
| // Skip the first segment to avoid initialization and ramping-in effects. |
| EXPECT_EQ(0, fseek(output_file, sizeof(output_value) * |
| kSampleRateHz / 1000 * kSkipOutputMs, SEEK_SET)); |
| int samples_read = 0; |
| while (fread(&output_value, sizeof(output_value), 1, output_file) == 1) { |
| samples_read++; |
| std::ostringstream trace_stream; |
| trace_stream << samples_read << " samples read"; |
| SCOPED_TRACE(trace_stream.str()); |
| EXPECT_LE(output_value, max_output_value); |
| EXPECT_GE(output_value, min_output_value); |
| } |
| // Ensure the recording length is close to the duration of the test. |
| // We have to use a relaxed tolerance here due to filesystem flakiness on |
| // the bots. |
| ASSERT_GE((samples_read * 1000.0) / kSampleRateHz, |
| 0.7 * (kTestDurationMs - kSkipOutputMs)); |
| // Ensure we read the entire file. |
| ASSERT_NE(0, feof(output_file)); |
| ASSERT_EQ(0, fclose(output_file)); |
| } |
| |
| // Start up local streams ("anonymous" participants). |
| void StartLocalStreams(const std::vector<int>& streams) { |
| for (size_t i = 0; i < streams.size(); ++i) { |
| EXPECT_EQ(0, voe_base_->StartPlayout(streams[i])); |
| EXPECT_EQ(0, voe_file_->StartPlayingFileLocally(streams[i], |
| input_filename_.c_str(), true)); |
| } |
| } |
| |
| void StopLocalStreams(const std::vector<int>& streams) { |
| for (size_t i = 0; i < streams.size(); ++i) { |
| EXPECT_EQ(0, voe_base_->StopPlayout(streams[i])); |
| EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i])); |
| } |
| } |
| |
| // Start up remote streams ("normal" participants). |
| void StartRemoteStreams(const std::vector<int>& streams, |
| int num_remote_streams_using_mono) { |
| // Use L16 at 16kHz to minimize distortion (file recording is 16kHz and |
| // resampling will cause distortion). |
| CodecInst codec_inst; |
| strcpy(codec_inst.plname, "L16"); |
| codec_inst.channels = 1; |
| codec_inst.plfreq = kSampleRateHz; |
| codec_inst.pltype = 105; |
| codec_inst.pacsize = codec_inst.plfreq / 100; |
| codec_inst.rate = codec_inst.plfreq * sizeof(int16_t) * 8; // 8 bits/byte. |
| |
| for (int i = 0; i < num_remote_streams_using_mono; ++i) { |
| StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i); |
| } |
| |
| // The remainder of the streams will use stereo. |
| codec_inst.channels = 2; |
| codec_inst.pltype++; |
| for (size_t i = num_remote_streams_using_mono; i < streams.size(); ++i) { |
| StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i); |
| } |
| } |
| |
| // Start up a single remote stream. |
| void StartRemoteStream(int stream, const CodecInst& codec_inst, int port) { |
| EXPECT_EQ(0, voe_codec_->SetRecPayloadType(stream, codec_inst)); |
| EXPECT_EQ(0, voe_network_->RegisterExternalTransport(stream, *transport_)); |
| EXPECT_EQ(0, voe_base_->StartReceive(stream)); |
| EXPECT_EQ(0, voe_base_->StartPlayout(stream)); |
| EXPECT_EQ(0, voe_codec_->SetSendCodec(stream, codec_inst)); |
| EXPECT_EQ(0, voe_base_->StartSend(stream)); |
| EXPECT_EQ(0, voe_file_->StartPlayingFileAsMicrophone(stream, |
| input_filename_.c_str(), true)); |
| } |
| |
| void StopRemoteStreams(const std::vector<int>& streams) { |
| for (size_t i = 0; i < streams.size(); ++i) { |
| EXPECT_EQ(0, voe_base_->StopSend(streams[i])); |
| EXPECT_EQ(0, voe_base_->StopPlayout(streams[i])); |
| EXPECT_EQ(0, voe_base_->StopReceive(streams[i])); |
| EXPECT_EQ(0, voe_network_->DeRegisterExternalTransport(streams[i])); |
| EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i])); |
| } |
| } |
| |
| const std::string input_filename_; |
| const std::string output_filename_; |
| LoopBackTransport* transport_; |
| }; |
| |
| // These tests assume a maximum of three mixed participants. We typically allow |
| // a +/- 10% range around the expected output level to account for distortion |
| // from coding and processing in the loopback chain. |
| TEST_F(MixingTest, DISABLED_FourChannelsWithOnlyThreeMixed) { |
| const int16_t kInputValue = 1000; |
| const int16_t kExpectedOutput = kInputValue * 3; |
| RunMixingTest(4, 0, 4, kInputValue, 1.1 * kExpectedOutput, |
| 0.9 * kExpectedOutput); |
| } |
| |
| // Ensure the mixing saturation protection is working. We can do this because |
| // the mixing limiter is given some headroom, so the expected output is less |
| // than full scale. |
| TEST_F(MixingTest, DISABLED_VerifySaturationProtection) { |
| const int16_t kInputValue = 20000; |
| const int16_t kExpectedOutput = kLimiterHeadroom; |
| // If this isn't satisfied, we're not testing anything. |
| ASSERT_GT(kInputValue * 3, kInt16Max); |
| ASSERT_LT(1.1 * kExpectedOutput, kInt16Max); |
| RunMixingTest(3, 0, 3, kInputValue, 1.1 * kExpectedOutput, |
| 0.9 * kExpectedOutput); |
| } |
| |
| TEST_F(MixingTest, DISABLED_SaturationProtectionHasNoEffectOnOneChannel) { |
| const int16_t kInputValue = kInt16Max; |
| const int16_t kExpectedOutput = kInt16Max; |
| // If this isn't satisfied, we're not testing anything. |
| ASSERT_GT(0.95 * kExpectedOutput, kLimiterHeadroom); |
| // Tighter constraints are required here to properly test this. |
| RunMixingTest(1, 0, 1, kInputValue, kExpectedOutput, |
| 0.95 * kExpectedOutput); |
| } |
| |
| TEST_F(MixingTest, DISABLED_VerifyAnonymousAndNormalParticipantMixing) { |
| const int16_t kInputValue = 1000; |
| const int16_t kExpectedOutput = kInputValue * 2; |
| RunMixingTest(1, 1, 1, kInputValue, 1.1 * kExpectedOutput, |
| 0.9 * kExpectedOutput); |
| } |
| |
| TEST_F(MixingTest, DISABLED_AnonymousParticipantsAreAlwaysMixed) { |
| const int16_t kInputValue = 1000; |
| const int16_t kExpectedOutput = kInputValue * 4; |
| RunMixingTest(3, 1, 3, kInputValue, 1.1 * kExpectedOutput, |
| 0.9 * kExpectedOutput); |
| } |
| |
| TEST_F(MixingTest, DISABLED_VerifyStereoAndMonoMixing) { |
| const int16_t kInputValue = 1000; |
| const int16_t kExpectedOutput = kInputValue * 2; |
| RunMixingTest(2, 0, 1, kInputValue, 1.1 * kExpectedOutput, |
| 0.9 * kExpectedOutput); |
| } |
| |
| } // namespace webrtc |