• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "pc/dtmf_sender.h"
12 
13 #include <ctype.h>
14 #include <string.h>
15 
16 #include <string>
17 
18 #include "rtc_base/checks.h"
19 #include "rtc_base/logging.h"
20 #include "rtc_base/ref_counted_object.h"
21 #include "rtc_base/thread.h"
22 
23 namespace webrtc {
24 
25 // RFC4733
26 //  +-------+--------+------+---------+
27 //  | Event | Code   | Type | Volume? |
28 //  +-------+--------+------+---------+
29 //  | 0--9  | 0--9   | tone | yes     |
30 //  | *     | 10     | tone | yes     |
31 //  | #     | 11     | tone | yes     |
32 //  | A--D  | 12--15 | tone | yes     |
33 //  +-------+--------+------+---------+
34 // The "," is a special event defined by the WebRTC spec. It means to delay for
35 // 2 seconds before processing the next tone. We use -1 as its code.
36 static const int kDtmfCommaDelay = -1;
37 static const char kDtmfValidTones[] = ",0123456789*#ABCDabcd";
38 static const char kDtmfTonesTable[] = ",0123456789*#ABCD";
39 // The duration cannot be more than 6000ms or less than 40ms. The gap between
40 // tones must be at least 50 ms.
41 // Source for values: W3C WEBRTC specification.
42 // https://w3c.github.io/webrtc-pc/#dom-rtcdtmfsender-insertdtmf
43 static const int kDtmfDefaultDurationMs = 100;
44 static const int kDtmfMinDurationMs = 40;
45 static const int kDtmfMaxDurationMs = 6000;
46 static const int kDtmfDefaultGapMs = 50;
47 static const int kDtmfMinGapMs = 30;
48 
49 // Get DTMF code from the DTMF event character.
GetDtmfCode(char tone,int * code)50 bool GetDtmfCode(char tone, int* code) {
51   // Convert a-d to A-D.
52   char event = toupper(tone);
53   const char* p = strchr(kDtmfTonesTable, event);
54   if (!p) {
55     return false;
56   }
57   *code = p - kDtmfTonesTable - 1;
58   return true;
59 }
60 
Create(rtc::Thread * signaling_thread,DtmfProviderInterface * provider)61 rtc::scoped_refptr<DtmfSender> DtmfSender::Create(
62     rtc::Thread* signaling_thread,
63     DtmfProviderInterface* provider) {
64   if (!signaling_thread) {
65     return nullptr;
66   }
67   rtc::scoped_refptr<DtmfSender> dtmf_sender(
68       new rtc::RefCountedObject<DtmfSender>(signaling_thread, provider));
69   return dtmf_sender;
70 }
71 
DtmfSender(rtc::Thread * signaling_thread,DtmfProviderInterface * provider)72 DtmfSender::DtmfSender(rtc::Thread* signaling_thread,
73                        DtmfProviderInterface* provider)
74     : observer_(nullptr),
75       signaling_thread_(signaling_thread),
76       provider_(provider),
77       duration_(kDtmfDefaultDurationMs),
78       inter_tone_gap_(kDtmfDefaultGapMs),
79       comma_delay_(kDtmfDefaultCommaDelayMs) {
80   RTC_DCHECK(signaling_thread_);
81   if (provider_) {
82     RTC_DCHECK(provider_->GetOnDestroyedSignal());
83     provider_->GetOnDestroyedSignal()->connect(
84         this, &DtmfSender::OnProviderDestroyed);
85   }
86 }
87 
~DtmfSender()88 DtmfSender::~DtmfSender() {
89   StopSending();
90 }
91 
RegisterObserver(DtmfSenderObserverInterface * observer)92 void DtmfSender::RegisterObserver(DtmfSenderObserverInterface* observer) {
93   observer_ = observer;
94 }
95 
UnregisterObserver()96 void DtmfSender::UnregisterObserver() {
97   observer_ = nullptr;
98 }
99 
CanInsertDtmf()100 bool DtmfSender::CanInsertDtmf() {
101   RTC_DCHECK(signaling_thread_->IsCurrent());
102   if (!provider_) {
103     return false;
104   }
105   return provider_->CanInsertDtmf();
106 }
107 
InsertDtmf(const std::string & tones,int duration,int inter_tone_gap,int comma_delay)108 bool DtmfSender::InsertDtmf(const std::string& tones,
109                             int duration,
110                             int inter_tone_gap,
111                             int comma_delay) {
112   RTC_DCHECK(signaling_thread_->IsCurrent());
113 
114   if (duration > kDtmfMaxDurationMs || duration < kDtmfMinDurationMs ||
115       inter_tone_gap < kDtmfMinGapMs || comma_delay < kDtmfMinGapMs) {
116     RTC_LOG(LS_ERROR)
117         << "InsertDtmf is called with invalid duration or tones gap. "
118            "The duration cannot be more than "
119         << kDtmfMaxDurationMs << "ms or less than " << kDtmfMinDurationMs
120         << "ms. The gap between tones must be at least " << kDtmfMinGapMs
121         << "ms.";
122     return false;
123   }
124 
125   if (!CanInsertDtmf()) {
126     RTC_LOG(LS_ERROR)
127         << "InsertDtmf is called on DtmfSender that can't send DTMF.";
128     return false;
129   }
130 
131   tones_ = tones;
132   duration_ = duration;
133   inter_tone_gap_ = inter_tone_gap;
134   comma_delay_ = comma_delay;
135   // Clear the previous queue.
136   dtmf_driver_.Clear();
137   // Kick off a new DTMF task queue.
138   QueueInsertDtmf(RTC_FROM_HERE, 1 /*ms*/);
139   return true;
140 }
141 
tones() const142 std::string DtmfSender::tones() const {
143   return tones_;
144 }
145 
duration() const146 int DtmfSender::duration() const {
147   return duration_;
148 }
149 
inter_tone_gap() const150 int DtmfSender::inter_tone_gap() const {
151   return inter_tone_gap_;
152 }
153 
comma_delay() const154 int DtmfSender::comma_delay() const {
155   return comma_delay_;
156 }
157 
QueueInsertDtmf(const rtc::Location & posted_from,uint32_t delay_ms)158 void DtmfSender::QueueInsertDtmf(const rtc::Location& posted_from,
159                                  uint32_t delay_ms) {
160   dtmf_driver_.AsyncInvokeDelayed<void>(
161       posted_from, signaling_thread_, [this] { DoInsertDtmf(); }, delay_ms);
162 }
163 
DoInsertDtmf()164 void DtmfSender::DoInsertDtmf() {
165   RTC_DCHECK(signaling_thread_->IsCurrent());
166 
167   // Get the first DTMF tone from the tone buffer. Unrecognized characters will
168   // be ignored and skipped.
169   size_t first_tone_pos = tones_.find_first_of(kDtmfValidTones);
170   int code = 0;
171   if (first_tone_pos == std::string::npos) {
172     tones_.clear();
173     // Fire a “OnToneChange” event with an empty string and stop.
174     if (observer_) {
175       observer_->OnToneChange(std::string(), tones_);
176       observer_->OnToneChange(std::string());
177     }
178     return;
179   } else {
180     char tone = tones_[first_tone_pos];
181     if (!GetDtmfCode(tone, &code)) {
182       // The find_first_of(kDtmfValidTones) should have guarantee |tone| is
183       // a valid DTMF tone.
184       RTC_NOTREACHED();
185     }
186   }
187 
188   int tone_gap = inter_tone_gap_;
189   if (code == kDtmfCommaDelay) {
190     // Special case defined by WebRTC - By default, the character ',' indicates
191     // a delay of 2 seconds before processing the next character in the tones
192     // parameter. The comma delay can be set to a non default value via
193     // InsertDtmf to comply with legacy WebRTC clients.
194     tone_gap = comma_delay_;
195   } else {
196     if (!provider_) {
197       RTC_LOG(LS_ERROR) << "The DtmfProvider has been destroyed.";
198       return;
199     }
200     // The provider starts playout of the given tone on the
201     // associated RTP media stream, using the appropriate codec.
202     if (!provider_->InsertDtmf(code, duration_)) {
203       RTC_LOG(LS_ERROR) << "The DtmfProvider can no longer send DTMF.";
204       return;
205     }
206     // Wait for the number of milliseconds specified by |duration_|.
207     tone_gap += duration_;
208   }
209 
210   // Fire a “OnToneChange” event with the tone that's just processed.
211   if (observer_) {
212     observer_->OnToneChange(tones_.substr(first_tone_pos, 1),
213                             tones_.substr(first_tone_pos + 1));
214     observer_->OnToneChange(tones_.substr(first_tone_pos, 1));
215   }
216 
217   // Erase the unrecognized characters plus the tone that's just processed.
218   tones_.erase(0, first_tone_pos + 1);
219 
220   // Continue with the next tone.
221   QueueInsertDtmf(RTC_FROM_HERE, tone_gap);
222 }
223 
OnProviderDestroyed()224 void DtmfSender::OnProviderDestroyed() {
225   RTC_LOG(LS_INFO) << "The Dtmf provider is deleted. Clear the sending queue.";
226   StopSending();
227   provider_ = nullptr;
228 }
229 
StopSending()230 void DtmfSender::StopSending() {
231   dtmf_driver_.Clear();
232 }
233 
234 }  // namespace webrtc
235