| /* |
| * Copyright (c) 2015 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 <memory> |
| |
| #include "webrtc/modules/audio_coding/acm2/rent_a_codec.h" |
| #include "webrtc/rtc_base/arraysize.h" |
| #include "webrtc/test/gtest.h" |
| #include "webrtc/test/mock_audio_encoder.h" |
| |
| namespace webrtc { |
| namespace acm2 { |
| |
| using ::testing::Return; |
| |
| namespace { |
| |
| const int kDataLengthSamples = 80; |
| const int kPacketSizeSamples = 2 * kDataLengthSamples; |
| const int16_t kZeroData[kDataLengthSamples] = {0}; |
| const CodecInst kDefaultCodecInst = {0, "pcmu", 8000, kPacketSizeSamples, |
| 1, 64000}; |
| const int kCngPt = 13; |
| |
| class Marker final { |
| public: |
| MOCK_METHOD1(Mark, void(std::string desc)); |
| }; |
| |
| } // namespace |
| |
| class RentACodecTestF : public ::testing::Test { |
| protected: |
| void CreateCodec() { |
| auto speech_encoder = rent_a_codec_.RentEncoder(kDefaultCodecInst); |
| ASSERT_TRUE(speech_encoder); |
| RentACodec::StackParameters param; |
| param.use_cng = true; |
| param.speech_encoder = std::move(speech_encoder); |
| encoder_ = rent_a_codec_.RentEncoderStack(¶m); |
| } |
| |
| void EncodeAndVerify(size_t expected_out_length, |
| uint32_t expected_timestamp, |
| int expected_payload_type, |
| int expected_send_even_if_empty) { |
| rtc::Buffer out; |
| AudioEncoder::EncodedInfo encoded_info; |
| encoded_info = |
| encoder_->Encode(timestamp_, kZeroData, &out); |
| timestamp_ += kDataLengthSamples; |
| EXPECT_TRUE(encoded_info.redundant.empty()); |
| EXPECT_EQ(expected_out_length, encoded_info.encoded_bytes); |
| EXPECT_EQ(expected_timestamp, encoded_info.encoded_timestamp); |
| if (expected_payload_type >= 0) |
| EXPECT_EQ(expected_payload_type, encoded_info.payload_type); |
| if (expected_send_even_if_empty >= 0) |
| EXPECT_EQ(static_cast<bool>(expected_send_even_if_empty), |
| encoded_info.send_even_if_empty); |
| } |
| |
| RentACodec rent_a_codec_; |
| std::unique_ptr<AudioEncoder> encoder_; |
| uint32_t timestamp_ = 0; |
| }; |
| |
| // This test verifies that CNG frames are delivered as expected. Since the frame |
| // size is set to 20 ms, we expect the first encode call to produce no output |
| // (which is signaled as 0 bytes output of type kNoEncoding). The next encode |
| // call should produce one SID frame of 9 bytes. The third call should not |
| // result in any output (just like the first one). The fourth and final encode |
| // call should produce an "empty frame", which is like no output, but with |
| // AudioEncoder::EncodedInfo::send_even_if_empty set to true. (The reason to |
| // produce an empty frame is to drive sending of DTMF packets in the RTP/RTCP |
| // module.) |
| TEST_F(RentACodecTestF, VerifyCngFrames) { |
| CreateCodec(); |
| uint32_t expected_timestamp = timestamp_; |
| // Verify no frame. |
| { |
| SCOPED_TRACE("First encoding"); |
| EncodeAndVerify(0, expected_timestamp, -1, -1); |
| } |
| |
| // Verify SID frame delivered. |
| { |
| SCOPED_TRACE("Second encoding"); |
| EncodeAndVerify(9, expected_timestamp, kCngPt, 1); |
| } |
| |
| // Verify no frame. |
| { |
| SCOPED_TRACE("Third encoding"); |
| EncodeAndVerify(0, expected_timestamp, -1, -1); |
| } |
| |
| // Verify NoEncoding. |
| expected_timestamp += 2 * kDataLengthSamples; |
| { |
| SCOPED_TRACE("Fourth encoding"); |
| EncodeAndVerify(0, expected_timestamp, kCngPt, 1); |
| } |
| } |
| |
| TEST(RentACodecTest, ExternalEncoder) { |
| const int kSampleRateHz = 8000; |
| auto* external_encoder = new MockAudioEncoder; |
| EXPECT_CALL(*external_encoder, SampleRateHz()) |
| .WillRepeatedly(Return(kSampleRateHz)); |
| EXPECT_CALL(*external_encoder, NumChannels()).WillRepeatedly(Return(1)); |
| EXPECT_CALL(*external_encoder, SetFec(false)).WillRepeatedly(Return(true)); |
| |
| RentACodec rac; |
| RentACodec::StackParameters param; |
| param.speech_encoder = std::unique_ptr<AudioEncoder>(external_encoder); |
| std::unique_ptr<AudioEncoder> encoder_stack = rac.RentEncoderStack(¶m); |
| EXPECT_EQ(external_encoder, encoder_stack.get()); |
| const int kPacketSizeSamples = kSampleRateHz / 100; |
| int16_t audio[kPacketSizeSamples] = {0}; |
| rtc::Buffer encoded; |
| AudioEncoder::EncodedInfo info; |
| |
| Marker marker; |
| { |
| ::testing::InSequence s; |
| info.encoded_timestamp = 0; |
| EXPECT_CALL( |
| *external_encoder, |
| EncodeImpl(0, rtc::ArrayView<const int16_t>(audio), &encoded)) |
| .WillOnce(Return(info)); |
| EXPECT_CALL(marker, Mark("A")); |
| EXPECT_CALL(marker, Mark("B")); |
| EXPECT_CALL(*external_encoder, Die()); |
| EXPECT_CALL(marker, Mark("C")); |
| } |
| |
| info = encoder_stack->Encode(0, audio, &encoded); |
| EXPECT_EQ(0u, info.encoded_timestamp); |
| marker.Mark("A"); |
| |
| // Change to internal encoder. |
| CodecInst codec_inst = kDefaultCodecInst; |
| codec_inst.pacsize = kPacketSizeSamples; |
| param.speech_encoder = rac.RentEncoder(codec_inst); |
| ASSERT_TRUE(param.speech_encoder); |
| AudioEncoder* enc = param.speech_encoder.get(); |
| std::unique_ptr<AudioEncoder> stack = rac.RentEncoderStack(¶m); |
| EXPECT_EQ(enc, stack.get()); |
| |
| // Don't expect any more calls to the external encoder. |
| info = stack->Encode(1, audio, &encoded); |
| marker.Mark("B"); |
| encoder_stack.reset(); |
| marker.Mark("C"); |
| } |
| |
| // Verify that the speech encoder's Reset method is called when CNG or RED |
| // (or both) are switched on, but not when they're switched off. |
| void TestCngAndRedResetSpeechEncoder(bool use_cng, bool use_red) { |
| auto make_enc = [] { |
| auto speech_encoder = |
| std::unique_ptr<MockAudioEncoder>(new MockAudioEncoder); |
| EXPECT_CALL(*speech_encoder, NumChannels()).WillRepeatedly(Return(1)); |
| EXPECT_CALL(*speech_encoder, Max10MsFramesInAPacket()) |
| .WillRepeatedly(Return(2)); |
| EXPECT_CALL(*speech_encoder, SampleRateHz()).WillRepeatedly(Return(8000)); |
| EXPECT_CALL(*speech_encoder, SetFec(false)).WillRepeatedly(Return(true)); |
| return speech_encoder; |
| }; |
| auto speech_encoder1 = make_enc(); |
| auto speech_encoder2 = make_enc(); |
| Marker marker; |
| { |
| ::testing::InSequence s; |
| EXPECT_CALL(marker, Mark("disabled")); |
| EXPECT_CALL(*speech_encoder1, Die()); |
| EXPECT_CALL(marker, Mark("enabled")); |
| if (use_cng || use_red) |
| EXPECT_CALL(*speech_encoder2, Reset()); |
| EXPECT_CALL(*speech_encoder2, Die()); |
| } |
| |
| RentACodec::StackParameters param1, param2; |
| param1.speech_encoder = std::move(speech_encoder1); |
| param2.speech_encoder = std::move(speech_encoder2); |
| param2.use_cng = use_cng; |
| param2.use_red = use_red; |
| marker.Mark("disabled"); |
| RentACodec rac; |
| rac.RentEncoderStack(¶m1); |
| marker.Mark("enabled"); |
| rac.RentEncoderStack(¶m2); |
| } |
| |
| TEST(RentACodecTest, CngResetsSpeechEncoder) { |
| TestCngAndRedResetSpeechEncoder(true, false); |
| } |
| |
| TEST(RentACodecTest, RedResetsSpeechEncoder) { |
| TestCngAndRedResetSpeechEncoder(false, true); |
| } |
| |
| TEST(RentACodecTest, CngAndRedResetsSpeechEncoder) { |
| TestCngAndRedResetSpeechEncoder(true, true); |
| } |
| |
| TEST(RentACodecTest, NoCngAndRedNoSpeechEncoderReset) { |
| TestCngAndRedResetSpeechEncoder(false, false); |
| } |
| |
| TEST(RentACodecTest, RentEncoderError) { |
| const CodecInst codec_inst = { |
| 0, "Robert'); DROP TABLE Students;", 8000, 160, 1, 64000}; |
| RentACodec rent_a_codec; |
| EXPECT_FALSE(rent_a_codec.RentEncoder(codec_inst)); |
| } |
| |
| TEST(RentACodecTest, RentEncoderStackWithoutSpeechEncoder) { |
| RentACodec::StackParameters sp; |
| EXPECT_EQ(nullptr, sp.speech_encoder); |
| EXPECT_EQ(nullptr, RentACodec().RentEncoderStack(&sp)); |
| } |
| |
| } // namespace acm2 |
| } // namespace webrtc |