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/webrtc@7056 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/modules/audio_coding/neteq/audio_multi_vector.cc b/modules/audio_coding/neteq/audio_multi_vector.cc
index 5a208a6..64278ed 100644
--- a/modules/audio_coding/neteq/audio_multi_vector.cc
+++ b/modules/audio_coding/neteq/audio_multi_vector.cc
@@ -202,6 +202,12 @@
   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 {
   return *(channels_[index]);
 }
diff --git a/modules/audio_coding/neteq/audio_multi_vector.h b/modules/audio_coding/neteq/audio_multi_vector.h
index 908de93..1d05af7 100644
--- a/modules/audio_coding/neteq/audio_multi_vector.h
+++ b/modules/audio_coding/neteq/audio_multi_vector.h
@@ -117,6 +117,11 @@
 
   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
   // AudioMultiVector.
   const AudioVector& operator[](size_t index) const;
diff --git a/modules/audio_coding/neteq/audio_multi_vector_unittest.cc b/modules/audio_coding/neteq/audio_multi_vector_unittest.cc
index 9476038..7dabe64 100644
--- a/modules/audio_coding/neteq/audio_multi_vector_unittest.cc
+++ b/modules/audio_coding/neteq/audio_multi_vector_unittest.cc
@@ -300,6 +300,31 @@
   }
 }
 
+// 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,
                         AudioMultiVectorTest,
                         ::testing::Values(static_cast<size_t>(1),
diff --git a/modules/audio_coding/neteq/dtmf_tone_generator.cc b/modules/audio_coding/neteq/dtmf_tone_generator.cc
index 34c615d..3429bcd 100644
--- a/modules/audio_coding/neteq/dtmf_tone_generator.cc
+++ b/modules/audio_coding/neteq/dtmf_tone_generator.cc
@@ -158,10 +158,6 @@
   if (num_samples < 0 || !output) {
     return kParameterError;
   }
-  assert(output->Channels() == 1);  // Not adapted for multi-channel yet.
-  if (output->Channels() != 1) {
-    return kStereoNotSupported;
-  }
 
   output->AssertSize(num_samples);
   for (int i = 0; i < num_samples; ++i) {
@@ -185,6 +181,10 @@
     (*output)[0][i] =
         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;
 }
diff --git a/modules/audio_coding/neteq/dtmf_tone_generator.h b/modules/audio_coding/neteq/dtmf_tone_generator.h
index fc1e5e4..232eba4 100644
--- a/modules/audio_coding/neteq/dtmf_tone_generator.h
+++ b/modules/audio_coding/neteq/dtmf_tone_generator.h
@@ -24,7 +24,6 @@
   enum ReturnCodes {
     kNotInitialized = -1,
     kParameterError = -2,
-    kStereoNotSupported = -3,
   };
 
   DtmfToneGenerator();
diff --git a/modules/audio_coding/neteq/dtmf_tone_generator_unittest.cc b/modules/audio_coding/neteq/dtmf_tone_generator_unittest.cc
index 94f79dc..829d126 100644
--- a/modules/audio_coding/neteq/dtmf_tone_generator_unittest.cc
+++ b/modules/audio_coding/neteq/dtmf_tone_generator_unittest.cc
@@ -19,9 +19,129 @@
 
 namespace webrtc {
 
-TEST(DtmfToneGenerator, CreateAndDestroy) {
-  DtmfToneGenerator* tone_gen = new DtmfToneGenerator();
-  delete tone_gen;
+class DtmfToneGeneratorTest : public ::testing::Test {
+ protected:
+  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) {
@@ -58,85 +178,4 @@
             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