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