| Index: components/copresence/mediums/audio/audio_manager_impl.cc
|
| diff --git a/components/copresence/mediums/audio/audio_manager_impl.cc b/components/copresence/mediums/audio/audio_manager_impl.cc
|
| index 387eb54d33901ec0f65a2b9734945bdcf4c0d8be..c8a03a26e137f72a9205ccd94bbeb2a9d5eaeb7f 100644
|
| --- a/components/copresence/mediums/audio/audio_manager_impl.cc
|
| +++ b/components/copresence/mediums/audio/audio_manager_impl.cc
|
| @@ -5,14 +5,18 @@
|
| #include "components/copresence/mediums/audio/audio_manager_impl.h"
|
|
|
| #include <algorithm>
|
| +#include <limits>
|
| #include <vector>
|
|
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| +#include "base/command_line.h"
|
| +#include "base/files/file_util.h"
|
| #include "base/logging.h"
|
| #include "base/run_loop.h"
|
| #include "base/strings/string_util.h"
|
| #include "base/time/time.h"
|
| +#include "components/copresence/copresence_switches.h"
|
| #include "components/copresence/mediums/audio/audio_player_impl.h"
|
| #include "components/copresence/mediums/audio/audio_recorder_impl.h"
|
| #include "components/copresence/public/copresence_constants.h"
|
| @@ -21,27 +25,59 @@
|
| #include "media/audio/audio_manager.h"
|
| #include "media/audio/audio_manager_base.h"
|
| #include "media/base/audio_bus.h"
|
| +#include "third_party/webrtc/common_audio/wav_file.h"
|
|
|
| namespace copresence {
|
|
|
| namespace {
|
|
|
| +const int kSampleExpiryTimeMs = 60 * 60 * 1000; // 60 minutes.
|
| +const int kMaxSamples = 10000;
|
| +const int kTokenTimeoutMs = 2000;
|
| +const int kMonoChannelCount = 1;
|
| +
|
| // UrlSafe is defined as:
|
| // '/' represented by a '_' and '+' represented by a '-'
|
| -// TODO(rkc): Move this processing to the whispernet wrapper.
|
| +// TODO(ckehoe): Move this to a central place.
|
| std::string FromUrlSafe(std::string token) {
|
| base::ReplaceChars(token, "-", "+", &token);
|
| base::ReplaceChars(token, "_", "/", &token);
|
| return token;
|
| }
|
| +std::string ToUrlSafe(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;
|
| -const int kTokenTimeoutMs = 2000;
|
| +// TODO(ckehoe): Move this to a central place.
|
| +std::string AudioTypeToString(AudioType audio_type) {
|
| + if (audio_type == AUDIBLE)
|
| + return "audible";
|
| + if (audio_type == INAUDIBLE)
|
| + return "inaudible";
|
| +
|
| + NOTREACHED() << "Got unexpected token type " << audio_type;
|
| + return std::string();
|
| +}
|
| +
|
| +bool ReadBooleanFlag(const std::string& flag, bool default_value) {
|
| + const std::string flag_value = base::StringToLowerASCII(
|
| + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(flag));
|
| + if (flag_value == "true" || flag_value == "1")
|
| + return true;
|
| + if (flag_value == "false" || flag_value == "0")
|
| + return false;
|
| + LOG_IF(ERROR, !flag_value.empty())
|
| + << "Unrecognized value \"" << flag_value << " for flag "
|
| + << flag << ". Defaulting to " << default_value;
|
| + return default_value;
|
| +}
|
|
|
| } // namespace
|
|
|
| -// Public methods.
|
| +
|
| +// Public functions.
|
|
|
| AudioManagerImpl::AudioManagerImpl()
|
| : whispernet_client_(nullptr), recorder_(nullptr) {
|
| @@ -51,6 +87,10 @@ AudioManagerImpl::AudioManagerImpl()
|
| should_be_recording_[AUDIBLE] = false;
|
| should_be_recording_[INAUDIBLE] = false;
|
|
|
| + player_enabled_[AUDIBLE] = ReadBooleanFlag(
|
| + switches::kCopresenceEnableAudibleBroadcast, true);
|
| + player_enabled_[INAUDIBLE] = ReadBooleanFlag(
|
| + switches::kCopresenceEnableInaudibleBroadcast, true);
|
| player_[AUDIBLE] = nullptr;
|
| player_[INAUDIBLE] = nullptr;
|
| token_length_[0] = 0;
|
| @@ -88,6 +128,14 @@ void AudioManagerImpl::Initialize(WhispernetClient* whispernet_client,
|
| if (!recorder_)
|
| recorder_ = new AudioRecorderImpl();
|
| recorder_->Initialize(decode_cancelable_cb_.callback());
|
| +
|
| + dump_tokens_dir_ = base::FilePath(base::CommandLine::ForCurrentProcess()
|
| + ->GetSwitchValueASCII(switches::kCopresenceDumpTokensToDir));
|
| + content::BrowserThread::PostTask(
|
| + content::BrowserThread::FILE,
|
| + FROM_HERE,
|
| + base::Bind(&AudioManagerImpl::ValidateTokenDir,
|
| + base::Unretained(this)));
|
| }
|
|
|
| AudioManagerImpl::~AudioManagerImpl() {
|
| @@ -113,10 +161,16 @@ void AudioManagerImpl::StartPlaying(AudioType type) {
|
| // will call this code again (if we're still supposed to be playing).
|
| if (samples_cache_[type]->HasKey(playing_token_[type])) {
|
| DCHECK(!playing_token_[type].empty());
|
| - started_playing_[type] = base::Time::Now();
|
| - player_[type]->Play(samples_cache_[type]->GetValue(playing_token_[type]));
|
| - // If we're playing, we always record to hear what we are playing.
|
| - recorder_->Record();
|
| + if (player_enabled_[type]) {
|
| + started_playing_[type] = base::Time::Now();
|
| + player_[type]->Play(samples_cache_[type]->GetValue(playing_token_[type]));
|
| +
|
| + // If we're playing, we always record to hear what we are playing.
|
| + recorder_->Record();
|
| + } else {
|
| + DVLOG(3) << "Skipping playback for disabled " << AudioTypeToString(type)
|
| + << " player.";
|
| + }
|
| }
|
| }
|
|
|
| @@ -173,13 +227,15 @@ void AudioManagerImpl::SetTokenLength(AudioType type, size_t token_length) {
|
| token_length_[type] = token_length;
|
| }
|
|
|
| -// Private methods.
|
| +
|
| +// Private functions.
|
|
|
| void AudioManagerImpl::OnTokenEncoded(
|
| AudioType type,
|
| const std::string& token,
|
| const scoped_refptr<media::AudioBusRefCounted>& samples) {
|
| samples_cache_[type]->Add(token, samples);
|
| + DumpToken(type, token, samples.get());
|
| UpdateToken(type, token);
|
| }
|
|
|
| @@ -222,11 +278,8 @@ void AudioManagerImpl::RestartPlaying(AudioType type) {
|
| // in the cache.
|
| DCHECK(samples_cache_[type]->HasKey(playing_token_[type]));
|
|
|
| - started_playing_[type] = base::Time::Now();
|
| player_[type]->Stop();
|
| - player_[type]->Play(samples_cache_[type]->GetValue(playing_token_[type]));
|
| - // If we're playing, we always record to hear what we are playing.
|
| - recorder_->Record();
|
| + StartPlaying(type);
|
| }
|
|
|
| void AudioManagerImpl::DecodeSamplesConnector(const std::string& samples) {
|
| @@ -250,4 +303,41 @@ void AudioManagerImpl::DecodeSamplesConnector(const std::string& samples) {
|
| }
|
| }
|
|
|
| +void AudioManagerImpl::ValidateTokenDir() {
|
| + if (dump_tokens_dir_.empty())
|
| + return;
|
| +
|
| + if (!base::DirectoryExists(dump_tokens_dir_) ||
|
| + !base::PathIsWritable(dump_tokens_dir_)) {
|
| + LOG(ERROR) << "Invalid token dump directory \""
|
| + << dump_tokens_dir_.value() << "\"";
|
| + dump_tokens_dir_.clear();
|
| + }
|
| +}
|
| +
|
| +void AudioManagerImpl::DumpToken(AudioType audio_type,
|
| + const std::string& token,
|
| + const media::AudioBus* samples) {
|
| + if (dump_tokens_dir_.empty())
|
| + return;
|
| +
|
| + // Convert the samples to 16-bit integers.
|
| + std::vector<int16_t> int_samples;
|
| + int_samples.reserve(samples->frames());
|
| + for (int i = 0; i < samples->frames(); i++) {
|
| + int_samples.push_back(round(
|
| + samples->channel(0)[i] * std::numeric_limits<int16_t>::max()));
|
| + }
|
| + DCHECK_EQ(static_cast<int>(int_samples.size()), samples->frames());
|
| +
|
| + const std::string filename = AudioTypeToString(audio_type) +
|
| + " " + ToUrlSafe(token) + ".wav";
|
| + DVLOG(3) << "Dumping token " << filename;
|
| + DCHECK_EQ(kMonoChannelCount, samples->channels());
|
| + webrtc::WavWriter writer(dump_tokens_dir_.Append(filename).value(),
|
| + kDefaultSampleRate,
|
| + kMonoChannelCount);
|
| + writer.WriteSamples(int_samples.data(), int_samples.size());
|
| +}
|
| +
|
| } // namespace copresence
|
|
|