WebrtcNode publish 流程

news2024/10/7 12:24:49

WebrtcNode publish 流程

1. AmqpClient - RpcServer New message received

AmqpClient - RpcServer New message received {
  method: 'publish',
  args: [
    '67f9309ce6e645fc8a4bb9cac6406eb2',
    'webrtc',
    {
      transportId: '67f9309ce6e645fc8a4bb9cac6406eb2',
      tracks: [Array],
      controller: 'conference-56eea170c80b82268f69@192.168.221.62_0',
      owner: 'LRvj457Mpnk8QX0uAAAB'
    }
  ],
  corrID: 13,
  replyTo: 'amq.gen-p1mY173rLRg1y9y5sqwpWA'
}

这个消息是从conference-agent 发送过来的。

2. WebrtcNode - publish

webrtc_agent/webrtc/index.js

WebrtcNode - publish, 
operationId: 67f9309ce6e645fc8a4bb9cac6406eb2 
connectionType: webrtc 
options: {
  transportId: '67f9309ce6e645fc8a4bb9cac6406eb2',
  tracks: [
    {
      mid: '0',
      source: 'mic',
      type: 'audio',
      formatPreference: [Object]
    },
    {
      mid: '1',
      source: 'camera',
      type: 'video',
      formatPreference: [Object]
    }
  ],
  controller: 'conference-56eea170c80b82268f69@192.168.221.62_0',
  owner: 'LRvj457Mpnk8QX0uAAAB'
}


2023-04-04T11:45:26.657  - DEBUG: WebrtcNode - publish, options.formatPreference {
  optional: [
    { codec: 'opus', sampleRate: 48000, channelNum: 2 },
    { codec: 'isac', sampleRate: 16000 },
    { codec: 'isac', sampleRate: 32000 },
    { codec: 'g722', sampleRate: 16000, channelNum: 1 },
    { codec: 'pcma' },
    { codec: 'pcmu' },
    { codec: 'aac' },
    { codec: 'ac3' },
    { codec: 'nellymoser' },
    { codec: 'ilbc' }
  ]
}
2023-04-04T11:45:26.657  - DEBUG: WebrtcNode - publish, options.formatPreference {
  optional: [
    { codec: 'h264' },
    { codec: 'vp8' },
    { codec: 'vp9' },
    { codec: 'av1' },
    { codec: 'h265' }
  ]
}
		/*
     * For operations on type webrtc, publicTrackId is connectionId.
     * For operations on type internal, operationId is connectionId.
     */
    // functions: publish, unpublish, subscribe, unsubscribe, linkup, cutoff
    // options = {
    //   transportId,
    //   tracks = [{mid, type, formatPreference, scalabilityMode}],
    //   controller, owner
    // }
        // 其中 controller - 发送消息,发送给谁 to
        // 
    // formatPreference = {preferred: MediaFormat, optional: [MediaFormat]}
    that.publish = function (operationId, connectionType, options, callback) {
        log.debug('publish, operationId:', operationId, 'connectionType:', connectionType, 'options:', options);
        if (mappingTransports.has(operationId)) {
            return callback('callback', {type: 'failed', reason: 'Connection already exists:'+operationId});
        }

        // WrtcConnection
        var conn = null;
        if (connectionType === 'webrtc') {
            if (!options.transportId) {
                // Generate a transportId

            }
              // 1. 创建 WebRTCConnection
            conn = createWebRTCConnection(options.transportId, options.controller, options.owner);
              // 2. addTrackOperation
            options.tracks.forEach(function trackOp(t) { // t = options.tracks
                conn.addTrackOperation(operationId, 'sendonly', t);
            });
            mappingTransports.set(operationId, options.transportId);
            callback('callback', 'ok');
        } else {
            log.error('Connection type invalid:' + connectionType);
        }

        if (!conn) {
            log.error('Create connection failed', operationId, connectionType);
            callback('callback', {type: 'failed', reason: 'Create Connection failed'});
        }
    };

???callback 是哪里来的

2.1 createWebRTCConnection

创建WebRtcConnection

小节 3

2.2 addTrackOperation

小节 4

3. WebrtcNode - createWebRTCConnection——返回WrtcConnection

webrtc_agent/webrtc/index.js

 var createWebRTCConnection = function (transportId, controller, owner) {
        if (peerConnections.has(transportId)) {
            log.debug('PeerConnection already created:', transportId);
            return peerConnections.get(transportId);
        }
        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);
        });

        // map 存放WebRtcconneciton
        peerConnections.set(transportId, connection);
               // map 在同一个transportId,存放trackId对应的publicTrackId的map
        mappingPublicId.set(transportId, new Map());
        connection.controller = controller;
        return connection;
    };

3.1 peerConnections 成员

存放 WrtcConnection,transportId与connection一一对应

// Map { transportId => WrtcConnection }
var peerConnections = new Map();
----------------------------------       
peerConnections.set(transportId, connection);

3.2 mappingPublicId成员

// Map { transportId => Map { trackId => publicTrackId } }
var mappingPublicId = new Map();
--------------------------------------------
mappingPublicId.set(transportId, new Map());
  • 主要是存放publicTrackId, 在handleTrackInfotrack-added,或 destroyTransport 中获取publicTrackId
  • createWebRTCConnection 的时候,只是创建了空的Map,而Map中存放的{ trackId => publicTrackId } },是在 handleTrackInfotrackInfo.type === 'track-added' 中存入的。

dist-debug/webrtc_agent/webrtc/index.js

 var handleTrackInfo = function (transportId, trackInfo, controller) {
        var publicTrackId;
        var updateInfo;
        if (trackInfo.type === 'track-added') {
            // Generate public track ID
            const track = trackInfo.track;
            publicTrackId = transportId + '-' + track.id;
            if (mediaTracks.has(publicTrackId)) {
                log.error('Conflict public track id:', publicTrackId, transportId, track.id);
                return;
            }
 ...
            mappingPublicId.get(transportId).set(track.id, publicTrackId);

 ...
 }

mappingTransports

		// Map { operationId => transportId }
    var mappingTransports = new Map();

主要是在publish,subscribe的时候,存放了transportId,在publish,subscribe 重复创建connection。

====关于id的小结

  • transportId, 对应的就是WebRtcConnection,各种连接conneciton id都是这个;存放在peerConnections

  • trackId,就是对应的track,例如audio track,videotrack, 从0开始,往上递增;在同一个transportId下,tackId 对应的就是publicTrackId;

  • publicTrackId = transportId + ‘-’ + trackId; publicTrackId 对应的就是 WrtcTrack, 存放在mediatTracks;

  • operationId 和transportId 一一对应,看日志,目前两个值是一样的, 存放在mappingTransports;
    在这里插入图片描述

3.3 new WrtcConnection——创建rtc连接,并初始化

webrtc_agent/webrtc/wrtcConnection.js

 module.exports = function (spec, on_status, on_track) {
     ...


  wrtc = new Connection(wrtcId, threadPool, ioThreadPool, { ipAddresses });
   // CallBase后面的文章会讲到
  wrtc.callBase = new CallBase();
  // wrtc.addMediaStream(wrtcId, {label: ''}, direction === 'in');

  initWebRtcConnection(wrtc);
  return that;
};

3.3.1 new Connection

小节 3.4 , 创建WebrtcConnection

3.3.2 WrtcConnection - initWebRtcConnection

小节 3.5,主要是注册监听事件, sdp,candidate 协商结果, 都是从c++ callback 回来

3.4 new Connection——创建c++的WebrtcConnection

webrtc_agent/webrtc/connection.js

class Connection extends EventEmitter {
  constructor (id, threadPool, ioThreadPool, options = {}) {
    super();
    log.info(`message: Connection, id: ${id}`);
    this.id = id;
    this.threadPool = threadPool;
    this.ioThreadPool = ioThreadPool;
    this.mediaConfiguration = 'default';
    this.mediaStreams = new Map();
    this.initialized = false;
    this.options = options;
    this.ipAddresses = options.ipAddresses || '';
    this.trickleIce = options.trickleIce || false;
    this.metadata = this.options.metadata || {};
    this.isProcessingRemoteSdp = false;
    this.ready = false;
// native 的addon.WebRtcConnection
    this.wrtc = this._createWrtc();
  }
...
}

3.4.1 -----------Connection._createWrtc

 _createWrtc() {
    var wrtc = new addon.WebRtcConnection(
      this.threadPool, this.ioThreadPool, this.id,
      global.config.webrtc.stunserver,
      global.config.webrtc.stunport,
      global.config.webrtc.minport,
      global.config.webrtc.maxport,
      false, //this.trickleIce,
      this._getMediaConfiguration(this.mediaConfiguration),
      false,
      '', // turnserver,
      '', // turnport,
      '', //turnusername,
      '', //turnpass,
      '', //networkinterface
      this.ipAddresses
    );

    return wrtc;
  }

3.4.2 NAN_METHOD(WebRtcConnection::New)

source/agent/webrtc/rtcConn/WebRtcConnection.cc

NAN_METHOD(WebRtcConnection::New) {
    ...
    WebRtcConnection* obj = new WebRtcConnection();
    obj->me = std::make_shared<erizo::WebRtcConnection>(worker, io_worker, wrtcId, iceConfig,
                                                        rtp_mappings, ext_mappings, obj);

    uv_async_init(uv_default_loop(), &obj->async_, &WebRtcConnection::eventsCallback);
    obj->Wrap(info.This());
    info.GetReturnValue().Set(info.This());
    obj->asyncResource_ = new Nan::AsyncResource("WebRtcConnectionCallback");
    ...
}

3.4.3 WebRtcConnection::WebRtcConnection

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

WebRtcConnection::WebRtcConnection(std::shared_ptr<Worker> worker, std::shared_ptr<IOWorker> io_worker,
    const std::string& connection_id, const IceConfig& ice_config, const std::vector<RtpMap> rtp_mappings,
    const std::vector<erizo::ExtMap> ext_mappings, WebRtcConnectionEventListener* listener) :
    connection_id_{connection_id},
    audio_enabled_{false}, video_enabled_{false}, bundle_{false}, conn_event_listener_{listener},
    ice_config_{ice_config}, rtp_mappings_{rtp_mappings}, extension_processor_{ext_mappings},
    worker_{worker}, io_worker_{io_worker},
    remote_sdp_{std::make_shared<SdpInfo>(rtp_mappings)}, local_sdp_{std::make_shared<SdpInfo>(rtp_mappings)},
    audio_muted_{false}, video_muted_{false}, first_remote_sdp_processed_{false}
    {
  ELOG_INFO("%s message: constructor, stunserver: %s, stunPort: %d, minPort: %d, maxPort: %d",
      toLog(), ice_config.stun_server.c_str(), ice_config.stun_port, ice_config.min_port, ice_config.max_port);
  stats_ = std::make_shared<Stats>();
  // distributor_ = std::unique_ptr<BandwidthDistributionAlgorithm>(new TargetVideoBWDistributor());
  global_state_ = CONN_INITIAL;

  trickle_enabled_ = ice_config_.should_trickle;
  slide_show_mode_ = false;

  sending_ = true;
}

3.5 WrtcConnection - initWebRtcConnection

webrtc_agent/webrtc/wrtcConnection.js

/*
   * Given a WebRtcConnection waits for the state CANDIDATES_GATHERED for set remote SDP.
   */
    // wrtc 是Connection,Connection 继承于EventEmitter
  var initWebRtcConnection = function (wrtc) {
    // EventEmitter.on()用于监听事件
    // 在c++从回调到js中,就是在Connection.init中触发
    // Connection wrtc
    wrtc.on('status_event', (evt, status) => {
      if (evt.type === 'answer') {
        processAnswer(evt.sdp);

        const message = localSdp.toString();
        log.debug('Answer SDP', message);
        on_status({type: 'answer', sdp: message});

      } else if (evt.type === 'candidate') {
        let message = evt.candidate;
        networkInterfaces.forEach((i) => {
          if (i.ip_address && i.replaced_ip_address) {
            message = message.replace(new RegExp(i.ip_address, 'g'), i.replaced_ip_address);
          }
        });
        on_status({type: 'candidate', candidate: message});

      } else if (evt.type === 'failed') {
        log.warn('ICE failed, ', status, wrtc.id);
        on_status({type: 'failed', reason: 'Ice procedure failed.'});

      } else if (evt.type === 'ready') {
        log.debug('Connection ready, ', wrtc.wrtcId);
        on_status({
          type: 'ready'
        });
      }
    });

    // Connection wrtc, 小节 3.5.2
    //  var wrtcId = spec.connectionId;就是transportId
    wrtc.init(wrtcId);
  };

3.5.1 Connection/EventEmitter.on——监听事件

wrtc.on('status_event', (evt, status) => {
      if (evt.type === 'answer') {
        processAnswer(evt.sdp);

        const message = localSdp.toString();
        log.debug('Answer SDP', message);
        on_status({type: 'answer', sdp: message});

      } else if (evt.type === 'candidate') {
        let message = evt.candidate;
        networkInterfaces.forEach((i) => {
          if (i.ip_address && i.replaced_ip_address) {
            message = message.replace(new RegExp(i.ip_address, 'g'), i.replaced_ip_address);
          }
        });
        on_status({type: 'candidate', candidate: message});

      } else if (evt.type === 'failed') {
        log.warn('ICE failed, ', status, wrtc.id);
        on_status({type: 'failed', reason: 'Ice procedure failed.'});

      } else if (evt.type === 'ready') {
        log.debug('Connection ready, ', wrtc.wrtcId);
        on_status({
          type: 'ready'
        });
      }
    });

3.5.2 Connection.init——初始化c++的WebRtcConnection

webrtc_agent/webrtc/connection.js

//  streamId 就是 var wrtcId = spec.connectionId;就是transportId
init(streamId) {
    if (this.initialized) {
      return false;
    }
    const firstStreamId = streamId;
    this.initialized = true;
    log.debug(`message: Init Connection, connectionId: ${this.id} `+
              `${logger.objectToLog(this.options)}`);
    this.sessionVersion = 0;

    // WebRtcConnection c++ wrapper, 调用c++
    this.wrtc.init((newStatus, mess, streamId) => {
      // 对应日志3.5.6
      log.debug('message: WebRtcConnection status update, ' +
               'id: ' + this.id + ', status: ' + newStatus +
                ', ' + logger.objectToLog(this.metadata) + mess);
      switch(newStatus) {
        case CONN_INITIAL:
           // 触发3.5.1
          this.emit('status_event', {type: 'started'}, newStatus);
          break;

        case CONN_SDP_PROCESSED:
          this.isProcessingRemoteSdp = false;
          // this.latestSdp = mess;
          // this._maybeSendAnswer(newStatus, streamId);
          break;

        case CONN_SDP:
          this.latestSdp = mess;
          this._maybeSendAnswer(newStatus, streamId);
          break;

        case CONN_GATHERED:
          this.alreadyGathered = true;
          this.latestSdp = mess;
          this._maybeSendAnswer(newStatus, firstStreamId);
          break;

        case CONN_CANDIDATE:
          mess = mess.replace(this.options.privateRegexp, this.options.publicIP);
          this.emit('status_event', {type: 'candidate', candidate: mess}, newStatus);
          break;

        case CONN_FAILED:
          log.warn('message: failed the ICE process, ' + 'code: ' + WARN_BAD_CONNECTION +
                   ', id: ' + this.id);
          this.emit('status_event', {type: 'failed', sdp: mess}, newStatus);
          break;

        case CONN_READY:
          log.debug('message: connection ready, ' + 'id: ' + this.id +
                    ', ' + 'status: ' + newStatus + ' ' + mess + ',' + streamId);
          if (!this.ready) {
            this.ready = true;
            this.emit('status_event', {type: 'ready'}, newStatus);
          }
          break;
      }
    });
    if (this.options.createOffer) {
      log.debug('message: create offer requested, id:', this.id);
      const audioEnabled = this.options.createOffer.audio;
      const videoEnabled = this.options.createOffer.video;
      const bundle = this.options.createOffer.bundle;
        // WebRtcConnection c++ wrapper, 调用c++
      this.wrtc.createOffer(videoEnabled, audioEnabled, bundle);
    }
    // 触发3.5.1,代码就是3.5
    this.emit('status_event', {type: 'initializing'});
    return true;
  }

3.5.3 NAN_METHOD(WebRtcConnection::init)

source/agent/webrtc/rtcConn/WebRtcConnection.cc

NAN_METHOD(WebRtcConnection::init) {
  WebRtcConnection* obj = Nan::ObjectWrap::Unwrap<WebRtcConnection>(info.Holder());
  std::shared_ptr<erizo::WebRtcConnection> me = obj->me;

  obj->eventCallback_ = new Nan::Callback(info[0].As<Function>());
  bool r = me->init();

  info.GetReturnValue().Set(Nan::New(r));
}

3.5.4 WebRtcConnection::init()

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

bool WebRtcConnection::init() {
    maybeNotifyWebRtcConnectionEvent(global_state_, "");
    return true;
}

3.5.5 WebRtcConnection::maybeNotifyWebRtcConnectionEvent

void WebRtcConnection::maybeNotifyWebRtcConnectionEvent(const WebRTCEvent& event, const std::string& message,
    const std::string& stream_id) {
  boost::mutex::scoped_lock lock(event_listener_mutex_);
  if (!conn_event_listener_) {
      return;
  }
  conn_event_listener_->notifyEvent(event, message, stream_id);
}

—> 3.5.2

this.wrtc.init((newStatus, mess, streamId) => {
      log.debug('message: WebRtcConnection status update, ' +
               'id: ' + this.id + ', status: ' + newStatus +
                ', ' + logger.objectToLog(this.metadata) + mess);
      switch(newStatus) {
        case CONN_INITIAL:
          this.emit('status_event', {type: 'started'}, newStatus);
          break;
          ....
      });

—> 3.5.1

wrtc.on('status_event', (evt, status) => {
  ...
});

3.5.6 log—》3.5.2

这里对应的就是3.5.2 小节中 从c++ 调用到js的状态更新,CONN_INITIAL=101 初始化成功

Connection - message: WebRtcConnection status update, 
id: 67f9309ce6e645fc8a4bb9cac6406eb2, status: 101, {}

CONN_INITIAL = 101

3.5.7 status

const CONN_INITIAL        = 101;
const CONN_STARTED        = 102;
const CONN_GATHERED       = 103;
const CONN_READY          = 104;
const CONN_FINISHED       = 105;
const CONN_CANDIDATE      = 201;
const CONN_SDP            = 202;
const CONN_SDP_PROCESSED  = 203;
const CONN_FAILED         = 500;
const WARN_BAD_CONNECTION = 502;

4. WrtcConnection.addTrackOperation

dist/webrtc_agent/webrtc/wrtcConnection.js

// option = {mid, type, formatPreference, scalabilityMode}
  that.addTrackOperation = function (operationId, sdpDirection, option) {
    var ret = false;
    var {mid, type, formatPreference, scalabilityMode} = option;
    if (!operationMap.has(mid)) {
      log.debug(`MID ${mid} for operation ${operationId} add`);
      const enabled = true;
    // map
      operationMap.set(mid, {operationId, type, sdpDirection, formatPreference, enabled});
      if (scalabilityMode) {
        operationMap.get(mid).scalabilityMode = scalabilityMode;
      }
      ret = true;
    } else {
      log.warn(`MID ${mid} has mapped operation ${operationMap.get(mid).operationId}`);
    }
    return ret;
  };

4.1 WrtcConnection.operationMap

  // mid => { operationId, sdpDirection, type, formatPreference, rids, enabled, finalFormat }
  var operationMap = new Map();

mid 就是trackId,

存放了track相关的信息,如operationId, sdpDirection, type, formatPreference, rids, enabled, finalFormat

5. 流程图

在这里插入图片描述

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

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

相关文章

Spring注解开发——bean的作用范围与生命周期管理

文章目录 1.bean管理1.1 bean作用范围Scope注解 1.2 bean生命周期PostConstructPreDestroy 2.小结 1.bean管理 1.1 bean作用范围 Scope注解 不写或者添加Scope(“singleton”)表示的是单例 如何配置多例&#xff1f; 在Scope(“prototype”)表示的是多例 1.2 bean生命周…

chatgpt赋能python:Python中的倒序函数

Python中的倒序函数 Python是一种现代编程语言&#xff0c;它在不断地扩展和更新&#xff0c;使得它在编程领域中变得越来越流行。Python的一个特点是&#xff0c;代码简洁而且易于理解。其中一个重要的特性是它的倒序函数。在本文中&#xff0c;我们将介绍Python中的倒序函数…

一位年薪40W的测试被开除,回怼的一番话,令人沉思

一位年薪40W测试工程师被开除回怼道&#xff1a;“反正我有技术&#xff0c;在哪不一样” 一技傍身&#xff0c;万事不愁&#xff0c;当我们掌握了一技之长后&#xff0c;在职场上说话就硬气了许多&#xff0c;不用担心被炒&#xff0c;反过来还可以炒了老板&#xff0c;这一点…

基于深度学习的人脸识别与人员信息管理软件【python源码+UI界面+功能源码详解】

人脸识别功能演示 摘要&#xff1a;人脸识别&#xff08;Face Recognition&#xff09;是基于人的脸部特征信息进行身份识别的一种生物识别技术&#xff0c;可以用来确认用户身份。本文详细介绍了人脸识别基本的实现原理&#xff0c;并且基于python与pyqt开发了人脸识别与信息管…

智哪儿专访IF ROOM品牌主理人Amy:IF ROOM如何定义未来家居?

作为未来主义家居首倡者&#xff0c;IF ROOM在5月25日-28日举行的深圳时尚家居设计周上首次亮相就获得行业广泛热议。IF ROOM展馆以其未来主义设计风格和跨越东西方文化的美的意象&#xff0c;得到了包括设计师在内的广大行业人士追捧&#xff0c;IF ROOM展位成为深圳时尚家居设…

​字创未来 方正字库第十二届“方正奖”设计大赛正式来袭

传承汉字文化精髓&#xff0c;方正字库在字体行业不断探索深耕。方正字库一直致力于弘扬中华汉字文化&#xff0c;不断促进行业字体设计创新发展。于2001年在行业最艰难的时候&#xff0c;怀揣着对字体设计未来的美好向往&#xff0c;首届“北大方正奖”印刷字体设计大赛&#…

灵活使用Postman环境变量和全局变量,提高接口测试效率!

目录 前言&#xff1a; 环境变量和全局变量的概念 环境变量和全局变量的使用方法 1. 定义变量 2. 使用变量 环境变量和全局变量的实例代码 变量的继承和覆盖 变量的动态设置 总结&#xff1a; 前言&#xff1a; Postman是一个流行的API开发和接口测试工具&#xff0c;…

过滤器Filter,拦截器Interceptor

过滤器Filter 快速入门 详情 登录校验-Filter package com.itheima.filter;import com.alibaba.fastjson.JSONObject; import com.itheima.pojo.Result; import com.itheima.utils.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils…

码住!IC设计常用工具合集!

芯片设计过程中&#xff0c;选择和使用适合的工具是非常重要的。芯片设计工具通常分为三类&#xff1a;EDA工具、模拟仿真工具和布局工具。 一、EDA工具 EDA工具是芯片设计的核心&#xff0c;它包括原理图绘制、逻辑综合、门级仿真工具和物理版图编辑等&#xff0c;可以帮助设计…

Ubuntu 安装 jdk1.8

1 更新软件包 sudo apt update 2 安装jdk1.8 sudo apt install openjdk-8-jdk 3 查看版本 java -version安装成功

自适应滤波方法——LMS算法

自适应滤波器 自适应滤波器&#xff1a;一种能够根据输入信号自动调整自身参数的数字滤波器 非自适应滤波器&#xff1a;具有静态滤波器系统的数字滤波器&#xff0c;静态系数构成了滤波器的传递函数 对于一些应用&#xff08;如系统辨识、预测、去噪等&#xff09;&#xff…

固定翼无人机1:500地籍

引言 上几期&#xff0c;睿铂为大家推送了两篇相对精度验证的文章&#xff0c;其优秀的成果引来了业界人士不少的赞叹。同时&#xff0c;许多客户朋友又提出了更高的要求。目前&#xff0c;在地籍精度的项目中&#xff0c;使用多旋翼无人机挂载倾斜摄影相机作业&#xff0c;是能…

机器学习笔记 - 基于TensorFlow Lite的模型部署

一、简述 TensorFlow Lite 是一个移动端库,可用于在移动设备、微控制器和其他边缘设备上部署模型。 假设要执行图像分类任务。首先决定任务的模型。是要创建自定义模型;或者使用预训练模型,如 InceptionNet、MobileNet、NASNetLarge 等。又或者在预训练模型上应用迁…

【问题记录】解决vite多页应用路由改用history之后本地刷新404问题

当前包的版本信息&#xff1a; "vue": "^2.7.14", "vue-router": "^3.6.5" "vite": "^3.0.7", 首先&#xff0c;修改路由模式 首先&#xff0c;将之前多页项目中的某个页面路由模式改用 history &#xff0c;…

C++服务器框架开发4——日志系统logger/.cpp与.cc

该专栏记录了在学习一个开发项目的过程中遇到的疑惑和问题。 其教学视频见&#xff1a;[C高级教程]从零开始开发服务器框架(sylar) 上一篇&#xff1a;C服务器框架开发3——协程与线程的简单理解/并发与并行 C服务器框架开发4——日志系统logger 目前进度.cpp与.cc 目前进度 …

ByConity与主流开源OLAP引擎(Clickhouse、Doris、Presto)性能对比分析

引言&#xff1a; 随着数据量和数据复杂性的不断增加&#xff0c;越来越多的企业开始使用OLAP&#xff08;联机分析处理&#xff09;引擎来处理大规模数据并提供即时分析结果。在选择OLAP引擎时&#xff0c;性能是一个非常重要的因素。 因此&#xff0c;本文将使用TPC-DS基准…

排序算法、HashMap原理以及单例模式

文章目录 1、二分查找2、冒泡排序3、选择排序4、插入排序5、希尔排序6、快速排序&#xff08;面试写这个&#xff09;7、ArrayList8、Iterator9、LinkedList10、HashMap10.1、基本数据结构底层数据结构&#xff0c;1.7和1.8有什么不同&#xff1f; 10.2、树化与退化为何要用红黑…

【LINUX】进程间信号

文章目录 前言铺垫信号 信号的产生1、终端按钮产生信号2、调用系统函数向进程发送信号3、软件条件产生信号4、硬件异常产生信号 信号的保存补充&#xff1a;位图数据结构信号的处理结语 前言 铺垫 1、日常中我们能经常感受到信号的存在&#xff1a;红灯停绿灯行、三更鸡鸣、妈…

60.网络训练中的超参调整策略—学习率调整2

4、inverse_time_decay inverse_time_decay(learning_rate, global_step, decay_steps, decay_rate,staircase=False, name=None)逆时衰减,这种方式和指数型类似。如图, 5、cosine_decay cosine_decay(learning_rate, global_step