Use multiline attribute to check for IA2_STATE_MULTILINE.
[chromium-blink-merge.git] / components / audio_modem / modem_impl.cc
blob23d75d0ac8f6a3221fb3d419af95c691e53b8513
1 // Copyright 2015 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/audio_modem/modem_impl.h"
7 #include <algorithm>
8 #include <limits>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/command_line.h"
14 #include "base/logging.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/run_loop.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "base/time/time.h"
21 #include "components/audio_modem/audio_modem_switches.h"
22 #include "components/audio_modem/audio_player_impl.h"
23 #include "components/audio_modem/audio_recorder_impl.h"
24 #include "components/audio_modem/public/whispernet_client.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "media/audio/audio_manager.h"
27 #include "media/audio/audio_manager_base.h"
28 #include "media/base/audio_bus.h"
29 #include "third_party/webrtc/common_audio/wav_file.h"
31 namespace audio_modem {
33 namespace {
35 const int kMaxSamples = 10000;
36 const int kTokenTimeoutMs = 2000;
37 const int kMonoChannelCount = 1;
39 // UrlSafe is defined as:
40 // '/' represented by a '_' and '+' represented by a '-'
41 // TODO(ckehoe): Move this to a central place.
42 std::string FromUrlSafe(std::string token) {
43 base::ReplaceChars(token, "-", "+", &token);
44 base::ReplaceChars(token, "_", "/", &token);
45 return token;
47 std::string ToUrlSafe(std::string token) {
48 base::ReplaceChars(token, "+", "-", &token);
49 base::ReplaceChars(token, "/", "_", &token);
50 return token;
53 // TODO(ckehoe): Move this to a central place.
54 std::string AudioTypeToString(AudioType audio_type) {
55 if (audio_type == AUDIBLE)
56 return "audible";
57 if (audio_type == INAUDIBLE)
58 return "inaudible";
60 NOTREACHED() << "Got unexpected token type " << audio_type;
61 return std::string();
64 bool ReadBooleanFlag(const std::string& flag, bool default_value) {
65 const std::string flag_value = base::StringToLowerASCII(
66 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(flag));
67 if (flag_value == "true" || flag_value == "1")
68 return true;
69 if (flag_value == "false" || flag_value == "0")
70 return false;
71 LOG_IF(ERROR, !flag_value.empty())
72 << "Unrecognized value \"" << flag_value << " for flag "
73 << flag << ". Defaulting to " << default_value;
74 return default_value;
77 } // namespace
80 // Public functions.
82 ModemImpl::ModemImpl() : client_(nullptr), recorder_(nullptr) {
83 // TODO(rkc): Move all of these into initializer lists once it is allowed.
84 should_be_playing_[AUDIBLE] = false;
85 should_be_playing_[INAUDIBLE] = false;
86 should_be_recording_[AUDIBLE] = false;
87 should_be_recording_[INAUDIBLE] = false;
89 player_enabled_[AUDIBLE] = ReadBooleanFlag(
90 switches::kAudioModemEnableAudibleBroadcast, true);
91 player_enabled_[INAUDIBLE] = ReadBooleanFlag(
92 switches::kAudioModemEnableInaudibleBroadcast, true);
93 player_[AUDIBLE] = nullptr;
94 player_[INAUDIBLE] = nullptr;
96 samples_caches_.resize(2);
97 samples_caches_[AUDIBLE] = new SamplesMap(kMaxSamples);
98 samples_caches_[INAUDIBLE] = new SamplesMap(kMaxSamples);
101 void ModemImpl::Initialize(WhispernetClient* client,
102 const TokensCallback& tokens_cb) {
103 DCHECK(client);
104 client_ = client;
105 tokens_cb_ = tokens_cb;
107 // These will be unregistered on destruction, so unretained is safe to use.
108 client_->RegisterTokensCallback(
109 base::Bind(&ModemImpl::OnTokensFound, base::Unretained(this)));
110 client_->RegisterSamplesCallback(
111 base::Bind(&ModemImpl::OnTokenEncoded, base::Unretained(this)));
113 if (!player_[AUDIBLE])
114 player_[AUDIBLE] = new AudioPlayerImpl();
115 player_[AUDIBLE]->Initialize();
117 if (!player_[INAUDIBLE])
118 player_[INAUDIBLE] = new AudioPlayerImpl();
119 player_[INAUDIBLE]->Initialize();
121 decode_cancelable_cb_.Reset(base::Bind(
122 &ModemImpl::DecodeSamplesConnector, base::Unretained(this)));
123 if (!recorder_)
124 recorder_ = new AudioRecorderImpl();
125 recorder_->Initialize(decode_cancelable_cb_.callback());
127 dump_tokens_dir_ = base::FilePath(base::CommandLine::ForCurrentProcess()
128 ->GetSwitchValueNative(switches::kAudioModemDumpTokensToDir));
131 ModemImpl::~ModemImpl() {
132 if (player_[AUDIBLE])
133 player_[AUDIBLE]->Finalize();
134 if (player_[INAUDIBLE])
135 player_[INAUDIBLE]->Finalize();
136 if (recorder_)
137 recorder_->Finalize();
139 // Whispernet initialization may never have completed.
140 if (client_) {
141 client_->RegisterTokensCallback(TokensCallback());
142 client_->RegisterSamplesCallback(SamplesCallback());
146 void ModemImpl::StartPlaying(AudioType type) {
147 DCHECK(type == AUDIBLE || type == INAUDIBLE);
148 should_be_playing_[type] = true;
149 // If we don't have our token encoded yet, this check will be false, for now.
150 // Once our token is encoded, OnTokenEncoded will call UpdateToken, which
151 // will call this code again (if we're still supposed to be playing).
152 SamplesMap::iterator samples =
153 samples_caches_[type]->Get(playing_token_[type]);
154 if (samples != samples_caches_[type]->end()) {
155 DCHECK(!playing_token_[type].empty());
156 if (player_enabled_[type]) {
157 started_playing_[type] = base::Time::Now();
158 player_[type]->Play(samples->second);
160 // If we're playing, we always record to hear what we are playing.
161 recorder_->Record();
162 } else {
163 DVLOG(3) << "Skipping playback for disabled " << AudioTypeToString(type)
164 << " player.";
169 void ModemImpl::StopPlaying(AudioType type) {
170 DCHECK(type == AUDIBLE || type == INAUDIBLE);
171 should_be_playing_[type] = false;
172 player_[type]->Stop();
173 // If we were only recording to hear our own played tokens, stop.
174 if (!should_be_recording_[AUDIBLE] && !should_be_recording_[INAUDIBLE])
175 recorder_->Stop();
176 playing_token_[type] = std::string();
179 void ModemImpl::StartRecording(AudioType type) {
180 DCHECK(type == AUDIBLE || type == INAUDIBLE);
181 should_be_recording_[type] = true;
182 recorder_->Record();
185 void ModemImpl::StopRecording(AudioType type) {
186 DCHECK(type == AUDIBLE || type == INAUDIBLE);
187 should_be_recording_[type] = false;
188 recorder_->Stop();
191 void ModemImpl::SetToken(AudioType type,
192 const std::string& url_safe_token) {
193 DCHECK(type == AUDIBLE || type == INAUDIBLE);
194 std::string token = FromUrlSafe(url_safe_token);
195 if (samples_caches_[type]->Get(token) == samples_caches_[type]->end()) {
196 client_->EncodeToken(token, type, token_params_);
197 } else {
198 UpdateToken(type, token);
202 const std::string ModemImpl::GetToken(AudioType type) const {
203 return playing_token_[type];
206 bool ModemImpl::IsPlayingTokenHeard(AudioType type) const {
207 base::TimeDelta tokenTimeout =
208 base::TimeDelta::FromMilliseconds(kTokenTimeoutMs);
210 // This is a bit of a hack. If we haven't been playing long enough,
211 // return true to avoid tripping an audio fail alarm.
212 if (base::Time::Now() - started_playing_[type] < tokenTimeout)
213 return true;
215 return base::Time::Now() - heard_own_token_[type] < tokenTimeout;
218 void ModemImpl::SetTokenParams(AudioType type, const TokenParameters& params) {
219 DCHECK_GT(params.length, 0u);
220 token_params_[type] = params;
222 // TODO(ckehoe): Make whispernet handle different token lengths
223 // simultaneously without reinitializing the decoder over and over.
226 // static
227 scoped_ptr<Modem> Modem::Create() {
228 return make_scoped_ptr<Modem>(new ModemImpl);
231 // Private functions.
233 void ModemImpl::OnTokenEncoded(
234 AudioType type,
235 const std::string& token,
236 const scoped_refptr<media::AudioBusRefCounted>& samples) {
237 samples_caches_[type]->Put(token, samples);
238 DumpToken(type, token, samples.get());
239 UpdateToken(type, token);
242 void ModemImpl::OnTokensFound(const std::vector<AudioToken>& tokens) {
243 std::vector<AudioToken> tokens_to_report;
244 for (const auto& token : tokens) {
245 AudioType type = token.audible ? AUDIBLE : INAUDIBLE;
246 if (playing_token_[type] == token.token)
247 heard_own_token_[type] = base::Time::Now();
249 if (should_be_recording_[AUDIBLE] && token.audible) {
250 tokens_to_report.push_back(token);
251 } else if (should_be_recording_[INAUDIBLE] && !token.audible) {
252 tokens_to_report.push_back(token);
256 if (!tokens_to_report.empty())
257 tokens_cb_.Run(tokens_to_report);
260 void ModemImpl::UpdateToken(AudioType type, const std::string& token) {
261 DCHECK(type == AUDIBLE || type == INAUDIBLE);
262 if (playing_token_[type] == token)
263 return;
265 // Update token.
266 playing_token_[type] = token;
268 // If we are supposed to be playing this token type at this moment, switch
269 // out playback with the new samples.
270 if (should_be_playing_[type])
271 RestartPlaying(type);
274 void ModemImpl::RestartPlaying(AudioType type) {
275 DCHECK(type == AUDIBLE || type == INAUDIBLE);
276 // We should already have this token in the cache. This function is not
277 // called from anywhere except update token and only once we have our samples
278 // in the cache.
279 DCHECK(samples_caches_[type]->Get(playing_token_[type]) !=
280 samples_caches_[type]->end());
282 player_[type]->Stop();
283 StartPlaying(type);
286 void ModemImpl::DecodeSamplesConnector(const std::string& samples) {
287 // If we are either supposed to be recording *or* playing, audible or
288 // inaudible, we should be decoding that type. This is so that if we are
289 // just playing, we will still decode our recorded token so we can check
290 // if we heard our own token. Whether or not we report the token to the
291 // server is checked for and handled in OnTokensFound.
293 bool decode_audible =
294 should_be_recording_[AUDIBLE] || should_be_playing_[AUDIBLE];
295 bool decode_inaudible =
296 should_be_recording_[INAUDIBLE] || should_be_playing_[INAUDIBLE];
298 if (decode_audible && decode_inaudible) {
299 client_->DecodeSamples(BOTH, samples, token_params_);
300 } else if (decode_audible) {
301 client_->DecodeSamples(AUDIBLE, samples, token_params_);
302 } else if (decode_inaudible) {
303 client_->DecodeSamples(INAUDIBLE, samples, token_params_);
307 void ModemImpl::DumpToken(AudioType audio_type,
308 const std::string& token,
309 const media::AudioBus* samples) {
310 if (dump_tokens_dir_.empty())
311 return;
313 // Convert the samples to 16-bit integers.
314 std::vector<int16_t> int_samples;
315 int_samples.reserve(samples->frames());
316 for (int i = 0; i < samples->frames(); i++) {
317 int_samples.push_back(round(
318 samples->channel(0)[i] * std::numeric_limits<int16_t>::max()));
320 DCHECK_EQ(static_cast<int>(int_samples.size()), samples->frames());
321 DCHECK_EQ(kMonoChannelCount, samples->channels());
323 const std::string filename = base::StringPrintf("%s %s.wav",
324 AudioTypeToString(audio_type).c_str(), ToUrlSafe(token).c_str());
325 DVLOG(3) << "Dumping token " << filename;
327 std::string file_str;
328 #if defined(OS_WIN)
329 base::FilePath file_path = dump_tokens_dir_.Append(
330 base::SysNativeMBToWide(filename));
331 file_str = base::SysWideToNativeMB(file_path.value());
332 #else
333 file_str = dump_tokens_dir_.Append(filename).value();
334 #endif
336 webrtc::WavWriter writer(file_str, kDefaultSampleRate, kMonoChannelCount);
337 writer.WriteSamples(int_samples.data(), int_samples.size());
340 } // namespace audio_modem