blob: fcf1d8247c66c93a3cbe08e8d489b61f02eeba92 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/copresence/handlers/audio/audio_directive_handler.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "components/copresence/mediums/audio/audio_player.h"
#include "components/copresence/mediums/audio/audio_recorder.h"
#include "components/copresence/proto/data.pb.h"
#include "media/base/audio_bus.h"
namespace {
// UrlSafe is defined as:
// '/' represented by a '_' and '+' represented by a '-'
// TODO(rkc): Move this processing to the whispernet wrapper.
std::string FromUrlSafe(std::string token) {
base::ReplaceChars(token, "-", "+", &token);
base::ReplaceChars(token, "_", "/", &token);
return token;
}
const int kSampleExpiryTimeMs = 60 * 60 * 1000; // 60 minutes.
const int kMaxSamples = 10000;
} // namespace
namespace copresence {
// Public methods.
AudioDirectiveHandler::AudioDirectiveHandler(
const AudioRecorder::DecodeSamplesCallback& decode_cb,
const AudioDirectiveHandler::EncodeTokenCallback& encode_cb)
: player_audible_(NULL),
player_inaudible_(NULL),
recorder_(NULL),
decode_cb_(decode_cb),
encode_cb_(encode_cb),
samples_cache_audible_(
base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs),
kMaxSamples),
samples_cache_inaudible_(
base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs),
kMaxSamples) {
}
AudioDirectiveHandler::~AudioDirectiveHandler() {
if (player_audible_)
player_audible_->Finalize();
if (player_inaudible_)
player_inaudible_->Finalize();
if (recorder_)
recorder_->Finalize();
}
void AudioDirectiveHandler::Initialize() {
player_audible_ = new AudioPlayer();
player_audible_->Initialize();
player_inaudible_ = new AudioPlayer();
player_inaudible_->Initialize();
recorder_ = new AudioRecorder(decode_cb_);
recorder_->Initialize();
}
void AudioDirectiveHandler::AddInstruction(const TokenInstruction& instruction,
const std::string& op_id,
base::TimeDelta ttl) {
switch (instruction.token_instruction_type()) {
case TRANSMIT:
DVLOG(2) << "Audio Transmit Directive received. Token: "
<< instruction.token_id()
<< " with TTL=" << ttl.InMilliseconds();
switch (instruction.medium()) {
case AUDIO_ULTRASOUND_PASSBAND:
transmits_list_inaudible_.AddDirective(op_id, ttl);
HandleToken(instruction.token_id(), false);
break;
case AUDIO_AUDIBLE_DTMF:
transmits_list_audible_.AddDirective(op_id, ttl);
HandleToken(instruction.token_id(), true);
break;
default:
NOTREACHED();
}
break;
case RECEIVE:
DVLOG(2) << "Audio Receive Directive received. TTL="
<< ttl.InMilliseconds();
receives_list_.AddDirective(op_id, ttl);
break;
case UNKNOWN_TOKEN_INSTRUCTION_TYPE:
default:
LOG(WARNING) << "Unknown Audio Transmit Directive received.";
}
// ExecuteNextTransmit will be called by directive_list_ when Add is done.
ProcessNextReceive();
}
void AudioDirectiveHandler::RemoveInstructions(const std::string& op_id) {
transmits_list_audible_.RemoveDirective(op_id);
transmits_list_inaudible_.RemoveDirective(op_id);
receives_list_.RemoveDirective(op_id);
ProcessNextTransmit();
ProcessNextReceive();
}
// Private methods.
void AudioDirectiveHandler::ProcessNextTransmit() {
// If we have an active directive for audible or inaudible audio, ensure that
// we are playing our respective token; if we do not have a directive, then
// make sure we aren't playing. This is duplicate code, but for just two
// elements, it has hard to make a case for processing a loop instead.
scoped_ptr<AudioDirective> audible_transmit(
transmits_list_audible_.GetActiveDirective());
if (audible_transmit && !player_audible_->IsPlaying()) {
DVLOG(3) << "Playing audible for op_id: " << audible_transmit->op_id;
player_audible_->Play(
samples_cache_audible_.GetValue(current_token_audible_));
stop_audible_playback_timer_.Start(
FROM_HERE,
audible_transmit->end_time - base::Time::Now(),
this,
&AudioDirectiveHandler::ProcessNextTransmit);
} else if (!audible_transmit && player_audible_->IsPlaying()) {
DVLOG(3) << "Stopping audible playback.";
current_token_audible_.clear();
stop_audible_playback_timer_.Stop();
player_audible_->Stop();
}
scoped_ptr<AudioDirective> inaudible_transmit(
transmits_list_inaudible_.GetActiveDirective());
if (inaudible_transmit && !player_inaudible_->IsPlaying()) {
DVLOG(3) << "Playing inaudible for op_id: " << inaudible_transmit->op_id;
player_inaudible_->Play(
samples_cache_inaudible_.GetValue(current_token_inaudible_));
stop_inaudible_playback_timer_.Start(
FROM_HERE,
inaudible_transmit->end_time - base::Time::Now(),
this,
&AudioDirectiveHandler::ProcessNextTransmit);
} else if (!inaudible_transmit && player_inaudible_->IsPlaying()) {
DVLOG(3) << "Stopping inaudible playback.";
current_token_inaudible_.clear();
stop_inaudible_playback_timer_.Stop();
player_inaudible_->Stop();
}
}
void AudioDirectiveHandler::ProcessNextReceive() {
scoped_ptr<AudioDirective> receive(receives_list_.GetActiveDirective());
if (receive && !recorder_->IsRecording()) {
DVLOG(3) << "Recording for op_id: " << receive->op_id;
recorder_->Record();
stop_recording_timer_.Start(FROM_HERE,
receive->end_time - base::Time::Now(),
this,
&AudioDirectiveHandler::ProcessNextReceive);
} else if (!receive && recorder_->IsRecording()) {
DVLOG(3) << "Stopping Recording";
stop_recording_timer_.Stop();
recorder_->Stop();
}
}
void AudioDirectiveHandler::HandleToken(const std::string token, bool audible) {
std::string valid_token = FromUrlSafe(token);
if (audible && samples_cache_audible_.HasKey(valid_token)) {
current_token_audible_ = token;
ProcessNextTransmit();
return;
}
if (!audible && samples_cache_inaudible_.HasKey(valid_token)) {
current_token_inaudible_ = token;
ProcessNextTransmit();
return;
}
encode_cb_.Run(valid_token,
audible,
base::Bind(&AudioDirectiveHandler::OnTokenEncoded,
base::Unretained(this)));
}
void AudioDirectiveHandler::OnTokenEncoded(
const std::string& token,
bool audible,
const scoped_refptr<media::AudioBusRefCounted>& samples) {
DVLOG(3) << "Token: " << token << "[audible:" << audible << "] encoded.";
if (audible) {
samples_cache_audible_.Add(token, samples);
current_token_audible_ = token;
// Force process transmits to pick up the new token.
if (player_audible_->IsPlaying())
player_audible_->Stop();
} else {
samples_cache_inaudible_.Add(token, samples);
current_token_inaudible_ = token;
// Force process transmits to pick up the new token.
if (player_inaudible_->IsPlaying())
player_inaudible_->Stop();
}
ProcessNextTransmit();
}
} // namespace copresence