Adding FEC support in NetEq 4.

R=henrik.lundin@webrtc.org, turaj@webrtc.org

TEST=passes all trybots

BUG=

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5748 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder.cc b/webrtc/modules/audio_coding/neteq4/audio_decoder.cc
index 35422e3..2a252e6 100644
--- a/webrtc/modules/audio_coding/neteq4/audio_decoder.cc
+++ b/webrtc/modules/audio_coding/neteq4/audio_decoder.cc
@@ -41,6 +41,16 @@
   return kNotImplemented;
 }
 
+int AudioDecoder::PacketDurationRedundant(const uint8_t* encoded,
+                                          size_t encoded_len) const {
+  return kNotImplemented;
+}
+
+bool AudioDecoder::PacketHasFec(const uint8_t* encoded,
+                                size_t encoded_len) const {
+  return false;
+}
+
 NetEqDecoder AudioDecoder::codec_type() const { return codec_type_; }
 
 bool AudioDecoder::CodecSupported(NetEqDecoder codec_type) {
diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc
index 5296a1b..94e507e 100644
--- a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc
+++ b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.cc
@@ -458,6 +458,19 @@
   return ret;
 }
 
+int AudioDecoderOpus::DecodeRedundant(const uint8_t* encoded,
+                                      size_t encoded_len, int16_t* decoded,
+                                      SpeechType* speech_type) {
+  int16_t temp_type = 1;  // Default is speech.
+  int16_t ret = WebRtcOpus_DecodeFec(static_cast<OpusDecInst*>(state_), encoded,
+                                     static_cast<int16_t>(encoded_len), decoded,
+                                     &temp_type);
+  if (ret > 0)
+    ret *= static_cast<int16_t>(channels_);  // Return total number of samples.
+  *speech_type = ConvertSpeechType(temp_type);
+  return ret;
+}
+
 int AudioDecoderOpus::Init() {
   return WebRtcOpus_DecoderInitNew(static_cast<OpusDecInst*>(state_));
 }
@@ -467,6 +480,18 @@
   return WebRtcOpus_DurationEst(static_cast<OpusDecInst*>(state_),
                                 encoded, static_cast<int>(encoded_len));
 }
+
+int AudioDecoderOpus::PacketDurationRedundant(const uint8_t* encoded,
+                                              size_t encoded_len) const {
+  return WebRtcOpus_FecDurationEst(encoded, static_cast<int>(encoded_len));
+}
+
+bool AudioDecoderOpus::PacketHasFec(const uint8_t* encoded,
+                                    size_t encoded_len) const {
+  int fec;
+  fec = WebRtcOpus_PacketHasFec(encoded, static_cast<int>(encoded_len));
+  return (fec == 1);
+}
 #endif
 
 AudioDecoderCng::AudioDecoderCng(enum NetEqDecoder type)
diff --git a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h
index aa35db7..5df649a 100644
--- a/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h
+++ b/webrtc/modules/audio_coding/neteq4/audio_decoder_impl.h
@@ -236,8 +236,13 @@
   virtual ~AudioDecoderOpus();
   virtual int Decode(const uint8_t* encoded, size_t encoded_len,
                      int16_t* decoded, SpeechType* speech_type);
+  virtual int DecodeRedundant(const uint8_t* encoded, size_t encoded_len,
+                              int16_t* decoded, SpeechType* speech_type);
   virtual int Init();
   virtual int PacketDuration(const uint8_t* encoded, size_t encoded_len);
+  virtual int PacketDurationRedundant(const uint8_t* encoded,
+                                      size_t encoded_len) const;
+  virtual bool PacketHasFec(const uint8_t* encoded, size_t encoded_len) const;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(AudioDecoderOpus);
diff --git a/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h b/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h
index f3bcc71..6b4b191 100644
--- a/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h
+++ b/webrtc/modules/audio_coding/neteq4/interface/audio_decoder.h
@@ -108,6 +108,17 @@
   // is available, or -1 in case of an error.
   virtual int PacketDuration(const uint8_t* encoded, size_t encoded_len);
 
+  // Returns the duration in samples of the redandant payload in |encoded| which
+  // is |encoded_len| bytes long. Returns kNotImplemented if no duration
+  // estimate is available, or -1 in case of an error.
+  virtual int PacketDurationRedundant(const uint8_t* encoded,
+                                      size_t encoded_len) const;
+
+  // Detects whether a packet has forward error correction. The packet is
+  // comprised of the samples in |encoded| which is |encoded_len| bytes long.
+  // Returns true if the packet has FEC and false otherwise.
+  virtual bool PacketHasFec(const uint8_t* encoded, size_t encoded_len) const;
+
   virtual NetEqDecoder codec_type() const;
 
   // Returns the underlying decoder state.
diff --git a/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h b/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h
index f3d8c9b..369dfc4 100644
--- a/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h
+++ b/webrtc/modules/audio_coding/neteq4/mock/mock_payload_splitter.h
@@ -21,6 +21,8 @@
  public:
   MOCK_METHOD1(SplitRed,
       int(PacketList* packet_list));
+  MOCK_METHOD2(SplitFec,
+      int(PacketList* packet_list, DecoderDatabase* decoder_database));
   MOCK_METHOD2(CheckRedPayloads,
       int(PacketList* packet_list, const DecoderDatabase& decoder_database));
   MOCK_METHOD2(SplitAudio,
diff --git a/webrtc/modules/audio_coding/neteq4/neteq.gypi b/webrtc/modules/audio_coding/neteq4/neteq.gypi
index 6580d68..3e7ede4 100644
--- a/webrtc/modules/audio_coding/neteq4/neteq.gypi
+++ b/webrtc/modules/audio_coding/neteq4/neteq.gypi
@@ -189,6 +189,8 @@
             'tools/neteq_performance_test.h',
             'tools/rtp_generator.cc',
             'tools/rtp_generator.h',
+            'tools/neteq_quality_test.cc',
+            'tools/neteq_quality_test.h',
           ],
         }, # neteq_unittest_tools
       ], # targets
diff --git a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc b/webrtc/modules/audio_coding/neteq4/neteq_impl.cc
index 2b98d05..8b7e517 100644
--- a/webrtc/modules/audio_coding/neteq4/neteq_impl.cc
+++ b/webrtc/modules/audio_coding/neteq4/neteq_impl.cc
@@ -512,6 +512,19 @@
     memcpy(&main_header, &packet_list.front()->header, sizeof(main_header));
   }
 
+  // Check for FEC in packets, and separate payloads into several packets.
+  int ret = payload_splitter_->SplitFec(&packet_list, decoder_database_.get());
+  if (ret != PayloadSplitter::kOK) {
+    LOG_FERR1(LS_WARNING, SplitFec, packet_list.size());
+    PacketBuffer::DeleteAllPackets(&packet_list);
+    switch (ret) {
+      case PayloadSplitter::kUnknownPayloadType:
+        return kUnknownRtpPayloadType;
+      default:
+        return kOtherError;
+    }
+  }
+
   // Check payload types.
   if (decoder_database_->CheckPayloadTypes(packet_list) ==
       DecoderDatabase::kDecoderNotFound) {
@@ -561,7 +574,7 @@
   // Split payloads into smaller chunks. This also verifies that all payloads
   // are of a known payload type. SplitAudio() method is protected against
   // sync-packets.
-  int ret = payload_splitter_->SplitAudio(&packet_list, *decoder_database_);
+  ret = payload_splitter_->SplitAudio(&packet_list, *decoder_database_);
   if (ret != PayloadSplitter::kOK) {
     LOG_FERR1(LS_WARNING, SplitAudio, packet_list.size());
     PacketBuffer::DeleteAllPackets(&packet_list);
@@ -1777,8 +1790,14 @@
     AudioDecoder* decoder = decoder_database_->GetDecoder(
         packet->header.payloadType);
     if (decoder) {
-      packet_duration = packet->sync_packet ? decoder_frame_length_ :
-          decoder->PacketDuration(packet->payload, packet->payload_length);
+      if (packet->sync_packet) {
+        packet_duration = decoder_frame_length_;
+      } else {
+        packet_duration = packet->primary ?
+            decoder->PacketDuration(packet->payload, packet->payload_length) :
+            decoder->PacketDurationRedundant(packet->payload,
+                                             packet->payload_length);
+      }
     } else {
       LOG_FERR1(LS_WARNING, GetDecoder, packet->header.payloadType) <<
           "Could not find a decoder for a packet about to be extracted.";
diff --git a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi b/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi
index e1fcae7..a73c8a2 100644
--- a/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi
+++ b/webrtc/modules/audio_coding/neteq4/neteq_tests.gypi
@@ -167,6 +167,22 @@
     },
 
     {
+      'target_name': 'neteq4_opus_fec_quality_test',
+      'type': 'executable',
+      'dependencies': [
+        'NetEq4',
+        'neteq_unittest_tools',
+        'webrtc_opus',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+        '<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
+        '<(webrtc_root)/test/test.gyp:test_support_main',
+      ],
+      'sources': [
+        'test/neteq_opus_fec_quality_test.cc',
+      ],
+    },
+
+    {
      'target_name': 'NetEq4TestTools',
       # Collection of useful functions used in other tests.
       'type': 'static_library',
diff --git a/webrtc/modules/audio_coding/neteq4/packet_buffer.cc b/webrtc/modules/audio_coding/neteq4/packet_buffer.cc
index c461463..0cc0854 100644
--- a/webrtc/modules/audio_coding/neteq4/packet_buffer.cc
+++ b/webrtc/modules/audio_coding/neteq4/packet_buffer.cc
@@ -238,8 +238,15 @@
     AudioDecoder* decoder =
         decoder_database->GetDecoder(packet->header.payloadType);
     if (decoder) {
-      int duration = packet->sync_packet ? last_duration :
-          decoder->PacketDuration(packet->payload, packet->payload_length);
+      int duration;
+      if (packet->sync_packet) {
+        duration = last_duration;
+      } else {
+        duration = packet->primary ?
+            decoder->PacketDuration(packet->payload, packet->payload_length) :
+            decoder->PacketDurationRedundant(packet->payload,
+                                             packet->payload_length);
+      }
       if (duration >= 0) {
         last_duration = duration;  // Save the most up-to-date (valid) duration.
       }
diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter.cc b/webrtc/modules/audio_coding/neteq4/payload_splitter.cc
index 56039a5..0209ad9 100644
--- a/webrtc/modules/audio_coding/neteq4/payload_splitter.cc
+++ b/webrtc/modules/audio_coding/neteq4/payload_splitter.cc
@@ -119,6 +119,62 @@
   return ret;
 }
 
+int PayloadSplitter::SplitFec(PacketList* packet_list,
+                              DecoderDatabase* decoder_database) {
+  PacketList::iterator it = packet_list->begin();
+  // Iterate through all packets in |packet_list|.
+  while (it != packet_list->end()) {
+    Packet* packet = (*it);  // Just to make the notation more intuitive.
+    // Get codec type for this payload.
+    uint8_t payload_type = packet->header.payloadType;
+    const DecoderDatabase::DecoderInfo* info =
+        decoder_database->GetDecoderInfo(payload_type);
+    if (!info) {
+      return kUnknownPayloadType;
+    }
+    // No splitting for a sync-packet.
+    if (packet->sync_packet) {
+      ++it;
+      continue;
+    }
+
+    // Not an FEC packet.
+    AudioDecoder* decoder = decoder_database->GetDecoder(payload_type);
+    if (!decoder->PacketHasFec(packet->payload, packet->payload_length)) {
+      ++it;
+      continue;
+    }
+
+    switch (info->codec_type) {
+      case kDecoderOpus:
+      case kDecoderOpus_2ch: {
+        Packet* new_packet = new Packet;
+
+        new_packet->header = packet->header;
+        int duration = decoder->
+            PacketDurationRedundant(packet->payload,
+                                    packet->payload_length) * 3 / 2;
+        new_packet->header.timestamp -= duration;
+        new_packet->payload = new uint8_t[packet->payload_length];
+        memcpy(new_packet->payload, packet->payload, packet->payload_length);
+        new_packet->payload_length = packet->payload_length;
+        new_packet->primary = false;
+        new_packet->waiting_time = packet->waiting_time;
+        new_packet->sync_packet = packet->sync_packet;
+
+        packet_list->insert(it, new_packet);
+        break;
+      }
+      default: {
+        return kFecSplitError;
+      }
+    }
+
+    ++it;
+  }
+  return kOK;
+}
+
 int PayloadSplitter::CheckRedPayloads(PacketList* packet_list,
                                       const DecoderDatabase& decoder_database) {
   PacketList::iterator it = packet_list->begin();
@@ -283,7 +339,7 @@
     // increment it manually.
     it = packet_list->erase(it);
   }
-  return 0;
+  return kOK;
 }
 
 void PayloadSplitter::SplitBySamples(const Packet* packet,
diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter.h b/webrtc/modules/audio_coding/neteq4/payload_splitter.h
index 3768c2f..0f6caed 100644
--- a/webrtc/modules/audio_coding/neteq4/payload_splitter.h
+++ b/webrtc/modules/audio_coding/neteq4/payload_splitter.h
@@ -32,7 +32,8 @@
     kTooLargePayload = -1,
     kFrameSplitError = -2,
     kUnknownPayloadType = -3,
-    kRedLengthMismatch = -4
+    kRedLengthMismatch = -4,
+    kFecSplitError = -5,
   };
 
   PayloadSplitter() {}
@@ -47,6 +48,12 @@
   // Returns kOK or an error.
   virtual int SplitRed(PacketList* packet_list);
 
+  // Iterates through |packet_list| and, duplicate each audio payload that has
+  // FEC as new packet for redundant decoding. The decoder database is needed to
+  // get information about which payload type each packet contains.
+  virtual int SplitFec(PacketList* packet_list,
+                       DecoderDatabase* decoder_database);
+
   // Checks all packets in |packet_list|. Packets that are DTMF events or
   // comfort noise payloads are kept. Except that, only one single payload type
   // is accepted. Any packet with another payload type is discarded.
diff --git a/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc b/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc
index 5a7a6ca..97bdc5c 100644
--- a/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc
+++ b/webrtc/modules/audio_coding/neteq4/payload_splitter_unittest.cc
@@ -91,6 +91,34 @@
   return packet;
 }
 
+
+// A possible Opus packet that contains FEC is the following.
+// The frame is 20 ms in duration.
+//
+// 0                   1                   2                   3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// |0|0|0|0|1|0|0|0|x|1|x|x|x|x|x|x|x|                             |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                             |
+// |                    Compressed frame 1 (N-2 bytes)...          :
+// :                                                               |
+// |                                                               |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+Packet* CreateOpusFecPacket(uint8_t payload_type, int payload_length,
+                            uint8_t payload_value) {
+  Packet* packet = new Packet;
+  packet->header.payloadType = payload_type;
+  packet->header.timestamp = kBaseTimestamp;
+  packet->header.sequenceNumber = kSequenceNumber;
+  packet->payload_length = payload_length;
+  uint8_t* payload = new uint8_t[packet->payload_length];
+  payload[0] = 0x08;
+  payload[1] = 0x40;
+  memset(&payload[2], payload_value, payload_length - 2);
+  packet->payload = payload;
+  return packet;
+}
+
 // Create a packet with all payload bytes set to |payload_value|.
 Packet* CreatePacket(uint8_t payload_type, int payload_length,
                      uint8_t payload_value) {
@@ -691,4 +719,59 @@
   EXPECT_CALL(decoder_database, Die());
 }
 
+TEST(FecPayloadSplitter, MixedPayload) {
+  PacketList packet_list;
+  DecoderDatabase decoder_database;
+
+  decoder_database.RegisterPayload(0, kDecoderOpus);
+  decoder_database.RegisterPayload(1, kDecoderPCMu);
+
+  Packet* packet = CreateOpusFecPacket(0, 10, 0xFF);
+  packet_list.push_back(packet);
+
+  packet = CreatePacket(0, 10, 0); // Non-FEC Opus payload.
+  packet_list.push_back(packet);
+
+  packet = CreatePacket(1, 10, 0); // Non-Opus payload.
+  packet_list.push_back(packet);
+
+  PayloadSplitter splitter;
+  EXPECT_EQ(PayloadSplitter::kOK,
+            splitter.SplitFec(&packet_list, &decoder_database));
+  EXPECT_EQ(4u, packet_list.size());
+
+  // Check first packet.
+  packet = packet_list.front();
+  EXPECT_EQ(0, packet->header.payloadType);
+  EXPECT_EQ(kBaseTimestamp - 20 * 48, packet->header.timestamp);
+  EXPECT_EQ(10, packet->payload_length);
+  EXPECT_FALSE(packet->primary);
+  delete [] packet->payload;
+  delete packet;
+  packet_list.pop_front();
+
+  // Check second packet.
+  packet = packet_list.front();
+  EXPECT_EQ(0, packet->header.payloadType);
+  EXPECT_EQ(kBaseTimestamp, packet->header.timestamp);
+  EXPECT_EQ(10, packet->payload_length);
+  EXPECT_TRUE(packet->primary);
+  delete [] packet->payload;
+  delete packet;
+  packet_list.pop_front();
+
+  // Check third packet.
+  packet = packet_list.front();
+  VerifyPacket(packet, 10, 0, kSequenceNumber, kBaseTimestamp, 0, true);
+  delete [] packet->payload;
+  delete packet;
+  packet_list.pop_front();
+
+  // Check fourth packet.
+  packet = packet_list.front();
+  VerifyPacket(packet, 10, 1, kSequenceNumber, kBaseTimestamp, 0, true);
+  delete [] packet->payload;
+  delete packet;
+}
+
 }  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc b/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc
new file mode 100644
index 0000000..aa4522b
--- /dev/null
+++ b/webrtc/modules/audio_coding/neteq4/test/neteq_opus_fec_quality_test.cc
@@ -0,0 +1,178 @@
+/*
+ *  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 <gflags/gflags.h>
+#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h"
+#include "webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+using google::RegisterFlagValidator;
+using google::ParseCommandLineFlags;
+using std::string;
+using testing::InitGoogleTest;
+
+namespace webrtc {
+namespace test {
+
+static const int kOpusBlockDurationMs = 20;
+static const int kOpusInputSamplingKhz = 48;
+static const int kOpusOutputSamplingKhz = 32;
+
+static bool ValidateInFilename(const char* flagname, const string& value) {
+  FILE* fid = fopen(value.c_str(), "rb");
+  if (fid != NULL) {
+    fclose(fid);
+    return true;
+  }
+  printf("Invalid input filename.");
+  return false;
+}
+DEFINE_string(in_filename,
+              ResourcePath("audio_coding/speech_mono_32_48kHz", "pcm"),
+              "Filename for input audio (should be 48 kHz sampled raw data).");
+static const bool in_filename_dummy =
+    RegisterFlagValidator(&FLAGS_in_filename, &ValidateInFilename);
+
+static bool ValidateOutFilename(const char* flagname, const string& value) {
+  FILE* fid = fopen(value.c_str(), "wb");
+  if (fid != NULL) {
+    fclose(fid);
+    return true;
+  }
+  printf("Invalid output filename.");
+  return false;
+}
+DEFINE_string(out_filename, OutputPath() + "neteq4_opus_fec_quality_test.pcm",
+              "Name of output audio file.");
+static const bool out_filename_dummy =
+    RegisterFlagValidator(&FLAGS_out_filename, &ValidateOutFilename);
+
+static bool ValidateChannels(const char* flagname, int32_t value) {
+  if (value == 1 || value == 2)
+    return true;
+  printf("Invalid number of channels, should be either 1 or 2.");
+  return false;
+}
+DEFINE_int32(channels, 1, "Number of channels in input audio.");
+static const bool channels_dummy =
+    RegisterFlagValidator(&FLAGS_channels, &ValidateChannels);
+
+static bool ValidateBitRate(const char* flagname, int32_t value) {
+  if (value >= 6 && value <= 510)
+    return true;
+  printf("Invalid bit rate, should be between 6 and 510 kbps.");
+  return false;
+}
+DEFINE_int32(bit_rate_kbps, 32, "Target bit rate (kbps).");
+static const bool bit_rate_dummy =
+    RegisterFlagValidator(&FLAGS_bit_rate_kbps, &ValidateBitRate);
+
+static bool ValidatePacketLossRate(const char* flagname, int32_t value) {
+  if (value >= 0 && value <= 100)
+    return true;
+  printf("Invalid packet loss percentile, should be between 0 and 100.");
+  return false;
+}
+DEFINE_int32(reported_loss_rate, 10, "Reported percentile of packet loss.");
+static const bool reported_loss_rate_dummy =
+    RegisterFlagValidator(&FLAGS_reported_loss_rate, &ValidatePacketLossRate);
+DEFINE_int32(actual_loss_rate, 0, "Actual percentile of packet loss.");
+static const bool actual_loss_rate_dummy =
+    RegisterFlagValidator(&FLAGS_actual_loss_rate, &ValidatePacketLossRate);
+
+static bool ValidateRuntime(const char* flagname, int32_t value) {
+  if (value > 0)
+    return true;
+  printf("Invalid runtime, should be greater than 0.");
+  return false;
+}
+DEFINE_int32(runtime_ms, 10000, "Simulated runtime (milliseconds).");
+static const bool runtime_dummy =
+    RegisterFlagValidator(&FLAGS_runtime_ms, &ValidateRuntime);
+
+DEFINE_bool(fec, true, "Whether to enable FEC for encoding.");
+
+class NetEqOpusFecQualityTest : public NetEqQualityTest {
+ protected:
+  NetEqOpusFecQualityTest();
+  virtual void SetUp() OVERRIDE;
+  virtual void TearDown() OVERRIDE;
+  virtual int EncodeBlock(int16_t* in_data, int block_size_samples,
+                          uint8_t* payload, int max_bytes);
+  virtual bool PacketLost(int packet_input_time_ms);
+ private:
+  WebRtcOpusEncInst* opus_encoder_;
+  int channels_;
+  int bit_rate_kbps_;
+  bool fec_;
+  int target_loss_rate_;
+  int actual_loss_rate_;
+};
+
+NetEqOpusFecQualityTest::NetEqOpusFecQualityTest()
+    : NetEqQualityTest(kOpusBlockDurationMs, kOpusInputSamplingKhz,
+                       kOpusOutputSamplingKhz,
+                       (FLAGS_channels == 1) ? kDecoderOpus : kDecoderOpus_2ch,
+                       FLAGS_channels, 0.0f, FLAGS_in_filename,
+                       FLAGS_out_filename),
+      opus_encoder_(NULL),
+      channels_(FLAGS_channels),
+      bit_rate_kbps_(FLAGS_bit_rate_kbps),
+      fec_(FLAGS_fec),
+      target_loss_rate_(FLAGS_reported_loss_rate),
+      actual_loss_rate_(FLAGS_actual_loss_rate) {
+}
+
+void NetEqOpusFecQualityTest::SetUp() {
+  // Create encoder memory.
+  WebRtcOpus_EncoderCreate(&opus_encoder_, channels_);
+  ASSERT_TRUE(opus_encoder_ != NULL);
+  // Set bitrate.
+  EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, bit_rate_kbps_ * 1000));
+  if (fec_) {
+    EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_encoder_));
+    EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_,
+                                              target_loss_rate_));
+  }
+  NetEqQualityTest::SetUp();
+}
+
+void NetEqOpusFecQualityTest::TearDown() {
+  // Free memory.
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
+  NetEqQualityTest::TearDown();
+}
+
+int NetEqOpusFecQualityTest::EncodeBlock(int16_t* in_data,
+                                         int block_size_samples,
+                                         uint8_t* payload, int max_bytes) {
+  int value = WebRtcOpus_Encode(opus_encoder_, in_data,
+                                block_size_samples, max_bytes,
+                                payload);
+  EXPECT_GT(value, 0);
+  return value;
+}
+
+bool NetEqOpusFecQualityTest::PacketLost(int packet_input_time_ms) {
+  static int packets = 0, lost_packets = 0;
+  packets++;
+  if (lost_packets * 100 < actual_loss_rate_ * packets) {
+    lost_packets++;
+    return true;
+  }
+  return false;
+}
+
+TEST_F(NetEqOpusFecQualityTest, Test) {
+  Simulate(FLAGS_runtime_ms);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc b/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc
index b2b5b40..fb47616 100644
--- a/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc
+++ b/webrtc/modules/audio_coding/neteq4/timestamp_scaler.cc
@@ -55,7 +55,8 @@
       // Use timestamp scaling with factor 2/3 (32 kHz sample rate, but RTP
       // timestamps run on 48 kHz).
       // TODO(tlegrand): Remove scaling for kDecoderCNGswb48kHz once ACM has
-      // full 48 kHz support.
+      // full 48 kHz support. Change also ought to be made in
+      // PayloadSplitter::SplitFec().
       numerator_ = 2;
       denominator_ = 3;
     }
diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc
new file mode 100644
index 0000000..c56e5b9
--- /dev/null
+++ b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.cc
@@ -0,0 +1,113 @@
+/*
+ *  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 <stdio.h>
+#include "webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h"
+
+namespace webrtc {
+namespace test {
+
+const uint8_t kPayloadType = 95;
+const int kOutputSizeMs = 10;
+
+NetEqQualityTest::NetEqQualityTest(int block_duration_ms,
+                                   int in_sampling_khz,
+                                   int out_sampling_khz,
+                                   enum NetEqDecoder decoder_type,
+                                   int channels,
+                                   double drift_factor,
+                                   std::string in_filename,
+                                   std::string out_filename)
+    : decoded_time_ms_(0),
+      decodable_time_ms_(0),
+      drift_factor_(drift_factor),
+      block_duration_ms_(block_duration_ms),
+      in_sampling_khz_(in_sampling_khz),
+      out_sampling_khz_(out_sampling_khz),
+      decoder_type_(decoder_type),
+      channels_(channels),
+      in_filename_(in_filename),
+      out_filename_(out_filename),
+      in_size_samples_(in_sampling_khz_ * block_duration_ms_),
+      out_size_samples_(out_sampling_khz_ * kOutputSizeMs),
+      payload_size_bytes_(0),
+      max_payload_bytes_(0),
+      in_file_(new InputAudioFile(in_filename_)),
+      out_file_(NULL),
+      rtp_generator_(new RtpGenerator(in_sampling_khz_, 0, 0,
+                                      decodable_time_ms_)),
+      neteq_(NetEq::Create(out_sampling_khz_ * 1000)) {
+  max_payload_bytes_ = in_size_samples_ * channels_ * sizeof(int16_t);
+  in_data_.reset(new int16_t[in_size_samples_ * channels_]);
+  payload_.reset(new uint8_t[max_payload_bytes_]);
+  out_data_.reset(new int16_t[out_size_samples_ * channels_]);
+}
+
+void NetEqQualityTest::SetUp() {
+  out_file_ = fopen(out_filename_.c_str(), "wb");
+  ASSERT_TRUE(out_file_ != NULL);
+  ASSERT_EQ(0, neteq_->RegisterPayloadType(decoder_type_, kPayloadType));
+  rtp_generator_->set_drift_factor(drift_factor_);
+}
+
+void NetEqQualityTest::TearDown() {
+  fclose(out_file_);
+}
+
+int NetEqQualityTest::Transmit() {
+  int packet_input_time_ms =
+      rtp_generator_->GetRtpHeader(kPayloadType, in_size_samples_,
+                                   &rtp_header_);
+  if (!PacketLost(packet_input_time_ms) && payload_size_bytes_ > 0) {
+    int ret = neteq_->InsertPacket(rtp_header_, &payload_[0],
+                                   payload_size_bytes_,
+                                   packet_input_time_ms * in_sampling_khz_);
+    if (ret != NetEq::kOK)
+      return -1;
+  }
+  return packet_input_time_ms;
+}
+
+int NetEqQualityTest::DecodeBlock() {
+  int channels;
+  int samples;
+  int ret = neteq_->GetAudio(out_size_samples_ * channels_, &out_data_[0],
+                             &samples, &channels, NULL);
+
+  if (ret != NetEq::kOK) {
+    return -1;
+  } else {
+    assert(channels == channels_);
+    assert(samples == kOutputSizeMs * out_sampling_khz_);
+    fwrite(&out_data_[0], sizeof(int16_t), samples * channels, out_file_);
+    return samples;
+  }
+}
+
+void NetEqQualityTest::Simulate(int end_time_ms) {
+  int audio_size_samples;
+
+  while (decoded_time_ms_ < end_time_ms) {
+    while (decodable_time_ms_ - kOutputSizeMs < decoded_time_ms_) {
+      ASSERT_TRUE(in_file_->Read(in_size_samples_ * channels_, &in_data_[0]));
+      payload_size_bytes_ = EncodeBlock(&in_data_[0],
+                                        in_size_samples_, &payload_[0],
+                                        max_payload_bytes_);
+      decodable_time_ms_ = Transmit() + block_duration_ms_;
+    }
+    audio_size_samples = DecodeBlock();
+    if (audio_size_samples > 0) {
+      decoded_time_ms_ += audio_size_samples / out_sampling_khz_;
+    }
+  }
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h
new file mode 100644
index 0000000..03aabc8
--- /dev/null
+++ b/webrtc/modules/audio_coding/neteq4/tools/neteq_quality_test.h
@@ -0,0 +1,100 @@
+/*
+ *  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_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_
+#define WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_
+
+#include <string>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/audio_coding/neteq4/interface/neteq.h"
+#include "webrtc/modules/audio_coding/neteq4/tools/input_audio_file.h"
+#include "webrtc/modules/audio_coding/neteq4/tools/rtp_generator.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+namespace test {
+
+class NetEqQualityTest : public ::testing::Test {
+ protected:
+  NetEqQualityTest(int block_duration_ms,
+                   int in_sampling_khz,
+                   int out_sampling_khz,
+                   enum NetEqDecoder decoder_type,
+                   int channels,
+                   double drift_factor,
+                   std::string in_filename,
+                   std::string out_filename);
+  virtual void SetUp() OVERRIDE;
+  virtual void TearDown() OVERRIDE;
+
+  // EncodeBlock(...) does the following:
+  // 1. encodes a block of audio, saved in |in_data| and has a length of
+  // |block_size_samples| (samples per channel),
+  // 2. save the bit stream to |payload| of |max_bytes| bytes in size,
+  // 3. returns the length of the payload (in bytes),
+  virtual int EncodeBlock(int16_t* in_data, int block_size_samples,
+                          uint8_t* payload, int max_bytes) = 0;
+
+  // PacketLoss(...) determines weather a packet sent at an indicated time gets
+  // lost or not.
+  virtual bool PacketLost(int packet_input_time_ms) { return false; }
+
+  // DecodeBlock() decodes a block of audio using the payload stored in
+  // |payload_| with the length of |payload_size_bytes_| (bytes). The decoded
+  // audio is to be stored in |out_data_|.
+  int DecodeBlock();
+
+  // Transmit() uses |rtp_generator_| to generate a packet and passes it to
+  // |neteq_|.
+  int Transmit();
+
+  // Simulate(...) runs encoding / transmitting / decoding up to |end_time_ms|
+  // (miliseconds), the resulted audio is stored in the file with the name of
+  // |out_filename_|.
+  void Simulate(int end_time_ms);
+
+ private:
+  int decoded_time_ms_;
+  int decodable_time_ms_;
+  double drift_factor_;
+  const int block_duration_ms_;
+  const int in_sampling_khz_;
+  const int out_sampling_khz_;
+  const enum NetEqDecoder decoder_type_;
+  const int channels_;
+  const std::string in_filename_;
+  const std::string out_filename_;
+
+  // Number of samples per channel in a frame.
+  const int in_size_samples_;
+
+  // Expected output number of samples per channel in a frame.
+  const int out_size_samples_;
+
+  int payload_size_bytes_;
+  int max_payload_bytes_;
+
+  scoped_ptr<InputAudioFile> in_file_;
+  FILE* out_file_;
+
+  scoped_ptr<RtpGenerator> rtp_generator_;
+  scoped_ptr<NetEq> neteq_;
+
+  scoped_ptr<int16_t[]> in_data_;
+  scoped_ptr<uint8_t[]> payload_;
+  scoped_ptr<int16_t[]> out_data_;
+  WebRtcRTPHeader rtp_header_;
+};
+
+}  // namespace test
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_AUDIO_CODING_NETEQ4_TOOLS_NETEQ_QUALITY_TEST_H_