1 /* 2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include <stdio.h> 12 #include <string> 13 14 #include "webrtc/system_wrappers/include/sleep.h" 15 #include "webrtc/test/testsupport/fileutils.h" 16 #include "webrtc/voice_engine/test/auto_test/fixtures/after_initialization_fixture.h" 17 18 namespace webrtc { 19 namespace { 20 21 const int16_t kLimiterHeadroom = 29204; // == -1 dbFS 22 const int16_t kInt16Max = 0x7fff; 23 const int kPayloadType = 105; 24 const int kInSampleRateHz = 16000; // Input file taken as 16 kHz by default. 25 const int kRecSampleRateHz = 16000; // Recorded with 16 kHz L16. 26 const int kTestDurationMs = 3000; 27 const CodecInst kCodecL16 = {kPayloadType, "L16", 16000, 160, 1, 256000}; 28 const CodecInst kCodecOpus = {kPayloadType, "opus", 48000, 960, 1, 32000}; 29 30 } // namespace 31 32 class MixingTest : public AfterInitializationFixture { 33 protected: 34 MixingTest() 35 : output_filename_(test::OutputPath() + "mixing_test_output.pcm") { 36 } 37 void SetUp() { 38 transport_ = new LoopBackTransport(voe_network_, 0); 39 } 40 void TearDown() { 41 delete transport_; 42 } 43 44 // Creates and mixes |num_remote_streams| which play a file "as microphone" 45 // with |num_local_streams| which play a file "locally", using a constant 46 // amplitude of |input_value|. The local streams manifest as "anonymous" 47 // mixing participants, meaning they will be mixed regardless of the number 48 // of participants. (A stream is a VoiceEngine "channel"). 49 // 50 // The mixed output is verified to always fall between |max_output_value| and 51 // |min_output_value|, after a startup phase. 52 // 53 // |num_remote_streams_using_mono| of the remote streams use mono, with the 54 // remainder using stereo. 55 void RunMixingTest(int num_remote_streams, 56 int num_local_streams, 57 int num_remote_streams_using_mono, 58 bool real_audio, 59 int16_t input_value, 60 int16_t max_output_value, 61 int16_t min_output_value, 62 const CodecInst& codec_inst) { 63 ASSERT_LE(num_remote_streams_using_mono, num_remote_streams); 64 65 if (real_audio) { 66 input_filename_ = test::ResourcePath("voice_engine/audio_long16", "pcm"); 67 } else { 68 input_filename_ = test::OutputPath() + "mixing_test_input.pcm"; 69 GenerateInputFile(input_value); 70 } 71 72 std::vector<int> local_streams(num_local_streams); 73 for (size_t i = 0; i < local_streams.size(); ++i) { 74 local_streams[i] = voe_base_->CreateChannel(); 75 EXPECT_NE(-1, local_streams[i]); 76 } 77 StartLocalStreams(local_streams); 78 TEST_LOG("Playing %d local streams.\n", num_local_streams); 79 80 std::vector<int> remote_streams(num_remote_streams); 81 for (size_t i = 0; i < remote_streams.size(); ++i) { 82 remote_streams[i] = voe_base_->CreateChannel(); 83 EXPECT_NE(-1, remote_streams[i]); 84 } 85 StartRemoteStreams(remote_streams, num_remote_streams_using_mono, 86 codec_inst); 87 TEST_LOG("Playing %d remote streams.\n", num_remote_streams); 88 89 // Give it plenty of time to get started. 90 SleepMs(1000); 91 92 // Start recording the mixed output and wait. 93 EXPECT_EQ(0, voe_file_->StartRecordingPlayout(-1 /* record meeting */, 94 output_filename_.c_str())); 95 SleepMs(kTestDurationMs); 96 while (GetFileDurationMs(output_filename_.c_str()) < kTestDurationMs) { 97 SleepMs(200); 98 } 99 EXPECT_EQ(0, voe_file_->StopRecordingPlayout(-1)); 100 101 StopLocalStreams(local_streams); 102 StopRemoteStreams(remote_streams); 103 104 if (!real_audio) { 105 VerifyMixedOutput(max_output_value, min_output_value); 106 } 107 } 108 109 private: 110 // Generate input file with constant values equal to |input_value|. The file 111 // will be twice the duration of the test. 112 void GenerateInputFile(int16_t input_value) { 113 FILE* input_file = fopen(input_filename_.c_str(), "wb"); 114 ASSERT_TRUE(input_file != NULL); 115 for (int i = 0; i < kInSampleRateHz / 1000 * (kTestDurationMs * 2); i++) { 116 ASSERT_EQ(1u, fwrite(&input_value, sizeof(input_value), 1, input_file)); 117 } 118 ASSERT_EQ(0, fclose(input_file)); 119 } 120 121 void VerifyMixedOutput(int16_t max_output_value, int16_t min_output_value) { 122 // Verify the mixed output. 123 FILE* output_file = fopen(output_filename_.c_str(), "rb"); 124 ASSERT_TRUE(output_file != NULL); 125 int16_t output_value = 0; 126 int samples_read = 0; 127 while (fread(&output_value, sizeof(output_value), 1, output_file) == 1) { 128 samples_read++; 129 std::ostringstream trace_stream; 130 trace_stream << samples_read << " samples read"; 131 SCOPED_TRACE(trace_stream.str()); 132 EXPECT_LE(output_value, max_output_value); 133 EXPECT_GE(output_value, min_output_value); 134 } 135 // Ensure we've at least recorded half as much file as the duration of the 136 // test. We have to use a relaxed tolerance here due to filesystem flakiness 137 // on the bots. 138 ASSERT_GE((samples_read * 1000.0) / kRecSampleRateHz, kTestDurationMs); 139 // Ensure we read the entire file. 140 ASSERT_NE(0, feof(output_file)); 141 ASSERT_EQ(0, fclose(output_file)); 142 } 143 144 // Start up local streams ("anonymous" participants). 145 void StartLocalStreams(const std::vector<int>& streams) { 146 for (size_t i = 0; i < streams.size(); ++i) { 147 EXPECT_EQ(0, voe_base_->StartPlayout(streams[i])); 148 EXPECT_EQ(0, voe_file_->StartPlayingFileLocally(streams[i], 149 input_filename_.c_str(), true)); 150 } 151 } 152 153 void StopLocalStreams(const std::vector<int>& streams) { 154 for (size_t i = 0; i < streams.size(); ++i) { 155 EXPECT_EQ(0, voe_base_->StopPlayout(streams[i])); 156 EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i])); 157 } 158 } 159 160 // Start up remote streams ("normal" participants). 161 void StartRemoteStreams(const std::vector<int>& streams, 162 int num_remote_streams_using_mono, 163 const CodecInst& codec_inst) { 164 for (int i = 0; i < num_remote_streams_using_mono; ++i) { 165 // Add some delay between starting up the channels in order to give them 166 // different energies in the "real audio" test and hopefully exercise 167 // more code paths. 168 SleepMs(50); 169 StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i); 170 } 171 172 // The remainder of the streams will use stereo. 173 CodecInst codec_inst_stereo = codec_inst; 174 codec_inst_stereo.channels = 2; 175 codec_inst_stereo.pltype++; 176 for (size_t i = num_remote_streams_using_mono; i < streams.size(); ++i) { 177 StartRemoteStream(streams[i], codec_inst_stereo, 1234 + 2 * i); 178 } 179 } 180 181 // Start up a single remote stream. 182 void StartRemoteStream(int stream, const CodecInst& codec_inst, int port) { 183 EXPECT_EQ(0, voe_codec_->SetRecPayloadType(stream, codec_inst)); 184 EXPECT_EQ(0, voe_network_->RegisterExternalTransport(stream, *transport_)); 185 EXPECT_EQ(0, voe_rtp_rtcp_->SetLocalSSRC( 186 stream, static_cast<unsigned int>(stream))); 187 transport_->AddChannel(stream, stream); 188 EXPECT_EQ(0, voe_base_->StartReceive(stream)); 189 EXPECT_EQ(0, voe_base_->StartPlayout(stream)); 190 EXPECT_EQ(0, voe_codec_->SetSendCodec(stream, codec_inst)); 191 EXPECT_EQ(0, voe_base_->StartSend(stream)); 192 EXPECT_EQ(0, voe_file_->StartPlayingFileAsMicrophone(stream, 193 input_filename_.c_str(), true)); 194 } 195 196 void StopRemoteStreams(const std::vector<int>& streams) { 197 for (size_t i = 0; i < streams.size(); ++i) { 198 EXPECT_EQ(0, voe_base_->StopSend(streams[i])); 199 EXPECT_EQ(0, voe_base_->StopPlayout(streams[i])); 200 EXPECT_EQ(0, voe_base_->StopReceive(streams[i])); 201 EXPECT_EQ(0, voe_network_->DeRegisterExternalTransport(streams[i])); 202 EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i])); 203 } 204 } 205 206 int GetFileDurationMs(const char* file_name) { 207 FILE* fid = fopen(file_name, "rb"); 208 EXPECT_FALSE(fid == NULL); 209 fseek(fid, 0, SEEK_END); 210 int size = ftell(fid); 211 EXPECT_NE(-1, size); 212 fclose(fid); 213 // Divided by 2 due to 2 bytes/sample. 214 return size * 1000 / kRecSampleRateHz / 2; 215 } 216 217 std::string input_filename_; 218 const std::string output_filename_; 219 LoopBackTransport* transport_; 220 }; 221 222 // This test has no verification, but exercises additional code paths in a 223 // somewhat more realistic scenario using real audio. It can at least hunt for 224 // asserts and crashes. 225 TEST_F(MixingTest, MixManyChannelsForStress) { 226 RunMixingTest(10, 0, 10, true, 0, 0, 0, kCodecL16); 227 } 228 229 TEST_F(MixingTest, MixManyChannelsForStressOpus) { 230 RunMixingTest(10, 0, 10, true, 0, 0, 0, kCodecOpus); 231 } 232 233 // These tests assume a maximum of three mixed participants. We typically allow 234 // a +/- 10% range around the expected output level to account for distortion 235 // from coding and processing in the loopback chain. 236 TEST_F(MixingTest, FourChannelsWithOnlyThreeMixed) { 237 const int16_t kInputValue = 1000; 238 const int16_t kExpectedOutput = kInputValue * 3; 239 RunMixingTest(4, 0, 4, false, kInputValue, 1.1 * kExpectedOutput, 240 0.9 * kExpectedOutput, kCodecL16); 241 } 242 243 // Ensure the mixing saturation protection is working. We can do this because 244 // the mixing limiter is given some headroom, so the expected output is less 245 // than full scale. 246 TEST_F(MixingTest, VerifySaturationProtection) { 247 const int16_t kInputValue = 20000; 248 const int16_t kExpectedOutput = kLimiterHeadroom; 249 // If this isn't satisfied, we're not testing anything. 250 ASSERT_GT(kInputValue * 3, kInt16Max); 251 ASSERT_LT(1.1 * kExpectedOutput, kInt16Max); 252 RunMixingTest(3, 0, 3, false, kInputValue, 1.1 * kExpectedOutput, 253 0.9 * kExpectedOutput, kCodecL16); 254 } 255 256 TEST_F(MixingTest, SaturationProtectionHasNoEffectOnOneChannel) { 257 const int16_t kInputValue = kInt16Max; 258 const int16_t kExpectedOutput = kInt16Max; 259 // If this isn't satisfied, we're not testing anything. 260 ASSERT_GT(0.95 * kExpectedOutput, kLimiterHeadroom); 261 // Tighter constraints are required here to properly test this. 262 RunMixingTest(1, 0, 1, false, kInputValue, kExpectedOutput, 263 0.95 * kExpectedOutput, kCodecL16); 264 } 265 266 TEST_F(MixingTest, VerifyAnonymousAndNormalParticipantMixing) { 267 const int16_t kInputValue = 1000; 268 const int16_t kExpectedOutput = kInputValue * 2; 269 RunMixingTest(1, 1, 1, false, kInputValue, 1.1 * kExpectedOutput, 270 0.9 * kExpectedOutput, kCodecL16); 271 } 272 273 TEST_F(MixingTest, AnonymousParticipantsAreAlwaysMixed) { 274 const int16_t kInputValue = 1000; 275 const int16_t kExpectedOutput = kInputValue * 4; 276 RunMixingTest(3, 1, 3, false, kInputValue, 1.1 * kExpectedOutput, 277 0.9 * kExpectedOutput, kCodecL16); 278 } 279 280 TEST_F(MixingTest, VerifyStereoAndMonoMixing) { 281 const int16_t kInputValue = 1000; 282 const int16_t kExpectedOutput = kInputValue * 2; 283 RunMixingTest(2, 0, 1, false, kInputValue, 1.1 * kExpectedOutput, 284 // Lower than 0.9 due to observed flakiness on bots. 285 0.8 * kExpectedOutput, kCodecL16); 286 } 287 288 } // namespace webrtc 289