andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 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 | |
| 13 | #include <string> |
| 14 | |
pbos@webrtc.org | 471ae72 | 2013-05-21 13:52:32 +0000 | [diff] [blame] | 15 | #include "webrtc/test/testsupport/fileutils.h" |
| 16 | #include "webrtc/voice_engine/test/auto_test/fixtures/after_initialization_fixture.h" |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 17 | |
| 18 | namespace webrtc { |
| 19 | namespace { |
| 20 | |
| 21 | const int16_t kLimiterHeadroom = 29204; // == -1 dbFS |
| 22 | const int16_t kInt16Max = 0x7fff; |
| 23 | const int kSampleRateHz = 16000; |
| 24 | const int kTestDurationMs = 3000; |
| 25 | const int kSkipOutputMs = 500; |
| 26 | |
| 27 | } // namespace |
| 28 | |
| 29 | class MixingTest : public AfterInitializationFixture { |
| 30 | protected: |
| 31 | MixingTest() |
| 32 | : input_filename_(test::OutputPath() + "mixing_test_input.pcm"), |
| 33 | output_filename_(test::OutputPath() + "mixing_test_output.pcm") { |
| 34 | } |
pwestin@webrtc.org | e493218 | 2013-04-03 15:43:57 +0000 | [diff] [blame] | 35 | void SetUp() { |
| 36 | transport_ = new LoopBackTransport(voe_network_); |
| 37 | } |
| 38 | void TearDown() { |
| 39 | delete transport_; |
| 40 | } |
pwestin@webrtc.org | 912b7f7 | 2013-03-13 23:20:57 +0000 | [diff] [blame] | 41 | |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 42 | // Creates and mixes |num_remote_streams| which play a file "as microphone" |
| 43 | // with |num_local_streams| which play a file "locally", using a constant |
| 44 | // amplitude of |input_value|. The local streams manifest as "anonymous" |
| 45 | // mixing participants, meaning they will be mixed regardless of the number |
| 46 | // of participants. (A stream is a VoiceEngine "channel"). |
| 47 | // |
| 48 | // The mixed output is verified to always fall between |max_output_value| and |
| 49 | // |min_output_value|, after a startup phase. |
| 50 | // |
| 51 | // |num_remote_streams_using_mono| of the remote streams use mono, with the |
| 52 | // remainder using stereo. |
| 53 | void RunMixingTest(int num_remote_streams, |
| 54 | int num_local_streams, |
| 55 | int num_remote_streams_using_mono, |
| 56 | int16_t input_value, |
| 57 | int16_t max_output_value, |
| 58 | int16_t min_output_value) { |
| 59 | ASSERT_LE(num_remote_streams_using_mono, num_remote_streams); |
| 60 | |
| 61 | GenerateInputFile(input_value); |
| 62 | |
| 63 | std::vector<int> local_streams(num_local_streams); |
| 64 | for (size_t i = 0; i < local_streams.size(); ++i) { |
| 65 | local_streams[i] = voe_base_->CreateChannel(); |
| 66 | EXPECT_NE(-1, local_streams[i]); |
| 67 | } |
| 68 | StartLocalStreams(local_streams); |
| 69 | TEST_LOG("Playing %d local streams.\n", num_local_streams); |
| 70 | |
| 71 | std::vector<int> remote_streams(num_remote_streams); |
| 72 | for (size_t i = 0; i < remote_streams.size(); ++i) { |
| 73 | remote_streams[i] = voe_base_->CreateChannel(); |
| 74 | EXPECT_NE(-1, remote_streams[i]); |
| 75 | } |
| 76 | StartRemoteStreams(remote_streams, num_remote_streams_using_mono); |
| 77 | TEST_LOG("Playing %d remote streams.\n", num_remote_streams); |
| 78 | |
| 79 | // Start recording the mixed output and wait. |
| 80 | EXPECT_EQ(0, voe_file_->StartRecordingPlayout(-1 /* record meeting */, |
| 81 | output_filename_.c_str())); |
| 82 | Sleep(kTestDurationMs); |
| 83 | EXPECT_EQ(0, voe_file_->StopRecordingPlayout(-1)); |
| 84 | |
| 85 | StopLocalStreams(local_streams); |
| 86 | StopRemoteStreams(remote_streams); |
| 87 | |
| 88 | VerifyMixedOutput(max_output_value, min_output_value); |
| 89 | } |
| 90 | |
| 91 | private: |
| 92 | // Generate input file with constant values equal to |input_value|. The file |
| 93 | // will be one second longer than the duration of the test. |
| 94 | void GenerateInputFile(int16_t input_value) { |
| 95 | FILE* input_file = fopen(input_filename_.c_str(), "wb"); |
| 96 | ASSERT_TRUE(input_file != NULL); |
| 97 | for (int i = 0; i < kSampleRateHz / 1000 * (kTestDurationMs + 1000); i++) { |
| 98 | ASSERT_EQ(1u, fwrite(&input_value, sizeof(input_value), 1, input_file)); |
| 99 | } |
| 100 | ASSERT_EQ(0, fclose(input_file)); |
| 101 | } |
| 102 | |
| 103 | void VerifyMixedOutput(int16_t max_output_value, int16_t min_output_value) { |
| 104 | // Verify the mixed output. |
| 105 | FILE* output_file = fopen(output_filename_.c_str(), "rb"); |
| 106 | ASSERT_TRUE(output_file != NULL); |
| 107 | int16_t output_value = 0; |
| 108 | // Skip the first segment to avoid initialization and ramping-in effects. |
| 109 | EXPECT_EQ(0, fseek(output_file, sizeof(output_value) * |
| 110 | kSampleRateHz / 1000 * kSkipOutputMs, SEEK_SET)); |
| 111 | int samples_read = 0; |
| 112 | while (fread(&output_value, sizeof(output_value), 1, output_file) == 1) { |
| 113 | samples_read++; |
| 114 | std::ostringstream trace_stream; |
| 115 | trace_stream << samples_read << " samples read"; |
| 116 | SCOPED_TRACE(trace_stream.str()); |
| 117 | EXPECT_LE(output_value, max_output_value); |
| 118 | EXPECT_GE(output_value, min_output_value); |
| 119 | } |
| 120 | // Ensure the recording length is close to the duration of the test. |
| 121 | // We have to use a relaxed tolerance here due to filesystem flakiness on |
| 122 | // the bots. |
| 123 | ASSERT_GE((samples_read * 1000.0) / kSampleRateHz, |
| 124 | 0.7 * (kTestDurationMs - kSkipOutputMs)); |
| 125 | // Ensure we read the entire file. |
| 126 | ASSERT_NE(0, feof(output_file)); |
| 127 | ASSERT_EQ(0, fclose(output_file)); |
| 128 | } |
| 129 | |
| 130 | // Start up local streams ("anonymous" participants). |
| 131 | void StartLocalStreams(const std::vector<int>& streams) { |
| 132 | for (size_t i = 0; i < streams.size(); ++i) { |
| 133 | EXPECT_EQ(0, voe_base_->StartPlayout(streams[i])); |
| 134 | EXPECT_EQ(0, voe_file_->StartPlayingFileLocally(streams[i], |
| 135 | input_filename_.c_str(), true)); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | void StopLocalStreams(const std::vector<int>& streams) { |
| 140 | for (size_t i = 0; i < streams.size(); ++i) { |
| 141 | EXPECT_EQ(0, voe_base_->StopPlayout(streams[i])); |
| 142 | EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i])); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | // Start up remote streams ("normal" participants). |
| 147 | void StartRemoteStreams(const std::vector<int>& streams, |
| 148 | int num_remote_streams_using_mono) { |
| 149 | // Use L16 at 16kHz to minimize distortion (file recording is 16kHz and |
| 150 | // resampling will cause distortion). |
| 151 | CodecInst codec_inst; |
| 152 | strcpy(codec_inst.plname, "L16"); |
| 153 | codec_inst.channels = 1; |
| 154 | codec_inst.plfreq = kSampleRateHz; |
| 155 | codec_inst.pltype = 105; |
| 156 | codec_inst.pacsize = codec_inst.plfreq / 100; |
| 157 | codec_inst.rate = codec_inst.plfreq * sizeof(int16_t) * 8; // 8 bits/byte. |
| 158 | |
| 159 | for (int i = 0; i < num_remote_streams_using_mono; ++i) { |
| 160 | StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i); |
| 161 | } |
| 162 | |
| 163 | // The remainder of the streams will use stereo. |
| 164 | codec_inst.channels = 2; |
| 165 | codec_inst.pltype++; |
| 166 | for (size_t i = num_remote_streams_using_mono; i < streams.size(); ++i) { |
| 167 | StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i); |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | // Start up a single remote stream. |
| 172 | void StartRemoteStream(int stream, const CodecInst& codec_inst, int port) { |
| 173 | EXPECT_EQ(0, voe_codec_->SetRecPayloadType(stream, codec_inst)); |
pwestin@webrtc.org | e493218 | 2013-04-03 15:43:57 +0000 | [diff] [blame] | 174 | EXPECT_EQ(0, voe_network_->RegisterExternalTransport(stream, *transport_)); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 175 | EXPECT_EQ(0, voe_base_->StartReceive(stream)); |
| 176 | EXPECT_EQ(0, voe_base_->StartPlayout(stream)); |
| 177 | EXPECT_EQ(0, voe_codec_->SetSendCodec(stream, codec_inst)); |
| 178 | EXPECT_EQ(0, voe_base_->StartSend(stream)); |
| 179 | EXPECT_EQ(0, voe_file_->StartPlayingFileAsMicrophone(stream, |
| 180 | input_filename_.c_str(), true)); |
| 181 | } |
| 182 | |
| 183 | void StopRemoteStreams(const std::vector<int>& streams) { |
| 184 | for (size_t i = 0; i < streams.size(); ++i) { |
| 185 | EXPECT_EQ(0, voe_base_->StopSend(streams[i])); |
| 186 | EXPECT_EQ(0, voe_base_->StopPlayout(streams[i])); |
| 187 | EXPECT_EQ(0, voe_base_->StopReceive(streams[i])); |
pwestin@webrtc.org | e493218 | 2013-04-03 15:43:57 +0000 | [diff] [blame] | 188 | EXPECT_EQ(0, voe_network_->DeRegisterExternalTransport(streams[i])); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 189 | EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i])); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | const std::string input_filename_; |
| 194 | const std::string output_filename_; |
pwestin@webrtc.org | e493218 | 2013-04-03 15:43:57 +0000 | [diff] [blame] | 195 | LoopBackTransport* transport_; |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 196 | }; |
| 197 | |
| 198 | // These tests assume a maximum of three mixed participants. We typically allow |
phoglund@webrtc.org | c62e750 | 2013-01-03 14:33:00 +0000 | [diff] [blame] | 199 | // a +/- 10% range around the expected output level to account for distortion |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 200 | // from coding and processing in the loopback chain. |
henrika@webrtc.org | 84423e9 | 2013-04-04 15:19:10 +0000 | [diff] [blame] | 201 | TEST_F(MixingTest, DISABLED_FourChannelsWithOnlyThreeMixed) { |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 202 | const int16_t kInputValue = 1000; |
| 203 | const int16_t kExpectedOutput = kInputValue * 3; |
phoglund@webrtc.org | c62e750 | 2013-01-03 14:33:00 +0000 | [diff] [blame] | 204 | RunMixingTest(4, 0, 4, kInputValue, 1.1 * kExpectedOutput, |
| 205 | 0.9 * kExpectedOutput); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 206 | } |
| 207 | |
| 208 | // Ensure the mixing saturation protection is working. We can do this because |
| 209 | // the mixing limiter is given some headroom, so the expected output is less |
| 210 | // than full scale. |
henrika@webrtc.org | 84423e9 | 2013-04-04 15:19:10 +0000 | [diff] [blame] | 211 | TEST_F(MixingTest, DISABLED_VerifySaturationProtection) { |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 212 | const int16_t kInputValue = 20000; |
| 213 | const int16_t kExpectedOutput = kLimiterHeadroom; |
| 214 | // If this isn't satisfied, we're not testing anything. |
| 215 | ASSERT_GT(kInputValue * 3, kInt16Max); |
| 216 | ASSERT_LT(1.1 * kExpectedOutput, kInt16Max); |
phoglund@webrtc.org | c62e750 | 2013-01-03 14:33:00 +0000 | [diff] [blame] | 217 | RunMixingTest(3, 0, 3, kInputValue, 1.1 * kExpectedOutput, |
| 218 | 0.9 * kExpectedOutput); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 219 | } |
| 220 | |
henrika@webrtc.org | 84423e9 | 2013-04-04 15:19:10 +0000 | [diff] [blame] | 221 | TEST_F(MixingTest, DISABLED_SaturationProtectionHasNoEffectOnOneChannel) { |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 222 | const int16_t kInputValue = kInt16Max; |
| 223 | const int16_t kExpectedOutput = kInt16Max; |
| 224 | // If this isn't satisfied, we're not testing anything. |
| 225 | ASSERT_GT(0.95 * kExpectedOutput, kLimiterHeadroom); |
| 226 | // Tighter constraints are required here to properly test this. |
| 227 | RunMixingTest(1, 0, 1, kInputValue, kExpectedOutput, |
phoglund@webrtc.org | c62e750 | 2013-01-03 14:33:00 +0000 | [diff] [blame] | 228 | 0.95 * kExpectedOutput); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 229 | } |
| 230 | |
henrika@webrtc.org | 84423e9 | 2013-04-04 15:19:10 +0000 | [diff] [blame] | 231 | TEST_F(MixingTest, DISABLED_VerifyAnonymousAndNormalParticipantMixing) { |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 232 | const int16_t kInputValue = 1000; |
| 233 | const int16_t kExpectedOutput = kInputValue * 2; |
phoglund@webrtc.org | c62e750 | 2013-01-03 14:33:00 +0000 | [diff] [blame] | 234 | RunMixingTest(1, 1, 1, kInputValue, 1.1 * kExpectedOutput, |
| 235 | 0.9 * kExpectedOutput); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 236 | } |
| 237 | |
henrika@webrtc.org | 84423e9 | 2013-04-04 15:19:10 +0000 | [diff] [blame] | 238 | TEST_F(MixingTest, DISABLED_AnonymousParticipantsAreAlwaysMixed) { |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 239 | const int16_t kInputValue = 1000; |
| 240 | const int16_t kExpectedOutput = kInputValue * 4; |
phoglund@webrtc.org | c62e750 | 2013-01-03 14:33:00 +0000 | [diff] [blame] | 241 | RunMixingTest(3, 1, 3, kInputValue, 1.1 * kExpectedOutput, |
| 242 | 0.9 * kExpectedOutput); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 243 | } |
| 244 | |
henrika@webrtc.org | 84423e9 | 2013-04-04 15:19:10 +0000 | [diff] [blame] | 245 | TEST_F(MixingTest, DISABLED_VerifyStereoAndMonoMixing) { |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 246 | const int16_t kInputValue = 1000; |
| 247 | const int16_t kExpectedOutput = kInputValue * 2; |
phoglund@webrtc.org | c62e750 | 2013-01-03 14:33:00 +0000 | [diff] [blame] | 248 | RunMixingTest(2, 0, 1, kInputValue, 1.1 * kExpectedOutput, |
| 249 | 0.9 * kExpectedOutput); |
andrew@webrtc.org | b015cbe | 2012-10-22 18:19:23 +0000 | [diff] [blame] | 250 | } |
| 251 | |
| 252 | } // namespace webrtc |