Home | History | Annotate | Download | only in standard
      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