sdp offer 流程
1. AmqpClient - New message received
sdp offer 的消息
2023-04-26T21:54:19.790 - DEBUG: AmqpClient - RpcServer New message received {
method: 'onTransportSignaling',
args: [
'b149e44bb10d4e91bd162a8c6806ae7b',
{
sdp: 'v=0\r\n' +
'o=- 7177131362423164715 2 IN IP4 127.0.0.1\r\n' +
's=-\r\n' +
't=0 0\r\n' +
'a=group:BUNDLE 0 1\r\n' +
'a=msid-semantic: WMS\r\n' +
'm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126\r\n' +
'c=IN IP4 0.0.0.0\r\n' +
'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
'a=ice-ufrag:h7T4\r\n' +
'a=ice-pwd:F0L/DBkHlQzxDgFOclWm8vB7\r\n' +
'a=ice-options:trickle\r\n' +
'a=fingerprint:sha-256 5D:A4:FF:F1:C1:1B:51:19:CB:26:53:B6:49:2E:97:5F:F1:A4:B7:C7:41:68:AF:19:8D:FA:B1:D6:9D:02:60:4C\r\n' +
'a=setup:actpass\r\n' +
'a=mid:0\r\n' +
'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' +
'a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n' +
'a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n' +
'a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n' +
'a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n' +
'a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n' +
'a=recvonly\r\n' +
'a=rtcp-mux\r\n' +
'a=rtpmap:111 opus/48000/2\r\n' +
'a=rtcp-fb:111 transport-cc\r\n' +
'a=fmtp:111 minptime=10;useinbandfec=1\r\n' +
'a=rtpmap:103 ISAC/16000\r\n' +
'a=rtpmap:104 ISAC/32000\r\n' +
'a=rtpmap:9 G722/8000\r\n' +
'a=rtpmap:102 ILBC/8000\r\n' +
'a=rtpmap:0 PCMU/8000\r\n' +
'a=rtpmap:8 PCMA/8000\r\n' +
'a=rtpmap:106 CN/32000\r\n' +
'a=rtpmap:105 CN/16000\r\n' +
'a=rtpmap:13 CN/8000\r\n' +
'a=rtpmap:110 telephone-event/48000\r\n' +
'a=rtpmap:112 telephone-event/32000\r\n' +
'a=rtpmap:113 telephone-event/16000\r\n' +
'a=rtpmap:126 telephone-event/8000\r\n' +
'm=video 9 UDP/TLS/RTP/SAVPF 123 114 115 116 119\r\n' +
'c=IN IP4 0.0.0.0\r\n' +
'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
'a=ice-ufrag:h7T4\r\n' +
'a=ice-pwd:F0L/DBkHlQzxDgFOclWm8vB7\r\n' +
'a=ice-options:trickle\r\n' +
'a=fingerprint:sha-256 5D:A4:FF:F1:C1:1B:51:19:CB:26:53:B6:49:2E:97:5F:F1:A4:B7:C7:41:68:AF:19:8D:FA:B1:D6:9D:02:60:4C\r\n' +
'a=setup:actpass\r\n' +
'a=mid:1\r\n' +
'a=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\n' +
'a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n' +
'a=extmap:13 urn:3gpp:video-orientation\r\n' +
'a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n' +
'a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n' +
'a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n' +
'a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n' +
'a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\n' +
'a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n' +
'a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n' +
'a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n' +
'a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n' +
'a=recvonly\r\n' +
'a=rtcp-mux\r\n' +
'a=rtcp-rsize\r\n' +
'a=rtpmap:123 AV1/90000\r\n' +
'a=rtcp-fb:123 goog-remb\r\n' +
'a=rtcp-fb:123 transport-cc\r\n' +
'a=rtcp-fb:123 ccm fir\r\n' +
'a=rtcp-fb:123 nack\r\n' +
'a=rtcp-fb:123 nack pli\r\n' +
'a=rtpmap:114 red/90000\r\n' +
'a=rtpmap:115 rtx/90000\r\n' +
'a=fmtp:115 apt=114\r\n' +
'a=rtpmap:116 ulpfec/90000\r\n' +
'a=rtpmap:119 rtx/90000\r\n' +
'a=fmtp:119 apt=123\r\n',
type: 'offer'
}
],
corrID: 39,
replyTo: 'amq.gen-WtoELIbC4gJ1GfdYgkvSFA'
}
2. WebrtcNode - onTransportSignaling
dist/webrtc_agent/webrtc/index.js
// connectionId 应该就是operationId
that.onTransportSignaling = function (connectionId, msg, callback) {
log.debug('onTransportSignaling, connection id:', connectionId, 'msg:', msg);
// 通过 operationId 从mappingTransports 获取 transportId,
// 接着通过transportId从peerConnections 获取WrtcConnection
// 参考小结 2.1
var conn = getWebRTCConnection(connectionId);
// WrtcConnection conn
if (conn) {
// 参考小结 2.2
conn.onSignalling(msg, connectionId);
callback('callback', 'ok');
} else {
callback('callback', 'error', 'Connection does NOT exist:' + connectionId);
}
};
2.1 WebrtcNode - getWebRTCConnection——返回WrtcConnection
dist/webrtc_agent/webrtc/index.js
// 返回的是WrtcConnection
var getWebRTCConnection = function (operationId) {
// 在WebrtcNode-createWebRTCConnection 创建
var transportId = mappingTransports.get(operationId);
if (peerConnections.has(transportId)) {
return peerConnections.get(transportId);
}
return null;
};
2.2 WrtcConnection.onSignalling
dist/webrtc_agent/webrtc/wrtcConnection.js
处理 offer sdp。
that.onSignalling = function (msg, operationId) {
var processSignalling = function () {
// 这里type=’offer‘
if (msg.type === 'offer') {
log.debug('on offer:', msg.sdp);
processOffer(msg.sdp);
} else if (msg.type === 'candidate') {
...
} else if (msg.type === 'removed-candidates') {
...
}
};
// wrtc 是 Connection
if (wrtc) {
processSignalling();
} else {
// should not reach here
log.error('wrtc is not ready');
}
};
2.3 ==========WrtcConnection.processOffer
dist/webrtc_agent/webrtc/wrtcConnection.js
这里的 remoteSdp
是null,
const processOffer = function (sdp) {
// 第一进来,remoteSdp 是null
if (!remoteSdp) {
// 1. First offer
remoteSdp = new SdpInfo(sdp);
// Check mid, 获取到mid 数组
const mids = remoteSdp.mids();
for (const mid of mids) {
// 2. 设置finalformat
processOfferMedia(mid);
}
// 3. 创建answer sdp
localSdp = remoteSdp.answer();
// 4. Setup transport
let opId = null;
for (const mid of mids) {
if (remoteSdp.getMediaPort(mid) !== 0) {
opId = setupTransport(mid);
}
}
if (opId) {
on_track({
type: 'tracks-complete',
operationId: opId
});
}
} else {
...
}
};
var remoteSdp = null;
var localSdp = null;
2.3.1 SdpInfo.SdpInfo
dist/webrtc_agent/webrtc/sdpInfo.js
根据字符串,生成SdpInfo对象。
2.3.2 WrtcConnection.processOfferMedia——设置finalformat
WrtcConnection.addTrackOperation, 创建了元素operationMap
设置finalFormat
const processOfferMedia = function (mid) {
...
// Determine media format in offer
if (remoteSdp.mediaType(mid) === 'audio') {
const audioPreference = operationMap.get(mid).formatPreference;
// filter audio payload
const audioFormat = remoteSdp.filterAudioPayload(mid, audioPreference);
operationMap.get(mid).finalFormat = audioFormat;
} else if (remoteSdp.mediaType(mid) === 'video') {
// 这是publish的存储的数据
const videoPreference = operationMap.get(mid).formatPreference;
// filter video payload
const videoFormat = remoteSdp.filterVideoPayload(mid, videoPreference);
// 设置 finalFormat
operationMap.get(mid).finalFormat = videoFormat;
}
};
operationMap 的元素就是WrtcConnection.addTrackOperation中添加的,这里是给finalFormat赋值
// mid => { operationId, sdpDirection, type, formatPreference, rids, enabled, finalFormat }
var operationMap = new Map();
dist/webrtc_agent/webrtc/wrtcConnection.js,在WebrtcNode-subscirbe.md
的addTrackOperation 小节 有详细说明。
SdpInfo.filterVideoPayload
2023-05-31T16:44:11.144 - DEBUG: SdpInfo - filterVideoPayload,
mid=1,
preferenece={"optional":[{"codec":"h264"},
{"codec":"vp8"},{"codec":"vp9"},
{"codec":"av1"},{"codec":"h265"}]}
2023-05-31T16:44:11.145 - DEBUG: SdpInfo - filterVideoPayload,
finalFmt video: {"codec":"av1"}
mediaInfo.rtp
rtp:[
{
"payload":123,
"codec":"AV1",
"rate":90000
},
{
"payload":114,
"codec":"red",
"rate":90000
},
{
"payload":115,
"codec":"rtx",
"rate":90000
},
{
"payload":116,
"codec":"ulpfec",
"rate":90000
},
{
"payload":119,
"codec":"rtx",
"rate":90000
}
],
filterVideoPayload(mid, preference) {
log.debug("filterVideoPayload,mid="+ mid +",preferenece="+JSON.stringify(preference));
// Remove unsupported profiles
filterVP9(this.obj, preference, mid);
const finalPrf = filterH264(this.obj, preference, mid);
let finalFmt = null;
let selectedPayload = -1;
// 空
const preferred = preference.preferred;
const optionals = preference.optional || [];
const relatedPayloads = new Set();
const allowedFbTypes = [
'ccm fir',
'nack',
'transport-cc',
'goog-remb',
];
const reservedCodecs = ['red', 'ulpfec'];
const codecMap = new Map();
const payloadOrder = new Map();
const mediaInfo = this.media(mid);
if (mediaInfo && mediaInfo.type == 'video') {
let rtp, fmtp;
let payloads;
// Keep payload order in m line
mediaInfo.payloads.toString().split(' ')
.forEach((p, index) => {
payloadOrder.set(parseInt(p), index);
});
if (mediaInfo.direction === 'recvonly') {
// For subscription
// concat(optionals.map((fmt) => fmt.codec.toLowerCase()));
optionals.forEach((fmt) => {
reservedCodecs.push(fmt.codec.toLowerCase());
});
}
// mediaInfo.rtp 就是上面sdp里的payload
for (let i = 0; i < mediaInfo.rtp.length; i++) {
rtp = mediaInfo.rtp[i];
const codec = rtp.codec.toLowerCase();
if (reservedCodecs.includes(codec)) {
relatedPayloads.add(rtp.payload);
}
codecMap.set(rtp.payload, codec);
// 这个条件不会进入
if (preferred && preferred.codec === codec) {
selectedPayload = rtp.payload;
break;
}
// 在optional 中找mediaInfo.rtp 的codec
if (optionals.findIndex((fmt) => (fmt.codec === codec)) > -1) {
if (selectedPayload < 0 ||
payloadOrder.get(rtp.payload) < payloadOrder.get(selectedPayload)) {
selectedPayload = rtp.payload;
}
}
}
// TODO: uncomment following code after register rtx in video receiver
// if (!mediaInfo.simulcast) {
// for (i = 0; i < mediaInfo.fmtp.length; i++) {
// fmtp = mediaInfo.fmtp[i];
// if (fmtp.config.indexOf(`apt=${selectedPayload}`) > -1) {
// relatedPayloads.add(fmtp.payload);
// }
// }
// }
// Remove ulpfec if h264/h265 is selected
const selectedCodec = codecMap.get(selectedPayload);
if (['h264', 'h265'].includes(selectedCodec)) {
codecMap.forEach((codec, pt) => {
if (codec === 'ulpfec') {
relatedPayloads.delete(pt);
}
});
}
// Remove red if vp9 SVC
if (selectedCodec === 'vp9' && this.getLegacySimulcast(mid)) {
codecMap.forEach((codec, pt) => {
if (codec === 'red') {
relatedPayloads.delete(pt);
}
});
}
relatedPayloads.add(selectedPayload);
// Remove non-selected video payload
mediaInfo.rtp = mediaInfo.rtp.filter(
(rtp) => relatedPayloads.has(rtp.payload));
if (mediaInfo.fmtp) {
mediaInfo.fmtp = mediaInfo.fmtp.filter(
(fmtp) => relatedPayloads.has(fmtp.payload));
}
if (mediaInfo.rtcpFb) {
mediaInfo.rtcpFb = mediaInfo.rtcpFb.filter(
(rtcp) => allowedFbTypes.includes(rtcp.type));
mediaInfo.rtcpFb = mediaInfo.rtcpFb.filter(
(rtcp) => relatedPayloads.has(rtcp.payload));
}
const payloadList = mediaInfo.payloads.toString().split(' ');
if (selectedPayload !== -1) {
payloadList.unshift(selectedPayload.toString());
}
mediaInfo.payloads = payloadList
.filter((p) => relatedPayloads.has(parseInt(p)))
.filter((v, index, self) => self.indexOf(v) === index)
.join(' ');
}
if (selectedPayload !== -1) {
finalFmt = { codec: codecMap.get(selectedPayload) };
if (finalFmt.codec === 'h264' && finalPrf) {
finalFmt.profile = finalPrf;
}
}
log.debug('filterVideoPayload, finalFmt video:', JSON.stringify(finalFmt));
return finalFmt;
}
2.3.3 SdpInfo.answer——localSdp
通过offer到sdp,进行对应的修改,作为answer的sdp。赋值给localSdp中,在WrtcConnection.setupTransport用到。
answer() {
const answer = new SdpInfo(this.toString());
answer.obj.origin = {
username: '-',
sessionId: '0',
sessionVersion: 0,
netType: 'IN',
ipVer: 4,
address: '127.0.0.1'
};
answer.obj.media.forEach(mediaInfo => {
mediaInfo.port = 1;
mediaInfo.rtcp = {
port: 1,
netType: 'IN',
ipVer: 4,
address: '0.0.0.0'
};
if (mediaInfo.setup === 'active') {
mediaInfo.setup = 'passive';
} else {
mediaInfo.setup = 'active';
}
delete mediaInfo.iceOptions;
delete mediaInfo.rtcpRsize;
if (mediaInfo.direction === 'recvonly') {
mediaInfo.direction = 'sendonly';
} else if (mediaInfo.direction === 'sendonly') {
delete mediaInfo.msid;
delete mediaInfo.ssrcGroups;
delete mediaInfo.ssrcs;
mediaInfo.direction = 'recvonly';
}
if (mediaInfo.ext && Array.isArray(mediaInfo.ext)) {
const extMappings = [
'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
// 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',
'urn:ietf:params:rtp-hdrext:sdes:mid',
'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id',
'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id',
'urn:ietf:params:rtp-hdrext:toffset',
'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
// 'urn:3gpp:video-orientation',
// 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',
];
if (mediaInfo.type === 'video') {
extMappings.push(
'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'
);
}
mediaInfo.ext = mediaInfo.ext.filter((e) => {
return extMappings.includes(e.uri);
});
}
if (mediaInfo.rids && Array.isArray(mediaInfo.rids)) {
// Reverse rids direction
mediaInfo.rids.forEach(r => {
r.direction = (r.direction === 'send') ? 'recv' : 'send';
});
}
if (mediaInfo.simulcast) {
// Reverse simulcast direction
if (mediaInfo.simulcast.dir1 === 'send') {
mediaInfo.simulcast.dir1 = 'recv';
} else {
mediaInfo.simulcast.dir1 = 'send';
}
}
});
return answer;
}
SdpInfo.getMediaPort
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
m=video 9 UDP/TLS/RTP/SAVPF 123 114 115 116 119
9 就是port
SdpInfo.mediaType
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
m=video 9 UDP/TLS/RTP/SAVPF 123 114 115 116 119
auido/video, 就是mediaType
operationMap——存放的是track相关的内容
dist/webrtc_agent/webrtc/wrtcConnection.js
在WebrtcNode-subscribe.md
的addTrackOperation 小节 有详细说明
// mid => { operationId, sdpDirection, type, formatPreference, rids, enabled, finalFormat }
var operationMap = new Map();
2.3.4 WrtcConnection.setupTransport——创建WrtcStream
dist/webrtc_agent/webrtc/wrtcConnection.js
小节 3
2.3.5 on_track——这是创建WrtcConnection 传入的callback,tracks-complete
在WebrtcNode-subscribe.md
的 3.WebrtcNode - createWebRTCConnection——返回WrtcConnection
中传入了
function onTrack(trackInfo) {
handleTrackInfo(transportId, trackInfo, controller);
}
调用的代码
on_track({
type: 'tracks-complete',
operationId: opId
});
详细的看3.3, 3.4 小节
3. WrtcConnection.setupTransport——创建WrtcStream
主要是创建MediaStream。
dist/webrtc_agent/webrtc/wrtcConnection.js
2023-04-26T21:54:19.800 - DEBUG: WrtcConnection - Add ssrc 1735005623
to video in SDP for b149e44bb10d4e91bd162a8c6806ae7b
2023-04-26T21:54:19.800 - DEBUG: SdpInfo - Set SSRC: 1 [
{"id":1735005623,"attribute":"cname","value":"o/i14u9pJrxRKAsu"},
{"id":1735005623,"attribute":"msid","value":"guSQ9WN0ZG v0"},
{"id":1735005623,"attribute":"mslabel","value":"guSQ9WN0ZG"},
{"id":1735005623,"attribute":"label","value":"guSQ9WN0ZGv0"}]
2023-04-26 21:54:19,802 - DEBUG: WebRtcConnection -
id: b149e44bb10d4e91bd162a8c6806ae7b, message: setting remote SDP
2023-04-26 21:54:19,802 - DEBUG: WebRtcConnection -
id: b149e44bb10d4e91bd162a8c6806ae7b, message: processing remote SDP
const setupTransport = function (mid) {
let rids = remoteSdp.rids(mid);
const opSettings = operationMap.get(mid);
// recvonly, 是out
const direction = (opSettings.sdpDirection === 'sendonly') ? 'in' : 'out';
const simSsrcs = remoteSdp.getLegacySimulcast(mid);
const trackSettings = remoteSdp.getMediaSettings(mid);
const mediaType = remoteSdp.mediaType(mid);
trackSettings.owner = owner; // 用户id
trackSettings.enableBWE = that.enableBWE;
if (opSettings.finalFormat) {
trackSettings[mediaType].format = opSettings.finalFormat;
if (opSettings.finalFormat.codec === 'vp9' && simSsrcs) {
// Legacy simulcast for VP9 SVC
rids = null;
trackSettings['video'].scalabilityMode = opSettings.scalabilityMode;
}
}
if (rids) {
...
} else {
// 走这里的代码
// No simulcast
if (!trackMap.has(mid)) {
// 1. Connection wrtc
trackMap.set(mid, new WrtcStream(mid, wrtc, direction, trackSettings));
// Set ssrc in local sdp for out direction
// direct 是out
if (direction === 'out') {
// mtype = ‘video’
const mtype = localSdp.mediaType(mid);
// 2. 在WrtcStream 创建的时候,创建了videoFramePacketizer
// 从WrtcStream 获取ssrc
const ssrc = trackMap.get(mid).ssrc(mtype);
if (ssrc) {
log.debug(`Add ssrc ${ssrc} to ${mtype} in SDP for ${wrtcId}`);
const opId = opSettings.operationId;
let msid = msidMap.get(opId);
// 3. setSsrcs
if (msid) {
localSdp.setSsrcs(mid, [ssrc], msid);
} else {
msid = localSdp.setSsrcs(mid, [ssrc]);
msidMap.set(opId, msid);
}
}
}
// 4. Connection wrtc
// remoteSdp 是在processOffer 创建的对象, client端发过来的sdp
wrtc.setRemoteSdp(remoteSdp.singleMediaSdp(mid).toString(), mid);
// 5. Notify new track, 详细见小节 3.5, 3.6
// on_track, wrtcConnection 就是在创建的时候,传入的参数
on_track({
type: 'track-added',
track: trackMap.get(mid),// WrtcStream
operationId: opSettings.operationId,
mid: mid
});
} else {
log.warn(`Conflict trackId ${mid} for ${wrtcId}`);
}
}
return opSettings.operationId;
};
SdpInfo.rids
trackMap 属性——存放了WrtcStream
// composedId => WrtcStream
var trackMap = new Map();
composeId
const composeId = function (mid, rid) {
return mid + ':' + rid;
};
3.1 new WrtcStream
小节 4
创建了MediaStream
见文章 WebrtcNode-subscribe-sdpOffer(2).md
>>>>>>>>>>>>不同于publish的流程>>>>>>>>>>
3.2 WrtcStream.ssrc
dist/webrtc_agent/webrtc/wrtcConnection.js
ssrc(track) {
if (track === 'audio' && this.audioFramePacketizer) {
return this.audioFramePacketizer.ssrc();
}
if (track === 'video' && this.videoFramePacketizer) {
return this.videoFramePacketizer.ssrc();
}
return null;
}
3.2.1 addon.VideoFramePacketizer::getSsrc
source/agent/webrtc/rtcFrame/VideoFramePacketizerWrapper.cc
void VideoFramePacketizer::getSsrc(const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
VideoFramePacketizer* obj = ObjectWrap::Unwrap<VideoFramePacketizer>(args.Holder());
owt_base::VideoFramePacketizer* me = obj->me;
uint32_t ssrc = me->getSsrc();
args.GetReturnValue().Set(Number::New(isolate, ssrc));
}
3.2.2 owt_base::VideoFramePacketizer
uint32_t m_ssrc;
uint32_t getSsrc() { return m_ssrc; }
bool VideoFramePacketizer::init(VideoFramePacketizer::Config& config)
{
if (!m_videoSend) {
// Create Send Video Stream
...
m_videoSend = m_rtcAdapter->createVideoSender(sendConfig);
m_ssrc = m_videoSend->ssrc();
return true;
}
return false;
}
3.3 ???SdpInfo.setSsrcs
这里设置sscrs是什么作用
// ssrcs = [1735005623]
setSsrcs(mid, ssrcs, msid) {
log.debug('setSsrcs,mid='+mid+",ssrcs="+ssrcs+",msid="+msid);
const media = this.media(mid);
if (!media) {
return null;
}
if (!msid) {
// Generate msid
const alphanum = '0123456789' +
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz';
const msidLength = 10;
msid = '';
for (let i = 0; i < msidLength; i++) {
msid += alphanum[Math.floor(Math.random() * alphanum.length)];
}
}
const mtype = (media.type === 'audio') ? 'a' : 'v';
// Only support one ssrc now
const ssrc = ssrcs[0];
media.ssrcs = [
{id: ssrc, attribute: 'cname', value: 'o/i14u9pJrxRKAsu'},
{id: ssrc, attribute: 'msid', value: `${msid} ${mtype}0`},
{id: ssrc, attribute: 'mslabel', value: msid},
{id: ssrc, attribute: 'label', value: `${msid}${mtype}0`},
];
log.debug('Set SSRC:', mid, msid, JSON.stringify(media.ssrcs));
return msid;
}
<<<<<<<<<<<<<<<<<不同于publish的流程<<<<<<<<<<
3.4 Connection.setRemoteSdp
// streamId = mid
setRemoteSdp(sdp, streamId) {
// webRtcConnection wrtc
this.wrtc.setRemoteSdp(sdp, streamId || this.id);
}
3.4.1 NAN_METHOD(WebRtcConnection::setRemoteSdp)
source/agent/webrtc/rtcConn/WebRtcConnection.cc
NAN_METHOD(WebRtcConnection::setRemoteSdp) {
WebRtcConnection* obj = Nan::ObjectWrap::Unwrap<WebRtcConnection>(info.Holder());
std::shared_ptr<erizo::WebRtcConnection> me = obj->me;
if (!me) {
return;
}
std::string sdp = getString(info[0]);
std::string stream_id = getString(info[1]);
bool r = me->setRemoteSdp(sdp, stream_id);
info.GetReturnValue().Set(Nan::New(r));
}
3.4.2 erizo::WebRtcConnection::setRemoteSdp
source/agent/webrtc/rtcConn/erizo/src/erizo/WebRtcConnection.cpp
2023-04-26 21:54:19,802 - DEBUG: WebRtcConnection -
id: b149e44bb10d4e91bd162a8c6806ae7b,
message: setting remote SDP
bool WebRtcConnection::setRemoteSdpInfo(std::shared_ptr<SdpInfo> sdp, std::string stream_id) {
asyncTask([sdp, stream_id] (std::shared_ptr<WebRtcConnection> connection) {
ELOG_DEBUG("%s message: setting remote SDPInfo", connection->toLog());
if (!connection->sending_) {
return;
}
connection->remote_sdp_ = sdp;
// mid
connection->processRemoteSdp(stream_id);
});
return true;
}
??? erizo::WebRtcConnection::processRemoteSdp
source/agent/webrtc/rtcConn/erizo/src/erizo/WebRtcConnection.cpp
2023-04-26 21:54:19,802 - DEBUG: WebRtcConnection - id: b149e44bb10d4e91bd162a8c6806ae7b, message: processing remote SDP
见后文owt-server/WebRTCEvent.md
SdpInfo.singleMediaSdp
dist-debug/webrtc_agent/webrtc/sdpInfo.js
singleMediaSdp(mid) {
const sdp = new SdpInfo(this.toString());
sdp.obj.media = sdp.obj.media.filter(m => m.mid.toString() === mid);
sdp.setBundleMids([mid]);
return sdp;
}
3.5 on__track——track add
dist/webrtc_agent/webrtc/wrtcConnection.js
通知到WebrtcNode(/dist/webrtc_agent/webrtc/index.js)的createWebRTCConnection 中注册的callback, 回调一个对象,
// Notify new track
on_track({
type: 'track-added',
track: trackMap.get(mid), // WrtcStream
operationId: opSettings.operationId,
mid: mid
})
3.6 ======WebrtcNode.handleTrackInfo
dist/webrtc_agent/webrtc/index.js
// 这里传入onTrack, 就是on_track,就是 callback
var connection = new WrtcConnection({
connectionId: transportId,
threadPool: threadPool,
ioThreadPool: ioThreadPool,
network_interfaces: global.config.webrtc.network_interfaces,
owner,
}, function onTransportStatus(status) {
notifyTransportStatus(controller, transportId, status);
}, function onTrack(trackInfo) {
/* trackInfo 就是 {
type: 'track-added',
track: trackMap.get(mid), // WrtcStream
operationId: opSettings.operationId,
mid: mid
}*/
handleTrackInfo(transportId, trackInfo, controller);
});
// trackInfo 就是on__track 回调回来
var handleTrackInfo = function (transportId, trackInfo, controller) {
var publicTrackId;
var updateInfo;
if (trackInfo.type === 'track-added') {
// Generate public track ID
const track = trackInfo.track; // WrtcStream
publicTrackId = transportId + '-' + track.id;
if (mediaTracks.has(publicTrackId)) {
log.error('Conflict public track id:', publicTrackId, transportId, track.id);
return;
}
mediaTracks.set(publicTrackId, track);
mappingPublicId.get(transportId).set(track.id, publicTrackId);
if (track.direction === 'in') {
const trackSource = track.sender();
router.addLocalSource(publicTrackId, 'webrtc', trackSource)
.catch(e => log.warn('Unexpected error during track add:', e));
} else {
// 走这里
router.addLocalDestination(publicTrackId, 'webrtc', track)
.catch(e => log.warn('Unexpected error during track add:', e));
}
// Bind media-update handler
track.on('media-update', (jsonUpdate) => {
log.debug('notifyMediaUpdate:', publicTrackId, jsonUpdate);
notifyMediaUpdate(controller, publicTrackId, track.direction, JSON.parse(jsonUpdate));
});
// Notify controller
const mediaType = track.format('audio') ? 'audio' : 'video';
updateInfo = {
type: 'track-added',
trackId: publicTrackId,
mediaType: track.format('audio') ? 'audio' : 'video',
mediaFormat: track.format(mediaType),
direction: track.direction,
operationId: trackInfo.operationId,
mid: trackInfo.mid,
rid: trackInfo.rid,
active: true,
};
log.debug('notifyTrackUpdate', controller, publicTrackId, updateInfo);
notifyTrackUpdate(controller, transportId, updateInfo);
} else if (trackInfo.type === 'track-removed') {
publicTrackId = mappingPublicId.get(transportId).get(trackInfo.trackId);
if (!mediaTracks.has(publicTrackId)) {
log.error('Non-exist public track id:', publicTrackId, transportId, trackInfo.trackId);
return;
}
log.debug('track removed:', publicTrackId);
router.removeConnection(publicTrackId)
.then(ok => {
mediaTracks.get(publicTrackId).close();
mediaTracks.delete(publicTrackId);
mappingPublicId.get(transportId).delete(trackInfo.trackId);
})
.catch(e => log.warn('Unexpected error during track remove:', e));
// Notify controller
updateInfo = {
type: 'track-removed',
trackId: publicTrackId,
};
notifyTrackUpdate(controller, transportId, updateInfo);
} else if (trackInfo.type === 'tracks-complete') {
updateInfo = {
type: 'tracks-complete',
operationId: trackInfo.operationId
};
notifyTrackUpdate(controller, transportId, updateInfo);
}
};
详细说明见后文 WebrtcNode-subscribe-sdpOffer(3).md
的2. WebrtcNode.handleTrackInfo
。