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