diff options
Diffstat (limited to 'cast/streaming/compound_rtcp_builder.cc')
-rw-r--r-- | cast/streaming/compound_rtcp_builder.cc | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/cast/streaming/compound_rtcp_builder.cc b/cast/streaming/compound_rtcp_builder.cc new file mode 100644 index 00000000..b3cba68b --- /dev/null +++ b/cast/streaming/compound_rtcp_builder.cc @@ -0,0 +1,332 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/streaming/compound_rtcp_builder.h" + +#include <algorithm> +#include <iterator> +#include <limits> + +#include "cast/streaming/packet_util.h" +#include "cast/streaming/rtcp_session.h" +#include "util/integer_division.h" +#include "util/logging.h" +#include "util/std_util.h" + +using openscreen::AreElementsSortedAndUnique; +using openscreen::platform::Clock; + +namespace cast { +namespace streaming { + +CompoundRtcpBuilder::CompoundRtcpBuilder(RtcpSession* session) + : session_(session) { + OSP_DCHECK(session_); +} + +CompoundRtcpBuilder::~CompoundRtcpBuilder() = default; + +void CompoundRtcpBuilder::SetCheckpointFrame(FrameId frame_id) { + OSP_DCHECK_GE(frame_id, checkpoint_frame_id_); + checkpoint_frame_id_ = frame_id; +} + +void CompoundRtcpBuilder::SetPlayoutDelay(std::chrono::milliseconds delay) { + playout_delay_ = delay; +} + +void CompoundRtcpBuilder::SetPictureLossIndicator(bool picture_is_lost) { + picture_loss_indicator_ = picture_is_lost; +} + +void CompoundRtcpBuilder::IncludeReceiverReportInNextPacket( + const RtcpReportBlock& receiver_report) { + receiver_report_for_next_packet_ = receiver_report; +} + +void CompoundRtcpBuilder::IncludeFeedbackInNextPacket( + std::vector<PacketNack> packet_nacks, + std::vector<FrameId> frame_acks) { + // Note: Serialization of these lists will depend on the value of + // |checkpoint_frame_id_| when BuildPacket() is called later. + + nacks_for_next_packet_ = std::move(packet_nacks); + acks_for_next_packet_ = std::move(frame_acks); + +#if OSP_DCHECK_IS_ON() + OSP_DCHECK(AreElementsSortedAndUnique(nacks_for_next_packet_)); + OSP_DCHECK(AreElementsSortedAndUnique(acks_for_next_packet_)); + + // Consistency-check: An ACKed frame should not also be NACKed. + for (size_t ack_i = 0, nack_i = 0; ack_i < acks_for_next_packet_.size() && + nack_i < nacks_for_next_packet_.size();) { + const FrameId ack_frame_id = acks_for_next_packet_[ack_i]; + const FrameId nack_frame_id = nacks_for_next_packet_[nack_i].frame_id; + if (ack_frame_id < nack_frame_id) { + ++ack_i; + } else if (nack_frame_id < ack_frame_id) { + ++nack_i; + } else { + OSP_DCHECK_NE(ack_frame_id, nack_frame_id); + } + } + + // Redundancy-check: For any PacketNack whose packet ID is kAllPacketsLost, + // there should be no other PacketNack having the same FrameId. + for (size_t i = 1; i < nacks_for_next_packet_.size(); ++i) { + if (nacks_for_next_packet_[i].packet_id == kAllPacketsLost) { + // Since the elements are sorted, it's only necessary to check the + // immediately preceeding element to make sure it does not have the same + // FrameId. + OSP_DCHECK_NE(nacks_for_next_packet_[i].frame_id, + nacks_for_next_packet_[i - 1].frame_id); + } + } +#endif +} + +absl::Span<uint8_t> CompoundRtcpBuilder::BuildPacket( + Clock::time_point send_time, + absl::Span<uint8_t> buffer) { + OSP_CHECK_GE(buffer.size(), kRequiredBufferSize); + + uint8_t* const packet_begin = buffer.data(); + + // Receiver Report: Per RFC 3550, Section 6.4.2, all RTCP compound packets + // from receivers must include at least an empty receiver report at the start. + // It's not clear whether the Cast RTCP spec requires this, but it costs very + // little to do so. + AppendReceiverReportPacket(&buffer); + + // Receiver Reference Time Report: While this is optional in the Cast + // Streaming spec, it is always included by this implementation to improve the + // stability of the end-to-end system. + AppendReceiverReferenceTimeReportPacket(send_time, &buffer); + + // Picture Loss Indicator: Only included if the flag is currently set. + if (picture_loss_indicator_) { + AppendPictureLossIndicatorPacket(&buffer); + } + + // Cast Feedback: Checkpoint information, and add as many NACKs and ACKs as + // the remaning space available in the buffer will allow for. + AppendCastFeedbackPacket(&buffer); + + uint8_t* const packet_end = buffer.data(); + return absl::Span<uint8_t>(packet_begin, packet_end - packet_begin); +} + +void CompoundRtcpBuilder::AppendReceiverReportPacket( + absl::Span<uint8_t>* buffer) { + RtcpCommonHeader header; + header.packet_type = RtcpPacketType::kReceiverReport; + header.payload_size = kRtcpReceiverReportSize; + if (receiver_report_for_next_packet_) { + header.with.report_count = 1; + header.payload_size += kRtcpReportBlockSize; + } else { + header.with.report_count = 0; + } + header.AppendFields(buffer); + AppendField<uint32_t>(session_->receiver_ssrc(), buffer); + if (receiver_report_for_next_packet_) { + receiver_report_for_next_packet_->AppendFields(buffer); + receiver_report_for_next_packet_ = absl::nullopt; + } +} + +void CompoundRtcpBuilder::AppendReceiverReferenceTimeReportPacket( + Clock::time_point send_time, + absl::Span<uint8_t>* buffer) { + RtcpCommonHeader header; + header.packet_type = RtcpPacketType::kExtendedReports; + header.payload_size = kRtcpExtendedReportHeaderSize + + kRtcpExtendedReportBlockHeaderSize + + kRtcpReceiverReferenceTimeReportBlockSize; + header.AppendFields(buffer); + AppendField<uint32_t>(session_->receiver_ssrc(), buffer); + AppendField<uint8_t>(kRtcpReceiverReferenceTimeReportBlockType, buffer); + AppendField<uint8_t>(0 /* reserved/unused byte */, buffer); + AppendField<uint16_t>( + kRtcpReceiverReferenceTimeReportBlockSize / sizeof(uint32_t), buffer); + AppendField<uint64_t>(session_->ntp_converter().ToNtpTimestamp(send_time), + buffer); +} + +void CompoundRtcpBuilder::AppendPictureLossIndicatorPacket( + absl::Span<uint8_t>* buffer) { + RtcpCommonHeader header; + header.packet_type = RtcpPacketType::kPayloadSpecific; + header.with.subtype = RtcpSubtype::kPictureLossIndicator; + header.payload_size = kRtcpPictureLossIndicatorHeaderSize; + header.AppendFields(buffer); + AppendField<uint32_t>(session_->receiver_ssrc(), buffer); + AppendField<uint32_t>(session_->sender_ssrc(), buffer); +} + +void CompoundRtcpBuilder::AppendCastFeedbackPacket( + absl::Span<uint8_t>* buffer) { + // Reserve space for the RTCP Common Header. It will be serialized later, + // after the total size of the Cast Feedback message is known. + absl::Span<uint8_t> space_for_header = + ReserveSpace(kRtcpCommonHeaderSize, buffer); + uint8_t* const feedback_fields_begin = buffer->data(); + + // Append the mandatory fields. + AppendField<uint32_t>(session_->receiver_ssrc(), buffer); + AppendField<uint32_t>(session_->sender_ssrc(), buffer); + AppendField<uint32_t>(kRtcpCastIdentifierWord, buffer); + AppendField<uint8_t>(checkpoint_frame_id_.lower_8_bits(), buffer); + // The |loss_count_field| will be set after the Loss Fields are generated + // and the total count is known. + uint8_t* const loss_count_field = + ReserveSpace(sizeof(uint8_t), buffer).data(); + OSP_DCHECK_GT(playout_delay_.count(), 0); + OSP_DCHECK_LE(playout_delay_.count(), std::numeric_limits<uint16_t>::max()); + AppendField<uint16_t>(playout_delay_.count(), buffer); + + // Try to include as many Loss Fields as possible. Some of the NACKs might + // be dropped if the remaining space in the buffer is insufficient to + // include them all. + const int num_loss_fields = AppendCastFeedbackLossFields(buffer); + OSP_DCHECK_LE(num_loss_fields, std::numeric_limits<uint8_t>::max()); + *loss_count_field = num_loss_fields; + + // Try to include the CST2 header and ACK bit vector. Again, some of the + // ACKs might be dropped if the remaining space in the buffer is + // insufficient. + AppendCastFeedbackAckFields(buffer); + + // Go back and fill-in the header fields, now that the total size is known. + RtcpCommonHeader header; + header.packet_type = RtcpPacketType::kPayloadSpecific; + header.with.subtype = RtcpSubtype::kFeedback; + uint8_t* const feedback_fields_end = buffer->data(); + header.payload_size = feedback_fields_end - feedback_fields_begin; + header.AppendFields(&space_for_header); + + ++feedback_count_; +} + +int CompoundRtcpBuilder::AppendCastFeedbackLossFields( + absl::Span<uint8_t>* buffer) { + if (nacks_for_next_packet_.empty()) { + return 0; + } + + // The maximum number of entries is limited by available packet buffer space + // and the 8-bit |loss_count_field|. + const int max_num_loss_fields = + std::min<int>(buffer->size() / kRtcpFeedbackLossFieldSize, + std::numeric_limits<uint8_t>::max()); + + // Translate the |nacks_for_next_packet_| list into one or more entries + // representing specific packet losses. Omit any NACKs before the checkpoint. + OSP_DCHECK(AreElementsSortedAndUnique(nacks_for_next_packet_)); + auto it = + std::find_if(nacks_for_next_packet_.begin(), nacks_for_next_packet_.end(), + [this](const PacketNack& nack) { + return nack.frame_id > checkpoint_frame_id_; + }); + int num_loss_fields = 0; + while (it != nacks_for_next_packet_.end() && + num_loss_fields != max_num_loss_fields) { + const FrameId frame_id = it->frame_id; + const FramePacketId first_packet_id = it->packet_id; + uint8_t bit_vector = 0; + for (++it; it != nacks_for_next_packet_.end() && it->frame_id == frame_id; + ++it) { + const int shift = it->packet_id - first_packet_id - 1; + if (shift >= 8) { + break; + } + bit_vector |= 1 << shift; + } + AppendField<uint8_t>(frame_id.lower_8_bits(), buffer); + AppendField<uint16_t>(first_packet_id, buffer); + AppendField<uint8_t>(bit_vector, buffer); + ++num_loss_fields; + } + + nacks_for_next_packet_.clear(); + return num_loss_fields; +} + +void CompoundRtcpBuilder::AppendCastFeedbackAckFields( + absl::Span<uint8_t>* buffer) { + // Return if there is not enough space for the CST2 header and the + // smallest-possible ACK bit vector. + if (buffer->size() < + (kRtcpFeedbackAckHeaderSize + kRtcpMinAckBitVectorOctets)) { + return; + } + + // Write the CST2 header and reserve/initialize the start of the ACK bit + // vector. + AppendField<uint32_t>(kRtcpCst2IdentifierWord, buffer); + AppendField<uint8_t>(feedback_count_, buffer); + // The octet count field is set later, after the total is known. + uint8_t* const octet_count_field = + ReserveSpace(sizeof(uint8_t), buffer).data(); + // Start with the minimum required number of bit vector octets. + uint8_t* const ack_bitvector = + ReserveSpace(kRtcpMinAckBitVectorOctets, buffer).data(); + int num_ack_bitvector_octets = kRtcpMinAckBitVectorOctets; + memset(ack_bitvector, 0, kRtcpMinAckBitVectorOctets); + + // Set the bits of the ACK bit vector, auto-expanding the number of ACK octets + // if necessary (and while there is still room in the buffer). + if (!acks_for_next_packet_.empty()) { + OSP_DCHECK(AreElementsSortedAndUnique(acks_for_next_packet_)); + const FrameId first_frame_id = checkpoint_frame_id_ + 2; + for (const FrameId frame_id : acks_for_next_packet_) { + const int bit_index = frame_id - first_frame_id; + if (bit_index < 0) { + continue; + } + constexpr int kBitsPerOctet = 8; + const int octet_index = bit_index / kBitsPerOctet; + + // If needed, attempt to increase the number of ACK octets. + if (octet_index >= num_ack_bitvector_octets) { + // Compute how many additional octets are needed. + constexpr int kIncrement = sizeof(uint32_t); + const int num_additional = + openscreen::DividePositivesRoundingUp( + (octet_index + 1) - num_ack_bitvector_octets, kIncrement) * + kIncrement; + + // If there is not enough room in the buffer to add more ACKs, then do + // not continue. Also, if the new total count would exceed the design + // limit, do not continue. + if (static_cast<int>(buffer->size()) < num_additional) { + break; + } + const int new_count = num_ack_bitvector_octets + num_additional; + if (new_count > kRtcpMaxAckBitVectorOctets) { + break; + } + + // Reserve the additional space from the buffer, and initialize to zero. + memset(ReserveSpace(num_additional, buffer).data(), 0, num_additional); + num_ack_bitvector_octets = new_count; + } + + // At this point, the ACK bit vector is valid at |octet_index|. Set the + // bit representing the ACK for |frame_id|. + const int shift = bit_index % kBitsPerOctet; + ack_bitvector[octet_index] |= 1 << shift; + } + } + + // Now that the total size of the ACK bit vector is known, go back and set the + // octet count field. + OSP_DCHECK_LE(num_ack_bitvector_octets, std::numeric_limits<uint8_t>::max()); + *octet_count_field = num_ack_bitvector_octets; + + acks_for_next_packet_.clear(); +} + +} // namespace streaming +} // namespace cast |