OLD | NEW |
| (Empty) |
1 /* | |
2 * libjingle | |
3 * Copyright 2012 Google Inc. | |
4 * | |
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" | |
35 #include "webrtc/base/gunit.h" | |
36 #include "webrtc/base/logging.h" | |
37 #include "webrtc/base/timeutils.h" | |
38 | |
39 using webrtc::AudioTrackInterface; | |
40 using webrtc::AudioTrack; | |
41 using webrtc::DtmfProviderInterface; | |
42 using webrtc::DtmfSender; | |
43 using webrtc::DtmfSenderObserverInterface; | |
44 | |
45 static const char kTestAudioLabel[] = "test_audio_track"; | |
46 static const int kMaxWaitMs = 3000; | |
47 | |
48 class FakeDtmfObserver : public DtmfSenderObserverInterface { | |
49 public: | |
50 FakeDtmfObserver() : completed_(false) {} | |
51 | |
52 // Implements DtmfSenderObserverInterface. | |
53 void OnToneChange(const std::string& tone) override { | |
54 LOG(LS_VERBOSE) << "FakeDtmfObserver::OnToneChange '" << tone << "'."; | |
55 tones_.push_back(tone); | |
56 if (tone.empty()) { | |
57 completed_ = true; | |
58 } | |
59 } | |
60 | |
61 // getters | |
62 const std::vector<std::string>& tones() const { | |
63 return tones_; | |
64 } | |
65 bool completed() const { | |
66 return completed_; | |
67 } | |
68 | |
69 private: | |
70 std::vector<std::string> tones_; | |
71 bool completed_; | |
72 }; | |
73 | |
74 class FakeDtmfProvider : public DtmfProviderInterface { | |
75 public: | |
76 struct DtmfInfo { | |
77 DtmfInfo(int code, int duration, int gap) | |
78 : code(code), | |
79 duration(duration), | |
80 gap(gap) {} | |
81 int code; | |
82 int duration; | |
83 int gap; | |
84 }; | |
85 | |
86 FakeDtmfProvider() : last_insert_dtmf_call_(0) {} | |
87 | |
88 ~FakeDtmfProvider() { | |
89 SignalDestroyed(); | |
90 } | |
91 | |
92 // Implements DtmfProviderInterface. | |
93 bool CanInsertDtmf(const std::string& track_label) override { | |
94 return (can_insert_dtmf_tracks_.count(track_label) != 0); | |
95 } | |
96 | |
97 bool InsertDtmf(const std::string& track_label, | |
98 int code, | |
99 int duration) override { | |
100 int gap = 0; | |
101 // TODO(ronghuawu): Make the timer (basically the rtc::TimeNanos) | |
102 // mockable and use a fake timer in the unit tests. | |
103 if (last_insert_dtmf_call_ > 0) { | |
104 gap = static_cast<int>(rtc::Time() - last_insert_dtmf_call_); | |
105 } | |
106 last_insert_dtmf_call_ = rtc::Time(); | |
107 | |
108 LOG(LS_VERBOSE) << "FakeDtmfProvider::InsertDtmf code=" << code | |
109 << " duration=" << duration | |
110 << " gap=" << gap << "."; | |
111 dtmf_info_queue_.push_back(DtmfInfo(code, duration, gap)); | |
112 return true; | |
113 } | |
114 | |
115 virtual sigslot::signal0<>* GetOnDestroyedSignal() { | |
116 return &SignalDestroyed; | |
117 } | |
118 | |
119 // getter and setter | |
120 const std::vector<DtmfInfo>& dtmf_info_queue() const { | |
121 return dtmf_info_queue_; | |
122 } | |
123 | |
124 // helper functions | |
125 void AddCanInsertDtmfTrack(const std::string& label) { | |
126 can_insert_dtmf_tracks_.insert(label); | |
127 } | |
128 void RemoveCanInsertDtmfTrack(const std::string& label) { | |
129 can_insert_dtmf_tracks_.erase(label); | |
130 } | |
131 | |
132 private: | |
133 std::set<std::string> can_insert_dtmf_tracks_; | |
134 std::vector<DtmfInfo> dtmf_info_queue_; | |
135 int64_t last_insert_dtmf_call_; | |
136 sigslot::signal0<> SignalDestroyed; | |
137 }; | |
138 | |
139 class DtmfSenderTest : public testing::Test { | |
140 protected: | |
141 DtmfSenderTest() | |
142 : track_(AudioTrack::Create(kTestAudioLabel, NULL)), | |
143 observer_(new rtc::RefCountedObject<FakeDtmfObserver>()), | |
144 provider_(new FakeDtmfProvider()) { | |
145 provider_->AddCanInsertDtmfTrack(kTestAudioLabel); | |
146 dtmf_ = DtmfSender::Create(track_, rtc::Thread::Current(), | |
147 provider_.get()); | |
148 dtmf_->RegisterObserver(observer_.get()); | |
149 } | |
150 | |
151 ~DtmfSenderTest() { | |
152 if (dtmf_.get()) { | |
153 dtmf_->UnregisterObserver(); | |
154 } | |
155 } | |
156 | |
157 // Constructs a list of DtmfInfo from |tones|, |duration| and | |
158 // |inter_tone_gap|. | |
159 void GetDtmfInfoFromString(const std::string& tones, int duration, | |
160 int inter_tone_gap, | |
161 std::vector<FakeDtmfProvider::DtmfInfo>* dtmfs) { | |
162 // Init extra_delay as -inter_tone_gap - duration to ensure the first | |
163 // DtmfInfo's gap field will be 0. | |
164 int extra_delay = -1 * (inter_tone_gap + duration); | |
165 | |
166 std::string::const_iterator it = tones.begin(); | |
167 for (; it != tones.end(); ++it) { | |
168 char tone = *it; | |
169 int code = 0; | |
170 webrtc::GetDtmfCode(tone, &code); | |
171 if (tone == ',') { | |
172 extra_delay = 2000; // 2 seconds | |
173 } else { | |
174 dtmfs->push_back(FakeDtmfProvider::DtmfInfo(code, duration, | |
175 duration + inter_tone_gap + extra_delay)); | |
176 extra_delay = 0; | |
177 } | |
178 } | |
179 } | |
180 | |
181 void VerifyExpectedState(AudioTrackInterface* track, | |
182 const std::string& tones, | |
183 int duration, int inter_tone_gap) { | |
184 EXPECT_EQ(track, dtmf_->track()); | |
185 EXPECT_EQ(tones, dtmf_->tones()); | |
186 EXPECT_EQ(duration, dtmf_->duration()); | |
187 EXPECT_EQ(inter_tone_gap, dtmf_->inter_tone_gap()); | |
188 } | |
189 | |
190 // Verify the provider got all the expected calls. | |
191 void VerifyOnProvider(const std::string& tones, int duration, | |
192 int inter_tone_gap) { | |
193 std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref; | |
194 GetDtmfInfoFromString(tones, duration, inter_tone_gap, &dtmf_queue_ref); | |
195 VerifyOnProvider(dtmf_queue_ref); | |
196 } | |
197 | |
198 void VerifyOnProvider( | |
199 const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue_ref) { | |
200 const std::vector<FakeDtmfProvider::DtmfInfo>& dtmf_queue = | |
201 provider_->dtmf_info_queue(); | |
202 ASSERT_EQ(dtmf_queue_ref.size(), dtmf_queue.size()); | |
203 std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it_ref = | |
204 dtmf_queue_ref.begin(); | |
205 std::vector<FakeDtmfProvider::DtmfInfo>::const_iterator it = | |
206 dtmf_queue.begin(); | |
207 while (it_ref != dtmf_queue_ref.end() && it != dtmf_queue.end()) { | |
208 EXPECT_EQ(it_ref->code, it->code); | |
209 EXPECT_EQ(it_ref->duration, it->duration); | |
210 // Allow ~100ms error. | |
211 EXPECT_GE(it_ref->gap, it->gap - 100); | |
212 EXPECT_LE(it_ref->gap, it->gap + 100); | |
213 ++it_ref; | |
214 ++it; | |
215 } | |
216 } | |
217 | |
218 // Verify the observer got all the expected callbacks. | |
219 void VerifyOnObserver(const std::string& tones_ref) { | |
220 const std::vector<std::string>& tones = observer_->tones(); | |
221 // The observer will get an empty string at the end. | |
222 EXPECT_EQ(tones_ref.size() + 1, tones.size()); | |
223 EXPECT_TRUE(tones.back().empty()); | |
224 std::string::const_iterator it_ref = tones_ref.begin(); | |
225 std::vector<std::string>::const_iterator it = tones.begin(); | |
226 while (it_ref != tones_ref.end() && it != tones.end()) { | |
227 EXPECT_EQ(*it_ref, it->at(0)); | |
228 ++it_ref; | |
229 ++it; | |
230 } | |
231 } | |
232 | |
233 rtc::scoped_refptr<AudioTrackInterface> track_; | |
234 rtc::scoped_ptr<FakeDtmfObserver> observer_; | |
235 rtc::scoped_ptr<FakeDtmfProvider> provider_; | |
236 rtc::scoped_refptr<DtmfSender> dtmf_; | |
237 }; | |
238 | |
239 TEST_F(DtmfSenderTest, CanInsertDtmf) { | |
240 EXPECT_TRUE(dtmf_->CanInsertDtmf()); | |
241 provider_->RemoveCanInsertDtmfTrack(kTestAudioLabel); | |
242 EXPECT_FALSE(dtmf_->CanInsertDtmf()); | |
243 } | |
244 | |
245 TEST_F(DtmfSenderTest, InsertDtmf) { | |
246 std::string tones = "@1%a&*$"; | |
247 int duration = 100; | |
248 int inter_tone_gap = 50; | |
249 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap)); | |
250 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs); | |
251 | |
252 // The unrecognized characters should be ignored. | |
253 std::string known_tones = "1a*"; | |
254 VerifyOnProvider(known_tones, duration, inter_tone_gap); | |
255 VerifyOnObserver(known_tones); | |
256 } | |
257 | |
258 TEST_F(DtmfSenderTest, InsertDtmfTwice) { | |
259 std::string tones1 = "12"; | |
260 std::string tones2 = "ab"; | |
261 int duration = 100; | |
262 int inter_tone_gap = 50; | |
263 EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap)); | |
264 VerifyExpectedState(track_, tones1, duration, inter_tone_gap); | |
265 // Wait until the first tone got sent. | |
266 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs); | |
267 VerifyExpectedState(track_, "2", duration, inter_tone_gap); | |
268 // Insert with another tone buffer. | |
269 EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap)); | |
270 VerifyExpectedState(track_, tones2, duration, inter_tone_gap); | |
271 // Wait until it's completed. | |
272 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs); | |
273 | |
274 std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref; | |
275 GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref); | |
276 GetDtmfInfoFromString("ab", duration, inter_tone_gap, &dtmf_queue_ref); | |
277 VerifyOnProvider(dtmf_queue_ref); | |
278 VerifyOnObserver("1ab"); | |
279 } | |
280 | |
281 TEST_F(DtmfSenderTest, InsertDtmfWhileProviderIsDeleted) { | |
282 std::string tones = "@1%a&*$"; | |
283 int duration = 100; | |
284 int inter_tone_gap = 50; | |
285 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap)); | |
286 // Wait until the first tone got sent. | |
287 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs); | |
288 // Delete provider. | |
289 provider_.reset(); | |
290 // The queue should be discontinued so no more tone callbacks. | |
291 WAIT(false, 200); | |
292 EXPECT_EQ(1U, observer_->tones().size()); | |
293 } | |
294 | |
295 TEST_F(DtmfSenderTest, InsertDtmfWhileSenderIsDeleted) { | |
296 std::string tones = "@1%a&*$"; | |
297 int duration = 100; | |
298 int inter_tone_gap = 50; | |
299 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap)); | |
300 // Wait until the first tone got sent. | |
301 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs); | |
302 // Delete the sender. | |
303 dtmf_ = NULL; | |
304 // The queue should be discontinued so no more tone callbacks. | |
305 WAIT(false, 200); | |
306 EXPECT_EQ(1U, observer_->tones().size()); | |
307 } | |
308 | |
309 TEST_F(DtmfSenderTest, InsertEmptyTonesToCancelPreviousTask) { | |
310 std::string tones1 = "12"; | |
311 std::string tones2 = ""; | |
312 int duration = 100; | |
313 int inter_tone_gap = 50; | |
314 EXPECT_TRUE(dtmf_->InsertDtmf(tones1, duration, inter_tone_gap)); | |
315 // Wait until the first tone got sent. | |
316 EXPECT_TRUE_WAIT(observer_->tones().size() == 1, kMaxWaitMs); | |
317 // Insert with another tone buffer. | |
318 EXPECT_TRUE(dtmf_->InsertDtmf(tones2, duration, inter_tone_gap)); | |
319 // Wait until it's completed. | |
320 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs); | |
321 | |
322 std::vector<FakeDtmfProvider::DtmfInfo> dtmf_queue_ref; | |
323 GetDtmfInfoFromString("1", duration, inter_tone_gap, &dtmf_queue_ref); | |
324 VerifyOnProvider(dtmf_queue_ref); | |
325 VerifyOnObserver("1"); | |
326 } | |
327 | |
328 // Flaky when run in parallel. | |
329 // See https://code.google.com/p/webrtc/issues/detail?id=4219. | |
330 TEST_F(DtmfSenderTest, DISABLED_InsertDtmfWithCommaAsDelay) { | |
331 std::string tones = "3,4"; | |
332 int duration = 100; | |
333 int inter_tone_gap = 50; | |
334 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap)); | |
335 EXPECT_TRUE_WAIT(observer_->completed(), kMaxWaitMs); | |
336 | |
337 VerifyOnProvider(tones, duration, inter_tone_gap); | |
338 VerifyOnObserver(tones); | |
339 } | |
340 | |
341 TEST_F(DtmfSenderTest, TryInsertDtmfWhenItDoesNotWork) { | |
342 std::string tones = "3,4"; | |
343 int duration = 100; | |
344 int inter_tone_gap = 50; | |
345 provider_->RemoveCanInsertDtmfTrack(kTestAudioLabel); | |
346 EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap)); | |
347 } | |
348 | |
349 TEST_F(DtmfSenderTest, InsertDtmfWithInvalidDurationOrGap) { | |
350 std::string tones = "3,4"; | |
351 int duration = 100; | |
352 int inter_tone_gap = 50; | |
353 | |
354 EXPECT_FALSE(dtmf_->InsertDtmf(tones, 6001, inter_tone_gap)); | |
355 EXPECT_FALSE(dtmf_->InsertDtmf(tones, 69, inter_tone_gap)); | |
356 EXPECT_FALSE(dtmf_->InsertDtmf(tones, duration, 49)); | |
357 | |
358 EXPECT_TRUE(dtmf_->InsertDtmf(tones, duration, inter_tone_gap)); | |
359 } | |
OLD | NEW |