| OLD | NEW |
| (Empty) |
| 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_fixtu
re.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 ASSERT_LE(output_value, max_output_value); | |
| 133 ASSERT_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_->StartPlayout(stream)); | |
| 189 EXPECT_EQ(0, voe_codec_->SetSendCodec(stream, codec_inst)); | |
| 190 EXPECT_EQ(0, voe_base_->StartSend(stream)); | |
| 191 EXPECT_EQ(0, voe_file_->StartPlayingFileAsMicrophone(stream, | |
| 192 input_filename_.c_str(), true)); | |
| 193 } | |
| 194 | |
| 195 void StopRemoteStreams(const std::vector<int>& streams) { | |
| 196 for (size_t i = 0; i < streams.size(); ++i) { | |
| 197 EXPECT_EQ(0, voe_base_->StopSend(streams[i])); | |
| 198 EXPECT_EQ(0, voe_base_->StopPlayout(streams[i])); | |
| 199 EXPECT_EQ(0, voe_network_->DeRegisterExternalTransport(streams[i])); | |
| 200 EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i])); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 int GetFileDurationMs(const char* file_name) { | |
| 205 FILE* fid = fopen(file_name, "rb"); | |
| 206 EXPECT_FALSE(fid == NULL); | |
| 207 fseek(fid, 0, SEEK_END); | |
| 208 int size = ftell(fid); | |
| 209 EXPECT_NE(-1, size); | |
| 210 fclose(fid); | |
| 211 // Divided by 2 due to 2 bytes/sample. | |
| 212 return size * 1000 / kRecSampleRateHz / 2; | |
| 213 } | |
| 214 | |
| 215 std::string input_filename_; | |
| 216 const std::string output_filename_; | |
| 217 LoopBackTransport* transport_; | |
| 218 }; | |
| 219 | |
| 220 // This test has no verification, but exercises additional code paths in a | |
| 221 // somewhat more realistic scenario using real audio. It can at least hunt for | |
| 222 // asserts and crashes. | |
| 223 TEST_F(MixingTest, MixManyChannelsForStress) { | |
| 224 RunMixingTest(10, 0, 10, true, 0, 0, 0, kCodecL16); | |
| 225 } | |
| 226 | |
| 227 TEST_F(MixingTest, MixManyChannelsForStressOpus) { | |
| 228 RunMixingTest(10, 0, 10, true, 0, 0, 0, kCodecOpus); | |
| 229 } | |
| 230 | |
| 231 // These tests assume a maximum of three mixed participants. We typically allow | |
| 232 // a +/- 10% range around the expected output level to account for distortion | |
| 233 // from coding and processing in the loopback chain. | |
| 234 TEST_F(MixingTest, FourChannelsWithOnlyThreeMixed) { | |
| 235 const int16_t kInputValue = 1000; | |
| 236 const int16_t kExpectedOutput = kInputValue * 3; | |
| 237 RunMixingTest(4, 0, 4, false, kInputValue, 1.1 * kExpectedOutput, | |
| 238 0.9 * kExpectedOutput, kCodecL16); | |
| 239 } | |
| 240 | |
| 241 // Ensure the mixing saturation protection is working. We can do this because | |
| 242 // the mixing limiter is given some headroom, so the expected output is less | |
| 243 // than full scale. | |
| 244 TEST_F(MixingTest, VerifySaturationProtection) { | |
| 245 const int16_t kInputValue = 20000; | |
| 246 const int16_t kExpectedOutput = kLimiterHeadroom; | |
| 247 // If this isn't satisfied, we're not testing anything. | |
| 248 ASSERT_GT(kInputValue * 3, kInt16Max); | |
| 249 ASSERT_LT(1.1 * kExpectedOutput, kInt16Max); | |
| 250 RunMixingTest(3, 0, 3, false, kInputValue, 1.1 * kExpectedOutput, | |
| 251 0.9 * kExpectedOutput, kCodecL16); | |
| 252 } | |
| 253 | |
| 254 TEST_F(MixingTest, SaturationProtectionHasNoEffectOnOneChannel) { | |
| 255 const int16_t kInputValue = kInt16Max; | |
| 256 const int16_t kExpectedOutput = kInt16Max; | |
| 257 // If this isn't satisfied, we're not testing anything. | |
| 258 ASSERT_GT(0.95 * kExpectedOutput, kLimiterHeadroom); | |
| 259 // Tighter constraints are required here to properly test this. | |
| 260 RunMixingTest(1, 0, 1, false, kInputValue, kExpectedOutput, | |
| 261 0.95 * kExpectedOutput, kCodecL16); | |
| 262 } | |
| 263 | |
| 264 TEST_F(MixingTest, VerifyAnonymousAndNormalParticipantMixing) { | |
| 265 const int16_t kInputValue = 1000; | |
| 266 const int16_t kExpectedOutput = kInputValue * 2; | |
| 267 RunMixingTest(1, 1, 1, false, kInputValue, 1.1 * kExpectedOutput, | |
| 268 0.9 * kExpectedOutput, kCodecL16); | |
| 269 } | |
| 270 | |
| 271 TEST_F(MixingTest, AnonymousParticipantsAreAlwaysMixed) { | |
| 272 const int16_t kInputValue = 1000; | |
| 273 const int16_t kExpectedOutput = kInputValue * 4; | |
| 274 RunMixingTest(3, 1, 3, false, kInputValue, 1.1 * kExpectedOutput, | |
| 275 0.9 * kExpectedOutput, kCodecL16); | |
| 276 } | |
| 277 | |
| 278 TEST_F(MixingTest, VerifyStereoAndMonoMixing) { | |
| 279 const int16_t kInputValue = 1000; | |
| 280 const int16_t kExpectedOutput = kInputValue * 2; | |
| 281 RunMixingTest(2, 0, 1, false, kInputValue, 1.1 * kExpectedOutput, | |
| 282 // Lower than 0.9 due to observed flakiness on bots. | |
| 283 0.8 * kExpectedOutput, kCodecL16); | |
| 284 } | |
| 285 | |
| 286 } // namespace webrtc | |
| OLD | NEW |