WebrtcNode, publish-sdp offer 流程(1)

1. AmqpClient - New message received

sdp offer 的消息

AmqpClient - RpcServer New message received {method: 'onTransportSignaling',args: ['aa230ce0863e42baa8bae5c14e91e809',{sdp: 'v=0\r\n' +'o=- 2367615733001925388 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 MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa\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:YH25\r\n' +'a=ice-pwd:l47tWnjyoR1kn3nR0JWjuotU\r\n' +'a=ice-options:trickle\r\n' +'a=fingerprint:sha-256 65:8A:AE:75:0E:6B:50:51:A4:93:A4:01:76:43:3D:83:26:31:E7:75:B8:8A:DC:D4:13:78:14:4E:4D:0B:93:3E\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=sendonly\r\n' +'a=msid:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa AudioTrack-abd86f8a-1f74-4c73-b6fa-c20f5b175a1d\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' +'a=ssrc:3582341671 cname:JTBK6RkjYuislL5i\r\n' +'a=ssrc:3582341671 msid:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa AudioTrack-abd86f8a-1f74-4c73-b6fa-c20f5b175a1d\r\n' +'a=ssrc:3582341671 mslabel:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa\r\n' +'a=ssrc:3582341671 label:AudioTrack-abd86f8a-1f74-4c73-b6fa-c20f5b175a1d\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:YH25\r\n' +'a=ice-pwd:l47tWnjyoR1kn3nR0JWjuotU\r\n' +'a=ice-options:trickle\r\n' +'a=fingerprint:sha-256 65:8A:AE:75:0E:6B:50:51:A4:93:A4:01:76:43:3D:83:26:31:E7:75:B8:8A:DC:D4:13:78:14:4E:4D:0B:93:3E\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=sendonly\r\n' +'a=msid:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa VideoTrack-3e9cd783-7906-49e1-8887-99736156a1d2\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' +'a=ssrc-group:FID 4143402920 1935417042\r\n' +'a=ssrc:4143402920 cname:JTBK6RkjYuislL5i\r\n' +'a=ssrc:4143402920 msid:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa VideoTrack-3e9cd783-7906-49e1-8887-99736156a1d2\r\n' +'a=ssrc:4143402920 mslabel:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa\r\n' +'a=ssrc:4143402920 label:VideoTrack-3e9cd783-7906-49e1-8887-99736156a1d2\r\n' +'a=ssrc:1935417042 cname:JTBK6RkjYuislL5i\r\n' +'a=ssrc:1935417042 msid:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa VideoTrack-3e9cd783-7906-49e1-8887-99736156a1d2\r\n' +'a=ssrc:1935417042 mslabel:MediaStream-0176f9fc-a754-4838-b002-0bdda6d4deaa\r\n' +'a=ssrc:1935417042 label:VideoTrack-3e9cd783-7906-49e1-8887-99736156a1d2\r\n',type: 'offer'}],corrID: 14,replyTo: 'amq.gen-pj6CYWZ4AOsOkYmlAePDEA'
}

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.1var conn = getWebRTCConnection(connectionId);// WrtcConnection connif (conn) {// 参考小结 2.2conn.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

    // 返回的是WrtcConnectionvar 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 是 Connectionif (wrtc) {processSignalling();} else {// should not reach herelog.error('wrtc is not ready');}};

2.3 ==========WrtcConnection.processOffer

dist/webrtc_agent/webrtc/wrtcConnection.js

这里的 remoteSdp 是null,

 const processOffer = function (sdp) {// 第一进来,remoteSdp 是nullif (!remoteSdp) {// First offerremoteSdp = new SdpInfo(sdp);// Check mid, 获取到mid 数组const mids = remoteSdp.mids();for (const mid of mids) {// 设置finalformatprocessOfferMedia(mid);}// 创建answer sdplocalSdp = remoteSdp.answer();// Setup transportlet 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

2.3.2 WrtcConnection.processOfferMedia——设置finalformat

WrtcConnection.addTrackOperation, 创建了元素operationMap

设置finalFormat

 const processOfferMedia = function (mid) {...// Determine media format in offerif (remoteSdp.mediaType(mid) === 'audio') {const audioPreference = operationMap.get(mid).formatPreference;const audioFormat = remoteSdp.filterAudioPayload(mid, audioPreference);operationMap.get(mid).finalFormat = audioFormat;} else if (remoteSdp.mediaType(mid) === 'video') {const videoPreference = operationMap.get(mid).formatPreference;const videoFormat = remoteSdp.filterVideoPayload(mid, videoPreference);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-publish流程.md 的addTrackOperation 小节 有详细说明。

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 directionmediaInfo.rids.forEach(r => {r.direction = (r.direction === 'send') ? 'recv' : 'send';});}if (mediaInfo.simulcast) {// Reverse simulcast directionif (mediaInfo.simulcast.dir1 === 'send') {mediaInfo.simulcast.dir1 = 'recv';} else {mediaInfo.simulcast.dir1 = 'send';}}});return answer;}

SdpInfo.getMediaPort

SdpInfo.mediaType

operationMap——存放的是track相关的内容

dist/webrtc_agent/webrtc/wrtcConnection.js

WebrtcNode-publish流程.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-publish流程.md3.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

const setupTransport = function (mid) {let rids = remoteSdp.rids(mid);const opSettings = operationMap.get(mid);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;trackSettings.enableBWE = that.enableBWE;if (opSettings.finalFormat) {trackSettings[mediaType].format = opSettings.finalFormat;if (opSettings.finalFormat.codec === 'vp9' && simSsrcs) {// Legacy simulcast for VP9 SVCrids = null;trackSettings['video'].scalabilityMode = opSettings.scalabilityMode;}}if (rids) {...} else {// 走这里的代码// No simulcastif (!trackMap.has(mid)) {// 1. Connection wrtctrackMap.set(mid, new WrtcStream(mid, wrtc, direction, trackSettings));// Set ssrc in local sdp for out direction// direct 是inif (direction === 'out') {...}// 2. Connection wrtc// remoteSdp 是在processOffer 创建的对象wrtc.setRemoteSdp(remoteSdp.singleMediaSdp(mid).toString(), mid);// 3. Notify new track, 详细见小节 3.3, 3.4on_track({type: 'track-added',track: trackMap.get(mid),operationId: opSettings.operationId,mid: mid});} else {log.warn(`Conflict trackId ${mid} for ${wrtcId}`);}}return opSettings.operationId;};

SdpInfo.rids

trackMap 属性——存放了WrtcStream

  // composedId => WrtcStreamvar trackMap = new Map();

composeId

  const composeId = function (mid, rid) {return mid + ':' + rid;};

3.1 new WrtcStream

小节 4
创建了MediaStream

3.2 Connection.setRemoteSdp

// streamId = mid
setRemoteSdp(sdp, streamId) {// webRtcConnection wrtcthis.wrtc.setRemoteSdp(sdp, streamId || this.id);}

3.2.1 NAN_METHOD(WebRtcConnection::setRemoteSdp)

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.2.2 WebRtcConnection::setRemoteSdp

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;
}

??? WebRtcConnection::processRemoteSdp

source/agent/webrtc/rtcConn/erizo/src/erizo/WebRtcConnection.cpp

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.3 on__track——track add

dist/webrtc_agent/webrtc/wrtcConnection.js

通知到WebrtcNode(/dist/webrtc_agent/webrtc/index.js)的createWebRTCConnection 中注册的callback, 回调一个对象,

        // Notify new trackon_track({type: 'track-added',track: trackMap.get(mid), // WrtcStreamoperationId: opSettings.operationId,mid: mid})

3.4 ======WebrtcNode.handleTrackInfo

dist/webrtc_agent/webrtc/index.js

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) {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 IDconst track = trackInfo.track; // WrtcStreampublicTrackId = 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 handlertrack.on('media-update', (jsonUpdate) => {log.debug('notifyMediaUpdate:', publicTrackId, jsonUpdate);notifyMediaUpdate(controller, publicTrackId, track.direction, JSON.parse(jsonUpdate));});// Notify controllerconst 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 controllerupdateInfo = {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);}};

时序图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/254158.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

下载描述文件,体验使用 IOS 16 版新特性

简介 最新版本16.0&#xff0c;已经更新&#xff0c;虽然本文针对的是15.4升级&#xff0c;但是操作都是相同的。可以作为参考 温馨提示&#xff1a;大版本体验&#xff0c;一定要保证数据备份&#xff0c;不然有个大BUG的话&#xff0c;数据丢了得不偿失。 最新测试版&…

ios描述文件过期时间查看

这边主要是针对企业证书 以下对生产证书和描述文件过期时间的截图 以下是描述文件查看步骤 只能在苹果电脑查看原先这个包叫xxxx.ipa,要先将后缀改成zip双击这个文件&#xff0c;会生成一个文件夹 点击Payload进去&#xff0c;右键显示包内容找到embedded...bileprovision这…

iOS描述文件设置

iOS 12的某个版本后,iOS证书信任有了一些改动.下面我用Whistle来举个例子. 1. 设置要安装证书的设备的代理与端口 2. 扫码安装证书 其实这一步,扫码安装证书或者是在浏览器地址栏中输入对应的ip地址也是一样的 3. 证书的安装(旧版本这一步做完就能正常使用了) 本节的操作总结成…

iOS 理解证书与描述文件

简单的理一下iOS应用打包用到的Certificate与Provision Profile的关系 1.Certificate ——证书 1.1证书是什么? 百度百科是这么解释的: 由此,知道证书是由权利机关办法的用以证明资格或权利的文件. 1.2 我们向Apple申请证书是在干什么? 我们向Apple申请证书,就是在向权利…

iOS证书和描述文件申请详情步骤

因为之前在iOS开发这条道路上吃了很多不必要的亏&#xff0c;特此想把自己的经验分享给大家&#xff0c;对于初学者来讲不是上架App Store购买一个黑苹果是不划算的&#xff0c;简单的内测或者玩玩完全是浪费钱啊&#xff08;有钱可忽略&#xff09;&#xff0c;特此介绍一个小…

ios14测试版兼容软件,ios14描述文件官方版

ios14描述文件官方版是苹果在全球开发者大会上正式公布的最新版本&#xff0c;也就是iOS14的版本&#xff0c;想要升级的人员可以直接从开发人员中心或者通过系统的推送进行下载&#xff0c;整个申请的流程都和之前的升级没有什么区别&#xff0c;但是系统带来的改变却是显而易…

iOS证书和描述文件申请

首先你需要有一个苹果的 开发者帐号&#xff0c;一个 Mac系统。 如果没有开发者账号&#xff0c;可以先申请一个开发者账号&#xff0c;申请流程详见&#xff1a; iOS公司开发者账号申请教程。 如果你已经有了一个IDP&#xff0c;打开 https://developer.apple.com/account…

ios14测试版兼容软件,ios14描述文件

#ios14描述文件简介 在今天早些时候Apple发布了最新的ios14测试版系统&#xff0c;此次更新也算是一个大版本的更新了&#xff0c;相比ios13来说&#xff0c;ios14在整体的风格变化上并不是很大&#xff0c;主要还是以功能为主&#xff0c;比如小屏的来电显示&#xff0c;画中画…

iOS16 beta8 描述文件官方地址下载

iOS16 Beta8 开发者测试版 iOS16 Beta8版 描述文件下载官方地址&#xff08;需要AppID登录&#xff09; 1&#xff1a;新壁纸增加 本次更新后&#xff0c;家庭应用和车载 CarPlay 新增了一批新的 iOS16 主题壁纸。 2&#xff1a;信息撤回功能增强 现在苹果信息发送后&#x…

Scala初识

1.scala简介 是一种多范式的编程语言&#xff0c;其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台&#xff08;Java虚拟机&#xff09;并兼容现有的Java程序。 scala特点 1.Scala是面向对象的 Scala是一种纯粹的面向对象语言&#xff0c;每一个…

iOS 16描述文件升级方法 iOS 16升级描述文件下载

在6月7日凌晨&#xff0c;iOS 16正式发布了&#xff0c;而升级的方法&#xff0c;有些人准备用描述文件来升级&#xff0c;但不知道具体的升级方法&#xff0c;下面就为大家介绍iOS 16描述文件的具体升级方法。 iOS 16描述文件升级方法 iOS 16升级描述文件下载 一、使用描述文…

IOS项目证书,描述文件等生成和配置 整理

首先得描述一下各个证书的定位&#xff0c;作用&#xff0c;这样在制作的时候心中有谱&#xff0c;对整个流程的把握也会准确一些&#xff1b; 1、开发者证书&#xff08;分为开发和发布两种&#xff0c;类型为ios Development,ios Distribution&#xff09;&#xff0c;这个是…

C++ 遍历算法

&#x1f914;遍历算法&#xff1a; &#x1f642;1.for_each 遍历容器 &#x1f50d;介绍&#xff1a; 在C中&#xff0c;for_each是一个用于遍历容器元素并对它们进行操作的算法。它通常有三个参数&#xff1a; &#x1f4d6;1. 容器的起始位置&#xff08;iterator&am…

layui框架学习(25:弹出层模块_加载框询问框)

layui框架的弹出层模块layer中最重要的函数即layer.open&#xff0c;基于该函数&#xff0c;layer模块封装了很多常用弹出框&#xff0c;上文已介绍了消息框和提示框函数&#xff0c;本文学习加载框和询问框函数的基本用法&#xff0c;同时继续学习layer模块中基础参数的用法。…

【SpringCloud——Elasticsearch(上)】

一、什么是Elasticsearch elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。 二、倒排索引 1、正向索引 2、倒排索引 3、总结 三、ES和MySQL的区别 四、操作索引库 1、基于Kibana&#xff08;WEB界面&#xff09; 以下操作…

代码随想录算法训练营第五十七天 | 回文

647. 回文子串 文档讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;动态规划&#xff0c;字符串性质决定了DP数组的定义 | LeetCode&#xff1a;647.回文子串_哔哩哔哩_bilibili 状态&#xff1a;不会做。 思路 确定dp数组&#xff08;dp table&#xf…

驱动LSM6DS3TR-C实现高效运动检测与数据采集(3)----获取ID

概述 一旦传感器被正确初始化&#xff0c;可以通过SPI或I2C接口向传感器发送读取命令&#xff0c;并接收传感器返回的数据。这个读取过程包括获取LSM6DS3TR传感器提供的加速度计和陀螺仪数据&#xff0c;以及传感器对应的温度信息。 获取数据状态 STATUS_REG (1Eh)是该传感器…

chatgpt赋能python:Python中构造方法的介绍与应用

Python中构造方法的介绍与应用 在Python编程语言中&#xff0c;构造方法通常是类中的一个特殊方法&#xff0c;用于在对象创建时初始化其属性。构造方法使用__init__关键字来定义&#xff0c;而且通常会包含self参数&#xff0c;用于引用创建的新对象。在本文中&#xff0c;我…

木工专用计算机,木工做多功能电脑台带书柜架一体图片 自己打造电脑桌用实木还是生态木颗粒板...

黑色十字条纹状的书架&#xff0c;给人带来一种与众不同的感觉&#xff0c;褐色的实木地板铺贴在地面上&#xff0c;褐色的地面与整个橱柜形成了鲜明的对比。褐色给人一种灰溜溜的感觉&#xff0c;但是这种颜色很有古典美&#xff0c;而且褐色的地面又特别的耐脏&#xff0c;这…

python爬虫大众点评字体反爬

字形相同的字体反爬问题解析 问题所在&#xff1a;部分数据加载时使用网站自定义的字体&#xff0c;浏览器访问网页时字体文件会加载到浏览器中&#xff0c;爬虫访问时没有对应的自定义字体&#xff0c;所以就得不到那部分数据&#xff0c;如图1&#xff0c;加密的这部分数据在…