blob: bf074edfbd63e8cdbf4dc52d1d1b59375a8a203d [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/media/webrtc/media_stream_track_metrics.h"
#include <inttypes.h>
#include <set>
#include <string>
#include "base/md5.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/common/media/media_stream_track_metrics_host_messages.h"
#include "content/renderer/render_thread_impl.h"
#include "third_party/webrtc/api/mediastreaminterface.h"
using webrtc::AudioTrackVector;
using webrtc::MediaStreamInterface;
using webrtc::MediaStreamTrackInterface;
using webrtc::PeerConnectionInterface;
using webrtc::VideoTrackVector;
namespace content {
namespace {
typedef std::set<std::string> IdSet;
template <class T>
IdSet GetTrackIds(const std::vector<rtc::scoped_refptr<T>>& tracks) {
IdSet track_ids;
for (const auto& track : tracks)
track_ids.insert(track->id());
return track_ids;
}
// TODO(tommi): Consolidate this and TrackObserver since these implementations
// are fundamentally achieving the same thing (aside from specific logic inside
// the OnChanged callbacks).
class MediaStreamObserver
: public base::RefCountedThreadSafe<MediaStreamObserver>,
public webrtc::ObserverInterface {
public:
typedef base::Callback<
void(const IdSet& audio_track_ids, const IdSet& video_track_ids)>
OnChangedCallback;
MediaStreamObserver(
const OnChangedCallback& callback,
const scoped_refptr<base::SingleThreadTaskRunner>& main_thread,
webrtc::MediaStreamInterface* stream)
: main_thread_(main_thread), stream_(stream), callback_(callback) {
signaling_thread_.DetachFromThread();
stream_->RegisterObserver(this);
}
const scoped_refptr<webrtc::MediaStreamInterface>& stream() const {
DCHECK(main_thread_->BelongsToCurrentThread());
return stream_;
}
void Unregister() {
DCHECK(main_thread_->BelongsToCurrentThread());
callback_.Reset();
stream_->UnregisterObserver(this);
stream_ = nullptr;
}
private:
friend class base::RefCountedThreadSafe<MediaStreamObserver>;
~MediaStreamObserver() override {
DCHECK(!stream_.get()) << "must have been unregistered before deleting";
}
// webrtc::ObserverInterface implementation.
void OnChanged() override {
DCHECK(signaling_thread_.CalledOnValidThread());
main_thread_->PostTask(FROM_HERE,
base::Bind(&MediaStreamObserver::OnChangedOnMainThread, this,
GetTrackIds(stream_->GetAudioTracks()),
GetTrackIds(stream_->GetVideoTracks())));
}
void OnChangedOnMainThread(const IdSet& audio_track_ids,
const IdSet& video_track_ids) {
DCHECK(main_thread_->BelongsToCurrentThread());
if (!callback_.is_null())
callback_.Run(audio_track_ids, video_track_ids);
}
const scoped_refptr<base::SingleThreadTaskRunner> main_thread_;
scoped_refptr<webrtc::MediaStreamInterface> stream_;
OnChangedCallback callback_; // Only touched on the main thread.
base::ThreadChecker signaling_thread_;
};
} // namespace
class MediaStreamTrackMetricsObserver {
public:
MediaStreamTrackMetricsObserver(
MediaStreamTrackMetrics::StreamType stream_type,
MediaStreamInterface* stream,
MediaStreamTrackMetrics* owner);
~MediaStreamTrackMetricsObserver();
// Sends begin/end messages for all tracks currently tracked.
void SendLifetimeMessages(MediaStreamTrackMetrics::LifetimeEvent event);
MediaStreamInterface* stream() {
DCHECK(thread_checker_.CalledOnValidThread());
return observer_->stream().get();
}
MediaStreamTrackMetrics::StreamType stream_type() {
DCHECK(thread_checker_.CalledOnValidThread());
return stream_type_;
}
private:
void OnChanged(const IdSet& audio_track_ids, const IdSet& video_track_ids);
void ReportAddedAndRemovedTracks(
const IdSet& new_ids,
const IdSet& old_ids,
MediaStreamTrackMetrics::TrackType track_type);
// Sends a lifetime message for the given tracks. OK to call with an
// empty |ids|, in which case the method has no side effects.
void ReportTracks(const IdSet& ids,
MediaStreamTrackMetrics::TrackType track_type,
MediaStreamTrackMetrics::LifetimeEvent event);
// False until start/end of lifetime messages have been sent.
bool has_reported_start_;
bool has_reported_end_;
// IDs of audio and video tracks in the stream being observed.
IdSet audio_track_ids_;
IdSet video_track_ids_;
MediaStreamTrackMetrics::StreamType stream_type_;
scoped_refptr<MediaStreamObserver> observer_;
// Non-owning.
MediaStreamTrackMetrics* owner_;
base::ThreadChecker thread_checker_;
};
namespace {
// Used with std::find_if.
struct ObserverFinder {
ObserverFinder(MediaStreamTrackMetrics::StreamType stream_type,
MediaStreamInterface* stream)
: stream_type(stream_type), stream_(stream) {}
bool operator()(
const std::unique_ptr<MediaStreamTrackMetricsObserver>& observer) {
return stream_ == observer->stream() &&
stream_type == observer->stream_type();
}
MediaStreamTrackMetrics::StreamType stream_type;
MediaStreamInterface* stream_;
};
} // namespace
MediaStreamTrackMetricsObserver::MediaStreamTrackMetricsObserver(
MediaStreamTrackMetrics::StreamType stream_type,
MediaStreamInterface* stream,
MediaStreamTrackMetrics* owner)
: has_reported_start_(false),
has_reported_end_(false),
audio_track_ids_(GetTrackIds(stream->GetAudioTracks())),
video_track_ids_(GetTrackIds(stream->GetVideoTracks())),
stream_type_(stream_type),
observer_(new MediaStreamObserver(
base::Bind(&MediaStreamTrackMetricsObserver::OnChanged,
base::Unretained(this)),
base::ThreadTaskRunnerHandle::Get(),
stream)),
owner_(owner) {
}
MediaStreamTrackMetricsObserver::~MediaStreamTrackMetricsObserver() {
DCHECK(thread_checker_.CalledOnValidThread());
observer_->Unregister();
SendLifetimeMessages(MediaStreamTrackMetrics::DISCONNECTED);
}
void MediaStreamTrackMetricsObserver::SendLifetimeMessages(
MediaStreamTrackMetrics::LifetimeEvent event) {
DCHECK(thread_checker_.CalledOnValidThread());
if (event == MediaStreamTrackMetrics::CONNECTED) {
// Both ICE CONNECTED and COMPLETED can trigger the first
// start-of-life event, so we only report the first.
if (has_reported_start_)
return;
DCHECK(!has_reported_start_ && !has_reported_end_);
has_reported_start_ = true;
} else {
DCHECK(event == MediaStreamTrackMetrics::DISCONNECTED);
// We only report the first end-of-life event, since there are
// several cases where end-of-life can be reached. We also don't
// report end unless we've reported start.
if (has_reported_end_ || !has_reported_start_)
return;
has_reported_end_ = true;
}
ReportTracks(audio_track_ids_, MediaStreamTrackMetrics::AUDIO_TRACK, event);
ReportTracks(video_track_ids_, MediaStreamTrackMetrics::VIDEO_TRACK, event);
if (event == MediaStreamTrackMetrics::DISCONNECTED) {
// After disconnection, we can get reconnected, so we need to
// forget that we've sent lifetime events, while retaining all
// other state.
DCHECK(has_reported_start_ && has_reported_end_);
has_reported_start_ = false;
has_reported_end_ = false;
}
}
void MediaStreamTrackMetricsObserver::OnChanged(
const IdSet& audio_track_ids, const IdSet& video_track_ids) {
DCHECK(thread_checker_.CalledOnValidThread());
// We only report changes after our initial report, and never after
// our last report.
if (has_reported_start_ && !has_reported_end_) {
ReportAddedAndRemovedTracks(audio_track_ids,
audio_track_ids_,
MediaStreamTrackMetrics::AUDIO_TRACK);
ReportAddedAndRemovedTracks(video_track_ids,
video_track_ids_,
MediaStreamTrackMetrics::VIDEO_TRACK);
}
// We always update our sets of tracks.
audio_track_ids_ = audio_track_ids;
video_track_ids_ = video_track_ids;
}
void MediaStreamTrackMetricsObserver::ReportAddedAndRemovedTracks(
const IdSet& new_ids,
const IdSet& old_ids,
MediaStreamTrackMetrics::TrackType track_type) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(has_reported_start_ && !has_reported_end_);
IdSet added_tracks = base::STLSetDifference<IdSet>(new_ids, old_ids);
IdSet removed_tracks = base::STLSetDifference<IdSet>(old_ids, new_ids);
ReportTracks(added_tracks, track_type, MediaStreamTrackMetrics::CONNECTED);
ReportTracks(
removed_tracks, track_type, MediaStreamTrackMetrics::DISCONNECTED);
}
void MediaStreamTrackMetricsObserver::ReportTracks(
const IdSet& ids,
MediaStreamTrackMetrics::TrackType track_type,
MediaStreamTrackMetrics::LifetimeEvent event) {
DCHECK(thread_checker_.CalledOnValidThread());
for (IdSet::const_iterator it = ids.begin(); it != ids.end(); ++it) {
owner_->SendLifetimeMessage(*it, track_type, event, stream_type_);
}
}
MediaStreamTrackMetrics::MediaStreamTrackMetrics()
: ice_state_(webrtc::PeerConnectionInterface::kIceConnectionNew) {}
MediaStreamTrackMetrics::~MediaStreamTrackMetrics() {
for (const auto& observer : observers_) {
observer->SendLifetimeMessages(DISCONNECTED);
}
}
void MediaStreamTrackMetrics::AddStream(StreamType type,
MediaStreamInterface* stream) {
DCHECK(CalledOnValidThread());
observers_.push_back(
base::MakeUnique<MediaStreamTrackMetricsObserver>(type, stream, this));
SendLifeTimeMessageDependingOnIceState(observers_.back().get());
}
void MediaStreamTrackMetrics::RemoveStream(StreamType type,
MediaStreamInterface* stream) {
DCHECK(CalledOnValidThread());
auto it = std::find_if(observers_.begin(), observers_.end(),
ObserverFinder(type, stream));
if (it == observers_.end()) {
// Since external apps could call removeStream with a stream they
// never added, this can happen without it being an error.
return;
}
observers_.erase(it);
}
void MediaStreamTrackMetrics::IceConnectionChange(
PeerConnectionInterface::IceConnectionState new_state) {
DCHECK(CalledOnValidThread());
ice_state_ = new_state;
for (const auto& observer : observers_) {
SendLifeTimeMessageDependingOnIceState(observer.get());
}
}
void MediaStreamTrackMetrics::SendLifeTimeMessageDependingOnIceState(
MediaStreamTrackMetricsObserver* observer) {
// There is a state transition diagram for these states at
// http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCIceConnectionState
switch (ice_state_) {
case PeerConnectionInterface::kIceConnectionConnected:
case PeerConnectionInterface::kIceConnectionCompleted:
observer->SendLifetimeMessages(CONNECTED);
break;
case PeerConnectionInterface::kIceConnectionFailed:
// We don't really need to handle FAILED (it is only supposed
// to be preceded by CHECKING so we wouldn't yet have sent a
// lifetime message) but we might as well use belt and
// suspenders and handle it the same as the other "end call"
// states. It will be ignored anyway if the call is not
// already connected.
case PeerConnectionInterface::kIceConnectionNew:
// It's a bit weird to count NEW as an end-lifetime event, but
// it's possible to transition directly from a connected state
// (CONNECTED or COMPLETED) to NEW, which can then be followed
// by a new connection. The observer will ignore the end
// lifetime event if it was not preceded by a begin-lifetime
// event.
case PeerConnectionInterface::kIceConnectionDisconnected:
case PeerConnectionInterface::kIceConnectionClosed:
observer->SendLifetimeMessages(DISCONNECTED);
break;
default:
// We ignore the remaining state (CHECKING) as it is never
// involved in a transition from connected to disconnected or
// vice versa.
break;
}
}
void MediaStreamTrackMetrics::SendLifetimeMessage(const std::string& track_id,
TrackType track_type,
LifetimeEvent event,
StreamType stream_type) {
RenderThreadImpl* render_thread = RenderThreadImpl::current();
// |render_thread| can be NULL in certain cases when running as part
// |of a unit test.
if (render_thread) {
if (event == CONNECTED) {
RenderThreadImpl::current()->Send(
new MediaStreamTrackMetricsHost_AddTrack(
MakeUniqueId(track_id, stream_type),
track_type == AUDIO_TRACK,
stream_type == RECEIVED_STREAM));
} else {
DCHECK_EQ(DISCONNECTED, event);
RenderThreadImpl::current()->Send(
new MediaStreamTrackMetricsHost_RemoveTrack(
MakeUniqueId(track_id, stream_type)));
}
}
}
uint64_t MediaStreamTrackMetrics::MakeUniqueIdImpl(uint64_t pc_id,
const std::string& track_id,
StreamType stream_type) {
// We use a hash over the |track| pointer and the PeerConnection ID,
// plus a boolean flag indicating whether the track is remote (since
// you might conceivably have a remote track added back as a sent
// track) as the unique ID.
//
// We don't need a cryptographically secure hash (which MD5 should
// no longer be considered), just one with virtually zero chance of
// collisions when faced with non-malicious data.
std::string unique_id_string =
base::StringPrintf("%" PRIu64 " %s %d",
pc_id,
track_id.c_str(),
stream_type == RECEIVED_STREAM ? 1 : 0);
base::MD5Context ctx;
base::MD5Init(&ctx);
base::MD5Update(&ctx, unique_id_string);
base::MD5Digest digest;
base::MD5Final(&digest, &ctx);
static_assert(sizeof(digest.a) > sizeof(uint64_t), "need a bigger digest");
return *reinterpret_cast<uint64_t*>(digest.a);
}
uint64_t MediaStreamTrackMetrics::MakeUniqueId(const std::string& track_id,
StreamType stream_type) {
return MakeUniqueIdImpl(
reinterpret_cast<uint64_t>(reinterpret_cast<void*>(this)), track_id,
stream_type);
}
} // namespace content