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