| Index: media/cast/sender/frame_sender.cc
|
| diff --git a/media/cast/sender/frame_sender.cc b/media/cast/sender/frame_sender.cc
|
| index 4b28be5cf007ec872f0938a64c525f0f77f572b9..0b94f74a117ea1229c3316cf8101b35fe41da3fd 100644
|
| --- a/media/cast/sender/frame_sender.cc
|
| +++ b/media/cast/sender/frame_sender.cc
|
| @@ -13,8 +13,15 @@ namespace {
|
| const int kMinSchedulingDelayMs = 1;
|
| const int kNumAggressiveReportsSentAtStart = 100;
|
|
|
| +// The additional number of frames that can be in-flight when input exceeds the
|
| +// maximum frame rate.
|
| +const int kMaxFrameBurst = 5;
|
| +
|
| } // namespace
|
|
|
| +// Convenience macro used in logging statements throughout this file.
|
| +#define SENDER_SSRC (is_audio_ ? "AUDIO[" : "VIDEO[") << ssrc_ << "] "
|
| +
|
| FrameSender::FrameSender(scoped_refptr<CastEnvironment> cast_environment,
|
| bool is_audio,
|
| CastTransportSender* const transport_sender,
|
| @@ -37,8 +44,8 @@ FrameSender::FrameSender(scoped_refptr<CastEnvironment> cast_environment,
|
| last_sent_frame_id_(0),
|
| latest_acked_frame_id_(0),
|
| duplicate_ack_counter_(0),
|
| - rtp_timebase_(rtp_timebase),
|
| congestion_control_(congestion_control),
|
| + rtp_timebase_(rtp_timebase),
|
| is_audio_(is_audio),
|
| weak_factory_(this) {
|
| DCHECK(transport_sender_);
|
| @@ -121,7 +128,8 @@ void FrameSender::ResendCheck() {
|
| if (latest_acked_frame_id_ == last_sent_frame_id_) {
|
| // Last frame acked, no point in doing anything
|
| } else {
|
| - VLOG(1) << "ACK timeout; last acked frame: " << latest_acked_frame_id_;
|
| + VLOG(1) << SENDER_SSRC << "ACK timeout; last acked frame: "
|
| + << latest_acked_frame_id_;
|
| ResendForKickstart();
|
| }
|
| }
|
| @@ -146,8 +154,8 @@ void FrameSender::ScheduleNextResendCheck() {
|
| void FrameSender::ResendForKickstart() {
|
| DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
|
| DCHECK(!last_send_time_.is_null());
|
| - VLOG(1) << "Resending last packet of frame " << last_sent_frame_id_
|
| - << " to kick-start.";
|
| + VLOG(1) << SENDER_SSRC << "Resending last packet of frame "
|
| + << last_sent_frame_id_ << " to kick-start.";
|
| last_send_time_ = cast_environment_->Clock()->NowTicks();
|
| transport_sender_->ResendFrameForKickstart(ssrc_, last_sent_frame_id_);
|
| }
|
| @@ -170,11 +178,29 @@ RtpTimestamp FrameSender::GetRecordedRtpTimestamp(uint32 frame_id) const {
|
| return frame_rtp_timestamps_[frame_id % arraysize(frame_rtp_timestamps_)];
|
| }
|
|
|
| +int FrameSender::GetUnacknowledgedFrameCount() const {
|
| + const int count =
|
| + static_cast<int32>(last_sent_frame_id_ - latest_acked_frame_id_);
|
| + DCHECK_GE(count, 0);
|
| + return count;
|
| +}
|
| +
|
| +base::TimeDelta FrameSender::GetAllowedInFlightMediaDuration() const {
|
| + // The total amount allowed in-flight media should equal the amount that fits
|
| + // within the entire playout delay window, plus the amount of time it takes to
|
| + // receive an ACK from the receiver.
|
| + // TODO(miu): Research is needed, but there is likely a better formula.
|
| + return target_playout_delay_ + (current_round_trip_time_ / 2);
|
| +}
|
| +
|
| void FrameSender::SendEncodedFrame(
|
| int requested_bitrate_before_encode,
|
| scoped_ptr<EncodedFrame> encoded_frame) {
|
| DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
|
|
|
| + VLOG(2) << SENDER_SSRC << "About to send another frame: last_sent="
|
| + << last_sent_frame_id_ << ", latest_acked=" << latest_acked_frame_id_;
|
| +
|
| const uint32 frame_id = encoded_frame->frame_id;
|
|
|
| const bool is_first_frame_to_be_sent = last_send_time_.is_null();
|
| @@ -188,8 +214,8 @@ void FrameSender::SendEncodedFrame(
|
| ScheduleNextResendCheck();
|
| }
|
|
|
| - VLOG_IF(1, encoded_frame->dependency == EncodedFrame::KEY)
|
| - << "Send encoded key frame; frame_id: " << frame_id;
|
| + VLOG_IF(1, !is_audio_ && encoded_frame->dependency == EncodedFrame::KEY)
|
| + << SENDER_SSRC << "Sending encoded key frame, id=" << frame_id;
|
|
|
| cast_environment_->Logging()->InsertEncodedFrameEvent(
|
| last_send_time_, FRAME_ENCODED,
|
| @@ -222,7 +248,8 @@ void FrameSender::SendEncodedFrame(
|
| ++num_aggressive_rtcp_reports_sent_;
|
| const bool is_last_aggressive_report =
|
| (num_aggressive_rtcp_reports_sent_ == kNumAggressiveReportsSentAtStart);
|
| - VLOG_IF(1, is_last_aggressive_report) << "Sending last aggressive report.";
|
| + VLOG_IF(1, is_last_aggressive_report)
|
| + << SENDER_SSRC << "Sending last aggressive report.";
|
| SendRtcpReport(is_last_aggressive_report);
|
| }
|
|
|
| @@ -247,7 +274,8 @@ void FrameSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) {
|
| // based on it having received a report from here. Therefore, ensure this
|
| // sender stops aggressively sending reports.
|
| if (num_aggressive_rtcp_reports_sent_ < kNumAggressiveReportsSentAtStart) {
|
| - VLOG(1) << "No longer a need to send reports aggressively (sent "
|
| + VLOG(1) << SENDER_SSRC
|
| + << "No longer a need to send reports aggressively (sent "
|
| << num_aggressive_rtcp_reports_sent_ << ").";
|
| num_aggressive_rtcp_reports_sent_ = kNumAggressiveReportsSentAtStart;
|
| ScheduleNextRtcpReport();
|
| @@ -269,7 +297,8 @@ void FrameSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) {
|
| }
|
| // TODO(miu): The values "2" and "3" should be derived from configuration.
|
| if (duplicate_ack_counter_ >= 2 && duplicate_ack_counter_ % 3 == 2) {
|
| - VLOG(1) << "Received duplicate ACK for frame " << latest_acked_frame_id_;
|
| + VLOG(1) << SENDER_SSRC << "Received duplicate ACK for frame "
|
| + << latest_acked_frame_id_;
|
| ResendForKickstart();
|
| }
|
| } else {
|
| @@ -291,7 +320,8 @@ void FrameSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) {
|
| const bool is_acked_out_of_order =
|
| static_cast<int32>(cast_feedback.ack_frame_id -
|
| latest_acked_frame_id_) < 0;
|
| - VLOG(2) << "Received ACK" << (is_acked_out_of_order ? " out-of-order" : "")
|
| + VLOG(2) << SENDER_SSRC
|
| + << "Received ACK" << (is_acked_out_of_order ? " out-of-order" : "")
|
| << " for frame " << cast_feedback.ack_frame_id;
|
| if (!is_acked_out_of_order) {
|
| // Cancel resends of acked frames.
|
| @@ -305,31 +335,47 @@ void FrameSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) {
|
| }
|
| }
|
|
|
| -bool FrameSender::ShouldDropNextFrame(base::TimeTicks capture_time) const {
|
| - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
|
| - int frames_in_flight = 0;
|
| - base::TimeDelta duration_in_flight;
|
| - if (!last_send_time_.is_null()) {
|
| - frames_in_flight =
|
| - static_cast<int32>(last_sent_frame_id_ - latest_acked_frame_id_);
|
| - if (frames_in_flight > 0) {
|
| - const uint32 oldest_unacked_frame_id = latest_acked_frame_id_ + 1;
|
| - duration_in_flight =
|
| - capture_time - GetRecordedReferenceTime(oldest_unacked_frame_id);
|
| - }
|
| +bool FrameSender::ShouldDropNextFrame(base::TimeDelta frame_duration) const {
|
| + // Check that accepting the next frame won't cause more frames to become
|
| + // in-flight than the system's design limit.
|
| + const int count_frames_in_flight =
|
| + GetUnacknowledgedFrameCount() + GetNumberOfFramesInEncoder();
|
| + if (count_frames_in_flight >= kMaxUnackedFrames) {
|
| + VLOG(1) << SENDER_SSRC << "Dropping: Too many frames would be in-flight.";
|
| + return true;
|
| + }
|
| +
|
| + // Check that accepting the next frame won't exceed the configured maximum
|
| + // frame rate, allowing for short-term bursts.
|
| + base::TimeDelta duration_in_flight = GetInFlightMediaDuration();
|
| + const double max_frames_in_flight =
|
| + max_frame_rate_ * duration_in_flight.InSecondsF();
|
| + if (count_frames_in_flight >= max_frames_in_flight + kMaxFrameBurst) {
|
| + VLOG(1) << SENDER_SSRC << "Dropping: Burst threshold would be exceeded.";
|
| + return true;
|
| }
|
| - frames_in_flight += GetNumberOfFramesInEncoder();
|
| - VLOG(2) << frames_in_flight
|
| - << " frames in flight; last sent: " << last_sent_frame_id_
|
| - << "; latest acked: " << latest_acked_frame_id_
|
| - << "; frames in encoder: " << GetNumberOfFramesInEncoder()
|
| - << "; duration in flight: "
|
| - << duration_in_flight.InMicroseconds() << " usec ("
|
| - << (target_playout_delay_ > base::TimeDelta() ?
|
| - 100 * duration_in_flight / target_playout_delay_ :
|
| - kint64max) << "%)";
|
| - return frames_in_flight >= max_unacked_frames_ ||
|
| - duration_in_flight >= target_playout_delay_;
|
| +
|
| + // Check that accepting the next frame won't exceed the allowed in-flight
|
| + // media duration.
|
| + const base::TimeDelta duration_would_be_in_flight =
|
| + duration_in_flight + frame_duration;
|
| + const base::TimeDelta allowed_in_flight = GetAllowedInFlightMediaDuration();
|
| + if (VLOG_IS_ON(1)) {
|
| + const int64 percent = allowed_in_flight > base::TimeDelta() ?
|
| + 100 * duration_would_be_in_flight / allowed_in_flight : kint64max;
|
| + VLOG_IF(1, percent > 50)
|
| + << SENDER_SSRC
|
| + << duration_in_flight.InMicroseconds() << " usec in-flight + "
|
| + << frame_duration.InMicroseconds() << " usec for next frame --> "
|
| + << percent << "% of allowed in-flight.";
|
| + }
|
| + if (duration_would_be_in_flight > allowed_in_flight) {
|
| + VLOG(1) << SENDER_SSRC << "Dropping: In-flight duration would be too high.";
|
| + return true;
|
| + }
|
| +
|
| + // Next frame is accepted.
|
| + return false;
|
| }
|
|
|
| } // namespace cast
|
|
|