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
17 namespace {
18
19 // UrlSafe is defined as:
20 // '/' represented by a '_' and '+' represented by a '-'
21 // TODO(rkc): Move this processing to the whispernet wrapper.
FromUrlSafe(std::string token)22 std::string FromUrlSafe(std::string token) {
23 base::ReplaceChars(token, "-", "+", &token);
24 base::ReplaceChars(token, "_", "/", &token);
25 return token;
26 }
27
28 const int kSampleExpiryTimeMs = 60 * 60 * 1000; // 60 minutes.
29 const int kMaxSamples = 10000;
30
31 } // namespace
32
33 namespace copresence {
34
35 // Public methods.
36
AudioDirectiveHandler(const AudioRecorder::DecodeSamplesCallback & decode_cb,const AudioDirectiveHandler::EncodeTokenCallback & encode_cb)37 AudioDirectiveHandler::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
~AudioDirectiveHandler()53 AudioDirectiveHandler::~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
Initialize()62 void 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
AddInstruction(const TokenInstruction & instruction,const std::string & op_id,base::TimeDelta ttl)73 void 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
RemoveInstructions(const std::string & op_id)106 void 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
ProcessNextTransmit()117 void 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
ProcessNextReceive()162 void 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
PlayToken(const std::string token,bool audible)179 void 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
PlayEncodedToken(const std::string & token,bool audible,const scoped_refptr<media::AudioBusRefCounted> & samples)198 void 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