|  | /* | 
|  | *  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 <cstddef> | 
|  | #include <cstdint> | 
|  | #include <cstdio> | 
|  | #include <cstdlib> | 
|  | #include <cstring> | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <tuple> | 
|  |  | 
|  | #include "modules/audio_coding/codecs/opus/opus_interface.h" | 
|  | #include "test/gtest.h" | 
|  | #include "test/testsupport/file_utils.h" | 
|  |  | 
|  | using std::get; | 
|  | using std::string; | 
|  | using std::tuple; | 
|  | using ::testing::TestWithParam; | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | // Define coding parameter as <channels, bit_rate, filename, extension>. | 
|  | typedef tuple<size_t, 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 kOpusSamplingKhz = 48; | 
|  |  | 
|  | class OpusFecTest : public TestWithParam<coding_param> { | 
|  | protected: | 
|  | OpusFecTest(); | 
|  |  | 
|  | void SetUp() override; | 
|  | void TearDown() override; | 
|  |  | 
|  | virtual void EncodeABlock(); | 
|  |  | 
|  | virtual void DecodeABlock(bool lost_previous, bool lost_current); | 
|  |  | 
|  | int block_duration_ms_; | 
|  | int sampling_khz_; | 
|  | size_t block_length_sample_; | 
|  |  | 
|  | size_t channels_; | 
|  | int bit_rate_; | 
|  |  | 
|  | size_t data_pointer_; | 
|  | size_t loop_length_samples_; | 
|  | size_t max_bytes_; | 
|  | size_t encoded_bytes_; | 
|  |  | 
|  | WebRtcOpusEncInst* opus_encoder_; | 
|  | WebRtcOpusDecInst* opus_decoder_; | 
|  |  | 
|  | string in_filename_; | 
|  |  | 
|  | std::unique_ptr<int16_t[]> in_data_; | 
|  | std::unique_ptr<int16_t[]> out_data_; | 
|  | std::unique_ptr<uint8_t[]> bit_stream_; | 
|  | }; | 
|  |  | 
|  | void OpusFecTest::SetUp() { | 
|  | channels_ = get<0>(GetParam()); | 
|  | bit_rate_ = get<1>(GetParam()); | 
|  | printf("Coding %zu 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_ + block_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], | 
|  | block_length_sample_ * channels_ * sizeof(int16_t)); | 
|  |  | 
|  | // Maximum number of bytes in output bitstream. | 
|  | max_bytes_ = block_length_sample_ * channels_ * sizeof(int16_t); | 
|  |  | 
|  | out_data_.reset(new int16_t[2 * block_length_sample_ * channels_]); | 
|  | bit_stream_.reset(new uint8_t[max_bytes_]); | 
|  |  | 
|  | // If channels_ == 1, use Opus VOIP mode, otherwise, audio mode. | 
|  | int app = channels_ == 1 ? 0 : 1; | 
|  |  | 
|  | // Create encoder memory. | 
|  | EXPECT_EQ(0, WebRtcOpus_EncoderCreate(&opus_encoder_, channels_, app, 48000)); | 
|  | EXPECT_EQ(0, WebRtcOpus_DecoderCreate(&opus_decoder_, channels_, 48000)); | 
|  | // 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), | 
|  | sampling_khz_(kOpusSamplingKhz), | 
|  | block_length_sample_( | 
|  | static_cast<size_t>(block_duration_ms_ * sampling_khz_)), | 
|  | data_pointer_(0), | 
|  | max_bytes_(0), | 
|  | encoded_bytes_(0), | 
|  | opus_encoder_(NULL), | 
|  | opus_decoder_(NULL) {} | 
|  |  | 
|  | void OpusFecTest::EncodeABlock() { | 
|  | int value = | 
|  | WebRtcOpus_Encode(opus_encoder_, &in_data_[data_pointer_], | 
|  | block_length_sample_, max_bytes_, &bit_stream_[0]); | 
|  | EXPECT_GT(value, 0); | 
|  |  | 
|  | encoded_bytes_ = static_cast<size_t>(value); | 
|  | } | 
|  |  | 
|  | void OpusFecTest::DecodeABlock(bool lost_previous, bool lost_current) { | 
|  | int16_t audio_type; | 
|  | int 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 { | 
|  | // Call decoder PLC. | 
|  | while (value_1 < static_cast<int>(block_length_sample_)) { | 
|  | int ret = WebRtcOpus_Decode(opus_decoder_, NULL, 0, &out_data_[value_1], | 
|  | &audio_type); | 
|  | EXPECT_EQ(ret, sampling_khz_ * 10);  // Should return 10 ms of samples. | 
|  | value_1 += ret; | 
|  | } | 
|  | } | 
|  | EXPECT_EQ(static_cast<int>(block_length_sample_), value_1); | 
|  | } | 
|  |  | 
|  | if (!lost_current) { | 
|  | // Decode current frame. | 
|  | value_2 = WebRtcOpus_Decode(opus_decoder_, &bit_stream_[0], encoded_bytes_, | 
|  | &out_data_[value_1 * channels_], &audio_type); | 
|  | EXPECT_EQ(static_cast<int>(block_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. | 
|  | int 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_ + block_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[] = { | 
|  | std::make_tuple(1, | 
|  | 64000, | 
|  | string("audio_coding/testfile32kHz"), | 
|  | string("pcm")), | 
|  | std::make_tuple(1, | 
|  | 32000, | 
|  | string("audio_coding/testfile32kHz"), | 
|  | string("pcm")), | 
|  | std::make_tuple(2, | 
|  | 64000, | 
|  | string("audio_coding/teststereo32kHz"), | 
|  | string("pcm"))}; | 
|  |  | 
|  | // 64 kbps, stereo | 
|  | INSTANTIATE_TEST_SUITE_P(AllTest, OpusFecTest, ::testing::ValuesIn(param_set)); | 
|  |  | 
|  | }  // namespace webrtc |