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.
5 #include "components/copresence/handlers/audio/audio_directive_handler.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"
19 // UrlSafe is defined as:
20 // '/' represented by a '_' and '+' represented by a '-'
21 // TODO(rkc): Move this processing to the whispernet wrapper.
22 std::string FromUrlSafe(std::string token) {
23 base::ReplaceChars(token, "-", "+", &token);
24 base::ReplaceChars(token, "_", "/", &token);
28 const int kSampleExpiryTimeMs = 60 * 60 * 1000; // 60 minutes.
29 const int kMaxSamples = 10000;
33 namespace copresence {
37 AudioDirectiveHandler::AudioDirectiveHandler(
38 const AudioRecorder::DecodeSamplesCallback& decode_cb,
39 const AudioDirectiveHandler::EncodeTokenCallback& encode_cb)
40 : player_audible_(NULL),
41 player_inaudible_(NULL),
43 decode_cb_(decode_cb),
44 encode_cb_(encode_cb),
45 samples_cache_audible_(
46 base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs),
48 samples_cache_inaudible_(
49 base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs),
53 AudioDirectiveHandler::~AudioDirectiveHandler() {
55 player_audible_->Finalize();
56 if (player_inaudible_)
57 player_inaudible_->Finalize();
59 recorder_->Finalize();
62 void AudioDirectiveHandler::Initialize() {
63 player_audible_ = new AudioPlayer();
64 player_audible_->Initialize();
66 player_inaudible_ = new AudioPlayer();
67 player_inaudible_->Initialize();
69 recorder_ = new AudioRecorder(decode_cb_);
70 recorder_->Initialize();
73 void AudioDirectiveHandler::AddInstruction(const TokenInstruction& instruction,
74 const std::string& op_id,
75 base::TimeDelta ttl) {
76 switch (instruction.token_instruction_type()) {
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 HandleToken(instruction.token_id(), false);
86 case AUDIO_AUDIBLE_DTMF:
87 transmits_list_audible_.AddDirective(op_id, ttl);
88 HandleToken(instruction.token_id(), true);
95 DVLOG(2) << "Audio Receive Directive received. TTL="
96 << ttl.InMilliseconds();
97 receives_list_.AddDirective(op_id, ttl);
99 case UNKNOWN_TOKEN_INSTRUCTION_TYPE:
101 LOG(WARNING) << "Unknown Audio Transmit Directive received.";
103 // ExecuteNextTransmit will be called by directive_list_ when Add is done.
104 ProcessNextReceive();
107 void AudioDirectiveHandler::RemoveInstructions(const std::string& op_id) {
108 transmits_list_audible_.RemoveDirective(op_id);
109 transmits_list_inaudible_.RemoveDirective(op_id);
110 receives_list_.RemoveDirective(op_id);
112 ProcessNextTransmit();
113 ProcessNextReceive();
118 void AudioDirectiveHandler::ProcessNextTransmit() {
119 // If we have an active directive for audible or inaudible audio, ensure that
120 // we are playing our respective token; if we do not have a directive, then
121 // make sure we aren't playing. This is duplicate code, but for just two
122 // elements, it has hard to make a case for processing a loop instead.
124 scoped_ptr<AudioDirective> audible_transmit(
125 transmits_list_audible_.GetActiveDirective());
126 if (audible_transmit && !player_audible_->IsPlaying()) {
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(
132 audible_transmit->end_time - base::Time::Now(),
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();
142 scoped_ptr<AudioDirective> inaudible_transmit(
143 transmits_list_inaudible_.GetActiveDirective());
144 if (inaudible_transmit && !player_inaudible_->IsPlaying()) {
145 DVLOG(3) << "Playing inaudible for op_id: " << inaudible_transmit->op_id;
146 player_inaudible_->Play(
147 samples_cache_inaudible_.GetValue(current_token_inaudible_));
148 stop_inaudible_playback_timer_.Start(
150 inaudible_transmit->end_time - base::Time::Now(),
152 &AudioDirectiveHandler::ProcessNextTransmit);
153 } else if (!inaudible_transmit && player_inaudible_->IsPlaying()) {
154 DVLOG(3) << "Stopping inaudible playback.";
155 current_token_inaudible_.clear();
156 stop_inaudible_playback_timer_.Stop();
157 player_inaudible_->Stop();
161 void AudioDirectiveHandler::ProcessNextReceive() {
162 scoped_ptr<AudioDirective> receive(receives_list_.GetActiveDirective());
164 if (receive && !recorder_->IsRecording()) {
165 DVLOG(3) << "Recording for op_id: " << receive->op_id;
167 stop_recording_timer_.Start(FROM_HERE,
168 receive->end_time - base::Time::Now(),
170 &AudioDirectiveHandler::ProcessNextReceive);
171 } else if (!receive && recorder_->IsRecording()) {
172 DVLOG(3) << "Stopping Recording";
173 stop_recording_timer_.Stop();
178 void AudioDirectiveHandler::HandleToken(const std::string token, bool audible) {
179 std::string valid_token = FromUrlSafe(token);
181 if (audible && samples_cache_audible_.HasKey(valid_token)) {
182 current_token_audible_ = token;
183 ProcessNextTransmit();
187 if (!audible && samples_cache_inaudible_.HasKey(valid_token)) {
188 current_token_inaudible_ = token;
189 ProcessNextTransmit();
193 encode_cb_.Run(valid_token,
195 base::Bind(&AudioDirectiveHandler::OnTokenEncoded,
196 base::Unretained(this)));
199 void AudioDirectiveHandler::OnTokenEncoded(
200 const std::string& token,
202 const scoped_refptr<media::AudioBusRefCounted>& samples) {
203 DVLOG(3) << "Token: " << token << "[audible:" << audible << "] encoded.";
205 samples_cache_audible_.Add(token, samples);
206 current_token_audible_ = token;
207 // Force process transmits to pick up the new token.
208 if (player_audible_->IsPlaying())
209 player_audible_->Stop();
211 samples_cache_inaudible_.Add(token, samples);
212 current_token_inaudible_ = token;
213 // Force process transmits to pick up the new token.
214 if (player_inaudible_->IsPlaying())
215 player_inaudible_->Stop();
218 ProcessNextTransmit();
221 } // namespace copresence