blob: 45a4a58abbd56c0478029419691150fc2370a466 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:361/*
kjellanderb24317b2016-02-10 15:54:432 * Copyright 2012 The WebRTC project authors. All Rights Reserved.
henrike@webrtc.org28e20752013-07-10 00:45:363 *
kjellanderb24317b2016-02-10 15:54:434 * 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.
henrike@webrtc.org28e20752013-07-10 00:45:369 */
10
Steve Anton10542f22019-01-11 17:11:0011#include "pc/dtmf_sender.h"
henrike@webrtc.org28e20752013-07-10 00:45:3612
13#include <ctype.h>
Yves Gerey3e707812018-11-28 15:47:4914#include <string.h>
Jonas Olssona4d87372019-07-05 17:08:3315
Danil Chapovalova30439b2022-07-07 08:08:4916#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"
Mirko Bonadei92ea95e2017-09-15 04:47:3119#include "rtc_base/checks.h"
20#include "rtc_base/logging.h"
henrike@webrtc.org28e20752013-07-10 00:45:3621
22namespace webrtc {
23
henrike@webrtc.org28e20752013-07-10 00:45:3624// 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.
Aaron Alaniz529d8862020-01-21 03:09:4735static const int kDtmfCommaDelay = -1;
henrike@webrtc.org28e20752013-07-10 00:45:3636static const char kDtmfValidTones[] = ",0123456789*#ABCDabcd";
37static const char kDtmfTonesTable[] = ",0123456789*#ABCD";
dminor588101c2017-03-28 18:18:3238// The duration cannot be more than 6000ms or less than 40ms. The gap between
henrike@webrtc.org28e20752013-07-10 00:45:3639// tones must be at least 50 ms.
Harald Alvestrand52e58522018-02-20 07:15:3640// Source for values: W3C WEBRTC specification.
41// https://w3c.github.io/webrtc-pc/#dom-rtcdtmfsender-insertdtmf
henrike@webrtc.org28e20752013-07-10 00:45:3642static const int kDtmfDefaultDurationMs = 100;
dminor588101c2017-03-28 18:18:3243static const int kDtmfMinDurationMs = 40;
henrike@webrtc.org28e20752013-07-10 00:45:3644static const int kDtmfMaxDurationMs = 6000;
45static const int kDtmfDefaultGapMs = 50;
Harald Alvestrand52e58522018-02-20 07:15:3646static const int kDtmfMinGapMs = 30;
henrike@webrtc.org28e20752013-07-10 00:45:3647
48// Get DTMF code from the DTMF event character.
49bool 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
buildbot@webrtc.orgd4e598d2014-07-29 17:36:5260rtc::scoped_refptr<DtmfSender> DtmfSender::Create(
Danil Chapovalova30439b2022-07-07 08:08:4961 TaskQueueBase* signaling_thread,
henrike@webrtc.org28e20752013-07-10 00:45:3662 DtmfProviderInterface* provider) {
deadbeef20cb0c12017-02-02 04:27:0063 if (!signaling_thread) {
64 return nullptr;
henrike@webrtc.org28e20752013-07-10 00:45:3665 }
Tommi87f70902021-04-27 12:43:0866 return rtc::make_ref_counted<DtmfSender>(signaling_thread, provider);
henrike@webrtc.org28e20752013-07-10 00:45:3667}
68
Danil Chapovalova30439b2022-07-07 08:08:4969DtmfSender::DtmfSender(TaskQueueBase* signaling_thread,
henrike@webrtc.org28e20752013-07-10 00:45:3670 DtmfProviderInterface* provider)
Steve Antonb983bae2018-06-20 18:16:5371 : observer_(nullptr),
henrike@webrtc.org28e20752013-07-10 00:45:3672 signaling_thread_(signaling_thread),
73 provider_(provider),
74 duration_(kDtmfDefaultDurationMs),
Aaron Alaniz529d8862020-01-21 03:09:4775 inter_tone_gap_(kDtmfDefaultGapMs),
76 comma_delay_(kDtmfDefaultCommaDelayMs) {
Steve Antonb983bae2018-06-20 18:16:5377 RTC_DCHECK(signaling_thread_);
Fredrik Solenbergda2afbd2022-08-03 10:07:5178 RTC_DCHECK(provider_);
79}
80
81void 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;
henrike@webrtc.org28e20752013-07-10 00:45:3686}
87
88DtmfSender::~DtmfSender() {
Niels Möller08489942021-03-18 08:18:4889 RTC_DCHECK_RUN_ON(signaling_thread_);
henrike@webrtc.org28e20752013-07-10 00:45:3690 StopSending();
91}
92
93void DtmfSender::RegisterObserver(DtmfSenderObserverInterface* observer) {
Niels Möller08489942021-03-18 08:18:4894 RTC_DCHECK_RUN_ON(signaling_thread_);
henrike@webrtc.org28e20752013-07-10 00:45:3695 observer_ = observer;
96}
97
98void DtmfSender::UnregisterObserver() {
Niels Möller08489942021-03-18 08:18:4899 RTC_DCHECK_RUN_ON(signaling_thread_);
Steve Antonb983bae2018-06-20 18:16:53100 observer_ = nullptr;
henrike@webrtc.org28e20752013-07-10 00:45:36101}
102
103bool DtmfSender::CanInsertDtmf() {
Niels Möller08489942021-03-18 08:18:48104 RTC_DCHECK_RUN_ON(signaling_thread_);
henrike@webrtc.org28e20752013-07-10 00:45:36105 if (!provider_) {
106 return false;
107 }
deadbeef20cb0c12017-02-02 04:27:00108 return provider_->CanInsertDtmf();
henrike@webrtc.org28e20752013-07-10 00:45:36109}
110
Yves Gerey665174f2018-06-19 13:03:05111bool DtmfSender::InsertDtmf(const std::string& tones,
112 int duration,
Aaron Alaniz529d8862020-01-21 03:09:47113 int inter_tone_gap,
114 int comma_delay) {
Niels Möller08489942021-03-18 08:18:48115 RTC_DCHECK_RUN_ON(signaling_thread_);
henrike@webrtc.org28e20752013-07-10 00:45:36116
Yves Gerey665174f2018-06-19 13:03:05117 if (duration > kDtmfMaxDurationMs || duration < kDtmfMinDurationMs ||
Aaron Alaniz529d8862020-01-21 03:09:47118 inter_tone_gap < kDtmfMinGapMs || comma_delay < kDtmfMinGapMs) {
Mirko Bonadei675513b2017-11-09 10:09:25119 RTC_LOG(LS_ERROR)
120 << "InsertDtmf is called with invalid duration or tones gap. "
Jonas Olsson45cc8902018-02-13 09:37:07121 "The duration cannot be more than "
122 << kDtmfMaxDurationMs << "ms or less than " << kDtmfMinDurationMs
Yves Gerey665174f2018-06-19 13:03:05123 << "ms. The gap between tones must be at least " << kDtmfMinGapMs
124 << "ms.";
henrike@webrtc.org28e20752013-07-10 00:45:36125 return false;
126 }
127
128 if (!CanInsertDtmf()) {
Mirko Bonadei675513b2017-11-09 10:09:25129 RTC_LOG(LS_ERROR)
henrike@webrtc.org28e20752013-07-10 00:45:36130 << "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;
Aaron Alaniz529d8862020-01-21 03:09:47137 comma_delay_ = comma_delay;
Niels Möller08489942021-03-18 08:18:48138
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.
Danil Chapovalov9e09a1f2022-09-08 16:38:10145 QueueInsertDtmf(1 /*ms*/);
henrike@webrtc.org28e20752013-07-10 00:45:36146 return true;
147}
148
henrike@webrtc.org28e20752013-07-10 00:45:36149std::string DtmfSender::tones() const {
Niels Möller08489942021-03-18 08:18:48150 RTC_DCHECK_RUN_ON(signaling_thread_);
henrike@webrtc.org28e20752013-07-10 00:45:36151 return tones_;
152}
153
154int DtmfSender::duration() const {
Niels Möller08489942021-03-18 08:18:48155 RTC_DCHECK_RUN_ON(signaling_thread_);
henrike@webrtc.org28e20752013-07-10 00:45:36156 return duration_;
157}
158
159int DtmfSender::inter_tone_gap() const {
Niels Möller08489942021-03-18 08:18:48160 RTC_DCHECK_RUN_ON(signaling_thread_);
henrike@webrtc.org28e20752013-07-10 00:45:36161 return inter_tone_gap_;
162}
163
Aaron Alaniz529d8862020-01-21 03:09:47164int DtmfSender::comma_delay() const {
Niels Möller08489942021-03-18 08:18:48165 RTC_DCHECK_RUN_ON(signaling_thread_);
Aaron Alaniz529d8862020-01-21 03:09:47166 return comma_delay_;
167}
168
Danil Chapovalov9e09a1f2022-09-08 16:38:10169void DtmfSender::QueueInsertDtmf(uint32_t delay_ms) {
Henrik Boström2dd39152022-01-25 07:20:33170 signaling_thread_->PostDelayedHighPrecisionTask(
Danil Chapovalova30439b2022-07-07 08:08:49171 SafeTask(safety_flag_,
172 [this] {
173 RTC_DCHECK_RUN_ON(signaling_thread_);
174 DoInsertDtmf();
175 }),
176 TimeDelta::Millis(delay_ms));
henrike@webrtc.org28e20752013-07-10 00:45:36177}
178
179void DtmfSender::DoInsertDtmf() {
henrike@webrtc.org28e20752013-07-10 00:45:36180 // 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_) {
Harald Alvestrandd7b79af2018-09-06 07:33:56188 observer_->OnToneChange(std::string(), tones_);
henrike@webrtc.org28e20752013-07-10 00:45:36189 observer_->OnToneChange(std::string());
190 }
191 return;
192 } else {
193 char tone = tones_[first_tone_pos];
194 if (!GetDtmfCode(tone, &code)) {
Artem Titov880fa812021-07-30 20:30:23195 // The find_first_of(kDtmfValidTones) should have guarantee `tone` is
henrike@webrtc.org28e20752013-07-10 00:45:36196 // a valid DTMF tone.
Artem Titovd3251962021-11-15 15:57:07197 RTC_DCHECK_NOTREACHED();
henrike@webrtc.org28e20752013-07-10 00:45:36198 }
199 }
200
201 int tone_gap = inter_tone_gap_;
Aaron Alaniz529d8862020-01-21 03:09:47202 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_;
henrike@webrtc.org28e20752013-07-10 00:45:36208 } else {
209 if (!provider_) {
Mirko Bonadei675513b2017-11-09 10:09:25210 RTC_LOG(LS_ERROR) << "The DtmfProvider has been destroyed.";
henrike@webrtc.org28e20752013-07-10 00:45:36211 return;
212 }
213 // The provider starts playout of the given tone on the
214 // associated RTP media stream, using the appropriate codec.
deadbeef20cb0c12017-02-02 04:27:00215 if (!provider_->InsertDtmf(code, duration_)) {
Mirko Bonadei675513b2017-11-09 10:09:25216 RTC_LOG(LS_ERROR) << "The DtmfProvider can no longer send DTMF.";
henrike@webrtc.org28e20752013-07-10 00:45:36217 return;
218 }
Artem Titov880fa812021-07-30 20:30:23219 // Wait for the number of milliseconds specified by `duration_`.
henrike@webrtc.org28e20752013-07-10 00:45:36220 tone_gap += duration_;
221 }
222
223 // Fire a “OnToneChange” event with the tone that's just processed.
224 if (observer_) {
Harald Alvestrandd7b79af2018-09-06 07:33:56225 observer_->OnToneChange(tones_.substr(first_tone_pos, 1),
226 tones_.substr(first_tone_pos + 1));
henrike@webrtc.org28e20752013-07-10 00:45:36227 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.
Danil Chapovalov9e09a1f2022-09-08 16:38:10234 QueueInsertDtmf(tone_gap);
henrike@webrtc.org28e20752013-07-10 00:45:36235}
236
henrike@webrtc.org28e20752013-07-10 00:45:36237void DtmfSender::StopSending() {
Niels Möller08489942021-03-18 08:18:48238 if (safety_flag_) {
239 safety_flag_->SetNotAlive();
240 }
henrike@webrtc.org28e20752013-07-10 00:45:36241}
242
243} // namespace webrtc