创建完PeerConnectionFactory 和 PeerConnection这两个API层的操盘对象之后,紧接着需要初始化本地的媒体,也即创建本地的音频轨、视频轨、数据通道,并将这些本地的媒体轨道添加到PeerConnection对象中。如图中红色标注所示。
本文将详细描述上述轨道的创建细节 以及 轨道被添加到PeerConnection中的存储情况。
WebRTC的示例工程中使用如下几行代码实现AudioTrack的创建 && 添加AudioTrack到PeerConnection中。
rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
peer_connection_factory_->CreateAudioTrack(
kAudioLabel, peer_connection_factory_->CreateAudioSource(
cricket::AudioOptions())));
auto result_or_error = peer_connection_->AddTrack(audio_track, {kStreamId});
rtc::scoped_refptr<AudioSourceInterface>
PeerConnectionFactory::CreateAudioSource(const cricket::AudioOptions& options) {
RTC_DCHECK(signaling_thread_->IsCurrent());
rtc::scoped_refptr<LocalAudioSource> source(
LocalAudioSource::Create(&options));
return source;
}
rtc::scoped_refptr<LocalAudioSource> LocalAudioSource::Create(
const cricket::AudioOptions* audio_options) {
rtc::scoped_refptr<LocalAudioSource> source(
new rtc::RefCountedObject<LocalAudioSource>());
source->Initialize(audio_options);
return source;
}
void LocalAudioSource::Initialize(const cricket::AudioOptions* audio_options) {
if (!audio_options)
return;
options_ = *audio_options;
}
上述是本地音频源创建过程,由源码可知,创建的本地音频轨实体对象是LocalAudioSource,并且创建该对象后进行了初始化——>将音频选项对象传递给了LocalAudioSource进行存储。
PS1:有三套注册/注销接口,分别是:
WebRTC中,近端原始音视频数据总是要经过 “采集->音视频源->音频轨” 这样一条路径,至少视频数据是严格按照该条路径输出的。我们来看看近端音频的情况——LocalAudioSource
class LocalAudioSource : public Notifier<AudioSourceInterface> {
public:
// Creates an instance of LocalAudioSource.
static rtc::scoped_refptr<LocalAudioSource> Create(
const cricket::AudioOptions* audio_options);
SourceState state() const override { return kLive; }
bool remote() const override { return false; }
const cricket::AudioOptions options() const override { return options_; }
void AddSink(AudioTrackSinkInterface* sink) override {}
void RemoveSink(AudioTrackSinkInterface* sink) override {}
protected:
LocalAudioSource() {}
~LocalAudioSource() override {}
private:
void Initialize(const cricket::AudioOptions* audio_options);
cricket::AudioOptions options_;
};
源码如上所示,在本地创建轨道时所使用的音频源对象是LocalAudioSource。仔细查看LocalAudioSource类的代码,可以知道该音频源实质上什么也没有做:既没有与音频设备建立联系,从音频设备处获取采集的音频数据,也没有实质的提供注册Sink的方法,更没有向注册的Sink推送数据。由此可知,本地的音频数据的流转跟LocalAudioSource其实没什么关系,这个是我非常纳闷的一点,因为LocalAudioSource看起来像是一个没有完成、或者说是废弃的类,但示例中正常使用了,并且近端音频数据还是正常流转的。那么肯定是走了别的路径。
当前,音频设备模块ADM是被音频引擎VoiceEngine所持有的,因此,音频数据采集开始,最初的位置可能就是VoiceEngine。后续将专门出一篇文章来分析介绍近端音频流转。
与LocalAudioSource不一样,代表远端音频源的RemoteAudioSource类是真实有效的类,提供了继承树上所有接口和功能的实现。
远端音频源从哪儿获取数据?又将数据推向何处?
RemoteAudioSource::AudioDataProxy类的对象可以携带RemoteAudioSource对象被注册到VoiceEngine中,从那得到从远端收到的音频数据;RemoteAudioSource又可以向注册到其中的Sink列表进一步扇出音频数据。具体看如下源码:
class RemoteAudioSource::AudioDataProxy : public AudioSinkInterface {
public:
explicit AudioDataProxy(RemoteAudioSource* source) : source_(source) {
RTC_DCHECK(source);
}
~AudioDataProxy() override { source_->OnAudioChannelGone(); }
// AudioSinkInterface implementation.
void OnData(const AudioSinkInterface::Data& audio) override {
source_->OnData(audio);
}
private:
const rtc::scoped_refptr<RemoteAudioSource> source_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioDataProxy);
};
void RemoteAudioSource::OnData(const AudioSinkInterface::Data& audio) {
// Called on the externally-owned audio callback thread, via/from webrtc.
rtc::CritScope lock(&sink_lock_);
for (auto* sink : sinks_) {
sink->OnData(audio.data, 16, audio.sample_rate, audio.channels,
audio.samples_per_channel);
}
}
rtc::scoped_refptr<AudioTrackInterface> PeerConnectionFactory::CreateAudioTrack(
const std::string& id,
AudioSourceInterface* source) {
RTC_DCHECK(signaling_thread_->IsCurrent());
rtc::scoped_refptr<AudioTrackInterface> track(AudioTrack::Create(id, source));
return AudioTrackProxy::Create(signaling_thread_, track);
}
rtc::scoped_refptr<AudioTrack> AudioTrack::Create(
const std::string& id,
const rtc::scoped_refptr<AudioSourceInterface>& source) {
return new rtc::RefCountedObject<AudioTrack>(id, source);
}
AudioTrack::AudioTrack(const std::string& label,
const rtc::scoped_refptr<AudioSourceInterface>& source)
: MediaStreamTrack<AudioTrackInterface>(label), audio_source_(source) {
if (audio_source_) {
audio_source_->RegisterObserver(this);
OnChanged();
}
}
void AudioTrack::OnChanged() {
RTC_DCHECK(thread_checker_.IsCurrent());
if (audio_source_->state() == MediaSourceInterface::kEnded) {
set_state(kEnded);
} else {
set_state(kLive);
}
}
PeerConnection::AddTrack方法提供两个入参:媒体轨道Track以及媒体流id向量。暗示了WebRTC中一个概念:一个媒体轨道MediaTrack逻辑上可以归属多个媒体流MediaStream。入参stream_ids向量在SDP中会以msid参数出现,一个msid表示逻辑上的一个媒体流。往后,我们可以看到媒体Track会被添加到一个RtpSender中,stream_ids也会存储在RtpSender中。
在继续往下分析前,需要先略微分析下SDP,对SDP中的msid、mid做一个简单的介绍
a=group:BUNDLE audio video data
a=msid-semantic: WMS h1aZ20mbQB0GSsq0YxLfJmiYWE9CBfGch97C
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126
a=mid:audio
a=ssrc:18509423 msid:h1aZ20mbQB0GSsq0YxLfJmiYWE9CBfGch97C 15598a91-caf9-4fff-a28f-3082310b2b7a
m=video 9 UDP/TLS/RTP/SAVPF 100 101 107 116 117 96 97 99 98
a=mid:video
a=ssrc:3463951252 msid:h1aZ20mbQB0GSsq0YxLfJmiYWE9CBfGch97C ead4b4e9-b650-4ed5-86f8-6f5f5806346d
m=application 9 DTLS/SCTP 5000
a=mid:data
上述是一个被简化的SDP数据,从中简要的总结如下几点知识,可以辅助我们去理解后续的内容(我们总是基于Unified Plan这种SDP格式进行讨论,因为Plan B这种格式大多数情况下已经被弃用):
PeerConnection::AddTrack源码如下:
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids) {
// 1 进行一些条件判断
// 1.1 必须在信令线程
RTC_DCHECK_RUN_ON(signaling_thread());
TRACE_EVENT0("webrtc", "PeerConnection::AddTrack");
// 1.2 轨道不能为空,必须是音频 or 视频轨
if (!track) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Track is null.");
}
if (!(track->kind() == MediaStreamTrackInterface::kAudioKind ||
track->kind() == MediaStreamTrackInterface::kVideoKind)) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
"Track has invalid kind: " + track->kind());
}
// 1.3 PeerConnection的信令状态机不能是closed
if (IsClosed()) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE,
"PeerConnection is closed.");
}
// 2 遍历PeerConnection.RtpTransceiver列表.RtpSender列表,
// 查看当前的track是否在某个RtpSender中,存在则不能重复添加
if (FindSenderForTrack(track)) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_PARAMETER,
"Sender already exists for track " + track->id() + ".");
}
// 3 根据SDP采用UnifiedPlan还是Plan B决定如何添加Track到PeerConnection的成员中
auto sender_or_error =
(IsUnifiedPlan() ? AddTrackUnifiedPlan(track, stream_ids)
: AddTrackPlanB(track, stream_ids));
if (sender_or_error.ok()) {
// 4 是否需要进行重新协商
UpdateNegotiationNeeded();
// 5 添加轨道到统计数据收集器
stats_->AddTrack(track);
}
return sender_or_error;
}
添加轨道到PeerConnection过程如上源码分为5个步骤:
接下来将对2~5这4个步骤都进行详细的梳理。
防止重复添加同一个track,从PeerConnection保存track的字段中查找当前的track是否已存在,存在则返回对应的RtpSender,否则返回空指针。
rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
PeerConnection::FindSenderForTrack(MediaStreamTrackInterface* track) const {
for (const auto& transceiver : transceivers_) {
for (auto sender : transceiver->internal()->senders()) {
if (sender->track() == track) {
return sender;
}
}
}
return nullptr;
}
PeerConnection对象有个成员 std::vector
由于SDP的Plan B格式下,本地要发送的多个相同媒体类型的轨道(a=ssrc不同)可能会属于同一个mLine,因此,RtpTransceiver包含一个RtpSender的向量 ,每个RtpSender会存储其中一个轨道。当SDP采用Unified Plan时,RtpTransceiver的RtpSender向量实质上只会存在一个RtpSender,为了兼容Plan B的格式才会存在多个RtpSender。
RtpTransceiver即可以代表本地轨道数据的发送器,又能代表接收远端轨道数据的接收器。 因此,RtpTransceiver还包含一个RtpReceiver的向量。远端的轨道会被存储在RtpTransceiver的某个RtpReceiver中。
上述查找本地Track的过程就遍历了每个RtpTransceiver对象的每个RtpSender中的track的地址,看是否是同一个。 关于RtpTransceiver类的阐述可见——WebRTC源码分析——RtpTransceiver类
添加track到PeerConnection,根据SDP采用Unified Plan还是Plan B。由于Plab B大概率要被遗弃,因此,当前只分析Unified Plan。
RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
PeerConnection::AddTrackUnifiedPlan(
rtc::scoped_refptr<MediaStreamTrackInterface> track,
const std::vector<std::string>& stream_ids) {
// 1 查找是否存在可复用的RtpTransceiver
auto transceiver = FindFirstTransceiverForAddedTrack(track);
// 2 存在,添加track到该复用的RtpTransceiver,并修改必要的属性
if (transceiver) {
RTC_LOG(LS_INFO) << "Reusing an existing "
<< cricket::MediaTypeToString(transceiver->media_type())
<< " transceiver for AddTrack.";
// 2.1 设置RtpTransceiver的方向,注意不能将接收方向覆盖掉,即如果接收方向是存在的,
// 则必须保留,因此有如下的判断。
if (transceiver->direction() == RtpTransceiverDirection::kRecvOnly) {
transceiver->internal()->set_direction(
RtpTransceiverDirection::kSendRecv);
} else if (transceiver->direction() == RtpTransceiverDirection::kInactive) {
transceiver->internal()->set_direction(
RtpTransceiverDirection::kSendOnly);
}
// 2.2 添加track到对应的sender
transceiver->sender()->SetTrack(track);
// 2.3 设置RtpSender的流id
transceiver->internal()->sender_internal()->set_stream_ids(stream_ids);
// 2.4 设置重用标识
transceiver->internal()->set_reused_for_addtrack(true);
// 3 不存在,创建新的RtpTransceiver,添加track到新的RtpTransceiver
} else {
// 3.1 得到与轨道一致的媒体类型
cricket::MediaType media_type =
(track->kind() == MediaStreamTrackInterface::kAudioKind
? cricket::MEDIA_TYPE_AUDIO
: cricket::MEDIA_TYPE_VIDEO);
RTC_LOG(LS_INFO) << "Adding " << cricket::MediaTypeToString(media_type)
<< " transceiver in response to a call to AddTrack.";
// 3.2 得到或新建RtpSender的id,可以与track的id相同,但是不能与其他
// RtpSender的id重复,否则创建一个新的UUID作为RtpSender的唯一标识。
std::string sender_id = track->id();
// Avoid creating a sender with an existing ID by generating a random ID.
// This can happen if this is the second time AddTrack has created a sender
// for this track.
if (FindSenderById(sender_id)) {
sender_id = rtc::CreateRandomUuid();
}
// 3.3 创建新的RtpSender,传入track
auto sender = CreateSender(media_type, sender_id, track, stream_ids, {});
// 3.4 创建新的RtpReceiver,无track
auto receiver = CreateReceiver(media_type, rtc::CreateRandomUuid());
// 3.5 根据新的RtpSender,RtpReceiver创建新的RtpTransceiver
transceiver = CreateAndAddTransceiver(sender, receiver);
// 3.6 设置RtpTransceiver创建标识
transceiver->internal()->set_created_by_addtrack(true);
// 3.7 新创建的RtpTransceiver的方向设置为既接收又发送
transceiver->internal()->set_direction(RtpTransceiverDirection::kSendRecv);
}
return transceiver->sender();
}
大致过程就是判断是否存在可复用的RtpTransceiver,存在则添加track到可复用的RtpTransceiver,否则新建一个RtpTransceiver,添加进去。同时注意,需要修改RtpTransceiver的一些属性。
如何判断该RtpTransceiver是可复用的?判断依据是什么?
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
PeerConnection::FindFirstTransceiverForAddedTrack(
rtc::scoped_refptr<MediaStreamTrackInterface> track) {
RTC_DCHECK(track);
for (auto transceiver : transceivers_) {
if (!transceiver->sender()->track() &&
cricket::MediaTypeToString(transceiver->media_type()) ==
track->kind() &&
!transceiver->internal()->has_ever_been_used_to_send() &&
!transceiver->stopped()) {
return transceiver;
}
}
return nullptr;
}
如上源码所示,给本地track可复用的RtpTransceiver必须满足以下几个条件
UpdateNegotiationNeeded() 方法在往PC中添加/移除轨道、添加/移除流、添加/移除RtpTransceiver、应用local/remote sdp、状态需要进行回滚到KStable时都会被调用,经过各种条件检测后,更新PC的内部成员is_negotiation_needed_。更新后若is_negotiation_needed_为真,那么表示需要重新协商。
void PeerConnection::UpdateNegotiationNeeded() {
RTC_DCHECK_RUN_ON(signaling_thread());
// 1 如果是Plan B则需要协商,直接通知外部的观察者需要重新协商,不需要关注本方法的功能:
// 检查有无重新协商的必要,更新字段is_negotiation_needed_。 后续分析将忽略Plan B
// 时如何处理,因为Plan B将被遗弃。
if (!IsUnifiedPlan()) {
Observer()->OnRenegotiationNeeded();
return;
}
// 2 对PC的信令状态机的状态进行判断。
// 2.1 PC的信令状态为kClosed,表示会话已经被关闭了,无协商的必要了
if (IsClosed())
return;
// 2.2 PC的信令状态没有处于kStable(初始化状态),也不需判断是否需要进行协商
if (signaling_state() != kStable)
return;
// 3. 使用CheckIfNegotiationIsNeeded()判断是否需要重新协商
// NOTE
// The negotiation-needed flag will be updated once the state transitions to
// "stable", as part of the steps for setting an RTCSessionDescription.
bool is_negotiation_needed = CheckIfNegotiationIsNeeded();
// 4. 根据之前是否需要协商的状态,以及当前是否需要协商的结论,进行不同的响应
// 只有当false——>true的状态时,直接通知观察者进行重新协商。
// 4.1 如果当前结论不需要协商,则is_negotiation_needed_更新为false,返回
if (!is_negotiation_needed) {
is_negotiation_needed_ = false;
return;
}
// 4.2 如果当前需要协商,之前也是需要协商的状态,那就不必进行状态更新了
if (is_negotiation_needed_)
return;
// 4.3 如果当前需要协商,之前时不需要协商的状态,那么更新为需要进行协商,同时通知观察者
// 进行协商——即,调用观察者的OnRenegotiationNeeded()方法。
is_negotiation_needed_ = true;
Observer()->OnRenegotiationNeeded();
}
根据源码分析可以得出以下结论:
上述源码以及论述中,提到了PC信令状态,以及方法 CheckIfNegotiationIsNeeded(),接下来进行一定程度的讨论。
PC根据JESP会话进行程度,维护了一个信令状态机,状态迁移图如下所示:
各个状态代表的含义如下表格所示:
从呼叫和被呼端的视角分别去跟踪这个状态机会更好理解:
PS: 参阅 https://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate
是否需要重新协商?依据是什么?问这个问题之前,我们需要搞清楚另外一个问题,即协商的内容是什么?我们知道WebRTC中协商的内容是多样的媒体信息,传输信息,具体可见文章:WebRTC56版本SDP详细解析
协商的手段是收集sdp数据进行互换来达成的,而webrtc中收集sdp数据时,pc用本地会话对象和远端会话对象来存储sdp数据,这些数据的来源就是我们的PC中的ice相关信息,rtptranceiver对象等等,当应用层添加删除轨道等操作时,相应的数据来源会发生变化,但是这个变化并不会同步到存储sdp的近端/远端会话对象中,如此带来了信息的差异。此时,我们就需要重新进行协商,让会话对象存储的信息与数据源保持一致。
bool PeerConnection::CheckIfNegotiationIsNeeded() {
RTC_DCHECK_RUN_ON(signaling_thread());
// 1. If any implementation-specific negotiation is required, as described at
// the start of this section, return true.
// 2. If connection's [[RestartIce]] internal slot is true, return true.
// 如果有ICE的凭证了,则是需要协商的
if (local_ice_credentials_to_replace_->HasIceCredentials()) {
return true;
}
// 3. Let description be connection.[[CurrentLocalDescription]].
// 如果还没有本地的SDP,则是需要协商的
const SessionDescriptionInterface* description = current_local_description();
if (!description)
return true;
// 4. If connection has created any RTCDataChannels, and no m= section in
// description has been negotiated yet for data, return true.
// 如果创建了DataChannel,但是sdp中没有对应的mLine,则需要协商。
if (data_channel_controller_.HasSctpDataChannels()) {
if (!cricket::GetFirstDataContent(description->description()->contents()))
return true;
}
// 5. For each transceiver in connection's set of transceivers, perform the
// following checks:
// 对PC中的每个Rtptranceiver进行如下判断:
for (const auto& transceiver : transceivers_) {
// 获取Rtptranceiver在local sdp中的mline内容描述结构体ContentInfo
const ContentInfo* current_local_msection =
FindTransceiverMSection(transceiver.get(), description);
// 获取Rtptranceiver在remote sdp中的mline内容描述结构体ContentInfo
const ContentInfo* current_remote_msection = FindTransceiverMSection(
transceiver.get(), current_remote_description());
// 5.3 If transceiver is stopped and is associated with an m= section,
// but the associated m= section is not yet rejected in
// connection.[[CurrentLocalDescription]] or
// connection.[[CurrentRemoteDescription]], return true.
// 如果Rtptranceiver已经是停止状态,但是在local sdp或者是remote sdp中
// 不处于rejected状态,也即是有效的,这状况显然是不对的,因此需要进行协商。
if (transceiver->stopped()) {
if (current_local_msection && !current_local_msection->rejected &&
((current_remote_msection && !current_remote_msection->rejected) ||
!current_remote_msection)) {
return true;
}
continue;
}
// 5.1 If transceiver isn't stopped and isn't yet associated with an m=
// section in description, return true.
// 如果Rtptranceiver没有停止,并且在本地SDP中没有相应的mline,那么肯定需要
// 进行协商
if (!current_local_msection)
return true;
const MediaContentDescription* current_local_media_description =
current_local_msection->media_description();
// 5.2 If transceiver isn't stopped and is associated with an m= section
// in description then perform the following checks:
// 如果Rtptranceiver没有停止,并且也与本地sdp的mline进行了关联,那么获取对应的
// MediaContentDescription进行更细节性的排查
// 5.2.1 If transceiver.[[Direction]] is "sendrecv" or "sendonly", and the
// associated m= section in description either doesn't contain a single
// "a=msid" line, or the number of MSIDs from the "a=msid" lines in this
// m= section, or the MSID values themselves, differ from what is in
// transceiver.sender.[[AssociatedMediaStreamIds]], return true.
// 如果Rtptranceiver包含有效的RtpSender(即Rtptranceiver的方向包含send方向)
// 但是SDP中与其关联的mline没有包含单独的a=msid行,或者mline的a=msid行的msid值与
// Rtptranceiver的RtpSender的关联的媒体流id值不一致。需要进行协商
if (RtpTransceiverDirectionHasSend(transceiver->direction())) {
//如果mline所属流ID数量为0,即不归属于某个流,则需要进行协商
if (current_local_media_description->streams().size() == 0)
return true;
//遍历并提取所有关联的流ID到临时向量保存
std::vector<std::string> msection_msids;
for (const auto& stream : current_local_media_description->streams()) {
for (const std::string& msid : stream.stream_ids())
msection_msids.push_back(msid);
}
//若sender所属的流,ID数量和ID值与sdp中抽取的不一致,则需要进行协商。
std::vector<std::string> transceiver_msids =
transceiver->sender()->stream_ids();
if (msection_msids.size() != transceiver_msids.size())
return true;
absl::c_sort(transceiver_msids);
absl::c_sort(msection_msids);
if (transceiver_msids != msection_msids)
return true;
}
// 5.2.2 If description is of type "offer", and the direction of the
// associated m= section in neither connection.[[CurrentLocalDescription]]
// nor connection.[[CurrentRemoteDescription]] matches
// transceiver.[[Direction]], return true.
//
// 本地sdp为offer sdp(即当前pc为呼叫发起方),Rtptranceiver的mid必须
// 在本地sdp中有对应的mline,在远端sdp中也应该有对应的mline。并且Rtptranceiver的
// 方向必须与本地sdp mline中的方向一致,与远端sdp mline中的方向相反。
if (description->GetType() == SdpType::kOffer) {
if (!current_remote_description())
return true;
if (!current_remote_msection)
return true;
RtpTransceiverDirection current_local_direction =
current_local_media_description->direction();
RtpTransceiverDirection current_remote_direction =
current_remote_msection->media_description()->direction();
if (transceiver->direction() != current_local_direction &&
transceiver->direction() !=
RtpTransceiverDirectionReversed(current_remote_direction)) {
return true;
}
}
// 5.2.3 If description is of type "answer", and the direction of the
// associated m= section in the description does not match
// transceiver.[[Direction]] intersected with the offered direction (as
// described in [JSEP] (section 5.3.1.)), return true.
//
// 本地sdp为answer sdp(即当前pc为被呼方),那么远端sdp为offer sdp
// 此时,近端sdp的mline的方向如果与 “Rtptranceiver方向&&远端mline方向的交集”
// 不一致,则需要进行协商(此处较难理解,应反复斟酌)
if (description->GetType() == SdpType::kAnswer) {
// 远端sdp不存在,则需要进行协商
if (!remote_description())
return true;
// 获取远端sdp,也即offer sdp中对应的mline描述
const ContentInfo* offered_remote_msection =
FindTransceiverMSection(transceiver.get(), remote_description());
// 如果远端offser sdp中mline描述存在,则获取mline中描述的方向,否则认为offer sdp
// 中该mline是无效的。
RtpTransceiverDirection offered_direction =
offered_remote_msection
? offered_remote_msection->media_description()->direction()
: RtpTransceiverDirection::kInactive;
// 近端sdp的mline的方向如果与 “Rtptranceiver方向&&远端mline方向的交集”
// 不一致,则需要进行协商
if (current_local_media_description->direction() !=
// 求二者方向的交集
(RtpTransceiverDirectionIntersection(
transceiver->direction(),
// 先对远端offer sdp的mline方向取反
RtpTransceiverDirectionReversed(offered_direction)))) {
return true;
}
}
}
void StatsCollector::AddTrack(MediaStreamTrackInterface* track) {
if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
CreateTrackReport(static_cast<AudioTrackInterface*>(track), &reports_,
&track_ids_);
} else if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
CreateTrackReport(static_cast<VideoTrackInterface*>(track), &reports_,
&track_ids_);
} else {
RTC_NOTREACHED() << "Illegal track kind";
}
}
将该轨道纳入统计数据收集器,如此,可以出具关于该track的统计数据报表。详细分析可见后续WebRTC关于数据统计的分析,此处不赘述。
经过上述长篇论述,我们大致对WebRTC中的音频源,音频轨的继承结构,创建过程有了大致的了解;并且对PC如何添加、存储音频轨有了比较深刻的理解;同时,当音频轨被添加到PC中后,我们需要判断PC中近远端SDP会话对象 与 RtpTranceiver中保存的信息是否一致,从而决定了是否需要进行重新协商。 有一些观点需要再次强调,也有一些疑惑需要列举出来,以备往后源码分析中一一解惑。