视频
接收数据
- 入口
rtp_stream_receiver.cc
文件分析
bool RtpStreamReceiver::DeliverRtp(const uint8_t* rtp_packet,
size_t rtp_packet_length,
const PacketTime& packet_time)
rtp_packet :rtp包数据
rtp_packet _length: rtp包数据长度
packet_time: rtp包时间
-
DeliverRtp()
方法过程分析
1. 接收控制开关
{
rtc::CritScope lock(&receive_cs_);
if (!receiving_) {
return false;
}
}
receiving_ 是接收rtp数据的开关,由两个方法控制:
void RtpStreamReceiver::StartReceive() {..} void RtpStreamReceiver::StopReceive() {..}
2. rtp头解析
RTPHeader header;
if (!rtp_header_parser_->Parse(rtp_packet, rtp_packet_length,
&header)) {
return false;
}
rtp_header_parser_
指针是在 RtpStreamReceiver 构造函数里面创建的,实例是class RtpHeaderParserImpl
RTPHeader 结构内容描述:
struct RTPHeader {
RTPHeader(); //初始化
// 1 bit 该位的功能依赖于profile的定义。profile可以改变该位的长度,但是要保持marker和payload type总长度不变(一共是8 bit)。
bool markerBit;
// 7 bits 标记着RTP packet所携带信息的类型,标准类型列出在RFC3551 中。如果接收方不能识别该类型,必须忽略该packet。
uint8_t payloadType;
// 16 bits 序列号,每个RTP packet发送后该序列号加1,接收方可以根据该序列号重新排列数据包顺序。
uint16_t sequenceNumber;
// 32 bits 时间戳。反映RTP packet所携带信息包中第一个字节的采样时间。
uint32_t timestamp;
// 32 bits 标识数据源。在一个RTP Session其间每个数据流都应该有一个不同的SSRC。
uint32_t ssrc;
// 4 bits 在固定头部后存在多少个CSRC标记。
uint8_t numCSRCs;
// 0 to 15 items, 32 bits each 标识贡献的数据源。只有存在Mixer的时候才有效。如一个将多声道的语音流合并成一个单声道的语音流,在这里就列出原来每个声道的SSRC。
uint32_t arrOfCSRCs[kRtpCsrcSize];
// 1 bit 如果该位被设置,则在该packet末尾包含了额外的附加信息,附加信息的最后一个字节表示额外附加信息的长度(包含该字节本身)。该字段之所以存在是因为一些加密机制需要固定长度的数据块,或者为了在一个底层协议数据单元中传输多个RTP packets。
size_t paddingLength;
// 头部长度
size_t headerLength;
// 时钟频率,视频给的是 const int kVideoPayloadTypeFrequency = 90000;
int payload_type_frequency;
RTPHeaderExtension extension;
};
里面有部分信息是不明白意思的
3. 时间错控制
int64_t arrival_time_ms;
int64_t now_ms = clock_->TimeInMilliseconds();
if (packet_time.timestamp != -1)
arrival_time_ms = (packet_time.timestamp + 500) / 1000;
else
arrival_time_ms = now_ms;
{
// Periodically log the RTP header of incoming packets.
rtc::CritScope lock(&receive_cs_);
if (now_ms - last_packet_log_ms_ > kPacketLogIntervalMs) {
std::stringstream ss;
ss << "Packet received on SSRC: " << header.ssrc << " with payload type: "
<< static_cast<int>(header.payloadType) << ", timestamp: "
<< header.timestamp << ", sequence number: " << header.sequenceNumber
<< ", arrival time: " << arrival_time_ms;
if (header.extension.hasTransmissionTimeOffset)
ss << ", toffset: " << header.extension.transmissionTimeOffset;
if (header.extension.hasAbsoluteSendTime)
ss << ", abs send time: " << header.extension.absoluteSendTime;
LOG(LS_INFO) << ss.str();
last_packet_log_ms_ = now_ms;
}
}
header.payload_type_frequency = kVideoPayloadTypeFrequency;
如果方法带了输入时间错,那么就是用参数带的,否则就用重新计算的,单位毫秒。
设置视频的时钟频率为 90000
4. 顺序判断
bool in_order = IsPacketInOrder(header);
调用过程
-
RtpStreamReceiver::IsPacketInOrder(const RTPHeader& header)
通过header.ssrc 找到流统计信息的实例 -
StreamStatisticianImpl::IsPacketInOrder(uint16_t sequence_number)
调用内部私有方法 -
bool StreamStatisticianImpl::InOrderPacketInternal( uint16_t sequence_number)
调用seq比较方法,输入参数为当前sequence_number,收到的最大 received_seq_max_ -
inline bool IsNewerSequenceNumber(uint16_t sequence_number, uint16_t prev_sequence_number)
如果sequence_number 与 prev_sequence_number 相减等于32768,或者且不相等且 相减值小于32768 且不相等。那么认为seq是新的,也就是新的rtp包
这个地方的逻辑不太理解,不是65535吗
5. 注册接收的rtp负载类型
rtp_payload_registry_.SetIncomingPayloadType(header);
-
ReceivePacket ()
方法过程分析
1. red 和 rtx 负载类型独立流程处理
if (rtp_payload_registry_.IsEncapsulated(header)) {
return ParseAndHandleEncapsulatingHeader(packet, packet_length, header);
}
待分析,不懂这一块
2. 根据负载类型获得负载其他信息
PayloadUnion payload_specific;
if (!rtp_payload_registry_.GetPayloadSpecifics(header.payloadType,
&payload_specific)) {
return false;
}
比如视频的编码类型,音频的采样率通道,码率.下面是获得的结构体信息
struct AudioPayload {
uint32_t frequency;
size_t channels;
uint32_t rate;
};
struct VideoPayload {
RtpVideoCodecTypes videoCodecType;
};
union PayloadUnion {
AudioPayload Audio;
VideoPayload Video;
};
2. 调用 bool RtpReceiverImpl::IncomingRtpPacket()
rtp 接收的包 处理
从 rtp_receiver_impl.cc
方法 文件分析
bool RtpReceiverImpl::IncomingRtpPacket(
const RTPHeader& rtp_header,
const uint8_t* payload,
size_t payload_length,
PayloadUnion payload_specific,
bool in_order)
rtp_header: rtp 头
payload : 去掉头后的rtp数据指针
payload_length: 负载长度
payload_specific : 负载信息
in_order : 是否新seq
1. 调用 CheckSSRCChanged(rtp_header);
做了一下几件事情:
if (ssrc_ != rtp_header.ssrc ||
(last_received_payload_type == -1 && ssrc_ == 0)) {
如果保存ssrc 和如如ssrc不同,或者之前没有收到过负载类型和ssrc。那么读取一下playload的信息(PayloadUnion 结构体的信息),记录上次的一些信息,重新ssrc赋值 。new_ssrc 赋值为TRUE.重置一些参数(last_received_timestamp_,last_received_sequence_number_,last_received_frame_time_ms_)。
new_ssrc 为ture的时候,回调,通知rtcp模块,有ssrc的修改
// We need to get this to our RTCP sender and receiver.
// We need to do this outside critical section.
cb_rtp_feedback_->OnIncomingSSRCChanged(rtp_header.ssrc);
}
重建创建解码器
if (re_initialize_decoder) {
if (-1 ==
cb_rtp_feedback_->OnInitializeDecoder(
rtp_header.payloadType, payload_name,
rtp_header.payload_type_frequency, channels, rate)) {
// New stream, same codec.
LOG(LS_ERROR) << "Failed to create decoder for payload type: "
<< static_cast<int>(rtp_header.payloadType);
}
}
2. 调用 CheckPayloadChanged();
检查payload type 变化,如果变化后,一些信息无法创建,则返回失败返回
if (CheckPayloadChanged(rtp_header, first_payload_byte, &is_red,
&payload_specific) == -1) {
if (payload_length == 0) {
// OK, keep-alive packet.
return true;
}
LOG(LS_WARNING) << "Receiving invalid payload type.";
return false;
}
3. 构建基于webrtc结构的rtphead
WebRtcRTPHeader 是对rtphead的二次封装,包含了rtp_head
WebRtcRTPHeader webrtc_rtp_header;
memset(&webrtc_rtp_header, 0, sizeof(webrtc_rtp_header));
webrtc_rtp_header.header = rtp_header;
4. 判断rtp包是否是一帧的第一个包
bool is_first_packet_in_frame = false;
{
rtc::CritScope lock(&critical_section_rtp_receiver_);
if (HaveReceivedFrame()) {
is_first_packet_in_frame =
last_received_sequence_number_ + 1 == rtp_header.sequenceNumber &&
last_received_timestamp_ != rtp_header.timestamp;
} else {
is_first_packet_in_frame = true;
}
}
4. 调用RTPReceiverVideo 的ParseRtpPacket()
int32_t ret_val = rtp_media_receiver_->ParseRtpPacket(
&webrtc_rtp_header, payload_specific, is_red, payload, payload_length,
clock_->TimeInMilliseconds(), is_first_packet_in_frame);
rtp 包视频解析处理
- 从
rtp_receiver_video.cc
文件分析
int32_t RTPReceiverVideo::ParseRtpPacket(WebRtcRTPHeader* rtp_header,
const PayloadUnion& specific_payload,
bool is_red,
const uint8_t* payload,
size_t payload_length,
int64_t timestamp_ms,
bool is_first_packet) {
- ParseRtpPacket() 方法过程分析
1. 赋值视频编码信息
rtp_header->type.Video.codec = specific_payload.Video.videoCodecType;
2. 判空和判断是否收个rtp包
if (payload == NULL || payload_data_length == 0) {
return data_callback_->OnReceivedPayloadData(NULL, 0, rtp_header) == 0 ? 0
: -1;
}
if (first_packet_received_()) {
LOG(LS_INFO) << "Received first video RTP packet";
}
3. 解析
使用创建指向RtpDepacketizerH264
的指针。并且把相信息赋值给rtp_header
// We are not allowed to hold a critical section when calling below functions.
std::unique_ptr<RtpDepacketizer> depacketizer(
RtpDepacketizer::Create(rtp_header->type.Video.codec));
if (depacketizer.get() == NULL) {
LOG(LS_ERROR) << "Failed to create depacketizer.";
return -1;
}
rtp_header->type.Video.isFirstPacket = is_first_packet;
RtpDepacketizer::ParsedPayload parsed_payload;
if (!depacketizer->Parse(&parsed_payload, payload, payload_data_length))
return -1;
rtp_header->frameType = parsed_payload.frame_type;
rtp_header->type = parsed_payload.type;
rtp_header->type.Video.rotation = kVideoRotation_0;
// Retrieve the video rotation information.
if (rtp_header->header.extension.hasVideoRotation) {
rtp_header->type.Video.rotation = ConvertCVOByteToVideoRotation(
rtp_header->header.extension.videoRotation);
}
rtp_header->type.Video.playout_delay =
rtp_header->header.extension.playout_delay;
4. 回调到rtp_stream_receiver.cc
的RtpStreamReceiver::OnReceivedPayloadData()
方法
return data_callback_->OnReceivedPayloadData(parsed_payload.payload,
parsed_payload.payload_length,
rtp_header) == 0
? 0
: -1;
转入 rtp_stream_receiver.cc
文件
-
OnReceivedPayloadData()
方法分析
1. 计算ntptime
通过rtptime 计算得到ntptime 毫秒
WebRtcRTPHeader rtp_header_with_ntp = *rtp_header;
rtp_header_with_ntp.ntp_time_ms =
ntp_estimator_.Estimate(rtp_header->header.timestamp);
## 转入 `rtp_stream_receiver.cc` 文件
2. 回调到`video_receiver.cc' 的方法
if (video_receiver_->IncomingPacket(payload_data, payload_size,
rtp_header_with_ntp) != 0) {
// Check this...
return -1;
}
转入 `video_receiver.cc' 文件
-
IncomingPacket()
方法分析
1. 构造VCMPacket
如果负载数据指针为空,负载长度也设置为0
if (incomingPayload == nullptr) {
// The jitter buffer doesn't handle non-zero payload lengths for packets
// without payload.
// TODO(holmer): We should fix this in the jitter buffer.
payloadLength = 0;
}
const VCMPacket packet(incomingPayload, payloadLength, rtpInfo);
int32_t ret = _receiver.InsertPacket(packet, rtpInfo.type.Video.width,
rtpInfo.type.Video.height);
- 调用到
receiver.cc
的VCMReceiver::InsertPacket()
方法
`
转入 `receiver.cc' 文件
-
VCMReceiver::InsertPacket()
方法分析
1. 调用JitterBuffer
调用Jitter_Buffer.cc
的 ` VCMJitterBuffer::InsertPacket()方法
const VCMFrameBufferEnum ret =
jitter_buffer_.InsertPacket(packet, &retransmitted);
Jitter_buffer.cc
的 VCMJitterBuffer
详解
1. 把packet引用传入重传模块
if (nack_module_)
nack_module_->OnReceivedPacket(packet);
-
nack_module.cc
OnReceivedPacket()
的逻辑
- 如果未初始化,那么初始化最行收到的newest_seq_num。并且如果是关键帧。那么记录下这关键帧的seq_num。然后return 0。
bool is_retransmitted = true;
bool is_keyframe = packet.isFirstPacket && packet.frameType == kVideoFrameKey;
if (!initialized_) {
newest_seq_num_ = seq_num;
if (is_keyframe)
keyframe_list_.insert(seq_num);
initialized_ = true;
return 0;
}
- newest_seq_num_ 是收到过的。所以seq_num 不是会被重传过(怎么理解???)
// Since the |newest_seq_num_| is a packet we have actually received we know
// that packet has never been Nacked.
if (seq_num == newest_seq_num_)
return 0;
- 乱序
AheadOf
判断是否乱序的seq_num,如果是乱序进入这个if里面
nack_list_ 记录的是之前向外请求过的seq。这里查找当前收到的seq。
如果找到了if (nack_list_it != nack_list_.end())
那么打印一下重传成功记录,包括重传次数。并且从重传需求列表nack_list_ 里面删除这个seq_num。
if (!is_retransmitted)
里面的实际上部会执行
if (AheadOf(newest_seq_num_, seq_num)) {
// An out of order packet has been received.
auto nack_list_it = nack_list_.find(seq_num);
int nacks_sent_for_packet = 0;
if (nack_list_it != nack_list_.end()) {
nacks_sent_for_packet = nack_list_it->second.retries;
LOG(LS_INFO) << "Sequence number " << seq_num
<< " removed from NACK list due to has been received by try " << nacks_sent_for_packet;
nack_list_.erase(nack_list_it);
}
if (!is_retransmitted)
UpdateReorderingStatistics(seq_num);
return nacks_sent_for_packet;
}
- 如果不是重传的包。那就是有丢包了或者正常的。
吧newest_seq_num_ + 1 到 seq_seq 之间的seq都放入nack_list_ 里面。当然AddPacketsToNack ()
方法里面还有一些丢弃的逻辑。后面分析
AddPacketsToNack(newest_seq_num_ + 1, seq_num);
newest_seq_num_ = seq_num;
- 记录关键帧rtp的seq。并且控制keyframe_list_ 的大小
// Keep track of new keyframes.
if (is_keyframe)
keyframe_list_.insert(seq_num);
// And remove old ones so we don't accumulate keyframes.
auto it = keyframe_list_.lower_bound(seq_num - kMaxPacketAge);
if (it != keyframe_list_.begin())
keyframe_list_.erase(keyframe_list_.begin(), it);
- 发送需要重传的seq 给nack_sender,其实就是发送rtcp出去。所以这里的rtcp重传请求发送,还是很及时的。
// Are there any nacks that are waiting for this seq_num.
std::vector<uint16_t> nack_batch = GetNackBatch(kSeqNumOnly);
if (!nack_batch.empty())
nack_sender_->SendNack(nack_batch);
-
AddPacketsToNack ()
方法详解
- 控制nack_list 里面seq_num 的跨度,不超过kMaxPacketAge
lower_bound
的理解是从左边开始找位置。找到某个两个数据的中间或者等于某个数。如果是两个中间,就返回后面的位置。如果是等于。那么就是返回等于的这个位置。比如 0,2,4,6,710。 输入3返回 key为4的位置。比如 10,7,6,4,2.0。 输入3返回 可以key为2的位置的位置。
假如kMaxPacketAge = 100; 那么如果 seq_num_end <= 100 那么都不闪,如果seq_num_end > 100 。那么相当于seq_num_end 往前的seq。那么删除后。第一个元素到 seq_num_end 之间的差就不会超过100。
这里如果从65535 跨度到 0
// Remove old packets.
auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);
nack_list_.erase(nack_list_.begin(), it);
- 控制nack_list 里面数据长度
先获得需要增加的nack seq长度num_new_nacks 。
num_new_nacks 加上原有的nack_list 长度 如果大于最大限制的长度
kMaxNackPackets。那么进入到if里面
RemovePacketsUntilKeyFrame 删除nack_list里面的元素,从坐到右,遇到一个是I帧开头的seq,且长度满足kMaxNackPackets的为止。
如果一直没有I帧,那么也不会删除。就会导致。长度还是不满足要求。所以要宠幸请求一次I帧,并且把nack_list 里面的数据清空掉
// If the nack list is too large, remove packets from the nack list until
// the latest first packet of a keyframe. If the list is still too large,
// clear it and request a keyframe.
uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);
if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
while (RemovePacketsUntilKeyFrame() &&
nack_list_.size() + num_new_nacks > kMaxNackPackets) {
}
if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
nack_list_.clear();
LOG(LS_WARNING) << "NACK list full, clearing NACK"
" list and requesting keyframe.";
keyframe_request_sender_->RequestKeyFrame();
return;
}
}
- 添加新增的NackInfo ,关于
reordering_histogram_
什么还不太理解
for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {
NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5));
RTC_DCHECK(nack_list_.find(seq_num) == nack_list_.end());
nack_list_[seq_num] = nack_info;
}