Commit ff0ed12e authored by rkc@chromium.org's avatar rkc@chromium.org

This is the preliminary introduction for the code for the chrome.copresence

API which enables the exchange short messages with nearby devices.

This is the audio handling code of the copresence core component. We'll be
open sourcing this code in phases, this is the first part. This CL includes
the code for the directive handler for copresence, and all related classes.
Including blundell@ for the OWNERs review to add this component.

R=dalecurtis@chromium.org, derat@chromium.org, jochen@chromium.org, xiyuan@chromium.org, blundell@chromium.org
BUG=365493

Review URL: https://codereview.chromium.org/419073002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287900 0039d316-1c4b-4281-b951-d872f2087c98
parent 83b651b3
...@@ -583,6 +583,20 @@ ...@@ -583,6 +583,20 @@
'invalidation/unacked_invalidation_set_unittest.cc', 'invalidation/unacked_invalidation_set_unittest.cc',
], ],
}], }],
['OS != "ios" and OS != "android"', {
'sources': [
'copresence/handlers/audio/audio_directive_handler_unittest.cc',
'copresence/handlers/audio/audio_directive_list_unittest.cc',
'copresence/mediums/audio/audio_player_unittest.cc',
'copresence/mediums/audio/audio_recorder_unittest.cc',
'copresence/timed_map_unittest.cc',
],
'dependencies': [
# Dependencies for copresence.
'components.gyp:copresence',
'components.gyp:copresence_test_support',
],
}],
['chromeos==1', { ['chromeos==1', {
'sources!': [ 'sources!': [
'storage_monitor/storage_monitor_linux_unittest.cc', 'storage_monitor/storage_monitor_linux_unittest.cc',
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
'type': 'static_library', 'type': 'static_library',
'dependencies': [ 'dependencies': [
'../base/base.gyp:base', '../base/base.gyp:base',
'../content/content.gyp:content_common',
'../media/media.gyp:media',
'../media/media.gyp:shared_memory_support',
'../net/net.gyp:net', '../net/net.gyp:net',
'copresence_proto', 'copresence_proto',
], ],
...@@ -17,16 +20,40 @@ ...@@ -17,16 +20,40 @@
], ],
'sources': [ 'sources': [
'copresence/copresence_client.cc', 'copresence/copresence_client.cc',
'copresence/copresence_constants.cc',
'copresence/handlers/audio/audio_directive_handler.cc',
'copresence/handlers/audio/audio_directive_handler.h',
'copresence/handlers/audio/audio_directive_list.cc',
'copresence/handlers/audio/audio_directive_list.h',
'copresence/handlers/directive_handler.cc',
'copresence/handlers/directive_handler.h',
'copresence/mediums/audio/audio_player.cc',
'copresence/mediums/audio/audio_player.h',
'copresence/mediums/audio/audio_recorder.cc',
'copresence/mediums/audio/audio_recorder.h',
'copresence/public/copresence_client_delegate.h', 'copresence/public/copresence_client_delegate.h',
'copresence/public/copresence_client.h', 'copresence/public/copresence_client.h',
'copresence/public/copresence_constants.h',
'copresence/public/whispernet_client.h', 'copresence/public/whispernet_client.h',
'copresence/rpc/rpc_handler.cc', 'copresence/rpc/rpc_handler.cc'
'copresence/rpc/rpc_handler.h', 'copresence/rpc/rpc_handler.h'
'copresence/timed_map.h',
], ],
'export_dependent_settings': [ 'export_dependent_settings': [
'copresence_proto', 'copresence_proto',
], ],
}, },
{
'target_name': 'copresence_test_support',
'type': 'static_library',
'include_dirs': [
'..',
],
'sources': [
'copresence/test/audio_test_support.cc',
'copresence/test/audio_test_support.h',
],
},
{ {
# Protobuf compiler / generate rule for copresence. # Protobuf compiler / generate rule for copresence.
# GN version: //components/copresence/proto # GN version: //components/copresence/proto
......
...@@ -5,16 +5,31 @@ ...@@ -5,16 +5,31 @@
static_library("copresence") { static_library("copresence") {
sources = [ sources = [
"copresence_client.cc", "copresence_client.cc",
"copresence_constants.cc",
"handlers/audio/audio_directive_handler.cc",
"handlers/audio/audio_directive_handler.h",
"handlers/audio/audio_directive_list.cc",
"handlers/audio/audio_directive_list.h",
"handlers/directive_handler.cc",
"handlers/directive_handler.h",
"mediums/audio/audio_player.cc",
"mediums/audio/audio_player.h",
"mediums/audio/audio_recorder.cc",
"mediums/audio/audio_recorder.h",
"public/copresence_client_delegate.h", "public/copresence_client_delegate.h",
"public/copresence_client.h", "public/copresence_client.h",
"public/copresence_constants.h",
"public/whispernet_client.h", "public/whispernet_client.h",
"rpc/rpc_handler.cc", "rpc/rpc_handler.cc",
"rpc/rpc_handler.h", "rpc/rpc_handler.h",
"timed_map.h",
] ]
deps = [ deps = [
"//base", "//base",
"//components/copresence/proto", "//components/copresence/proto",
"//content",
"//media",
"//net", "//net",
] ]
} }
include_rules = [ include_rules = [
"+base", "+base",
"+content/public/browser",
"+content/public/test",
"+media",
] ]
// 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/public/copresence_constants.h"
namespace copresence {
const int kDefaultRepetitions = 5;
const float kDefaultSampleRate = 48000.0f;
const int kDefaultBitsPerSample = 16;
const float kDefaultCarrierFrequency = 18500.0f;
const int kDefaultChannels = 2;
const media::ChannelLayout kDefaultChannelLayout = media::CHANNEL_LAYOUT_STEREO;
} // namespace copresence
// 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/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 copresence {
// Public methods.
AudioDirectiveHandler::AudioDirectiveHandler(
const AudioRecorder::DecodeSamplesCallback& decode_cb,
const AudioDirectiveList::EncodeTokenCallback& encode_cb)
: directive_list_(encode_cb,
base::Bind(&AudioDirectiveHandler::ExecuteNextTransmit,
base::Unretained(this))),
player_(NULL),
recorder_(NULL),
decode_cb_(decode_cb) {
}
AudioDirectiveHandler::~AudioDirectiveHandler() {
if (player_)
player_->Finalize();
if (recorder_)
recorder_->Finalize();
}
void AudioDirectiveHandler::Initialize() {
player_ = new AudioPlayer();
player_->Initialize();
recorder_ = new AudioRecorder(decode_cb_);
recorder_->Initialize();
}
void AudioDirectiveHandler::AddInstruction(const TokenInstruction& instruction,
base::TimeDelta ttl) {
switch (instruction.token_instruction_type()) {
case TRANSMIT:
DVLOG(2) << "Audio Transmit Directive received. Token: "
<< instruction.token_id()
<< " with TTL=" << ttl.InMilliseconds();
// TODO(rkc): Fill in the op_id once we get it from the directive.
directive_list_.AddTransmitDirective(
instruction.token_id(), std::string(), ttl);
break;
case RECEIVE:
DVLOG(2) << "Audio Receive Directive received. TTL="
<< ttl.InMilliseconds();
// TODO(rkc): Fill in the op_id once we get it from the directive.
directive_list_.AddReceiveDirective(std::string(), 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.
ExecuteNextReceive();
}
// Protected methods.
void AudioDirectiveHandler::PlayAudio(
const scoped_refptr<media::AudioBusRefCounted>& samples,
base::TimeDelta duration) {
player_->Play(samples);
stop_playback_timer_.Start(
FROM_HERE, duration, this, &AudioDirectiveHandler::StopPlayback);
}
void AudioDirectiveHandler::RecordAudio(base::TimeDelta duration) {
recorder_->Record();
stop_recording_timer_.Start(
FROM_HERE, duration, this, &AudioDirectiveHandler::StopRecording);
}
// Private methods.
void AudioDirectiveHandler::StopPlayback() {
player_->Stop();
DVLOG(2) << "Done playing audio.";
ExecuteNextTransmit();
}
void AudioDirectiveHandler::StopRecording() {
recorder_->Stop();
DVLOG(2) << "Done recording audio.";
ExecuteNextReceive();
}
void AudioDirectiveHandler::ExecuteNextTransmit() {
scoped_ptr<AudioDirective> transmit(directive_list_.GetNextTransmit());
if (transmit)
PlayAudio(transmit->samples, transmit->end_time - base::Time::Now());
}
void AudioDirectiveHandler::ExecuteNextReceive() {
scoped_ptr<AudioDirective> receive(directive_list_.GetNextReceive());
if (receive)
RecordAudio(receive->end_time - base::Time::Now());
}
} // namespace copresence
// 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.
#ifndef COMPONENTS_COPRESENCE_HANDLERS_AUDIO_AUDIO_DIRECTIVE_HANDLER_
#define COMPONENTS_COPRESENCE_HANDLERS_AUDIO_AUDIO_DIRECTIVE_HANDLER_
#include <vector>
#include "base/basictypes.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/copresence/handlers/audio/audio_directive_list.h"
#include "components/copresence/mediums/audio/audio_recorder.h"
#include "components/copresence/proto/data.pb.h"
namespace media {
class AudioBusRefCounted;
}
namespace copresence {
class AudioPlayer;
// The AudioDirectiveHandler handles audio transmit and receive instructions.
class AudioDirectiveHandler {
public:
AudioDirectiveHandler(
const AudioRecorder::DecodeSamplesCallback& decode_cb,
const AudioDirectiveList::EncodeTokenCallback& encode_cb);
virtual ~AudioDirectiveHandler();
// Do not use this class before calling this.
void Initialize();
// Adds an instruction to our handler. The instruction will execute and be
// removed after the ttl expires.
void AddInstruction(const copresence::TokenInstruction& instruction,
base::TimeDelta ttl_ms);
protected:
// Protected and virtual since we want to be able to mock these out.
virtual void PlayAudio(
const scoped_refptr<media::AudioBusRefCounted>& samples,
base::TimeDelta duration);
virtual void RecordAudio(base::TimeDelta duration);
private:
void StopPlayback();
void StopRecording();
// Execute the next active transmit instruction.
void ExecuteNextTransmit();
// Execute the next active receive instruction.
void ExecuteNextReceive();
AudioDirectiveList directive_list_;
// The next two pointers are self-deleting. When we call Finalize on them,
// they clean themselves up on the Audio thread.
AudioPlayer* player_;
AudioRecorder* recorder_;
AudioRecorder::DecodeSamplesCallback decode_cb_;
base::OneShotTimer<AudioDirectiveHandler> stop_playback_timer_;
base::OneShotTimer<AudioDirectiveHandler> stop_recording_timer_;
DISALLOW_COPY_AND_ASSIGN(AudioDirectiveHandler);
};
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_HANDLERS_AUDIO_AUDIO_DIRECTIVE_HANDLER_
// 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/message_loop/message_loop.h"
#include "components/copresence/test/audio_test_support.h"
#include "media/base/audio_bus.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Le;
namespace copresence {
class MockAudioDirectiveHandler : public AudioDirectiveHandler {
public:
MockAudioDirectiveHandler(
const AudioDirectiveList::EncodeTokenCallback& encode_cb)
: AudioDirectiveHandler(AudioRecorder::DecodeSamplesCallback(),
encode_cb) {}
virtual ~MockAudioDirectiveHandler() {}
// Mock out the play/record methods.
MOCK_METHOD2(PlayAudio,
void(const scoped_refptr<media::AudioBusRefCounted>&,
base::TimeDelta));
MOCK_METHOD1(RecordAudio, void(base::TimeDelta));
private:
DISALLOW_COPY_AND_ASSIGN(MockAudioDirectiveHandler);
};
class AudioDirectiveHandlerTest : public testing::Test {
public:
AudioDirectiveHandlerTest()
: directive_handler_(new MockAudioDirectiveHandler(
base::Bind(&AudioDirectiveHandlerTest::EncodeToken,
base::Unretained(this)))) {}
virtual ~AudioDirectiveHandlerTest() {}
void DirectiveAdded() {}
protected:
void EncodeToken(const std::string& token,
const AudioDirectiveList::SamplesCallback& callback) {
callback.Run(token, CreateRandomAudioRefCounted(0x1337, 1, 0x7331));
}
copresence::TokenInstruction CreateTransmitInstruction(
const std::string& token) {
copresence::TokenInstruction instruction;
instruction.set_token_instruction_type(copresence::TRANSMIT);
instruction.set_token_id(token);
return instruction;
}
copresence::TokenInstruction CreateReceiveInstruction() {
copresence::TokenInstruction instruction;
instruction.set_token_instruction_type(copresence::RECEIVE);
return instruction;
}
// This order is important. We want the message loop to get created before
// our the audio directive handler since the directive list ctor (invoked
// from the directive handler ctor) will post tasks.
base::MessageLoop message_loop_;
scoped_ptr<MockAudioDirectiveHandler> directive_handler_;
private:
DISALLOW_COPY_AND_ASSIGN(AudioDirectiveHandlerTest);
};
TEST_F(AudioDirectiveHandlerTest, Basic) {
const base::TimeDelta kSmallTtl = base::TimeDelta::FromMilliseconds(0x1337);
const base::TimeDelta kLargeTtl = base::TimeDelta::FromSeconds(0x7331);
// Expect to play and record instructions for 'less' than the TTL specified,
// since by the time that the token would have gotten encoded, we would
// have (TTL - time_to_encode) left to play on that instruction.
EXPECT_CALL(*directive_handler_, PlayAudio(_, testing::Le(kLargeTtl)))
.Times(3);
directive_handler_->AddInstruction(CreateTransmitInstruction("token1"),
kLargeTtl);
directive_handler_->AddInstruction(CreateTransmitInstruction("token2"),
kLargeTtl);
directive_handler_->AddInstruction(CreateTransmitInstruction("token3"),
kSmallTtl);
EXPECT_CALL(*directive_handler_, RecordAudio(Le(kLargeTtl))).Times(3);
directive_handler_->AddInstruction(CreateReceiveInstruction(), kLargeTtl);
directive_handler_->AddInstruction(CreateReceiveInstruction(), kSmallTtl);
directive_handler_->AddInstruction(CreateReceiveInstruction(), kLargeTtl);
}
// TODO(rkc): When we are keeping track of which token we're currently playing,
// add tests to make sure we don't replay if we get a token with a lower ttl
// than the current active.
} // namespace copresence
// 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_list.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/string_util.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.
AudioDirective::AudioDirective() {
}
AudioDirective::AudioDirective(const std::string& token,
const std::string& op_id,
base::Time end_time)
: token(token), op_id(op_id), end_time(end_time) {
}
AudioDirective::AudioDirective(
const std::string& token,
const std::string& op_id,
base::Time end_time,
const scoped_refptr<media::AudioBusRefCounted>& samples)
: token(token), op_id(op_id), end_time(end_time), samples(samples) {
}
AudioDirective::~AudioDirective() {
}
AudioDirectiveList::AudioDirectiveList(
const EncodeTokenCallback& encode_token_callback,
const base::Closure& token_added_callback)
: encode_token_callback_(encode_token_callback),
token_added_callback_(token_added_callback),
samples_cache_(base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs),
kMaxSamples) {
}
AudioDirectiveList::~AudioDirectiveList() {
}
void AudioDirectiveList::AddTransmitDirective(const std::string& token,
const std::string& op_id,
base::TimeDelta ttl) {
std::string valid_token = FromUrlSafe(token);
base::Time end_time = base::Time::Now() + ttl;
if (samples_cache_.HasKey(valid_token)) {
active_transmit_tokens_.push(AudioDirective(
valid_token, op_id, end_time, samples_cache_.GetValue(valid_token)));
return;
}
// If an encode request for this token has been sent, don't send it again.
if (pending_transmit_tokens_.find(valid_token) !=
pending_transmit_tokens_.end()) {
return;
}
pending_transmit_tokens_[valid_token] =
AudioDirective(valid_token, op_id, end_time);
// All whispernet callbacks will be cleared before we are destructed, so
// unretained is safe to use here.
encode_token_callback_.Run(
valid_token,
base::Bind(&AudioDirectiveList::OnTokenEncoded, base::Unretained(this)));
}
void AudioDirectiveList::AddReceiveDirective(const std::string& op_id,
base::TimeDelta ttl) {
active_receive_tokens_.push(
AudioDirective(std::string(), op_id, base::Time::Now() + ttl));
}
scoped_ptr<AudioDirective> AudioDirectiveList::GetNextTransmit() {
return GetNextFromList(&active_transmit_tokens_);
}
scoped_ptr<AudioDirective> AudioDirectiveList::GetNextReceive() {
return GetNextFromList(&active_receive_tokens_);
}
scoped_ptr<AudioDirective> AudioDirectiveList::GetNextFromList(
AudioDirectiveQueue* list) {
CHECK(list);
// Checks if we have any valid tokens at all (since the top of the list is
// always pointing to the token with the latest expiry time). If we don't
// have any valid tokens left, clear the list.
if (!list->empty() && list->top().end_time < base::Time::Now()) {
while (!list->empty())
list->pop();
}
if (list->empty())
return make_scoped_ptr<AudioDirective>(NULL);
return make_scoped_ptr(new AudioDirective(list->top()));
}
void AudioDirectiveList::OnTokenEncoded(
const std::string& token,
const scoped_refptr<media::AudioBusRefCounted>& samples) {
// We shouldn't re-encode a token if it's already in the cache.
DCHECK(!samples_cache_.HasKey(token));
DVLOG(3) << "Token: " << token << " encoded.";
samples_cache_.Add(token, samples);
// Copy the samples into their corresponding directive object and move
// that object into the active queue.
std::map<std::string, AudioDirective>::iterator it =
pending_transmit_tokens_.find(token);
it->second.samples = samples;
active_transmit_tokens_.push(it->second);
pending_transmit_tokens_.erase(it);
if (!token_added_callback_.is_null())
token_added_callback_.Run();
}
} // namespace copresence
// 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.
#ifndef COMPONENTS_COPRESENCE_HANDLERS_AUDIO_AUDIO_DIRECTIVE_LIST_
#define COMPONENTS_COPRESENCE_HANDLERS_AUDIO_AUDIO_DIRECTIVE_LIST_
#include <map>
#include <queue>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
#include "components/copresence/timed_map.h"
namespace media {
class AudioBusRefCounted;
}
namespace copresence {
struct AudioDirective {
// Default ctor, required by the priority queue.
AudioDirective();
// ctor used to store transmit directives that are awaiting samples.
AudioDirective(const std::string& token,
const std::string& op_id,
base::Time end_time);
// ctor used to construct a complete transmit directive.
AudioDirective(const std::string& token,
const std::string& op_id,
base::Time end_time,
const scoped_refptr<media::AudioBusRefCounted>& samples);
~AudioDirective();
std::string token;
std::string op_id;
base::Time end_time;
scoped_refptr<media::AudioBusRefCounted> samples;
};
// This class maintains a list of active audio directives. It fetches the audio
// samples associated with a audio transmit directives and expires directives
// that have outlived their TTL.
// TODO(rkc): Once we implement more token technologies, move reusable code
// from here to a base class and inherit various XxxxDirectiveList
// classes from it.
class AudioDirectiveList {
public:
typedef base::Callback<
void(const std::string&, const scoped_refptr<media::AudioBusRefCounted>&)>
SamplesCallback;
typedef base::Callback<void(const std::string&, const SamplesCallback&)>
EncodeTokenCallback;
AudioDirectiveList(const EncodeTokenCallback& encode_token_callback,
const base::Closure& token_added_callback);
virtual ~AudioDirectiveList();
// Adds a token to the token queue, after getting its corresponding samples
// from whispernet.
void AddTransmitDirective(const std::string& token,
const std::string& op_id,
base::TimeDelta ttl);
void AddReceiveDirective(const std::string& op_id, base::TimeDelta ttl);
// Returns the next audio token to play. This also cleans up expired tokens.
scoped_ptr<AudioDirective> GetNextTransmit();
scoped_ptr<AudioDirective> GetNextReceive();
// This is the method that the whispernet client needs to call to return
// samples to us.
void OnTokenEncoded(const std::string& token,
const scoped_refptr<media::AudioBusRefCounted>& samples);
private:
// Comparator for comparing end_times on audio tokens.
class LatestFirstComparator {
public:
// This will sort our queue with the 'latest' time being the top.
bool operator()(const AudioDirective& left,
const AudioDirective& right) const {
return left.end_time < right.end_time;
}
};
typedef std::priority_queue<AudioDirective,
std::vector<AudioDirective>,
LatestFirstComparator> AudioDirectiveQueue;
typedef TimedMap<std::string, scoped_refptr<media::AudioBusRefCounted> >
SamplesMap;
scoped_ptr<AudioDirective> GetNextFromList(AudioDirectiveQueue* list);
// A map of tokens that are awaiting their samples before we can
// add them to the active transmit tokens list.
std::map<std::string, AudioDirective> pending_transmit_tokens_;
AudioDirectiveQueue active_transmit_tokens_;
AudioDirectiveQueue active_receive_tokens_;
EncodeTokenCallback encode_token_callback_;
base::Closure token_added_callback_;
// Cache that holds the encoded samples. After reaching its limit, the cache
// expires the oldest samples first.
SamplesMap samples_cache_;
DISALLOW_COPY_AND_ASSIGN(AudioDirectiveList);
};
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_HANDLERS_AUDIO_AUDIO_DIRECTIVE_LIST_
// 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_list.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/message_loop/message_loop.h"
#include "components/copresence/test/audio_test_support.h"
#include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace copresence {
class AudioDirectiveListTest : public testing::Test {
public:
AudioDirectiveListTest()
: directive_list_(new AudioDirectiveList(
base::Bind(&AudioDirectiveListTest::EncodeToken,
base::Unretained(this)),
base::Bind(&base::DoNothing))) {}
virtual ~AudioDirectiveListTest() {}
protected:
void EncodeToken(const std::string& token,
const AudioDirectiveList::SamplesCallback& callback) {
callback.Run(token, CreateRandomAudioRefCounted(0x1337, 1, 0x7331));
}
base::MessageLoop message_loop_;
scoped_ptr<AudioDirectiveList> directive_list_;
};
TEST_F(AudioDirectiveListTest, Basic) {
const base::TimeDelta kZeroTtl = base::TimeDelta::FromMilliseconds(0);
const base::TimeDelta kLargeTtl = base::TimeDelta::FromSeconds(0x7331);
directive_list_->AddTransmitDirective("token1", "op_id1", kZeroTtl);
directive_list_->AddTransmitDirective("token2", "op_id2", kLargeTtl);
directive_list_->AddTransmitDirective("token3", "op_id1", kZeroTtl);
EXPECT_EQ("token2", directive_list_->GetNextTransmit()->token);
directive_list_->AddReceiveDirective("op_id1", kZeroTtl);
directive_list_->AddReceiveDirective("op_id3", kZeroTtl);
directive_list_->AddReceiveDirective("op_id3", kLargeTtl);
directive_list_->AddReceiveDirective("op_id7", kZeroTtl);
EXPECT_EQ("op_id3", directive_list_->GetNextReceive()->op_id);
}
TEST_F(AudioDirectiveListTest, OutOfOrderAndMultiple) {
const base::TimeDelta kZeroTtl = base::TimeDelta::FromMilliseconds(0);
const base::TimeDelta kLargeTtl = base::TimeDelta::FromSeconds(0x7331);
EXPECT_EQ(NULL, directive_list_->GetNextTransmit().get());
EXPECT_EQ(NULL, directive_list_->GetNextReceive().get());
directive_list_->AddTransmitDirective("token1", "op_id1", kZeroTtl);
directive_list_->AddTransmitDirective("token2", "op_id2", kLargeTtl);
directive_list_->AddTransmitDirective("token3", "op_id1", kLargeTtl);
// Should keep getting the directive till it expires or we add a newer one.
EXPECT_EQ("token3", directive_list_->GetNextTransmit()->token);
EXPECT_EQ("token3", directive_list_->GetNextTransmit()->token);
EXPECT_EQ("token3", directive_list_->GetNextTransmit()->token);
EXPECT_EQ(NULL, directive_list_->GetNextReceive().get());
directive_list_->AddReceiveDirective("op_id1", kLargeTtl);
directive_list_->AddReceiveDirective("op_id3", kZeroTtl);
directive_list_->AddReceiveDirective("op_id3", kLargeTtl);
directive_list_->AddReceiveDirective("op_id7", kLargeTtl);
// Should keep getting the directive till it expires or we add a newer one.
EXPECT_EQ("op_id7", directive_list_->GetNextReceive()->op_id);
EXPECT_EQ("op_id7", directive_list_->GetNextReceive()->op_id);
EXPECT_EQ("op_id7", directive_list_->GetNextReceive()->op_id);
}
} // namespace copresence
// 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/directive_handler.h"
#include "base/time/time.h"
#include "components/copresence/handlers/audio/audio_directive_handler.h"
#include "components/copresence/proto/data.pb.h"
namespace copresence {
DirectiveHandler::DirectiveHandler(
const AudioRecorder::DecodeSamplesCallback& decode_cb,
const AudioDirectiveList::EncodeTokenCallback& encode_cb)
: audio_handler_(new AudioDirectiveHandler(decode_cb, encode_cb)) {
audio_handler_->Initialize();
}
DirectiveHandler::~DirectiveHandler() {
}
void DirectiveHandler::AddDirective(const Directive& directive) {
// We only handle Token directives; wifi/ble requests aren't implemented.
DCHECK_EQ(directive.instruction_type(), TOKEN);
const TokenInstruction& ti = directive.token_instruction();
// We currently only support audio.
DCHECK_EQ(ti.medium(), AUDIO_ULTRASOUND_PASSBAND);
audio_handler_->AddInstruction(
ti, base::TimeDelta::FromMilliseconds(directive.ttl_millis()));
}
void DirectiveHandler::RemoveDirectives(const std::string& /* op_id */) {
// TODO(rkc): Forward the remove directive call to all the directive handlers.
}
} // namespace copresence
// 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.
#ifndef COMPONENTS_COPRESENCE_HANDLERS_DIRECTIVE_HANDLER_
#define COMPONENTS_COPRESENCE_HANDLERS_DIRECTIVE_HANDLER_
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "components/copresence/handlers/audio/audio_directive_list.h"
#include "components/copresence/mediums/audio/audio_recorder.h"
namespace copresence {
class AudioDirectiveHandler;
class Directive;
// The directive handler manages transmit and receive directives given to it
// by the client.
class DirectiveHandler {
public:
DirectiveHandler(const AudioRecorder::DecodeSamplesCallback& decode_cb,
const AudioDirectiveList::EncodeTokenCallback& encode_cb);
~DirectiveHandler();
// Adds a directive to handle.
void AddDirective(const copresence::Directive& directive);
// Removes any directives associated with the given operation id.
void RemoveDirectives(const std::string& op_id);
private:
scoped_ptr<AudioDirectiveHandler> audio_handler_;
DISALLOW_COPY_AND_ASSIGN(DirectiveHandler);
};
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_HANDLERS_DIRECTIVE_HANDLER_
// 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/mediums/audio/audio_player.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "components/copresence/public/copresence_constants.h"
#include "content/public/browser/browser_thread.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_parameters.h"
#include "media/base/audio_bus.h"
namespace {
const int kDefaultFrameCount = 1024;
const double kOutputVolumePercent = 1.0f;
} // namespace
namespace copresence {
// Public methods.
AudioPlayer::AudioPlayer()
: stream_(NULL), is_playing_(false), frame_index_(0) {
}
AudioPlayer::~AudioPlayer() {
}
void AudioPlayer::Initialize() {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioPlayer::InitializeOnAudioThread,
base::Unretained(this)));
}
void AudioPlayer::Play(
const scoped_refptr<media::AudioBusRefCounted>& samples) {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(
&AudioPlayer::PlayOnAudioThread, base::Unretained(this), samples));
}
void AudioPlayer::Stop() {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioPlayer::StopOnAudioThread, base::Unretained(this)));
}
void AudioPlayer::Finalize() {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioPlayer::FinalizeOnAudioThread, base::Unretained(this)));
}
// Private methods.
void AudioPlayer::InitializeOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
stream_ = output_stream_for_testing_
? output_stream_for_testing_.get()
: media::AudioManager::Get()->MakeAudioOutputStreamProxy(
media::AudioParameters(
media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO,
kDefaultSampleRate,
kDefaultBitsPerSample,
kDefaultFrameCount),
std::string());
if (!stream_ || !stream_->Open()) {
LOG(ERROR) << "Failed to open an output stream.";
if (stream_) {
stream_->Close();
stream_ = NULL;
}
return;
}
stream_->SetVolume(kOutputVolumePercent);
}
void AudioPlayer::PlayOnAudioThread(
const scoped_refptr<media::AudioBusRefCounted>& samples) {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
if (!stream_)
return;
{
base::AutoLock al(state_lock_);
samples_ = samples;
frame_index_ = 0;
if (is_playing_)
return;
}
DVLOG(2) << "Playing Audio.";
is_playing_ = true;
stream_->Start(this);
}
void AudioPlayer::StopOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
if (!stream_)
return;
stream_->Stop();
is_playing_ = false;
}
void AudioPlayer::StopAndCloseOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
if (!stream_)
return;
if (is_playing_)
stream_->Stop();
stream_->Close();
stream_ = NULL;
is_playing_ = false;
}
void AudioPlayer::FinalizeOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
StopAndCloseOnAudioThread();
delete this;
}
int AudioPlayer::OnMoreData(media::AudioBus* dest,
media::AudioBuffersState /* state */) {
base::AutoLock al(state_lock_);
// Continuously play our samples till explicitly told to stop.
const int leftover_frames = samples_->frames() - frame_index_;
const int frames_to_copy = std::min(dest->frames(), leftover_frames);
samples_->CopyPartialFramesTo(frame_index_, frames_to_copy, 0, dest);
frame_index_ += frames_to_copy;
// If we didn't fill the destination audio bus, wrap around and fill the rest.
if (leftover_frames <= dest->frames()) {
samples_->CopyPartialFramesTo(
0, dest->frames() - frames_to_copy, frames_to_copy, dest);
frame_index_ = dest->frames() - frames_to_copy;
}
return dest->frames();
}
void AudioPlayer::OnError(media::AudioOutputStream* /* stream */) {
LOG(ERROR) << "Error during system sound reproduction.";
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioPlayer::StopAndCloseOnAudioThread,
base::Unretained(this)));
}
void AudioPlayer::FlushAudioLoopForTesting() {
if (media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread())
return;
// Queue task on the audio thread, when it is executed, that means we've
// successfully executed all the tasks before us.
base::RunLoop rl;
media::AudioManager::Get()->GetTaskRunner()->PostTaskAndReply(
FROM_HERE,
base::Bind(base::IgnoreResult(&AudioPlayer::FlushAudioLoopForTesting),
base::Unretained(this)),
rl.QuitClosure());
rl.Run();
}
} // namespace copresence
// 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.
#ifndef COMPONENTS_COPRESENCE_MEDIUMS_AUDIO_AUDIO_PLAYER_
#define COMPONENTS_COPRESENCE_MEDIUMS_AUDIO_AUDIO_PLAYER_
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "media/audio/audio_io.h"
namespace media {
class AudioBus;
class AudioBusRefCounted;
}
namespace copresence {
// The AudioPlayer class will play a set of samples till it is told to stop.
class AudioPlayer : public media::AudioOutputStream::AudioSourceCallback {
public:
AudioPlayer();
// Initializes the object. Do not use this object before calling this method.
void Initialize();
// Play the given samples. These samples will keep on being played in a loop
// till we explicitly tell the player to stop playing.
void Play(const scoped_refptr<media::AudioBusRefCounted>& samples);
// Stop playing.
void Stop();
// Cleans up and deletes this object. Do not use object after this call.
void Finalize();
// Takes ownership of the stream.
void set_output_stream_for_testing(
media::AudioOutputStream* output_stream_for_testing) {
output_stream_for_testing_.reset(output_stream_for_testing);
}
private:
friend class AudioPlayerTest;
FRIEND_TEST_ALL_PREFIXES(AudioPlayerTest, BasicPlayAndStop);
FRIEND_TEST_ALL_PREFIXES(AudioPlayerTest, OutOfOrderPlayAndStopMultiple);
virtual ~AudioPlayer();
// Methods to do our various operations; all of these need to be run on the
// audio thread.
void InitializeOnAudioThread();
void PlayOnAudioThread(
const scoped_refptr<media::AudioBusRefCounted>& samples);
void StopOnAudioThread();
void StopAndCloseOnAudioThread();
void FinalizeOnAudioThread();
// AudioOutputStream::AudioSourceCallback overrides:
// Following methods could be called from *ANY* thread.
virtual int OnMoreData(media::AudioBus* dest,
media::AudioBuffersState /* state */) OVERRIDE;
virtual void OnError(media::AudioOutputStream* /* stream */) OVERRIDE;
// Flushes the audio loop, making sure that any queued operations are
// performed.
void FlushAudioLoopForTesting();
// Self-deleting object.
media::AudioOutputStream* stream_;
scoped_ptr<media::AudioOutputStream> output_stream_for_testing_;
bool is_playing_;
// All fields below here are protected by this lock.
base::Lock state_lock_;
scoped_refptr<media::AudioBusRefCounted> samples_;
// Index to the frame in the samples that we need to play next.
int frame_index_;
DISALLOW_COPY_AND_ASSIGN(AudioPlayer);
};
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_MEDIUMS_AUDIO_AUDIO_PLAYER_
// 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/mediums/audio/audio_player.h"
#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "components/copresence/public/copresence_constants.h"
#include "components/copresence/test/audio_test_support.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_manager_base.h"
#include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class TestAudioOutputStream : public media::AudioOutputStream {
public:
typedef base::Callback<void(scoped_ptr<media::AudioBus>, int frames)>
GatherSamplesCallback;
TestAudioOutputStream(int default_frame_count,
int max_frame_count,
GatherSamplesCallback gather_callback)
: default_frame_count_(default_frame_count),
max_frame_count_(max_frame_count),
gather_callback_(gather_callback),
callback_(NULL) {
caller_loop_ = base::MessageLoop::current();
}
virtual ~TestAudioOutputStream() {}
virtual bool Open() OVERRIDE { return true; }
virtual void Start(AudioSourceCallback* callback) OVERRIDE {
callback_ = callback;
GatherPlayedSamples();
}
virtual void Stop() OVERRIDE {}
virtual void SetVolume(double volume) OVERRIDE {}
virtual void GetVolume(double* volume) OVERRIDE {}
virtual void Close() OVERRIDE {}
private:
void GatherPlayedSamples() {
int frames = 0, total_frames = 0;
do {
// Call back into the player to get samples that it wants us to play.
scoped_ptr<media::AudioBus> dest =
media::AudioBus::Create(1, default_frame_count_);
frames = callback_->OnMoreData(dest.get(), media::AudioBuffersState());
total_frames += frames;
// Send the samples given to us by the player to the gather callback.
caller_loop_->PostTask(
FROM_HERE, base::Bind(gather_callback_, base::Passed(&dest), frames));
} while (frames && total_frames < max_frame_count_);
}
int default_frame_count_;
int max_frame_count_;
GatherSamplesCallback gather_callback_;
AudioSourceCallback* callback_;
base::MessageLoop* caller_loop_;
DISALLOW_COPY_AND_ASSIGN(TestAudioOutputStream);
};
} // namespace
namespace copresence {
class AudioPlayerTest : public testing::Test,
public base::SupportsWeakPtr<AudioPlayerTest> {
public:
AudioPlayerTest() : buffer_index_(0), player_(NULL) {
if (!media::AudioManager::Get())
media::AudioManager::CreateForTesting();
}
virtual ~AudioPlayerTest() { DeletePlayer(); }
void CreatePlayer() {
DeletePlayer();
player_ = new AudioPlayer();
player_->set_output_stream_for_testing(new TestAudioOutputStream(
kDefaultFrameCount,
kMaxFrameCount,
base::Bind(&AudioPlayerTest::GatherSamples, AsWeakPtr())));
player_->Initialize();
}
void DeletePlayer() {
if (!player_)
return;
player_->Finalize();
player_ = NULL;
}
void PlayAndVerifySamples(
const scoped_refptr<media::AudioBusRefCounted>& samples) {
buffer_ = media::AudioBus::Create(1, kMaxFrameCount);
player_->Play(samples);
player_->FlushAudioLoopForTesting();
int differences = 0;
for (int i = 0; i < samples->frames(); ++i)
differences += (buffer_->channel(0)[i] != samples->channel(0)[i]);
ASSERT_EQ(0, differences);
buffer_.reset();
}
void GatherSamples(scoped_ptr<media::AudioBus> bus, int frames) {
if (!buffer_.get())
return;
bus->CopyPartialFramesTo(0, frames, buffer_index_, buffer_.get());
buffer_index_ += frames;
}
protected:
bool IsPlaying() {
player_->FlushAudioLoopForTesting();
return player_->is_playing_;
}
static const int kDefaultFrameCount = 1024;
static const int kMaxFrameCount = 1024 * 10;
scoped_ptr<media::AudioBus> buffer_;
int buffer_index_;
AudioPlayer* player_;
base::MessageLoop message_loop_;
};
TEST_F(AudioPlayerTest, BasicPlayAndStop) {
CreatePlayer();
scoped_refptr<media::AudioBusRefCounted> samples =
media::AudioBusRefCounted::Create(1, 7331);
player_->Play(samples);
EXPECT_TRUE(IsPlaying());
player_->Stop();
EXPECT_FALSE(IsPlaying());
player_->Play(samples);
EXPECT_TRUE(IsPlaying());
player_->Stop();
EXPECT_FALSE(IsPlaying());
player_->Play(samples);
EXPECT_TRUE(IsPlaying());
player_->Stop();
EXPECT_FALSE(IsPlaying());
DeletePlayer();
}
TEST_F(AudioPlayerTest, OutOfOrderPlayAndStopMultiple) {
CreatePlayer();
scoped_refptr<media::AudioBusRefCounted> samples =
media::AudioBusRefCounted::Create(1, 1337);
player_->Stop();
player_->Stop();
player_->Stop();
EXPECT_FALSE(IsPlaying());
player_->Play(samples);
player_->Play(samples);
EXPECT_TRUE(IsPlaying());
player_->Stop();
player_->Stop();
EXPECT_FALSE(IsPlaying());
DeletePlayer();
}
TEST_F(AudioPlayerTest, PlayingEndToEnd) {
const int kNumSamples = kDefaultFrameCount * 10;
CreatePlayer();
PlayAndVerifySamples(CreateRandomAudioRefCounted(0x1337, 1, kNumSamples));
DeletePlayer();
}
} // namespace copresence
// 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/mediums/audio/audio_recorder.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "components/copresence/public/copresence_constants.h"
#include "content/public/browser/browser_thread.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_manager_base.h"
#include "media/base/audio_bus.h"
namespace copresence {
namespace {
const float kProcessIntervalMs = 500.0f; // milliseconds.
void AudioBusToString(scoped_ptr<media::AudioBus> source, std::string* buffer) {
buffer->resize(source->frames() * source->channels() * sizeof(float));
float* buffer_view = reinterpret_cast<float*>(string_as_array(buffer));
const int channels = source->channels();
for (int ch = 0; ch < channels; ++ch) {
for (int si = 0, di = ch; si < source->frames(); ++si, di += channels)
buffer_view[di] = source->channel(ch)[si];
}
}
// Called every kProcessIntervalMs to process the recorded audio. This
// converts our samples to the required sample rate, interleaves the samples
// and sends them to the whispernet decoder to process.
void ProcessSamples(scoped_ptr<media::AudioBus> bus,
const AudioRecorder::DecodeSamplesCallback& callback) {
std::string samples;
AudioBusToString(bus.Pass(), &samples);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, base::Bind(callback, samples));
}
} // namespace
// Public methods.
AudioRecorder::AudioRecorder(const DecodeSamplesCallback& decode_callback)
: stream_(NULL),
is_recording_(false),
decode_callback_(decode_callback),
total_buffer_frames_(0),
buffer_frame_index_(0) {
}
void AudioRecorder::Initialize() {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioRecorder::InitializeOnAudioThread,
base::Unretained(this)));
}
AudioRecorder::~AudioRecorder() {
}
void AudioRecorder::Record() {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioRecorder::RecordOnAudioThread, base::Unretained(this)));
}
void AudioRecorder::Stop() {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioRecorder::StopOnAudioThread, base::Unretained(this)));
}
void AudioRecorder::Finalize() {
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioRecorder::FinalizeOnAudioThread,
base::Unretained(this)));
}
// Private methods.
void AudioRecorder::InitializeOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
media::AudioParameters params =
params_for_testing_
? *params_for_testing_
: media::AudioManager::Get()->GetInputStreamParameters(
media::AudioManagerBase::kDefaultDeviceId);
const media::AudioParameters dest_params(params.format(),
kDefaultChannelLayout,
kDefaultChannels,
params.input_channels(),
kDefaultSampleRate,
kDefaultBitsPerSample,
params.frames_per_buffer(),
media::AudioParameters::NO_EFFECTS);
converter_.reset(new media::AudioConverter(
params, dest_params, params.sample_rate() == dest_params.sample_rate()));
converter_->AddInput(this);
total_buffer_frames_ = kProcessIntervalMs * dest_params.sample_rate() / 1000;
buffer_ =
media::AudioBus::Create(dest_params.channels(), total_buffer_frames_);
buffer_frame_index_ = 0;
stream_ = input_stream_for_testing_
? input_stream_for_testing_.get()
: media::AudioManager::Get()->MakeAudioInputStream(
params, media::AudioManagerBase::kDefaultDeviceId);
if (!stream_ || !stream_->Open()) {
LOG(ERROR) << "Failed to open an input stream.";
if (stream_) {
stream_->Close();
stream_ = NULL;
}
return;
}
stream_->SetVolume(stream_->GetMaxVolume());
}
void AudioRecorder::RecordOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
if (!stream_ || is_recording_)
return;
DVLOG(2) << "Recording Audio.";
converter_->Reset();
stream_->Start(this);
is_recording_ = true;
}
void AudioRecorder::StopOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
if (!stream_ || !is_recording_)
return;
stream_->Stop();
is_recording_ = false;
}
void AudioRecorder::StopAndCloseOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
if (!stream_)
return;
StopOnAudioThread();
stream_->Close();
stream_ = NULL;
}
void AudioRecorder::FinalizeOnAudioThread() {
DCHECK(media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
StopAndCloseOnAudioThread();
delete this;
}
void AudioRecorder::OnData(media::AudioInputStream* stream,
const media::AudioBus* source,
uint32 /* hardware_delay_bytes */,
double /* volume */) {
temp_conversion_buffer_ = source;
while (temp_conversion_buffer_) {
// source->frames() == source_params.frames_per_buffer(), so we only have
// one chunk of data in the source; correspondingly set the destination
// size to one chunk.
// TODO(rkc): Optimize this to directly write into buffer_ so we can avoid
// the copy into this buffer and then the copy back into buffer_.
scoped_ptr<media::AudioBus> converted_source =
media::AudioBus::Create(kDefaultChannels, converter_->ChunkSize());
// Convert accumulated samples into converted_source. Note: One call may not
// be enough to consume the samples from |source|. The converter may have
// accumulated samples over time due to a fractional input:output sample
// rate ratio. Since |source| is ephemeral, Convert() must be called until
// |source| is at least buffered into the converter. Once |source| is
// consumed during ProvideInput(), |temp_conversion_buffer_| will be set to
// NULL, which will break the conversion loop.
converter_->Convert(converted_source.get());
int remaining_buffer_frames = buffer_->frames() - buffer_frame_index_;
int frames_to_copy =
std::min(remaining_buffer_frames, converted_source->frames());
converted_source->CopyPartialFramesTo(
0, frames_to_copy, buffer_frame_index_, buffer_.get());
buffer_frame_index_ += frames_to_copy;
// Buffer full, send it for processing.
if (buffer_->frames() == buffer_frame_index_) {
ProcessSamples(buffer_.Pass(), decode_callback_);
buffer_ = media::AudioBus::Create(kDefaultChannels, total_buffer_frames_);
buffer_frame_index_ = 0;
// Copy any remaining frames in the source to our buffer.
int remaining_source_frames = converted_source->frames() - frames_to_copy;
converted_source->CopyPartialFramesTo(frames_to_copy,
remaining_source_frames,
buffer_frame_index_,
buffer_.get());
buffer_frame_index_ += remaining_source_frames;
}
}
}
void AudioRecorder::OnError(media::AudioInputStream* /* stream */) {
LOG(ERROR) << "Error during sound recording.";
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&AudioRecorder::StopAndCloseOnAudioThread,
base::Unretained(this)));
}
double AudioRecorder::ProvideInput(media::AudioBus* dest,
base::TimeDelta /* buffer_delay */) {
DCHECK(temp_conversion_buffer_);
DCHECK_LE(temp_conversion_buffer_->frames(), dest->frames());
temp_conversion_buffer_->CopyTo(dest);
temp_conversion_buffer_ = NULL;
return 1.0;
}
void AudioRecorder::FlushAudioLoopForTesting() {
if (media::AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread())
return;
// Queue task on the audio thread, when it is executed, that means we've
// successfully executed all the tasks before us.
base::RunLoop rl;
media::AudioManager::Get()->GetTaskRunner()->PostTaskAndReply(
FROM_HERE,
base::Bind(base::IgnoreResult(&AudioRecorder::FlushAudioLoopForTesting),
base::Unretained(this)),
rl.QuitClosure());
rl.Run();
}
} // namespace copresence
// 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.
#ifndef COMPONENTS_COPRESENCE_MEDIUMS_AUDIO_AUDIO_RECORDER_
#define COMPONENTS_COPRESENCE_MEDIUMS_AUDIO_AUDIO_RECORDER_
#include <string>
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_parameters.h"
#include "media/base/audio_converter.h"
namespace base {
class MessageLoop;
}
namespace media {
class AudioBus;
}
namespace copresence {
// The AudioRecorder class will record audio until told to stop.
class AudioRecorder : public media::AudioInputStream::AudioInputCallback,
public media::AudioConverter::InputCallback {
public:
typedef base::Callback<void(const std::string&)> DecodeSamplesCallback;
explicit AudioRecorder(const DecodeSamplesCallback& decode_callback);
// Initializes the object. Do not use this object before calling this method.
void Initialize();
void Record();
void Stop();
// Cleans up and deletes this object. Do not use object after this call.
void Finalize();
// Takes ownership of the stream.
void set_input_stream_for_testing(
media::AudioInputStream* input_stream_for_testing) {
input_stream_for_testing_.reset(input_stream_for_testing);
}
// Takes ownership of the params.
void set_params_for_testing(media::AudioParameters* params_for_testing) {
params_for_testing_.reset(params_for_testing);
}
private:
friend class AudioRecorderTest;
FRIEND_TEST_ALL_PREFIXES(AudioRecorderTest, BasicRecordAndStop);
FRIEND_TEST_ALL_PREFIXES(AudioRecorderTest, OutOfOrderRecordAndStopMultiple);
virtual ~AudioRecorder();
// Methods to do our various operations; all of these need to be run on the
// audio thread.
void InitializeOnAudioThread();
void RecordOnAudioThread();
void StopOnAudioThread();
void StopAndCloseOnAudioThread();
void FinalizeOnAudioThread();
// AudioInputStream::AudioInputCallback overrides:
// Called by the audio recorder when a full packet of audio data is
// available. This is called from a special audio thread and the
// implementation should return as soon as possible.
virtual void OnData(media::AudioInputStream* stream,
const media::AudioBus* source,
uint32 hardware_delay_bytes,
double volume) OVERRIDE;
virtual void OnError(media::AudioInputStream* stream) OVERRIDE;
// AudioConverter::InputCallback overrides:
virtual double ProvideInput(media::AudioBus* dest,
base::TimeDelta buffer_delay) OVERRIDE;
// Flushes the audio loop, making sure that any queued operations are
// performed.
void FlushAudioLoopForTesting();
media::AudioInputStream* stream_;
bool is_recording_;
DecodeSamplesCallback decode_callback_;
// ProvideInput will use this buffer as its source.
const media::AudioBus* temp_conversion_buffer_;
// Outside of the ctor/Initialize method, only access the next variables on
// the recording thread.
scoped_ptr<media::AudioBus> buffer_;
int total_buffer_frames_;
int buffer_frame_index_;
scoped_ptr<media::AudioConverter> converter_;
scoped_ptr<media::AudioInputStream> input_stream_for_testing_;
scoped_ptr<media::AudioParameters> params_for_testing_;
DISALLOW_COPY_AND_ASSIGN(AudioRecorder);
};
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_MEDIUMS_AUDIO_AUDIO_RECORDER_
// 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/mediums/audio/audio_recorder.h"
#include "base/bind.h"
#include "base/memory/aligned_memory.h"
#include "base/run_loop.h"
#include "components/copresence/public/copresence_constants.h"
#include "components/copresence/test/audio_test_support.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_manager_base.h"
#include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class TestAudioInputStream : public media::AudioInputStream {
public:
TestAudioInputStream(const media::AudioParameters& params,
const std::vector<float*> channel_data,
size_t samples)
: callback_(NULL), params_(params) {
buffer_ = media::AudioBus::CreateWrapper(2);
for (size_t i = 0; i < channel_data.size(); ++i)
buffer_->SetChannelData(i, channel_data[i]);
buffer_->set_frames(samples);
}
virtual ~TestAudioInputStream() {}
virtual bool Open() OVERRIDE { return true; }
virtual void Start(AudioInputCallback* callback) OVERRIDE {
DCHECK(callback);
callback_ = callback;
media::AudioManager::Get()->GetTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&TestAudioInputStream::SimulateRecording,
base::Unretained(this)));
}
virtual void Stop() OVERRIDE {}
virtual void Close() OVERRIDE {}
virtual double GetMaxVolume() OVERRIDE { return 1.0; }
virtual void SetVolume(double volume) OVERRIDE {}
virtual double GetVolume() OVERRIDE { return 1.0; }
virtual void SetAutomaticGainControl(bool enabled) OVERRIDE {}
virtual bool GetAutomaticGainControl() OVERRIDE { return true; }
private:
void SimulateRecording() {
const int fpb = params_.frames_per_buffer();
for (int i = 0; i < buffer_->frames() / fpb; ++i) {
scoped_ptr<media::AudioBus> source = media::AudioBus::Create(2, fpb);
buffer_->CopyPartialFramesTo(i * fpb, fpb, 0, source.get());
callback_->OnData(this, source.get(), fpb, 1.0);
}
}
AudioInputCallback* callback_;
media::AudioParameters params_;
scoped_ptr<media::AudioBus> buffer_;
DISALLOW_COPY_AND_ASSIGN(TestAudioInputStream);
};
} // namespace
namespace copresence {
class AudioRecorderTest : public testing::Test {
public:
AudioRecorderTest() : total_samples_(0), recorder_(NULL) {
if (!media::AudioManager::Get())
media::AudioManager::CreateForTesting();
}
virtual ~AudioRecorderTest() {
DeleteRecorder();
for (size_t i = 0; i < channel_data_.size(); ++i)
base::AlignedFree(channel_data_[i]);
}
void CreateSimpleRecorder() {
DeleteRecorder();
recorder_ = new AudioRecorder(
base::Bind(&AudioRecorderTest::DecodeSamples, base::Unretained(this)));
recorder_->Initialize();
}
void CreateRecorder(size_t channels,
size_t sample_rate,
size_t bits_per_sample,
size_t samples) {
DeleteRecorder();
params_.Reset(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
kDefaultChannelLayout,
channels,
2,
sample_rate,
bits_per_sample,
4096);
channel_data_.clear();
channel_data_.push_back(GenerateSamples(0x1337, samples));
channel_data_.push_back(GenerateSamples(0x7331, samples));
total_samples_ = samples;
recorder_ = new AudioRecorder(
base::Bind(&AudioRecorderTest::DecodeSamples, base::Unretained(this)));
recorder_->set_input_stream_for_testing(
new TestAudioInputStream(params_, channel_data_, samples));
recorder_->set_params_for_testing(new media::AudioParameters(params_));
recorder_->Initialize();
}
void DeleteRecorder() {
if (!recorder_)
return;
recorder_->Finalize();
recorder_ = NULL;
}
void RecordAndVerifySamples() {
received_samples_.clear();
run_loop_.reset(new base::RunLoop());
recorder_->Record();
run_loop_->Run();
}
void DecodeSamples(const std::string& samples) {
received_samples_ += samples;
// We expect one less decode than our total samples would ideally have
// triggered since we process data in 4k chunks. So our sample processing
// will never rarely be perfectly aligned with 0.5s worth of samples, hence
// we will almost always run with a buffer of leftover samples that will
// not get sent to this callback since the recorder will be waiting for
// more data.
const size_t decode_buffer = params_.sample_rate() / 2; // 0.5s
const size_t expected_samples =
(total_samples_ / decode_buffer - 1) * decode_buffer;
const size_t expected_samples_size =
expected_samples * sizeof(float) * params_.channels();
if (received_samples_.size() == expected_samples_size) {
VerifySamples();
run_loop_->Quit();
}
}
void VerifySamples() {
int differences = 0;
float* buffer_view =
reinterpret_cast<float*>(string_as_array(&received_samples_));
const int channels = params_.channels();
const int frames =
received_samples_.size() / sizeof(float) / params_.channels();
for (int ch = 0; ch < channels; ++ch) {
for (int si = 0, di = ch; si < frames; ++si, di += channels)
differences += (buffer_view[di] != channel_data_[ch][si]);
}
ASSERT_EQ(0, differences);
}
protected:
float* GenerateSamples(int random_seed, size_t size) {
float* samples = static_cast<float*>(base::AlignedAlloc(
size * sizeof(float), media::AudioBus::kChannelAlignment));
PopulateSamples(0x1337, size, samples);
return samples;
}
bool IsRecording() {
recorder_->FlushAudioLoopForTesting();
return recorder_->is_recording_;
}
std::vector<float*> channel_data_;
media::AudioParameters params_;
size_t total_samples_;
AudioRecorder* recorder_;
std::string received_samples_;
scoped_ptr<base::RunLoop> run_loop_;
content::TestBrowserThreadBundle thread_bundle_;
};
#if defined(OS_WIN) || defined(OS_MACOSX)
// Windows does not let us use non-OS params. The tests need to be rewritten to
// use the params provided to us by the audio manager rather than setting our
// own params.
#define MAYBE_BasicRecordAndStop DISABLED_BasicRecordAndStop
#define MAYBE_OutOfOrderRecordAndStopMultiple DISABLED_OutOfOrderRecordAndStopMultiple
#define MAYBE_RecordingEndToEnd DISABLED_RecordingEndToEnd
#else
#define MAYBE_BasicRecordAndStop BasicRecordAndStop
#define MAYBE_OutOfOrderRecordAndStopMultiple OutOfOrderRecordAndStopMultiple
#define MAYBE_RecordingEndToEnd RecordingEndToEnd
#endif
TEST_F(AudioRecorderTest, MAYBE_BasicRecordAndStop) {
CreateSimpleRecorder();
recorder_->Record();
EXPECT_TRUE(IsRecording());
recorder_->Stop();
EXPECT_FALSE(IsRecording());
recorder_->Record();
EXPECT_TRUE(IsRecording());
recorder_->Stop();
EXPECT_FALSE(IsRecording());
recorder_->Record();
EXPECT_TRUE(IsRecording());
recorder_->Stop();
EXPECT_FALSE(IsRecording());
DeleteRecorder();
}
TEST_F(AudioRecorderTest, MAYBE_OutOfOrderRecordAndStopMultiple) {
CreateSimpleRecorder();
recorder_->Stop();
recorder_->Stop();
recorder_->Stop();
EXPECT_FALSE(IsRecording());
recorder_->Record();
recorder_->Record();
EXPECT_TRUE(IsRecording());
recorder_->Stop();
recorder_->Stop();
EXPECT_FALSE(IsRecording());
DeleteRecorder();
}
TEST_F(AudioRecorderTest, MAYBE_RecordingEndToEnd) {
const int kNumSamples = 48000 * 3;
CreateRecorder(
kDefaultChannels, kDefaultSampleRate, kDefaultBitsPerSample, kNumSamples);
RecordAndVerifySamples();
DeleteRecorder();
}
// TODO(rkc): Add tests with recording different sample rates.
} // namespace copresence
// 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.
#ifndef COMPONENTS_COPRESENCE_PUBLIC_COPRESENCE_CONSTANTS_
#define COMPONENTS_COPRESENCE_PUBLIC_COPRESENCE_CONSTANTS_
#include "media/base/channel_layout.h"
namespace copresence {
// Audio constants. Currently used from the AudioPlayer/AudioRecorder.
// TODO(rkc): Make these values configurable then remove them from here.
// Number of repetitions of the audio token in one sequence of samples.
extern const int kDefaultRepetitions;
// The default sample rate. We need to ensure that both the recorder and the
// player on _all platforms use the same rate.
extern const float kDefaultSampleRate;
extern const int kDefaultBitsPerSample;
// 18500 for ultrasound, needs to be consistent between platforms.
extern const float kDefaultCarrierFrequency;
// The next two really need to be configurable since they don't need to be
// consistent across platforms, or even playing/recording.
extern const int kDefaultChannels;
extern const media::ChannelLayout kDefaultChannelLayout;
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_PUBLIC_COPRESENCE_CONSTANTS_
// 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/test/audio_test_support.h"
#include <cstdlib>
#include "media/base/audio_bus.h"
namespace copresence {
void PopulateSamples(int random_seed, size_t size, float* samples) {
srand(random_seed);
for (size_t i = 0; i < size; ++i)
samples[i] = (2.0 * rand() / RAND_MAX) - 1;
}
scoped_ptr<media::AudioBus> CreateRandomAudio(int random_seed,
int channels,
int samples) {
scoped_ptr<media::AudioBus> bus = media::AudioBus::Create(channels, samples);
for (int ch = 0; ch < channels; ++ch)
PopulateSamples(random_seed, samples, bus->channel(ch));
return bus.Pass();
}
scoped_refptr<media::AudioBusRefCounted>
CreateRandomAudioRefCounted(int random_seed, int channels, int samples) {
scoped_refptr<media::AudioBusRefCounted> bus =
media::AudioBusRefCounted::Create(channels, samples);
for (int ch = 0; ch < channels; ++ch)
PopulateSamples(random_seed, samples, bus->channel(ch));
return bus;
}
} // namespace copresence
// 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.
#ifndef COMPONENTS_COPRESENCE_COMMON_AUDIO_TEST_SUPPORT_
#define COMPONENTS_COPRESENCE_COMMON_AUDIO_TEST_SUPPORT_
#include <cstddef>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
namespace media {
class AudioBus;
class AudioBusRefCounted;
}
namespace copresence {
// Populate random samples given a random seed into the samples array.
void PopulateSamples(int random_seed, size_t size, float* samples);
// Create an audio bus populated with random samples.
scoped_ptr<media::AudioBus> CreateRandomAudio(int random_seed,
int channels,
int samples);
// Create an ref counted audio bus populated with random samples.
scoped_refptr<media::AudioBusRefCounted>
CreateRandomAudioRefCounted(int random_seed, int channels, int samples);
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_COMMON_AUDIO_TEST_SUPPORT_
// 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.
#ifndef COMPONENTS_COPRESENCE_TIMED_MAP_
#define COMPONENTS_COPRESENCE_TIMED_MAP_
#include <map>
#include <queue>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/time/default_tick_clock.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
namespace copresence {
// TimedMap is a map with the added functionality of clearing any
// key/value pair after its specified lifetime is over.
template <typename KeyType, typename ValueType>
class TimedMap {
public:
TimedMap(const base::TimeDelta& lifetime, size_t max_elements)
: kEmptyValue(ValueType()),
clock_(new base::DefaultTickClock()),
lifetime_(lifetime),
max_elements_(max_elements) {
timer_.Start(FROM_HERE, lifetime_, this, &TimedMap::ClearExpiredTokens);
}
~TimedMap() {}
void Add(const KeyType& key, const ValueType& value) {
map_[key] = value;
expiry_queue_.push(KeyTimeTuple(key, clock_->NowTicks() + lifetime_));
while (map_.size() > max_elements_)
ClearOldestToken();
}
bool HasKey(const KeyType& key) {
ClearExpiredTokens();
return map_.find(key) != map_.end();
}
const ValueType& GetValue(const KeyType& key) {
ClearExpiredTokens();
typename std::map<KeyType, ValueType>::const_iterator elt = map_.find(key);
return elt == map_.end() ? kEmptyValue : elt->second;
}
void set_clock_for_testing(base::TickClock* clock) { clock_ = clock; }
private:
void ClearExpiredTokens() {
while (!expiry_queue_.empty() &&
expiry_queue_.top().second <= clock_->NowTicks())
ClearOldestToken();
}
void ClearOldestToken() {
map_.erase(expiry_queue_.top().first);
expiry_queue_.pop();
}
typedef std::pair<KeyType, base::TimeTicks> KeyTimeTuple;
class EarliestFirstComparator {
public:
// This will sort our queue with the 'earliest' time being the top.
bool operator()(const KeyTimeTuple& left, const KeyTimeTuple& right) const {
return left.second > right.second;
}
};
typedef std::priority_queue<KeyTimeTuple, std::vector<KeyTimeTuple>,
EarliestFirstComparator> ExpiryQueue;
const ValueType kEmptyValue;
base::TickClock* clock_;
base::RepeatingTimer<TimedMap> timer_;
const base::TimeDelta lifetime_;
const size_t max_elements_;
std::map<KeyType, ValueType> map_;
// Priority queue with our element keys ordered by the earliest expiring keys
// first.
ExpiryQueue expiry_queue_;
DISALLOW_COPY_AND_ASSIGN(TimedMap);
};
} // namespace copresence
#endif // COMPONENTS_COPRESENCE_TIMED_MAP_
// Copyright (c) 2011 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 "base/basictypes.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "components/copresence/timed_map.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
struct Value {
Value() : value(0) {}
explicit Value(int new_value) : value(new_value) {}
int value;
};
} // namespace
class TimedMapTest : public testing::Test {
public:
TimedMapTest() {}
private:
// Exists since the timer needs a message loop.
base::MessageLoop message_loop_;
DISALLOW_COPY_AND_ASSIGN(TimedMapTest);
};
TEST_F(TimedMapTest, Basic) {
typedef copresence::TimedMap<int, Value> Map;
Map map(base::TimeDelta::FromSeconds(9999), 3);
EXPECT_FALSE(map.HasKey(0));
EXPECT_EQ(0, map.GetValue(0).value);
map.Add(0x1337, Value(0x7331));
EXPECT_TRUE(map.HasKey(0x1337));
EXPECT_EQ(0x7331, map.GetValue(0x1337).value);
map.Add(0xbaad, Value(0xf00d));
EXPECT_TRUE(map.HasKey(0xbaad));
EXPECT_EQ(0xf00d, map.GetValue(0xbaad).value);
EXPECT_EQ(0x7331, map.GetValue(0x1337).value);
map.Add(0x1234, Value(0x5678));
EXPECT_TRUE(map.HasKey(0x1234));
EXPECT_TRUE(map.HasKey(0xbaad));
EXPECT_TRUE(map.HasKey(0x1337));
EXPECT_EQ(0x5678, map.GetValue(0x1234).value);
EXPECT_EQ(0xf00d, map.GetValue(0xbaad).value);
EXPECT_EQ(0x7331, map.GetValue(0x1337).value);
}
TEST_F(TimedMapTest, ValueReplacement) {
typedef copresence::TimedMap<int, Value> Map;
Map map(base::TimeDelta::FromSeconds(9999), 10);
map.Add(0x1337, Value(0x7331));
EXPECT_TRUE(map.HasKey(0x1337));
EXPECT_EQ(0x7331, map.GetValue(0x1337).value);
map.Add(0xbaad, Value(0xf00d));
EXPECT_TRUE(map.HasKey(0xbaad));
EXPECT_EQ(0xf00d, map.GetValue(0xbaad).value);
map.Add(0x1337, Value(0xd00d));
EXPECT_TRUE(map.HasKey(0x1337));
EXPECT_EQ(0xd00d, map.GetValue(0x1337).value);
}
TEST_F(TimedMapTest, SizeEvict) {
typedef copresence::TimedMap<int, Value> Map;
Map two_element_map(base::TimeDelta::FromSeconds(9999), 2);
two_element_map.Add(0x1337, Value(0x7331));
EXPECT_TRUE(two_element_map.HasKey(0x1337));
EXPECT_EQ(0x7331, two_element_map.GetValue(0x1337).value);
two_element_map.Add(0xbaad, Value(0xf00d));
EXPECT_TRUE(two_element_map.HasKey(0xbaad));
EXPECT_EQ(0xf00d, two_element_map.GetValue(0xbaad).value);
two_element_map.Add(0x1234, Value(0x5678));
EXPECT_TRUE(two_element_map.HasKey(0x1234));
EXPECT_EQ(0xf00d, two_element_map.GetValue(0xbaad).value);
EXPECT_FALSE(two_element_map.HasKey(0x1337));
EXPECT_EQ(0, two_element_map.GetValue(0x1337).value);
}
TEST_F(TimedMapTest, TimedEvict) {
const int kLargeTimeValueSeconds = 9999;
base::SimpleTestTickClock clock;
typedef copresence::TimedMap<int, Value> Map;
Map map(base::TimeDelta::FromSeconds(kLargeTimeValueSeconds), 2);
map.set_clock_for_testing(&clock);
// Add value at T=0.
map.Add(0x1337, Value(0x7331));
EXPECT_TRUE(map.HasKey(0x1337));
EXPECT_EQ(0x7331, map.GetValue(0x1337).value);
// Add value at T=kLargeTimeValueSeconds-1.
clock.Advance(base::TimeDelta::FromSeconds(kLargeTimeValueSeconds - 1));
map.Add(0xbaad, Value(0xf00d));
// Check values at T=kLargeTimeValueSeconds-1.
EXPECT_TRUE(map.HasKey(0xbaad));
EXPECT_EQ(0xf00d, map.GetValue(0xbaad).value);
EXPECT_TRUE(map.HasKey(0x1337));
EXPECT_EQ(0x7331, map.GetValue(0x1337).value);
// Check values at T=kLargeTimeValueSeconds.
clock.Advance(base::TimeDelta::FromSeconds(1));
EXPECT_FALSE(map.HasKey(0x1337));
EXPECT_EQ(0, map.GetValue(0x1337).value);
EXPECT_TRUE(map.HasKey(0xbaad));
EXPECT_EQ(0xf00d, map.GetValue(0xbaad).value);
// Check values at T=2*kLargeTimeValueSeconds
clock.Advance(base::TimeDelta::FromSeconds(kLargeTimeValueSeconds));
EXPECT_FALSE(map.HasKey(0xbaad));
EXPECT_EQ(0, map.GetValue(0xbaad).value);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment