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.
5 #include "components/copresence/mediums/audio/audio_manager_impl.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/logging.h"
13 #include "base/run_loop.h"
14 #include "base/strings/string_util.h"
15 #include "base/time/time.h"
16 #include "components/copresence/mediums/audio/audio_player_impl.h"
17 #include "components/copresence/mediums/audio/audio_recorder_impl.h"
18 #include "components/copresence/public/copresence_constants.h"
19 #include "components/copresence/public/whispernet_client.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "media/audio/audio_manager.h"
22 #include "media/audio/audio_manager_base.h"
23 #include "media/base/audio_bus.h"
25 namespace copresence
{
29 // UrlSafe is defined as:
30 // '/' represented by a '_' and '+' represented by a '-'
31 // TODO(rkc): Move this processing to the whispernet wrapper.
32 std::string
FromUrlSafe(std::string token
) {
33 base::ReplaceChars(token
, "-", "+", &token
);
34 base::ReplaceChars(token
, "_", "/", &token
);
38 const int kSampleExpiryTimeMs
= 60 * 60 * 1000; // 60 minutes.
39 const int kMaxSamples
= 10000;
40 const int kTokenTimeoutMs
= 2000;
46 AudioManagerImpl::AudioManagerImpl()
47 : whispernet_client_(nullptr), recorder_(nullptr) {
48 // TODO(rkc): Move all of these into initializer lists once it is allowed.
49 should_be_playing_
[AUDIBLE
] = false;
50 should_be_playing_
[INAUDIBLE
] = false;
51 should_be_recording_
[AUDIBLE
] = false;
52 should_be_recording_
[INAUDIBLE
] = false;
54 player_
[AUDIBLE
] = nullptr;
55 player_
[INAUDIBLE
] = nullptr;
60 void AudioManagerImpl::Initialize(WhispernetClient
* whispernet_client
,
61 const TokensCallback
& tokens_cb
) {
62 samples_cache_
.resize(2);
63 samples_cache_
[AUDIBLE
] = new SamplesMap(
64 base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs
), kMaxSamples
);
65 samples_cache_
[INAUDIBLE
] = new SamplesMap(
66 base::TimeDelta::FromMilliseconds(kSampleExpiryTimeMs
), kMaxSamples
);
68 DCHECK(whispernet_client
);
69 whispernet_client_
= whispernet_client
;
70 tokens_cb_
= tokens_cb
;
72 // These will be unregistered on destruction, so unretained is safe to use.
73 whispernet_client_
->RegisterTokensCallback(
74 base::Bind(&AudioManagerImpl::OnTokensFound
, base::Unretained(this)));
75 whispernet_client_
->RegisterSamplesCallback(
76 base::Bind(&AudioManagerImpl::OnTokenEncoded
, base::Unretained(this)));
78 if (!player_
[AUDIBLE
])
79 player_
[AUDIBLE
] = new AudioPlayerImpl();
80 player_
[AUDIBLE
]->Initialize();
82 if (!player_
[INAUDIBLE
])
83 player_
[INAUDIBLE
] = new AudioPlayerImpl();
84 player_
[INAUDIBLE
]->Initialize();
86 decode_cancelable_cb_
.Reset(base::Bind(
87 &AudioManagerImpl::DecodeSamplesConnector
, base::Unretained(this)));
89 recorder_
= new AudioRecorderImpl();
90 recorder_
->Initialize(decode_cancelable_cb_
.callback());
93 AudioManagerImpl::~AudioManagerImpl() {
95 player_
[AUDIBLE
]->Finalize();
96 if (player_
[INAUDIBLE
])
97 player_
[INAUDIBLE
]->Finalize();
99 recorder_
->Finalize();
101 // Whispernet initialization may never have completed.
102 if (whispernet_client_
) {
103 whispernet_client_
->RegisterTokensCallback(TokensCallback());
104 whispernet_client_
->RegisterSamplesCallback(SamplesCallback());
108 void AudioManagerImpl::StartPlaying(AudioType type
) {
109 DCHECK(type
== AUDIBLE
|| type
== INAUDIBLE
);
110 should_be_playing_
[type
] = true;
111 // If we don't have our token encoded yet, this check will be false, for now.
112 // Once our token is encoded, OnTokenEncoded will call UpdateToken, which
113 // will call this code again (if we're still supposed to be playing).
114 if (samples_cache_
[type
]->HasKey(playing_token_
[type
])) {
115 DCHECK(!playing_token_
[type
].empty());
116 started_playing_
[type
] = base::Time::Now();
117 player_
[type
]->Play(samples_cache_
[type
]->GetValue(playing_token_
[type
]));
118 // If we're playing, we always record to hear what we are playing.
123 void AudioManagerImpl::StopPlaying(AudioType type
) {
124 DCHECK(type
== AUDIBLE
|| type
== INAUDIBLE
);
125 should_be_playing_
[type
] = false;
126 player_
[type
]->Stop();
127 // If we were only recording to hear our own played tokens, stop.
128 if (!should_be_recording_
[AUDIBLE
] && !should_be_recording_
[INAUDIBLE
])
130 playing_token_
[type
] = std::string();
133 void AudioManagerImpl::StartRecording(AudioType type
) {
134 DCHECK(type
== AUDIBLE
|| type
== INAUDIBLE
);
135 should_be_recording_
[type
] = true;
139 void AudioManagerImpl::StopRecording(AudioType type
) {
140 DCHECK(type
== AUDIBLE
|| type
== INAUDIBLE
);
141 should_be_recording_
[type
] = false;
145 void AudioManagerImpl::SetToken(AudioType type
,
146 const std::string
& url_safe_token
) {
147 DCHECK(type
== AUDIBLE
|| type
== INAUDIBLE
);
148 std::string token
= FromUrlSafe(url_safe_token
);
149 if (!samples_cache_
[type
]->HasKey(token
)) {
150 whispernet_client_
->EncodeToken(token
, type
);
152 UpdateToken(type
, token
);
156 const std::string
AudioManagerImpl::GetToken(AudioType type
) {
157 return playing_token_
[type
];
160 bool AudioManagerImpl::IsPlayingTokenHeard(AudioType type
) {
161 base::TimeDelta tokenTimeout
=
162 base::TimeDelta::FromMilliseconds(kTokenTimeoutMs
);
164 // This is a bit of a hack. If we haven't been playing long enough,
165 // return true to avoid tripping an audio fail alarm.
166 if (base::Time::Now() - started_playing_
[type
] < tokenTimeout
)
169 return base::Time::Now() - heard_own_token_
[type
] < tokenTimeout
;
172 void AudioManagerImpl::SetTokenLength(AudioType type
, size_t token_length
) {
173 token_length_
[type
] = token_length
;
178 void AudioManagerImpl::OnTokenEncoded(
180 const std::string
& token
,
181 const scoped_refptr
<media::AudioBusRefCounted
>& samples
) {
182 samples_cache_
[type
]->Add(token
, samples
);
183 UpdateToken(type
, token
);
186 void AudioManagerImpl::OnTokensFound(const std::vector
<AudioToken
>& tokens
) {
187 std::vector
<AudioToken
> tokens_to_report
;
188 for (const auto& token
: tokens
) {
189 AudioType type
= token
.audible
? AUDIBLE
: INAUDIBLE
;
190 if (playing_token_
[type
] == token
.token
)
191 heard_own_token_
[type
] = base::Time::Now();
193 if (should_be_recording_
[AUDIBLE
] && token
.audible
) {
194 tokens_to_report
.push_back(token
);
195 } else if (should_be_recording_
[INAUDIBLE
] && !token
.audible
) {
196 tokens_to_report
.push_back(token
);
200 if (!tokens_to_report
.empty())
201 tokens_cb_
.Run(tokens_to_report
);
204 void AudioManagerImpl::UpdateToken(AudioType type
, const std::string
& token
) {
205 DCHECK(type
== AUDIBLE
|| type
== INAUDIBLE
);
206 if (playing_token_
[type
] == token
)
210 playing_token_
[type
] = token
;
212 // If we are supposed to be playing this token type at this moment, switch
213 // out playback with the new samples.
214 if (should_be_playing_
[type
])
215 RestartPlaying(type
);
218 void AudioManagerImpl::RestartPlaying(AudioType type
) {
219 DCHECK(type
== AUDIBLE
|| type
== INAUDIBLE
);
220 // We should already have this token in the cache. This function is not
221 // called from anywhere except update token and only once we have our samples
223 DCHECK(samples_cache_
[type
]->HasKey(playing_token_
[type
]));
225 started_playing_
[type
] = base::Time::Now();
226 player_
[type
]->Stop();
227 player_
[type
]->Play(samples_cache_
[type
]->GetValue(playing_token_
[type
]));
228 // If we're playing, we always record to hear what we are playing.
232 void AudioManagerImpl::DecodeSamplesConnector(const std::string
& samples
) {
233 // If we are either supposed to be recording *or* playing, audible or
234 // inaudible, we should be decoding that type. This is so that if we are
235 // just playing, we will still decode our recorded token so we can check
236 // if we heard our own token. Whether or not we report the token to the
237 // server is checked for and handled in OnTokensFound.
239 bool decode_audible
=
240 should_be_recording_
[AUDIBLE
] || should_be_playing_
[AUDIBLE
];
241 bool decode_inaudible
=
242 should_be_recording_
[INAUDIBLE
] || should_be_playing_
[INAUDIBLE
];
244 if (decode_audible
&& decode_inaudible
) {
245 whispernet_client_
->DecodeSamples(BOTH
, samples
, token_length_
);
246 } else if (decode_audible
) {
247 whispernet_client_
->DecodeSamples(AUDIBLE
, samples
, token_length_
);
248 } else if (decode_inaudible
) {
249 whispernet_client_
->DecodeSamples(INAUDIBLE
, samples
, token_length_
);
253 } // namespace copresence