Index: media/remoting/remote_renderer_impl.cc |
diff --git a/media/remoting/remote_renderer_impl.cc b/media/remoting/remote_renderer_impl.cc |
index 4e7112ab29c136ea55cc530d219e64306f6642ee..d06c7095e4660c07be3d8d6d86d498db7d9dfc72 100644 |
--- a/media/remoting/remote_renderer_impl.cc |
+++ b/media/remoting/remote_renderer_impl.cc |
@@ -5,6 +5,7 @@ |
#include "media/remoting/remote_renderer_impl.h" |
#include <algorithm> |
+#include <limits> |
#include <utility> |
#include "base/bind.h" |
@@ -12,6 +13,7 @@ |
#include "base/callback_helpers.h" |
#include "base/memory/ptr_util.h" |
#include "base/message_loop/message_loop.h" |
+#include "base/numerics/safe_math.h" |
#include "base/threading/thread_task_runner_handle.h" |
#include "base/time/time.h" |
#include "media/base/bind_to_current_loop.h" |
@@ -40,7 +42,13 @@ constexpr int kMaxNumVideoFramesDroppedPercentage = 3; |
// Flush(). |
constexpr base::TimeDelta kStabilizationPeriod = |
base::TimeDelta::FromSeconds(2); |
-} |
+ |
+// The amount of time between polling the DemuxerStreamAdapters to measure their |
+// data flow rates for metrics. |
+constexpr base::TimeDelta kDataFlowPollPeriod = |
+ base::TimeDelta::FromSeconds(10); |
+ |
+} // namespace |
namespace media { |
@@ -102,8 +110,13 @@ void RemoteRendererImpl::Initialize( |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
DCHECK(demuxer_stream_provider); |
DCHECK(client); |
- DCHECK(media_task_runner_->RunsTasksOnCurrentThread()); |
- DCHECK_EQ(state_, STATE_UNINITIALIZED); |
+ |
+ if (state_ != STATE_UNINITIALIZED) { |
+ media_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(init_cb, PIPELINE_ERROR_INVALID_STATE)); |
+ return; |
+ } |
+ |
demuxer_stream_provider_ = demuxer_stream_provider; |
client_ = client; |
init_workflow_done_callback_ = init_cb; |
@@ -155,6 +168,10 @@ void RemoteRendererImpl::Flush(const base::Closure& flush_cb) { |
if (state_ != STATE_PLAYING) { |
DCHECK_EQ(state_, STATE_ERROR); |
+ // In the error state, this renderer will be shut down shortly. To prevent |
+ // breaking the pipeline impl, just run the done callback (interface |
+ // requirement). |
+ media_task_runner_->PostTask(FROM_HERE, flush_cb); |
return; |
} |
@@ -217,15 +234,17 @@ void RemoteRendererImpl::StartPlayingFrom(base::TimeDelta time) { |
base::AutoLock auto_lock(time_lock_); |
current_media_time_ = time; |
} |
- ResetQueues(); |
+ ResetMeasurements(); |
} |
void RemoteRendererImpl::SetPlaybackRate(double playback_rate) { |
VLOG(2) << __func__ << ": " << playback_rate; |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
- if (state_ != STATE_PLAYING) |
+ if (state_ != STATE_FLUSHING && state_ != STATE_PLAYING) { |
+ DCHECK_EQ(state_, STATE_ERROR); |
return; |
+ } |
// Issues RPC_R_SETPLAYBACKRATE RPC message. |
std::unique_ptr<remoting::pb::RpcMessage> rpc(new remoting::pb::RpcMessage()); |
@@ -236,13 +255,18 @@ void RemoteRendererImpl::SetPlaybackRate(double playback_rate) { |
<< " with rate=" << rpc->double_value(); |
SendRpcToRemote(std::move(rpc)); |
playback_rate_ = playback_rate; |
- ResetQueues(); |
+ ResetMeasurements(); |
} |
void RemoteRendererImpl::SetVolume(float volume) { |
VLOG(2) << __func__ << ": " << volume; |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
+ if (state_ != STATE_FLUSHING && state_ != STATE_PLAYING) { |
+ DCHECK_EQ(state_, STATE_ERROR); |
+ return; |
+ } |
+ |
// Issues RPC_R_SETVOLUME RPC message. |
std::unique_ptr<remoting::pb::RpcMessage> rpc(new remoting::pb::RpcMessage()); |
rpc->set_handle(remote_renderer_handle_); |
@@ -294,6 +318,9 @@ void RemoteRendererImpl::OnDataPipeCreated( |
VLOG(2) << __func__; |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
DCHECK(!init_workflow_done_callback_.is_null()); |
+ |
+ if (state_ == STATE_ERROR) |
+ return; // Abort because something went wrong in the meantime. |
DCHECK_EQ(state_, STATE_CREATE_PIPE); |
// Create audio demuxer stream adapter if audio is available. |
@@ -306,7 +333,9 @@ void RemoteRendererImpl::OnDataPipeCreated( |
new remoting::RemoteDemuxerStreamAdapter( |
main_task_runner_, media_task_runner_, "audio", |
audio_demuxer_stream, rpc_broker_, audio_rpc_handle, |
- std::move(audio), std::move(audio_handle))); |
+ std::move(audio), std::move(audio_handle), |
+ base::Bind(&RemoteRendererImpl::OnFatalError, |
+ base::Unretained(this)))); |
} |
// Create video demuxer stream adapter if video is available. |
@@ -319,12 +348,14 @@ void RemoteRendererImpl::OnDataPipeCreated( |
new remoting::RemoteDemuxerStreamAdapter( |
main_task_runner_, media_task_runner_, "video", |
video_demuxer_stream, rpc_broker_, video_rpc_handle, |
- std::move(video), std::move(video_handle))); |
+ std::move(video), std::move(video_handle), |
+ base::Bind(&RemoteRendererImpl::OnFatalError, |
+ base::Unretained(this)))); |
} |
// Checks if data pipe is created successfully. |
if (!audio_demuxer_stream_adapter_ && !video_demuxer_stream_adapter_) { |
- OnFatalError(PIPELINE_ERROR_ABORT); |
+ OnFatalError(remoting::DATA_PIPE_CREATE_ERROR); |
return; |
} |
@@ -378,7 +409,7 @@ void RemoteRendererImpl::OnReceivedRpc( |
break; |
case remoting::pb::RpcMessage::RPC_RC_ONERROR: |
VLOG(2) << __func__ << ": Received RPC_RC_ONERROR."; |
- OnFatalError(PIPELINE_ERROR_DECODE); |
+ OnFatalError(remoting::RECEIVER_PIPELINE_ERROR); |
break; |
case remoting::pb::RpcMessage::RPC_RC_ONVIDEONATURALSIZECHANGE: |
OnVideoNaturalSizeChange(std::move(message)); |
@@ -415,16 +446,17 @@ void RemoteRendererImpl::AcquireRendererDone( |
std::unique_ptr<remoting::pb::RpcMessage> message) { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
DCHECK(message); |
- DCHECK(!init_workflow_done_callback_.is_null()); |
- if (state_ != STATE_ACQUIRING || init_workflow_done_callback_.is_null()) { |
- VLOG(1) << "Unexpected acquire renderer done RPC. Shutting down."; |
- OnFatalError(PIPELINE_ERROR_ABORT); |
- return; |
- } |
+ |
remote_renderer_handle_ = message->integer_value(); |
VLOG(2) << __func__ |
<< ": Received RPC_ACQUIRE_RENDERER_DONE with remote_renderer_handle=" |
<< remote_renderer_handle_; |
+ |
+ if (state_ != STATE_ACQUIRING || init_workflow_done_callback_.is_null()) { |
+ LOG(WARNING) << "Unexpected acquire renderer done RPC."; |
+ OnFatalError(remoting::PEERS_OUT_OF_SYNC); |
+ return; |
+ } |
state_ = STATE_INITIALIZING; |
// Issues RPC_R_INITIALIZE RPC message to initialize renderer. |
@@ -455,40 +487,45 @@ void RemoteRendererImpl::InitializeCallback( |
std::unique_ptr<remoting::pb::RpcMessage> message) { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
DCHECK(message); |
- DCHECK(!init_workflow_done_callback_.is_null()); |
- if (state_ != STATE_INITIALIZING || init_workflow_done_callback_.is_null()) { |
- VLOG(1) << "Unexpected initialize callback RPC. Shutting down."; |
- OnFatalError(PIPELINE_ERROR_ABORT); |
- return; |
- } |
const bool success = message->boolean_value(); |
VLOG(2) << __func__ |
<< ": Received RPC_R_INITIALIZE_CALLBACK with success=" << success; |
+ |
+ if (state_ != STATE_INITIALIZING || init_workflow_done_callback_.is_null()) { |
+ LOG(WARNING) << "Unexpected initialize callback RPC."; |
+ OnFatalError(remoting::PEERS_OUT_OF_SYNC); |
+ return; |
+ } |
+ |
if (!success) { |
- OnFatalError(PIPELINE_ERROR_ABORT); |
+ OnFatalError(remoting::RECEIVER_INITIALIZE_FAILED); |
return; |
} |
+ metrics_recorder_.OnRendererInitialized(); |
+ |
state_ = STATE_PLAYING; |
base::ResetAndReturn(&init_workflow_done_callback_).Run(::media::PIPELINE_OK); |
} |
void RemoteRendererImpl::FlushUntilCallback() { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
+ VLOG(2) << __func__ << ": Received RPC_R_FLUSHUNTIL_CALLBACK"; |
+ |
if (state_ != STATE_FLUSHING || flush_cb_.is_null()) { |
- VLOG(1) << "Unexpected flushuntil callback RPC. Shutting down."; |
- OnFatalError(PIPELINE_ERROR_ABORT); |
+ LOG(WARNING) << "Unexpected flushuntil callback RPC."; |
+ OnFatalError(remoting::PEERS_OUT_OF_SYNC); |
return; |
} |
- VLOG(2) << __func__ << ": Received RPC_R_FLUSHUNTIL_CALLBACK"; |
+ |
state_ = STATE_PLAYING; |
if (audio_demuxer_stream_adapter_) |
audio_demuxer_stream_adapter_->SignalFlush(false); |
if (video_demuxer_stream_adapter_) |
video_demuxer_stream_adapter_->SignalFlush(false); |
base::ResetAndReturn(&flush_cb_).Run(); |
- ResetQueues(); |
+ ResetMeasurements(); |
} |
void RemoteRendererImpl::SetCdmCallback( |
@@ -509,7 +546,7 @@ void RemoteRendererImpl::OnTimeUpdate( |
// Shutdown remoting session if receiving malformed RPC message. |
if (!message->has_rendererclient_ontimeupdate_rpc()) { |
VLOG(1) << __func__ << " missing required RPC message"; |
- OnFatalError(PIPELINE_ERROR_ABORT); |
+ OnFatalError(remoting::RPC_INVALID); |
return; |
} |
const int64_t time_usec = |
@@ -531,6 +568,7 @@ void RemoteRendererImpl::OnTimeUpdate( |
current_max_time_ = base::TimeDelta::FromMicroseconds(max_time_usec); |
} |
+ metrics_recorder_.OnEvidenceOfPlayoutAtReceiver(); |
OnMediaTimeUpdated(); |
} |
@@ -540,7 +578,7 @@ void RemoteRendererImpl::OnBufferingStateChange( |
DCHECK(message); |
if (!message->has_rendererclient_onbufferingstatechange_rpc()) { |
VLOG(1) << __func__ << " missing required RPC message"; |
- OnFatalError(PIPELINE_ERROR_ABORT); |
+ OnFatalError(remoting::RPC_INVALID); |
return; |
} |
VLOG(2) << __func__ << ": Received RPC_RC_ONBUFFERINGSTATECHANGE with state=" |
@@ -560,7 +598,7 @@ void RemoteRendererImpl::OnVideoNaturalSizeChange( |
// Shutdown remoting session if receiving malformed RPC message. |
if (!message->has_rendererclient_onvideonatualsizechange_rpc()) { |
VLOG(1) << __func__ << " missing required RPC message"; |
- OnFatalError(PIPELINE_ERROR_ABORT); |
+ OnFatalError(remoting::RPC_INVALID); |
return; |
} |
const auto& size_change = |
@@ -590,7 +628,7 @@ void RemoteRendererImpl::OnStatisticsUpdate( |
// Shutdown remoting session if receiving malformed RPC message. |
if (!message->has_rendererclient_onstatisticsupdate_rpc()) { |
VLOG(1) << __func__ << " missing required RPC message"; |
- OnFatalError(PIPELINE_ERROR_ABORT); |
+ OnFatalError(remoting::RPC_INVALID); |
return; |
} |
const auto& rpc_message = message->rendererclient_onstatisticsupdate_rpc(); |
@@ -611,6 +649,10 @@ void RemoteRendererImpl::OnStatisticsUpdate( |
<< ", audio_memory_usage=" << stats.audio_memory_usage |
<< ", video_memory_usage=" << stats.video_memory_usage; |
+ if (stats.audio_bytes_decoded > 0 || stats.video_frames_decoded > 0 || |
+ stats.video_frames_dropped > 0) { |
+ metrics_recorder_.OnEvidenceOfPlayoutAtReceiver(); |
+ } |
UpdateVideoStatsQueue(stats.video_frames_decoded, stats.video_frames_dropped); |
client_->OnStatisticsUpdate(stats); |
} |
@@ -627,31 +669,31 @@ void RemoteRendererImpl::OnDurationChange( |
base::TimeDelta::FromMicroseconds(message->integer64_value())); |
} |
-void RemoteRendererImpl::OnFatalError(PipelineStatus error) { |
+void RemoteRendererImpl::OnFatalError(remoting::StopTrigger stop_trigger) { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
- DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; |
+ DCHECK_NE(remoting::UNKNOWN_STOP_TRIGGER, stop_trigger); |
- // An error has already been delivered. |
- if (state_ == STATE_ERROR) |
- return; |
+ VLOG(2) << __func__ << " with StopTrigger " << stop_trigger; |
- VLOG(2) << __func__ << " with PipelineStatus error=" << error; |
+ // If this is the first error, notify the controller. It is expected the |
+ // controller will shut down this renderer shortly. |
+ if (state_ != STATE_ERROR) { |
+ state_ = STATE_ERROR; |
+ main_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&RemotingRendererController::OnRendererFatalError, |
+ remoting_renderer_controller_, stop_trigger)); |
+ } |
- const State old_state = state_; |
- state_ = STATE_ERROR; |
+ data_flow_poll_timer_.Stop(); |
if (!init_workflow_done_callback_.is_null()) { |
- DCHECK(old_state == STATE_CREATE_PIPE || old_state == STATE_ACQUIRING || |
- old_state == STATE_INITIALIZING); |
- base::ResetAndReturn(&init_workflow_done_callback_).Run(error); |
+ base::ResetAndReturn(&init_workflow_done_callback_) |
+ .Run(PIPELINE_ERROR_INITIALIZATION_FAILED); |
return; |
} |
if (!flush_cb_.is_null()) |
base::ResetAndReturn(&flush_cb_).Run(); |
- |
- // After OnError() returns, the pipeline may destroy |this|. |
- client_->OnError(error); |
} |
// static |
@@ -705,7 +747,7 @@ void RemoteRendererImpl::OnMediaTimeUpdated() { |
VLOG(1) << "Irregular playback detected: Media playback delayed." |
<< " media_duration = " << media_duration |
<< " update_duration = " << update_duration; |
- OnIrregularPlaybackDetected(); |
+ OnFatalError(remoting::PACING_TOO_SLOWLY); |
} |
// Prune |media_time_queue_|. |
while (media_time_queue_.back().first - media_time_queue_.front().first >= |
@@ -746,7 +788,7 @@ void RemoteRendererImpl::UpdateVideoStatsQueue(int video_frames_decoded, |
VLOG(1) << "Irregular playback detected: Too many video frames dropped." |
<< " video_frames_decoded= " << sum_video_frames_decoded_ |
<< " video_frames_dropped= " << sum_video_frames_dropped_; |
- OnIrregularPlaybackDetected(); |
+ OnFatalError(remoting::FRAME_DROP_RATE_HIGH); |
} |
// Prune |video_stats_queue_|. |
while (std::get<0>(video_stats_queue_.back()) - |
@@ -758,7 +800,7 @@ void RemoteRendererImpl::UpdateVideoStatsQueue(int video_frames_decoded, |
} |
} |
-void RemoteRendererImpl::ResetQueues() { |
+void RemoteRendererImpl::ResetMeasurements() { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
media_time_queue_.clear(); |
video_stats_queue_.clear(); |
@@ -766,14 +808,52 @@ void RemoteRendererImpl::ResetQueues() { |
sum_video_frames_decoded_ = 0; |
stats_updated_ = false; |
ignore_updates_until_time_ = base::TimeTicks::Now() + kStabilizationPeriod; |
+ |
+ if (state_ != STATE_ERROR && |
+ (audio_demuxer_stream_adapter_ || video_demuxer_stream_adapter_)) { |
+ data_flow_poll_timer_.Start(FROM_HERE, kDataFlowPollPeriod, this, |
+ &RemoteRendererImpl::MeasureAndRecordDataRates); |
+ } |
} |
-void RemoteRendererImpl::OnIrregularPlaybackDetected() { |
+void RemoteRendererImpl::MeasureAndRecordDataRates() { |
DCHECK(media_task_runner_->BelongsToCurrentThread()); |
- main_task_runner_->PostTask( |
- FROM_HERE, |
- base::Bind(&RemotingRendererController::OnIrregularPlaybackDetected, |
- remoting_renderer_controller_)); |
+ |
+ // Whenever media is first started or flushed/seeked, there is a "burst |
+ // bufferring" period as the remote device rapidly fills its buffer before |
+ // resuming playback. Since the goal here is to measure the sustained content |
+ // bitrates, ignore the byte counts the first time since the last |
+ // ResetMeasurements() call. |
+ const base::TimeTicks current_time = base::TimeTicks::Now(); |
+ if (current_time < ignore_updates_until_time_ + kDataFlowPollPeriod) { |
+ if (audio_demuxer_stream_adapter_) |
+ audio_demuxer_stream_adapter_->GetBytesWrittenAndReset(); |
+ if (video_demuxer_stream_adapter_) |
+ video_demuxer_stream_adapter_->GetBytesWrittenAndReset(); |
+ return; |
+ } |
+ |
+ const int kBytesPerKilobit = 1024 / 8; |
+ if (audio_demuxer_stream_adapter_) { |
+ const double kilobits_per_second = |
+ (audio_demuxer_stream_adapter_->GetBytesWrittenAndReset() / |
+ kDataFlowPollPeriod.InSecondsF()) / |
+ kBytesPerKilobit; |
+ DCHECK_GE(kilobits_per_second, 0); |
+ const base::CheckedNumeric<int> checked_kbps = kilobits_per_second; |
+ metrics_recorder_.OnAudioRateEstimate( |
+ checked_kbps.ValueOrDefault(std::numeric_limits<int>::max())); |
+ } |
+ if (video_demuxer_stream_adapter_) { |
+ const double kilobits_per_second = |
+ (video_demuxer_stream_adapter_->GetBytesWrittenAndReset() / |
+ kDataFlowPollPeriod.InSecondsF()) / |
+ kBytesPerKilobit; |
+ DCHECK_GE(kilobits_per_second, 0); |
+ const base::CheckedNumeric<int> checked_kbps = kilobits_per_second; |
+ metrics_recorder_.OnVideoRateEstimate( |
+ checked_kbps.ValueOrDefault(std::numeric_limits<int>::max())); |
+ } |
} |
} // namespace media |