1 /*
2  * libjingle
3  * Copyright 2012 Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "talk/app/webrtc/dtmfsender.h"
29 
30 #include <ctype.h>
31 
32 #include <string>
33 
34 #include "webrtc/base/logging.h"
35 #include "webrtc/base/thread.h"
36 
37 namespace webrtc {
38 
39 enum {
40   MSG_DO_INSERT_DTMF = 0,
41 };
42 
43 // RFC4733
44 //  +-------+--------+------+---------+
45 //  | Event | Code   | Type | Volume? |
46 //  +-------+--------+------+---------+
47 //  | 0--9  | 0--9   | tone | yes     |
48 //  | *     | 10     | tone | yes     |
49 //  | #     | 11     | tone | yes     |
50 //  | A--D  | 12--15 | tone | yes     |
51 //  +-------+--------+------+---------+
52 // The "," is a special event defined by the WebRTC spec. It means to delay for
53 // 2 seconds before processing the next tone. We use -1 as its code.
54 static const int kDtmfCodeTwoSecondDelay = -1;
55 static const int kDtmfTwoSecondInMs = 2000;
56 static const char kDtmfValidTones[] = ",0123456789*#ABCDabcd";
57 static const char kDtmfTonesTable[] = ",0123456789*#ABCD";
58 // The duration cannot be more than 6000ms or less than 70ms. The gap between
59 // tones must be at least 50 ms.
60 static const int kDtmfDefaultDurationMs = 100;
61 static const int kDtmfMinDurationMs = 70;
62 static const int kDtmfMaxDurationMs = 6000;
63 static const int kDtmfDefaultGapMs = 50;
64 static const int kDtmfMinGapMs = 50;
65 
66 // Get DTMF code from the DTMF event character.
GetDtmfCode(char tone,int * code)67 bool GetDtmfCode(char tone, int* code) {
68   // Convert a-d to A-D.
69   char event = toupper(tone);
70   const char* p = strchr(kDtmfTonesTable, event);
71   if (!p) {
72     return false;
73   }
74   *code = p - kDtmfTonesTable - 1;
75   return true;
76 }
77 
Create(AudioTrackInterface * track,rtc::Thread * signaling_thread,DtmfProviderInterface * provider)78 rtc::scoped_refptr<DtmfSender> DtmfSender::Create(
79     AudioTrackInterface* track,
80     rtc::Thread* signaling_thread,
81     DtmfProviderInterface* provider) {
82   if (!track || !signaling_thread) {
83     return NULL;
84   }
85   rtc::scoped_refptr<DtmfSender> dtmf_sender(
86       new rtc::RefCountedObject<DtmfSender>(track, signaling_thread,
87                                                   provider));
88   return dtmf_sender;
89 }
90 
DtmfSender(AudioTrackInterface * track,rtc::Thread * signaling_thread,DtmfProviderInterface * provider)91 DtmfSender::DtmfSender(AudioTrackInterface* track,
92                        rtc::Thread* signaling_thread,
93                        DtmfProviderInterface* provider)
94     : track_(track),
95       observer_(NULL),
96       signaling_thread_(signaling_thread),
97       provider_(provider),
98       duration_(kDtmfDefaultDurationMs),
99       inter_tone_gap_(kDtmfDefaultGapMs) {
100   ASSERT(track_ != NULL);
101   ASSERT(signaling_thread_ != NULL);
102   if (provider_) {
103     ASSERT(provider_->GetOnDestroyedSignal() != NULL);
104     provider_->GetOnDestroyedSignal()->connect(
105         this, &DtmfSender::OnProviderDestroyed);
106   }
107 }
108 
~DtmfSender()109 DtmfSender::~DtmfSender() {
110   if (provider_) {
111     ASSERT(provider_->GetOnDestroyedSignal() != NULL);
112     provider_->GetOnDestroyedSignal()->disconnect(this);
113   }
114   StopSending();
115 }
116 
RegisterObserver(DtmfSenderObserverInterface * observer)117 void DtmfSender::RegisterObserver(DtmfSenderObserverInterface* observer) {
118   observer_ = observer;
119 }
120 
UnregisterObserver()121 void DtmfSender::UnregisterObserver() {
122   observer_ = NULL;
123 }
124 
CanInsertDtmf()125 bool DtmfSender::CanInsertDtmf() {
126   ASSERT(signaling_thread_->IsCurrent());
127   if (!provider_) {
128     return false;
129   }
130   return provider_->CanInsertDtmf(track_->id());
131 }
132 
InsertDtmf(const std::string & tones,int duration,int inter_tone_gap)133 bool DtmfSender::InsertDtmf(const std::string& tones, int duration,
134                             int inter_tone_gap) {
135   ASSERT(signaling_thread_->IsCurrent());
136 
137   if (duration > kDtmfMaxDurationMs ||
138       duration < kDtmfMinDurationMs ||
139       inter_tone_gap < kDtmfMinGapMs) {
140     LOG(LS_ERROR) << "InsertDtmf is called with invalid duration or tones gap. "
141         << "The duration cannot be more than " << kDtmfMaxDurationMs
142         << "ms or less than " << kDtmfMinDurationMs << "ms. "
143         << "The gap between tones must be at least " << kDtmfMinGapMs << "ms.";
144     return false;
145   }
146 
147   if (!CanInsertDtmf()) {
148     LOG(LS_ERROR)
149         << "InsertDtmf is called on DtmfSender that can't send DTMF.";
150     return false;
151   }
152 
153   tones_ = tones;
154   duration_ = duration;
155   inter_tone_gap_ = inter_tone_gap;
156   // Clear the previous queue.
157   signaling_thread_->Clear(this, MSG_DO_INSERT_DTMF);
158   // Kick off a new DTMF task queue.
159   signaling_thread_->Post(this, MSG_DO_INSERT_DTMF);
160   return true;
161 }
162 
track() const163 const AudioTrackInterface* DtmfSender::track() const {
164   return track_;
165 }
166 
tones() const167 std::string DtmfSender::tones() const {
168   return tones_;
169 }
170 
duration() const171 int DtmfSender::duration() const {
172   return duration_;
173 }
174 
inter_tone_gap() const175 int DtmfSender::inter_tone_gap() const {
176   return inter_tone_gap_;
177 }
178 
OnMessage(rtc::Message * msg)179 void DtmfSender::OnMessage(rtc::Message* msg) {
180   switch (msg->message_id) {
181     case MSG_DO_INSERT_DTMF: {
182       DoInsertDtmf();
183       break;
184     }
185     default: {
186       ASSERT(false);
187       break;
188     }
189   }
190 }
191 
DoInsertDtmf()192 void DtmfSender::DoInsertDtmf() {
193   ASSERT(signaling_thread_->IsCurrent());
194 
195   // Get the first DTMF tone from the tone buffer. Unrecognized characters will
196   // be ignored and skipped.
197   size_t first_tone_pos = tones_.find_first_of(kDtmfValidTones);
198   int code = 0;
199   if (first_tone_pos == std::string::npos) {
200     tones_.clear();
201     // Fire a “OnToneChange” event with an empty string and stop.
202     if (observer_) {
203       observer_->OnToneChange(std::string());
204     }
205     return;
206   } else {
207     char tone = tones_[first_tone_pos];
208     if (!GetDtmfCode(tone, &code)) {
209       // The find_first_of(kDtmfValidTones) should have guarantee |tone| is
210       // a valid DTMF tone.
211       ASSERT(false);
212     }
213   }
214 
215   int tone_gap = inter_tone_gap_;
216   if (code == kDtmfCodeTwoSecondDelay) {
217     // Special case defined by WebRTC - The character',' indicates a delay of 2
218     // seconds before processing the next character in the tones parameter.
219     tone_gap = kDtmfTwoSecondInMs;
220   } else {
221     if (!provider_) {
222       LOG(LS_ERROR) << "The DtmfProvider has been destroyed.";
223       return;
224     }
225     // The provider starts playout of the given tone on the
226     // associated RTP media stream, using the appropriate codec.
227     if (!provider_->InsertDtmf(track_->id(), code, duration_)) {
228       LOG(LS_ERROR) << "The DtmfProvider can no longer send DTMF.";
229       return;
230     }
231     // Wait for the number of milliseconds specified by |duration_|.
232     tone_gap += duration_;
233   }
234 
235   // Fire a “OnToneChange” event with the tone that's just processed.
236   if (observer_) {
237     observer_->OnToneChange(tones_.substr(first_tone_pos, 1));
238   }
239 
240   // Erase the unrecognized characters plus the tone that's just processed.
241   tones_.erase(0, first_tone_pos + 1);
242 
243   // Continue with the next tone.
244   signaling_thread_->PostDelayed(tone_gap, this, MSG_DO_INSERT_DTMF);
245 }
246 
OnProviderDestroyed()247 void DtmfSender::OnProviderDestroyed() {
248   LOG(LS_INFO) << "The Dtmf provider is deleted. Clear the sending queue.";
249   StopSending();
250   provider_ = NULL;
251 }
252 
StopSending()253 void DtmfSender::StopSending() {
254   signaling_thread_->Clear(this);
255 }
256 
257 }  // namespace webrtc
258