audio_directive_handler.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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          HandleToken(instruction.token_id(), false);
85          break;
86        case AUDIO_AUDIBLE_DTMF:
87          transmits_list_audible_.AddDirective(op_id, ttl);
88          HandleToken(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      break;
99    case UNKNOWN_TOKEN_INSTRUCTION_TYPE:
100    default:
101      LOG(WARNING) << "Unknown Audio Transmit Directive received.";
102  }
103  // ExecuteNextTransmit will be called by directive_list_ when Add is done.
104  ProcessNextReceive();
105}
106
107void 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);
111
112  ProcessNextTransmit();
113  ProcessNextReceive();
114}
115
116// Private methods.
117
118void 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.
123
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(
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    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(
149        FROM_HERE,
150        inaudible_transmit->end_time - base::Time::Now(),
151        this,
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();
158  }
159}
160
161void AudioDirectiveHandler::ProcessNextReceive() {
162  scoped_ptr<AudioDirective> receive(receives_list_.GetActiveDirective());
163
164  if (receive && !recorder_->IsRecording()) {
165    DVLOG(3) << "Recording for op_id: " << receive->op_id;
166    recorder_->Record();
167    stop_recording_timer_.Start(FROM_HERE,
168                                receive->end_time - base::Time::Now(),
169                                this,
170                                &AudioDirectiveHandler::ProcessNextReceive);
171  } else if (!receive && recorder_->IsRecording()) {
172    DVLOG(3) << "Stopping Recording";
173    stop_recording_timer_.Stop();
174    recorder_->Stop();
175  }
176}
177
178void AudioDirectiveHandler::HandleToken(const std::string token, bool audible) {
179  std::string valid_token = FromUrlSafe(token);
180
181  if (audible && samples_cache_audible_.HasKey(valid_token)) {
182    current_token_audible_ = token;
183    ProcessNextTransmit();
184    return;
185  }
186
187  if (!audible && samples_cache_inaudible_.HasKey(valid_token)) {
188    current_token_inaudible_ = token;
189    ProcessNextTransmit();
190    return;
191  }
192
193  encode_cb_.Run(valid_token,
194                 audible,
195                 base::Bind(&AudioDirectiveHandler::OnTokenEncoded,
196                            base::Unretained(this)));
197}
198
199void AudioDirectiveHandler::OnTokenEncoded(
200    const std::string& token,
201    bool audible,
202    const scoped_refptr<media::AudioBusRefCounted>& samples) {
203  DVLOG(3) << "Token: " << token << "[audible:" << audible << "] encoded.";
204  if (audible) {
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();
210  } else {
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();
216  }
217
218  ProcessNextTransmit();
219}
220
221}  // namespace copresence
222