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 35 MediaStreamInterface* stream() { return stream_; } 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> 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 { 85 ObserverFinder(MediaStreamTrackMetrics::StreamType stream_type, 86 MediaStreamInterface* stream) 87 : stream_type(stream_type), stream_(stream) {} 88 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 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 111 MediaStreamTrackMetricsObserver::~MediaStreamTrackMetricsObserver() { 112 stream_->UnregisterObserver(this); 113 SendLifetimeMessages(MediaStreamTrackMetrics::DISCONNECTED); 114 } 115 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 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 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 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 195 MediaStreamTrackMetrics::MediaStreamTrackMetrics() 196 : ice_state_(webrtc::PeerConnectionInterface::kIceConnectionNew) {} 197 198 MediaStreamTrackMetrics::~MediaStreamTrackMetrics() { 199 for (ObserverVector::iterator it = observers_.begin(); it != observers_.end(); 200 ++it) { 201 (*it)->SendLifetimeMessages(DISCONNECTED); 202 } 203 } 204 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 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 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 } 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 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 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 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