blob: ba2e163f1338a35ef739c4a0a7c668ffbe2ac561 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
2 * libjingle
jlmiller@webrtc.org5f93d0a2015-01-20 21:36:13 +00003 * Copyright 2012 Google Inc.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00004 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/app/webrtc/dtmfsender.h"
29
30#include <set>
31#include <string>
32#include <vector>
33
34#include "talk/app/webrtc/audiotrack.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000035#include "webrtc/base/gunit.h"
36#include "webrtc/base/logging.h"
Magnus Jedvert1b40a9a2015-10-12 15:50:43 +020037#include "webrtc/base/refcount.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000038#include "webrtc/base/timeutils.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000039
40using webrtc::AudioTrackInterface;
41using webrtc::AudioTrack;
42using webrtc::DtmfProviderInterface;
43using webrtc::DtmfSender;
44using webrtc::DtmfSenderObserverInterface;
45
46static const char kTestAudioLabel[] = "test_audio_track";
47static const int kMaxWaitMs = 3000;
48
Magnus Jedvert1b40a9a2015-10-12 15:50:43 +020049class FakeDtmfObserver : public DtmfSenderObserverInterface, RefCountInterface {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000050 public:
51 FakeDtmfObserver() : completed_(false) {}
52
53 // Implements DtmfSenderObserverInterface.
kjellander@webrtc.org14665ff2015-03-04 12:58:35 +000054 void OnToneChange(const std::string& tone) override {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000055 LOG(LS_VERBOSE) << "FakeDtmfObserver::OnToneChange '" << tone << "'.";
56 tones_.push_back(tone);
57 if (tone.empty()) {
58 completed_ = true;
59 }
60 }
61
62 // getters
63 const std::vector<std::string>& tones() const {
64 return tones_;
65 }
66 bool completed() const {
67 return completed_;
68 }
69
70 private:
71 std::vector<std::string> tones_;
72 bool completed_;
73};
74
75class FakeDtmfProvider : public DtmfProviderInterface {
76 public:
77 struct DtmfInfo {
78 DtmfInfo(int code, int duration, int gap)
79 : code(code),
80 duration(duration),
81 gap(gap) {}
82 int code;
83 int duration;
84 int gap;
85 };
86
87 FakeDtmfProvider() : last_insert_dtmf_call_(0) {}
88
89 ~FakeDtmfProvider() {
90 SignalDestroyed();
91 }
92
93 // Implements DtmfProviderInterface.
kjellander@webrtc.org14665ff2015-03-04 12:58:35 +000094 bool CanInsertDtmf(const std::string& track_label) override {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000095 return (can_insert_dtmf_tracks_.count(track_label) != 0);
96 }
97
kjellander@webrtc.org14665ff2015-03-04 12:58:35 +000098 bool InsertDtmf(const std::string& track_label,
99 int code,
100 int duration) override {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000101 int gap = 0;
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000102 // TODO(ronghuawu): Make the timer (basically the rtc::TimeNanos)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000103 // mockable and use a fake timer in the unit tests.
104 if (last_insert_dtmf_call_ > 0) {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000105 gap = static_cast<int>(rtc::Time() - last_insert_dtmf_call_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000106 }
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000107 last_insert_dtmf_call_ = rtc::Time();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000108
109 LOG(LS_VERBOSE) << "FakeDtmfProvider::InsertDtmf code=" << code
110 << " duration=" << duration
111 << " gap=" << gap << ".";
112 dtmf_info_queue_.push_back(DtmfInfo(code, duration, gap));
113 return true;
114 }
115
116 virtual sigslot::signal0<>* GetOnDestroyedSignal() {
117 return &SignalDestroyed;
118 }
119
120 // getter and setter
121 const std::vector<DtmfInfo>& dtmf_info_queue() const {
122 return dtmf_info_queue_;
123 }
124
125 // helper functions
126 void AddCanInsertDtmfTrack(const std::string& label) {
127 can_insert_dtmf_tracks_.insert(label);
128 }
129 void RemoveCanInsertDtmfTrack(const std::string& label) {
130 can_insert_dtmf_tracks_.erase(label);
131 }
132
133 private:
134 std::set<std::string> can_insert_dtmf_tracks_;
135 std::vector<DtmfInfo> dtmf_info_queue_;
Peter Boström0c4e06b2015-10-07 12:23:21 +0200136 int64_t last_insert_dtmf_call_;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000137 sigslot::signal0<> SignalDestroyed;
138};
139
140class DtmfSenderTest : public testing::Test {
141 protected:
142 DtmfSenderTest()
143 : track_(AudioTrack::Create(kTestAudioLabel, NULL)),
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000144 observer_(new rtc::RefCountedObject<FakeDtmfObserver>()),
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000145 provider_(new FakeDtmfProvider()) {
146 provider_->AddCanInsertDtmfTrack(kTestAudioLabel);
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000147 dtmf_ = DtmfSender::Create(track_, rtc::Thread::Current(),
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000148 provider_.get());
149 dtmf_->RegisterObserver(observer_.get());
150 }
151
152 ~DtmfSenderTest() {
153 if (dtmf_.get()) {
154 dtmf_->UnregisterObserver();
155 }
156 }
157
158 // Constructs a list of DtmfInfo from |tones|, |duration| and
159 // |inter_tone_gap|.
160 void GetDtmfInfoFromString(const std::string& tones, int duration,
161 int inter_tone_gap,
162 std::vector<FakeDtmfProvider::DtmfInfo>* dtmfs) {
163 // Init extra_delay as -inter_tone_gap - duration to ensure the first
164 // DtmfInfo's gap field will be 0.
165 int extra_delay = -1 * (inter_tone_gap + duration);
166
167 std::string::const_iterator it = tones.begin();
168 for (; it != tones.end(); ++it) {
169 char tone = *it;
170 int code = 0;
171 webrtc::GetDtmfCode(tone, &code);
172 if (tone == ',') {
173 extra_delay = 2000; // 2 seconds
174 } else {
175 dtmfs->push_back(FakeDtmfProvider::DtmfInfo(code, duration,
176 duration + inter_tone_gap + extra_delay));
177 extra_delay = 0;
178 }
179 }
180 }
181
182 void VerifyExpectedState(AudioTrackInterface* track,
183 const std::string& tones,
184 int duration, int inter_tone_gap) {
185 EXPECT_EQ(track, dtmf_->track());
186 EXPECT_EQ(tones, dtmf_->tones());
187 EXPECT_EQ(duration, dtmf_->duration());
188 EXPECT_EQ(inter_tone_gap, dtmf_->inter_tone_gap());
189 }
190
191 // Verify the provider got all the expected calls.
192 void VerifyOnProvider(const std::string& tones, int duration,
193 int inter_tone_gap) {
194 std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
195 GetDtmfInfoFromString(tones, duration, inter_tone_gap, &dtmf_queue_ref);
196 VerifyOnProvider(dtmf_queue_ref);
197 }
198
199 void VerifyOnProvider(
200 const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue_ref) {
201 const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue =
202 provider_->dtmf_info_queue();
203 ASSERT_EQ(dtmf_queue_ref.size(), dtmf_queue.size());
204 std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it_ref =
205 dtmf_queue_ref.begin();
206 std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it =
207 dtmf_queue.begin();
208 while (it_ref != dtmf_queue_ref.end() && it != dtmf_queue.end()) {
209 EXPECT_EQ(it_ref->code, it->code);
210 EXPECT_EQ(it_ref->duration, it->duration);
wu@webrtc.org8d1e4d62013-09-18 18:01:07 +0000211 // Allow ~100ms error.
212 EXPECT_GE(it_ref->gap, it->gap - 100);
213 EXPECT_LE(it_ref->gap, it->gap + 100);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000214 ++it_ref;
215 ++it;
216 }
217 }
218
219 // Verify the observer got all the expected callbacks.
220 void VerifyOnObserver(const std::string& tones_ref) {
221 const std::vector<std::string>& tones = observer_->tones();
222 // The observer will get an empty string at the end.
223 EXPECT_EQ(tones_ref.size() + 1, tones.size());
224 EXPECT_TRUE(tones.back().empty());
225 std::string::const_iterator it_ref = tones_ref.begin();
226 std::vector<std::string>::const_iterator it = tones.begin();
227 while (it_ref != tones_ref.end() && it != tones.end()) {
228 EXPECT_EQ(*it_ref, it->at(0));
229 ++it_ref;
230 ++it;
231 }
232 }
233
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000234 rtc::scoped_refptr<AudioTrackInterface> track_;
235 rtc::scoped_ptr<FakeDtmfObserver> observer_;
236 rtc::scoped_ptr<FakeDtmfProvider> provider_;
237 rtc::scoped_refptr<DtmfSender> dtmf_;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000238};
239
240TEST_F(DtmfSenderTest, CanInsertDtmf) {
241 EXPECT_TRUE(dtmf_->CanInsertDtmf());
242 provider_->RemoveCanInsertDtmfTrack(kTestAudioLabel);
243 EXPECT_FALSE(dtmf_->CanInsertDtmf());
244}
245
246TEST_F(DtmfSenderTest, InsertDtmf) {
247 std::string tones = "@1%a&*$";
248 int duration = 100;
249 int inter_tone_gap = 50;
250 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
251 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
252
253 // The unrecognized characters should be ignored.
254 std::string known_tones = "1a*";
255 VerifyOnProvider(known_tones, duration, inter_tone_gap);
256 VerifyOnObserver(known_tones);
257}
258
259TEST_F(DtmfSenderTest, InsertDtmfTwice) {
260 std::string tones1 = "12";
261 std::string tones2 = "ab";
262 int duration = 100;
263 int inter_tone_gap = 50;
264 EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap));
265 VerifyExpectedState(track_, tones1, duration, inter_tone_gap);
266 // Wait until the first tone got sent.
267 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
268 VerifyExpectedState(track_, "2", duration, inter_tone_gap);
269 // Insert with another tone buffer.
270 EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap));
271 VerifyExpectedState(track_, tones2, duration, inter_tone_gap);
272 // Wait until it's completed.
273 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
274
275 std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
276 GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref);
277 GetDtmfInfoFromString("ab", duration, inter_tone_gap, &dtmf_queue_ref);
278 VerifyOnProvider(dtmf_queue_ref);
279 VerifyOnObserver("1ab");
280}
281
282TEST_F(DtmfSenderTest, InsertDtmfWhileProviderIsDeleted) {
283 std::string tones = "@1%a&*$";
284 int duration = 100;
285 int inter_tone_gap = 50;
286 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
287 // Wait until the first tone got sent.
288 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
289 // Delete provider.
290 provider_.reset();
291 // The queue should be discontinued so no more tone callbacks.
292 WAIT(false, 200);
293 EXPECT_EQ(1U, observer_->tones().size());
294}
295
296TEST_F(DtmfSenderTest, InsertDtmfWhileSenderIsDeleted) {
297 std::string tones = "@1%a&*$";
298 int duration = 100;
299 int inter_tone_gap = 50;
300 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
301 // Wait until the first tone got sent.
302 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
303 // Delete the sender.
304 dtmf_ = NULL;
305 // The queue should be discontinued so no more tone callbacks.
306 WAIT(false, 200);
307 EXPECT_EQ(1U, observer_->tones().size());
308}
309
310TEST_F(DtmfSenderTest, InsertEmptyTonesToCancelPreviousTask) {
311 std::string tones1 = "12";
312 std::string tones2 = "";
313 int duration = 100;
314 int inter_tone_gap = 50;
315 EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap));
316 // Wait until the first tone got sent.
317 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs);
318 // Insert with another tone buffer.
319 EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap));
320 // Wait until it's completed.
321 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
322
323 std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref;
324 GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref);
325 VerifyOnProvider(dtmf_queue_ref);
326 VerifyOnObserver("1");
327}
328
kjellander@webrtc.orga02d7682015-01-23 14:34:52 +0000329// Flaky when run in parallel.
330// See https://code.google.com/p/webrtc/issues/detail?id=4219.
331TEST_F(DtmfSenderTest, DISABLED_InsertDtmfWithCommaAsDelay) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000332 std::string tones = "3,4";
333 int duration = 100;
334 int inter_tone_gap = 50;
335 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
336 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs);
337
338 VerifyOnProvider(tones, duration, inter_tone_gap);
339 VerifyOnObserver(tones);
340}
341
342TEST_F(DtmfSenderTest, TryInsertDtmfWhenItDoesNotWork) {
343 std::string tones = "3,4";
344 int duration = 100;
345 int inter_tone_gap = 50;
346 provider_->RemoveCanInsertDtmfTrack(kTestAudioLabel);
347 EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
348}
349
350TEST_F(DtmfSenderTest, InsertDtmfWithInvalidDurationOrGap) {
351 std::string tones = "3,4";
352 int duration = 100;
353 int inter_tone_gap = 50;
354
355 EXPECT_FALSE(dtmf_->InsertDtmf(tones, 6001, inter_tone_gap));
356 EXPECT_FALSE(dtmf_->InsertDtmf(tones, 69, inter_tone_gap));
357 EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, 49));
358
359 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap));
360}