| /* | |
| * libjingle | |
| * Copyright 2012 Google Inc. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * | |
| * 1. Redistributions of source code must retain the above copyright notice, | |
| * this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright notice, | |
| * this list of conditions and the following disclaimer in the documentation | |
| * and/or other materials provided with the distribution. | |
| * 3. The name of the author may not be used to endorse or promote products | |
| * derived from this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include "talk/media/base/rtpdataengine.h" | |
| #include "talk/media/base/codec.h" | |
| #include "talk/media/base/constants.h" | |
| #include "talk/media/base/rtputils.h" | |
| #include "talk/media/base/streamparams.h" | |
| #include "webrtc/base/buffer.h" | |
| #include "webrtc/base/helpers.h" | |
| #include "webrtc/base/logging.h" | |
| #include "webrtc/base/ratelimiter.h" | |
| #include "webrtc/base/timing.h" | |
| namespace cricket { | |
| // We want to avoid IP fragmentation. | |
| static const size_t kDataMaxRtpPacketLen = 1200U; | |
| // We reserve space after the RTP header for future wiggle room. | |
| static const unsigned char kReservedSpace[] = { | |
| 0x00, 0x00, 0x00, 0x00 | |
| }; | |
| // Amount of overhead SRTP may take. We need to leave room in the | |
| // buffer for it, otherwise SRTP will fail later. If SRTP ever uses | |
| // more than this, we need to increase this number. | |
| static const size_t kMaxSrtpHmacOverhead = 16; | |
| RtpDataEngine::RtpDataEngine() { | |
| data_codecs_.push_back( | |
| DataCodec(kGoogleRtpDataCodecId, | |
| kGoogleRtpDataCodecName, 0)); | |
| SetTiming(new rtc::Timing()); | |
| } | |
| DataMediaChannel* RtpDataEngine::CreateChannel( | |
| DataChannelType data_channel_type) { | |
| if (data_channel_type != DCT_RTP) { | |
| return NULL; | |
| } | |
| return new RtpDataMediaChannel(timing_.get()); | |
| } | |
| bool FindCodecByName(const std::vector<DataCodec>& codecs, | |
| const std::string& name, DataCodec* codec_out) { | |
| std::vector<DataCodec>::const_iterator iter; | |
| for (iter = codecs.begin(); iter != codecs.end(); ++iter) { | |
| if (iter->name == name) { | |
| *codec_out = *iter; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| RtpDataMediaChannel::RtpDataMediaChannel(rtc::Timing* timing) { | |
| Construct(timing); | |
| } | |
| RtpDataMediaChannel::RtpDataMediaChannel() { | |
| Construct(NULL); | |
| } | |
| void RtpDataMediaChannel::Construct(rtc::Timing* timing) { | |
| sending_ = false; | |
| receiving_ = false; | |
| timing_ = timing; | |
| send_limiter_.reset(new rtc::RateLimiter(kDataMaxBandwidth / 8, 1.0)); | |
| } | |
| RtpDataMediaChannel::~RtpDataMediaChannel() { | |
| std::map<uint32, RtpClock*>::const_iterator iter; | |
| for (iter = rtp_clock_by_send_ssrc_.begin(); | |
| iter != rtp_clock_by_send_ssrc_.end(); | |
| ++iter) { | |
| delete iter->second; | |
| } | |
| } | |
| void RtpClock::Tick( | |
| double now, int* seq_num, uint32* timestamp) { | |
| *seq_num = ++last_seq_num_; | |
| *timestamp = timestamp_offset_ + static_cast<uint32>(now * clockrate_); | |
| } | |
| const DataCodec* FindUnknownCodec(const std::vector<DataCodec>& codecs) { | |
| DataCodec data_codec(kGoogleRtpDataCodecId, kGoogleRtpDataCodecName, 0); | |
| std::vector<DataCodec>::const_iterator iter; | |
| for (iter = codecs.begin(); iter != codecs.end(); ++iter) { | |
| if (!iter->Matches(data_codec)) { | |
| return &(*iter); | |
| } | |
| } | |
| return NULL; | |
| } | |
| const DataCodec* FindKnownCodec(const std::vector<DataCodec>& codecs) { | |
| DataCodec data_codec(kGoogleRtpDataCodecId, kGoogleRtpDataCodecName, 0); | |
| std::vector<DataCodec>::const_iterator iter; | |
| for (iter = codecs.begin(); iter != codecs.end(); ++iter) { | |
| if (iter->Matches(data_codec)) { | |
| return &(*iter); | |
| } | |
| } | |
| return NULL; | |
| } | |
| bool RtpDataMediaChannel::SetRecvCodecs(const std::vector<DataCodec>& codecs) { | |
| const DataCodec* unknown_codec = FindUnknownCodec(codecs); | |
| if (unknown_codec) { | |
| LOG(LS_WARNING) << "Failed to SetRecvCodecs because of unknown codec: " | |
| << unknown_codec->ToString(); | |
| return false; | |
| } | |
| recv_codecs_ = codecs; | |
| return true; | |
| } | |
| bool RtpDataMediaChannel::SetSendCodecs(const std::vector<DataCodec>& codecs) { | |
| const DataCodec* known_codec = FindKnownCodec(codecs); | |
| if (!known_codec) { | |
| LOG(LS_WARNING) << | |
| "Failed to SetSendCodecs because there is no known codec."; | |
| return false; | |
| } | |
| send_codecs_ = codecs; | |
| return true; | |
| } | |
| bool RtpDataMediaChannel::AddSendStream(const StreamParams& stream) { | |
| if (!stream.has_ssrcs()) { | |
| return false; | |
| } | |
| if (GetStreamBySsrc(send_streams_, stream.first_ssrc())) { | |
| LOG(LS_WARNING) << "Not adding data send stream '" << stream.id | |
| << "' with ssrc=" << stream.first_ssrc() | |
| << " because stream already exists."; | |
| return false; | |
| } | |
| send_streams_.push_back(stream); | |
| // TODO(pthatcher): This should be per-stream, not per-ssrc. | |
| // And we should probably allow more than one per stream. | |
| rtp_clock_by_send_ssrc_[stream.first_ssrc()] = new RtpClock( | |
| kDataCodecClockrate, | |
| rtc::CreateRandomNonZeroId(), rtc::CreateRandomNonZeroId()); | |
| LOG(LS_INFO) << "Added data send stream '" << stream.id | |
| << "' with ssrc=" << stream.first_ssrc(); | |
| return true; | |
| } | |
| bool RtpDataMediaChannel::RemoveSendStream(uint32 ssrc) { | |
| if (!GetStreamBySsrc(send_streams_, ssrc)) { | |
| return false; | |
| } | |
| RemoveStreamBySsrc(&send_streams_, ssrc); | |
| delete rtp_clock_by_send_ssrc_[ssrc]; | |
| rtp_clock_by_send_ssrc_.erase(ssrc); | |
| return true; | |
| } | |
| bool RtpDataMediaChannel::AddRecvStream(const StreamParams& stream) { | |
| if (!stream.has_ssrcs()) { | |
| return false; | |
| } | |
| if (GetStreamBySsrc(recv_streams_, stream.first_ssrc())) { | |
| LOG(LS_WARNING) << "Not adding data recv stream '" << stream.id | |
| << "' with ssrc=" << stream.first_ssrc() | |
| << " because stream already exists."; | |
| return false; | |
| } | |
| recv_streams_.push_back(stream); | |
| LOG(LS_INFO) << "Added data recv stream '" << stream.id | |
| << "' with ssrc=" << stream.first_ssrc(); | |
| return true; | |
| } | |
| bool RtpDataMediaChannel::RemoveRecvStream(uint32 ssrc) { | |
| RemoveStreamBySsrc(&recv_streams_, ssrc); | |
| return true; | |
| } | |
| void RtpDataMediaChannel::OnPacketReceived( | |
| rtc::Buffer* packet, const rtc::PacketTime& packet_time) { | |
| RtpHeader header; | |
| if (!GetRtpHeader(packet->data(), packet->size(), &header)) { | |
| // Don't want to log for every corrupt packet. | |
| // LOG(LS_WARNING) << "Could not read rtp header from packet of length " | |
| // << packet->length() << "."; | |
| return; | |
| } | |
| size_t header_length; | |
| if (!GetRtpHeaderLen(packet->data(), packet->size(), &header_length)) { | |
| // Don't want to log for every corrupt packet. | |
| // LOG(LS_WARNING) << "Could not read rtp header" | |
| // << length from packet of length " | |
| // << packet->length() << "."; | |
| return; | |
| } | |
| const char* data = | |
| packet->data<char>() + header_length + sizeof(kReservedSpace); | |
| size_t data_len = packet->size() - header_length - sizeof(kReservedSpace); | |
| if (!receiving_) { | |
| LOG(LS_WARNING) << "Not receiving packet " | |
| << header.ssrc << ":" << header.seq_num | |
| << " before SetReceive(true) called."; | |
| return; | |
| } | |
| DataCodec codec; | |
| if (!FindCodecById(recv_codecs_, header.payload_type, &codec)) { | |
| // For bundling, this will be logged for every message. | |
| // So disable this logging. | |
| // LOG(LS_WARNING) << "Not receiving packet " | |
| // << header.ssrc << ":" << header.seq_num | |
| // << " (" << data_len << ")" | |
| // << " because unknown payload id: " << header.payload_type; | |
| return; | |
| } | |
| if (!GetStreamBySsrc(recv_streams_, header.ssrc)) { | |
| LOG(LS_WARNING) << "Received packet for unknown ssrc: " << header.ssrc; | |
| return; | |
| } | |
| // Uncomment this for easy debugging. | |
| // const auto* found_stream = GetStreamBySsrc(recv_streams_, header.ssrc); | |
| // LOG(LS_INFO) << "Received packet" | |
| // << " groupid=" << found_stream.groupid | |
| // << ", ssrc=" << header.ssrc | |
| // << ", seqnum=" << header.seq_num | |
| // << ", timestamp=" << header.timestamp | |
| // << ", len=" << data_len; | |
| ReceiveDataParams params; | |
| params.ssrc = header.ssrc; | |
| params.seq_num = header.seq_num; | |
| params.timestamp = header.timestamp; | |
| SignalDataReceived(params, data, data_len); | |
| } | |
| bool RtpDataMediaChannel::SetMaxSendBandwidth(int bps) { | |
| if (bps <= 0) { | |
| bps = kDataMaxBandwidth; | |
| } | |
| send_limiter_.reset(new rtc::RateLimiter(bps / 8, 1.0)); | |
| LOG(LS_INFO) << "RtpDataMediaChannel::SetSendBandwidth to " << bps << "bps."; | |
| return true; | |
| } | |
| bool RtpDataMediaChannel::SendData( | |
| const SendDataParams& params, | |
| const rtc::Buffer& payload, | |
| SendDataResult* result) { | |
| if (result) { | |
| // If we return true, we'll set this to SDR_SUCCESS. | |
| *result = SDR_ERROR; | |
| } | |
| if (!sending_) { | |
| LOG(LS_WARNING) << "Not sending packet with ssrc=" << params.ssrc | |
| << " len=" << payload.size() << " before SetSend(true)."; | |
| return false; | |
| } | |
| if (params.type != cricket::DMT_TEXT) { | |
| LOG(LS_WARNING) << "Not sending data because binary type is unsupported."; | |
| return false; | |
| } | |
| const StreamParams* found_stream = | |
| GetStreamBySsrc(send_streams_, params.ssrc); | |
| if (!found_stream) { | |
| LOG(LS_WARNING) << "Not sending data because ssrc is unknown: " | |
| << params.ssrc; | |
| return false; | |
| } | |
| DataCodec found_codec; | |
| if (!FindCodecByName(send_codecs_, kGoogleRtpDataCodecName, &found_codec)) { | |
| LOG(LS_WARNING) << "Not sending data because codec is unknown: " | |
| << kGoogleRtpDataCodecName; | |
| return false; | |
| } | |
| size_t packet_len = (kMinRtpPacketLen + sizeof(kReservedSpace) + | |
| payload.size() + kMaxSrtpHmacOverhead); | |
| if (packet_len > kDataMaxRtpPacketLen) { | |
| return false; | |
| } | |
| double now = timing_->TimerNow(); | |
| if (!send_limiter_->CanUse(packet_len, now)) { | |
| LOG(LS_VERBOSE) << "Dropped data packet of len=" << packet_len | |
| << "; already sent " << send_limiter_->used_in_period() | |
| << "/" << send_limiter_->max_per_period(); | |
| return false; | |
| } | |
| RtpHeader header; | |
| header.payload_type = found_codec.id; | |
| header.ssrc = params.ssrc; | |
| rtp_clock_by_send_ssrc_[header.ssrc]->Tick( | |
| now, &header.seq_num, &header.timestamp); | |
| rtc::Buffer packet(kMinRtpPacketLen, packet_len); | |
| if (!SetRtpHeader(packet.data(), packet.size(), header)) { | |
| return false; | |
| } | |
| packet.AppendData(kReservedSpace); | |
| packet.AppendData(payload); | |
| LOG(LS_VERBOSE) << "Sent RTP data packet: " | |
| << " stream=" << found_stream->id << " ssrc=" << header.ssrc | |
| << ", seqnum=" << header.seq_num | |
| << ", timestamp=" << header.timestamp | |
| << ", len=" << payload.size(); | |
| MediaChannel::SendPacket(&packet); | |
| send_limiter_->Use(packet_len, now); | |
| if (result) { | |
| *result = SDR_SUCCESS; | |
| } | |
| return true; | |
| } | |
| } // namespace cricket |