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 |