blob: 9b960afa82b76ad0f2903e15c620609a2dbf9be6 [file] [log] [blame]
/*
* Copyright (c) 2012 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 "testing/gtest/include/gtest/gtest.h"
#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h"
#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h"
#include "webrtc/modules/audio_coding/main/test/PCMFile.h"
#include "webrtc/modules/audio_coding/main/test/utility.h"
#include "webrtc/modules/interface/module_common_types.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/typedefs.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/gtest_disable.h"
namespace webrtc {
class DualStreamTest : public AudioPacketizationCallback,
public ::testing::Test {
protected:
DualStreamTest();
~DualStreamTest();
void RunTest(int frame_size_primary_samples,
int num_channels_primary,
int sampling_rate,
bool start_in_sync,
int num_channels_input);
void ApiTest();
virtual int32_t SendData(
FrameType frameType,
uint8_t payload_type,
uint32_t timestamp,
const uint8_t* payload_data,
size_t payload_size,
const RTPFragmentationHeader* fragmentation) OVERRIDE;
void Perform(bool start_in_sync, int num_channels_input);
void InitializeSender(int frame_size_primary_samples,
int num_channels_primary, int sampling_rate);
void PopulateCodecInstances(int frame_size_primary_ms,
int num_channels_primary, int sampling_rate);
void Validate(bool start_in_sync, size_t tolerance);
bool EqualTimestamp(int stream, int position);
size_t EqualPayloadLength(int stream, int position);
bool EqualPayloadData(int stream, int position);
static const int kMaxNumStoredPayloads = 2;
enum {
kPrimary = 0,
kSecondary,
kMaxNumStreams
};
scoped_ptr<AudioCodingModule> acm_dual_stream_;
scoped_ptr<AudioCodingModule> acm_ref_primary_;
scoped_ptr<AudioCodingModule> acm_ref_secondary_;
CodecInst primary_encoder_;
CodecInst secondary_encoder_;
CodecInst red_encoder_;
int payload_ref_is_stored_[kMaxNumStreams][kMaxNumStoredPayloads];
int payload_dual_is_stored_[kMaxNumStreams][kMaxNumStoredPayloads];
uint32_t timestamp_ref_[kMaxNumStreams][kMaxNumStoredPayloads];
uint32_t timestamp_dual_[kMaxNumStreams][kMaxNumStoredPayloads];
size_t payload_len_ref_[kMaxNumStreams][kMaxNumStoredPayloads];
size_t payload_len_dual_[kMaxNumStreams][kMaxNumStoredPayloads];
uint8_t payload_data_ref_[kMaxNumStreams][MAX_PAYLOAD_SIZE_BYTE
* kMaxNumStoredPayloads];
uint8_t payload_data_dual_[kMaxNumStreams][MAX_PAYLOAD_SIZE_BYTE
* kMaxNumStoredPayloads];
int num_received_payloads_dual_[kMaxNumStreams];
int num_received_payloads_ref_[kMaxNumStreams];
int num_compared_payloads_[kMaxNumStreams];
uint32_t last_timestamp_[kMaxNumStreams];
bool received_payload_[kMaxNumStreams];
};
DualStreamTest::DualStreamTest()
: acm_dual_stream_(AudioCodingModule::Create(0)),
acm_ref_primary_(AudioCodingModule::Create(1)),
acm_ref_secondary_(AudioCodingModule::Create(2)),
payload_ref_is_stored_(),
payload_dual_is_stored_(),
timestamp_ref_(),
num_received_payloads_dual_(),
num_received_payloads_ref_(),
num_compared_payloads_(),
last_timestamp_(),
received_payload_() {}
DualStreamTest::~DualStreamTest() {}
void DualStreamTest::PopulateCodecInstances(int frame_size_primary_ms,
int num_channels_primary,
int sampling_rate) {
CodecInst my_codec;
// Invalid values. To check later on if the codec are found in the database.
primary_encoder_.pltype = -1;
secondary_encoder_.pltype = -1;
red_encoder_.pltype = -1;
for (int n = 0; n < AudioCodingModule::NumberOfCodecs(); n++) {
AudioCodingModule::Codec(n, &my_codec);
if (strcmp(my_codec.plname, "ISAC") == 0
&& my_codec.plfreq == sampling_rate) {
my_codec.rate = 32000;
my_codec.pacsize = 30 * sampling_rate / 1000;
memcpy(&secondary_encoder_, &my_codec, sizeof(my_codec));
} else if (strcmp(my_codec.plname, "L16") == 0
&& my_codec.channels == num_channels_primary
&& my_codec.plfreq == sampling_rate) {
my_codec.pacsize = frame_size_primary_ms * sampling_rate / 1000;
memcpy(&primary_encoder_, &my_codec, sizeof(my_codec));
} else if (strcmp(my_codec.plname, "red") == 0) {
memcpy(&red_encoder_, &my_codec, sizeof(my_codec));
}
}
ASSERT_GE(primary_encoder_.pltype, 0);
ASSERT_GE(secondary_encoder_.pltype, 0);
ASSERT_GE(red_encoder_.pltype, 0);
}
void DualStreamTest::InitializeSender(int frame_size_primary_samples,
int num_channels_primary,
int sampling_rate) {
ASSERT_TRUE(acm_dual_stream_.get() != NULL);
ASSERT_TRUE(acm_ref_primary_.get() != NULL);
ASSERT_TRUE(acm_ref_secondary_.get() != NULL);
ASSERT_EQ(0, acm_dual_stream_->InitializeSender());
ASSERT_EQ(0, acm_ref_primary_->InitializeSender());
ASSERT_EQ(0, acm_ref_secondary_->InitializeSender());
PopulateCodecInstances(frame_size_primary_samples, num_channels_primary,
sampling_rate);
ASSERT_EQ(0, acm_ref_primary_->RegisterSendCodec(primary_encoder_));
ASSERT_EQ(0, acm_ref_secondary_->RegisterSendCodec(secondary_encoder_));
ASSERT_EQ(0, acm_dual_stream_->RegisterSendCodec(primary_encoder_));
ASSERT_EQ(0,
acm_dual_stream_->RegisterSecondarySendCodec(secondary_encoder_));
ASSERT_EQ(0, acm_ref_primary_->RegisterTransportCallback(this));
ASSERT_EQ(0, acm_ref_secondary_->RegisterTransportCallback(this));
ASSERT_EQ(0, acm_dual_stream_->RegisterTransportCallback(this));
}
void DualStreamTest::Perform(bool start_in_sync, int num_channels_input) {
PCMFile pcm_file;
std::string file_name = test::ResourcePath(
(num_channels_input == 1) ?
"audio_coding/testfile32kHz" : "audio_coding/teststereo32kHz",
"pcm");
pcm_file.Open(file_name, 32000, "rb");
pcm_file.ReadStereo(num_channels_input == 2);
AudioFrame audio_frame;
size_t tolerance = 0;
if (num_channels_input == 2 && primary_encoder_.channels == 2
&& secondary_encoder_.channels == 1) {
tolerance = 12;
}
if (!start_in_sync) {
pcm_file.Read10MsData(audio_frame);
// Unregister secondary codec and feed only the primary
acm_dual_stream_->UnregisterSecondarySendCodec();
EXPECT_EQ(0, acm_dual_stream_->Add10MsData(audio_frame));
EXPECT_EQ(0, acm_ref_primary_->Add10MsData(audio_frame));
ASSERT_EQ(0,
acm_dual_stream_->RegisterSecondarySendCodec(secondary_encoder_));
}
const int kNumFramesToProcess = 100;
int frame_cntr = 0;
while (!pcm_file.EndOfFile() && frame_cntr < kNumFramesToProcess) {
pcm_file.Read10MsData(audio_frame);
frame_cntr++;
EXPECT_EQ(0, acm_dual_stream_->Add10MsData(audio_frame));
EXPECT_EQ(0, acm_ref_primary_->Add10MsData(audio_frame));
EXPECT_EQ(0, acm_ref_secondary_->Add10MsData(audio_frame));
EXPECT_GE(acm_dual_stream_->Process(), 0);
EXPECT_GE(acm_ref_primary_->Process(), 0);
EXPECT_GE(acm_ref_secondary_->Process(), 0);
if (start_in_sync || frame_cntr > 7) {
// If we haven't started in sync the first few audio frames might
// slightly differ due to the difference in the state of the resamplers
// of dual-ACM and reference-ACM.
Validate(start_in_sync, tolerance);
} else {
// SendData stores the payloads, if we are not comparing we have to free
// the space by resetting these flags.
memset(payload_ref_is_stored_, 0, sizeof(payload_ref_is_stored_));
memset(payload_dual_is_stored_, 0, sizeof(payload_dual_is_stored_));
}
}
pcm_file.Close();
// Make sure that number of received payloads match. In case of secondary
// encoder, the dual-stream might deliver one lesser payload. The reason is
// that some secondary payloads are stored to be sent with a payload generated
// later and the input file may end before the "next" payload .
EXPECT_EQ(num_received_payloads_ref_[kPrimary],
num_received_payloads_dual_[kPrimary]);
EXPECT_TRUE(
num_received_payloads_ref_[kSecondary]
== num_received_payloads_dual_[kSecondary]
|| num_received_payloads_ref_[kSecondary]
== (num_received_payloads_dual_[kSecondary] + 1));
// Make sure all received payloads are compared.
if (start_in_sync) {
EXPECT_EQ(num_received_payloads_dual_[kPrimary],
num_compared_payloads_[kPrimary]);
EXPECT_EQ(num_received_payloads_dual_[kSecondary],
num_compared_payloads_[kSecondary]);
} else {
// In asynchronous test we don't compare couple of first frames, so we
// should account for them in our counting.
EXPECT_GE(num_compared_payloads_[kPrimary],
num_received_payloads_dual_[kPrimary] - 4);
EXPECT_GE(num_compared_payloads_[kSecondary],
num_received_payloads_dual_[kSecondary] - 4);
}
}
bool DualStreamTest::EqualTimestamp(int stream_index, int position) {
if (timestamp_dual_[stream_index][position]
!= timestamp_ref_[stream_index][position]) {
return false;
}
return true;
}
size_t DualStreamTest::EqualPayloadLength(int stream_index, int position) {
size_t dual = payload_len_dual_[stream_index][position];
size_t ref = payload_len_ref_[stream_index][position];
return (dual > ref) ? (dual - ref) : (ref - dual);
}
bool DualStreamTest::EqualPayloadData(int stream_index, int position) {
assert(
payload_len_dual_[stream_index][position]
== payload_len_ref_[stream_index][position]);
int offset = position * MAX_PAYLOAD_SIZE_BYTE;
for (size_t n = 0; n < payload_len_dual_[stream_index][position]; n++) {
if (payload_data_dual_[stream_index][offset + n]
!= payload_data_ref_[stream_index][offset + n]) {
return false;
}
}
return true;
}
void DualStreamTest::Validate(bool start_in_sync, size_t tolerance) {
for (int stream_index = 0; stream_index < kMaxNumStreams; stream_index++) {
size_t my_tolerance = stream_index == kPrimary ? 0 : tolerance;
for (int position = 0; position < kMaxNumStoredPayloads; position++) {
if (payload_ref_is_stored_[stream_index][position] == 1
&& payload_dual_is_stored_[stream_index][position] == 1) {
// Check timestamps only if codecs started in sync or it is primary.
if (start_in_sync || stream_index == 0)
EXPECT_TRUE(EqualTimestamp(stream_index, position));
EXPECT_LE(EqualPayloadLength(stream_index, position), my_tolerance);
if (my_tolerance == 0)
EXPECT_TRUE(EqualPayloadData(stream_index, position));
num_compared_payloads_[stream_index]++;
payload_ref_is_stored_[stream_index][position] = 0;
payload_dual_is_stored_[stream_index][position] = 0;
}
}
}
}
int32_t DualStreamTest::SendData(FrameType frameType, uint8_t payload_type,
uint32_t timestamp,
const uint8_t* payload_data,
size_t payload_size,
const RTPFragmentationHeader* fragmentation) {
int position;
int stream_index;
if (payload_type == red_encoder_.pltype) {
if (fragmentation == NULL) {
assert(false);
return -1;
}
// As the oldest payloads are in the higher indices of fragmentation,
// to be able to check the increment of timestamps are correct we loop
// backward.
for (int n = fragmentation->fragmentationVectorSize - 1; n >= 0; --n) {
if (fragmentation->fragmentationPlType[n] == primary_encoder_.pltype) {
// Received primary payload from dual stream.
stream_index = kPrimary;
} else if (fragmentation->fragmentationPlType[n]
== secondary_encoder_.pltype) {
// Received secondary payload from dual stream.
stream_index = kSecondary;
} else {
assert(false);
return -1;
}
num_received_payloads_dual_[stream_index]++;
if (payload_dual_is_stored_[stream_index][0] == 0) {
position = 0;
} else if (payload_dual_is_stored_[stream_index][1] == 0) {
position = 1;
} else {
assert(false);
return -1;
}
timestamp_dual_[stream_index][position] = timestamp
- fragmentation->fragmentationTimeDiff[n];
payload_len_dual_[stream_index][position] = fragmentation
->fragmentationLength[n];
memcpy(
&payload_data_dual_[stream_index][position * MAX_PAYLOAD_SIZE_BYTE],
&payload_data[fragmentation->fragmentationOffset[n]],
fragmentation->fragmentationLength[n]);
payload_dual_is_stored_[stream_index][position] = 1;
// Check if timestamps are incremented correctly.
if (received_payload_[stream_index]) {
int t = timestamp_dual_[stream_index][position]
- last_timestamp_[stream_index];
if ((stream_index == kPrimary) && (t != primary_encoder_.pacsize)) {
assert(false);
return -1;
}
if ((stream_index == kSecondary) && (t != secondary_encoder_.pacsize)) {
assert(false);
return -1;
}
} else {
received_payload_[stream_index] = true;
}
last_timestamp_[stream_index] = timestamp_dual_[stream_index][position];
}
} else {
if (fragmentation != NULL) {
assert(false);
return -1;
}
if (payload_type == primary_encoder_.pltype) {
stream_index = kPrimary;
} else if (payload_type == secondary_encoder_.pltype) {
stream_index = kSecondary;
} else {
assert(false);
return -1;
}
num_received_payloads_ref_[stream_index]++;
if (payload_ref_is_stored_[stream_index][0] == 0) {
position = 0;
} else if (payload_ref_is_stored_[stream_index][1] == 0) {
position = 1;
} else {
assert(false);
return -1;
}
timestamp_ref_[stream_index][position] = timestamp;
payload_len_ref_[stream_index][position] = payload_size;
memcpy(&payload_data_ref_[stream_index][position * MAX_PAYLOAD_SIZE_BYTE],
payload_data, payload_size);
payload_ref_is_stored_[stream_index][position] = 1;
}
return 0;
}
// Mono input, mono primary WB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimaryWb20Ms)) {
InitializeSender(20, 1, 16000);
Perform(true, 1);
}
// Mono input, stereo primary WB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncMonoInput_StereoPrimaryWb20Ms)) {
InitializeSender(20, 2, 16000);
Perform(true, 1);
}
// Mono input, mono primary SWB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimarySwb20Ms)) {
InitializeSender(20, 1, 32000);
Perform(true, 1);
}
// Mono input, stereo primary SWB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncMonoInputStereoPrimarySwb20Ms)) {
InitializeSender(20, 2, 32000);
Perform(true, 1);
}
// Mono input, mono primary WB 40 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncMonoInputMonoPrimaryWb40Ms)) {
InitializeSender(40, 1, 16000);
Perform(true, 1);
}
// Mono input, stereo primary WB 40 ms frame
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncMonoInputStereoPrimaryWb40Ms)) {
InitializeSender(40, 2, 16000);
Perform(true, 1);
}
// Stereo input, mono primary WB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimaryWb20Ms)) {
InitializeSender(20, 1, 16000);
Perform(true, 2);
}
// Stereo input, stereo primary WB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimaryWb20Ms)) {
InitializeSender(20, 2, 16000);
Perform(true, 2);
}
// Stereo input, mono primary SWB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimarySwb20Ms)) {
InitializeSender(20, 1, 32000);
Perform(true, 2);
}
// Stereo input, stereo primary SWB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimarySwb20Ms)) {
InitializeSender(20, 2, 32000);
Perform(true, 2);
}
// Stereo input, mono primary WB 40 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncStereoInputMonoPrimaryWb40Ms)) {
InitializeSender(40, 1, 16000);
Perform(true, 2);
}
// Stereo input, stereo primary WB 40 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactSyncStereoInputStereoPrimaryWb40Ms)) {
InitializeSender(40, 2, 16000);
Perform(true, 2);
}
// Asynchronous test, ACM is fed with data then secondary coder is registered.
// Mono input, mono primary WB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactAsyncMonoInputMonoPrimaryWb20Ms)) {
InitializeSender(20, 1, 16000);
Perform(false, 1);
}
// Mono input, mono primary WB 20 ms frame.
TEST_F(DualStreamTest,
DISABLED_ON_ANDROID(BitExactAsyncMonoInputMonoPrimaryWb40Ms)) {
InitializeSender(40, 1, 16000);
Perform(false, 1);
}
TEST_F(DualStreamTest, DISABLED_ON_ANDROID(Api)) {
PopulateCodecInstances(20, 1, 16000);
CodecInst my_codec;
ASSERT_EQ(0, acm_dual_stream_->InitializeSender());
ASSERT_EQ(-1, acm_dual_stream_->SecondarySendCodec(&my_codec));
// Not allowed to register secondary codec if primary is not registered yet.
ASSERT_EQ(-1,
acm_dual_stream_->RegisterSecondarySendCodec(secondary_encoder_));
ASSERT_EQ(-1, acm_dual_stream_->SecondarySendCodec(&my_codec));
ASSERT_EQ(0, acm_dual_stream_->RegisterSendCodec(primary_encoder_));
ASSERT_EQ(0, acm_dual_stream_->SetVAD(true, true, VADNormal));
// Make sure vad is activated.
bool vad_status;
bool dtx_status;
ACMVADMode vad_mode;
EXPECT_EQ(0, acm_dual_stream_->VAD(&vad_status, &dtx_status, &vad_mode));
EXPECT_TRUE(vad_status);
EXPECT_TRUE(dtx_status);
EXPECT_EQ(VADNormal, vad_mode);
ASSERT_EQ(0,
acm_dual_stream_->RegisterSecondarySendCodec(secondary_encoder_));
ASSERT_EQ(0, acm_dual_stream_->SecondarySendCodec(&my_codec));
ASSERT_EQ(0, memcmp(&my_codec, &secondary_encoder_, sizeof(my_codec)));
// Test if VAD get disabled after registering secondary codec.
EXPECT_EQ(0, acm_dual_stream_->VAD(&vad_status, &dtx_status, &vad_mode));
EXPECT_FALSE(vad_status);
EXPECT_FALSE(dtx_status);
// Activating VAD should fail.
ASSERT_EQ(-1, acm_dual_stream_->SetVAD(true, true, VADNormal));
// Unregister secondary encoder and it should be possible to activate VAD.
acm_dual_stream_->UnregisterSecondarySendCodec();
// Should fail.
ASSERT_EQ(-1, acm_dual_stream_->SecondarySendCodec(&my_codec));
ASSERT_EQ(0, acm_dual_stream_->SetVAD(true, true, VADVeryAggr));
// Make sure VAD is activated.
EXPECT_EQ(0, acm_dual_stream_->VAD(&vad_status, &dtx_status, &vad_mode));
EXPECT_TRUE(vad_status);
EXPECT_TRUE(dtx_status);
EXPECT_EQ(VADVeryAggr, vad_mode);
}
} // namespace webrtc