Home | History | Annotate | Download | only in webrtc
      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 "talk/base/logging.h"
     35 #include "talk/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.
     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 
     78 talk_base::scoped_refptr<DtmfSender> DtmfSender::Create(
     79     AudioTrackInterface* track,
     80     talk_base::Thread* signaling_thread,
     81     DtmfProviderInterface* provider) {
     82   if (!track || !signaling_thread) {
     83     return NULL;
     84   }
     85   talk_base::scoped_refptr<DtmfSender> dtmf_sender(
     86       new talk_base::RefCountedObject<DtmfSender>(track, signaling_thread,
     87                                                   provider));
     88   return dtmf_sender;
     89 }
     90 
     91 DtmfSender::DtmfSender(AudioTrackInterface* track,
     92                        talk_base::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 
    109 DtmfSender::~DtmfSender() {
    110   if (provider_) {
    111     ASSERT(provider_->GetOnDestroyedSignal() != NULL);
    112     provider_->GetOnDestroyedSignal()->disconnect(this);
    113   }
    114   StopSending();
    115 }
    116 
    117 void DtmfSender::RegisterObserver(DtmfSenderObserverInterface* observer) {
    118   observer_ = observer;
    119 }
    120 
    121 void DtmfSender::UnregisterObserver() {
    122   observer_ = NULL;
    123 }
    124 
    125 bool DtmfSender::CanInsertDtmf() {
    126   ASSERT(signaling_thread_->IsCurrent());
    127   if (!provider_) {
    128     return false;
    129   }
    130   return provider_->CanInsertDtmf(track_->id());
    131 }
    132 
    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 
    163 const AudioTrackInterface* DtmfSender::track() const {
    164   return track_;
    165 }
    166 
    167 std::string DtmfSender::tones() const {
    168   return tones_;
    169 }
    170 
    171 int DtmfSender::duration() const {
    172   return duration_;
    173 }
    174 
    175 int DtmfSender::inter_tone_gap() const {
    176   return inter_tone_gap_;
    177 }
    178 
    179 void DtmfSender::OnMessage(talk_base::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 
    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 
    247 void DtmfSender::OnProviderDestroyed() {
    248   LOG(LS_INFO) << "The Dtmf provider is deleted. Clear the sending queue.";
    249   StopSending();
    250   provider_ = NULL;
    251 }
    252 
    253 void DtmfSender::StopSending() {
    254   signaling_thread_->Clear(this);
    255 }
    256 
    257 }  // namespace webrtc
    258