| /* | 
 |  *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | 
 |  * | 
 |  *  Use of this source code is governed by a BSD-style license | 
 |  *  that can be found in the LICENSE file in the root of the source | 
 |  *  tree. An additional intellectual property rights grant can be found | 
 |  *  in the file PATENTS.  All contributing project authors may | 
 |  *  be found in the AUTHORS file in the root of the source tree. | 
 |  */ | 
 |  | 
 | #include "webrtc/modules/rtp_rtcp/source/rtp_sender_video.h" | 
 |  | 
 | #include <assert.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 |  | 
 | #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" | 
 | #include "webrtc/modules/rtp_rtcp/source/producer_fec.h" | 
 | #include "webrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h" | 
 | #include "webrtc/modules/rtp_rtcp/source/rtp_format_vp8.h" | 
 | #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" | 
 | #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" | 
 | #include "webrtc/system_wrappers/interface/logging.h" | 
 | #include "webrtc/system_wrappers/interface/trace_event.h" | 
 |  | 
 | namespace webrtc { | 
 | enum { REDForFECHeaderLength = 1 }; | 
 |  | 
 | struct RtpPacket { | 
 |   uint16_t rtpHeaderLength; | 
 |   ForwardErrorCorrection::Packet* pkt; | 
 | }; | 
 |  | 
 | RTPSenderVideo::RTPSenderVideo(Clock* clock, | 
 |                                RTPSenderInterface* rtpSender) | 
 |     : _rtpSender(*rtpSender), | 
 |       _sendVideoCritsect(CriticalSectionWrapper::CreateCriticalSection()), | 
 |       _videoType(kRtpVideoGeneric), | 
 |       _videoCodecInformation(NULL), | 
 |       _maxBitrate(0), | 
 |       _retransmissionSettings(kRetransmitBaseLayer), | 
 |  | 
 |       // Generic FEC | 
 |       _fec(), | 
 |       _fecEnabled(false), | 
 |       _payloadTypeRED(-1), | 
 |       _payloadTypeFEC(-1), | 
 |       _numberFirstPartition(0), | 
 |       delta_fec_params_(), | 
 |       key_fec_params_(), | 
 |       producer_fec_(&_fec), | 
 |       _fecOverheadRate(clock, NULL), | 
 |       _videoBitrate(clock, NULL) { | 
 |   memset(&delta_fec_params_, 0, sizeof(delta_fec_params_)); | 
 |   memset(&key_fec_params_, 0, sizeof(key_fec_params_)); | 
 |   delta_fec_params_.max_fec_frames = key_fec_params_.max_fec_frames = 1; | 
 |   delta_fec_params_.fec_mask_type = key_fec_params_.fec_mask_type = | 
 |         kFecMaskRandom; | 
 | } | 
 |  | 
 | RTPSenderVideo::~RTPSenderVideo() | 
 | { | 
 |     if(_videoCodecInformation) | 
 |     { | 
 |         delete _videoCodecInformation; | 
 |     } | 
 |     delete _sendVideoCritsect; | 
 | } | 
 |  | 
 | void | 
 | RTPSenderVideo::SetVideoCodecType(RtpVideoCodecTypes videoType) | 
 | { | 
 |     CriticalSectionScoped cs(_sendVideoCritsect); | 
 |     _videoType = videoType; | 
 | } | 
 |  | 
 | RtpVideoCodecTypes | 
 | RTPSenderVideo::VideoCodecType() const | 
 | { | 
 |     return _videoType; | 
 | } | 
 |  | 
 | int32_t RTPSenderVideo::RegisterVideoPayload( | 
 |     const char payloadName[RTP_PAYLOAD_NAME_SIZE], | 
 |     const int8_t payloadType, | 
 |     const uint32_t maxBitRate, | 
 |     RtpUtility::Payload*& payload) { | 
 |   CriticalSectionScoped cs(_sendVideoCritsect); | 
 |  | 
 |   RtpVideoCodecTypes videoType = kRtpVideoGeneric; | 
 |   if (RtpUtility::StringCompare(payloadName, "VP8", 3)) { | 
 |     videoType = kRtpVideoVp8; | 
 |   } else if (RtpUtility::StringCompare(payloadName, "H264", 4)) { | 
 |     videoType = kRtpVideoH264; | 
 |   } else if (RtpUtility::StringCompare(payloadName, "I420", 4)) { | 
 |     videoType = kRtpVideoGeneric; | 
 |   } else { | 
 |     videoType = kRtpVideoGeneric; | 
 |   } | 
 |   payload = new RtpUtility::Payload; | 
 |   payload->name[RTP_PAYLOAD_NAME_SIZE - 1] = 0; | 
 |   strncpy(payload->name, payloadName, RTP_PAYLOAD_NAME_SIZE - 1); | 
 |   payload->typeSpecific.Video.videoCodecType = videoType; | 
 |   payload->typeSpecific.Video.maxRate = maxBitRate; | 
 |   payload->audio = false; | 
 |   return 0; | 
 | } | 
 |  | 
 | int32_t | 
 | RTPSenderVideo::SendVideoPacket(uint8_t* data_buffer, | 
 |                                 const uint16_t payload_length, | 
 |                                 const uint16_t rtp_header_length, | 
 |                                 const uint32_t capture_timestamp, | 
 |                                 int64_t capture_time_ms, | 
 |                                 StorageType storage, | 
 |                                 bool protect) { | 
 |   if(_fecEnabled) { | 
 |     int ret = 0; | 
 |     int fec_overhead_sent = 0; | 
 |     int video_sent = 0; | 
 |  | 
 |     RedPacket* red_packet = producer_fec_.BuildRedPacket(data_buffer, | 
 |                                                          payload_length, | 
 |                                                          rtp_header_length, | 
 |                                                          _payloadTypeRED); | 
 |     TRACE_EVENT_INSTANT2("webrtc_rtp", "Video::PacketRed", | 
 |                          "timestamp", capture_timestamp, | 
 |                          "seqnum", _rtpSender.SequenceNumber()); | 
 |     // Sending the media packet with RED header. | 
 |     int packet_success = _rtpSender.SendToNetwork( | 
 |         red_packet->data(), | 
 |         red_packet->length() - rtp_header_length, | 
 |         rtp_header_length, | 
 |         capture_time_ms, | 
 |         storage, | 
 |         PacedSender::kNormalPriority); | 
 |  | 
 |     ret |= packet_success; | 
 |  | 
 |     if (packet_success == 0) { | 
 |       video_sent += red_packet->length(); | 
 |     } | 
 |     delete red_packet; | 
 |     red_packet = NULL; | 
 |  | 
 |     if (protect) { | 
 |       ret = producer_fec_.AddRtpPacketAndGenerateFec(data_buffer, | 
 |                                                      payload_length, | 
 |                                                      rtp_header_length); | 
 |       if (ret != 0) | 
 |         return ret; | 
 |     } | 
 |  | 
 |     while (producer_fec_.FecAvailable()) { | 
 |       red_packet = producer_fec_.GetFecPacket( | 
 |           _payloadTypeRED, | 
 |           _payloadTypeFEC, | 
 |           _rtpSender.IncrementSequenceNumber(), | 
 |           rtp_header_length); | 
 |       StorageType storage = kDontRetransmit; | 
 |       if (_retransmissionSettings & kRetransmitFECPackets) { | 
 |         storage = kAllowRetransmission; | 
 |       } | 
 |       TRACE_EVENT_INSTANT2("webrtc_rtp", "Video::PacketFec", | 
 |                            "timestamp", capture_timestamp, | 
 |                            "seqnum", _rtpSender.SequenceNumber()); | 
 |       // Sending FEC packet with RED header. | 
 |       int packet_success = _rtpSender.SendToNetwork( | 
 |           red_packet->data(), | 
 |           red_packet->length() - rtp_header_length, | 
 |           rtp_header_length, | 
 |           capture_time_ms, | 
 |           storage, | 
 |           PacedSender::kNormalPriority); | 
 |  | 
 |       ret |= packet_success; | 
 |  | 
 |       if (packet_success == 0) { | 
 |         fec_overhead_sent += red_packet->length(); | 
 |       } | 
 |       delete red_packet; | 
 |       red_packet = NULL; | 
 |     } | 
 |     _videoBitrate.Update(video_sent); | 
 |     _fecOverheadRate.Update(fec_overhead_sent); | 
 |     return ret; | 
 |   } | 
 |   TRACE_EVENT_INSTANT2("webrtc_rtp", "Video::PacketNormal", | 
 |                        "timestamp", capture_timestamp, | 
 |                        "seqnum", _rtpSender.SequenceNumber()); | 
 |   int ret = _rtpSender.SendToNetwork(data_buffer, | 
 |                                      payload_length, | 
 |                                      rtp_header_length, | 
 |                                      capture_time_ms, | 
 |                                      storage, | 
 |                                      PacedSender::kNormalPriority); | 
 |   if (ret == 0) { | 
 |     _videoBitrate.Update(payload_length + rtp_header_length); | 
 |   } | 
 |   return ret; | 
 | } | 
 |  | 
 | int32_t | 
 | RTPSenderVideo::SendRTPIntraRequest() | 
 | { | 
 |     // RFC 2032 | 
 |     // 5.2.1.  Full intra-frame Request (FIR) packet | 
 |  | 
 |     uint16_t length = 8; | 
 |     uint8_t data[8]; | 
 |     data[0] = 0x80; | 
 |     data[1] = 192; | 
 |     data[2] = 0; | 
 |     data[3] = 1; // length | 
 |  | 
 |     RtpUtility::AssignUWord32ToBuffer(data + 4, _rtpSender.SSRC()); | 
 |  | 
 |     TRACE_EVENT_INSTANT1("webrtc_rtp", | 
 |                          "Video::IntraRequest", | 
 |                          "seqnum", _rtpSender.SequenceNumber()); | 
 |     return _rtpSender.SendToNetwork(data, 0, length, -1, kDontStore, | 
 |                                     PacedSender::kNormalPriority); | 
 | } | 
 |  | 
 | int32_t | 
 | RTPSenderVideo::SetGenericFECStatus(const bool enable, | 
 |                                     const uint8_t payloadTypeRED, | 
 |                                     const uint8_t payloadTypeFEC) | 
 | { | 
 |     _fecEnabled = enable; | 
 |     _payloadTypeRED = payloadTypeRED; | 
 |     _payloadTypeFEC = payloadTypeFEC; | 
 |     memset(&delta_fec_params_, 0, sizeof(delta_fec_params_)); | 
 |     memset(&key_fec_params_, 0, sizeof(key_fec_params_)); | 
 |     delta_fec_params_.max_fec_frames = key_fec_params_.max_fec_frames = 1; | 
 |     delta_fec_params_.fec_mask_type = key_fec_params_.fec_mask_type = | 
 |           kFecMaskRandom; | 
 |     return 0; | 
 | } | 
 |  | 
 | int32_t | 
 | RTPSenderVideo::GenericFECStatus(bool& enable, | 
 |                                  uint8_t& payloadTypeRED, | 
 |                                  uint8_t& payloadTypeFEC) const | 
 | { | 
 |     enable = _fecEnabled; | 
 |     payloadTypeRED = _payloadTypeRED; | 
 |     payloadTypeFEC = _payloadTypeFEC; | 
 |     return 0; | 
 | } | 
 |  | 
 | uint16_t | 
 | RTPSenderVideo::FECPacketOverhead() const | 
 | { | 
 |     if (_fecEnabled) | 
 |     { | 
 |       // Overhead is FEC headers plus RED for FEC header plus anything in RTP | 
 |       // header beyond the 12 bytes base header (CSRC list, extensions...) | 
 |       // This reason for the header extensions to be included here is that | 
 |       // from an FEC viewpoint, they are part of the payload to be protected. | 
 |       // (The base RTP header is already protected by the FEC header.) | 
 |       return ForwardErrorCorrection::PacketOverhead() + REDForFECHeaderLength + | 
 |              (_rtpSender.RTPHeaderLength() - kRtpHeaderSize); | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | int32_t RTPSenderVideo::SetFecParameters( | 
 |     const FecProtectionParams* delta_params, | 
 |     const FecProtectionParams* key_params) { | 
 |   assert(delta_params); | 
 |   assert(key_params); | 
 |   delta_fec_params_ = *delta_params; | 
 |   key_fec_params_ = *key_params; | 
 |   return 0; | 
 | } | 
 |  | 
 | int32_t RTPSenderVideo::SendVideo(const RtpVideoCodecTypes videoType, | 
 |                                   const FrameType frameType, | 
 |                                   const int8_t payloadType, | 
 |                                   const uint32_t captureTimeStamp, | 
 |                                   int64_t capture_time_ms, | 
 |                                   const uint8_t* payloadData, | 
 |                                   const uint32_t payloadSize, | 
 |                                   const RTPFragmentationHeader* fragmentation, | 
 |                                   VideoCodecInformation* codecInfo, | 
 |                                   const RTPVideoTypeHeader* rtpTypeHdr) { | 
 |   if (payloadSize == 0) { | 
 |     return -1; | 
 |   } | 
 |  | 
 |   if (frameType == kVideoFrameKey) { | 
 |     producer_fec_.SetFecParameters(&key_fec_params_, _numberFirstPartition); | 
 |   } else { | 
 |     producer_fec_.SetFecParameters(&delta_fec_params_, _numberFirstPartition); | 
 |   } | 
 |  | 
 |   // Default setting for number of first partition packets: | 
 |   // Will be extracted in SendVP8 for VP8 codec; other codecs use 0 | 
 |   _numberFirstPartition = 0; | 
 |  | 
 |   switch (videoType) { | 
 |     case kRtpVideoGeneric: | 
 |       return SendGeneric(frameType, | 
 |                          payloadType, | 
 |                          captureTimeStamp, | 
 |                          capture_time_ms, | 
 |                          payloadData, | 
 |                          payloadSize); | 
 |     case kRtpVideoVp8: | 
 |       return SendVP8(frameType, | 
 |                      payloadType, | 
 |                      captureTimeStamp, | 
 |                      capture_time_ms, | 
 |                      payloadData, | 
 |                      payloadSize, | 
 |                      fragmentation, | 
 |                      rtpTypeHdr); | 
 |     case kRtpVideoH264: | 
 |       return SendH264(frameType, | 
 |                       payloadType, | 
 |                       captureTimeStamp, | 
 |                       capture_time_ms, | 
 |                       payloadData, | 
 |                       payloadSize, | 
 |                       fragmentation, | 
 |                       rtpTypeHdr) | 
 |                  ? 0 | 
 |                  : -1; | 
 |     default: | 
 |       assert(false); | 
 |       break; | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | int32_t RTPSenderVideo::SendGeneric(const FrameType frame_type, | 
 |                                     const int8_t payload_type, | 
 |                                     const uint32_t capture_timestamp, | 
 |                                     int64_t capture_time_ms, | 
 |                                     const uint8_t* payload, | 
 |                                     uint32_t size) { | 
 |   assert(frame_type == kVideoFrameKey || frame_type == kVideoFrameDelta); | 
 |   uint16_t rtp_header_length = _rtpSender.RTPHeaderLength(); | 
 |   uint16_t max_length = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - | 
 |                         rtp_header_length - (1 /* generic header length */); | 
 |  | 
 |   // Fragment packets more evenly by splitting the payload up evenly. | 
 |   uint32_t num_packets = (size + max_length - 1) / max_length; | 
 |   uint32_t payload_length = (size + num_packets - 1) / num_packets; | 
 |   assert(payload_length <= max_length); | 
 |  | 
 |   // Fragment packet into packets of max MaxPayloadLength bytes payload. | 
 |   uint8_t buffer[IP_PACKET_SIZE]; | 
 |  | 
 |   uint8_t generic_header = RtpFormatVideoGeneric::kFirstPacketBit; | 
 |   if (frame_type == kVideoFrameKey) { | 
 |     generic_header |= RtpFormatVideoGeneric::kKeyFrameBit; | 
 |   } | 
 |  | 
 |   while (size > 0) { | 
 |     if (size < payload_length) { | 
 |       payload_length = size; | 
 |     } | 
 |     size -= payload_length; | 
 |  | 
 |     // MarkerBit is 1 on final packet (bytes_to_send == 0) | 
 |     if (_rtpSender.BuildRTPheader(buffer, payload_type, size == 0, | 
 |                                   capture_timestamp, | 
 |                                   capture_time_ms) != rtp_header_length) { | 
 |       return -1; | 
 |     } | 
 |  | 
 |     uint8_t* out_ptr = &buffer[rtp_header_length]; | 
 |  | 
 |     // Put generic header in packet | 
 |     *out_ptr++ = generic_header; | 
 |     // Remove first-packet bit, following packets are intermediate | 
 |     generic_header &= ~RtpFormatVideoGeneric::kFirstPacketBit; | 
 |  | 
 |     // Put payload in packet | 
 |     memcpy(out_ptr, payload, payload_length); | 
 |     payload += payload_length; | 
 |  | 
 |     if (SendVideoPacket(buffer, payload_length + 1, rtp_header_length, | 
 |                         capture_timestamp, capture_time_ms, | 
 |                         kAllowRetransmission, true)) { | 
 |       return -1; | 
 |     } | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | VideoCodecInformation* | 
 | RTPSenderVideo::CodecInformationVideo() | 
 | { | 
 |     return _videoCodecInformation; | 
 | } | 
 |  | 
 | void | 
 | RTPSenderVideo::SetMaxConfiguredBitrateVideo(const uint32_t maxBitrate) | 
 | { | 
 |     _maxBitrate = maxBitrate; | 
 | } | 
 |  | 
 | uint32_t | 
 | RTPSenderVideo::MaxConfiguredBitrateVideo() const | 
 | { | 
 |     return _maxBitrate; | 
 | } | 
 |  | 
 | int32_t | 
 | RTPSenderVideo::SendVP8(const FrameType frameType, | 
 |                         const int8_t payloadType, | 
 |                         const uint32_t captureTimeStamp, | 
 |                         int64_t capture_time_ms, | 
 |                         const uint8_t* payloadData, | 
 |                         const uint32_t payloadSize, | 
 |                         const RTPFragmentationHeader* fragmentation, | 
 |                         const RTPVideoTypeHeader* rtpTypeHdr) | 
 | { | 
 |     const uint16_t rtpHeaderLength = _rtpSender.RTPHeaderLength(); | 
 |  | 
 |     int32_t payloadBytesToSend = payloadSize; | 
 |     const uint8_t* data = payloadData; | 
 |  | 
 |     uint16_t maxPayloadLengthVP8 = _rtpSender.MaxDataPayloadLength(); | 
 |  | 
 |     assert(rtpTypeHdr); | 
 |     // Initialize disregarding partition boundaries: this will use kEqualSize | 
 |     // packetization mode, which produces ~equal size packets for each frame. | 
 |     RtpPacketizerVp8 packetizer(rtpTypeHdr->VP8, maxPayloadLengthVP8); | 
 |     packetizer.SetPayloadData(data, payloadBytesToSend, NULL); | 
 |  | 
 |     StorageType storage = kAllowRetransmission; | 
 |     if (rtpTypeHdr->VP8.temporalIdx == 0 && | 
 |         !(_retransmissionSettings & kRetransmitBaseLayer)) { | 
 |       storage = kDontRetransmit; | 
 |     } else if (rtpTypeHdr->VP8.temporalIdx != kNoTemporalIdx && | 
 |         rtpTypeHdr->VP8.temporalIdx > 0 && | 
 |         !(_retransmissionSettings & kRetransmitHigherLayers)) { | 
 |       storage = kDontRetransmit; | 
 |     } | 
 |  | 
 |     bool last = false; | 
 |     _numberFirstPartition = 0; | 
 |     // |rtpTypeHdr->VP8.temporalIdx| is zero for base layers, or kNoTemporalIdx | 
 |     // if the field isn't used (so all layers are the base layer).  We currently | 
 |     // only protect base layers, so look for these two cases. | 
 |     bool protect = rtpTypeHdr->VP8.temporalIdx == 0 || | 
 |         rtpTypeHdr->VP8.temporalIdx == kNoTemporalIdx; | 
 |     while (!last) | 
 |     { | 
 |         // Write VP8 Payload Descriptor and VP8 payload. | 
 |         uint8_t dataBuffer[IP_PACKET_SIZE] = {0}; | 
 |         size_t payloadBytesInPacket = 0; | 
 |         if (!packetizer.NextPacket( | 
 |                 &dataBuffer[rtpHeaderLength], &payloadBytesInPacket, &last)) | 
 |           return -1; | 
 |  | 
 |         // Write RTP header. | 
 |         // Set marker bit true if this is the last packet in frame. | 
 |         _rtpSender.BuildRTPheader(dataBuffer, payloadType, last, | 
 |             captureTimeStamp, capture_time_ms); | 
 |         if (-1 == SendVideoPacket(dataBuffer, payloadBytesInPacket, | 
 |                                   rtpHeaderLength, captureTimeStamp, | 
 |                                   capture_time_ms, storage, protect)) | 
 |         { | 
 |           LOG(LS_WARNING) | 
 |               << "RTPSenderVideo::SendVP8 failed to send packet number " | 
 |               << _rtpSender.SequenceNumber(); | 
 |         } | 
 |     } | 
 |     TRACE_EVENT_ASYNC_END1("webrtc", "Video", capture_time_ms, | 
 |                            "timestamp", _rtpSender.Timestamp()); | 
 |     return 0; | 
 | } | 
 |  | 
 | bool RTPSenderVideo::SendH264(const FrameType frameType, | 
 |                               const int8_t payloadType, | 
 |                               const uint32_t captureTimeStamp, | 
 |                               int64_t capture_time_ms, | 
 |                               const uint8_t* payloadData, | 
 |                               const uint32_t payloadSize, | 
 |                               const RTPFragmentationHeader* fragmentation, | 
 |                               const RTPVideoTypeHeader* rtpTypeHdr) { | 
 |   size_t rtp_header_length = _rtpSender.RTPHeaderLength(); | 
 |   int32_t payload_bytes_to_send = payloadSize; | 
 |   const uint8_t* data = payloadData; | 
 |   size_t max_payload_length = _rtpSender.MaxDataPayloadLength(); | 
 |  | 
 |   scoped_ptr<RtpPacketizer> packetizer( | 
 |       RtpPacketizer::Create(kRtpVideoH264, max_payload_length)); | 
 |   packetizer->SetPayloadData(data, payload_bytes_to_send, fragmentation); | 
 |  | 
 |   StorageType storage = kAllowRetransmission; | 
 |   bool protect = (frameType == kVideoFrameKey); | 
 |   bool last = false; | 
 |  | 
 |   while (!last) { | 
 |     // Write H264 payload. | 
 |     uint8_t dataBuffer[IP_PACKET_SIZE] = {0}; | 
 |     size_t payload_bytes_in_packet = 0; | 
 |     if (!packetizer->NextPacket( | 
 |             &dataBuffer[rtp_header_length], &payload_bytes_in_packet, &last)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     // Write RTP header. | 
 |     // Set marker bit true if this is the last packet in frame. | 
 |     _rtpSender.BuildRTPheader( | 
 |         dataBuffer, payloadType, last, captureTimeStamp, capture_time_ms); | 
 |     if (SendVideoPacket(dataBuffer, | 
 |                         payload_bytes_in_packet, | 
 |                         rtp_header_length, | 
 |                         captureTimeStamp, | 
 |                         capture_time_ms, | 
 |                         storage, | 
 |                         protect)) { | 
 |       LOG(LS_WARNING) | 
 |           << "RTPSenderVideo::SendH264 failed to send packet number " | 
 |           << _rtpSender.SequenceNumber(); | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void RTPSenderVideo::ProcessBitrate() { | 
 |   _videoBitrate.Process(); | 
 |   _fecOverheadRate.Process(); | 
 | } | 
 |  | 
 | uint32_t RTPSenderVideo::VideoBitrateSent() const { | 
 |   return _videoBitrate.BitrateLast(); | 
 | } | 
 |  | 
 | uint32_t RTPSenderVideo::FecOverheadRate() const { | 
 |   return _fecOverheadRate.BitrateLast(); | 
 | } | 
 |  | 
 | int RTPSenderVideo::SelectiveRetransmissions() const { | 
 |   return _retransmissionSettings; | 
 | } | 
 |  | 
 | int RTPSenderVideo::SetSelectiveRetransmissions(uint8_t settings) { | 
 |   _retransmissionSettings = settings; | 
 |   return 0; | 
 | } | 
 |  | 
 | }  // namespace webrtc |