| /* | |
| * Copyright (c) 2013 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. | |
| */ | |
| // Test to verify correct operation for externally created decoders. | |
| #include <memory> | |
| #include "api/audio/audio_frame.h" | |
| #include "api/audio_codecs/builtin_audio_decoder_factory.h" | |
| #include "modules/audio_coding/neteq/mock/mock_external_decoder_pcm16b.h" | |
| #include "modules/audio_coding/neteq/tools/input_audio_file.h" | |
| #include "modules/audio_coding/neteq/tools/neteq_external_decoder_test.h" | |
| #include "modules/audio_coding/neteq/tools/rtp_generator.h" | |
| #include "rtc_base/strings/string_builder.h" | |
| #include "test/gmock.h" | |
| #include "test/testsupport/file_utils.h" | |
| namespace webrtc { | |
| using ::testing::_; | |
| using ::testing::Return; | |
| class NetEqExternalDecoderUnitTest : public test::NetEqExternalDecoderTest { | |
| protected: | |
| static const int kFrameSizeMs = 10; // Frame size of Pcm16B. | |
| NetEqExternalDecoderUnitTest(NetEqDecoder codec, | |
| int sample_rate_hz, | |
| MockExternalPcm16B* decoder) | |
| : NetEqExternalDecoderTest(codec, sample_rate_hz, decoder), | |
| external_decoder_(decoder), | |
| samples_per_ms_(sample_rate_hz / 1000), | |
| frame_size_samples_(kFrameSizeMs * samples_per_ms_), | |
| rtp_generator_(new test::RtpGenerator(samples_per_ms_)), | |
| input_(new int16_t[frame_size_samples_]), | |
| // Payload should be no larger than input. | |
| encoded_(new uint8_t[2 * frame_size_samples_]), | |
| payload_size_bytes_(0), | |
| last_send_time_(0), | |
| last_arrival_time_(0) { | |
| // NetEq is not allowed to delete the external decoder (hence Times(0)). | |
| EXPECT_CALL(*external_decoder_, Die()).Times(0); | |
| Init(); | |
| const std::string file_name = | |
| webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); | |
| input_file_.reset(new test::InputAudioFile(file_name)); | |
| } | |
| virtual ~NetEqExternalDecoderUnitTest() { | |
| delete[] input_; | |
| delete[] encoded_; | |
| // ~NetEqExternalDecoderTest() will delete |external_decoder_|, so expecting | |
| // Die() to be called. | |
| EXPECT_CALL(*external_decoder_, Die()).Times(1); | |
| } | |
| // Method to draw kFrameSizeMs audio and verify the output. | |
| // Use gTest methods. e.g. ASSERT_EQ() inside to trigger errors. | |
| virtual void GetAndVerifyOutput() = 0; | |
| // Method to get the number of calls to the Decode() method of the external | |
| // decoder. | |
| virtual int NumExpectedDecodeCalls(int num_loops) = 0; | |
| // Method to generate packets and return the send time of the packet. | |
| int GetNewPacket() { | |
| if (!input_file_->Read(frame_size_samples_, input_)) { | |
| return -1; | |
| } | |
| payload_size_bytes_ = | |
| WebRtcPcm16b_Encode(input_, frame_size_samples_, encoded_); | |
| int next_send_time = rtp_generator_->GetRtpHeader( | |
| kPayloadType, frame_size_samples_, &rtp_header_); | |
| return next_send_time; | |
| } | |
| // Method to decide packet losses. | |
| virtual bool Lost() { return false; } | |
| // Method to calculate packet arrival time. | |
| int GetArrivalTime(int send_time) { | |
| int arrival_time = last_arrival_time_ + (send_time - last_send_time_); | |
| last_send_time_ = send_time; | |
| last_arrival_time_ = arrival_time; | |
| return arrival_time; | |
| } | |
| void RunTest(int num_loops) { | |
| // Get next input packets (mono and multi-channel). | |
| uint32_t next_send_time; | |
| uint32_t next_arrival_time; | |
| do { | |
| next_send_time = GetNewPacket(); | |
| next_arrival_time = GetArrivalTime(next_send_time); | |
| } while (Lost()); // If lost, immediately read the next packet. | |
| EXPECT_CALL( | |
| *external_decoder_, | |
| DecodeInternal(_, payload_size_bytes_, 1000 * samples_per_ms_, _, _)) | |
| .Times(NumExpectedDecodeCalls(num_loops)); | |
| uint32_t time_now = 0; | |
| for (int k = 0; k < num_loops; ++k) { | |
| while (time_now >= next_arrival_time) { | |
| InsertPacket( | |
| rtp_header_, | |
| rtc::ArrayView<const uint8_t>(encoded_, payload_size_bytes_), | |
| next_arrival_time); | |
| // Get next input packet. | |
| do { | |
| next_send_time = GetNewPacket(); | |
| next_arrival_time = GetArrivalTime(next_send_time); | |
| } while (Lost()); // If lost, immediately read the next packet. | |
| } | |
| rtc::StringBuilder ss; | |
| ss << "Lap number " << k << "."; | |
| SCOPED_TRACE(ss.str()); // Print out the parameter values on failure. | |
| // Compare mono and multi-channel. | |
| ASSERT_NO_FATAL_FAILURE(GetAndVerifyOutput()); | |
| time_now += kOutputLengthMs; | |
| } | |
| } | |
| void InsertPacket(RTPHeader rtp_header, | |
| rtc::ArrayView<const uint8_t> payload, | |
| uint32_t receive_timestamp) override { | |
| EXPECT_CALL(*external_decoder_, | |
| IncomingPacket(_, payload.size(), rtp_header.sequenceNumber, | |
| rtp_header.timestamp, receive_timestamp)); | |
| NetEqExternalDecoderTest::InsertPacket(rtp_header, payload, | |
| receive_timestamp); | |
| } | |
| MockExternalPcm16B* external_decoder() { return external_decoder_.get(); } | |
| void ResetRtpGenerator(test::RtpGenerator* rtp_generator) { | |
| rtp_generator_.reset(rtp_generator); | |
| } | |
| int samples_per_ms() const { return samples_per_ms_; } | |
| private: | |
| std::unique_ptr<MockExternalPcm16B> external_decoder_; | |
| int samples_per_ms_; | |
| size_t frame_size_samples_; | |
| std::unique_ptr<test::RtpGenerator> rtp_generator_; | |
| int16_t* input_; | |
| uint8_t* encoded_; | |
| size_t payload_size_bytes_; | |
| uint32_t last_send_time_; | |
| uint32_t last_arrival_time_; | |
| std::unique_ptr<test::InputAudioFile> input_file_; | |
| RTPHeader rtp_header_; | |
| }; | |
| // This test encodes a few packets of PCM16b 32 kHz data and inserts it into two | |
| // different NetEq instances. The first instance uses the internal version of | |
| // the decoder object, while the second one uses an externally created decoder | |
| // object (ExternalPcm16B wrapped in MockExternalPcm16B, both defined above). | |
| // The test verifies that the output from both instances match. | |
| class NetEqExternalVsInternalDecoderTest : public NetEqExternalDecoderUnitTest, | |
| public ::testing::Test { | |
| protected: | |
| static const size_t kMaxBlockSize = 480; // 10 ms @ 48 kHz. | |
| NetEqExternalVsInternalDecoderTest() | |
| : NetEqExternalDecoderUnitTest(NetEqDecoder::kDecoderPCM16Bswb32kHz, | |
| 32000, | |
| new MockExternalPcm16B(32000)), | |
| sample_rate_hz_(32000) { | |
| NetEq::Config config; | |
| config.sample_rate_hz = sample_rate_hz_; | |
| neteq_internal_.reset( | |
| NetEq::Create(config, CreateBuiltinAudioDecoderFactory())); | |
| } | |
| void SetUp() override { | |
| ASSERT_EQ(true, neteq_internal_->RegisterPayloadType( | |
| kPayloadType, SdpAudioFormat("L16", 32000, 1))); | |
| } | |
| void GetAndVerifyOutput() override { | |
| // Get audio from internal decoder instance. | |
| bool muted; | |
| EXPECT_EQ(NetEq::kOK, neteq_internal_->GetAudio(&output_internal_, &muted)); | |
| ASSERT_FALSE(muted); | |
| EXPECT_EQ(1u, output_internal_.num_channels_); | |
| EXPECT_EQ(static_cast<size_t>(kOutputLengthMs * sample_rate_hz_ / 1000), | |
| output_internal_.samples_per_channel_); | |
| // Get audio from external decoder instance. | |
| GetOutputAudio(&output_); | |
| const int16_t* output_data = output_.data(); | |
| const int16_t* output_internal_data = output_internal_.data(); | |
| for (size_t i = 0; i < output_.samples_per_channel_; ++i) { | |
| ASSERT_EQ(output_data[i], output_internal_data[i]) | |
| << "Diff in sample " << i << "."; | |
| } | |
| } | |
| void InsertPacket(RTPHeader rtp_header, | |
| rtc::ArrayView<const uint8_t> payload, | |
| uint32_t receive_timestamp) override { | |
| // Insert packet in internal decoder. | |
| ASSERT_EQ(NetEq::kOK, neteq_internal_->InsertPacket(rtp_header, payload, | |
| receive_timestamp)); | |
| // Insert packet in external decoder instance. | |
| NetEqExternalDecoderUnitTest::InsertPacket(rtp_header, payload, | |
| receive_timestamp); | |
| } | |
| int NumExpectedDecodeCalls(int num_loops) override { return num_loops; } | |
| private: | |
| int sample_rate_hz_; | |
| std::unique_ptr<NetEq> neteq_internal_; | |
| AudioFrame output_internal_; | |
| AudioFrame output_; | |
| }; | |
| TEST_F(NetEqExternalVsInternalDecoderTest, RunTest) { | |
| RunTest(100); // Run 100 laps @ 10 ms each in the test loop. | |
| } | |
| class LargeTimestampJumpTest : public NetEqExternalDecoderUnitTest, | |
| public ::testing::Test { | |
| protected: | |
| static const size_t kMaxBlockSize = 480; // 10 ms @ 48 kHz. | |
| enum TestStates { | |
| kInitialPhase, | |
| kNormalPhase, | |
| kExpandPhase, | |
| kFadedExpandPhase, | |
| kRecovered | |
| }; | |
| LargeTimestampJumpTest() | |
| : NetEqExternalDecoderUnitTest(NetEqDecoder::kDecoderPCM16B, | |
| 8000, | |
| new MockExternalPcm16B(8000)), | |
| test_state_(kInitialPhase) { | |
| EXPECT_CALL(*external_decoder(), HasDecodePlc()) | |
| .WillRepeatedly(Return(false)); | |
| } | |
| virtual void UpdateState(AudioFrame::SpeechType output_type) { | |
| switch (test_state_) { | |
| case kInitialPhase: { | |
| if (output_type == AudioFrame::kNormalSpeech) { | |
| test_state_ = kNormalPhase; | |
| } | |
| break; | |
| } | |
| case kNormalPhase: { | |
| if (output_type == AudioFrame::kPLC) { | |
| test_state_ = kExpandPhase; | |
| } | |
| break; | |
| } | |
| case kExpandPhase: { | |
| if (output_type == AudioFrame::kPLCCNG) { | |
| test_state_ = kFadedExpandPhase; | |
| } else if (output_type == AudioFrame::kNormalSpeech) { | |
| test_state_ = kRecovered; | |
| } | |
| break; | |
| } | |
| case kFadedExpandPhase: { | |
| if (output_type == AudioFrame::kNormalSpeech) { | |
| test_state_ = kRecovered; | |
| } | |
| break; | |
| } | |
| case kRecovered: { | |
| break; | |
| } | |
| } | |
| } | |
| void GetAndVerifyOutput() override { | |
| AudioFrame output; | |
| GetOutputAudio(&output); | |
| UpdateState(output.speech_type_); | |
| if (test_state_ == kExpandPhase || test_state_ == kFadedExpandPhase) { | |
| // Don't verify the output in this phase of the test. | |
| return; | |
| } | |
| ASSERT_EQ(1u, output.num_channels_); | |
| const int16_t* output_data = output.data(); | |
| for (size_t i = 0; i < output.samples_per_channel_; ++i) { | |
| if (output_data[i] != 0) | |
| return; | |
| } | |
| EXPECT_TRUE(false) | |
| << "Expected at least one non-zero sample in each output block."; | |
| } | |
| int NumExpectedDecodeCalls(int num_loops) override { | |
| // Some packets at the end of the stream won't be decoded. When the jump in | |
| // timestamp happens, NetEq will do Expand during one GetAudio call. In the | |
| // next call it will decode the packet after the jump, but the net result is | |
| // that the delay increased by 1 packet. In another call, a Pre-emptive | |
| // Expand operation is performed, leading to delay increase by 1 packet. In | |
| // total, the test will end with a 2-packet delay, which results in the 2 | |
| // last packets not being decoded. | |
| return num_loops - 2; | |
| } | |
| TestStates test_state_; | |
| }; | |
| TEST_F(LargeTimestampJumpTest, JumpLongerThanHalfRange) { | |
| // Set the timestamp series to start at 2880, increase to 7200, then jump to | |
| // 2869342376. The sequence numbers start at 42076 and increase by 1 for each | |
| // packet, also when the timestamp jumps. | |
| static const uint16_t kStartSeqeunceNumber = 42076; | |
| static const uint32_t kStartTimestamp = 2880; | |
| static const uint32_t kJumpFromTimestamp = 7200; | |
| static const uint32_t kJumpToTimestamp = 2869342376; | |
| static_assert(kJumpFromTimestamp < kJumpToTimestamp, | |
| "timestamp jump should not result in wrap"); | |
| static_assert( | |
| static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) > 0x7FFFFFFF, | |
| "jump should be larger than half range"); | |
| // Replace the default RTP generator with one that jumps in timestamp. | |
| ResetRtpGenerator(new test::TimestampJumpRtpGenerator( | |
| samples_per_ms(), kStartSeqeunceNumber, kStartTimestamp, | |
| kJumpFromTimestamp, kJumpToTimestamp)); | |
| RunTest(130); // Run 130 laps @ 10 ms each in the test loop. | |
| EXPECT_EQ(kRecovered, test_state_); | |
| } | |
| TEST_F(LargeTimestampJumpTest, JumpLongerThanHalfRangeAndWrap) { | |
| // Make a jump larger than half the 32-bit timestamp range. Set the start | |
| // timestamp such that the jump will result in a wrap around. | |
| static const uint16_t kStartSeqeunceNumber = 42076; | |
| // Set the jump length slightly larger than 2^31. | |
| static const uint32_t kStartTimestamp = 3221223116; | |
| static const uint32_t kJumpFromTimestamp = 3221223216; | |
| static const uint32_t kJumpToTimestamp = 1073744278; | |
| static_assert(kJumpToTimestamp < kJumpFromTimestamp, | |
| "timestamp jump should result in wrap"); | |
| static_assert( | |
| static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) > 0x7FFFFFFF, | |
| "jump should be larger than half range"); | |
| // Replace the default RTP generator with one that jumps in timestamp. | |
| ResetRtpGenerator(new test::TimestampJumpRtpGenerator( | |
| samples_per_ms(), kStartSeqeunceNumber, kStartTimestamp, | |
| kJumpFromTimestamp, kJumpToTimestamp)); | |
| RunTest(130); // Run 130 laps @ 10 ms each in the test loop. | |
| EXPECT_EQ(kRecovered, test_state_); | |
| } | |
| class ShortTimestampJumpTest : public LargeTimestampJumpTest { | |
| protected: | |
| void UpdateState(AudioFrame::SpeechType output_type) override { | |
| switch (test_state_) { | |
| case kInitialPhase: { | |
| if (output_type == AudioFrame::kNormalSpeech) { | |
| test_state_ = kNormalPhase; | |
| } | |
| break; | |
| } | |
| case kNormalPhase: { | |
| if (output_type == AudioFrame::kPLC) { | |
| test_state_ = kExpandPhase; | |
| } | |
| break; | |
| } | |
| case kExpandPhase: { | |
| if (output_type == AudioFrame::kNormalSpeech) { | |
| test_state_ = kRecovered; | |
| } | |
| break; | |
| } | |
| case kRecovered: { | |
| break; | |
| } | |
| default: { FAIL(); } | |
| } | |
| } | |
| int NumExpectedDecodeCalls(int num_loops) override { | |
| // Some packets won't be decoded because of the timestamp jump. | |
| return num_loops - 2; | |
| } | |
| }; | |
| TEST_F(ShortTimestampJumpTest, JumpShorterThanHalfRange) { | |
| // Make a jump shorter than half the 32-bit timestamp range. Set the start | |
| // timestamp such that the jump will not result in a wrap around. | |
| static const uint16_t kStartSeqeunceNumber = 42076; | |
| // Set the jump length slightly smaller than 2^31. | |
| static const uint32_t kStartTimestamp = 4711; | |
| static const uint32_t kJumpFromTimestamp = 4811; | |
| static const uint32_t kJumpToTimestamp = 2147483747; | |
| static_assert(kJumpFromTimestamp < kJumpToTimestamp, | |
| "timestamp jump should not result in wrap"); | |
| static_assert( | |
| static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) < 0x7FFFFFFF, | |
| "jump should be smaller than half range"); | |
| // Replace the default RTP generator with one that jumps in timestamp. | |
| ResetRtpGenerator(new test::TimestampJumpRtpGenerator( | |
| samples_per_ms(), kStartSeqeunceNumber, kStartTimestamp, | |
| kJumpFromTimestamp, kJumpToTimestamp)); | |
| RunTest(130); // Run 130 laps @ 10 ms each in the test loop. | |
| EXPECT_EQ(kRecovered, test_state_); | |
| } | |
| TEST_F(ShortTimestampJumpTest, JumpShorterThanHalfRangeAndWrap) { | |
| // Make a jump shorter than half the 32-bit timestamp range. Set the start | |
| // timestamp such that the jump will result in a wrap around. | |
| static const uint16_t kStartSeqeunceNumber = 42076; | |
| // Set the jump length slightly smaller than 2^31. | |
| static const uint32_t kStartTimestamp = 3221227827; | |
| static const uint32_t kJumpFromTimestamp = 3221227927; | |
| static const uint32_t kJumpToTimestamp = 1073739567; | |
| static_assert(kJumpToTimestamp < kJumpFromTimestamp, | |
| "timestamp jump should result in wrap"); | |
| static_assert( | |
| static_cast<uint32_t>(kJumpToTimestamp - kJumpFromTimestamp) < 0x7FFFFFFF, | |
| "jump should be smaller than half range"); | |
| // Replace the default RTP generator with one that jumps in timestamp. | |
| ResetRtpGenerator(new test::TimestampJumpRtpGenerator( | |
| samples_per_ms(), kStartSeqeunceNumber, kStartTimestamp, | |
| kJumpFromTimestamp, kJumpToTimestamp)); | |
| RunTest(130); // Run 130 laps @ 10 ms each in the test loop. | |
| EXPECT_EQ(kRecovered, test_state_); | |
| } | |
| } // namespace webrtc |