Add support for multi-channel DTMF tone generation
This CL opens up support for DTMF tones to be played to multi-channel outputs. The same tones are replicated across all channels. Unit tests are updated. Also adding a new method AudioMultiVector::CopyChannel. BUG=crbug/407114 R=tina.legrand@webrtc.org Review URL: https://webrtc-codereview.appspot.com/20279004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7056 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
@@ -202,6 +202,12 @@ bool AudioMultiVector::Empty() const {
|
|||||||
return channels_[0]->Empty();
|
return channels_[0]->Empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioMultiVector::CopyChannel(size_t from_channel, size_t to_channel) {
|
||||||
|
assert(from_channel < num_channels_);
|
||||||
|
assert(to_channel < num_channels_);
|
||||||
|
channels_[from_channel]->CopyFrom(channels_[to_channel]);
|
||||||
|
}
|
||||||
|
|
||||||
const AudioVector& AudioMultiVector::operator[](size_t index) const {
|
const AudioVector& AudioMultiVector::operator[](size_t index) const {
|
||||||
return *(channels_[index]);
|
return *(channels_[index]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ class AudioMultiVector {
|
|||||||
|
|
||||||
virtual bool Empty() const;
|
virtual bool Empty() const;
|
||||||
|
|
||||||
|
// Copies the data between two channels in the AudioMultiVector. The method
|
||||||
|
// does not add any new channel. Thus, |from_channel| and |to_channel| must
|
||||||
|
// both be valid channel numbers.
|
||||||
|
virtual void CopyChannel(size_t from_channel, size_t to_channel);
|
||||||
|
|
||||||
// Accesses and modifies a channel (i.e., an AudioVector object) of this
|
// Accesses and modifies a channel (i.e., an AudioVector object) of this
|
||||||
// AudioMultiVector.
|
// AudioMultiVector.
|
||||||
const AudioVector& operator[](size_t index) const;
|
const AudioVector& operator[](size_t index) const;
|
||||||
|
|||||||
@@ -300,6 +300,31 @@ TEST_P(AudioMultiVectorTest, OverwriteAt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the CopyChannel method, when the test is instantiated with at least two
|
||||||
|
// channels.
|
||||||
|
TEST_P(AudioMultiVectorTest, CopyChannel) {
|
||||||
|
if (num_channels_ < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AudioMultiVector vec(num_channels_);
|
||||||
|
vec.PushBackInterleaved(array_interleaved_, interleaved_length_);
|
||||||
|
// Create a reference copy.
|
||||||
|
AudioMultiVector ref(num_channels_);
|
||||||
|
ref.PushBack(vec);
|
||||||
|
// Copy from first to last channel.
|
||||||
|
vec.CopyChannel(0, num_channels_ - 1);
|
||||||
|
// Verify that the first and last channels are identical; the others should
|
||||||
|
// be left untouched.
|
||||||
|
for (size_t i = 0; i < array_length(); ++i) {
|
||||||
|
// Verify that all but the last channel are untouched.
|
||||||
|
for (size_t channel = 0; channel < num_channels_ - 1; ++channel) {
|
||||||
|
EXPECT_EQ(ref[channel][i], vec[channel][i]);
|
||||||
|
}
|
||||||
|
// Verify that the last and the first channels are identical.
|
||||||
|
EXPECT_EQ(vec[0][i], vec[num_channels_ - 1][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
INSTANTIATE_TEST_CASE_P(TestNumChannels,
|
INSTANTIATE_TEST_CASE_P(TestNumChannels,
|
||||||
AudioMultiVectorTest,
|
AudioMultiVectorTest,
|
||||||
::testing::Values(static_cast<size_t>(1),
|
::testing::Values(static_cast<size_t>(1),
|
||||||
|
|||||||
@@ -158,10 +158,6 @@ int DtmfToneGenerator::Generate(int num_samples,
|
|||||||
if (num_samples < 0 || !output) {
|
if (num_samples < 0 || !output) {
|
||||||
return kParameterError;
|
return kParameterError;
|
||||||
}
|
}
|
||||||
assert(output->Channels() == 1); // Not adapted for multi-channel yet.
|
|
||||||
if (output->Channels() != 1) {
|
|
||||||
return kStereoNotSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
output->AssertSize(num_samples);
|
output->AssertSize(num_samples);
|
||||||
for (int i = 0; i < num_samples; ++i) {
|
for (int i = 0; i < num_samples; ++i) {
|
||||||
@@ -185,6 +181,10 @@ int DtmfToneGenerator::Generate(int num_samples,
|
|||||||
(*output)[0][i] =
|
(*output)[0][i] =
|
||||||
static_cast<int16_t>((temp_val * amplitude_ + 8192) >> 14);
|
static_cast<int16_t>((temp_val * amplitude_ + 8192) >> 14);
|
||||||
}
|
}
|
||||||
|
// Copy first channel to all other channels.
|
||||||
|
for (size_t channel = 1; channel < output->Channels(); ++channel) {
|
||||||
|
output->CopyChannel(0, channel);
|
||||||
|
}
|
||||||
|
|
||||||
return num_samples;
|
return num_samples;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ class DtmfToneGenerator {
|
|||||||
enum ReturnCodes {
|
enum ReturnCodes {
|
||||||
kNotInitialized = -1,
|
kNotInitialized = -1,
|
||||||
kParameterError = -2,
|
kParameterError = -2,
|
||||||
kStereoNotSupported = -3,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DtmfToneGenerator();
|
DtmfToneGenerator();
|
||||||
|
|||||||
@@ -19,9 +19,129 @@
|
|||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
TEST(DtmfToneGenerator, CreateAndDestroy) {
|
class DtmfToneGeneratorTest : public ::testing::Test {
|
||||||
DtmfToneGenerator* tone_gen = new DtmfToneGenerator();
|
protected:
|
||||||
delete tone_gen;
|
static const double kLowFreqHz[16];
|
||||||
|
static const double kHighFreqHz[16];
|
||||||
|
// This is the attenuation applied to all cases.
|
||||||
|
const double kBaseAttenuation = 16141.0 / 16384.0;
|
||||||
|
const double k3dbAttenuation = 23171.0 / 32768;
|
||||||
|
const int kNumSamples = 10;
|
||||||
|
|
||||||
|
void TestAllTones(int fs_hz, int channels) {
|
||||||
|
AudioMultiVector signal(channels);
|
||||||
|
|
||||||
|
for (int event = 0; event <= 15; ++event) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "Checking event " << event << " at sample rate " << fs_hz;
|
||||||
|
SCOPED_TRACE(ss.str());
|
||||||
|
const int kAttenuation = 0;
|
||||||
|
ASSERT_EQ(0, tone_gen_.Init(fs_hz, event, kAttenuation));
|
||||||
|
EXPECT_TRUE(tone_gen_.initialized());
|
||||||
|
EXPECT_EQ(kNumSamples, tone_gen_.Generate(kNumSamples, &signal));
|
||||||
|
|
||||||
|
double f1 = kLowFreqHz[event];
|
||||||
|
double f2 = kHighFreqHz[event];
|
||||||
|
const double pi = 3.14159265358979323846;
|
||||||
|
|
||||||
|
for (int n = 0; n < kNumSamples; ++n) {
|
||||||
|
double x = k3dbAttenuation * sin(2.0 * pi * f1 / fs_hz * (-n - 1)) +
|
||||||
|
sin(2.0 * pi * f2 / fs_hz * (-n - 1));
|
||||||
|
x *= kBaseAttenuation;
|
||||||
|
x = ldexp(x, 14); // Scale to Q14.
|
||||||
|
for (int channel = 0; channel < channels; ++channel) {
|
||||||
|
EXPECT_NEAR(x, static_cast<double>(signal[channel][n]), 25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tone_gen_.Reset();
|
||||||
|
EXPECT_FALSE(tone_gen_.initialized());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAmplitudes(int fs_hz, int channels) {
|
||||||
|
AudioMultiVector signal(channels);
|
||||||
|
AudioMultiVector ref_signal(channels);
|
||||||
|
|
||||||
|
const int event_vec[] = {0, 4, 9, 13}; // Test a few events.
|
||||||
|
for (int e = 0; e < 4; ++e) {
|
||||||
|
int event = event_vec[e];
|
||||||
|
// Create full-scale reference.
|
||||||
|
ASSERT_EQ(0, tone_gen_.Init(fs_hz, event, 0)); // 0 attenuation.
|
||||||
|
EXPECT_EQ(kNumSamples, tone_gen_.Generate(kNumSamples, &ref_signal));
|
||||||
|
// Test every 5 steps (to save time).
|
||||||
|
for (int attenuation = 1; attenuation <= 36; attenuation += 5) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "Checking event " << event << " at sample rate " << fs_hz;
|
||||||
|
ss << "; attenuation " << attenuation;
|
||||||
|
SCOPED_TRACE(ss.str());
|
||||||
|
ASSERT_EQ(0, tone_gen_.Init(fs_hz, event, attenuation));
|
||||||
|
EXPECT_EQ(kNumSamples, tone_gen_.Generate(kNumSamples, &signal));
|
||||||
|
for (int n = 0; n < kNumSamples; ++n) {
|
||||||
|
double attenuation_factor =
|
||||||
|
pow(10, -static_cast<double>(attenuation) / 20);
|
||||||
|
// Verify that the attenuation is correct.
|
||||||
|
for (int channel = 0; channel < channels; ++channel) {
|
||||||
|
EXPECT_NEAR(attenuation_factor * ref_signal[channel][n],
|
||||||
|
signal[channel][n],
|
||||||
|
2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tone_gen_.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DtmfToneGenerator tone_gen_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Low and high frequencies for events 0 through 15.
|
||||||
|
const double DtmfToneGeneratorTest::kLowFreqHz[16] = {
|
||||||
|
941.0, 697.0, 697.0, 697.0, 770.0, 770.0, 770.0, 852.0,
|
||||||
|
852.0, 852.0, 941.0, 941.0, 697.0, 770.0, 852.0, 941.0};
|
||||||
|
const double DtmfToneGeneratorTest::kHighFreqHz[16] = {
|
||||||
|
1336.0, 1209.0, 1336.0, 1477.0, 1209.0, 1336.0, 1477.0, 1209.0,
|
||||||
|
1336.0, 1477.0, 1209.0, 1477.0, 1633.0, 1633.0, 1633.0, 1633.0};
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test8000Mono) {
|
||||||
|
TestAllTones(8000, 1);
|
||||||
|
TestAmplitudes(8000, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test16000Mono) {
|
||||||
|
TestAllTones(16000, 1);
|
||||||
|
TestAmplitudes(16000, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test32000Mono) {
|
||||||
|
TestAllTones(32000, 1);
|
||||||
|
TestAmplitudes(32000, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test48000Mono) {
|
||||||
|
TestAllTones(48000, 1);
|
||||||
|
TestAmplitudes(48000, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test8000Stereo) {
|
||||||
|
TestAllTones(8000, 2);
|
||||||
|
TestAmplitudes(8000, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test16000Stereo) {
|
||||||
|
TestAllTones(16000, 2);
|
||||||
|
TestAmplitudes(16000, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test32000Stereo) {
|
||||||
|
TestAllTones(32000, 2);
|
||||||
|
TestAmplitudes(32000, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DtmfToneGeneratorTest, Test48000Stereo) {
|
||||||
|
TestAllTones(48000, 2);
|
||||||
|
TestAmplitudes(48000, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DtmfToneGenerator, TestErrors) {
|
TEST(DtmfToneGenerator, TestErrors) {
|
||||||
@@ -58,85 +178,4 @@ TEST(DtmfToneGenerator, TestErrors) {
|
|||||||
tone_gen.Generate(kNumSamples, NULL));
|
tone_gen.Generate(kNumSamples, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DtmfToneGenerator, TestTones) {
|
|
||||||
DtmfToneGenerator tone_gen;
|
|
||||||
const int kAttenuation = 0;
|
|
||||||
const int kNumSamples = 10;
|
|
||||||
AudioMultiVector signal(1); // One channel.
|
|
||||||
|
|
||||||
// Low and high frequencies for events 0 through 15.
|
|
||||||
const double low_freq_hz[] = { 941.0, 697.0, 697.0, 697.0, 770.0, 770.0,
|
|
||||||
770.0, 852.0, 852.0, 852.0, 941.0, 941.0, 697.0, 770.0, 852.0, 941.0 };
|
|
||||||
const double hi_freq_hz[] = { 1336.0, 1209.0, 1336.0, 1477.0, 1209.0, 1336.0,
|
|
||||||
1477.0, 1209.0, 1336.0, 1477.0, 1209.0, 1477.0, 1633.0, 1633.0, 1633.0,
|
|
||||||
1633.0 };
|
|
||||||
const double attenuate_3dB = 23171.0 / 32768; // 3 dB attenuation.
|
|
||||||
const double base_attenuation = 16141.0 / 16384.0; // This is the attenuation
|
|
||||||
// applied to all cases.
|
|
||||||
const int fs_vec[] = { 8000, 16000, 32000, 48000 };
|
|
||||||
for (int f = 0; f < 4; ++f) {
|
|
||||||
int fs = fs_vec[f];
|
|
||||||
for (int event = 0; event <= 15; ++event) {
|
|
||||||
std::ostringstream ss;
|
|
||||||
ss << "Checking event " << event << " at sample rate " << fs;
|
|
||||||
SCOPED_TRACE(ss.str());
|
|
||||||
ASSERT_EQ(0, tone_gen.Init(fs, event, kAttenuation));
|
|
||||||
EXPECT_TRUE(tone_gen.initialized());
|
|
||||||
EXPECT_EQ(kNumSamples, tone_gen.Generate(kNumSamples, &signal));
|
|
||||||
|
|
||||||
double f1 = low_freq_hz[event];
|
|
||||||
double f2 = hi_freq_hz[event];
|
|
||||||
const double pi = 3.14159265358979323846;
|
|
||||||
|
|
||||||
for (int n = 0; n < kNumSamples; ++n) {
|
|
||||||
double x = attenuate_3dB * sin(2.0 * pi * f1 / fs * (-n - 1))
|
|
||||||
+ sin(2.0 * pi * f2 / fs * (-n - 1));
|
|
||||||
x *= base_attenuation;
|
|
||||||
x = ldexp(x, 14); // Scale to Q14.
|
|
||||||
static const int kChannel = 0;
|
|
||||||
EXPECT_NEAR(x, static_cast<double>(signal[kChannel][n]), 25);
|
|
||||||
}
|
|
||||||
|
|
||||||
tone_gen.Reset();
|
|
||||||
EXPECT_FALSE(tone_gen.initialized());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(DtmfToneGenerator, TestAmplitudes) {
|
|
||||||
DtmfToneGenerator tone_gen;
|
|
||||||
const int kNumSamples = 10;
|
|
||||||
AudioMultiVector signal(1); // One channel.
|
|
||||||
AudioMultiVector ref_signal(1); // One channel.
|
|
||||||
|
|
||||||
const int fs_vec[] = { 8000, 16000, 32000, 48000 };
|
|
||||||
const int event_vec[] = { 0, 4, 9, 13 }; // Test a few events.
|
|
||||||
for (int f = 0; f < 4; ++f) {
|
|
||||||
int fs = fs_vec[f];
|
|
||||||
int event = event_vec[f];
|
|
||||||
// Create full-scale reference.
|
|
||||||
ASSERT_EQ(0, tone_gen.Init(fs, event, 0)); // 0 attenuation.
|
|
||||||
EXPECT_EQ(kNumSamples, tone_gen.Generate(kNumSamples, &ref_signal));
|
|
||||||
// Test every 5 steps (to save time).
|
|
||||||
for (int attenuation = 1; attenuation <= 36; attenuation += 5) {
|
|
||||||
std::ostringstream ss;
|
|
||||||
ss << "Checking event " << event << " at sample rate " << fs;
|
|
||||||
ss << "; attenuation " << attenuation;
|
|
||||||
SCOPED_TRACE(ss.str());
|
|
||||||
ASSERT_EQ(0, tone_gen.Init(fs, event, attenuation));
|
|
||||||
EXPECT_EQ(kNumSamples, tone_gen.Generate(kNumSamples, &signal));
|
|
||||||
for (int n = 0; n < kNumSamples; ++n) {
|
|
||||||
double attenuation_factor =
|
|
||||||
pow(10, -static_cast<double>(attenuation)/20);
|
|
||||||
// Verify that the attenuation is correct.
|
|
||||||
static const int kChannel = 0;
|
|
||||||
EXPECT_NEAR(attenuation_factor * ref_signal[kChannel][n],
|
|
||||||
signal[kChannel][n], 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
tone_gen.Reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
Reference in New Issue
Block a user