| /* |
| * 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 <cstring> |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "webrtc/base/arraysize.h" |
| #include "webrtc/base/safe_conversions.h" |
| #include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h" |
| #include "webrtc/modules/audio_coding/main/acm2/codec_owner.h" |
| |
| namespace webrtc { |
| namespace acm2 { |
| |
| using ::testing::Return; |
| using ::testing::InSequence; |
| |
| 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; |
| } // namespace |
| |
| class CodecOwnerTest : public ::testing::Test { |
| protected: |
| CodecOwnerTest() : timestamp_(0) {} |
| |
| void CreateCodec() { |
| ASSERT_TRUE( |
| codec_owner_.SetEncoders(kDefaultCodecInst, kCngPt, VADNormal, -1)); |
| } |
| |
| void EncodeAndVerify(size_t expected_out_length, |
| uint32_t expected_timestamp, |
| int expected_payload_type, |
| int expected_send_even_if_empty) { |
| uint8_t out[kPacketSizeSamples]; |
| AudioEncoder::EncodedInfo encoded_info; |
| encoded_info = codec_owner_.Encoder()->Encode( |
| timestamp_, kZeroData, kDataLengthSamples, kPacketSizeSamples, 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); |
| } |
| |
| // 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) { |
| MockAudioEncoder speech_encoder; |
| EXPECT_CALL(speech_encoder, NumChannels()) |
| .WillRepeatedly(Return(1)); |
| EXPECT_CALL(speech_encoder, Max10MsFramesInAPacket()) |
| .WillRepeatedly(Return(2)); |
| EXPECT_CALL(speech_encoder, SampleRateHz()) |
| .WillRepeatedly(Return(8000)); |
| { |
| InSequence s; |
| EXPECT_CALL(speech_encoder, Mark("start off")); |
| EXPECT_CALL(speech_encoder, Mark("switch on")); |
| if (use_cng || use_red) |
| EXPECT_CALL(speech_encoder, Reset()); |
| EXPECT_CALL(speech_encoder, Mark("start on")); |
| if (use_cng || use_red) |
| EXPECT_CALL(speech_encoder, Reset()); |
| EXPECT_CALL(speech_encoder, Mark("switch off")); |
| EXPECT_CALL(speech_encoder, Die()); |
| } |
| |
| int cng_pt = use_cng ? 17 : -1; |
| int red_pt = use_red ? 19 : -1; |
| speech_encoder.Mark("start off"); |
| codec_owner_.SetEncoders(&speech_encoder, -1, VADNormal, -1); |
| speech_encoder.Mark("switch on"); |
| codec_owner_.ChangeCngAndRed(cng_pt, VADNormal, red_pt); |
| speech_encoder.Mark("start on"); |
| codec_owner_.SetEncoders(&speech_encoder, cng_pt, VADNormal, red_pt); |
| speech_encoder.Mark("switch off"); |
| codec_owner_.ChangeCngAndRed(-1, VADNormal, -1); |
| } |
| |
| CodecOwner codec_owner_; |
| uint32_t timestamp_; |
| }; |
| |
| // 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(CodecOwnerTest, 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_F(CodecOwnerTest, ExternalEncoder) { |
| MockAudioEncoder external_encoder; |
| codec_owner_.SetEncoders(&external_encoder, -1, VADNormal, -1); |
| const int kSampleRateHz = 8000; |
| const int kPacketSizeSamples = kSampleRateHz / 100; |
| int16_t audio[kPacketSizeSamples] = {0}; |
| uint8_t encoded[kPacketSizeSamples]; |
| AudioEncoder::EncodedInfo info; |
| EXPECT_CALL(external_encoder, SampleRateHz()) |
| .WillRepeatedly(Return(kSampleRateHz)); |
| |
| { |
| InSequence s; |
| info.encoded_timestamp = 0; |
| EXPECT_CALL(external_encoder, |
| EncodeInternal(0, audio, arraysize(encoded), encoded)) |
| .WillOnce(Return(info)); |
| EXPECT_CALL(external_encoder, Mark("A")); |
| EXPECT_CALL(external_encoder, Mark("B")); |
| info.encoded_timestamp = 2; |
| EXPECT_CALL(external_encoder, |
| EncodeInternal(2, audio, arraysize(encoded), encoded)) |
| .WillOnce(Return(info)); |
| EXPECT_CALL(external_encoder, Die()); |
| } |
| |
| info = codec_owner_.Encoder()->Encode(0, audio, arraysize(audio), |
| arraysize(encoded), encoded); |
| EXPECT_EQ(0u, info.encoded_timestamp); |
| external_encoder.Mark("A"); |
| |
| // Change to internal encoder. |
| CodecInst codec_inst = kDefaultCodecInst; |
| codec_inst.pacsize = kPacketSizeSamples; |
| ASSERT_TRUE(codec_owner_.SetEncoders(codec_inst, -1, VADNormal, -1)); |
| // Don't expect any more calls to the external encoder. |
| info = codec_owner_.Encoder()->Encode(1, audio, arraysize(audio), |
| arraysize(encoded), encoded); |
| external_encoder.Mark("B"); |
| |
| // Change back to external encoder again. |
| codec_owner_.SetEncoders(&external_encoder, -1, VADNormal, -1); |
| info = codec_owner_.Encoder()->Encode(2, audio, arraysize(audio), |
| arraysize(encoded), encoded); |
| EXPECT_EQ(2u, info.encoded_timestamp); |
| } |
| |
| TEST_F(CodecOwnerTest, CngResetsSpeechEncoder) { |
| TestCngAndRedResetSpeechEncoder(true, false); |
| } |
| |
| TEST_F(CodecOwnerTest, RedResetsSpeechEncoder) { |
| TestCngAndRedResetSpeechEncoder(false, true); |
| } |
| |
| TEST_F(CodecOwnerTest, CngAndRedResetsSpeechEncoder) { |
| TestCngAndRedResetSpeechEncoder(true, true); |
| } |
| |
| TEST_F(CodecOwnerTest, NoCngAndRedNoSpeechEncoderReset) { |
| TestCngAndRedResetSpeechEncoder(false, false); |
| } |
| |
| TEST_F(CodecOwnerTest, SetEncodersError) { |
| CodecInst codec_inst = kDefaultCodecInst; |
| static const char bad_name[] = "Robert'); DROP TABLE Students;"; |
| std::memcpy(codec_inst.plname, bad_name, sizeof bad_name); |
| EXPECT_FALSE(codec_owner_.SetEncoders(codec_inst, -1, VADNormal, -1)); |
| } |
| |
| } // namespace acm2 |
| } // namespace webrtc |