|  | /* | 
|  | *  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 "common_types.h" | 
|  | #include "modules/audio_coding/acm2/rent_a_codec.h" | 
|  | #include "rtc_base/arraysize.h" | 
|  | #include "test/gtest.h" | 
|  | #include "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(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(marker, Mark("enabled")); | 
|  | if (use_cng || use_red) | 
|  | EXPECT_CALL(*speech_encoder2, Reset()); | 
|  | } | 
|  |  | 
|  | 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 |