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
16using webrtc::AudioTrackVector;
17using webrtc::MediaStreamInterface;
18using webrtc::MediaStreamTrackInterface;
19using webrtc::PeerConnectionInterface;
20using webrtc::VideoTrackVector;
21
22namespace content {
23
24class 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
81namespace {
82
83// Used with std::find_if.
84struct 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
98MediaStreamTrackMetricsObserver::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
111MediaStreamTrackMetricsObserver::~MediaStreamTrackMetricsObserver() {
112  stream_->UnregisterObserver(this);
113  SendLifetimeMessages(MediaStreamTrackMetrics::DISCONNECTED);
114}
115
116void 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
149void 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
172void 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
186void 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
195MediaStreamTrackMetrics::MediaStreamTrackMetrics()
196    : ice_state_(webrtc::PeerConnectionInterface::kIceConnectionNew) {}
197
198MediaStreamTrackMetrics::~MediaStreamTrackMetrics() {
199  for (ObserverVector::iterator it = observers_.begin(); it != observers_.end();
200       ++it) {
201    (*it)->SendLifetimeMessages(DISCONNECTED);
202  }
203}
204
205void 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
214void 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
228void 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}
237void 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
274void 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
297uint64 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
324uint64 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