| 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 |