| Index: webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc | 
| diff --git a/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc b/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc | 
| index 38450742be9e29022cbabd2245952a88a1b09ddf..4e46b78baa6090fd98c68529b6d1a9700fa735fc 100644 | 
| --- a/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc | 
| +++ b/webrtc/modules/rtp_rtcp/source/rtp_sender_video.cc | 
| @@ -13,9 +13,10 @@ | 
| #include <stdlib.h> | 
| #include <string.h> | 
|  | 
| +#include <limits> | 
| #include <memory> | 
| -#include <vector> | 
| #include <utility> | 
| +#include <vector> | 
|  | 
| #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h" | 
| #include "webrtc/modules/rtp_rtcp/source/byte_io.h" | 
| @@ -33,6 +34,7 @@ namespace webrtc { | 
|  | 
| namespace { | 
| constexpr size_t kRedForFecHeaderLength = 1; | 
| +constexpr int64_t kMaxUnretransmittableFrameIntervalMs = 33 * 4; | 
|  | 
| void BuildRedPayload(const RtpPacketToSend& media_packet, | 
| RtpPacketToSend* red_packet) { | 
| @@ -53,7 +55,8 @@ RTPSenderVideo::RTPSenderVideo(Clock* clock, | 
| : rtp_sender_(rtp_sender), | 
| clock_(clock), | 
| video_type_(kRtpVideoGeneric), | 
| -      retransmission_settings_(kRetransmitBaseLayer), | 
| +      retransmission_settings_(kRetransmitBaseLayer | | 
| +                               kConditionallyRetransmitHigherLayers), | 
| last_rotation_(kVideoRotation_0), | 
| red_payload_type_(-1), | 
| ulpfec_payload_type_(-1), | 
| @@ -292,7 +295,8 @@ bool RTPSenderVideo::SendVideo(RtpVideoCodecTypes video_type, | 
| const uint8_t* payload_data, | 
| size_t payload_size, | 
| const RTPFragmentationHeader* fragmentation, | 
| -                               const RTPVideoHeader* video_header) { | 
| +                               const RTPVideoHeader* video_header, | 
| +                               int64_t expected_retransmission_time_ms) { | 
| if (payload_size == 0) | 
| return false; | 
|  | 
| @@ -365,8 +369,11 @@ bool RTPSenderVideo::SendVideo(RtpVideoCodecTypes video_type, | 
| std::unique_ptr<RtpPacketizer> packetizer(RtpPacketizer::Create( | 
| video_type, max_data_payload_length, last_packet_reduction_len, | 
| video_header ? &(video_header->codecHeader) : nullptr, frame_type)); | 
| -  // Media packet storage. | 
| -  StorageType storage = packetizer->GetStorageType(retransmission_settings); | 
| + | 
| +  const uint8_t temporal_id = | 
| +      video_header ? GetTemporalId(*video_header) : kNoTemporalIdx; | 
| +  StorageType storage = GetStorageType(temporal_id, retransmission_settings, | 
| +                                       expected_retransmission_time_ms); | 
|  | 
| // TODO(changbin): we currently don't support to configure the codec to | 
| // output multiple partitions for VP8. Should remove below check after the | 
| @@ -392,7 +399,9 @@ bool RTPSenderVideo::SendVideo(RtpVideoCodecTypes video_type, | 
| if (!rtp_sender_->AssignSequenceNumber(packet.get())) | 
| return false; | 
|  | 
| -    bool protect_packet = (packetizer->GetProtectionType() == kProtectedPacket); | 
| +    // No FEC protection for upper temporal layers, if used. | 
| +    bool protect_packet = temporal_id == 0 || temporal_id == kNoTemporalIdx; | 
| + | 
| // Put packetization finish timestamp into extension. | 
| if (packet->HasExtension<VideoTimingExtension>()) { | 
| packet->set_packetization_finish_time_ms(clock_->TimeInMilliseconds()); | 
| @@ -453,4 +462,90 @@ void RTPSenderVideo::SetSelectiveRetransmissions(uint8_t settings) { | 
| retransmission_settings_ = settings; | 
| } | 
|  | 
| +StorageType RTPSenderVideo::GetStorageType( | 
| +    uint8_t temporal_id, | 
| +    int32_t retransmission_settings, | 
| +    int64_t expected_retransmission_time_ms) { | 
| +  if (retransmission_settings == kRetransmitOff) | 
| +    return StorageType::kDontRetransmit; | 
| +  if (retransmission_settings == kRetransmitAllPackets) | 
| +    return StorageType::kAllowRetransmission; | 
| + | 
| +  rtc::CritScope cs(&stats_crit_); | 
| +  // Media packet storage. | 
| +  if ((retransmission_settings & kConditionallyRetransmitHigherLayers) && | 
| +      UpdateConditionalRetransmit(temporal_id, | 
| +                                  expected_retransmission_time_ms)) { | 
| +    retransmission_settings |= kRetransmitHigherLayers; | 
| +  } | 
| + | 
| +  if (temporal_id == kNoTemporalIdx) | 
| +    return kAllowRetransmission; | 
| + | 
| +  if ((retransmission_settings & kRetransmitBaseLayer) && temporal_id == 0) | 
| +    return kAllowRetransmission; | 
| + | 
| +  if ((retransmission_settings & kRetransmitHigherLayers) && temporal_id > 0) | 
| +    return kAllowRetransmission; | 
| + | 
| +  return kDontRetransmit; | 
| +} | 
| + | 
| +uint8_t RTPSenderVideo::GetTemporalId(const RTPVideoHeader& header) { | 
| +  switch (header.codec) { | 
| +    case kRtpVideoVp8: | 
| +      return header.codecHeader.VP8.temporalIdx; | 
| +    case kRtpVideoVp9: | 
| +      return header.codecHeader.VP9.temporal_idx; | 
| +    default: | 
| +      return kNoTemporalIdx; | 
| +  } | 
| +} | 
| + | 
| +bool RTPSenderVideo::UpdateConditionalRetransmit( | 
| +    uint8_t temporal_id, | 
| +    int64_t expected_retransmission_time_ms) { | 
| +  int64_t now_ms = clock_->TimeInMilliseconds(); | 
| +  // Update stats for any temporal layer. | 
| +  TemporalLayerStats* current_layer_stats = | 
| +      &frame_stats_by_temporal_layer_[temporal_id]; | 
| +  current_layer_stats->frame_rate_fp1000s.Update(1, now_ms); | 
| +  int64_t tl_frame_interval = now_ms - current_layer_stats->last_frame_time_ms; | 
| +  current_layer_stats->last_frame_time_ms = now_ms; | 
| + | 
| +  // Conditional retransmit only applies to upper layers. | 
| +  if (temporal_id != kNoTemporalIdx && temporal_id > 0) { | 
| +    if (tl_frame_interval >= kMaxUnretransmittableFrameIntervalMs) { | 
| +      // Too long since a retransmittable frame in this layer, enable NACK | 
| +      // protection. | 
| +      return true; | 
| +    } else { | 
| +      // Estimate when the next frame of any lower layer will be sent. | 
| +      const int64_t kUndefined = std::numeric_limits<int64_t>::max(); | 
| +      int64_t expected_next_frame_time = kUndefined; | 
| +      for (int i = temporal_id - 1; i >= 0; --i) { | 
| +        TemporalLayerStats* stats = &frame_stats_by_temporal_layer_[i]; | 
| +        rtc::Optional<uint32_t> rate = stats->frame_rate_fp1000s.Rate(now_ms); | 
| +        if (rate) { | 
| +          int64_t tl_next = stats->last_frame_time_ms + 1000000 / *rate; | 
| +          if (tl_next - now_ms > -expected_retransmission_time_ms && | 
| +              tl_next < expected_next_frame_time) { | 
| +            expected_next_frame_time = tl_next; | 
| +          } | 
| +        } | 
| +      } | 
| + | 
| +      if (expected_next_frame_time == kUndefined || | 
| +          expected_next_frame_time - now_ms > expected_retransmission_time_ms) { | 
| +        // The next frame in a lower layer is expected at a later time (or | 
| +        // unable to tell due to lack of data) than a retransmission is | 
| +        // estimated to be able to arrive, so allow this packet to be nacked. | 
| +        return true; | 
| +      } | 
| +    } | 
| +  } | 
| + | 
| +  return false; | 
| +} | 
| + | 
| }  // namespace webrtc | 
|  |