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 "content/renderer/media/webrtc/media_stream_track_metrics.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "content/common/media/media_stream_track_metrics_host_messages.h"
14 #include "content/renderer/render_thread_impl.h"
15 #include "third_party/libjingle/source/talk/app/webrtc/mediastreaminterface.h"
17 using webrtc::AudioTrackVector
;
18 using webrtc::MediaStreamInterface
;
19 using webrtc::MediaStreamTrackInterface
;
20 using webrtc::PeerConnectionInterface
;
21 using webrtc::VideoTrackVector
;
25 typedef std::set
<std::string
> IdSet
;
28 IdSet
GetTrackIds(const std::vector
<rtc::scoped_refptr
<T
>>& tracks
) {
30 for (const auto& track
: tracks
)
31 track_ids
.insert(track
->id());
35 // TODO(tommi): Consolidate this and TrackObserver since these implementations
36 // are fundamentally achieving the same thing (aside from specific logic inside
37 // the OnChanged callbacks).
38 class MediaStreamObserver
39 : public base::RefCountedThreadSafe
<MediaStreamObserver
>,
40 public webrtc::ObserverInterface
{
42 typedef base::Callback
<
43 void(const IdSet
& audio_track_ids
, const IdSet
& video_track_ids
)>
47 const OnChangedCallback
& callback
,
48 const scoped_refptr
<base::SingleThreadTaskRunner
>& main_thread
,
49 webrtc::MediaStreamInterface
* stream
)
50 : main_thread_(main_thread
), stream_(stream
), callback_(callback
) {
51 signaling_thread_
.DetachFromThread();
52 stream_
->RegisterObserver(this);
55 const scoped_refptr
<webrtc::MediaStreamInterface
>& stream() const {
56 DCHECK(main_thread_
->BelongsToCurrentThread());
61 DCHECK(main_thread_
->BelongsToCurrentThread());
63 stream_
->UnregisterObserver(this);
68 friend class base::RefCountedThreadSafe
<MediaStreamObserver
>;
69 ~MediaStreamObserver() override
{
70 DCHECK(!stream_
.get()) << "must have been unregistered before deleting";
73 // webrtc::ObserverInterface implementation.
74 void OnChanged() override
{
75 DCHECK(signaling_thread_
.CalledOnValidThread());
76 main_thread_
->PostTask(FROM_HERE
,
77 base::Bind(&MediaStreamObserver::OnChangedOnMainThread
, this,
78 GetTrackIds(stream_
->GetAudioTracks()),
79 GetTrackIds(stream_
->GetVideoTracks())));
82 void OnChangedOnMainThread(const IdSet
& audio_track_ids
,
83 const IdSet
& video_track_ids
) {
84 DCHECK(main_thread_
->BelongsToCurrentThread());
85 if (!callback_
.is_null())
86 callback_
.Run(audio_track_ids
, video_track_ids
);
89 const scoped_refptr
<base::SingleThreadTaskRunner
> main_thread_
;
90 scoped_refptr
<webrtc::MediaStreamInterface
> stream_
;
91 OnChangedCallback callback_
; // Only touched on the main thread.
92 base::ThreadChecker signaling_thread_
;
97 class MediaStreamTrackMetricsObserver
{
99 MediaStreamTrackMetricsObserver(
100 MediaStreamTrackMetrics::StreamType stream_type
,
101 MediaStreamInterface
* stream
,
102 MediaStreamTrackMetrics
* owner
);
103 ~MediaStreamTrackMetricsObserver();
105 // Sends begin/end messages for all tracks currently tracked.
106 void SendLifetimeMessages(MediaStreamTrackMetrics::LifetimeEvent event
);
108 MediaStreamInterface
* stream() {
109 DCHECK(thread_checker_
.CalledOnValidThread());
110 return observer_
->stream().get();
113 MediaStreamTrackMetrics::StreamType
stream_type() {
114 DCHECK(thread_checker_
.CalledOnValidThread());
119 void OnChanged(const IdSet
& audio_track_ids
, const IdSet
& video_track_ids
);
121 void ReportAddedAndRemovedTracks(
122 const IdSet
& new_ids
,
123 const IdSet
& old_ids
,
124 MediaStreamTrackMetrics::TrackType track_type
);
126 // Sends a lifetime message for the given tracks. OK to call with an
127 // empty |ids|, in which case the method has no side effects.
128 void ReportTracks(const IdSet
& ids
,
129 MediaStreamTrackMetrics::TrackType track_type
,
130 MediaStreamTrackMetrics::LifetimeEvent event
);
132 // False until start/end of lifetime messages have been sent.
133 bool has_reported_start_
;
134 bool has_reported_end_
;
136 // IDs of audio and video tracks in the stream being observed.
137 IdSet audio_track_ids_
;
138 IdSet video_track_ids_
;
140 MediaStreamTrackMetrics::StreamType stream_type_
;
141 scoped_refptr
<MediaStreamObserver
> observer_
;
144 MediaStreamTrackMetrics
* owner_
;
145 base::ThreadChecker thread_checker_
;
150 // Used with std::find_if.
151 struct ObserverFinder
{
152 ObserverFinder(MediaStreamTrackMetrics::StreamType stream_type
,
153 MediaStreamInterface
* stream
)
154 : stream_type(stream_type
), stream_(stream
) {}
155 bool operator()(MediaStreamTrackMetricsObserver
* observer
) {
156 return stream_
== observer
->stream() &&
157 stream_type
== observer
->stream_type();
159 MediaStreamTrackMetrics::StreamType stream_type
;
160 MediaStreamInterface
* stream_
;
165 MediaStreamTrackMetricsObserver::MediaStreamTrackMetricsObserver(
166 MediaStreamTrackMetrics::StreamType stream_type
,
167 MediaStreamInterface
* stream
,
168 MediaStreamTrackMetrics
* owner
)
169 : has_reported_start_(false),
170 has_reported_end_(false),
171 audio_track_ids_(GetTrackIds(stream
->GetAudioTracks())),
172 video_track_ids_(GetTrackIds(stream
->GetVideoTracks())),
173 stream_type_(stream_type
),
174 observer_(new MediaStreamObserver(
175 base::Bind(&MediaStreamTrackMetricsObserver::OnChanged
,
176 base::Unretained(this)),
177 base::ThreadTaskRunnerHandle::Get(),
182 MediaStreamTrackMetricsObserver::~MediaStreamTrackMetricsObserver() {
183 DCHECK(thread_checker_
.CalledOnValidThread());
184 observer_
->Unregister();
185 SendLifetimeMessages(MediaStreamTrackMetrics::DISCONNECTED
);
188 void MediaStreamTrackMetricsObserver::SendLifetimeMessages(
189 MediaStreamTrackMetrics::LifetimeEvent event
) {
190 DCHECK(thread_checker_
.CalledOnValidThread());
191 if (event
== MediaStreamTrackMetrics::CONNECTED
) {
192 // Both ICE CONNECTED and COMPLETED can trigger the first
193 // start-of-life event, so we only report the first.
194 if (has_reported_start_
)
196 DCHECK(!has_reported_start_
&& !has_reported_end_
);
197 has_reported_start_
= true;
199 DCHECK(event
== MediaStreamTrackMetrics::DISCONNECTED
);
201 // We only report the first end-of-life event, since there are
202 // several cases where end-of-life can be reached. We also don't
203 // report end unless we've reported start.
204 if (has_reported_end_
|| !has_reported_start_
)
206 has_reported_end_
= true;
209 ReportTracks(audio_track_ids_
, MediaStreamTrackMetrics::AUDIO_TRACK
, event
);
210 ReportTracks(video_track_ids_
, MediaStreamTrackMetrics::VIDEO_TRACK
, event
);
212 if (event
== MediaStreamTrackMetrics::DISCONNECTED
) {
213 // After disconnection, we can get reconnected, so we need to
214 // forget that we've sent lifetime events, while retaining all
216 DCHECK(has_reported_start_
&& has_reported_end_
);
217 has_reported_start_
= false;
218 has_reported_end_
= false;
222 void MediaStreamTrackMetricsObserver::OnChanged(
223 const IdSet
& audio_track_ids
, const IdSet
& video_track_ids
) {
224 DCHECK(thread_checker_
.CalledOnValidThread());
226 // We only report changes after our initial report, and never after
228 if (has_reported_start_
&& !has_reported_end_
) {
229 ReportAddedAndRemovedTracks(audio_track_ids
,
231 MediaStreamTrackMetrics::AUDIO_TRACK
);
232 ReportAddedAndRemovedTracks(video_track_ids
,
234 MediaStreamTrackMetrics::VIDEO_TRACK
);
237 // We always update our sets of tracks.
238 audio_track_ids_
= audio_track_ids
;
239 video_track_ids_
= video_track_ids
;
242 void MediaStreamTrackMetricsObserver::ReportAddedAndRemovedTracks(
243 const IdSet
& new_ids
,
244 const IdSet
& old_ids
,
245 MediaStreamTrackMetrics::TrackType track_type
) {
246 DCHECK(thread_checker_
.CalledOnValidThread());
247 DCHECK(has_reported_start_
&& !has_reported_end_
);
249 IdSet added_tracks
= base::STLSetDifference
<IdSet
>(new_ids
, old_ids
);
250 IdSet removed_tracks
= base::STLSetDifference
<IdSet
>(old_ids
, new_ids
);
252 ReportTracks(added_tracks
, track_type
, MediaStreamTrackMetrics::CONNECTED
);
254 removed_tracks
, track_type
, MediaStreamTrackMetrics::DISCONNECTED
);
257 void MediaStreamTrackMetricsObserver::ReportTracks(
259 MediaStreamTrackMetrics::TrackType track_type
,
260 MediaStreamTrackMetrics::LifetimeEvent event
) {
261 DCHECK(thread_checker_
.CalledOnValidThread());
262 for (IdSet::const_iterator it
= ids
.begin(); it
!= ids
.end(); ++it
) {
263 owner_
->SendLifetimeMessage(*it
, track_type
, event
, stream_type_
);
267 MediaStreamTrackMetrics::MediaStreamTrackMetrics()
268 : ice_state_(webrtc::PeerConnectionInterface::kIceConnectionNew
) {}
270 MediaStreamTrackMetrics::~MediaStreamTrackMetrics() {
271 for (ObserverVector::iterator it
= observers_
.begin(); it
!= observers_
.end();
273 (*it
)->SendLifetimeMessages(DISCONNECTED
);
277 void MediaStreamTrackMetrics::AddStream(StreamType type
,
278 MediaStreamInterface
* stream
) {
279 DCHECK(CalledOnValidThread());
280 MediaStreamTrackMetricsObserver
* observer
=
281 new MediaStreamTrackMetricsObserver(type
, stream
, this);
282 observers_
.insert(observers_
.end(), observer
);
283 SendLifeTimeMessageDependingOnIceState(observer
);
286 void MediaStreamTrackMetrics::RemoveStream(StreamType type
,
287 MediaStreamInterface
* stream
) {
288 DCHECK(CalledOnValidThread());
289 ObserverVector::iterator it
= std::find_if(
290 observers_
.begin(), observers_
.end(), ObserverFinder(type
, stream
));
291 if (it
== observers_
.end()) {
292 // Since external apps could call removeStream with a stream they
293 // never added, this can happen without it being an error.
297 observers_
.erase(it
);
300 void MediaStreamTrackMetrics::IceConnectionChange(
301 PeerConnectionInterface::IceConnectionState new_state
) {
302 DCHECK(CalledOnValidThread());
303 ice_state_
= new_state
;
304 for (ObserverVector::iterator it
= observers_
.begin(); it
!= observers_
.end();
306 SendLifeTimeMessageDependingOnIceState(*it
);
309 void MediaStreamTrackMetrics::SendLifeTimeMessageDependingOnIceState(
310 MediaStreamTrackMetricsObserver
* observer
) {
311 // There is a state transition diagram for these states at
312 // http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCIceConnectionState
313 switch (ice_state_
) {
314 case PeerConnectionInterface::kIceConnectionConnected
:
315 case PeerConnectionInterface::kIceConnectionCompleted
:
316 observer
->SendLifetimeMessages(CONNECTED
);
319 case PeerConnectionInterface::kIceConnectionFailed
:
320 // We don't really need to handle FAILED (it is only supposed
321 // to be preceded by CHECKING so we wouldn't yet have sent a
322 // lifetime message) but we might as well use belt and
323 // suspenders and handle it the same as the other "end call"
324 // states. It will be ignored anyway if the call is not
325 // already connected.
326 case PeerConnectionInterface::kIceConnectionNew
:
327 // It's a bit weird to count NEW as an end-lifetime event, but
328 // it's possible to transition directly from a connected state
329 // (CONNECTED or COMPLETED) to NEW, which can then be followed
330 // by a new connection. The observer will ignore the end
331 // lifetime event if it was not preceded by a begin-lifetime
333 case PeerConnectionInterface::kIceConnectionDisconnected
:
334 case PeerConnectionInterface::kIceConnectionClosed
:
335 observer
->SendLifetimeMessages(DISCONNECTED
);
339 // We ignore the remaining state (CHECKING) as it is never
340 // involved in a transition from connected to disconnected or
346 void MediaStreamTrackMetrics::SendLifetimeMessage(const std::string
& track_id
,
347 TrackType track_type
,
349 StreamType stream_type
) {
350 RenderThreadImpl
* render_thread
= RenderThreadImpl::current();
351 // |render_thread| can be NULL in certain cases when running as part
354 if (event
== CONNECTED
) {
355 RenderThreadImpl::current()->Send(
356 new MediaStreamTrackMetricsHost_AddTrack(
357 MakeUniqueId(track_id
, stream_type
),
358 track_type
== AUDIO_TRACK
,
359 stream_type
== RECEIVED_STREAM
));
361 DCHECK_EQ(DISCONNECTED
, event
);
362 RenderThreadImpl::current()->Send(
363 new MediaStreamTrackMetricsHost_RemoveTrack(
364 MakeUniqueId(track_id
, stream_type
)));
369 uint64
MediaStreamTrackMetrics::MakeUniqueIdImpl(uint64 pc_id
,
370 const std::string
& track_id
,
371 StreamType stream_type
) {
372 // We use a hash over the |track| pointer and the PeerConnection ID,
373 // plus a boolean flag indicating whether the track is remote (since
374 // you might conceivably have a remote track added back as a sent
375 // track) as the unique ID.
377 // We don't need a cryptographically secure hash (which MD5 should
378 // no longer be considered), just one with virtually zero chance of
379 // collisions when faced with non-malicious data.
380 std::string unique_id_string
=
381 base::StringPrintf("%" PRIu64
" %s %d",
384 stream_type
== RECEIVED_STREAM
? 1 : 0);
386 base::MD5Context ctx
;
388 base::MD5Update(&ctx
, unique_id_string
);
389 base::MD5Digest digest
;
390 base::MD5Final(&digest
, &ctx
);
392 static_assert(sizeof(digest
.a
) > sizeof(uint64
), "need a bigger digest");
393 return *reinterpret_cast<uint64
*>(digest
.a
);
396 uint64
MediaStreamTrackMetrics::MakeUniqueId(const std::string
& track_id
,
397 StreamType stream_type
) {
398 return MakeUniqueIdImpl(
399 reinterpret_cast<uint64
>(reinterpret_cast<void*>(this)),
404 } // namespace content