xref: /aosp_15_r20/external/webrtc/pc/dtmf_sender.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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