1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "components/copresence/handlers/audio/audio_directive_handler.h" 6 7#include "base/bind.h" 8#include "base/logging.h" 9#include "base/memory/scoped_ptr.h" 10#include "base/strings/string_util.h" 11#include "base/time/time.h" 12#include "components/copresence/mediums/audio/audio_player.h" 13#include "components/copresence/mediums/audio/audio_recorder.h" 14#include "components/copresence/proto/data.pb.h" 15#include "media/base/audio_bus.h" 16 17namespace { 18 19// UrlSafe is defined as: 20// '/' represented by a '_' and '+' represented by a '-' 21// TODO(rkc): Move this processing to the whispernet wrapper. 22std::string FromUrlSafe(std::string token) { 23 base::ReplaceChars(token, "-", "+", &token); 24 base::ReplaceChars(token, "_", "/", &token); 25 return token; 26} 27 28const int kSampleExpiryTimeMs = 60 * 60 * 1000; // 60 minutes. 29const int kMaxSamples = 10000; 30 31} // namespace 32 33namespace copresence { 34 35// Public methods. 36 37AudioDirectiveHandler::AudioDirectiveHandler( 38 const AudioRecorder::DecodeSamplesCallback& decode_cb, 39 const AudioDirectiveHandler::EncodeTokenCallback& encode_cb) 40 : player_audible_(NULL), 41 player_inaudible_(NULL), 42 recorder_(NULL), 43 decode_cb_(decode_cb), 44 encode_cb_(encode_cb), 45 samples_cache_audible_( 46 base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs), 47 kMaxSamples), 48 samples_cache_inaudible_( 49 base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs), 50 kMaxSamples) { 51} 52 53AudioDirectiveHandler::~AudioDirectiveHandler() { 54 if (player_audible_) 55 player_audible_->Finalize(); 56 if (player_inaudible_) 57 player_inaudible_->Finalize(); 58 if (recorder_) 59 recorder_->Finalize(); 60} 61 62void AudioDirectiveHandler::Initialize() { 63 player_audible_ = new AudioPlayer(); 64 player_audible_->Initialize(); 65 66 player_inaudible_ = new AudioPlayer(); 67 player_inaudible_->Initialize(); 68 69 recorder_ = new AudioRecorder(decode_cb_); 70 recorder_->Initialize(); 71} 72 73void AudioDirectiveHandler::AddInstruction(const TokenInstruction& instruction, 74 const std::string& op_id, 75 base::TimeDelta ttl) { 76 switch (instruction.token_instruction_type()) { 77 case TRANSMIT: 78 DVLOG(2) << "Audio Transmit Directive received. Token: " 79 << instruction.token_id() 80 << " with TTL=" << ttl.InMilliseconds(); 81 switch (instruction.medium()) { 82 case AUDIO_ULTRASOUND_PASSBAND: 83 transmits_list_inaudible_.AddDirective(op_id, ttl); 84 PlayToken(instruction.token_id(), false); 85 break; 86 case AUDIO_AUDIBLE_DTMF: 87 transmits_list_audible_.AddDirective(op_id, ttl); 88 PlayToken(instruction.token_id(), true); 89 break; 90 default: 91 NOTREACHED(); 92 } 93 break; 94 case RECEIVE: 95 DVLOG(2) << "Audio Receive Directive received. TTL=" 96 << ttl.InMilliseconds(); 97 receives_list_.AddDirective(op_id, ttl); 98 ProcessNextReceive(); 99 break; 100 case UNKNOWN_TOKEN_INSTRUCTION_TYPE: 101 default: 102 LOG(WARNING) << "Unknown Audio Transmit Directive received."; 103 } 104} 105 106void AudioDirectiveHandler::RemoveInstructions(const std::string& op_id) { 107 transmits_list_audible_.RemoveDirective(op_id); 108 transmits_list_inaudible_.RemoveDirective(op_id); 109 receives_list_.RemoveDirective(op_id); 110 111 ProcessNextTransmit(); 112 ProcessNextReceive(); 113} 114 115// Private methods. 116 117void AudioDirectiveHandler::ProcessNextTransmit() { 118 // If we have an active directive for audible or inaudible audio, ensure that 119 // we are playing our respective token; if we do not have a directive, then 120 // make sure we aren't playing. This is duplicate code, but for just two 121 // elements, it has hard to make a case for processing a loop instead. 122 123 scoped_ptr<AudioDirective> audible_transmit( 124 transmits_list_audible_.GetActiveDirective()); 125 if (audible_transmit && !player_audible_->IsPlaying() && 126 samples_cache_audible_.HasKey(current_token_audible_)) { 127 DVLOG(3) << "Playing audible for op_id: " << audible_transmit->op_id; 128 player_audible_->Play( 129 samples_cache_audible_.GetValue(current_token_audible_)); 130 stop_audible_playback_timer_.Start( 131 FROM_HERE, 132 audible_transmit->end_time - base::Time::Now(), 133 this, 134 &AudioDirectiveHandler::ProcessNextTransmit); 135 } else if (!audible_transmit && player_audible_->IsPlaying()) { 136 DVLOG(3) << "Stopping audible playback."; 137 current_token_audible_.clear(); 138 stop_audible_playback_timer_.Stop(); 139 player_audible_->Stop(); 140 } 141 142 scoped_ptr<AudioDirective> inaudible_transmit( 143 transmits_list_inaudible_.GetActiveDirective()); 144 if (inaudible_transmit && !player_inaudible_->IsPlaying() && 145 samples_cache_inaudible_.HasKey(current_token_inaudible_)) { 146 DVLOG(3) << "Playing inaudible for op_id: " << inaudible_transmit->op_id; 147 player_inaudible_->Play( 148 samples_cache_inaudible_.GetValue(current_token_inaudible_)); 149 stop_inaudible_playback_timer_.Start( 150 FROM_HERE, 151 inaudible_transmit->end_time - base::Time::Now(), 152 this, 153 &AudioDirectiveHandler::ProcessNextTransmit); 154 } else if (!inaudible_transmit && player_inaudible_->IsPlaying()) { 155 DVLOG(3) << "Stopping inaudible playback."; 156 current_token_inaudible_.clear(); 157 stop_inaudible_playback_timer_.Stop(); 158 player_inaudible_->Stop(); 159 } 160} 161 162void AudioDirectiveHandler::ProcessNextReceive() { 163 scoped_ptr<AudioDirective> receive(receives_list_.GetActiveDirective()); 164 165 if (receive && !recorder_->IsRecording()) { 166 DVLOG(3) << "Recording for op_id: " << receive->op_id; 167 recorder_->Record(); 168 stop_recording_timer_.Start(FROM_HERE, 169 receive->end_time - base::Time::Now(), 170 this, 171 &AudioDirectiveHandler::ProcessNextReceive); 172 } else if (!receive && recorder_->IsRecording()) { 173 DVLOG(3) << "Stopping Recording"; 174 stop_recording_timer_.Stop(); 175 recorder_->Stop(); 176 } 177} 178 179void AudioDirectiveHandler::PlayToken(const std::string token, bool audible) { 180 std::string valid_token = FromUrlSafe(token); 181 182 // If the token has been encoded already, use the cached samples. 183 if (audible && samples_cache_audible_.HasKey(valid_token)) { 184 current_token_audible_ = token; 185 ProcessNextTransmit(); 186 } else if (!audible && samples_cache_inaudible_.HasKey(valid_token)) { 187 current_token_inaudible_ = token; 188 ProcessNextTransmit(); 189 } else { 190 // Otherwise, encode the token and then play it. 191 encode_cb_.Run(valid_token, 192 audible, 193 base::Bind(&AudioDirectiveHandler::PlayEncodedToken, 194 base::Unretained(this))); 195 } 196} 197 198void AudioDirectiveHandler::PlayEncodedToken( 199 const std::string& token, 200 bool audible, 201 const scoped_refptr<media::AudioBusRefCounted>& samples) { 202 DVLOG(3) << "Token " << token << "[audible:" << audible << "] encoded."; 203 if (audible) { 204 samples_cache_audible_.Add(token, samples); 205 current_token_audible_ = token; 206 // Force process transmits to pick up the new token. 207 if (player_audible_->IsPlaying()) 208 player_audible_->Stop(); 209 } else { 210 samples_cache_inaudible_.Add(token, samples); 211 current_token_inaudible_ = token; 212 // Force process transmits to pick up the new token. 213 if (player_inaudible_->IsPlaying()) 214 player_inaudible_->Stop(); 215 } 216 217 ProcessNextTransmit(); 218} 219 220} // namespace copresence 221