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