blob: bec48b8fc64ab4fa18d5cc06f377daf993d81d24 [file] [log] [blame]
[email protected]11158e2d2013-02-01 02:31:561// Copyright (c) 2012 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.
4
5#include "content/browser/media/media_internals.h"
6
avi7f277562015-12-25 02:41:267#include <stddef.h>
dcheng98e96a72016-06-11 03:41:488
9#include <memory>
xhwang29c5ad202017-04-14 07:02:1910#include <tuple>
dcheng36b6aec92015-12-26 06:16:3611#include <utility>
avi7f277562015-12-25 02:41:2612
13#include "base/macros.h"
Dale Curtis4a6f54c02017-06-06 23:32:2014#include "base/metrics/histogram_functions.h"
asvitkine8d51e9d2016-09-02 23:55:4315#include "base/metrics/histogram_macros.h"
[email protected]348fbaac2013-06-11 06:31:5116#include "base/strings/string16.h"
[email protected]9367e032014-03-07 19:42:3717#include "base/strings/string_number_conversions.h"
[email protected]348fbaac2013-06-11 06:31:5118#include "base/strings/stringprintf.h"
avi7f277562015-12-25 02:41:2619#include "build/build_config.h"
guidou50db1a62016-06-01 17:12:1720#include "content/browser/renderer_host/media/media_stream_manager.h"
xhwang29c5ad202017-04-14 07:02:1921#include "content/public/browser/browser_context.h"
[email protected]11158e2d2013-02-01 02:31:5622#include "content/public/browser/browser_thread.h"
prabhur53bb9182014-11-13 03:25:1723#include "content/public/browser/notification_service.h"
24#include "content/public/browser/notification_types.h"
dalecurtise6aa75f2015-03-31 02:39:3825#include "content/public/browser/render_frame_host.h"
prabhur53bb9182014-11-13 03:25:1726#include "content/public/browser/render_process_host.h"
dalecurtise6aa75f2015-03-31 02:39:3827#include "content/public/browser/web_contents.h"
[email protected]11158e2d2013-02-01 02:31:5628#include "content/public/browser/web_ui.h"
jrummell37a54c02016-04-22 19:54:2729#include "media/base/audio_parameters.h"
[email protected]11158e2d2013-02-01 02:31:5630#include "media/base/media_log_event.h"
prabhur957d46c72014-11-19 03:01:1631#include "media/filters/gpu_video_decoder.h"
[email protected]11158e2d2013-02-01 02:31:5632
kraush42521052017-05-04 19:14:3433#if !defined(OS_ANDROID)
xhwangf2189392015-10-19 22:21:5134#include "media/filters/decrypting_video_decoder.h"
35#endif
36
[email protected]69946cf2013-11-27 00:11:4237namespace {
38
[email protected]fcf75d42013-12-03 20:11:2639base::string16 SerializeUpdate(const std::string& function,
40 const base::Value* value) {
[email protected]69946cf2013-11-27 00:11:4241 return content::WebUI::GetJavascriptCall(
42 function, std::vector<const base::Value*>(1, value));
43}
44
[email protected]9367e032014-03-07 19:42:3745std::string EffectsToString(int effects) {
46 if (effects == media::AudioParameters::NO_EFFECTS)
47 return "NO_EFFECTS";
48
49 struct {
50 int flag;
51 const char* name;
52 } flags[] = {
53 { media::AudioParameters::ECHO_CANCELLER, "ECHO_CANCELLER" },
54 { media::AudioParameters::DUCKING, "DUCKING" },
[email protected]81495eb2014-03-19 06:08:1855 { media::AudioParameters::KEYBOARD_MIC, "KEYBOARD_MIC" },
dalecurtisb9a6b7872015-02-06 22:41:5556 { media::AudioParameters::HOTWORD, "HOTWORD" },
[email protected]9367e032014-03-07 19:42:3757 };
58
59 std::string ret;
viettrungluu2dfaba72014-10-16 05:30:2560 for (size_t i = 0; i < arraysize(flags); ++i) {
[email protected]9367e032014-03-07 19:42:3761 if (effects & flags[i].flag) {
62 if (!ret.empty())
63 ret += " | ";
64 ret += flags[i].name;
65 effects &= ~flags[i].flag;
66 }
67 }
68
69 if (effects) {
70 if (!ret.empty())
71 ret += " | ";
72 ret += base::IntToString(effects);
73 }
74
75 return ret;
76}
77
dalecurtisb9a6b7872015-02-06 22:41:5578std::string FormatToString(media::AudioParameters::Format format) {
79 switch (format) {
80 case media::AudioParameters::AUDIO_PCM_LINEAR:
81 return "pcm_linear";
82 case media::AudioParameters::AUDIO_PCM_LOW_LATENCY:
83 return "pcm_low_latency";
tsunghung59327d62016-11-19 16:09:4884 case media::AudioParameters::AUDIO_BITSTREAM_AC3:
85 return "ac3";
86 case media::AudioParameters::AUDIO_BITSTREAM_EAC3:
87 return "eac3";
dalecurtisb9a6b7872015-02-06 22:41:5588 case media::AudioParameters::AUDIO_FAKE:
89 return "fake";
dalecurtisb9a6b7872015-02-06 22:41:5590 }
91
92 NOTREACHED();
93 return "unknown";
94}
95
xhwang29c5ad202017-04-14 07:02:1996// Whether the player is in incognito mode or ChromeOS guest mode.
97bool IsIncognito(int render_process_id) {
98 content::RenderProcessHost* render_process_host =
99 content::RenderProcessHost::FromID(render_process_id);
100 if (!render_process_host) {
101 // This could happen in tests.
102 LOG(ERROR) << "Cannot get RenderProcessHost";
103 return false;
104 }
105
106 content::BrowserContext* browser_context =
107 render_process_host->GetBrowserContext();
108 DCHECK(browser_context);
109
110 return browser_context->IsOffTheRecord();
111}
112
[email protected]69946cf2013-11-27 00:11:42113const char kAudioLogStatusKey[] = "status";
114const char kAudioLogUpdateFunction[] = "media.updateAudioComponent";
115
116} // namespace
117
[email protected]11158e2d2013-02-01 02:31:56118namespace content {
119
[email protected]69946cf2013-11-27 00:11:42120class AudioLogImpl : public media::AudioLog {
121 public:
122 AudioLogImpl(int owner_id,
123 media::AudioLogFactory::AudioComponent component,
124 content::MediaInternals* media_internals);
dchengc2282aa2014-10-21 12:07:58125 ~AudioLogImpl() override;
[email protected]69946cf2013-11-27 00:11:42126
dchengc2282aa2014-10-21 12:07:58127 void OnCreated(int component_id,
128 const media::AudioParameters& params,
129 const std::string& device_id) override;
130 void OnStarted(int component_id) override;
131 void OnStopped(int component_id) override;
132 void OnClosed(int component_id) override;
133 void OnError(int component_id) override;
134 void OnSetVolume(int component_id, double volume) override;
guidou61e29df2015-06-11 16:13:56135 void OnSwitchOutputDevice(int component_id,
136 const std::string& device_id) override;
guidou50db1a62016-06-01 17:12:17137 void OnLogMessage(int component_id, const std::string& message) override;
[email protected]69946cf2013-11-27 00:11:42138
dalecurtise6aa75f2015-03-31 02:39:38139 // Called by MediaInternals to update the WebContents title for a stream.
140 void SendWebContentsTitle(int component_id,
141 int render_process_id,
142 int render_frame_id);
143
[email protected]69946cf2013-11-27 00:11:42144 private:
145 void SendSingleStringUpdate(int component_id,
146 const std::string& key,
147 const std::string& value);
148 void StoreComponentMetadata(int component_id, base::DictionaryValue* dict);
149 std::string FormatCacheKey(int component_id);
150
dcheng3b4fe472016-04-08 23:45:13151 static void SendWebContentsTitleHelper(
152 const std::string& cache_key,
153 std::unique_ptr<base::DictionaryValue> dict,
154 int render_process_id,
155 int render_frame_id);
dalecurtise6aa75f2015-03-31 02:39:38156
[email protected]69946cf2013-11-27 00:11:42157 const int owner_id_;
158 const media::AudioLogFactory::AudioComponent component_;
159 content::MediaInternals* const media_internals_;
160
161 DISALLOW_COPY_AND_ASSIGN(AudioLogImpl);
162};
163
164AudioLogImpl::AudioLogImpl(int owner_id,
165 media::AudioLogFactory::AudioComponent component,
166 content::MediaInternals* media_internals)
167 : owner_id_(owner_id),
168 component_(component),
169 media_internals_(media_internals) {}
170
171AudioLogImpl::~AudioLogImpl() {}
172
173void AudioLogImpl::OnCreated(int component_id,
174 const media::AudioParameters& params,
[email protected]25d7f892014-02-13 15:22:45175 const std::string& device_id) {
[email protected]69946cf2013-11-27 00:11:42176 base::DictionaryValue dict;
177 StoreComponentMetadata(component_id, &dict);
178
179 dict.SetString(kAudioLogStatusKey, "created");
[email protected]25d7f892014-02-13 15:22:45180 dict.SetString("device_id", device_id);
dalecurtisb9a6b7872015-02-06 22:41:55181 dict.SetString("device_type", FormatToString(params.format()));
[email protected]69946cf2013-11-27 00:11:42182 dict.SetInteger("frames_per_buffer", params.frames_per_buffer());
183 dict.SetInteger("sample_rate", params.sample_rate());
[email protected]ad82f412013-11-27 04:20:41184 dict.SetInteger("channels", params.channels());
185 dict.SetString("channel_layout",
[email protected]69946cf2013-11-27 00:11:42186 ChannelLayoutToString(params.channel_layout()));
[email protected]9367e032014-03-07 19:42:37187 dict.SetString("effects", EffectsToString(params.effects()));
[email protected]69946cf2013-11-27 00:11:42188
xhwang002c154f2015-06-16 02:55:54189 media_internals_->UpdateAudioLog(MediaInternals::CREATE,
190 FormatCacheKey(component_id),
191 kAudioLogUpdateFunction, &dict);
[email protected]69946cf2013-11-27 00:11:42192}
193
194void AudioLogImpl::OnStarted(int component_id) {
195 SendSingleStringUpdate(component_id, kAudioLogStatusKey, "started");
196}
197
198void AudioLogImpl::OnStopped(int component_id) {
199 SendSingleStringUpdate(component_id, kAudioLogStatusKey, "stopped");
200}
201
202void AudioLogImpl::OnClosed(int component_id) {
203 base::DictionaryValue dict;
204 StoreComponentMetadata(component_id, &dict);
205 dict.SetString(kAudioLogStatusKey, "closed");
xhwang002c154f2015-06-16 02:55:54206 media_internals_->UpdateAudioLog(MediaInternals::UPDATE_AND_DELETE,
207 FormatCacheKey(component_id),
208 kAudioLogUpdateFunction, &dict);
[email protected]69946cf2013-11-27 00:11:42209}
210
211void AudioLogImpl::OnError(int component_id) {
212 SendSingleStringUpdate(component_id, "error_occurred", "true");
213}
214
215void AudioLogImpl::OnSetVolume(int component_id, double volume) {
216 base::DictionaryValue dict;
217 StoreComponentMetadata(component_id, &dict);
218 dict.SetDouble("volume", volume);
xhwang002c154f2015-06-16 02:55:54219 media_internals_->UpdateAudioLog(MediaInternals::UPDATE_IF_EXISTS,
220 FormatCacheKey(component_id),
221 kAudioLogUpdateFunction, &dict);
dalecurtise6aa75f2015-03-31 02:39:38222}
223
guidou61e29df2015-06-11 16:13:56224void AudioLogImpl::OnSwitchOutputDevice(int component_id,
225 const std::string& device_id) {
226 base::DictionaryValue dict;
227 StoreComponentMetadata(component_id, &dict);
228 dict.SetString("device_id", device_id);
xhwang002c154f2015-06-16 02:55:54229 media_internals_->UpdateAudioLog(MediaInternals::UPDATE_IF_EXISTS,
230 FormatCacheKey(component_id),
231 kAudioLogUpdateFunction, &dict);
guidou61e29df2015-06-11 16:13:56232}
233
guidou50db1a62016-06-01 17:12:17234void AudioLogImpl::OnLogMessage(int component_id, const std::string& message) {
235 MediaStreamManager::SendMessageToNativeLog(message);
236}
237
dalecurtise6aa75f2015-03-31 02:39:38238void AudioLogImpl::SendWebContentsTitle(int component_id,
239 int render_process_id,
240 int render_frame_id) {
dcheng3b4fe472016-04-08 23:45:13241 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dalecurtise6aa75f2015-03-31 02:39:38242 StoreComponentMetadata(component_id, dict.get());
dcheng36b6aec92015-12-26 06:16:36243 SendWebContentsTitleHelper(FormatCacheKey(component_id), std::move(dict),
dalecurtise6aa75f2015-03-31 02:39:38244 render_process_id, render_frame_id);
[email protected]69946cf2013-11-27 00:11:42245}
246
247std::string AudioLogImpl::FormatCacheKey(int component_id) {
248 return base::StringPrintf("%d:%d:%d", owner_id_, component_, component_id);
249}
250
dalecurtise6aa75f2015-03-31 02:39:38251// static
252void AudioLogImpl::SendWebContentsTitleHelper(
253 const std::string& cache_key,
dcheng3b4fe472016-04-08 23:45:13254 std::unique_ptr<base::DictionaryValue> dict,
dalecurtise6aa75f2015-03-31 02:39:38255 int render_process_id,
256 int render_frame_id) {
257 // Page title information can only be retrieved from the UI thread.
258 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
tzika5b0e30d2017-08-17 12:22:51259 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
260 base::BindOnce(&SendWebContentsTitleHelper,
261 cache_key, base::Passed(&dict),
262 render_process_id, render_frame_id));
dalecurtise6aa75f2015-03-31 02:39:38263 return;
264 }
265
266 const WebContents* web_contents = WebContents::FromRenderFrameHost(
267 RenderFrameHost::FromID(render_process_id, render_frame_id));
268 if (!web_contents)
269 return;
270
271 // Note: by this point the given audio log entry could have been destroyed, so
272 // we use UPDATE_IF_EXISTS to discard such instances.
273 dict->SetInteger("render_process_id", render_process_id);
274 dict->SetString("web_contents_title", web_contents->GetTitle());
xhwang002c154f2015-06-16 02:55:54275 MediaInternals::GetInstance()->UpdateAudioLog(
dalecurtise6aa75f2015-03-31 02:39:38276 MediaInternals::UPDATE_IF_EXISTS, cache_key, kAudioLogUpdateFunction,
277 dict.get());
278}
279
[email protected]69946cf2013-11-27 00:11:42280void AudioLogImpl::SendSingleStringUpdate(int component_id,
281 const std::string& key,
282 const std::string& value) {
283 base::DictionaryValue dict;
284 StoreComponentMetadata(component_id, &dict);
285 dict.SetString(key, value);
xhwang002c154f2015-06-16 02:55:54286 media_internals_->UpdateAudioLog(MediaInternals::UPDATE_IF_EXISTS,
287 FormatCacheKey(component_id),
288 kAudioLogUpdateFunction, &dict);
[email protected]69946cf2013-11-27 00:11:42289}
290
291void AudioLogImpl::StoreComponentMetadata(int component_id,
292 base::DictionaryValue* dict) {
293 dict->SetInteger("owner_id", owner_id_);
294 dict->SetInteger("component_id", component_id);
295 dict->SetInteger("component_type", component_);
296}
297
xhwang0fdb8312015-06-10 23:15:38298// This class lives on the browser UI thread.
xhwange63e59b8d2015-06-10 00:45:35299class MediaInternals::MediaInternalsUMAHandler {
prabhur53bb9182014-11-13 03:25:17300 public:
Dale Curtis1adbe6a2017-08-02 02:09:13301 MediaInternalsUMAHandler();
prabhur53bb9182014-11-13 03:25:17302
xhwange63e59b8d2015-06-10 00:45:35303 // Called when a render process is terminated. Reports the pipeline status to
304 // UMA for every player associated with the renderer process and then deletes
305 // the player state.
306 void OnProcessTerminated(int render_process_id);
prabhur53bb9182014-11-13 03:25:17307
308 // Helper function to save the event payload to RendererPlayerMap.
xhwange63e59b8d2015-06-10 00:45:35309 void SavePlayerState(int render_process_id,
310 const media::MediaLogEvent& event);
prabhur53bb9182014-11-13 03:25:17311
312 private:
313 struct PipelineInfo {
xhwang29c5ad202017-04-14 07:02:19314 explicit PipelineInfo(bool is_incognito) : is_incognito(is_incognito) {}
315
dalecurtisa14620dd2016-02-27 02:13:25316 bool has_pipeline = false;
dalecurtisc58ff8e2017-02-22 18:20:45317 bool has_ever_played = false;
318 bool has_reached_have_enough = false;
dalecurtisa14620dd2016-02-27 02:13:25319 media::PipelineStatus last_pipeline_status = media::PIPELINE_OK;
320 bool has_audio = false;
321 bool has_video = false;
322 bool video_dds = false;
323 bool video_decoder_changed = false;
xhwang29c5ad202017-04-14 07:02:19324 bool has_cdm = false;
325 bool is_incognito = false;
prabhur53bb9182014-11-13 03:25:17326 std::string audio_codec_name;
327 std::string video_codec_name;
328 std::string video_decoder;
dalecurtis2cff7f372017-05-24 08:30:08329 GURL origin_url;
prabhur53bb9182014-11-13 03:25:17330 };
331
332 // Helper function to report PipelineStatus associated with a player to UMA.
333 void ReportUMAForPipelineStatus(const PipelineInfo& player_info);
334
prabhurc4812392014-12-05 19:59:42335 // Helper to generate PipelineStatus UMA name for AudioVideo streams.
336 std::string GetUMANameForAVStream(const PipelineInfo& player_info);
337
xhwang0fdb8312015-06-10 23:15:38338 // Key is player id.
prabhur53bb9182014-11-13 03:25:17339 typedef std::map<int, PipelineInfo> PlayerInfoMap;
340
xhwang0fdb8312015-06-10 23:15:38341 // Key is renderer id.
prabhur53bb9182014-11-13 03:25:17342 typedef std::map<int, PlayerInfoMap> RendererPlayerMap;
343
xhwang0fdb8312015-06-10 23:15:38344 // Stores player information per renderer.
prabhur53bb9182014-11-13 03:25:17345 RendererPlayerMap renderer_info_;
346
prabhur53bb9182014-11-13 03:25:17347 DISALLOW_COPY_AND_ASSIGN(MediaInternalsUMAHandler);
348};
349
Dale Curtis1adbe6a2017-08-02 02:09:13350MediaInternals::MediaInternalsUMAHandler::MediaInternalsUMAHandler() {}
prabhur53bb9182014-11-13 03:25:17351
352void MediaInternals::MediaInternalsUMAHandler::SavePlayerState(
xhwange63e59b8d2015-06-10 00:45:35353 int render_process_id,
354 const media::MediaLogEvent& event) {
xhwang0fdb8312015-06-10 23:15:38355 DCHECK_CURRENTLY_ON(BrowserThread::UI);
xhwang29c5ad202017-04-14 07:02:19356
357 PlayerInfoMap& player_info_map = renderer_info_[render_process_id];
358
359 auto it = player_info_map.find(event.id);
360 if (it == player_info_map.end()) {
361 bool success = false;
362 std::tie(it, success) = player_info_map.emplace(
363 std::make_pair(event.id, PipelineInfo(IsIncognito(render_process_id))));
364 if (!success) {
365 LOG(ERROR) << "Failed to insert a new PipelineInfo.";
366 return;
367 }
368 }
369
370 PipelineInfo& player_info = it->second;
371
prabhur53bb9182014-11-13 03:25:17372 switch (event.type) {
dalecurtis2cff7f372017-05-24 08:30:08373 case media::MediaLogEvent::Type::WEBMEDIAPLAYER_CREATED: {
374 std::string origin_url;
375 event.params.GetString("origin_url", &origin_url);
376 player_info.origin_url = GURL(origin_url);
377 break;
378 }
dalecurtisc58ff8e2017-02-22 18:20:45379 case media::MediaLogEvent::PLAY: {
xhwang29c5ad202017-04-14 07:02:19380 player_info.has_ever_played = true;
dalecurtisc58ff8e2017-02-22 18:20:45381 break;
382 }
dalecurtisa14620dd2016-02-27 02:13:25383 case media::MediaLogEvent::PIPELINE_STATE_CHANGED: {
xhwang29c5ad202017-04-14 07:02:19384 player_info.has_pipeline = true;
dalecurtisa14620dd2016-02-27 02:13:25385 break;
386 }
prabhur53bb9182014-11-13 03:25:17387 case media::MediaLogEvent::PIPELINE_ERROR: {
388 int status;
389 event.params.GetInteger("pipeline_error", &status);
xhwang29c5ad202017-04-14 07:02:19390 player_info.last_pipeline_status =
prabhur53bb9182014-11-13 03:25:17391 static_cast<media::PipelineStatus>(status);
392 break;
393 }
394 case media::MediaLogEvent::PROPERTY_CHANGE:
395 if (event.params.HasKey("found_audio_stream")) {
xhwang29c5ad202017-04-14 07:02:19396 event.params.GetBoolean("found_audio_stream", &player_info.has_audio);
prabhur53bb9182014-11-13 03:25:17397 }
398 if (event.params.HasKey("found_video_stream")) {
xhwang29c5ad202017-04-14 07:02:19399 event.params.GetBoolean("found_video_stream", &player_info.has_video);
prabhur53bb9182014-11-13 03:25:17400 }
401 if (event.params.HasKey("audio_codec_name")) {
402 event.params.GetString("audio_codec_name",
xhwang29c5ad202017-04-14 07:02:19403 &player_info.audio_codec_name);
prabhur53bb9182014-11-13 03:25:17404 }
405 if (event.params.HasKey("video_codec_name")) {
406 event.params.GetString("video_codec_name",
xhwang29c5ad202017-04-14 07:02:19407 &player_info.video_codec_name);
prabhur53bb9182014-11-13 03:25:17408 }
409 if (event.params.HasKey("video_decoder")) {
xhwang29c5ad202017-04-14 07:02:19410 std::string previous_video_decoder(player_info.video_decoder);
411 event.params.GetString("video_decoder", &player_info.video_decoder);
watkc85d60e72015-01-14 19:08:28412 if (!previous_video_decoder.empty() &&
xhwang29c5ad202017-04-14 07:02:19413 previous_video_decoder != player_info.video_decoder) {
414 player_info.video_decoder_changed = true;
watkc85d60e72015-01-14 19:08:28415 }
prabhur53bb9182014-11-13 03:25:17416 }
prabhurc4812392014-12-05 19:59:42417 if (event.params.HasKey("video_dds")) {
xhwang29c5ad202017-04-14 07:02:19418 event.params.GetBoolean("video_dds", &player_info.video_dds);
419 }
420 if (event.params.HasKey("has_cdm")) {
421 event.params.GetBoolean("has_cdm", &player_info.has_cdm);
prabhurc4812392014-12-05 19:59:42422 }
dalecurtisc58ff8e2017-02-22 18:20:45423 if (event.params.HasKey("pipeline_buffering_state")) {
424 std::string buffering_state;
425 event.params.GetString("pipeline_buffering_state", &buffering_state);
426 if (buffering_state == "BUFFERING_HAVE_ENOUGH")
xhwang29c5ad202017-04-14 07:02:19427 player_info.has_reached_have_enough = true;
dalecurtisc58ff8e2017-02-22 18:20:45428 }
prabhur53bb9182014-11-13 03:25:17429 break;
dalecurtis04bdb582016-08-17 22:15:23430 case media::MediaLogEvent::Type::WEBMEDIAPLAYER_DESTROYED: {
431 // Upon player destruction report UMA data; if the player is not torn down
432 // before process exit, it will be logged during OnProcessTerminated().
xhwang29c5ad202017-04-14 07:02:19433 auto it = player_info_map.find(event.id);
434 if (it == player_info_map.end())
dalecurtis04bdb582016-08-17 22:15:23435 break;
436
437 ReportUMAForPipelineStatus(it->second);
xhwang29c5ad202017-04-14 07:02:19438 player_info_map.erase(it);
dalecurtis04bdb582016-08-17 22:15:23439 }
prabhur53bb9182014-11-13 03:25:17440 default:
441 break;
442 }
443 return;
444}
445
prabhurc4812392014-12-05 19:59:42446std::string MediaInternals::MediaInternalsUMAHandler::GetUMANameForAVStream(
447 const PipelineInfo& player_info) {
xhwang0fdb8312015-06-10 23:15:38448 DCHECK_CURRENTLY_ON(BrowserThread::UI);
prabhurc4812392014-12-05 19:59:42449 static const char kPipelineUmaPrefix[] = "Media.PipelineStatus.AudioVideo.";
450 std::string uma_name = kPipelineUmaPrefix;
451 if (player_info.video_codec_name == "vp8") {
452 uma_name += "VP8.";
453 } else if (player_info.video_codec_name == "vp9") {
454 uma_name += "VP9.";
455 } else if (player_info.video_codec_name == "h264") {
456 uma_name += "H264.";
457 } else {
458 return uma_name + "Other";
459 }
460
kraush42521052017-05-04 19:14:34461#if !defined(OS_ANDROID)
prabhurc4812392014-12-05 19:59:42462 if (player_info.video_decoder ==
463 media::DecryptingVideoDecoder::kDecoderName) {
464 return uma_name + "DVD";
465 }
xhwangf2189392015-10-19 22:21:51466#endif
prabhurc4812392014-12-05 19:59:42467
468 if (player_info.video_dds) {
469 uma_name += "DDS.";
470 }
471
472 if (player_info.video_decoder == media::GpuVideoDecoder::kDecoderName) {
473 uma_name += "HW";
474 } else {
475 uma_name += "SW";
476 }
477 return uma_name;
478}
479
xhwang29c5ad202017-04-14 07:02:19480// TODO(xhwang): This function reports more metrics than just pipeline status
481// and should be renamed. Similarly, PipelineInfo should be PlayerInfo.
prabhur53bb9182014-11-13 03:25:17482void MediaInternals::MediaInternalsUMAHandler::ReportUMAForPipelineStatus(
483 const PipelineInfo& player_info) {
xhwang0fdb8312015-06-10 23:15:38484 DCHECK_CURRENTLY_ON(BrowserThread::UI);
dalecurtisa14620dd2016-02-27 02:13:25485
486 // Don't log pipeline status for players which don't actually have a pipeline;
487 // e.g., the Android MediaSourcePlayer implementation.
488 if (!player_info.has_pipeline)
489 return;
490
prabhur53bb9182014-11-13 03:25:17491 if (player_info.has_video && player_info.has_audio) {
Dale Curtis4a6f54c02017-06-06 23:32:20492 base::UmaHistogramExactLinear(GetUMANameForAVStream(player_info),
493 player_info.last_pipeline_status,
494 media::PIPELINE_STATUS_MAX);
prabhur53bb9182014-11-13 03:25:17495 } else if (player_info.has_audio) {
prabhurdc15c402014-11-21 22:53:01496 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioOnly",
prabhur53bb9182014-11-13 03:25:17497 player_info.last_pipeline_status,
498 media::PIPELINE_STATUS_MAX + 1);
499 } else if (player_info.has_video) {
prabhurdc15c402014-11-21 22:53:01500 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.VideoOnly",
prabhur53bb9182014-11-13 03:25:17501 player_info.last_pipeline_status,
502 media::PIPELINE_STATUS_MAX + 1);
503 } else {
dalecurtis69da898e2016-03-18 21:26:37504 // Note: This metric can be recorded as a result of normal operation with
505 // Media Source Extensions. If a site creates a MediaSource object but never
506 // creates a source buffer or appends data, PIPELINE_OK will be recorded.
prabhur53bb9182014-11-13 03:25:17507 UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.Unsupported",
508 player_info.last_pipeline_status,
509 media::PIPELINE_STATUS_MAX + 1);
510 }
watkc85d60e72015-01-14 19:08:28511 // Report whether video decoder fallback happened, but only if a video decoder
512 // was reported.
513 if (!player_info.video_decoder.empty()) {
514 UMA_HISTOGRAM_BOOLEAN("Media.VideoDecoderFallback",
515 player_info.video_decoder_changed);
516 }
dalecurtisc58ff8e2017-02-22 18:20:45517
518 // Report whether this player ever saw a playback event. Used to measure the
519 // effectiveness of efforts to reduce loaded-but-never-used players.
520 if (player_info.has_reached_have_enough)
521 UMA_HISTOGRAM_BOOLEAN("Media.HasEverPlayed", player_info.has_ever_played);
xhwang29c5ad202017-04-14 07:02:19522
523 // Report whether an encrypted playback is in incognito window, excluding
524 // never-used players.
525 if (player_info.has_cdm && player_info.has_ever_played)
526 UMA_HISTOGRAM_BOOLEAN("Media.EME.IsIncognito", player_info.is_incognito);
prabhur53bb9182014-11-13 03:25:17527}
528
xhwange63e59b8d2015-06-10 00:45:35529void MediaInternals::MediaInternalsUMAHandler::OnProcessTerminated(
prabhur53bb9182014-11-13 03:25:17530 int render_process_id) {
xhwang0fdb8312015-06-10 23:15:38531 DCHECK_CURRENTLY_ON(BrowserThread::UI);
xhwange63e59b8d2015-06-10 00:45:35532
prabhur53bb9182014-11-13 03:25:17533 auto players_it = renderer_info_.find(render_process_id);
534 if (players_it == renderer_info_.end())
535 return;
536 auto it = players_it->second.begin();
537 while (it != players_it->second.end()) {
538 ReportUMAForPipelineStatus(it->second);
539 players_it->second.erase(it++);
540 }
xhwange63e59b8d2015-06-10 00:45:35541 renderer_info_.erase(players_it);
prabhur53bb9182014-11-13 03:25:17542}
543
[email protected]11158e2d2013-02-01 02:31:56544MediaInternals* MediaInternals::GetInstance() {
dalecurtisc3af5092017-02-11 02:08:18545 static content::MediaInternals* internals = new content::MediaInternals();
546 return internals;
[email protected]11158e2d2013-02-01 02:31:56547}
548
prabhur53bb9182014-11-13 03:25:17549MediaInternals::MediaInternals()
xhwangfe338e92015-06-08 17:36:09550 : can_update_(false),
551 owner_ids_(),
Dale Curtis1adbe6a2017-08-02 02:09:13552 uma_handler_(new MediaInternalsUMAHandler()) {
Dan Sandersb26585d2017-07-12 23:51:30553 // TODO(sandersd): Is there ever a relevant case where TERMINATED is sent
554 // without CLOSED also being sent?
555 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED,
556 NotificationService::AllBrowserContextsAndSources());
xhwange63e59b8d2015-06-10 00:45:35557 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
558 NotificationService::AllBrowserContextsAndSources());
prabhur53bb9182014-11-13 03:25:17559}
560
[email protected]11158e2d2013-02-01 02:31:56561MediaInternals::~MediaInternals() {}
562
xhwange63e59b8d2015-06-10 00:45:35563void MediaInternals::Observe(int type,
564 const NotificationSource& source,
565 const NotificationDetails& details) {
566 DCHECK_CURRENTLY_ON(BrowserThread::UI);
xhwange63e59b8d2015-06-10 00:45:35567 RenderProcessHost* process = Source<RenderProcessHost>(source).ptr();
xhwange63e59b8d2015-06-10 00:45:35568 uma_handler_->OnProcessTerminated(process->GetID());
Dan Sandersb26585d2017-07-12 23:51:30569 // TODO(sandersd): Send a termination event before clearing the log.
watkb5983f92016-08-09 20:47:51570 saved_events_by_process_.erase(process->GetID());
xhwange63e59b8d2015-06-10 00:45:35571}
572
573// Converts the |event| to a |update|. Returns whether the conversion succeeded.
574static bool ConvertEventToUpdate(int render_process_id,
575 const media::MediaLogEvent& event,
576 base::string16* update) {
577 DCHECK(update);
578
579 base::DictionaryValue dict;
580 dict.SetInteger("renderer", render_process_id);
581 dict.SetInteger("player", event.id);
582 dict.SetString("type", media::MediaLog::EventTypeToString(event.type));
583
584 // TODO(dalecurtis): This is technically not correct. TimeTicks "can't" be
585 // converted to to a human readable time format. See base/time/time.h.
586 const double ticks = event.time.ToInternalValue();
587 const double ticks_millis = ticks / base::Time::kMicrosecondsPerMillisecond;
588 dict.SetDouble("ticksMillis", ticks_millis);
589
590 // Convert PipelineStatus to human readable string
591 if (event.type == media::MediaLogEvent::PIPELINE_ERROR) {
592 int status;
593 if (!event.params.GetInteger("pipeline_error", &status) ||
594 status < static_cast<int>(media::PIPELINE_OK) ||
595 status > static_cast<int>(media::PIPELINE_STATUS_MAX)) {
596 return false;
597 }
598 media::PipelineStatus error = static_cast<media::PipelineStatus>(status);
599 dict.SetString("params.pipeline_error",
600 media::MediaLog::PipelineStatusToString(error));
601 } else {
jdoerriecc9f5732017-08-23 14:12:30602 dict.SetKey("params", event.params.Clone());
xhwange63e59b8d2015-06-10 00:45:35603 }
604
605 *update = SerializeUpdate("media.onMediaEvent", &dict);
606 return true;
607}
608
[email protected]0e7ee582013-05-04 14:06:59609void MediaInternals::OnMediaEvents(
Dale Curtis1adbe6a2017-08-02 02:09:13610 int render_process_id,
611 const std::vector<media::MediaLogEvent>& events) {
xhwang0fdb8312015-06-10 23:15:38612 DCHECK_CURRENTLY_ON(BrowserThread::UI);
[email protected]69946cf2013-11-27 00:11:42613 // Notify observers that |event| has occurred.
xhwange63e59b8d2015-06-10 00:45:35614 for (const auto& event : events) {
Dale Curtis1adbe6a2017-08-02 02:09:13615 if (CanUpdate()) {
616 base::string16 update;
617 if (ConvertEventToUpdate(render_process_id, event, &update))
618 SendUpdate(update);
prabhur53bb9182014-11-13 03:25:17619 }
Dale Curtis1adbe6a2017-08-02 02:09:13620 SaveEvent(render_process_id, event);
xhwange63e59b8d2015-06-10 00:45:35621 uma_handler_->SavePlayerState(render_process_id, event);
[email protected]0e7ee582013-05-04 14:06:59622 }
[email protected]11158e2d2013-02-01 02:31:56623}
624
625void MediaInternals::AddUpdateCallback(const UpdateCallback& callback) {
xhwangfe338e92015-06-08 17:36:09626 DCHECK_CURRENTLY_ON(BrowserThread::UI);
[email protected]11158e2d2013-02-01 02:31:56627 update_callbacks_.push_back(callback);
xhwangfe338e92015-06-08 17:36:09628
629 base::AutoLock auto_lock(lock_);
630 can_update_ = true;
[email protected]11158e2d2013-02-01 02:31:56631}
632
633void MediaInternals::RemoveUpdateCallback(const UpdateCallback& callback) {
xhwangfe338e92015-06-08 17:36:09634 DCHECK_CURRENTLY_ON(BrowserThread::UI);
[email protected]11158e2d2013-02-01 02:31:56635 for (size_t i = 0; i < update_callbacks_.size(); ++i) {
636 if (update_callbacks_[i].Equals(callback)) {
637 update_callbacks_.erase(update_callbacks_.begin() + i);
xhwangfe338e92015-06-08 17:36:09638 break;
[email protected]11158e2d2013-02-01 02:31:56639 }
640 }
xhwangfe338e92015-06-08 17:36:09641
642 base::AutoLock auto_lock(lock_);
643 can_update_ = !update_callbacks_.empty();
644}
645
646bool MediaInternals::CanUpdate() {
647 base::AutoLock auto_lock(lock_);
648 return can_update_;
[email protected]11158e2d2013-02-01 02:31:56649}
650
xhwange63e59b8d2015-06-10 00:45:35651void MediaInternals::SendHistoricalMediaEvents() {
652 DCHECK_CURRENTLY_ON(BrowserThread::UI);
watkb5983f92016-08-09 20:47:51653 for (const auto& saved_events : saved_events_by_process_) {
654 for (const auto& event : saved_events.second) {
xhwange63e59b8d2015-06-10 00:45:35655 base::string16 update;
watkb5983f92016-08-09 20:47:51656 if (ConvertEventToUpdate(saved_events.first, event, &update))
xhwange63e59b8d2015-06-10 00:45:35657 SendUpdate(update);
658 }
659 }
660 // Do not clear the map/list here so that refreshing the UI or opening a
661 // second UI still works nicely!
662}
663
mcasasfcb5c7de2014-10-11 20:22:17664void MediaInternals::SendAudioStreamData() {
665 base::string16 audio_stream_update;
[email protected]69946cf2013-11-27 00:11:42666 {
667 base::AutoLock auto_lock(lock_);
mcasasfcb5c7de2014-10-11 20:22:17668 audio_stream_update = SerializeUpdate(
669 "media.onReceiveAudioStreamData", &audio_streams_cached_data_);
[email protected]11158e2d2013-02-01 02:31:56670 }
mcasasfcb5c7de2014-10-11 20:22:17671 SendUpdate(audio_stream_update);
672}
673
burnik71963562014-10-17 14:57:14674void MediaInternals::SendVideoCaptureDeviceCapabilities() {
mostynb4c27d042015-03-18 21:47:47675 DCHECK_CURRENTLY_ON(BrowserThread::IO);
xhwangfe338e92015-06-08 17:36:09676
677 if (!CanUpdate())
678 return;
679
burnik71963562014-10-17 14:57:14680 SendUpdate(SerializeUpdate("media.onReceiveVideoCaptureCapabilities",
681 &video_capture_capabilities_cached_data_));
682}
683
mcasasfcb5c7de2014-10-11 20:22:17684void MediaInternals::UpdateVideoCaptureDeviceCapabilities(
chfremer1c38eb42016-08-02 15:54:31685 const std::vector<std::tuple<media::VideoCaptureDeviceDescriptor,
686 media::VideoCaptureFormats>>&
687 descriptors_and_formats) {
mostynb4c27d042015-03-18 21:47:47688 DCHECK_CURRENTLY_ON(BrowserThread::IO);
burnik71963562014-10-17 14:57:14689 video_capture_capabilities_cached_data_.Clear();
mcasasfcb5c7de2014-10-11 20:22:17690
chfremer1c38eb42016-08-02 15:54:31691 for (const auto& device_format_pair : descriptors_and_formats) {
Jeremy Roman04f27c372017-10-27 15:20:55692 auto format_list = std::make_unique<base::ListValue>();
nisse7a153fb2015-12-07 09:30:59693 // TODO(nisse): Representing format information as a string, to be
694 // parsed by the javascript handler, is brittle. Consider passing
695 // a list of mappings instead.
696
chfremer1c38eb42016-08-02 15:54:31697 const media::VideoCaptureDeviceDescriptor& descriptor =
698 std::get<0>(device_format_pair);
699 const media::VideoCaptureFormats& supported_formats =
700 std::get<1>(device_format_pair);
701 for (const auto& format : supported_formats)
ajose5d00e98782015-07-01 21:09:48702 format_list->AppendString(media::VideoCaptureFormat::ToString(format));
burnik71963562014-10-17 14:57:14703
dcheng98e96a72016-06-11 03:41:48704 std::unique_ptr<base::DictionaryValue> device_dict(
705 new base::DictionaryValue());
chfremer1c38eb42016-08-02 15:54:31706 device_dict->SetString("id", descriptor.device_id);
707 device_dict->SetString("name", descriptor.GetNameAndModel());
jdoerrie664305c2017-06-07 08:34:34708 device_dict->Set("formats", std::move(format_list));
emircan86e26b252015-03-24 20:29:40709#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
710 defined(OS_ANDROID)
chfremer1c38eb42016-08-02 15:54:31711 device_dict->SetString("captureApi", descriptor.GetCaptureApiTypeString());
mcasasfcb5c7de2014-10-11 20:22:17712#endif
dcheng98e96a72016-06-11 03:41:48713 video_capture_capabilities_cached_data_.Append(std::move(device_dict));
mcasasfcb5c7de2014-10-11 20:22:17714 }
burnik71963562014-10-17 14:57:14715
xhwangfe338e92015-06-08 17:36:09716 SendVideoCaptureDeviceCapabilities();
mcasasfcb5c7de2014-10-11 20:22:17717}
718
dcheng3b4fe472016-04-08 23:45:13719std::unique_ptr<media::AudioLog> MediaInternals::CreateAudioLog(
mcasasfcb5c7de2014-10-11 20:22:17720 AudioComponent component) {
721 base::AutoLock auto_lock(lock_);
dcheng3b4fe472016-04-08 23:45:13722 return std::unique_ptr<media::AudioLog>(
723 new AudioLogImpl(owner_ids_[component]++, component, this));
[email protected]e2fd1f72013-08-16 00:34:25724}
725
dalecurtise6aa75f2015-03-31 02:39:38726void MediaInternals::SetWebContentsTitleForAudioLogEntry(
727 int component_id,
728 int render_process_id,
729 int render_frame_id,
730 media::AudioLog* audio_log) {
731 static_cast<AudioLogImpl*>(audio_log)
732 ->SendWebContentsTitle(component_id, render_process_id, render_frame_id);
733}
734
dalecurtisc45b1c4e2017-04-01 07:14:27735void MediaInternals::OnProcessTerminatedForTesting(int process_id) {
736 uma_handler_->OnProcessTerminated(process_id);
737}
738
[email protected]fcf75d42013-12-03 20:11:26739void MediaInternals::SendUpdate(const base::string16& update) {
xhwangfe338e92015-06-08 17:36:09740 // SendUpdate() may be called from any thread, but must run on the UI thread.
741 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
tzika5b0e30d2017-08-17 12:22:51742 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
743 base::BindOnce(&MediaInternals::SendUpdate,
744 base::Unretained(this), update));
[email protected]11158e2d2013-02-01 02:31:56745 return;
[email protected]69946cf2013-11-27 00:11:42746 }
[email protected]11158e2d2013-02-01 02:31:56747
[email protected]11158e2d2013-02-01 02:31:56748 for (size_t i = 0; i < update_callbacks_.size(); i++)
749 update_callbacks_[i].Run(update);
750}
751
xhwange63e59b8d2015-06-10 00:45:35752void MediaInternals::SaveEvent(int process_id,
753 const media::MediaLogEvent& event) {
xhwang0fdb8312015-06-10 23:15:38754 DCHECK_CURRENTLY_ON(BrowserThread::UI);
xhwange63e59b8d2015-06-10 00:45:35755
Dale Curtis1ff5a375a2017-06-16 21:38:31756// Save the event and limit the total number per renderer. At the time of
757// writing, 512 events of the kind: { "property": value } together consume
758// ~88kb of memory on linux.
759#if defined(OS_ANDROID)
760 const size_t kEventLimit = 128;
761#else
762 const size_t kEventLimit = 512;
763#endif
xhwange63e59b8d2015-06-10 00:45:35764
Dale Curtis1ff5a375a2017-06-16 21:38:31765 auto& saved_events = saved_events_by_process_[process_id];
watkb5983f92016-08-09 20:47:51766 saved_events.push_back(event);
Dale Curtis1ff5a375a2017-06-16 21:38:31767 if (saved_events.size() > kEventLimit) {
watkb5983f92016-08-09 20:47:51768 // Remove all events for a given player as soon as we have to remove a
769 // single event for that player to avoid showing incomplete players.
Dale Curtis1ff5a375a2017-06-16 21:38:31770 const int id_to_remove = saved_events.front().id;
771 saved_events.erase(std::remove_if(saved_events.begin(), saved_events.end(),
772 [&](const media::MediaLogEvent& event) {
773 return event.id == id_to_remove;
774 }),
775 saved_events.end());
watkb5983f92016-08-09 20:47:51776 }
xhwange63e59b8d2015-06-10 00:45:35777}
778
xhwang002c154f2015-06-16 02:55:54779void MediaInternals::UpdateAudioLog(AudioLogUpdateType type,
780 const std::string& cache_key,
781 const std::string& function,
782 const base::DictionaryValue* value) {
dalecurtise6aa75f2015-03-31 02:39:38783 {
784 base::AutoLock auto_lock(lock_);
785 const bool has_entry = audio_streams_cached_data_.HasKey(cache_key);
786 if ((type == UPDATE_IF_EXISTS || type == UPDATE_AND_DELETE) && !has_entry) {
787 return;
788 } else if (!has_entry) {
789 DCHECK_EQ(type, CREATE);
jdoerriecc9f5732017-08-23 14:12:30790 audio_streams_cached_data_.Set(
Jeremy Roman04f27c372017-10-27 15:20:55791 cache_key, std::make_unique<base::Value>(value->Clone()));
dalecurtise6aa75f2015-03-31 02:39:38792 } else if (type == UPDATE_AND_DELETE) {
dcheng3b4fe472016-04-08 23:45:13793 std::unique_ptr<base::Value> out_value;
dalecurtise6aa75f2015-03-31 02:39:38794 CHECK(audio_streams_cached_data_.Remove(cache_key, &out_value));
795 } else {
796 base::DictionaryValue* existing_dict = NULL;
797 CHECK(
798 audio_streams_cached_data_.GetDictionary(cache_key, &existing_dict));
799 existing_dict->MergeDictionary(value);
800 }
[email protected]69946cf2013-11-27 00:11:42801 }
802
xhwang002c154f2015-06-16 02:55:54803 if (CanUpdate())
804 SendUpdate(SerializeUpdate(function, value));
[email protected]69946cf2013-11-27 00:11:42805}
806
[email protected]11158e2d2013-02-01 02:31:56807} // namespace content