rtp_sender.cc revision 20ed36dada62ad56ec01263fc0eef0ed198f6476
1/* 2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#include "webrtc/modules/rtp_rtcp/source/rtp_sender.h" 12 13#include <cstdlib> // srand 14 15#include "webrtc/modules/pacing/include/paced_sender.h" 16#include "webrtc/modules/rtp_rtcp/source/rtp_packet_history.h" 17#include "webrtc/modules/rtp_rtcp/source/rtp_sender_audio.h" 18#include "webrtc/modules/rtp_rtcp/source/rtp_sender_video.h" 19#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" 20#include "webrtc/system_wrappers/interface/trace.h" 21 22namespace webrtc { 23RTPSender::RTPSender(const WebRtc_Word32 id, 24 const bool audio, 25 Clock* clock, 26 Transport* transport, 27 RtpAudioFeedback* audio_feedback, 28 PacedSender* paced_sender) 29 : Bitrate(clock), 30 _id(id), 31 _audioConfigured(audio), 32 _audio(NULL), 33 _video(NULL), 34 paced_sender_(paced_sender), 35 _sendCritsect(CriticalSectionWrapper::CreateCriticalSection()), 36 _transport(transport), 37 _sendingMedia(true), // Default to sending media 38 39 _maxPayloadLength(IP_PACKET_SIZE-28), // default is IP-v4/UDP 40 _targetSendBitrate(0), 41 _packetOverHead(28), 42 43 _payloadType(-1), 44 _payloadTypeMap(), 45 46 _rtpHeaderExtensionMap(), 47 _transmissionTimeOffset(0), 48 49 // NACK 50 _nackByteCountTimes(), 51 _nackByteCount(), 52 _nackBitrate(clock), 53 _packetHistory(new RTPPacketHistory(clock)), 54 55 // statistics 56 _packetsSent(0), 57 _payloadBytesSent(0), 58 59 _startTimeStampForced(false), 60 _startTimeStamp(0), 61 _ssrcDB(*SSRCDatabase::GetSSRCDatabase()), 62 _remoteSSRC(0), 63 _sequenceNumberForced(false), 64 _sequenceNumber(0), 65 _sequenceNumberRTX(0), 66 _ssrcForced(false), 67 _ssrc(0), 68 _timeStamp(0), 69 _CSRCs(0), 70 _CSRC(), 71 _includeCSRCs(true), 72 _RTX(false), 73 _ssrcRTX(0) { 74 memset(_nackByteCountTimes, 0, sizeof(_nackByteCountTimes)); 75 memset(_nackByteCount, 0, sizeof(_nackByteCount)); 76 memset(_CSRC, 0, sizeof(_CSRC)); 77 // We need to seed the random generator. 78 srand( (WebRtc_UWord32)clock_.TimeInMilliseconds() ); 79 _ssrc = _ssrcDB.CreateSSRC(); // Can't be 0. 80 81 if (audio) { 82 _audio = new RTPSenderAudio(id, &clock_, this); 83 _audio->RegisterAudioCallback(audio_feedback); 84 } else { 85 _video = new RTPSenderVideo(id, &clock_, this); 86 } 87 WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id, "%s created", __FUNCTION__); 88} 89 90RTPSender::~RTPSender() { 91 if (_remoteSSRC != 0) { 92 _ssrcDB.ReturnSSRC(_remoteSSRC); 93 } 94 _ssrcDB.ReturnSSRC(_ssrc); 95 96 SSRCDatabase::ReturnSSRCDatabase(); 97 delete _sendCritsect; 98 while (!_payloadTypeMap.empty()) { 99 std::map<WebRtc_Word8, ModuleRTPUtility::Payload*>::iterator it = 100 _payloadTypeMap.begin(); 101 delete it->second; 102 _payloadTypeMap.erase(it); 103 } 104 delete _packetHistory; 105 delete _audio; 106 delete _video; 107 108 WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, _id, "%s deleted", __FUNCTION__); 109} 110 111void RTPSender::SetTargetSendBitrate(const WebRtc_UWord32 bits) { 112 _targetSendBitrate = static_cast<uint16_t>(bits / 1000); 113} 114 115WebRtc_UWord16 RTPSender::ActualSendBitrateKbit() const { 116 return (WebRtc_UWord16) (Bitrate::BitrateNow() / 1000); 117} 118 119WebRtc_UWord32 RTPSender::VideoBitrateSent() const { 120 if (_video) { 121 return _video->VideoBitrateSent(); 122 } 123 return 0; 124} 125 126WebRtc_UWord32 RTPSender::FecOverheadRate() const { 127 if (_video) { 128 return _video->FecOverheadRate(); 129 } 130 return 0; 131} 132 133WebRtc_UWord32 RTPSender::NackOverheadRate() const { 134 return _nackBitrate.BitrateLast(); 135} 136 137WebRtc_Word32 RTPSender::SetTransmissionTimeOffset( 138 const WebRtc_Word32 transmissionTimeOffset) { 139 if (transmissionTimeOffset > (0x800000 - 1) || 140 transmissionTimeOffset < -(0x800000 - 1)) { // Word24 141 return -1; 142 } 143 CriticalSectionScoped cs(_sendCritsect); 144 _transmissionTimeOffset = transmissionTimeOffset; 145 return 0; 146} 147 148WebRtc_Word32 RTPSender::RegisterRtpHeaderExtension(const RTPExtensionType type, 149 const WebRtc_UWord8 id) { 150 CriticalSectionScoped cs(_sendCritsect); 151 return _rtpHeaderExtensionMap.Register(type, id); 152} 153 154WebRtc_Word32 RTPSender::DeregisterRtpHeaderExtension( 155 const RTPExtensionType type) { 156 CriticalSectionScoped cs(_sendCritsect); 157 return _rtpHeaderExtensionMap.Deregister(type); 158} 159 160WebRtc_UWord16 RTPSender::RtpHeaderExtensionTotalLength() const { 161 CriticalSectionScoped cs(_sendCritsect); 162 return _rtpHeaderExtensionMap.GetTotalLengthInBytes(); 163} 164 165WebRtc_Word32 RTPSender::RegisterPayload( 166 const char payloadName[RTP_PAYLOAD_NAME_SIZE], 167 const WebRtc_Word8 payloadNumber, 168 const WebRtc_UWord32 frequency, 169 const WebRtc_UWord8 channels, 170 const WebRtc_UWord32 rate) { 171 assert(payloadName); 172 CriticalSectionScoped cs(_sendCritsect); 173 174 std::map<WebRtc_Word8, ModuleRTPUtility::Payload*>::iterator it = 175 _payloadTypeMap.find(payloadNumber); 176 177 if (_payloadTypeMap.end() != it) { 178 // we already use this payload type 179 ModuleRTPUtility::Payload* payload = it->second; 180 assert(payload); 181 182 // check if it's the same as we already have 183 if (ModuleRTPUtility::StringCompare(payload->name, payloadName, 184 RTP_PAYLOAD_NAME_SIZE - 1)) { 185 if (_audioConfigured && payload->audio && 186 payload->typeSpecific.Audio.frequency == frequency && 187 (payload->typeSpecific.Audio.rate == rate || 188 payload->typeSpecific.Audio.rate == 0 || rate == 0)) { 189 payload->typeSpecific.Audio.rate = rate; 190 // Ensure that we update the rate if new or old is zero 191 return 0; 192 } 193 if (!_audioConfigured && !payload->audio) { 194 return 0; 195 } 196 } 197 return -1; 198 } 199 WebRtc_Word32 retVal = -1; 200 ModuleRTPUtility::Payload* payload = NULL; 201 if (_audioConfigured) { 202 retVal = _audio->RegisterAudioPayload(payloadName, payloadNumber, frequency, 203 channels, rate, payload); 204 } else { 205 retVal = _video->RegisterVideoPayload(payloadName, payloadNumber, rate, 206 payload); 207 } 208 if (payload) { 209 _payloadTypeMap[payloadNumber] = payload; 210 } 211 return retVal; 212} 213 214WebRtc_Word32 RTPSender::DeRegisterSendPayload(const WebRtc_Word8 payloadType) { 215 CriticalSectionScoped lock(_sendCritsect); 216 217 std::map<WebRtc_Word8, ModuleRTPUtility::Payload*>::iterator it = 218 _payloadTypeMap.find(payloadType); 219 220 if (_payloadTypeMap.end() == it) { 221 return -1; 222 } 223 ModuleRTPUtility::Payload* payload = it->second; 224 delete payload; 225 _payloadTypeMap.erase(it); 226 return 0; 227} 228 229WebRtc_Word8 RTPSender::SendPayloadType() const { 230 return _payloadType; 231} 232 233int RTPSender::SendPayloadFrequency() const { 234 return _audio->AudioFrequency(); 235} 236 237WebRtc_Word32 RTPSender::SetMaxPayloadLength( 238 const WebRtc_UWord16 maxPayloadLength, 239 const WebRtc_UWord16 packetOverHead) { 240 // sanity check 241 if (maxPayloadLength < 100 || maxPayloadLength > IP_PACKET_SIZE) { 242 WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, 243 "%s invalid argument", __FUNCTION__); 244 return -1; 245 } 246 CriticalSectionScoped cs(_sendCritsect); 247 _maxPayloadLength = maxPayloadLength; 248 _packetOverHead = packetOverHead; 249 250 WEBRTC_TRACE(kTraceInfo, kTraceRtpRtcp, _id, 251 "SetMaxPayloadLength to %d.", maxPayloadLength); 252 return 0; 253} 254 255WebRtc_UWord16 RTPSender::MaxDataPayloadLength() const { 256 if (_audioConfigured) { 257 return _maxPayloadLength - RTPHeaderLength(); 258 } else { 259 return _maxPayloadLength - RTPHeaderLength() - 260 _video->FECPacketOverhead() - ((_RTX) ? 2 : 0); 261 // Include the FEC/ULP/RED overhead. 262 } 263} 264 265WebRtc_UWord16 RTPSender::MaxPayloadLength() const { 266 return _maxPayloadLength; 267} 268 269WebRtc_UWord16 RTPSender::PacketOverHead() const { 270 return _packetOverHead; 271} 272 273void RTPSender::SetRTXStatus(const bool enable, 274 const bool setSSRC, 275 const WebRtc_UWord32 SSRC) { 276 CriticalSectionScoped cs(_sendCritsect); 277 _RTX = enable; 278 if (enable) { 279 if (setSSRC) { 280 _ssrcRTX = SSRC; 281 } else { 282 _ssrcRTX = _ssrcDB.CreateSSRC(); // can't be 0 283 } 284 } 285} 286 287void RTPSender::RTXStatus(bool* enable, WebRtc_UWord32* SSRC) const { 288 CriticalSectionScoped cs(_sendCritsect); 289 *enable = _RTX; 290 *SSRC = _ssrcRTX; 291} 292 293WebRtc_Word32 RTPSender::CheckPayloadType(const WebRtc_Word8 payloadType, 294 RtpVideoCodecTypes& videoType) { 295 CriticalSectionScoped cs(_sendCritsect); 296 297 if (payloadType < 0) { 298 WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, 299 "\tinvalid payloadType (%d)", payloadType); 300 return -1; 301 } 302 if (_audioConfigured) { 303 WebRtc_Word8 redPlType = -1; 304 if (_audio->RED(redPlType) == 0) { 305 // We have configured RED. 306 if (redPlType == payloadType) { 307 // And it's a match... 308 return 0; 309 } 310 } 311 } 312 if (_payloadType == payloadType) { 313 if (!_audioConfigured) { 314 videoType = _video->VideoCodecType(); 315 } 316 return 0; 317 } 318 std::map<WebRtc_Word8, ModuleRTPUtility::Payload*>::iterator it = 319 _payloadTypeMap.find(payloadType); 320 if (it == _payloadTypeMap.end()) { 321 WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, 322 "\tpayloadType:%d not registered", payloadType); 323 return -1; 324 } 325 _payloadType = payloadType; 326 ModuleRTPUtility::Payload* payload = it->second; 327 assert(payload); 328 if (!payload->audio && !_audioConfigured) { 329 _video->SetVideoCodecType(payload->typeSpecific.Video.videoCodecType); 330 videoType = payload->typeSpecific.Video.videoCodecType; 331 _video->SetMaxConfiguredBitrateVideo(payload->typeSpecific.Video.maxRate); 332 } 333 return 0; 334} 335 336WebRtc_Word32 RTPSender::SendOutgoingData( 337 const FrameType frame_type, 338 const WebRtc_Word8 payload_type, 339 const WebRtc_UWord32 capture_timestamp, 340 int64_t capture_time_ms, 341 const WebRtc_UWord8* payload_data, 342 const WebRtc_UWord32 payload_size, 343 const RTPFragmentationHeader* fragmentation, 344 VideoCodecInformation* codec_info, 345 const RTPVideoTypeHeader* rtp_type_hdr) { 346 { 347 // Drop this packet if we're not sending media packets. 348 CriticalSectionScoped cs(_sendCritsect); 349 if (!_sendingMedia) { 350 return 0; 351 } 352 } 353 RtpVideoCodecTypes video_type = kRtpNoVideo; 354 if (CheckPayloadType(payload_type, video_type) != 0) { 355 WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, 356 "%s invalid argument failed to find payloadType:%d", 357 __FUNCTION__, payload_type); 358 return -1; 359 } 360 361 if (_audioConfigured) { 362 assert(frame_type == kAudioFrameSpeech || 363 frame_type == kAudioFrameCN || 364 frame_type == kFrameEmpty); 365 366 return _audio->SendAudio(frame_type, payload_type, capture_timestamp, 367 payload_data, payload_size,fragmentation); 368 } else { 369 assert(frame_type != kAudioFrameSpeech && 370 frame_type != kAudioFrameCN); 371 372 if (frame_type == kFrameEmpty) { 373 return SendPaddingAccordingToBitrate(payload_type, capture_timestamp, 374 capture_time_ms); 375 } 376 return _video->SendVideo(video_type, 377 frame_type, 378 payload_type, 379 capture_timestamp, 380 capture_time_ms, 381 payload_data, 382 payload_size, 383 fragmentation, 384 codec_info, 385 rtp_type_hdr); 386 } 387} 388 389WebRtc_Word32 RTPSender::SendPaddingAccordingToBitrate( 390 WebRtc_Word8 payload_type, 391 WebRtc_UWord32 capture_timestamp, 392 int64_t capture_time_ms) { 393 // Current bitrate since last estimate(1 second) averaged with the 394 // estimate since then, to get the most up to date bitrate. 395 uint32_t current_bitrate = BitrateNow(); 396 int bitrate_diff = _targetSendBitrate * 1000 - current_bitrate; 397 if (bitrate_diff <= 0) { 398 return 0; 399 } 400 int bytes = 0; 401 if (current_bitrate == 0) { 402 // Start up phase. Send one 33.3 ms batch to start with. 403 bytes = (bitrate_diff / 8) / 30; 404 } else { 405 bytes = (bitrate_diff / 8); 406 // Cap at 200 ms of target send data. 407 int bytes_cap = _targetSendBitrate * 25; // 1000 / 8 / 5 408 if (bytes > bytes_cap) { 409 bytes = bytes_cap; 410 } 411 } 412 return SendPadData(payload_type, capture_timestamp, capture_time_ms, bytes); 413} 414 415WebRtc_Word32 RTPSender::SendPadData(WebRtc_Word8 payload_type, 416 WebRtc_UWord32 capture_timestamp, 417 int64_t capture_time_ms, 418 WebRtc_Word32 bytes) { 419 // Drop this packet if we're not sending media packets 420 if (!_sendingMedia) { 421 return 0; 422 } 423 // Max in the RFC 3550 is 255 bytes, we limit it to be modulus 32 for SRTP. 424 int max_length = 224; 425 WebRtc_UWord8 data_buffer[IP_PACKET_SIZE]; 426 427 for (; bytes > 0; bytes -= max_length) { 428 int padding_bytes_in_packet = max_length; 429 if (bytes < max_length) { 430 padding_bytes_in_packet = (bytes + 16) & 0xffe0; // Keep our modulus 32. 431 } 432 if (padding_bytes_in_packet < 32) { 433 // Sanity don't send empty packets. 434 break; 435 } 436 // Correct seq num, timestamp and payload type. 437 int header_length = BuildRTPheader(data_buffer, 438 payload_type, 439 false, // No markerbit. 440 capture_timestamp, 441 true, // Timestamp provided. 442 true); // Increment sequence number. 443 data_buffer[0] |= 0x20; // Set padding bit. 444 WebRtc_Word32* data = 445 reinterpret_cast<WebRtc_Word32*>(&(data_buffer[header_length])); 446 447 // Fill data buffer with random data. 448 for (int j = 0; j < (padding_bytes_in_packet >> 2); j++) { 449 data[j] = rand(); 450 } 451 // Set number of padding bytes in the last byte of the packet. 452 data_buffer[header_length + padding_bytes_in_packet - 1] = 453 padding_bytes_in_packet; 454 // Send the packet 455 if (0 > SendToNetwork(data_buffer, 456 padding_bytes_in_packet, 457 header_length, 458 capture_time_ms, 459 kDontRetransmit)) { 460 // Error sending the packet. 461 break; 462 } 463 } 464 if (bytes > 31) { // 31 due to our modulus 32. 465 // We did not manage to send all bytes. 466 return -1; 467 } 468 return 0; 469} 470 471void RTPSender::SetStorePacketsStatus( 472 const bool enable, 473 const WebRtc_UWord16 numberToStore) { 474 _packetHistory->SetStorePacketsStatus(enable, numberToStore); 475} 476 477bool RTPSender::StorePackets() const { 478 return _packetHistory->StorePackets(); 479} 480 481WebRtc_Word32 RTPSender::ReSendPacket(WebRtc_UWord16 packet_id, 482 WebRtc_UWord32 min_resend_time) { 483 484 WebRtc_UWord16 length = IP_PACKET_SIZE; 485 WebRtc_UWord8 data_buffer[IP_PACKET_SIZE]; 486 WebRtc_UWord8* buffer_to_send_ptr = data_buffer; 487 488 int64_t stored_time_in_ms; 489 StorageType type; 490 bool found = _packetHistory->GetRTPPacket(packet_id, 491 min_resend_time, data_buffer, &length, &stored_time_in_ms, &type); 492 if (!found) { 493 // Packet not found. 494 return 0; 495 } 496 if (length == 0 || type == kDontRetransmit) { 497 // No bytes copied (packet recently resent, skip resending) or 498 // packet should not be retransmitted. 499 return 0; 500 } 501 WebRtc_UWord8 data_buffer_rtx[IP_PACKET_SIZE]; 502 if (_RTX) { 503 buffer_to_send_ptr = data_buffer_rtx; 504 505 CriticalSectionScoped cs(_sendCritsect); 506 // Add RTX header. 507 ModuleRTPUtility::RTPHeaderParser rtpParser( 508 reinterpret_cast<const WebRtc_UWord8*>(data_buffer), 509 length); 510 511 WebRtcRTPHeader rtp_header; 512 rtpParser.Parse(rtp_header); 513 514 // Add original RTP header. 515 memcpy(data_buffer_rtx, data_buffer, rtp_header.header.headerLength); 516 517 // Replace sequence number. 518 WebRtc_UWord8* ptr = data_buffer_rtx + 2; 519 ModuleRTPUtility::AssignUWord16ToBuffer(ptr, _sequenceNumberRTX++); 520 521 // Replace SSRC. 522 ptr += 6; 523 ModuleRTPUtility::AssignUWord32ToBuffer(ptr, _ssrcRTX); 524 525 // Add OSN (original sequence number). 526 ptr = data_buffer_rtx + rtp_header.header.headerLength; 527 ModuleRTPUtility::AssignUWord16ToBuffer( 528 ptr, rtp_header.header.sequenceNumber); 529 ptr += 2; 530 531 // Add original payload data. 532 memcpy(ptr, 533 data_buffer + rtp_header.header.headerLength, 534 length - rtp_header.header.headerLength); 535 length += 2; 536 } 537 WebRtc_Word32 bytes_sent = ReSendToNetwork(buffer_to_send_ptr, length); 538 if (bytes_sent <= 0) { 539 WEBRTC_TRACE(kTraceWarning, kTraceRtpRtcp, _id, 540 "Transport failed to resend packet_id %u", packet_id); 541 return -1; 542 } 543 // Store the time when the packet was last resent. 544 _packetHistory->UpdateResendTime(packet_id); 545 return bytes_sent; 546} 547 548WebRtc_Word32 RTPSender::ReSendToNetwork(const WebRtc_UWord8* packet, 549 const WebRtc_UWord32 size) { 550 WebRtc_Word32 bytes_sent = -1; 551 if (_transport) { 552 bytes_sent = _transport->SendPacket(_id, packet, size); 553 } 554 if (bytes_sent <= 0) { 555 return -1; 556 } 557 // Update send statistics 558 CriticalSectionScoped cs(_sendCritsect); 559 Bitrate::Update(bytes_sent); 560 _packetsSent++; 561 // We on purpose don't add to _payloadBytesSent since this is a 562 // re-transmit and not new payload data. 563 return bytes_sent; 564} 565 566int RTPSender::SelectiveRetransmissions() const { 567 if (!_video) return -1; 568 return _video->SelectiveRetransmissions(); 569} 570 571int RTPSender::SetSelectiveRetransmissions(uint8_t settings) { 572 if (!_video) return -1; 573 return _video->SetSelectiveRetransmissions(settings); 574} 575 576void RTPSender::OnReceivedNACK(const WebRtc_UWord16 nackSequenceNumbersLength, 577 const WebRtc_UWord16* nackSequenceNumbers, 578 const WebRtc_UWord16 avgRTT) { 579 const WebRtc_Word64 now = clock_.TimeInMilliseconds(); 580 WebRtc_UWord32 bytesReSent = 0; 581 582 // Enough bandwidth to send NACK? 583 if (!ProcessNACKBitRate(now)) { 584 WEBRTC_TRACE(kTraceStream, 585 kTraceRtpRtcp, 586 _id, 587 "NACK bitrate reached. Skip sending NACK response. Target %d", 588 _targetSendBitrate); 589 return; 590 } 591 592 for (WebRtc_UWord16 i = 0; i < nackSequenceNumbersLength; ++i) { 593 const WebRtc_Word32 bytesSent = ReSendPacket(nackSequenceNumbers[i], 594 5+avgRTT); 595 if (bytesSent > 0) { 596 bytesReSent += bytesSent; 597 } else if (bytesSent == 0) { 598 // The packet has previously been resent. 599 // Try resending next packet in the list. 600 continue; 601 } else if (bytesSent < 0) { 602 // Failed to send one Sequence number. Give up the rest in this nack. 603 WEBRTC_TRACE(kTraceWarning, 604 kTraceRtpRtcp, 605 _id, 606 "Failed resending RTP packet %d, Discard rest of packets", 607 nackSequenceNumbers[i]); 608 break; 609 } 610 // delay bandwidth estimate (RTT * BW) 611 if (_targetSendBitrate != 0 && avgRTT) { 612 // kbits/s * ms = bits => bits/8 = bytes 613 WebRtc_UWord32 targetBytes = 614 (static_cast<WebRtc_UWord32>(_targetSendBitrate) * avgRTT) >> 3; 615 if (bytesReSent > targetBytes) { 616 break; // ignore the rest of the packets in the list 617 } 618 } 619 } 620 if (bytesReSent > 0) { 621 // TODO(pwestin) consolidate these two methods. 622 UpdateNACKBitRate(bytesReSent, now); 623 _nackBitrate.Update(bytesReSent); 624 } 625} 626 627bool RTPSender::ProcessNACKBitRate(const WebRtc_UWord32 now) { 628 WebRtc_UWord32 num = 0; 629 WebRtc_Word32 byteCount = 0; 630 const WebRtc_UWord32 avgInterval=1000; 631 632 CriticalSectionScoped cs(_sendCritsect); 633 634 if (_targetSendBitrate == 0) { 635 return true; 636 } 637 for (num = 0; num < NACK_BYTECOUNT_SIZE; num++) { 638 if ((now - _nackByteCountTimes[num]) > avgInterval) { 639 // don't use data older than 1sec 640 break; 641 } else { 642 byteCount += _nackByteCount[num]; 643 } 644 } 645 WebRtc_Word32 timeInterval = avgInterval; 646 if (num == NACK_BYTECOUNT_SIZE) { 647 // More than NACK_BYTECOUNT_SIZE nack messages has been received 648 // during the last msgInterval 649 timeInterval = now - _nackByteCountTimes[num-1]; 650 if (timeInterval < 0) { 651 timeInterval = avgInterval; 652 } 653 } 654 return (byteCount*8) < (_targetSendBitrate * timeInterval); 655} 656 657void RTPSender::UpdateNACKBitRate(const WebRtc_UWord32 bytes, 658 const WebRtc_UWord32 now) { 659 CriticalSectionScoped cs(_sendCritsect); 660 661 // save bitrate statistics 662 if (bytes > 0) { 663 if (now == 0) { 664 // add padding length 665 _nackByteCount[0] += bytes; 666 } else { 667 if (_nackByteCountTimes[0] == 0) { 668 // first no shift 669 } else { 670 // shift 671 for (int i = (NACK_BYTECOUNT_SIZE-2); i >= 0 ; i--) { 672 _nackByteCount[i+1] = _nackByteCount[i]; 673 _nackByteCountTimes[i+1] = _nackByteCountTimes[i]; 674 } 675 } 676 _nackByteCount[0] = bytes; 677 _nackByteCountTimes[0] = now; 678 } 679 } 680} 681 682void RTPSender::TimeToSendPacket(uint16_t sequence_number, 683 int64_t capture_time_ms) { 684 StorageType type; 685 uint16_t length = IP_PACKET_SIZE; 686 uint8_t data_buffer[IP_PACKET_SIZE]; 687 int64_t stored_time_ms; // TODO(pwestin) can we depricate this? 688 689 if (_packetHistory == NULL) { 690 return; 691 } 692 if (!_packetHistory->GetRTPPacket(sequence_number, 0, data_buffer, 693 &length, &stored_time_ms, &type)) { 694 assert(false); 695 return; 696 } 697 assert(length > 0); 698 699 ModuleRTPUtility::RTPHeaderParser rtpParser(data_buffer, length); 700 WebRtcRTPHeader rtp_header; 701 rtpParser.Parse(rtp_header); 702 703 int64_t diff_ms = clock_.TimeInMilliseconds() - capture_time_ms; 704 if (UpdateTransmissionTimeOffset(data_buffer, length, rtp_header, diff_ms)) { 705 // Update stored packet in case of receiving a re-transmission request. 706 _packetHistory->ReplaceRTPHeader(data_buffer, 707 rtp_header.header.sequenceNumber, 708 rtp_header.header.headerLength); 709 } 710 int bytes_sent = -1; 711 if (_transport) { 712 bytes_sent = _transport->SendPacket(_id, data_buffer, length); 713 } 714 if (bytes_sent <= 0) { 715 return; 716 } 717 // Update send statistics 718 CriticalSectionScoped cs(_sendCritsect); 719 Bitrate::Update(bytes_sent); 720 _packetsSent++; 721 if (bytes_sent > rtp_header.header.headerLength) { 722 _payloadBytesSent += bytes_sent - rtp_header.header.headerLength; 723 } 724} 725 726// TODO(pwestin): send in the RTPHeaderParser to avoid parsing it again 727WebRtc_Word32 RTPSender::SendToNetwork(uint8_t* buffer, 728 int payload_length, 729 int rtp_header_length, 730 int64_t capture_time_ms, 731 StorageType storage) { 732 ModuleRTPUtility::RTPHeaderParser rtpParser(buffer, 733 payload_length + rtp_header_length); 734 WebRtcRTPHeader rtp_header; 735 rtpParser.Parse(rtp_header); 736 737 // |capture_time_ms| <= 0 is considered invalid. 738 // TODO(holmer): This should be changed all over Video Engine so that negative 739 // time is consider invalid, while 0 is considered a valid time. 740 if (capture_time_ms > 0) { 741 int64_t time_now = clock_.TimeInMilliseconds(); 742 UpdateTransmissionTimeOffset(buffer, payload_length + rtp_header_length, 743 rtp_header, time_now - capture_time_ms); 744 } 745 // Used for NACK and to spread out the transmission of packets. 746 if (_packetHistory->PutRTPPacket(buffer, rtp_header_length + payload_length, 747 _maxPayloadLength, capture_time_ms, storage) != 0) { 748 return -1; 749 } 750 if (paced_sender_) { 751 if (!paced_sender_ ->SendPacket(PacedSender::kNormalPriority, 752 rtp_header.header.ssrc, 753 rtp_header.header.sequenceNumber, 754 capture_time_ms, 755 payload_length + rtp_header_length)) { 756 // We can't send the packet right now. 757 // We will be called when it is time. 758 return payload_length + rtp_header_length; 759 } 760 } 761 // Send packet 762 WebRtc_Word32 bytes_sent = -1; 763 if (_transport) { 764 bytes_sent = _transport->SendPacket(_id, 765 buffer, 766 payload_length + rtp_header_length); 767 } 768 if (bytes_sent <= 0) { 769 return -1; 770 } 771 // Update send statistics 772 CriticalSectionScoped cs(_sendCritsect); 773 Bitrate::Update(bytes_sent); 774 _packetsSent++; 775 if (bytes_sent > rtp_header_length) { 776 _payloadBytesSent += bytes_sent - rtp_header_length; 777 } 778 return 0; 779} 780 781void RTPSender::ProcessBitrate() { 782 CriticalSectionScoped cs(_sendCritsect); 783 Bitrate::Process(); 784 _nackBitrate.Process(); 785 if (_audioConfigured) { 786 return; 787 } 788 _video->ProcessBitrate(); 789} 790 791WebRtc_UWord16 RTPSender::RTPHeaderLength() const { 792 WebRtc_UWord16 rtpHeaderLength = 12; 793 if (_includeCSRCs) { 794 rtpHeaderLength += sizeof(WebRtc_UWord32)*_CSRCs; 795 } 796 rtpHeaderLength += RtpHeaderExtensionTotalLength(); 797 return rtpHeaderLength; 798} 799 800WebRtc_UWord16 RTPSender::IncrementSequenceNumber() { 801 CriticalSectionScoped cs(_sendCritsect); 802 return _sequenceNumber++; 803} 804 805void RTPSender::ResetDataCounters() { 806 _packetsSent = 0; 807 _payloadBytesSent = 0; 808} 809 810WebRtc_UWord32 RTPSender::Packets() const { 811 // Don't use critsect to avoid potental deadlock 812 return _packetsSent; 813} 814 815// number of sent RTP bytes 816// dont use critsect to avoid potental deadlock 817WebRtc_UWord32 RTPSender::Bytes() const { 818 return _payloadBytesSent; 819} 820 821WebRtc_Word32 RTPSender::BuildRTPheader(WebRtc_UWord8* dataBuffer, 822 const WebRtc_Word8 payloadType, 823 const bool markerBit, 824 const WebRtc_UWord32 captureTimeStamp, 825 const bool timeStampProvided, 826 const bool incSequenceNumber) { 827 assert(payloadType>=0); 828 CriticalSectionScoped cs(_sendCritsect); 829 830 dataBuffer[0] = static_cast<WebRtc_UWord8>(0x80); // version 2 831 dataBuffer[1] = static_cast<WebRtc_UWord8>(payloadType); 832 if (markerBit) { 833 dataBuffer[1] |= kRtpMarkerBitMask; // MarkerBit is set 834 } 835 if (timeStampProvided) { 836 _timeStamp = _startTimeStamp + captureTimeStamp; 837 } else { 838 // make a unique time stamp 839 // we can't inc by the actual time, since then we increase the risk of back 840 // timing. 841 _timeStamp++; 842 } 843 ModuleRTPUtility::AssignUWord16ToBuffer(dataBuffer+2, _sequenceNumber); 844 ModuleRTPUtility::AssignUWord32ToBuffer(dataBuffer+4, _timeStamp); 845 ModuleRTPUtility::AssignUWord32ToBuffer(dataBuffer+8, _ssrc); 846 WebRtc_Word32 rtpHeaderLength = 12; 847 848 // Add the CSRCs if any 849 if (_includeCSRCs && _CSRCs > 0) { 850 if (_CSRCs > kRtpCsrcSize) { 851 // error 852 assert(false); 853 return -1; 854 } 855 WebRtc_UWord8* ptr = &dataBuffer[rtpHeaderLength]; 856 for (WebRtc_UWord32 i = 0; i < _CSRCs; ++i) { 857 ModuleRTPUtility::AssignUWord32ToBuffer(ptr, _CSRC[i]); 858 ptr +=4; 859 } 860 dataBuffer[0] = (dataBuffer[0]&0xf0) | _CSRCs; 861 862 // Update length of header 863 rtpHeaderLength += sizeof(WebRtc_UWord32)*_CSRCs; 864 } 865 _sequenceNumber++; // prepare for next packet 866 867 WebRtc_UWord16 len = BuildRTPHeaderExtension(dataBuffer + rtpHeaderLength); 868 if (len) { 869 dataBuffer[0] |= 0x10; // set eXtension bit 870 rtpHeaderLength += len; 871 } 872 return rtpHeaderLength; 873} 874 875WebRtc_UWord16 RTPSender::BuildRTPHeaderExtension( 876 WebRtc_UWord8* dataBuffer) const { 877 if (_rtpHeaderExtensionMap.Size() <= 0) { 878 return 0; 879 } 880 /* RTP header extension, RFC 3550. 881 0 1 2 3 882 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 883 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 884 | defined by profile | length | 885 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 886 | header extension | 887 | .... | 888 */ 889 const WebRtc_UWord32 kPosLength = 2; 890 const WebRtc_UWord32 kHeaderLength = RTP_ONE_BYTE_HEADER_LENGTH_IN_BYTES; 891 892 // Add extension ID (0xBEDE). 893 ModuleRTPUtility::AssignUWord16ToBuffer(dataBuffer, 894 RTP_ONE_BYTE_HEADER_EXTENSION); 895 896 // Add extensions. 897 WebRtc_UWord16 total_block_length = 0; 898 899 RTPExtensionType type = _rtpHeaderExtensionMap.First(); 900 while (type != kRtpExtensionNone) { 901 WebRtc_UWord8 block_length = 0; 902 if (type == kRtpExtensionTransmissionTimeOffset) { 903 block_length = BuildTransmissionTimeOffsetExtension( 904 dataBuffer + kHeaderLength + total_block_length); 905 } 906 total_block_length += block_length; 907 type = _rtpHeaderExtensionMap.Next(type); 908 } 909 if (total_block_length == 0) { 910 // No extension added. 911 return 0; 912 } 913 // Set header length (in number of Word32, header excluded). 914 assert(total_block_length % 4 == 0); 915 ModuleRTPUtility::AssignUWord16ToBuffer(dataBuffer + kPosLength, 916 total_block_length / 4); 917 // Total added length. 918 return kHeaderLength + total_block_length; 919} 920 921WebRtc_UWord8 RTPSender::BuildTransmissionTimeOffsetExtension( 922 WebRtc_UWord8* dataBuffer) const { 923 // From RFC 5450: Transmission Time Offsets in RTP Streams. 924 // 925 // The transmission time is signaled to the receiver in-band using the 926 // general mechanism for RTP header extensions [RFC5285]. The payload 927 // of this extension (the transmitted value) is a 24-bit signed integer. 928 // When added to the RTP timestamp of the packet, it represents the 929 // "effective" RTP transmission time of the packet, on the RTP 930 // timescale. 931 // 932 // The form of the transmission offset extension block: 933 // 934 // 0 1 2 3 935 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 936 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 937 // | ID | len=2 | transmission offset | 938 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 939 940 // Get id defined by user. 941 WebRtc_UWord8 id; 942 if (_rtpHeaderExtensionMap.GetId(kRtpExtensionTransmissionTimeOffset, &id) 943 != 0) { 944 // Not registered. 945 return 0; 946 } 947 int pos = 0; 948 const WebRtc_UWord8 len = 2; 949 dataBuffer[pos++] = (id << 4) + len; 950 ModuleRTPUtility::AssignUWord24ToBuffer(dataBuffer + pos, 951 _transmissionTimeOffset); 952 pos += 3; 953 assert(pos == TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES); 954 return TRANSMISSION_TIME_OFFSET_LENGTH_IN_BYTES; 955} 956 957bool RTPSender::UpdateTransmissionTimeOffset( 958 WebRtc_UWord8* rtp_packet, 959 const WebRtc_UWord16 rtp_packet_length, 960 const WebRtcRTPHeader& rtp_header, 961 const WebRtc_Word64 time_diff_ms) const { 962 CriticalSectionScoped cs(_sendCritsect); 963 964 // Get length until start of transmission block. 965 int transmission_block_pos = 966 _rtpHeaderExtensionMap.GetLengthUntilBlockStartInBytes( 967 kRtpExtensionTransmissionTimeOffset); 968 if (transmission_block_pos < 0) { 969 WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, 970 "Failed to update transmission time offset, not registered."); 971 return false; 972 } 973 int block_pos = 12 + rtp_header.header.numCSRCs + transmission_block_pos; 974 if (rtp_packet_length < block_pos + 4 || 975 rtp_header.header.headerLength < block_pos + 4) { 976 WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, 977 "Failed to update transmission time offset, invalid length."); 978 return false; 979 } 980 // Verify that header contains extension. 981 if (!((rtp_packet[12 + rtp_header.header.numCSRCs] == 0xBE) && 982 (rtp_packet[12 + rtp_header.header.numCSRCs + 1] == 0xDE))) { 983 WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, 984 "Failed to update transmission time offset, hdr extension not found."); 985 return false; 986 } 987 // Get id. 988 WebRtc_UWord8 id = 0; 989 if (_rtpHeaderExtensionMap.GetId(kRtpExtensionTransmissionTimeOffset, 990 &id) != 0) { 991 WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, 992 "Failed to update transmission time offset, no id."); 993 return false; 994 } 995 // Verify first byte in block. 996 const WebRtc_UWord8 first_block_byte = (id << 4) + 2; 997 if (rtp_packet[block_pos] != first_block_byte) { 998 WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, _id, 999 "Failed to update transmission time offset."); 1000 return false; 1001 } 1002 // Update transmission offset field. 1003 ModuleRTPUtility::AssignUWord24ToBuffer(rtp_packet + block_pos + 1, 1004 time_diff_ms * 90); // RTP timestamp. 1005 return true; 1006} 1007 1008void RTPSender::SetSendingStatus(const bool enabled) { 1009 if (enabled) { 1010 WebRtc_UWord32 frequency_hz; 1011 if (_audioConfigured) { 1012 WebRtc_UWord32 frequency = _audio->AudioFrequency(); 1013 1014 // sanity 1015 switch(frequency) { 1016 case 8000: 1017 case 12000: 1018 case 16000: 1019 case 24000: 1020 case 32000: 1021 break; 1022 default: 1023 assert(false); 1024 return; 1025 } 1026 frequency_hz = frequency; 1027 } else { 1028 frequency_hz = kDefaultVideoFrequency; 1029 } 1030 WebRtc_UWord32 RTPtime = ModuleRTPUtility::GetCurrentRTP(&clock_, 1031 frequency_hz); 1032 1033 // will be ignored if it's already configured via API 1034 SetStartTimestamp(RTPtime, false); 1035 } else { 1036 if (!_ssrcForced) { 1037 // generate a new SSRC 1038 _ssrcDB.ReturnSSRC(_ssrc); 1039 _ssrc = _ssrcDB.CreateSSRC(); // can't be 0 1040 1041 } 1042 // Don't initialize seq number if SSRC passed externally. 1043 if (!_sequenceNumberForced && !_ssrcForced) { 1044 // generate a new sequence number 1045 _sequenceNumber = rand() / (RAND_MAX / MAX_INIT_RTP_SEQ_NUMBER); 1046 } 1047 } 1048} 1049 1050void RTPSender::SetSendingMediaStatus(const bool enabled) { 1051 CriticalSectionScoped cs(_sendCritsect); 1052 _sendingMedia = enabled; 1053} 1054 1055bool RTPSender::SendingMedia() const { 1056 CriticalSectionScoped cs(_sendCritsect); 1057 return _sendingMedia; 1058} 1059 1060WebRtc_UWord32 RTPSender::Timestamp() const { 1061 CriticalSectionScoped cs(_sendCritsect); 1062 return _timeStamp; 1063} 1064 1065void RTPSender::SetStartTimestamp(WebRtc_UWord32 timestamp, bool force) { 1066 CriticalSectionScoped cs(_sendCritsect); 1067 if (force) { 1068 _startTimeStampForced = force; 1069 _startTimeStamp = timestamp; 1070 } else { 1071 if (!_startTimeStampForced) { 1072 _startTimeStamp = timestamp; 1073 } 1074 } 1075} 1076 1077WebRtc_UWord32 RTPSender::StartTimestamp() const { 1078 CriticalSectionScoped cs(_sendCritsect); 1079 return _startTimeStamp; 1080} 1081 1082WebRtc_UWord32 RTPSender::GenerateNewSSRC() { 1083 // if configured via API, return 0 1084 CriticalSectionScoped cs(_sendCritsect); 1085 1086 if (_ssrcForced) { 1087 return 0; 1088 } 1089 _ssrc = _ssrcDB.CreateSSRC(); // can't be 0 1090 return _ssrc; 1091} 1092 1093void RTPSender::SetSSRC(WebRtc_UWord32 ssrc) { 1094 // this is configured via the API 1095 CriticalSectionScoped cs(_sendCritsect); 1096 1097 if (_ssrc == ssrc && _ssrcForced) { 1098 return; // since it's same ssrc, don't reset anything 1099 } 1100 _ssrcForced = true; 1101 _ssrcDB.ReturnSSRC(_ssrc); 1102 _ssrcDB.RegisterSSRC(ssrc); 1103 _ssrc = ssrc; 1104 if (!_sequenceNumberForced) { 1105 _sequenceNumber = rand() / (RAND_MAX / MAX_INIT_RTP_SEQ_NUMBER); 1106 } 1107} 1108 1109WebRtc_UWord32 RTPSender::SSRC() const { 1110 CriticalSectionScoped cs(_sendCritsect); 1111 return _ssrc; 1112} 1113 1114void RTPSender::SetCSRCStatus(const bool include) { 1115 _includeCSRCs = include; 1116} 1117 1118void RTPSender::SetCSRCs(const WebRtc_UWord32 arrOfCSRC[kRtpCsrcSize], 1119 const WebRtc_UWord8 arrLength) { 1120 assert(arrLength <= kRtpCsrcSize); 1121 CriticalSectionScoped cs(_sendCritsect); 1122 1123 for (int i = 0; i < arrLength;i++) { 1124 _CSRC[i] = arrOfCSRC[i]; 1125 } 1126 _CSRCs = arrLength; 1127} 1128 1129WebRtc_Word32 RTPSender::CSRCs(WebRtc_UWord32 arrOfCSRC[kRtpCsrcSize]) const { 1130 assert(arrOfCSRC); 1131 CriticalSectionScoped cs(_sendCritsect); 1132 for (int i = 0; i < _CSRCs && i < kRtpCsrcSize;i++) { 1133 arrOfCSRC[i] = _CSRC[i]; 1134 } 1135 return _CSRCs; 1136} 1137 1138void RTPSender::SetSequenceNumber(WebRtc_UWord16 seq) { 1139 CriticalSectionScoped cs(_sendCritsect); 1140 _sequenceNumberForced = true; 1141 _sequenceNumber = seq; 1142} 1143 1144WebRtc_UWord16 RTPSender::SequenceNumber() const { 1145 CriticalSectionScoped cs(_sendCritsect); 1146 return _sequenceNumber; 1147} 1148 1149/* 1150 * Audio 1151 */ 1152WebRtc_Word32 RTPSender::SendTelephoneEvent(const WebRtc_UWord8 key, 1153 const WebRtc_UWord16 time_ms, 1154 const WebRtc_UWord8 level) { 1155 if (!_audioConfigured) { 1156 return -1; 1157 } 1158 return _audio->SendTelephoneEvent(key, time_ms, level); 1159} 1160 1161bool RTPSender::SendTelephoneEventActive(WebRtc_Word8& telephoneEvent) const { 1162 if (!_audioConfigured) { 1163 return false; 1164 } 1165 return _audio->SendTelephoneEventActive(telephoneEvent); 1166} 1167 1168WebRtc_Word32 RTPSender::SetAudioPacketSize( 1169 const WebRtc_UWord16 packetSizeSamples) { 1170 if (!_audioConfigured) { 1171 return -1; 1172 } 1173 return _audio->SetAudioPacketSize(packetSizeSamples); 1174} 1175 1176WebRtc_Word32 1177RTPSender::SetAudioLevelIndicationStatus(const bool enable, 1178 const WebRtc_UWord8 ID) { 1179 if (!_audioConfigured) { 1180 return -1; 1181 } 1182 return _audio->SetAudioLevelIndicationStatus(enable, ID); 1183} 1184 1185WebRtc_Word32 RTPSender::AudioLevelIndicationStatus(bool& enable, 1186 WebRtc_UWord8& ID) const { 1187 return _audio->AudioLevelIndicationStatus(enable, ID); 1188} 1189 1190WebRtc_Word32 RTPSender::SetAudioLevel(const WebRtc_UWord8 level_dBov) { 1191 return _audio->SetAudioLevel(level_dBov); 1192} 1193 1194WebRtc_Word32 RTPSender::SetRED(const WebRtc_Word8 payloadType) { 1195 if (!_audioConfigured) { 1196 return -1; 1197 } 1198 return _audio->SetRED(payloadType); 1199} 1200 1201WebRtc_Word32 RTPSender::RED(WebRtc_Word8& payloadType) const { 1202 if (!_audioConfigured) { 1203 return -1; 1204 } 1205 return _audio->RED(payloadType); 1206} 1207 1208/* 1209 * Video 1210 */ 1211VideoCodecInformation* RTPSender::CodecInformationVideo() { 1212 if (_audioConfigured) { 1213 return NULL; 1214 } 1215 return _video->CodecInformationVideo(); 1216} 1217 1218RtpVideoCodecTypes RTPSender::VideoCodecType() const { 1219 if (_audioConfigured) { 1220 return kRtpNoVideo; 1221 } 1222 return _video->VideoCodecType(); 1223} 1224 1225WebRtc_UWord32 RTPSender::MaxConfiguredBitrateVideo() const { 1226 if (_audioConfigured) { 1227 return 0; 1228 } 1229 return _video->MaxConfiguredBitrateVideo(); 1230} 1231 1232WebRtc_Word32 RTPSender::SendRTPIntraRequest() { 1233 if (_audioConfigured) { 1234 return -1; 1235 } 1236 return _video->SendRTPIntraRequest(); 1237} 1238 1239WebRtc_Word32 RTPSender::SetGenericFECStatus( 1240 const bool enable, 1241 const WebRtc_UWord8 payloadTypeRED, 1242 const WebRtc_UWord8 payloadTypeFEC) { 1243 if (_audioConfigured) { 1244 return -1; 1245 } 1246 return _video->SetGenericFECStatus(enable, payloadTypeRED, payloadTypeFEC); 1247} 1248 1249WebRtc_Word32 RTPSender::GenericFECStatus(bool& enable, 1250 WebRtc_UWord8& payloadTypeRED, 1251 WebRtc_UWord8& payloadTypeFEC) const { 1252 if (_audioConfigured) { 1253 return -1; 1254 } 1255 return _video->GenericFECStatus(enable, payloadTypeRED, payloadTypeFEC); 1256} 1257 1258WebRtc_Word32 RTPSender::SetFecParameters( 1259 const FecProtectionParams* delta_params, 1260 const FecProtectionParams* key_params) { 1261 if (_audioConfigured) { 1262 return -1; 1263 } 1264 return _video->SetFecParameters(delta_params, key_params); 1265} 1266} // namespace webrtc 1267