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.
4
5 #include "content/renderer/media/webrtc/media_stream_track_metrics.h"
6
7 #include <inttypes.h>
8 #include <set>
9 #include <string>
10
11 #include "base/md5.h"
12 #include "content/common/media/media_stream_track_metrics_host_messages.h"
13 #include "content/renderer/render_thread_impl.h"
14 #include "third_party/libjingle/source/talk/app/webrtc/mediastreaminterface.h"
15
16 using webrtc::AudioTrackVector;
17 using webrtc::MediaStreamInterface;
18 using webrtc::MediaStreamTrackInterface;
19 using webrtc::PeerConnectionInterface;
20 using webrtc::VideoTrackVector;
21
22 namespace content {
23
24 class MediaStreamTrackMetricsObserver : public webrtc::ObserverInterface {
25 public:
26 MediaStreamTrackMetricsObserver(
27 MediaStreamTrackMetrics::StreamType stream_type,
28 MediaStreamInterface* stream,
29 MediaStreamTrackMetrics* owner);
30 virtual ~MediaStreamTrackMetricsObserver();
31
32 // Sends begin/end messages for all tracks currently tracked.
33 void SendLifetimeMessages(MediaStreamTrackMetrics::LifetimeEvent event);
34
stream()35 MediaStreamInterface* stream() { return stream_; }
stream_type()36 MediaStreamTrackMetrics::StreamType stream_type() { return stream_type_; }
37
38 private:
39 typedef std::set<std::string> IdSet;
40
41 // webrtc::ObserverInterface implementation.
42 virtual void OnChanged() OVERRIDE;
43
44 template <class T>
GetTrackIds(const std::vector<rtc::scoped_refptr<T>> & tracks)45 IdSet GetTrackIds(const std::vector<rtc::scoped_refptr<T> >& tracks) {
46 IdSet track_ids;
47 typename std::vector<rtc::scoped_refptr<T> >::const_iterator it =
48 tracks.begin();
49 for (; it != tracks.end(); ++it) {
50 track_ids.insert((*it)->id());
51 }
52 return track_ids;
53 }
54
55 void ReportAddedAndRemovedTracks(
56 const IdSet& new_ids,
57 const IdSet& old_ids,
58 MediaStreamTrackMetrics::TrackType track_type);
59
60 // Sends a lifetime message for the given tracks. OK to call with an
61 // empty |ids|, in which case the method has no side effects.
62 void ReportTracks(const IdSet& ids,
63 MediaStreamTrackMetrics::TrackType track_type,
64 MediaStreamTrackMetrics::LifetimeEvent event);
65
66 // False until start/end of lifetime messages have been sent.
67 bool has_reported_start_;
68 bool has_reported_end_;
69
70 // IDs of audio and video tracks in the stream being observed.
71 IdSet audio_track_ids_;
72 IdSet video_track_ids_;
73
74 MediaStreamTrackMetrics::StreamType stream_type_;
75 rtc::scoped_refptr<MediaStreamInterface> stream_;
76
77 // Non-owning.
78 MediaStreamTrackMetrics* owner_;
79 };
80
81 namespace {
82
83 // Used with std::find_if.
84 struct ObserverFinder {
ObserverFindercontent::__anon858bd2e00111::ObserverFinder85 ObserverFinder(MediaStreamTrackMetrics::StreamType stream_type,
86 MediaStreamInterface* stream)
87 : stream_type(stream_type), stream_(stream) {}
operator ()content::__anon858bd2e00111::ObserverFinder88 bool operator()(MediaStreamTrackMetricsObserver* observer) {
89 return stream_ == observer->stream() &&
90 stream_type == observer->stream_type();
91 }
92 MediaStreamTrackMetrics::StreamType stream_type;
93 MediaStreamInterface* stream_;
94 };
95
96 } // namespace
97
MediaStreamTrackMetricsObserver(MediaStreamTrackMetrics::StreamType stream_type,MediaStreamInterface * stream,MediaStreamTrackMetrics * owner)98 MediaStreamTrackMetricsObserver::MediaStreamTrackMetricsObserver(
99 MediaStreamTrackMetrics::StreamType stream_type,
100 MediaStreamInterface* stream,
101 MediaStreamTrackMetrics* owner)
102 : has_reported_start_(false),
103 has_reported_end_(false),
104 stream_type_(stream_type),
105 stream_(stream),
106 owner_(owner) {
107 OnChanged(); // To populate initial tracks.
108 stream_->RegisterObserver(this);
109 }
110
~MediaStreamTrackMetricsObserver()111 MediaStreamTrackMetricsObserver::~MediaStreamTrackMetricsObserver() {
112 stream_->UnregisterObserver(this);
113 SendLifetimeMessages(MediaStreamTrackMetrics::DISCONNECTED);
114 }
115
SendLifetimeMessages(MediaStreamTrackMetrics::LifetimeEvent event)116 void MediaStreamTrackMetricsObserver::SendLifetimeMessages(
117 MediaStreamTrackMetrics::LifetimeEvent event) {
118 if (event == MediaStreamTrackMetrics::CONNECTED) {
119 // Both ICE CONNECTED and COMPLETED can trigger the first
120 // start-of-life event, so we only report the first.
121 if (has_reported_start_)
122 return;
123 DCHECK(!has_reported_start_ && !has_reported_end_);
124 has_reported_start_ = true;
125 } else {
126 DCHECK(event == MediaStreamTrackMetrics::DISCONNECTED);
127
128 // We only report the first end-of-life event, since there are
129 // several cases where end-of-life can be reached. We also don't
130 // report end unless we've reported start.
131 if (has_reported_end_ || !has_reported_start_)
132 return;
133 has_reported_end_ = true;
134 }
135
136 ReportTracks(audio_track_ids_, MediaStreamTrackMetrics::AUDIO_TRACK, event);
137 ReportTracks(video_track_ids_, MediaStreamTrackMetrics::VIDEO_TRACK, event);
138
139 if (event == MediaStreamTrackMetrics::DISCONNECTED) {
140 // After disconnection, we can get reconnected, so we need to
141 // forget that we've sent lifetime events, while retaining all
142 // other state.
143 DCHECK(has_reported_start_ && has_reported_end_);
144 has_reported_start_ = false;
145 has_reported_end_ = false;
146 }
147 }
148
OnChanged()149 void MediaStreamTrackMetricsObserver::OnChanged() {
150 AudioTrackVector all_audio_tracks = stream_->GetAudioTracks();
151 IdSet all_audio_track_ids = GetTrackIds(all_audio_tracks);
152
153 VideoTrackVector all_video_tracks = stream_->GetVideoTracks();
154 IdSet all_video_track_ids = GetTrackIds(all_video_tracks);
155
156 // We only report changes after our initial report, and never after
157 // our last report.
158 if (has_reported_start_ && !has_reported_end_) {
159 ReportAddedAndRemovedTracks(all_audio_track_ids,
160 audio_track_ids_,
161 MediaStreamTrackMetrics::AUDIO_TRACK);
162 ReportAddedAndRemovedTracks(all_video_track_ids,
163 video_track_ids_,
164 MediaStreamTrackMetrics::VIDEO_TRACK);
165 }
166
167 // We always update our sets of tracks.
168 audio_track_ids_ = all_audio_track_ids;
169 video_track_ids_ = all_video_track_ids;
170 }
171
ReportAddedAndRemovedTracks(const IdSet & new_ids,const IdSet & old_ids,MediaStreamTrackMetrics::TrackType track_type)172 void MediaStreamTrackMetricsObserver::ReportAddedAndRemovedTracks(
173 const IdSet& new_ids,
174 const IdSet& old_ids,
175 MediaStreamTrackMetrics::TrackType track_type) {
176 DCHECK(has_reported_start_ && !has_reported_end_);
177
178 IdSet added_tracks = base::STLSetDifference<IdSet>(new_ids, old_ids);
179 IdSet removed_tracks = base::STLSetDifference<IdSet>(old_ids, new_ids);
180
181 ReportTracks(added_tracks, track_type, MediaStreamTrackMetrics::CONNECTED);
182 ReportTracks(
183 removed_tracks, track_type, MediaStreamTrackMetrics::DISCONNECTED);
184 }
185
ReportTracks(const IdSet & ids,MediaStreamTrackMetrics::TrackType track_type,MediaStreamTrackMetrics::LifetimeEvent event)186 void MediaStreamTrackMetricsObserver::ReportTracks(
187 const IdSet& ids,
188 MediaStreamTrackMetrics::TrackType track_type,
189 MediaStreamTrackMetrics::LifetimeEvent event) {
190 for (IdSet::const_iterator it = ids.begin(); it != ids.end(); ++it) {
191 owner_->SendLifetimeMessage(*it, track_type, event, stream_type_);
192 }
193 }
194
MediaStreamTrackMetrics()195 MediaStreamTrackMetrics::MediaStreamTrackMetrics()
196 : ice_state_(webrtc::PeerConnectionInterface::kIceConnectionNew) {}
197
~MediaStreamTrackMetrics()198 MediaStreamTrackMetrics::~MediaStreamTrackMetrics() {
199 for (ObserverVector::iterator it = observers_.begin(); it != observers_.end();
200 ++it) {
201 (*it)->SendLifetimeMessages(DISCONNECTED);
202 }
203 }
204
AddStream(StreamType type,MediaStreamInterface * stream)205 void MediaStreamTrackMetrics::AddStream(StreamType type,
206 MediaStreamInterface* stream) {
207 DCHECK(CalledOnValidThread());
208 MediaStreamTrackMetricsObserver* observer =
209 new MediaStreamTrackMetricsObserver(type, stream, this);
210 observers_.insert(observers_.end(), observer);
211 SendLifeTimeMessageDependingOnIceState(observer);
212 }
213
RemoveStream(StreamType type,MediaStreamInterface * stream)214 void MediaStreamTrackMetrics::RemoveStream(StreamType type,
215 MediaStreamInterface* stream) {
216 DCHECK(CalledOnValidThread());
217 ObserverVector::iterator it = std::find_if(
218 observers_.begin(), observers_.end(), ObserverFinder(type, stream));
219 if (it == observers_.end()) {
220 // Since external apps could call removeStream with a stream they
221 // never added, this can happen without it being an error.
222 return;
223 }
224
225 observers_.erase(it);
226 }
227
IceConnectionChange(PeerConnectionInterface::IceConnectionState new_state)228 void MediaStreamTrackMetrics::IceConnectionChange(
229 PeerConnectionInterface::IceConnectionState new_state) {
230 DCHECK(CalledOnValidThread());
231 ice_state_ = new_state;
232 for (ObserverVector::iterator it = observers_.begin(); it != observers_.end();
233 ++it) {
234 SendLifeTimeMessageDependingOnIceState(*it);
235 }
236 }
SendLifeTimeMessageDependingOnIceState(MediaStreamTrackMetricsObserver * observer)237 void MediaStreamTrackMetrics::SendLifeTimeMessageDependingOnIceState(
238 MediaStreamTrackMetricsObserver* observer) {
239 // There is a state transition diagram for these states at
240 // http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCIceConnectionState
241 switch (ice_state_) {
242 case PeerConnectionInterface::kIceConnectionConnected:
243 case PeerConnectionInterface::kIceConnectionCompleted:
244 observer->SendLifetimeMessages(CONNECTED);
245 break;
246
247 case PeerConnectionInterface::kIceConnectionFailed:
248 // We don't really need to handle FAILED (it is only supposed
249 // to be preceded by CHECKING so we wouldn't yet have sent a
250 // lifetime message) but we might as well use belt and
251 // suspenders and handle it the same as the other "end call"
252 // states. It will be ignored anyway if the call is not
253 // already connected.
254 case PeerConnectionInterface::kIceConnectionNew:
255 // It's a bit weird to count NEW as an end-lifetime event, but
256 // it's possible to transition directly from a connected state
257 // (CONNECTED or COMPLETED) to NEW, which can then be followed
258 // by a new connection. The observer will ignore the end
259 // lifetime event if it was not preceded by a begin-lifetime
260 // event.
261 case PeerConnectionInterface::kIceConnectionDisconnected:
262 case PeerConnectionInterface::kIceConnectionClosed:
263 observer->SendLifetimeMessages(DISCONNECTED);
264 break;
265
266 default:
267 // We ignore the remaining state (CHECKING) as it is never
268 // involved in a transition from connected to disconnected or
269 // vice versa.
270 break;
271 }
272 }
273
SendLifetimeMessage(const std::string & track_id,TrackType track_type,LifetimeEvent event,StreamType stream_type)274 void MediaStreamTrackMetrics::SendLifetimeMessage(const std::string& track_id,
275 TrackType track_type,
276 LifetimeEvent event,
277 StreamType stream_type) {
278 RenderThreadImpl* render_thread = RenderThreadImpl::current();
279 // |render_thread| can be NULL in certain cases when running as part
280 // |of a unit test.
281 if (render_thread) {
282 if (event == CONNECTED) {
283 RenderThreadImpl::current()->Send(
284 new MediaStreamTrackMetricsHost_AddTrack(
285 MakeUniqueId(track_id, stream_type),
286 track_type == AUDIO_TRACK,
287 stream_type == RECEIVED_STREAM));
288 } else {
289 DCHECK_EQ(DISCONNECTED, event);
290 RenderThreadImpl::current()->Send(
291 new MediaStreamTrackMetricsHost_RemoveTrack(
292 MakeUniqueId(track_id, stream_type)));
293 }
294 }
295 }
296
MakeUniqueIdImpl(uint64 pc_id,const std::string & track_id,StreamType stream_type)297 uint64 MediaStreamTrackMetrics::MakeUniqueIdImpl(uint64 pc_id,
298 const std::string& track_id,
299 StreamType stream_type) {
300 // We use a hash over the |track| pointer and the PeerConnection ID,
301 // plus a boolean flag indicating whether the track is remote (since
302 // you might conceivably have a remote track added back as a sent
303 // track) as the unique ID.
304 //
305 // We don't need a cryptographically secure hash (which MD5 should
306 // no longer be considered), just one with virtually zero chance of
307 // collisions when faced with non-malicious data.
308 std::string unique_id_string =
309 base::StringPrintf("%" PRIu64 " %s %d",
310 pc_id,
311 track_id.c_str(),
312 stream_type == RECEIVED_STREAM ? 1 : 0);
313
314 base::MD5Context ctx;
315 base::MD5Init(&ctx);
316 base::MD5Update(&ctx, unique_id_string);
317 base::MD5Digest digest;
318 base::MD5Final(&digest, &ctx);
319
320 COMPILE_ASSERT(sizeof(digest.a) > sizeof(uint64), NeedBiggerDigest);
321 return *reinterpret_cast<uint64*>(digest.a);
322 }
323
MakeUniqueId(const std::string & track_id,StreamType stream_type)324 uint64 MediaStreamTrackMetrics::MakeUniqueId(const std::string& track_id,
325 StreamType stream_type) {
326 return MakeUniqueIdImpl(
327 reinterpret_cast<uint64>(reinterpret_cast<void*>(this)),
328 track_id,
329 stream_type);
330 }
331
332 } // namespace content
333