adding FEC support to WebRTC Opus wrapper and tests.

BUG=
R=tina.legrand@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5656 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h b/webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h
index 8226f45..7998fdb 100644
--- a/webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h
+++ b/webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h
@@ -60,6 +60,49 @@
 int16_t WebRtcOpus_SetBitRate(OpusEncInst* inst, int32_t rate);
 
 /****************************************************************************
+ * WebRtcOpus_SetPacketLossRate(...)
+ *
+ * This function configures the encoder's expected packet loss percentage.
+ *
+ * Input:
+ *      - inst               : Encoder context
+ *      - loss_rate          : loss percentage in the range 0-100, inclusive.
+ * Return value              :  0 - Success
+ *                             -1 - Error
+ */
+int16_t WebRtcOpus_SetPacketLossRate(OpusEncInst* inst, int32_t loss_rate);
+
+/* TODO(minyue): Check whether an API to check the FEC and the packet loss rate
+ * is needed. It might not be very useful since there are not many use cases and
+ * the caller can always maintain the states. */
+
+/****************************************************************************
+ * WebRtcOpus_EnableFec()
+ *
+ * This function enables FEC for encoding.
+ *
+ * Input:
+ *      - inst               : Encoder context
+ *
+ * Return value              :  0 - Success
+ *                             -1 - Error
+ */
+int16_t WebRtcOpus_EnableFec(OpusEncInst* inst);
+
+/****************************************************************************
+ * WebRtcOpus_DisableFec()
+ *
+ * This function disables FEC for encoding.
+ *
+ * Input:
+ *      - inst               : Encoder context
+ *
+ * Return value              :  0 - Success
+ *                             -1 - Error
+ */
+int16_t WebRtcOpus_DisableFec(OpusEncInst* inst);
+
+/*
  * WebRtcOpus_SetComplexity(...)
  *
  * This function adjusts the computational complexity. The effect is the same as
@@ -128,6 +171,7 @@
 int16_t WebRtcOpus_DecodeSlave(OpusDecInst* inst, const int16_t* encoded,
                                int16_t encoded_bytes, int16_t* decoded,
                                int16_t* audio_type);
+
 /****************************************************************************
  * WebRtcOpus_DecodePlc(...)
  * TODO(tlegrand): Remove master and slave functions when NetEq4 is in place.
@@ -153,6 +197,28 @@
                                   int16_t number_of_lost_frames);
 
 /****************************************************************************
+ * WebRtcOpus_DecodeFec(...)
+ *
+ * This function decodes the FEC data from an Opus packet into one or more audio
+ * frames at the ACM interface's sampling rate (32 kHz).
+ *
+ * Input:
+ *      - inst               : Decoder context
+ *      - encoded            : Encoded data
+ *      - encoded_bytes      : Bytes in encoded vector
+ *
+ * Output:
+ *      - decoded            : The decoded vector (previous frame)
+ *
+ * Return value              : >0 - Samples per channel in decoded vector
+ *                              0 - No FEC data in the packet
+ *                             -1 - Error
+ */
+int16_t WebRtcOpus_DecodeFec(OpusDecInst* inst, const uint8_t* encoded,
+                             int16_t encoded_bytes, int16_t* decoded,
+                             int16_t* audio_type);
+
+/****************************************************************************
  * WebRtcOpus_DurationEst(...)
  *
  * This function calculates the duration of an opus packet.
@@ -167,6 +233,40 @@
                            const uint8_t* payload,
                            int payload_length_bytes);
 
+/* TODO(minyue): Check whether it is needed to add a decoder context to the
+ * arguments, like WebRtcOpus_DurationEst(...). In fact, the packet itself tells
+ * the duration. The decoder context in WebRtcOpus_DurationEst(...) is not used.
+ * So it may be advisable to remove it from WebRtcOpus_DurationEst(...). */
+
+/****************************************************************************
+ * WebRtcOpus_FecDurationEst(...)
+ *
+ * This function calculates the duration of the FEC data within an opus packet.
+ * Input:
+ *        - payload              : Encoded data pointer
+ *        - payload_length_bytes : Bytes of encoded data
+ *
+ * Return value                  : >0 - The duration of the FEC data in the
+ *                                 packet in samples.
+ *                                  0 - No FEC data in the packet.
+ */
+int WebRtcOpus_FecDurationEst(const uint8_t* payload,
+                              int payload_length_bytes);
+
+/****************************************************************************
+ * WebRtcOpus_PacketHasFec(...)
+ *
+ * This function detects if an opus packet has FEC.
+ * Input:
+ *        - payload              : Encoded data pointer
+ *        - payload_length_bytes : Bytes of encoded data
+ *
+ * Return value                  : 0 - the packet does NOT contain FEC.
+ *                                 1 - the packet contains FEC.
+ */
+int WebRtcOpus_PacketHasFec(const uint8_t* payload,
+                            int payload_length_bytes);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/webrtc/modules/audio_coding/codecs/opus/opus.gypi b/webrtc/modules/audio_coding/codecs/opus/opus.gypi
index 4068702..b1dedd7 100644
--- a/webrtc/modules/audio_coding/codecs/opus/opus.gypi
+++ b/webrtc/modules/audio_coding/codecs/opus/opus.gypi
@@ -32,4 +32,26 @@
       ],
     },
   ],
+  'conditions': [
+    ['include_tests==1', {
+      'targets': [
+        {
+          'target_name': 'webrtc_opus_fec_test',
+          'type': 'executable',
+          'dependencies': [
+            'webrtc_opus',
+            '<(webrtc_root)/common_audio/common_audio.gyp:common_audio',
+            '<(webrtc_root)/test/test.gyp:test_support_main',
+            '<(DEPTH)/testing/gtest.gyp:gtest',
+          ],
+          'include_dirs': [
+            '<(webrtc_root)',
+          ],
+          'sources': [
+            'opus_fec_test.cc',
+          ],
+        },
+      ],
+    }],
+  ],
 }
diff --git a/webrtc/modules/audio_coding/codecs/opus/opus_fec_test.cc b/webrtc/modules/audio_coding/codecs/opus/opus_fec_test.cc
new file mode 100644
index 0000000..fb4cb04
--- /dev/null
+++ b/webrtc/modules/audio_coding/codecs/opus/opus_fec_test.cc
@@ -0,0 +1,249 @@
+/*
+ *  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 "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h"
+#include "webrtc/test/testsupport/fileutils.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+
+using ::std::string;
+using ::std::tr1::tuple;
+using ::std::tr1::make_tuple;
+using ::std::tr1::get;
+using ::testing::TestWithParam;
+using ::testing::ValuesIn;
+
+namespace webrtc {
+
+// Define coding parameter as <channels, bit_rate, filename, extension>.
+typedef tuple<int, int, string, string> coding_param;
+typedef struct mode mode;
+
+struct mode {
+  bool fec;
+  uint8_t target_packet_loss_rate;
+};
+
+const int kOpusBlockDurationMs = 20;
+const int kOpusInputSamplingKhz = 48;
+const int kOpusOutputSamplingKhz = 32;
+
+class OpusFecTest : public TestWithParam<coding_param> {
+ protected:
+  OpusFecTest();
+
+  virtual void SetUp();
+  virtual void TearDown();
+
+  virtual void EncodeABlock();
+
+  virtual void DecodeABlock(bool lost_previous, bool lost_current);
+
+  int block_duration_ms_;
+  int input_sampling_khz_;
+  int output_sampling_khz_;
+
+  // Number of samples-per-channel in a frame.
+  int input_length_sample_;
+
+  // Expected output number of samples-per-channel in a frame.
+  int output_length_sample_;
+
+  int channels_;
+  int bit_rate_;
+
+  size_t data_pointer_;
+  size_t loop_length_samples_;
+  int max_bytes_;
+  int encoded_bytes_;
+
+  WebRtcOpusEncInst* opus_encoder_;
+  WebRtcOpusDecInst* opus_decoder_;
+
+  string in_filename_;
+
+  scoped_ptr<int16_t[]> in_data_;
+  scoped_ptr<int16_t[]> out_data_;
+  scoped_ptr<uint8_t[]> bit_stream_;
+};
+
+void OpusFecTest::SetUp() {
+  channels_ = get<0>(GetParam());
+  bit_rate_ = get<1>(GetParam());
+  printf("Coding %d channel signal at %d bps.\n", channels_, bit_rate_);
+
+  in_filename_ = test::ResourcePath(get<2>(GetParam()), get<3>(GetParam()));
+
+  FILE* fp = fopen(in_filename_.c_str(), "rb");
+  ASSERT_FALSE(fp == NULL);
+
+  // Obtain file size.
+  fseek(fp, 0, SEEK_END);
+  loop_length_samples_ = ftell(fp) / sizeof(int16_t);
+  rewind(fp);
+
+  // Allocate memory to contain the whole file.
+  in_data_.reset(new int16_t[loop_length_samples_ +
+      input_length_sample_ * channels_]);
+
+  // Copy the file into the buffer.
+  ASSERT_EQ(fread(&in_data_[0], sizeof(int16_t), loop_length_samples_, fp),
+            loop_length_samples_);
+  fclose(fp);
+
+  // The audio will be used in a looped manner. To ease the acquisition of an
+  // audio frame that crosses the end of the excerpt, we add an extra block
+  // length of samples to the end of the array, starting over again from the
+  // beginning of the array. Audio frames cross the end of the excerpt always
+  // appear as a continuum of memory.
+  memcpy(&in_data_[loop_length_samples_], &in_data_[0],
+         input_length_sample_ * channels_ * sizeof(int16_t));
+
+  // Maximum number of bytes in output bitstream.
+  max_bytes_ = input_length_sample_ * channels_ * sizeof(int16_t);
+
+  out_data_.reset(new int16_t[2 * output_length_sample_ * channels_]);
+  bit_stream_.reset(new uint8_t[max_bytes_]);
+
+  // Create encoder memory.
+  EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_, channels_));
+  EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_));
+  // Set bitrate.
+  EXPECT_EQ(0, WebRtcOpus_SetBitRate(opus_encoder_, bit_rate_));
+}
+
+void OpusFecTest::TearDown() {
+  // Free memory.
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_decoder_));
+}
+
+OpusFecTest::OpusFecTest()
+    : block_duration_ms_(kOpusBlockDurationMs),
+      input_sampling_khz_(kOpusInputSamplingKhz),
+      output_sampling_khz_(kOpusOutputSamplingKhz),
+      input_length_sample_(block_duration_ms_ * input_sampling_khz_),
+      output_length_sample_(block_duration_ms_ * output_sampling_khz_),
+      data_pointer_(0),
+      max_bytes_(0),
+      encoded_bytes_(0),
+      opus_encoder_(NULL),
+      opus_decoder_(NULL) {
+}
+
+void OpusFecTest::EncodeABlock() {
+  int16_t value = WebRtcOpus_Encode(opus_encoder_,
+                                    &in_data_[data_pointer_],
+                                    input_length_sample_,
+                                    max_bytes_, &bit_stream_[0]);
+  EXPECT_GT(value, 0);
+
+  encoded_bytes_ = value;
+}
+
+void OpusFecTest::DecodeABlock(bool lost_previous, bool lost_current) {
+  int16_t audio_type;
+  int16_t value_1 = 0, value_2 = 0;
+
+  if (lost_previous) {
+    // Decode previous frame.
+    if (!lost_current &&
+        WebRtcOpus_PacketHasFec(&bit_stream_[0], encoded_bytes_) == 1) {
+      value_1 = WebRtcOpus_DecodeFec(opus_decoder_, &bit_stream_[0],
+                                     encoded_bytes_, &out_data_[0],
+                                     &audio_type);
+    } else {
+      value_1 = WebRtcOpus_DecodePlc(opus_decoder_, &out_data_[0], 1);
+    }
+    EXPECT_EQ(output_length_sample_, value_1);
+  }
+
+  if (!lost_current) {
+    // Decode current frame.
+    value_2 = WebRtcOpus_DecodeNew(opus_decoder_, &bit_stream_[0],
+                                   encoded_bytes_,
+                                   &out_data_[value_1 * channels_],
+                                   &audio_type);
+    EXPECT_EQ(output_length_sample_, value_2);
+  }
+}
+
+TEST_P(OpusFecTest, RandomPacketLossTest) {
+  const int kDurationMs = 200000;
+  int time_now_ms, fec_frames;
+  int actual_packet_loss_rate;
+  bool lost_current, lost_previous;
+  mode mode_set[3] = {{true, 0},
+                      {false, 0},
+                      {true, 50}};
+
+  lost_current = false;
+  for (int i = 0; i < 3; i++) {
+    if (mode_set[i].fec) {
+      EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_encoder_));
+      EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_encoder_,
+          mode_set[i].target_packet_loss_rate));
+      printf("FEC is ON, target at packet loss rate %d percent.\n",
+             mode_set[i].target_packet_loss_rate);
+    } else {
+      EXPECT_EQ(0, WebRtcOpus_DisableFec(opus_encoder_));
+      printf("FEC is OFF.\n");
+    }
+    // In this test, we let the target packet loss rate match the actual rate.
+    actual_packet_loss_rate = mode_set[i].target_packet_loss_rate;
+    // Run every mode a certain time.
+    time_now_ms = 0;
+    fec_frames = 0;
+    while (time_now_ms < kDurationMs) {
+      // Encode & decode.
+      EncodeABlock();
+
+      // Check if payload has FEC.
+      int16_t fec = WebRtcOpus_PacketHasFec(&bit_stream_[0], encoded_bytes_);
+
+      // If FEC is disabled or the target packet loss rate is set to 0, there
+      // should be no FEC in the bit stream.
+      if (!mode_set[i].fec || mode_set[i].target_packet_loss_rate == 0) {
+        EXPECT_EQ(fec, 0);
+      } else if (fec == 1) {
+        fec_frames++;
+      }
+
+      lost_previous = lost_current;
+      lost_current = rand() < actual_packet_loss_rate * (RAND_MAX / 100);
+      DecodeABlock(lost_previous, lost_current);
+
+      time_now_ms += block_duration_ms_;
+
+      // |data_pointer_| is incremented and wrapped across
+      // |loop_length_samples_|.
+      data_pointer_ = (data_pointer_ + input_length_sample_ * channels_) %
+        loop_length_samples_;
+    }
+    if (mode_set[i].fec) {
+      printf("%.2f percent frames has FEC.\n",
+             static_cast<float>(fec_frames) * block_duration_ms_ / 2000);
+    }
+  }
+}
+
+const coding_param param_set[] =
+    {make_tuple(1, 64000, string("audio_coding/testfile32kHz"),
+                string("pcm")),
+     make_tuple(1, 32000, string("audio_coding/testfile32kHz"),
+                string("pcm")),
+     make_tuple(2, 64000, string("audio_coding/teststereo32kHz"),
+                string("pcm"))};
+
+// 64 kbps, stereo
+INSTANTIATE_TEST_CASE_P(AllTest, OpusFecTest,
+                        ValuesIn(param_set));
+
+}  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/codecs/opus/opus_interface.c b/webrtc/modules/audio_coding/codecs/opus/opus_interface.c
index c4380a3..24fc4fc 100644
--- a/webrtc/modules/audio_coding/codecs/opus/opus_interface.c
+++ b/webrtc/modules/audio_coding/codecs/opus/opus_interface.c
@@ -109,6 +109,31 @@
   }
 }
 
+int16_t WebRtcOpus_SetPacketLossRate(OpusEncInst* inst, int32_t loss_rate) {
+  if (inst) {
+    return opus_encoder_ctl(inst->encoder,
+                            OPUS_SET_PACKET_LOSS_PERC(loss_rate));
+  } else {
+    return -1;
+  }
+}
+
+int16_t WebRtcOpus_EnableFec(OpusEncInst* inst) {
+  if (inst) {
+    return opus_encoder_ctl(inst->encoder, OPUS_SET_INBAND_FEC(1));
+  } else {
+    return -1;
+  }
+}
+
+int16_t WebRtcOpus_DisableFec(OpusEncInst* inst) {
+  if (inst) {
+    return opus_encoder_ctl(inst->encoder, OPUS_SET_INBAND_FEC(0));
+  } else {
+    return -1;
+  }
+}
+
 int16_t WebRtcOpus_SetComplexity(OpusEncInst* inst, int32_t complexity) {
   if (inst) {
     return opus_encoder_ctl(inst->encoder, OPUS_SET_COMPLEXITY(complexity));
@@ -225,6 +250,23 @@
   return -1;
 }
 
+static int DecodeFec(OpusDecoder* inst, const int16_t* encoded,
+                     int16_t encoded_bytes, int frame_size,
+                     int16_t* decoded, int16_t* audio_type) {
+  unsigned char* coded = (unsigned char*) encoded;
+  opus_int16* audio = (opus_int16*) decoded;
+
+  int res = opus_decode(inst, coded, encoded_bytes, audio, frame_size, 1);
+
+  /* TODO(tlegrand): set to DTX for zero-length packets? */
+  *audio_type = 0;
+
+  if (res > 0) {
+    return res;
+  }
+  return -1;
+}
+
 /* Resample from 48 to 32 kHz. Length of state is assumed to be
  * kWebRtcOpusStateSize (7).
  */
@@ -550,6 +592,52 @@
   return resampled_samples;
 }
 
+int16_t WebRtcOpus_DecodeFec(OpusDecInst* inst, const uint8_t* encoded,
+                             int16_t encoded_bytes, int16_t* decoded,
+                             int16_t* audio_type) {
+  /* |buffer| is big enough for 120 ms (the largest Opus packet size) of stereo
+   * audio at 48 kHz. */
+  int16_t buffer[kWebRtcOpusMaxFrameSize];
+  int16_t* coded = (int16_t*)encoded;
+  int decoded_samples;
+  int resampled_samples;
+  int fec_samples;
+
+  if (WebRtcOpus_PacketHasFec(encoded, encoded_bytes) != 1) {
+    return 0;
+  }
+
+  fec_samples = opus_packet_get_samples_per_frame(encoded, 48000);
+
+  /* Decode to a temporary buffer. */
+  decoded_samples = DecodeFec(inst->decoder_left, coded, encoded_bytes,
+                              fec_samples, buffer, audio_type);
+  if (decoded_samples < 0) {
+    return -1;
+  }
+
+  /* If mono case, just do a regular call to the decoder.
+   * If stereo, we need to de-interleave the stereo output into blocks with
+   * left and right channel. Each block is resampled to 32 kHz, and then
+   * interleaved again. */
+  if (inst->channels == 2) {
+    /* De-interleave and resample. */
+    resampled_samples = WebRtcOpus_DeInterleaveResample(inst,
+                                                        buffer,
+                                                        decoded_samples,
+                                                        decoded);
+  } else {
+    /* Resample from 48 kHz to 32 kHz. Filter state memory for left channel is
+     * used for mono signals. */
+    resampled_samples = WebRtcOpus_Resample48to32(buffer,
+                                                  decoded_samples,
+                                                  inst->state_48_32_left,
+                                                  decoded);
+  }
+
+  return resampled_samples;
+}
+
 int WebRtcOpus_DurationEst(OpusDecInst* inst,
                            const uint8_t* payload,
                            int payload_length_bytes) {
@@ -570,3 +658,79 @@
   samples = samples * 2 / 3;
   return samples;
 }
+
+int WebRtcOpus_FecDurationEst(const uint8_t* payload,
+                              int payload_length_bytes) {
+  int samples;
+  if (WebRtcOpus_PacketHasFec(payload, payload_length_bytes) != 1) {
+    return 0;
+  }
+
+  samples = opus_packet_get_samples_per_frame(payload, 48000);
+  if (samples < 480 || samples > 5760) {
+    /* Invalid payload duration. */
+    return 0;
+  }
+  /* Compensate for the down-sampling from 48 kHz to 32 kHz.
+   * This should be removed when the resampling in WebRtcOpus_Decode is
+   * removed. */
+  samples = samples * 2 / 3;
+  return samples;
+}
+
+int WebRtcOpus_PacketHasFec(const uint8_t* payload,
+                            int payload_length_bytes) {
+  int frames, channels, payload_length_ms;
+  int n;
+  opus_int16 frame_sizes[48];
+  const unsigned char *frame_data[48];
+
+  if (payload == NULL || payload_length_bytes <= 0)
+    return 0;
+
+  /* In CELT_ONLY mode, packets should not have FEC. */
+  if (payload[0] & 0x80)
+    return 0;
+
+  payload_length_ms = opus_packet_get_samples_per_frame(payload, 48000) / 48;
+  if (10 > payload_length_ms)
+    payload_length_ms = 10;
+
+  channels = opus_packet_get_nb_channels(payload);
+
+  switch (payload_length_ms) {
+    case 10:
+    case 20: {
+      frames = 1;
+      break;
+    }
+    case 40: {
+      frames = 2;
+      break;
+    }
+    case 60: {
+      frames = 3;
+      break;
+    }
+    default: {
+      return 0; // It is actually even an invalid packet.
+    }
+  }
+
+  /* The following is to parse the LBRR flags. */
+  if (opus_packet_parse(payload, payload_length_bytes, NULL, frame_data,
+                        frame_sizes, NULL) < 0) {
+    return 0;
+  }
+
+  if (frame_sizes[0] <= 1) {
+    return 0;
+  }
+
+  for (n = 0; n < channels; n++) {
+    if (frame_data[0][0] & (0x80 >> ((n + 1) * (frames + 1) - 1)))
+      return 1;
+  }
+
+  return 0;
+}
diff --git a/webrtc/modules/audio_coding/codecs/opus/opus_unittest.cc b/webrtc/modules/audio_coding/codecs/opus/opus_unittest.cc
index 5b29c23..ed876cd 100644
--- a/webrtc/modules/audio_coding/codecs/opus/opus_unittest.cc
+++ b/webrtc/modules/audio_coding/codecs/opus/opus_unittest.cc
@@ -286,6 +286,47 @@
   EXPECT_EQ(0, WebRtcOpus_DecoderFree(opus_stereo_decoder_new_));
 }
 
+TEST_F(OpusTest, OpusEnableDisableFec) {
+  // Test without creating encoder memory.
+  EXPECT_EQ(-1, WebRtcOpus_EnableFec(opus_mono_encoder_));
+  EXPECT_EQ(-1, WebRtcOpus_DisableFec(opus_stereo_encoder_));
+
+  // Create encoder memory, try with different bitrates.
+  EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_mono_encoder_, 1));
+  EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_stereo_encoder_, 2));
+
+  EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_mono_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_EnableFec(opus_stereo_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_DisableFec(opus_mono_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_DisableFec(opus_stereo_encoder_));
+
+  // Free memory.
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_mono_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_stereo_encoder_));
+}
+
+TEST_F(OpusTest, OpusSetPacketLossRate) {
+  // Test without creating encoder memory.
+  EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_mono_encoder_, 50));
+  EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_stereo_encoder_, 50));
+
+  // Create encoder memory, try with different bitrates.
+  EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_mono_encoder_, 1));
+  EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_stereo_encoder_, 2));
+
+  EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_mono_encoder_, 50));
+  EXPECT_EQ(0, WebRtcOpus_SetPacketLossRate(opus_stereo_encoder_, 50));
+  EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_mono_encoder_, -1));
+  EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_stereo_encoder_, -1));
+  EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_mono_encoder_, 101));
+  EXPECT_EQ(-1, WebRtcOpus_SetPacketLossRate(opus_stereo_encoder_, 101));
+
+  // Free memory.
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_mono_encoder_));
+  EXPECT_EQ(0, WebRtcOpus_EncoderFree(opus_stereo_encoder_));
+}
+
+
 // PLC in mono mode.
 TEST_F(OpusTest, OpusDecodePlcMono) {
   // Create encoder memory.