// 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 #include #include #include "cast/streaming/packet_util.h" #include "cast/streaming/rtcp_session.h" #include "util/integer_division.h" #include "util/osp_logging.h" #include "util/std_util.h" namespace openscreen { namespace cast { 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 packet_nacks, std::vector 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 CompoundRtcpBuilder::BuildPacket( Clock::time_point send_time, absl::Span 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(packet_begin, packet_end - packet_begin); } void CompoundRtcpBuilder::AppendReceiverReportPacket( absl::Span* 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(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* buffer) { RtcpCommonHeader header; header.packet_type = RtcpPacketType::kExtendedReports; header.payload_size = kRtcpExtendedReportHeaderSize + kRtcpExtendedReportBlockHeaderSize + kRtcpReceiverReferenceTimeReportBlockSize; header.AppendFields(buffer); AppendField(session_->receiver_ssrc(), buffer); AppendField(kRtcpReceiverReferenceTimeReportBlockType, buffer); AppendField(0 /* reserved/unused byte */, buffer); AppendField( kRtcpReceiverReferenceTimeReportBlockSize / sizeof(uint32_t), buffer); AppendField(session_->ntp_converter().ToNtpTimestamp(send_time), buffer); } void CompoundRtcpBuilder::AppendPictureLossIndicatorPacket( absl::Span* buffer) { RtcpCommonHeader header; header.packet_type = RtcpPacketType::kPayloadSpecific; header.with.subtype = RtcpSubtype::kPictureLossIndicator; header.payload_size = kRtcpPictureLossIndicatorHeaderSize; header.AppendFields(buffer); AppendField(session_->receiver_ssrc(), buffer); AppendField(session_->sender_ssrc(), buffer); } void CompoundRtcpBuilder::AppendCastFeedbackPacket( absl::Span* 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 space_for_header = ReserveSpace(kRtcpCommonHeaderSize, buffer); uint8_t* const feedback_fields_begin = buffer->data(); // Append the mandatory fields. AppendField(session_->receiver_ssrc(), buffer); AppendField(session_->sender_ssrc(), buffer); AppendField(kRtcpCastIdentifierWord, buffer); AppendField(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::max()); AppendField(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::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* 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(buffer->size() / kRtcpFeedbackLossFieldSize, std::numeric_limits::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(frame_id.lower_8_bits(), buffer); AppendField(first_packet_id, buffer); AppendField(bit_vector, buffer); ++num_loss_fields; } nacks_for_next_packet_.clear(); return num_loss_fields; } void CompoundRtcpBuilder::AppendCastFeedbackAckFields( absl::Span* 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(kRtcpCst2IdentifierWord, buffer); AppendField(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 = 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(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::max()); *octet_count_field = num_ack_bitvector_octets; acks_for_next_packet_.clear(); } } // namespace cast } // namespace openscreen