diff options
author | Torne (Richard Coles) <torne@google.com> | 2014-06-25 10:30:53 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2014-06-25 10:30:53 +0100 |
commit | 6d86b77056ed63eb6871182f42a9fd5f07550f90 (patch) | |
tree | 4bd56255660f52e406fbd45083c006cd6ddb2877 /media/cast | |
parent | e9f930807da3850e29ecc641d2becc0403b5709c (diff) | |
download | chromium_org-6d86b77056ed63eb6871182f42a9fd5f07550f90.tar.gz |
Merge from Chromium at DEPS revision 278856
This commit was generated by merge_to_master.py.
Change-Id: If3807744d3e5d3ee84b897bd2d099a2b7ed2e7a3
Diffstat (limited to 'media/cast')
24 files changed, 408 insertions, 115 deletions
diff --git a/media/cast/audio_sender/audio_encoder.cc b/media/cast/audio_sender/audio_encoder.cc index f81ad26377..8860c7dd2d 100644 --- a/media/cast/audio_sender/audio_encoder.cc +++ b/media/cast/audio_sender/audio_encoder.cc @@ -9,14 +9,12 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/location.h" -#include "base/logging.h" #include "base/stl_util.h" #include "base/sys_byteorder.h" #include "base/time/time.h" #include "media/base/audio_bus.h" #include "media/cast/cast_defines.h" #include "media/cast/cast_environment.h" -#include "media/cast/logging/logging_defines.h" #include "third_party/opus/src/include/opus.h" namespace media { @@ -33,28 +31,6 @@ const int kFrameDurationMillis = 1000 / kFramesPerSecond; // No remainder! // coming in too slow with respect to the capture timestamps. const int kUnderrunThresholdMillis = 3 * kFrameDurationMillis; -void LogAudioFrameEncodedEvent( - const scoped_refptr<media::cast::CastEnvironment>& cast_environment, - base::TimeTicks event_time, - media::cast::RtpTimestamp rtp_timestamp, - uint32 frame_id, - size_t frame_size) { - if (!cast_environment->CurrentlyOn(CastEnvironment::MAIN)) { - cast_environment->PostTask( - CastEnvironment::MAIN, - FROM_HERE, - base::Bind(&LogAudioFrameEncodedEvent, - cast_environment, event_time, - rtp_timestamp, frame_id, frame_size)); - return; - } - cast_environment->Logging()->InsertEncodedFrameEvent( - event_time, media::cast::FRAME_ENCODED, media::cast::AUDIO_EVENT, - rtp_timestamp, frame_id, - static_cast<int>(frame_size), /* key_frame - unused */ false, - /*target_bitrate - unused*/ 0); -} - } // namespace @@ -150,11 +126,6 @@ class AudioEncoder::ImplBase audio_frame->reference_time = frame_capture_time_; if (EncodeFromFilledBuffer(&audio_frame->data)) { - LogAudioFrameEncodedEvent(cast_environment_, - cast_environment_->Clock()->NowTicks(), - audio_frame->rtp_timestamp, - audio_frame->frame_id, - audio_frame->data.size()); cast_environment_->PostTask( CastEnvironment::MAIN, FROM_HERE, diff --git a/media/cast/audio_sender/audio_sender.cc b/media/cast/audio_sender/audio_sender.cc index 27b42d0dc8..878f3456c8 100644 --- a/media/cast/audio_sender/audio_sender.cc +++ b/media/cast/audio_sender/audio_sender.cc @@ -8,7 +8,9 @@ #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "media/cast/audio_sender/audio_encoder.h" -#include "media/cast/transport/cast_transport_defines.h" +#include "media/cast/cast_defines.h" +#include "media/cast/rtcp/rtcp_defines.h" +#include "media/cast/transport/cast_transport_config.h" namespace media { namespace cast { @@ -16,13 +18,24 @@ namespace cast { const int kNumAggressiveReportsSentAtStart = 100; const int kMinSchedulingDelayMs = 1; -// TODO(mikhal): Reduce heap allocation when not needed. +// TODO(miu): This should be specified in AudioSenderConfig, but currently it is +// fixed to 100 FPS (i.e., 10 ms per frame), and AudioEncoder assumes this as +// well. +const int kAudioFrameRate = 100; + AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, const AudioSenderConfig& audio_config, transport::CastTransportSender* const transport_sender) : cast_environment_(cast_environment), + target_playout_delay_(base::TimeDelta::FromMilliseconds( + audio_config.rtp_config.max_delay_ms)), transport_sender_(transport_sender), - rtp_timestamp_helper_(audio_config.frequency), + max_unacked_frames_( + std::min(kMaxUnackedFrames, + 1 + static_cast<int>(target_playout_delay_ * + kAudioFrameRate / + base::TimeDelta::FromSeconds(1)))), + configured_encoder_bitrate_(audio_config.bitrate), rtcp_(cast_environment, this, transport_sender_, @@ -34,10 +47,16 @@ AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, audio_config.incoming_feedback_ssrc, audio_config.rtcp_c_name, AUDIO_EVENT), + rtp_timestamp_helper_(audio_config.frequency), num_aggressive_rtcp_reports_sent_(0), + last_sent_frame_id_(0), + latest_acked_frame_id_(0), + duplicate_ack_counter_(0), cast_initialization_status_(STATUS_AUDIO_UNINITIALIZED), weak_factory_(this) { - rtcp_.SetCastReceiverEventHistorySize(kReceiverRtcpEventHistorySize); + VLOG(1) << "max_unacked_frames " << max_unacked_frames_; + DCHECK_GT(max_unacked_frames_, 0); + if (!audio_config.use_external_encoder) { audio_encoder_.reset( new AudioEncoder(cast_environment, @@ -47,7 +66,7 @@ AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, cast_initialization_status_ = audio_encoder_->InitializationResult(); } else { NOTREACHED(); // No support for external audio encoding. - cast_initialization_status_ = STATUS_AUDIO_INITIALIZED; + cast_initialization_status_ = STATUS_AUDIO_UNINITIALIZED; } media::cast::transport::CastTransportAudioConfig transport_config; @@ -55,10 +74,11 @@ AudioSender::AudioSender(scoped_refptr<CastEnvironment> cast_environment, transport_config.rtp.config = audio_config.rtp_config; transport_config.frequency = audio_config.frequency; transport_config.channels = audio_config.channels; - transport_config.rtp.max_outstanding_frames = - audio_config.rtp_config.max_delay_ms / 100 + 1; + transport_config.rtp.max_outstanding_frames = max_unacked_frames_; transport_sender_->InitializeAudio(transport_config); + rtcp_.SetCastReceiverEventHistorySize(kReceiverRtcpEventHistorySize); + memset(frame_id_to_rtp_timestamp_, 0, sizeof(frame_id_to_rtp_timestamp_)); } @@ -72,16 +92,43 @@ void AudioSender::InsertAudio(scoped_ptr<AudioBus> audio_bus, return; } DCHECK(audio_encoder_.get()) << "Invalid internal state"; + + if (AreTooManyFramesInFlight()) { + VLOG(1) << "Dropping frame due to too many frames currently in-flight."; + return; + } + audio_encoder_->InsertAudio(audio_bus.Pass(), recorded_time); } void AudioSender::SendEncodedAudioFrame( - scoped_ptr<transport::EncodedFrame> audio_frame) { + scoped_ptr<transport::EncodedFrame> encoded_frame) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - DCHECK(!audio_frame->reference_time.is_null()); - rtp_timestamp_helper_.StoreLatestTime(audio_frame->reference_time, - audio_frame->rtp_timestamp); + const uint32 frame_id = encoded_frame->frame_id; + + const bool is_first_frame_to_be_sent = last_send_time_.is_null(); + last_send_time_ = cast_environment_->Clock()->NowTicks(); + last_sent_frame_id_ = frame_id; + // If this is the first frame about to be sent, fake the value of + // |latest_acked_frame_id_| to indicate the receiver starts out all caught up. + // Also, schedule the periodic frame re-send checks. + if (is_first_frame_to_be_sent) { + latest_acked_frame_id_ = frame_id - 1; + ScheduleNextResendCheck(); + } + + cast_environment_->Logging()->InsertEncodedFrameEvent( + last_send_time_, FRAME_ENCODED, AUDIO_EVENT, encoded_frame->rtp_timestamp, + frame_id, static_cast<int>(encoded_frame->data.size()), + encoded_frame->dependency == transport::EncodedFrame::KEY, + configured_encoder_bitrate_); + // Only use lowest 8 bits as key. + frame_id_to_rtp_timestamp_[frame_id & 0xff] = encoded_frame->rtp_timestamp; + + DCHECK(!encoded_frame->reference_time.is_null()); + rtp_timestamp_helper_.StoreLatestTime(encoded_frame->reference_time, + encoded_frame->rtp_timestamp); // At the start of the session, it's important to send reports before each // frame so that the receiver can properly compute playout times. The reason @@ -98,15 +145,7 @@ void AudioSender::SendEncodedAudioFrame( SendRtcpReport(is_last_aggressive_report); } - frame_id_to_rtp_timestamp_[audio_frame->frame_id & 0xff] = - audio_frame->rtp_timestamp; - transport_sender_->InsertCodedAudioFrame(*audio_frame); -} - -void AudioSender::ResendPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets) { - DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); - transport_sender_->ResendPackets(true, missing_frames_and_packets, false); + transport_sender_->InsertCodedAudioFrame(*encoded_frame); } void AudioSender::IncomingRtcpPacket(scoped_ptr<Packet> packet) { @@ -146,6 +185,37 @@ void AudioSender::SendRtcpReport(bool schedule_future_reports) { ScheduleNextRtcpReport(); } +void AudioSender::ScheduleNextResendCheck() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + DCHECK(!last_send_time_.is_null()); + base::TimeDelta time_to_next = + last_send_time_ - cast_environment_->Clock()->NowTicks() + + target_playout_delay_; + time_to_next = std::max( + time_to_next, base::TimeDelta::FromMilliseconds(kMinSchedulingDelayMs)); + cast_environment_->PostDelayedTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&AudioSender::ResendCheck, weak_factory_.GetWeakPtr()), + time_to_next); +} + +void AudioSender::ResendCheck() { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + DCHECK(!last_send_time_.is_null()); + const base::TimeDelta time_since_last_send = + cast_environment_->Clock()->NowTicks() - last_send_time_; + if (time_since_last_send > target_playout_delay_) { + 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_; + ResendForKickstart(); + } + } + ScheduleNextResendCheck(); +} + void AudioSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); @@ -161,15 +231,105 @@ void AudioSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { } } - if (!cast_feedback.missing_frames_and_packets_.empty()) { - ResendPackets(cast_feedback.missing_frames_and_packets_); + if (last_send_time_.is_null()) + return; // Cannot get an ACK without having first sent a frame. + + if (cast_feedback.missing_frames_and_packets_.empty()) { + // We only count duplicate ACKs when we have sent newer frames. + if (latest_acked_frame_id_ == cast_feedback.ack_frame_id_ && + latest_acked_frame_id_ != last_sent_frame_id_) { + duplicate_ack_counter_++; + } else { + duplicate_ack_counter_ = 0; + } + // 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_; + ResendForKickstart(); + } + } else { + // Only count duplicated ACKs if there is no NACK request in between. + // This is to avoid aggresive resend. + duplicate_ack_counter_ = 0; + + base::TimeDelta rtt; + base::TimeDelta avg_rtt; + base::TimeDelta min_rtt; + base::TimeDelta max_rtt; + rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt); + + // A NACK is also used to cancel pending re-transmissions. + transport_sender_->ResendPackets( + true, cast_feedback.missing_frames_and_packets_, false, min_rtt); } - uint32 acked_frame_id = static_cast<uint32>(cast_feedback.ack_frame_id_); - VLOG(2) << "Received audio ACK: " << acked_frame_id; - cast_environment_->Logging()->InsertFrameEvent( - cast_environment_->Clock()->NowTicks(), - FRAME_ACK_RECEIVED, AUDIO_EVENT, - frame_id_to_rtp_timestamp_[acked_frame_id & 0xff], acked_frame_id); + + const base::TimeTicks now = cast_environment_->Clock()->NowTicks(); + + const RtpTimestamp rtp_timestamp = + frame_id_to_rtp_timestamp_[cast_feedback.ack_frame_id_ & 0xff]; + cast_environment_->Logging()->InsertFrameEvent(now, + FRAME_ACK_RECEIVED, + AUDIO_EVENT, + rtp_timestamp, + cast_feedback.ack_frame_id_); + + 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" : "") + << " for frame " << cast_feedback.ack_frame_id_; + if (!is_acked_out_of_order) { + // Cancel resends of acked frames. + MissingFramesAndPacketsMap missing_frames_and_packets; + PacketIdSet missing; + while (latest_acked_frame_id_ != cast_feedback.ack_frame_id_) { + latest_acked_frame_id_++; + missing_frames_and_packets[latest_acked_frame_id_] = missing; + } + transport_sender_->ResendPackets( + true, missing_frames_and_packets, true, base::TimeDelta()); + latest_acked_frame_id_ = cast_feedback.ack_frame_id_; + } +} + +bool AudioSender::AreTooManyFramesInFlight() const { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + int frames_in_flight = 0; + if (!last_send_time_.is_null()) { + frames_in_flight += + static_cast<int32>(last_sent_frame_id_ - latest_acked_frame_id_); + } + VLOG(2) << frames_in_flight + << " frames in flight; last sent: " << last_sent_frame_id_ + << " latest acked: " << latest_acked_frame_id_; + return frames_in_flight >= max_unacked_frames_; +} + +void AudioSender::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."; + // Send the first packet of the last encoded frame to kick start + // retransmission. This gives enough information to the receiver what + // packets and frames are missing. + MissingFramesAndPacketsMap missing_frames_and_packets; + PacketIdSet missing; + missing.insert(kRtcpCastLastPacket); + missing_frames_and_packets.insert( + std::make_pair(last_sent_frame_id_, missing)); + last_send_time_ = cast_environment_->Clock()->NowTicks(); + + base::TimeDelta rtt; + base::TimeDelta avg_rtt; + base::TimeDelta min_rtt; + base::TimeDelta max_rtt; + rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt); + + // Sending this extra packet is to kick-start the session. There is + // no need to optimize re-transmission for this case. + transport_sender_->ResendPackets( + true, missing_frames_and_packets, false, min_rtt); } } // namespace cast diff --git a/media/cast/audio_sender/audio_sender.h b/media/cast/audio_sender/audio_sender.h index 03de633bef..80cf8a4e9e 100644 --- a/media/cast/audio_sender/audio_sender.h +++ b/media/cast/audio_sender/audio_sender.h @@ -14,6 +14,8 @@ #include "base/time/time.h" #include "media/base/audio_bus.h" #include "media/cast/cast_config.h" +#include "media/cast/cast_environment.h" +#include "media/cast/logging/logging_defines.h" #include "media/cast/rtcp/rtcp.h" #include "media/cast/rtp_timestamp_helper.h" @@ -22,8 +24,12 @@ namespace cast { class AudioEncoder; -// This class is not thread safe. -// It's only called from the main cast thread. +// Not thread safe. Only called from the main cast thread. +// This class owns all objects related to sending audio, objects that create RTP +// packets, congestion control, audio encoder, parsing and sending of +// RTCP packets. +// Additionally it posts a bunch of delayed tasks to the main thread for various +// timeouts. class AudioSender : public RtcpSenderFeedback, public base::NonThreadSafe, public base::SupportsWeakPtr<AudioSender> { @@ -38,6 +44,10 @@ class AudioSender : public RtcpSenderFeedback, return cast_initialization_status_; } + // Note: It is not guaranteed that |audio_frame| will actually be encoded and + // sent, if AudioSender detects too many frames in flight. Therefore, clients + // should be careful about the rate at which this method is called. + // // Note: It is invalid to call this method if InitializationResult() returns // anything but STATUS_AUDIO_INITIALIZED. void InsertAudio(scoped_ptr<AudioBus> audio_bus, @@ -46,31 +56,98 @@ class AudioSender : public RtcpSenderFeedback, // Only called from the main cast thread. void IncomingRtcpPacket(scoped_ptr<Packet> packet); - private: - void ResendPackets( - const MissingFramesAndPacketsMap& missing_frames_and_packets); + protected: + // Protected for testability. + virtual void OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) + OVERRIDE; + private: + // Schedule and execute periodic sending of RTCP report. void ScheduleNextRtcpReport(); void SendRtcpReport(bool schedule_future_reports); + // Schedule and execute periodic checks for re-sending packets. If no + // acknowledgements have been received for "too long," AudioSender will + // speculatively re-send certain packets of an unacked frame to kick-start + // re-transmission. This is a last resort tactic to prevent the session from + // getting stuck after a long outage. + void ScheduleNextResendCheck(); + void ResendCheck(); + void ResendForKickstart(); + + // Returns true if there are too many frames in flight, as defined by the + // configured target playout delay plus simple logic. When this is true, + // InsertAudio() will silenty drop frames instead of sending them to the audio + // encoder. + bool AreTooManyFramesInFlight() const; + // Called by the |audio_encoder_| with the next EncodedFrame to send. void SendEncodedAudioFrame(scoped_ptr<transport::EncodedFrame> audio_frame); - virtual void OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) - OVERRIDE; - - scoped_refptr<CastEnvironment> cast_environment_; + const scoped_refptr<CastEnvironment> cast_environment_; + + // The total amount of time between a frame's capture/recording on the sender + // and its playback on the receiver (i.e., shown to a user). This is fixed as + // a value large enough to give the system sufficient time to encode, + // transmit/retransmit, receive, decode, and render; given its run-time + // environment (sender/receiver hardware performance, network conditions, + // etc.). + const base::TimeDelta target_playout_delay_; + + // Sends encoded frames over the configured transport (e.g., UDP). In + // Chromium, this could be a proxy that first sends the frames from a renderer + // process to the browser process over IPC, with the browser process being + // responsible for "packetizing" the frames and pushing packets into the + // network layer. transport::CastTransportSender* const transport_sender_; + + // Maximum number of outstanding frames before the encoding and sending of + // new frames shall halt. + const int max_unacked_frames_; + + // Encodes AudioBuses into EncodedFrames. scoped_ptr<AudioEncoder> audio_encoder_; - RtpTimestampHelper rtp_timestamp_helper_; + const int configured_encoder_bitrate_; + + // Manages sending/receiving of RTCP packets, including sender/receiver + // reports. Rtcp rtcp_; + + // Records lip-sync (i.e., mapping of RTP <--> NTP timestamps), and + // extrapolates this mapping to any other point in time. + RtpTimestampHelper rtp_timestamp_helper_; + + // Counts how many RTCP reports are being "aggressively" sent (i.e., one per + // frame) at the start of the session. Once a threshold is reached, RTCP + // reports are instead sent at the configured interval + random drift. int num_aggressive_rtcp_reports_sent_; + // This is "null" until the first frame is sent. Thereafter, this tracks the + // last time any frame was sent or re-sent. + base::TimeTicks last_send_time_; + + // The ID of the last frame sent. Logic throughout AudioSender assumes this + // can safely wrap-around. This member is invalid until + // |!last_send_time_.is_null()|. + uint32 last_sent_frame_id_; + + // The ID of the latest (not necessarily the last) frame that has been + // acknowledged. Logic throughout AudioSender assumes this can safely + // wrap-around. This member is invalid until |!last_send_time_.is_null()|. + uint32 latest_acked_frame_id_; + + // Counts the number of duplicate ACK that are being received. When this + // number reaches a threshold, the sender will take this as a sign that the + // receiver hasn't yet received the first packet of the next frame. In this + // case, AudioSender will trigger a re-send of the next frame. + int duplicate_ack_counter_; + // If this sender is ready for use, this is STATUS_AUDIO_INITIALIZED. CastInitializationStatus cast_initialization_status_; - // Used to map the lower 8 bits of the frame id to a RTP timestamp. This is - // good enough as we only use it for logging. + // This is a "good enough" mapping for finding the RTP timestamp associated + // with a video frame. The key is the lowest 8 bits of frame id (which is + // what is sent via RTCP). This map is used for logging purposes. RtpTimestamp frame_id_to_rtp_timestamp_[256]; // NOTE: Weak pointers must be invalidated before all other member variables. diff --git a/media/cast/logging/log_deserializer.cc b/media/cast/logging/log_deserializer.cc index a4c79b3de9..1c6dd57224 100644 --- a/media/cast/logging/log_deserializer.cc +++ b/media/cast/logging/log_deserializer.cc @@ -35,7 +35,12 @@ void MergePacketEvent(const AggregatedPacketEvent& from, for (int j = 0; j < to->base_packet_event_size(); j++) { BasePacketEvent* to_base_event = to->mutable_base_packet_event(j); if (from_base_event.packet_id() == to_base_event->packet_id()) { + int packet_size = std::max( + from_base_event.size(), to_base_event->size()); + // Need special merge logic here because we need to prevent a valid + // packet size (> 0) from being overwritten with an invalid one (= 0). to_base_event->MergeFrom(from_base_event); + to_base_event->set_size(packet_size); merged = true; break; } diff --git a/media/cast/logging/logging_defines.cc b/media/cast/logging/logging_defines.cc index d0dd5c8e57..05ceeb9521 100644 --- a/media/cast/logging/logging_defines.cc +++ b/media/cast/logging/logging_defines.cc @@ -25,6 +25,7 @@ const char* CastLoggingToString(CastLoggingEvent event) { ENUM_TO_STRING(FRAME_PLAYOUT); ENUM_TO_STRING(PACKET_SENT_TO_NETWORK); ENUM_TO_STRING(PACKET_RETRANSMITTED); + ENUM_TO_STRING(PACKET_RTX_REJECTED); ENUM_TO_STRING(PACKET_RECEIVED); } NOTREACHED(); diff --git a/media/cast/logging/logging_defines.h b/media/cast/logging/logging_defines.h index b3f3841ffb..021a3c99a7 100644 --- a/media/cast/logging/logging_defines.h +++ b/media/cast/logging/logging_defines.h @@ -32,6 +32,7 @@ enum CastLoggingEvent { // Sender side packet events. PACKET_SENT_TO_NETWORK, PACKET_RETRANSMITTED, + PACKET_RTX_REJECTED, // Receiver side packet events. PACKET_RECEIVED, kNumOfLoggingEvents = PACKET_RECEIVED diff --git a/media/cast/logging/proto/proto_utils.cc b/media/cast/logging/proto/proto_utils.cc index 1f05616d83..03251e64c0 100644 --- a/media/cast/logging/proto/proto_utils.cc +++ b/media/cast/logging/proto/proto_utils.cc @@ -25,6 +25,7 @@ proto::EventType ToProtoEventType(CastLoggingEvent event) { TO_PROTO_ENUM(FRAME_PLAYOUT); TO_PROTO_ENUM(PACKET_SENT_TO_NETWORK); TO_PROTO_ENUM(PACKET_RETRANSMITTED); + TO_PROTO_ENUM(PACKET_RTX_REJECTED); TO_PROTO_ENUM(PACKET_RECEIVED); } NOTREACHED(); diff --git a/media/cast/logging/proto/raw_events.proto b/media/cast/logging/proto/raw_events.proto index 08bf53d19a..1d2c537db8 100644 --- a/media/cast/logging/proto/raw_events.proto +++ b/media/cast/logging/proto/raw_events.proto @@ -66,6 +66,7 @@ enum EventType { PACKET_SENT_TO_NETWORK = 36; PACKET_RETRANSMITTED = 37; PACKET_RECEIVED = 38; + PACKET_RTX_REJECTED = 39; } // Contains information independent of the stream that describes the system diff --git a/media/cast/rtcp/rtcp_sender_unittest.cc b/media/cast/rtcp/rtcp_sender_unittest.cc index 2bd807f380..0b0c7d3ab8 100644 --- a/media/cast/rtcp/rtcp_sender_unittest.cc +++ b/media/cast/rtcp/rtcp_sender_unittest.cc @@ -59,7 +59,8 @@ class TestRtcpTransport : public transport::PacedPacketSender { return false; } virtual bool ResendPackets( - const transport::SendPacketVector& packets) OVERRIDE { + const transport::SendPacketVector& packets, + base::TimeDelta dedupe_window) OVERRIDE { return false; } diff --git a/media/cast/rtcp/rtcp_unittest.cc b/media/cast/rtcp/rtcp_unittest.cc index d5bf312c3e..095e6d24df 100644 --- a/media/cast/rtcp/rtcp_unittest.cc +++ b/media/cast/rtcp/rtcp_unittest.cc @@ -104,7 +104,8 @@ class LocalRtcpTransport : public transport::PacedPacketSender { } virtual bool ResendPackets( - const transport::SendPacketVector& packets) OVERRIDE { + const transport::SendPacketVector& packets, + base::TimeDelta dedupe_window) OVERRIDE { return false; } diff --git a/media/cast/test/cast_benchmarks.cc b/media/cast/test/cast_benchmarks.cc index c3468a2cdb..66257626bd 100644 --- a/media/cast/test/cast_benchmarks.cc +++ b/media/cast/test/cast_benchmarks.cc @@ -212,9 +212,10 @@ class CastTransportSenderWrapper : public transport::CastTransportSender { virtual void ResendPackets( bool is_audio, const MissingFramesAndPacketsMap& missing_packets, - bool cancel_rtx_if_not_in_list) OVERRIDE { + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) OVERRIDE { transport_->ResendPackets( - is_audio, missing_packets, cancel_rtx_if_not_in_list); + is_audio, missing_packets, cancel_rtx_if_not_in_list, dedupe_window); } private: diff --git a/media/cast/transport/cast_transport_sender.h b/media/cast/transport/cast_transport_sender.h index 2556a8bd3d..e88f2f4f09 100644 --- a/media/cast/transport/cast_transport_sender.h +++ b/media/cast/transport/cast_transport_sender.h @@ -96,11 +96,14 @@ class CastTransportSender : public base::NonThreadSafe { // frame to be re-transmitted. // If |cancel_rtx_if_not_in_list| is used as an optimization to cancel // pending re-transmission requests of packets not listed in - // |missing_packets|. + // |missing_packets|. If the requested packet(s) were sent recently + // (how long is specified by |dedupe_window|) then this re-transmit + // will be ignored. virtual void ResendPackets( bool is_audio, const MissingFramesAndPacketsMap& missing_packets, - bool cancel_rtx_if_not_in_list) = 0; + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) = 0; }; } // namespace transport diff --git a/media/cast/transport/cast_transport_sender_impl.cc b/media/cast/transport/cast_transport_sender_impl.cc index 2f51a934e7..6fd848f27b 100644 --- a/media/cast/transport/cast_transport_sender_impl.cc +++ b/media/cast/transport/cast_transport_sender_impl.cc @@ -7,6 +7,7 @@ #include "base/single_thread_task_runner.h" #include "media/cast/transport/cast_transport_config.h" #include "media/cast/transport/cast_transport_defines.h" +#include "net/base/net_util.h" namespace media { namespace cast { @@ -66,6 +67,11 @@ CastTransportSenderImpl::CastTransportSenderImpl( this, &CastTransportSenderImpl::SendRawEvents); } + if (transport_) { + // The default DSCP value for cast is AF41. Which gives it a higher + // priority over other traffic. + transport_->SetDscp(net::DSCP_AF41); + } } CastTransportSenderImpl::~CastTransportSenderImpl() { @@ -178,15 +184,18 @@ void CastTransportSenderImpl::SendRtcpFromRtpSender( void CastTransportSenderImpl::ResendPackets( bool is_audio, const MissingFramesAndPacketsMap& missing_packets, - bool cancel_rtx_if_not_in_list) { + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) { if (is_audio) { DCHECK(audio_sender_) << "Audio sender uninitialized"; audio_sender_->ResendPackets(missing_packets, - cancel_rtx_if_not_in_list); + cancel_rtx_if_not_in_list, + dedupe_window); } else { DCHECK(video_sender_) << "Video sender uninitialized"; video_sender_->ResendPackets(missing_packets, - cancel_rtx_if_not_in_list); + cancel_rtx_if_not_in_list, + dedupe_window); } } diff --git a/media/cast/transport/cast_transport_sender_impl.h b/media/cast/transport/cast_transport_sender_impl.h index 4fc074c0b9..035ef844b6 100644 --- a/media/cast/transport/cast_transport_sender_impl.h +++ b/media/cast/transport/cast_transport_sender_impl.h @@ -67,7 +67,8 @@ class CastTransportSenderImpl : public CastTransportSender { virtual void ResendPackets(bool is_audio, const MissingFramesAndPacketsMap& missing_packets, - bool cancel_rtx_if_not_in_list) + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) OVERRIDE; private: diff --git a/media/cast/transport/pacing/mock_paced_packet_sender.h b/media/cast/transport/pacing/mock_paced_packet_sender.h index 9f6d204f7f..20b7647035 100644 --- a/media/cast/transport/pacing/mock_paced_packet_sender.h +++ b/media/cast/transport/pacing/mock_paced_packet_sender.h @@ -18,7 +18,8 @@ class MockPacedPacketSender : public PacedPacketSender { virtual ~MockPacedPacketSender(); MOCK_METHOD1(SendPackets, bool(const SendPacketVector& packets)); - MOCK_METHOD1(ResendPackets, bool(const SendPacketVector& packets)); + MOCK_METHOD2(ResendPackets, bool(const SendPacketVector& packets, + base::TimeDelta dedupe_window)); MOCK_METHOD2(SendRtcpPacket, bool(unsigned int ssrc, PacketRef packet)); MOCK_METHOD1(CancelSendingPacket, void(const PacketKey& packet_key)); }; diff --git a/media/cast/transport/pacing/paced_sender.cc b/media/cast/transport/pacing/paced_sender.cc index 10b4224075..20cbde85be 100644 --- a/media/cast/transport/pacing/paced_sender.cc +++ b/media/cast/transport/pacing/paced_sender.cc @@ -20,6 +20,7 @@ static const int64 kPacingIntervalMs = 10; static const size_t kPacingMaxBurstsPerFrame = 3; static const size_t kTargetBurstSize = 10; static const size_t kMaxBurstSize = 20; +static const size_t kMaxDedupeWindowMs = 500; } // namespace @@ -73,11 +74,21 @@ bool PacedSender::SendPackets(const SendPacketVector& packets) { return true; } -bool PacedSender::ResendPackets(const SendPacketVector& packets) { +bool PacedSender::ResendPackets(const SendPacketVector& packets, + base::TimeDelta dedupe_window) { if (packets.empty()) { return true; } + base::TimeTicks now = clock_->NowTicks(); for (size_t i = 0; i < packets.size(); i++) { + std::map<PacketKey, base::TimeTicks>::const_iterator j = + sent_time_.find(packets[i].first); + + if (j != sent_time_.end() && now - j->second < dedupe_window) { + LogPacketEvent(packets[i].second->data, PACKET_RTX_REJECTED); + continue; + } + packet_list_[packets[i].first] = make_pair(PacketType_Resend, packets[i].second); } @@ -108,11 +119,13 @@ void PacedSender::CancelSendingPacket(const PacketKey& packet_key) { packet_list_.erase(packet_key); } -PacketRef PacedSender::GetNextPacket(PacketType* packet_type) { +PacketRef PacedSender::GetNextPacket(PacketType* packet_type, + PacketKey* packet_key) { std::map<PacketKey, std::pair<PacketType, PacketRef> >::iterator i; i = packet_list_.begin(); DCHECK(i != packet_list_.end()); *packet_type = i->second.first; + *packet_key = i->first; PacketRef ret = i->second.second; packet_list_.erase(i); return ret; @@ -185,14 +198,17 @@ void PacedSender::SendStoredPackets() { return; } PacketType packet_type; - PacketRef packet = GetNextPacket(&packet_type); + PacketKey packet_key; + PacketRef packet = GetNextPacket(&packet_type, &packet_key); + sent_time_[packet_key] = now; + sent_time_buffer_[packet_key] = now; switch (packet_type) { case PacketType_Resend: - LogPacketEvent(packet->data, true); + LogPacketEvent(packet->data, PACKET_RETRANSMITTED); break; case PacketType_Normal: - LogPacketEvent(packet->data, false); + LogPacketEvent(packet->data, PACKET_SENT_TO_NETWORK); break; case PacketType_RTCP: break; @@ -203,10 +219,20 @@ void PacedSender::SendStoredPackets() { } current_burst_size_++; } + // Keep ~0.5 seconds of data (1000 packets) + if (sent_time_buffer_.size() >= + kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs) { + sent_time_.swap(sent_time_buffer_); + sent_time_buffer_.clear(); + } + DCHECK_LE(sent_time_buffer_.size(), + kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs); + DCHECK_LE(sent_time_.size(), + 2 * kMaxBurstSize * kMaxDedupeWindowMs / kPacingIntervalMs); state_ = State_Unblocked; } -void PacedSender::LogPacketEvent(const Packet& packet, bool retransmit) { +void PacedSender::LogPacketEvent(const Packet& packet, CastLoggingEvent event) { // Get SSRC from packet and compare with the audio_ssrc / video_ssrc to see // if the packet is audio or video. DCHECK_GE(packet.size(), 12u); @@ -224,8 +250,6 @@ void PacedSender::LogPacketEvent(const Packet& packet, bool retransmit) { return; } - CastLoggingEvent event = retransmit ? - PACKET_RETRANSMITTED : PACKET_SENT_TO_NETWORK; EventMediaType media_type = is_audio ? AUDIO_EVENT : VIDEO_EVENT; logging_->InsertSinglePacketEvent(clock_->NowTicks(), event, media_type, packet); diff --git a/media/cast/transport/pacing/paced_sender.h b/media/cast/transport/pacing/paced_sender.h index 2373fb5966..9fc0c8b8b8 100644 --- a/media/cast/transport/pacing/paced_sender.h +++ b/media/cast/transport/pacing/paced_sender.h @@ -41,7 +41,8 @@ typedef std::vector<std::pair<PacketKey, PacketRef> > SendPacketVector; class PacedPacketSender { public: virtual bool SendPackets(const SendPacketVector& packets) = 0; - virtual bool ResendPackets(const SendPacketVector& packets) = 0; + virtual bool ResendPackets(const SendPacketVector& packets, + base::TimeDelta dedupe_window) = 0; virtual bool SendRtcpPacket(uint32 ssrc, PacketRef packet) = 0; virtual void CancelSendingPacket(const PacketKey& packet_key) = 0; @@ -72,14 +73,15 @@ class PacedSender : public PacedPacketSender, // PacedPacketSender implementation. virtual bool SendPackets(const SendPacketVector& packets) OVERRIDE; - virtual bool ResendPackets(const SendPacketVector& packets) OVERRIDE; + virtual bool ResendPackets(const SendPacketVector& packets, + base::TimeDelta dedupe_window) OVERRIDE; virtual bool SendRtcpPacket(uint32 ssrc, PacketRef packet) OVERRIDE; virtual void CancelSendingPacket(const PacketKey& packet_key) OVERRIDE; private: // Actually sends the packets to the transport. void SendStoredPackets(); - void LogPacketEvent(const Packet& packet, bool retransmit); + void LogPacketEvent(const Packet& packet, CastLoggingEvent event); enum PacketType { PacketType_RTCP, @@ -108,7 +110,8 @@ class PacedSender : public PacedPacketSender, // Returns the next packet to send. RTCP packets have highest priority, // resend packets have second highest priority and then comes everything // else. - PacketRef GetNextPacket(PacketType* packet_type); + PacketRef GetNextPacket(PacketType* packet_type, + PacketKey* packet_key); base::TickClock* const clock_; // Not owned by this class. LoggingImpl* const logging_; // Not owned by this class. @@ -117,6 +120,8 @@ class PacedSender : public PacedPacketSender, uint32 audio_ssrc_; uint32 video_ssrc_; std::map<PacketKey, std::pair<PacketType, PacketRef> > packet_list_; + std::map<PacketKey, base::TimeTicks> sent_time_; + std::map<PacketKey, base::TimeTicks> sent_time_buffer_; // Maximum burst size for the next three bursts. size_t max_burst_size_; diff --git a/media/cast/transport/pacing/paced_sender_unittest.cc b/media/cast/transport/pacing/paced_sender_unittest.cc index ef9d89b5f0..5e24fca4b5 100644 --- a/media/cast/transport/pacing/paced_sender_unittest.cc +++ b/media/cast/transport/pacing/paced_sender_unittest.cc @@ -129,7 +129,7 @@ TEST_F(PacedSenderTest, PassThroughRtcp) { SendPacketVector packets = CreateSendPacketVector(kSize1, 1, true); EXPECT_TRUE(paced_sender_->SendPackets(packets)); - EXPECT_TRUE(paced_sender_->ResendPackets(packets)); + EXPECT_TRUE(paced_sender_->ResendPackets(packets, base::TimeDelta())); mock_transport_.AddExpectedSize(kSize2, 1); Packet tmp(kSize2, kValue); @@ -202,7 +202,7 @@ TEST_F(PacedSenderTest, PaceWithNack) { EXPECT_TRUE(paced_sender_->SendPackets(first_frame_packets)); // Add first NACK request. - EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets)); + EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, base::TimeDelta())); // Check that we get the first NACK burst. mock_transport_.AddExpectedSize(kNackSize, 10); @@ -211,7 +211,7 @@ TEST_F(PacedSenderTest, PaceWithNack) { task_runner_->RunTasks(); // Add second NACK request. - EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets)); + EXPECT_TRUE(paced_sender_->ResendPackets(nack_packets, base::TimeDelta())); // Check that we get the next NACK burst. mock_transport_.AddExpectedSize(kNackSize, 10); diff --git a/media/cast/transport/rtp_sender/rtp_sender.cc b/media/cast/transport/rtp_sender/rtp_sender.cc index 2604d253de..b807b34757 100644 --- a/media/cast/transport/rtp_sender/rtp_sender.cc +++ b/media/cast/transport/rtp_sender/rtp_sender.cc @@ -4,6 +4,7 @@ #include "media/cast/transport/rtp_sender/rtp_sender.h" +#include "base/big_endian.h" #include "base/logging.h" #include "base/rand_util.h" #include "media/cast/transport/cast_transport_defines.h" @@ -75,7 +76,8 @@ void RtpSender::SendFrame(const EncodedFrame& frame) { void RtpSender::ResendPackets( const MissingFramesAndPacketsMap& missing_frames_and_packets, - bool cancel_rtx_if_not_in_list) { + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window) { DCHECK(storage_); // Iterate over all frames in the list. for (MissingFramesAndPacketsMap::const_iterator it = @@ -129,15 +131,18 @@ void RtpSender::ResendPackets( transport_->CancelSendingPacket(it->first); } } - transport_->ResendPackets(packets_to_resend); + transport_->ResendPackets(packets_to_resend, dedupe_window); } } void RtpSender::UpdateSequenceNumber(Packet* packet) { - uint16 new_sequence_number = packetizer_->NextSequenceNumber(); - int index = 2; - (*packet)[index] = (static_cast<uint8>(new_sequence_number)); - (*packet)[index + 1] = (static_cast<uint8>(new_sequence_number >> 8)); + // TODO(miu): This is an abstraction violation. This needs to be a part of + // the overall packet (de)serialization consolidation. + static const int kByteOffsetToSequenceNumber = 2; + base::BigEndianWriter big_endian_writer( + reinterpret_cast<char*>((&packet->front()) + kByteOffsetToSequenceNumber), + sizeof(uint16)); + big_endian_writer.WriteU16(packetizer_->NextSequenceNumber()); } } // namespace transport diff --git a/media/cast/transport/rtp_sender/rtp_sender.h b/media/cast/transport/rtp_sender/rtp_sender.h index bfb46cb09e..e65326abf1 100644 --- a/media/cast/transport/rtp_sender/rtp_sender.h +++ b/media/cast/transport/rtp_sender/rtp_sender.h @@ -51,7 +51,8 @@ class RtpSender { void SendFrame(const EncodedFrame& frame); void ResendPackets(const MissingFramesAndPacketsMap& missing_packets, - bool cancel_rtx_if_not_in_list); + bool cancel_rtx_if_not_in_list, + base::TimeDelta dedupe_window); size_t send_packet_count() const { return packetizer_ ? packetizer_->send_packet_count() : 0; diff --git a/media/cast/transport/transport/udp_transport.cc b/media/cast/transport/transport/udp_transport.cc index bcce4f72f3..9669b17d43 100644 --- a/media/cast/transport/transport/udp_transport.cc +++ b/media/cast/transport/transport/udp_transport.cc @@ -54,6 +54,7 @@ UdpTransport::UdpTransport( send_pending_(false), receive_pending_(false), client_connected_(false), + next_dscp_value_(net::DSCP_NO_CHANGE), status_callback_(status_callback), weak_factory_(this) { DCHECK(!IsEmpty(local_end_point) || !IsEmpty(remote_end_point)); @@ -88,6 +89,11 @@ void UdpTransport::StartReceiving( ScheduleReceiveNextPacket(); } +void UdpTransport::SetDscp(net::DiffServCodePoint dscp) { + DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); + next_dscp_value_ = dscp; +} + void UdpTransport::ScheduleReceiveNextPacket() { DCHECK(io_thread_proxy_->RunsTasksOnCurrentThread()); if (!packet_receiver_.is_null() && !receive_pending_) { @@ -162,6 +168,16 @@ bool UdpTransport::SendPacket(PacketRef packet, const base::Closure& cb) { return true; } + if (next_dscp_value_ != net::DSCP_NO_CHANGE) { + int result = udp_socket_->SetDiffServCodePoint(next_dscp_value_); + if (result != net::OK) { + LOG(ERROR) << "Unable to set DSCP: " << next_dscp_value_ + << " to socket; Error: " << result; + } + // Don't change DSCP in next send. + next_dscp_value_ = net::DSCP_NO_CHANGE; + } + scoped_refptr<net::IOBuffer> buf = new net::WrappedIOBuffer(reinterpret_cast<char*>(&packet->data.front())); diff --git a/media/cast/transport/transport/udp_transport.h b/media/cast/transport/transport/udp_transport.h index 17b0d77599..1a568501d5 100644 --- a/media/cast/transport/transport/udp_transport.h +++ b/media/cast/transport/transport/udp_transport.h @@ -12,6 +12,7 @@ #include "media/cast/transport/cast_transport_config.h" #include "media/cast/transport/cast_transport_sender.h" #include "net/base/ip_endpoint.h" +#include "net/base/net_util.h" #include "net/udp/udp_socket.h" namespace net { @@ -46,6 +47,10 @@ class UdpTransport : public PacketSender { // Start receiving packets. Packets are submitted to |packet_receiver|. void StartReceiving(const PacketReceiverCallback& packet_receiver); + // Set a new DSCP value to the socket. The value will be set right before + // the next send. + void SetDscp(net::DiffServCodePoint dscp); + // PacketSender implementations. virtual bool SendPacket(PacketRef packet, const base::Closure& cb) OVERRIDE; @@ -72,6 +77,7 @@ class UdpTransport : public PacketSender { bool send_pending_; bool receive_pending_; bool client_connected_; + net::DiffServCodePoint next_dscp_value_; scoped_ptr<Packet> next_packet_; scoped_refptr<net::WrappedIOBuffer> recv_buf_; net::IPEndPoint recv_addr_; diff --git a/media/cast/video_sender/video_sender.cc b/media/cast/video_sender/video_sender.cc index cc8b158950..cf050b7f10 100644 --- a/media/cast/video_sender/video_sender.cc +++ b/media/cast/video_sender/video_sender.cc @@ -78,7 +78,7 @@ VideoSender::VideoSender( media::cast::transport::CastTransportVideoConfig transport_config; transport_config.codec = video_config.codec; transport_config.rtp.config = video_config.rtp_config; - transport_config.rtp.max_outstanding_frames = max_unacked_frames_ + 1; + transport_config.rtp.max_outstanding_frames = max_unacked_frames_; transport_sender_->InitializeVideo(transport_config); rtcp_.SetCastReceiverEventHistorySize(kReceiverRtcpEventHistorySize); @@ -321,7 +321,7 @@ void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { // A NACK is also used to cancel pending re-transmissions. transport_sender_->ResendPackets( - false, cast_feedback.missing_frames_and_packets_, true); + false, cast_feedback.missing_frames_and_packets_, true, rtt); } base::TimeTicks now = cast_environment_->Clock()->NowTicks(); @@ -348,7 +348,8 @@ void VideoSender::OnReceivedCastFeedback(const RtcpCastMessage& cast_feedback) { latest_acked_frame_id_++; missing_frames_and_packets[latest_acked_frame_id_] = missing; } - transport_sender_->ResendPackets(false, missing_frames_and_packets, true); + transport_sender_->ResendPackets( + false, missing_frames_and_packets, true, rtt); latest_acked_frame_id_ = cast_feedback.ack_frame_id_; } } @@ -382,10 +383,16 @@ void VideoSender::ResendForKickstart() { std::make_pair(last_sent_frame_id_, missing)); last_send_time_ = cast_environment_->Clock()->NowTicks(); + base::TimeDelta rtt; + base::TimeDelta avg_rtt; + base::TimeDelta min_rtt; + base::TimeDelta max_rtt; + rtcp_.Rtt(&rtt, &avg_rtt, &min_rtt, &max_rtt); + // Sending this extra packet is to kick-start the session. There is // no need to optimize re-transmission for this case. transport_sender_->ResendPackets(false, missing_frames_and_packets, - false); + false, rtt); } } // namespace cast diff --git a/media/cast/video_sender/video_sender.h b/media/cast/video_sender/video_sender.h index 30066cbbf8..cf8d27511c 100644 --- a/media/cast/video_sender/video_sender.h +++ b/media/cast/video_sender/video_sender.h @@ -76,15 +76,13 @@ class VideoSender : public RtcpSenderFeedback, void ScheduleNextRtcpReport(); void SendRtcpReport(bool schedule_future_reports); - // Schedule and execute periodic checks for re-sending frames. If no + // Schedule and execute periodic checks for re-sending packets. If no // acknowledgements have been received for "too long," VideoSender will - // speculatively re-send the frame just after |latest_acked_frame_id_| (the - // whole frame). This is a last resort tactic to prevent the session from + // speculatively re-send certain packets of an unacked frame to kick-start + // re-transmission. This is a last resort tactic to prevent the session from // getting stuck after a long outage. void ScheduleNextResendCheck(); void ResendCheck(); - - // Resend certain packets of an unacked frame to kick start re-transmission. void ResendForKickstart(); // Returns true if there are too many frames in flight, as defined by the @@ -169,10 +167,7 @@ class VideoSender : public RtcpSenderFeedback, // This is a "good enough" mapping for finding the RTP timestamp associated // with a video frame. The key is the lowest 8 bits of frame id (which is - // what is sent via RTCP). This map is used for logging purposes. The only - // time when this mapping will be incorrect is when it receives an ACK for a - // old enough frame such that 8-bit wrap around has already occurred, which - // should be pretty rare. + // what is sent via RTCP). This map is used for logging purposes. RtpTimestamp frame_id_to_rtp_timestamp_[256]; // NOTE: Weak pointers must be invalidated before all other member variables. |