Receiver bit-exactness test for AudioCoding Module

This CL introduces a bit-exactness test for the receive-side of the
AudioCoding Module. The main part of the test is done in the helper
class AcmReceiveTest. The test is executed from the test fixture
AcmReceiverBitExactness.

The test inserts packets from a pre-encoded RTP file. The output is
summed up into a checksum, which is verified versus a reference at the
end of the test. Alternatively, if the flag --generate_output is given,
the output is written to a file for subjective verification.

R=kwiberg@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/13769004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6549 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receive_test.cc b/webrtc/modules/audio_coding/main/acm2/acm_receive_test.cc
new file mode 100644
index 0000000..43d623d
--- /dev/null
+++ b/webrtc/modules/audio_coding/main/acm2/acm_receive_test.cc
@@ -0,0 +1,181 @@
+/*
+ *  Copyright (c) 2014 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 "webrtc/modules/audio_coding/main/acm2/acm_receive_test.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h"
+#include "webrtc/modules/audio_coding/neteq/tools/audio_sink.h"
+#include "webrtc/modules/audio_coding/neteq/tools/packet.h"
+#include "webrtc/modules/audio_coding/neteq/tools/packet_source.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+// Returns true if the codec should be registered, otherwise false. Changes
+// the number of channels for the Opus codec to always be 1.
+bool ModifyAndUseThisCodec(CodecInst* codec_param) {
+  if (STR_CASE_CMP(codec_param->plname, "CN") == 0 &&
+      codec_param->plfreq == 48000)
+    return false;  // Skip 48 kHz comfort noise.
+
+  if (STR_CASE_CMP(codec_param->plname, "telephone-event") == 0)
+    return false;  // Skip DTFM.
+
+  if (STR_CASE_CMP(codec_param->plname, "opus") == 0)
+    codec_param->channels = 1;  // Always register Opus as mono.
+  else if (codec_param->channels > 1)
+    return false;  // Skip all non-mono codecs.
+
+  return true;
+}
+
+// Remaps payload types from ACM's default to those used in the resource file
+// neteq_universal_new.rtp. Returns true if the codec should be registered,
+// otherwise false. The payload types are set as follows (all are mono codecs):
+// PCMu = 0;
+// PCMa = 8;
+// Comfort noise 8 kHz = 13
+// Comfort noise 16 kHz = 98
+// Comfort noise 32 kHz = 99
+// iLBC = 102
+// iSAC wideband = 103
+// iSAC super-wideband = 104
+// iSAC fullband = 124
+// AVT/DTMF = 106
+// RED = 117
+// PCM16b 8 kHz = 93
+// PCM16b 16 kHz = 94
+// PCM16b 32 kHz = 95
+// G.722 = 94
+bool RemapPltypeAndUseThisCodec(const char* plname,
+                                int plfreq,
+                                int channels,
+                                int* pltype) {
+  if (channels != 1)
+    return false;  // Don't use non-mono codecs.
+
+  // Re-map pltypes to those used in the NetEq test files.
+  if (STR_CASE_CMP(plname, "PCMU") == 0 && plfreq == 8000) {
+    *pltype = 0;
+  } else if (STR_CASE_CMP(plname, "PCMA") == 0 && plfreq == 8000) {
+    *pltype = 8;
+  } else if (STR_CASE_CMP(plname, "CN") == 0 && plfreq == 8000) {
+    *pltype = 13;
+  } else if (STR_CASE_CMP(plname, "CN") == 0 && plfreq == 16000) {
+    *pltype = 98;
+  } else if (STR_CASE_CMP(plname, "CN") == 0 && plfreq == 32000) {
+    *pltype = 99;
+  } else if (STR_CASE_CMP(plname, "ILBC") == 0) {
+    *pltype = 102;
+  } else if (STR_CASE_CMP(plname, "ISAC") == 0 && plfreq == 16000) {
+    *pltype = 103;
+  } else if (STR_CASE_CMP(plname, "ISAC") == 0 && plfreq == 32000) {
+    *pltype = 104;
+  } else if (STR_CASE_CMP(plname, "ISAC") == 0 && plfreq == 48000) {
+    *pltype = 124;
+  } else if (STR_CASE_CMP(plname, "telephone-event") == 0) {
+    *pltype = 106;
+  } else if (STR_CASE_CMP(plname, "red") == 0) {
+    *pltype = 117;
+  } else if (STR_CASE_CMP(plname, "L16") == 0 && plfreq == 8000) {
+    *pltype = 93;
+  } else if (STR_CASE_CMP(plname, "L16") == 0 && plfreq == 16000) {
+    *pltype = 94;
+  } else if (STR_CASE_CMP(plname, "L16") == 0 && plfreq == 32000) {
+    *pltype = 95;
+  } else if (STR_CASE_CMP(plname, "G722") == 0) {
+    *pltype = 9;
+  } else {
+    // Don't use any other codecs.
+    return false;
+  }
+  return true;
+}
+}  // namespace
+
+AcmReceiveTest::AcmReceiveTest(PacketSource* packet_source,
+                               AudioSink* audio_sink,
+                               int output_freq_hz)
+    : clock_(0),
+      acm_(webrtc::AudioCodingModule::Create(0, &clock_)),
+      packet_source_(packet_source),
+      audio_sink_(audio_sink),
+      output_freq_hz_(output_freq_hz) {
+}
+
+void AcmReceiveTest::RegisterDefaultCodecs() {
+  CodecInst my_codec_param;
+  for (int n = 0; n < acm_->NumberOfCodecs(); n++) {
+    ASSERT_EQ(0, acm_->Codec(n, &my_codec_param)) << "Failed to get codec.";
+    if (ModifyAndUseThisCodec(&my_codec_param)) {
+      ASSERT_EQ(0, acm_->RegisterReceiveCodec(my_codec_param))
+          << "Couldn't register receive codec.\n";
+    }
+  }
+}
+
+void AcmReceiveTest::RegisterNetEqTestCodecs() {
+  CodecInst my_codec_param;
+  for (int n = 0; n < acm_->NumberOfCodecs(); n++) {
+    ASSERT_EQ(0, acm_->Codec(n, &my_codec_param)) << "Failed to get codec.";
+    if (!ModifyAndUseThisCodec(&my_codec_param)) {
+      // Skip this codec.
+      continue;
+    }
+
+    if (RemapPltypeAndUseThisCodec(my_codec_param.plname,
+                                   my_codec_param.plfreq,
+                                   my_codec_param.channels,
+                                   &my_codec_param.pltype)) {
+      ASSERT_EQ(0, acm_->RegisterReceiveCodec(my_codec_param))
+          << "Couldn't register receive codec.\n";
+    }
+  }
+}
+
+void AcmReceiveTest::Run() {
+  for (scoped_ptr<Packet> packet(packet_source_->NextPacket()); packet;
+       packet.reset(packet_source_->NextPacket())) {
+    // Pull audio until time to insert packet.
+    while (clock_.TimeInMilliseconds() < packet->time_ms()) {
+      AudioFrame output_frame;
+      EXPECT_EQ(0, acm_->PlayoutData10Ms(output_freq_hz_, &output_frame));
+      EXPECT_EQ(output_freq_hz_, output_frame.sample_rate_hz_);
+      const int samples_per_block = output_freq_hz_ * 10 / 1000;
+      EXPECT_EQ(samples_per_block, output_frame.samples_per_channel_);
+      EXPECT_EQ(1, output_frame.num_channels_);
+      ASSERT_TRUE(audio_sink_->WriteAudioFrame(output_frame));
+      clock_.AdvanceTimeMilliseconds(10);
+    }
+
+    // Insert packet after converting from RTPHeader to WebRtcRTPHeader.
+    WebRtcRTPHeader header;
+    header.header = packet->header();
+    header.frameType = kAudioFrameSpeech;
+    memset(&header.type.Audio, 0, sizeof(RTPAudioHeader));
+    EXPECT_EQ(0,
+              acm_->IncomingPacket(
+                  packet->payload(),
+                  static_cast<int32_t>(packet->payload_length_bytes()),
+                  header))
+        << "Failure when inserting packet:" << std::endl
+        << "  PT = " << static_cast<int>(header.header.payloadType) << std::endl
+        << "  TS = " << header.header.timestamp << std::endl
+        << "  SN = " << header.header.sequenceNumber;
+  }
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/main/acm2/acm_receive_test.h b/webrtc/modules/audio_coding/main/acm2/acm_receive_test.h
new file mode 100644
index 0000000..672c929
--- /dev/null
+++ b/webrtc/modules/audio_coding/main/acm2/acm_receive_test.h
@@ -0,0 +1,55 @@
+/*
+ *  Copyright (c) 2014 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.
+ */
+
+#ifndef WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_ACM_RECEIVE_TEST_H_
+#define WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_ACM_RECEIVE_TEST_H_
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/system_wrappers/interface/clock.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+
+namespace webrtc {
+class AudioCodingModule;
+struct CodecInst;
+
+namespace test {
+class AudioSink;
+class PacketSource;
+
+class AcmReceiveTest {
+ public:
+  AcmReceiveTest(PacketSource* packet_source,
+                 AudioSink* audio_sink,
+                 int output_freq_hz);
+  virtual ~AcmReceiveTest() {}
+
+  // Registers the codecs with default parameters from ACM.
+  void RegisterDefaultCodecs();
+
+  // Registers codecs with payload types matching the pre-encoded NetEq test
+  // files.
+  void RegisterNetEqTestCodecs();
+
+  // Runs the test and returns true if successful.
+  void Run();
+
+ private:
+  SimulatedClock clock_;
+  scoped_ptr<AudioCodingModule> acm_;
+  PacketSource* packet_source_;
+  AudioSink* audio_sink_;
+  const int output_freq_hz_;
+
+  DISALLOW_COPY_AND_ASSIGN(AcmReceiveTest);
+};
+
+}  // namespace test
+}  // namespace webrtc
+#endif  // WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_ACM_RECEIVE_TEST_H_
diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi
index 90dad6c..dccfe68 100644
--- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi
+++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module.gypi
@@ -117,6 +117,19 @@
     ['include_tests==1', {
       'targets': [
         {
+          'target_name': 'acm_receive_test',
+          'type': 'static_library',
+          'dependencies': [
+            'audio_coding_module',
+            'neteq_unittest_tools',
+            '<(DEPTH)/testing/gtest.gyp:gtest',
+          ],
+          'sources': [
+            'acm_receive_test.cc',
+            'acm_receive_test.h',
+          ],
+        }, # acm_receive_test
+        {
           'target_name': 'delay_test',
           'type': 'executable',
           'dependencies': [
diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_unittest.cc b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_unittest.cc
index 37cd70e..a73effb 100644
--- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_unittest.cc
+++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_unittest.cc
@@ -12,9 +12,13 @@
 #include <vector>
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/audio_coding/main/acm2/acm_receive_test.h"
 #include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h"
 #include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h"
+#include "webrtc/modules/audio_coding/neteq/tools/audio_checksum.h"
 #include "webrtc/modules/audio_coding/neteq/tools/audio_loop.h"
+#include "webrtc/modules/audio_coding/neteq/tools/output_audio_file.h"
+#include "webrtc/modules/audio_coding/neteq/tools/rtp_file_source.h"
 #include "webrtc/modules/interface/module_common_types.h"
 #include "webrtc/system_wrappers/interface/clock.h"
 #include "webrtc/system_wrappers/interface/compile_assert.h"
@@ -511,4 +515,76 @@
   EXPECT_EQ(kEventSignaled, RunTest());
 }
 
+class AcmReceiverBitExactness : public ::testing::Test {
+ protected:
+  void Run(int output_freq_hz, const std::string& checksum_ref) {
+    const std::string input_file_name =
+        webrtc::test::ResourcePath("audio_coding/neteq_universal_new", "rtp");
+    scoped_ptr<test::RtpFileSource> packet_source(
+        test::RtpFileSource::Create(input_file_name));
+#ifdef WEBRTC_ANDROID
+    // Filter out iLBC and iSAC-swb since they are not supported on Android.
+    packet_source->FilterOutPayloadType(102);  // iLBC.
+    packet_source->FilterOutPayloadType(104);  // iSAC-swb.
+#endif
+
+    test::AudioChecksum checksum;
+    const std::string output_file_name =
+        webrtc::test::OutputPath() +
+        ::testing::UnitTest::GetInstance()
+            ->current_test_info()
+            ->test_case_name() +
+        "_" + ::testing::UnitTest::GetInstance()->current_test_info()->name() +
+        "_output.pcm";
+    test::OutputAudioFile output_file(output_file_name);
+    test::AudioSinkFork output(&checksum, &output_file);
+
+    test::AcmReceiveTest test(packet_source.get(), &output, output_freq_hz);
+    ASSERT_NO_FATAL_FAILURE(test.RegisterNetEqTestCodecs());
+    test.Run();
+
+    std::string checksum_string = checksum.Finish();
+    EXPECT_EQ(checksum_ref, checksum_string);
+  }
+
+  static std::string PlatformChecksum(std::string win64,
+                                      std::string android,
+                                      std::string others) {
+#if defined(_WIN32) && defined(WEBRTC_ARCH_64_BITS)
+    return win64;
+#elif defined(WEBRTC_ANDROID)
+    return android;
+#else
+    return others;
+#endif
+  }
+};
+
+TEST_F(AcmReceiverBitExactness, 8kHzOutput) {
+  Run(8000,
+      PlatformChecksum("a53573d9a44a53ea852056e9550fbd53",
+                       "7924385273062b9f07aa3d4dff30d601",
+                       "c54fd4a532cdb400bca2758d3a941eee"));
+}
+
+TEST_F(AcmReceiverBitExactness, 16kHzOutput) {
+  Run(16000,
+      PlatformChecksum("16ed8ee37bad45de2e1ad2b34c7c3910",
+                       "d1d3dde41da936f80fa63d718fbc0fc0",
+                       "68a8b57a0672356f846b3cea51e49903"));
+}
+
+TEST_F(AcmReceiverBitExactness, 32kHzOutput) {
+  Run(32000,
+      PlatformChecksum("f0f41f494d5d811f5a1cfce8fd89d9db",
+                       "23b82b2605e3aab3d4d9e67dba341355",
+                       "f2a69bcdedca515e548cd2c5af75d046"));
+}
+
+TEST_F(AcmReceiverBitExactness, 48kHzOutput) {
+  Run(48000,
+      PlatformChecksum("77730099d995180ab6cb60379d4a9715",
+                       "580c2d0b273ffa8fa0796d784908cbdb",
+                       "5c1bdee51750e13fbb9413bc9280c0dd"));
+}
 }  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/neteq/tools/audio_sink.h b/webrtc/modules/audio_coding/neteq/tools/audio_sink.h
index 5743c36..474ec1c 100644
--- a/webrtc/modules/audio_coding/neteq/tools/audio_sink.h
+++ b/webrtc/modules/audio_coding/neteq/tools/audio_sink.h
@@ -41,6 +41,23 @@
   DISALLOW_COPY_AND_ASSIGN(AudioSink);
 };
 
+// Forks the output audio to two AudioSink objects.
+class AudioSinkFork : public AudioSink {
+ public:
+  AudioSinkFork(AudioSink* left, AudioSink* right)
+      : left_sink_(left), right_sink_(right) {}
+
+  virtual bool WriteArray(const int16_t* audio, size_t num_samples) OVERRIDE {
+    return left_sink_->WriteArray(audio, num_samples) &&
+           right_sink_->WriteArray(audio, num_samples);
+  }
+
+ private:
+  AudioSink* left_sink_;
+  AudioSink* right_sink_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioSinkFork);
+};
 }  // namespace test
 }  // namespace webrtc
 #endif  // WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_AUDIO_SINK_H_
diff --git a/webrtc/modules/audio_coding/neteq/tools/packet_source.h b/webrtc/modules/audio_coding/neteq/tools/packet_source.h
index 669bc14..ab9ef83 100644
--- a/webrtc/modules/audio_coding/neteq/tools/packet_source.h
+++ b/webrtc/modules/audio_coding/neteq/tools/packet_source.h
@@ -11,7 +11,10 @@
 #ifndef WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_SOURCE_H_
 #define WEBRTC_MODULES_AUDIO_CODING_NETEQ_TOOLS_PACKET_SOURCE_H_
 
+#include <bitset>
+
 #include "webrtc/base/constructormagic.h"
+#include "webrtc/typedefs.h"
 
 namespace webrtc {
 namespace test {
@@ -28,6 +31,13 @@
   // depleted, or if an error occurred.
   virtual Packet* NextPacket() = 0;
 
+  virtual void FilterOutPayloadType(uint8_t payload_type) {
+    filter_.set(payload_type, true);
+  }
+
+ protected:
+  std::bitset<128> filter_;  // Payload type is 7 bits in the RFC.
+
  private:
   DISALLOW_COPY_AND_ASSIGN(PacketSource);
 };
diff --git a/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.cc b/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.cc
index 6490d46..6924a7f 100644
--- a/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.cc
+++ b/webrtc/modules/audio_coding/neteq/tools/rtp_file_source.cc
@@ -92,6 +92,10 @@
       assert(false);
       return NULL;
     }
+    if (filter_.test(packet->header().payloadType)) {
+      // This payload type should be filtered out. Continue to the next packet.
+      continue;
+    }
     return packet.release();
   }
   return NULL;
diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp
index 8dec125..d054fe9 100644
--- a/webrtc/modules/modules.gyp
+++ b/webrtc/modules/modules.gyp
@@ -68,6 +68,7 @@
             '<@(audio_coding_defines)',
           ],
           'dependencies': [
+            'acm_receive_test',
             'audio_coding_module',
             'audio_processing',
             'bitrate_controller',