webrtc-m79-RTP NTP 线性回归

1 几个定义

class RtpVideoStreamReceiver : public LossNotificationSender,
                               public RecoveredPacketReceiver,
                               public RtpPacketSinkInterface,
                               public KeyFrameRequestSender,
                               public video_coding::OnAssembledFrameCallback,
                               public video_coding::OnCompleteFrameCallback,
                               public OnDecryptedFrameCallback,
                               public OnDecryptionStatusChangeCallback 
===>
RemoteNtpTimeEstimator ntp_estimator_; //


class RemoteNtpTimeEstimator {
 public:
  explicit RemoteNtpTimeEstimator(Clock* clock);

  ~RemoteNtpTimeEstimator();

  // Updates the estimator with round trip time |rtt|, NTP seconds |ntp_secs|,
  // NTP fraction |ntp_frac| and RTP timestamp |rtcp_timestamp|.
  bool UpdateRtcpTimestamp(int64_t rtt,
                           uint32_t ntp_secs,
                           uint32_t ntp_frac,
                           uint32_t rtp_timestamp);

  // Estimates the NTP timestamp in local timebase from |rtp_timestamp|.
  // Returns the NTP timestamp in ms when success. -1 if failed.
  int64_t Estimate(uint32_t rtp_timestamp);

 private:
  Clock* clock_;
  MovingMedianFilter<int64_t> ntp_clocks_offset_estimator_;
  RtpToNtpEstimator rtp_to_ntp_; // 
  int64_t last_timing_log_ms_;
  RTC_DISALLOW_COPY_AND_ASSIGN(RemoteNtpTimeEstimator);
};


class RtpToNtpEstimator {
 public:
  RtpToNtpEstimator();
  ~RtpToNtpEstimator();

  // RTP and NTP timestamp pair from a RTCP SR report.
  struct RtcpMeasurement {
    RtcpMeasurement(uint32_t ntp_secs,
                    uint32_t ntp_frac,
                    int64_t unwrapped_timestamp);
    bool IsEqual(const RtcpMeasurement& other) const;

    NtpTime ntp_time;
    int64_t unwrapped_rtp_timestamp;
  };

  // Estimated parameters from RTP and NTP timestamp pairs in |measurements_|.
  struct Parameters {
    Parameters() : frequency_khz(0.0), offset_ms(0.0) {}

    Parameters(double frequency_khz, double offset_ms)
        : frequency_khz(frequency_khz), offset_ms(offset_ms) {}

    double frequency_khz;
    double offset_ms;
  };

  // Updates measurements with RTP/NTP timestamp pair from a RTCP sender report.
  // |new_rtcp_sr| is set to true if a new report is added.
  bool UpdateMeasurements(uint32_t ntp_secs,
                          uint32_t ntp_frac,
                          uint32_t rtp_timestamp,
                          bool* new_rtcp_sr);

  // Converts an RTP timestamp to the NTP domain in milliseconds.
  // Returns true on success, false otherwise.
  bool Estimate(int64_t rtp_timestamp, int64_t* ntp_timestamp_ms) const;

  // Returns estimated rtp to ntp linear transform parameters.
  const absl::optional<Parameters> params() const;

  static const int kMaxInvalidSamples = 3;

 private:
  void UpdateParameters(); // 

  int consecutive_invalid_samples_;
  std::list<RtcpMeasurement> measurements_;
  absl::optional<Parameters> params_; // 
  mutable TimestampUnwrapper unwrapper_;
};

2 线性回归过程

PacketReceiver::DeliveryStatus Call::DeliverPacket(
    MediaType media_type,
    rtc::CopyOnWriteBuffer packet,
    int64_t packet_time_us) {
  RTC_DCHECK_RUN_ON(&configuration_sequence_checker_);
  if (IsRtcp(packet.cdata(), packet.size()))
    return DeliverRtcp(media_type, packet.cdata(), packet.size()); // 处理 RTCP 报文 

  return DeliverRtp(media_type, std::move(packet), packet_time_us);
}


									// 通过缩进来表示一些中间对象的创建过程
									VideoReceiveStream::VideoReceiveStream(
									    TaskQueueFactory* task_queue_factory,
									    RtpStreamReceiverControllerInterface* receiver_controller,
									    int num_cpu_cores,
									    PacketRouter* packet_router,
									    VideoReceiveStream::Config config, // config 来自 WebRtcVideoChannel::AddRecvStream
									    ProcessThread* process_thread,
									    CallStats* call_stats,
									    Clock* clock,
									    VCMTiming* timing)
									    : task_queue_factory_(task_queue_factory),
									      transport_adapter_(config.rtcp_send_transport), // config.rtcp_send_transport 就是 WebRtcVideoChannel 的 this指针,具体见 WebRtcVideoChannel::AddRecvStream
									      config_(std::move(config)), // transport_adapter_ 是 webrtc::internal::TransportAdapter 类型,其内包含  webrtc::Transport* 的成员变量,该变量指向 WebRtcVideoChannel
									      num_cpu_cores_(num_cpu_cores),
									      process_thread_(process_thread),
									      clock_(clock),
									      call_stats_(call_stats),
									      source_tracker_(clock_),
									      stats_proxy_(&config_, clock_),
									      rtp_receive_statistics_(ReceiveStatistics::Create(clock_)),
									      timing_(timing),
									      video_receiver_(clock_, timing_.get()),
									      rtp_video_stream_receiver_(clock_,
									                                 &transport_adapter_, // 
									                                 call_stats,
									                                 packet_router,
									                                 &config_,
									                                 rtp_receive_statistics_.get(),
									                                 &stats_proxy_,
									                                 process_thread_,
									                                 this,     // NackSender
									                                 nullptr,  // Use default KeyFrameRequestSender
									                                 this,     // OnCompleteFrameCallback
									                                 config_.frame_decryptor), // RtpVideoStreamReceiver rtp_video_stream_receiver_;
									
									
									RtpVideoStreamReceiver::RtpVideoStreamReceiver(
									    Clock* clock,
									    Transport* transport, // transport 指向的是 webrtc::internal::TransportAdapter, 其内包含  webrtc::Transport* 的成员变量,该变量指向 WebRtcVideoChannel
									    RtcpRttStats* rtt_stats,
									    PacketRouter* packet_router,
									    const VideoReceiveStream::Config* config,
									    ReceiveStatistics* rtp_receive_statistics,
									    ReceiveStatisticsProxy* receive_stats_proxy,
									    ProcessThread* process_thread,
									    NackSender* nack_sender,
									    KeyFrameRequestSender* keyframe_request_sender,
									    video_coding::OnCompleteFrameCallback* complete_frame_callback,
									    rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor)
									    : clock_(clock),
									      config_(*config),
									      packet_router_(packet_router),
									      process_thread_(process_thread),
									      ntp_estimator_(clock),
									      rtp_header_extensions_(config_.rtp.extensions),
									      rtp_receive_statistics_(rtp_receive_statistics),
									      ulpfec_receiver_(UlpfecReceiver::Create(config->rtp.remote_ssrc,
									                                              this,
									                                              config->rtp.extensions)),
									      receiving_(false),
									      last_packet_log_ms_(-1),
									      rtp_rtcp_(CreateRtpRtcpModule(clock,
									                                    rtp_receive_statistics_,
									                                    transport, // transport 指向的是 webrtc::internal::TransportAdapter, 其内包含  webrtc::Transport* 的成员变量,该变量指向 WebRtcVideoChannel
									                                    rtt_stats,
									                                    receive_stats_proxy,
									                                    config_.rtp.local_ssrc)), // const std::unique_ptr<RtpRtcp> rtp_rtcp_;
									      complete_frame_callback_(complete_frame_callback),
									      keyframe_request_sender_(keyframe_request_sender),
									      // TODO(bugs.webrtc.org/10336): Let |rtcp_feedback_buffer_| communicate
									      // directly with |rtp_rtcp_|.
									      rtcp_feedback_buffer_(this, nack_sender, this),
									      packet_buffer_(clock_,
									                     kPacketBufferStartSize,
									                     PacketBufferMaxSize(),
									                     this),
									      has_received_frame_(false),
									      frames_decryptable_(false) {
									  constexpr bool remb_candidate = true;
									  if (packet_router_)
									    packet_router_->AddReceiveRtpModule(rtp_rtcp_.get(), remb_candidate);
									
									  RTC_DCHECK(config_.rtp.rtcp_mode != RtcpMode::kOff)
									      << "A stream should not be configured with RTCP disabled. This value is "
									         "reserved for internal usage.";
									  // TODO(pbos): What's an appropriate local_ssrc for receive-only streams?
									  RTC_DCHECK(config_.rtp.local_ssrc != 0);
									  RTC_DCHECK(config_.rtp.remote_ssrc != config_.rtp.local_ssrc);
									
									  rtp_rtcp_->SetRTCPStatus(config_.rtp.rtcp_mode);
									  rtp_rtcp_->SetRemoteSSRC(config_.rtp.remote_ssrc);
									
									  static const int kMaxPacketAgeToNack = 450;
									  const int max_reordering_threshold = (config_.rtp.nack.rtp_history_ms > 0)
									                                           ? kMaxPacketAgeToNack
									                                           : kDefaultMaxReorderingThreshold;
									  rtp_receive_statistics_->SetMaxReorderingThreshold(config_.rtp.remote_ssrc,
									                                                     max_reordering_threshold);
									  // TODO(nisse): For historic reasons, we applied the above
									  // max_reordering_threshold also for RTX stats, which makes little sense since
									  // we don't NACK rtx packets. Consider deleting the below block, and rely on
									  // the default threshold.
									  if (config_.rtp.rtx_ssrc) {
									    rtp_receive_statistics_->SetMaxReorderingThreshold(
									        config_.rtp.rtx_ssrc, max_reordering_threshold);
									  }
									  if (config_.rtp.rtcp_xr.receiver_reference_time_report)
									    rtp_rtcp_->SetRtcpXrRrtrStatus(true);
									
									  // Stats callback for CNAME changes.
									  rtp_rtcp_->RegisterRtcpCnameCallback(receive_stats_proxy);
									
									  process_thread_->RegisterModule(rtp_rtcp_.get(), RTC_FROM_HERE);
									
									  if (config_.rtp.lntf.enabled) {
									    loss_notification_controller_ =
									        std::make_unique<LossNotificationController>(&rtcp_feedback_buffer_,
									                                                     &rtcp_feedback_buffer_);
									  }
									
									  if (config_.rtp.nack.rtp_history_ms != 0) {
									    nack_module_ = std::make_unique<NackModule>(clock_, &rtcp_feedback_buffer_,
									                                                &rtcp_feedback_buffer_);
									    process_thread_->RegisterModule(nack_module_.get(), RTC_FROM_HERE);
									  }
									
									  reference_finder_ =
									      std::make_unique<video_coding::RtpFrameReferenceFinder>(this);
									
									  // Only construct the encrypted receiver if frame encryption is enabled.
									  if (config_.crypto_options.sframe.require_frame_encryption) {
									    buffered_frame_decryptor_ =
									        std::make_unique<BufferedFrameDecryptor>(this, this);
									    if (frame_decryptor != nullptr) {
									      buffered_frame_decryptor_->SetFrameDecryptor(std::move(frame_decryptor));
									    }
									  }
									}
									
									
									
									std::unique_ptr<RtpRtcp> CreateRtpRtcpModule(
									    Clock* clock,
									    ReceiveStatistics* receive_statistics,
									    Transport* outgoing_transport, // 注意这里
									    RtcpRttStats* rtt_stats,
									    RtcpPacketTypeCounterObserver* rtcp_packet_type_counter_observer,
									    uint32_t local_ssrc) {
									  RtpRtcp::Configuration configuration;
									  configuration.clock = clock;
									  configuration.audio = false;
									  configuration.receiver_only = true;
									  configuration.receive_statistics = receive_statistics;
									  configuration.outgoing_transport = outgoing_transport; //注意这里
									  configuration.rtt_stats = rtt_stats;
									  configuration.rtcp_packet_type_counter_observer =
									      rtcp_packet_type_counter_observer;
									  configuration.local_media_ssrc = local_ssrc;
									
									  std::unique_ptr<RtpRtcp> rtp_rtcp = RtpRtcp::Create(configuration); // 返回的是指向 ModuleRtpRtcpImpl 的智能指针
									  rtp_rtcp->SetRTCPStatus(RtcpMode::kCompound);
									
									  return rtp_rtcp;
									}
									
									// ModuleRtpRtcpImpl 的派生关系
									class RtpRtcp : public Module, public RtcpFeedbackSenderInterface
									class ModuleRtpRtcpImpl : public RtpRtcp, public RTCPReceiver::ModuleRtpRtcp
									
									std::unique_ptr<RtpRtcp> RtpRtcp::Create(const Configuration& configuration) {
									  RTC_DCHECK(configuration.clock);
									  return std::make_unique<ModuleRtpRtcpImpl>(configuration); //注意这里
									}





bool RtpVideoStreamReceiver::DeliverRtcp(const uint8_t* rtcp_packet,
                                         size_t rtcp_packet_length) {
  RTC_DCHECK_RUN_ON(&worker_task_checker_);

  if (!receiving_) {
    return false;
  }

  rtp_rtcp_->IncomingRtcpPacket(rtcp_packet, rtcp_packet_length); // ModuleRtpRtcpImpl::IncomingRtcpPacket 

  int64_t rtt = 0;
  rtp_rtcp_->RTT(config_.rtp.remote_ssrc, &rtt, nullptr, nullptr, nullptr); // ModuleRtpRtcpImpl::RTT
  if (rtt == 0) {
    // Waiting for valid rtt.
    return true;
  }
  uint32_t ntp_secs = 0;
  uint32_t ntp_frac = 0;
  uint32_t rtp_timestamp = 0;
  uint32_t recieved_ntp_secs = 0;
  uint32_t recieved_ntp_frac = 0;
  // ntp_secs 就是最近一次接收到的SR包中的NTP时间中的单位为秒的部分
  // ntp_frac 就是最近一次接收到的SR包中的NTP时间中的单位为1/2^32秒的部分
  // recieved_ntp_secs 表示接收到这个SR包的时候,本地时间的NTP表示中的单位为秒的部分
  // recieved_ntp_frac 表示接收到这个SR包的时候,本地时间的NTP表示中的单位为1/2^32秒的部分
  // rtp_timestamp 表示最近一次接收到的SR包中的 RTP 时间戳
  if (rtp_rtcp_->RemoteNTP(&ntp_secs, &ntp_frac, &recieved_ntp_secs,
                           &recieved_ntp_frac, &rtp_timestamp) != 0) { // ModuleRtpRtcpImpl::RemoteNTP
    // Waiting for RTCP.
    return true;
  }
  
  // 接收到最近一次SR包的本地时间的NTP表示,由于发送端和接收端没有进行时间同步,所以SR包中的NTP时间和本地的NTP时间很有可能有较大的的偏差
  NtpTime recieved_ntp(recieved_ntp_secs, recieved_ntp_frac);
  // 从最近一次收到SR包到现在所流逝的时间
  int64_t time_since_recieved =
      clock_->CurrentNtpInMilliseconds() - recieved_ntp.ToMs();
  // Don't use old SRs to estimate time.
  if (time_since_recieved <= 1) {
    ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp); // RemoteNtpTimeEstimator::UpdateRtcpTimestamp
  }

  return true;
}

										// 接收 RTCP 包
										void ModuleRtpRtcpImpl::IncomingRtcpPacket(const uint8_t* rtcp_packet,
										                                           const size_t length) {
										  rtcp_receiver_.IncomingPacket(rtcp_packet, length); // RTCPReceiver::IncomingPacket
										}
										
										void RTCPReceiver::IncomingPacket(const uint8_t* packet, size_t packet_size) {
										  if (packet_size == 0) {
										    RTC_LOG(LS_WARNING) << "Incoming empty RTCP packet";
										    return;
										  }
										
										  PacketInformation packet_information;
										  if (!ParseCompoundPacket(packet, packet + packet_size, &packet_information))
										    return;
										  TriggerCallbacksFromRtcpPacket(packet_information);
										}


										// 计算 RTT 
										int32_t ModuleRtpRtcpImpl::RTT(const uint32_t remote_ssrc,
										                               int64_t* rtt,
										                               int64_t* avg_rtt,
										                               int64_t* min_rtt,
										                               int64_t* max_rtt) const {
										  int32_t ret = rtcp_receiver_.RTT(remote_ssrc, rtt, avg_rtt, min_rtt, max_rtt); // RTCPReceiver::RTT
										  if (rtt && *rtt == 0) {
										    // Try to get RTT from RtcpRttStats class.
										    *rtt = rtt_ms();
										  }
										  return ret;
										}
										
										int32_t RTCPReceiver::RTT(uint32_t remote_ssrc,
										                          int64_t* last_rtt_ms,
										                          int64_t* avg_rtt_ms,
										                          int64_t* min_rtt_ms,
										                          int64_t* max_rtt_ms) const {
										  rtc::CritScope lock(&rtcp_receiver_lock_);
										
										  auto it = received_report_blocks_.find(main_ssrc_);
										  if (it == received_report_blocks_.end())
										    return -1;
										
										  auto it_info = it->second.find(remote_ssrc);
										  if (it_info == it->second.end())
										    return -1;
										
										  const ReportBlockData* report_block_data = &it_info->second;
										
										  if (report_block_data->num_rtts() == 0)
										    return -1;
										
										  if (last_rtt_ms)
										    *last_rtt_ms = report_block_data->last_rtt_ms();
										
										  if (avg_rtt_ms) {
										    *avg_rtt_ms =
										        report_block_data->sum_rtt_ms() / report_block_data->num_rtts();
										  }
										
										  if (min_rtt_ms)
										    *min_rtt_ms = report_block_data->min_rtt_ms();
										
										  if (max_rtt_ms)
										    *max_rtt_ms = report_block_data->max_rtt_ms();
										
										  return 0;
										}



										// ntp 相关计算
										int32_t ModuleRtpRtcpImpl::RemoteNTP(uint32_t* received_ntpsecs,
										                                     uint32_t* received_ntpfrac,
										                                     uint32_t* rtcp_arrival_time_secs,
										                                     uint32_t* rtcp_arrival_time_frac,
										                                     uint32_t* rtcp_timestamp) const {
										  return rtcp_receiver_.NTP(received_ntpsecs, received_ntpfrac,  // RTCPReceiver::NTP
										                            rtcp_arrival_time_secs, rtcp_arrival_time_frac,
										                            rtcp_timestamp)
										             ? 0
										             : -1;
										}
										
										bool RTCPReceiver::NTP(uint32_t* received_ntp_secs,
										                       uint32_t* received_ntp_frac,
										                       uint32_t* rtcp_arrival_time_secs,
										                       uint32_t* rtcp_arrival_time_frac,
										                       uint32_t* rtcp_timestamp) const {
										  rtc::CritScope lock(&rtcp_receiver_lock_);
										  if (!last_received_sr_ntp_.Valid())
										    return false;
										
										  // NTP from incoming SenderReport.
										  if (received_ntp_secs)
										    *received_ntp_secs = remote_sender_ntp_time_.seconds();
										  if (received_ntp_frac)
										    *received_ntp_frac = remote_sender_ntp_time_.fractions();
										
										  // Rtp time from incoming SenderReport.
										  if (rtcp_timestamp)
										    *rtcp_timestamp = remote_sender_rtp_time_;
										
										  // Local NTP time when we received a RTCP packet with a send block.
										  if (rtcp_arrival_time_secs)
										    *rtcp_arrival_time_secs = last_received_sr_ntp_.seconds();
										  if (rtcp_arrival_time_frac)
										    *rtcp_arrival_time_frac = last_received_sr_ntp_.fractions();
										
										  return true;
										}





// rtt 是在发送端计算出来的RTT
// ntp_secs 就是最近一次接收到的SR包中的NTP时间中的单位为秒的部分
// ntp_frac 就是最近一次接收到的SR包中的NTP时间中的单位为1/2^32秒的部分
// rtp_timestamp 表示最近一次接收到的SR包中的 RTP 时间戳
bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(int64_t rtt,
                                                 uint32_t ntp_secs,
                                                 uint32_t ntp_frac,
                                                 uint32_t rtcp_timestamp) {
  bool new_rtcp_sr = false;
  if (!rtp_to_ntp_.UpdateMeasurements(ntp_secs, ntp_frac, rtcp_timestamp, // RtpToNtpEstimator::UpdateMeasurements
                                      &new_rtcp_sr)) {
    return false;
  }
  if (!new_rtcp_sr) {
    // No new RTCP SR since last time this function was called.
    return true;
  }

  // Update extrapolator with the new arrival time.
  // The extrapolator assumes the TimeInMilliseconds time.
  int64_t receiver_arrival_time_ms = clock_->TimeInMilliseconds();
  int64_t sender_send_time_ms = Clock::NtpToMs(ntp_secs, ntp_frac);
  int64_t sender_arrival_time_ms = sender_send_time_ms + rtt / 2;
  int64_t remote_to_local_clocks_offset =
      receiver_arrival_time_ms - sender_arrival_time_ms;
  ntp_clocks_offset_estimator_.Insert(remote_to_local_clocks_offset);
  return true;
}



// ntp_secs 就是最近一次接收到的SR包中的NTP时间中的单位为秒的部分
// ntp_frac 就是最近一次接收到的SR包中的NTP时间中的单位为1/2^32秒的部分
// rtp_timestamp 表示最近一次接收到的SR包中的 RTP 
// 注意:发送SR包的肯定是发送者,但如果SR包中也有report block 就证明这个发送者也是接收者,
// 与这个接收者对应的发送者端就可以根据SR包中的report block计算RTT
bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
                                           uint32_t ntp_frac,
                                           uint32_t rtp_timestamp,
                                           bool* new_rtcp_sr) {
  *new_rtcp_sr = false;

  int64_t unwrapped_rtp_timestamp = unwrapper_.Unwrap(rtp_timestamp);
	// 构建一个度量信息,分析 unwrapper_ 的代码,可以知道 unwrapped_rtp_timestamp 就是不回绕的时间戳。
	// 举个例子,回绕的时间戳是:(2^32 - 3600) -> 0 -> 3600,不回绕的时间戳就是:(2^32 - 3600) -> 2^32 -> (2^32 + 3600)
  RtcpMeasurement new_measurement(ntp_secs, ntp_frac, unwrapped_rtp_timestamp);

  if (Contains(measurements_, new_measurement)) {
    // RTCP SR report already added.
    return true;
  }

  if (!new_measurement.ntp_time.Valid())
    return false;

  int64_t ntp_ms_new = new_measurement.ntp_time.ToMs();
  bool invalid_sample = false;
  if (!measurements_.empty()) {
    int64_t old_rtp_timestamp = measurements_.front().unwrapped_rtp_timestamp;
    int64_t old_ntp_ms = measurements_.front().ntp_time.ToMs();
    if (ntp_ms_new <= old_ntp_ms ||
        ntp_ms_new > old_ntp_ms + kMaxAllowedRtcpNtpIntervalMs) {
      invalid_sample = true;
    } else if (unwrapped_rtp_timestamp <= old_rtp_timestamp) {
      RTC_LOG(LS_WARNING)
          << "Newer RTCP SR report with older RTP timestamp, dropping";
      invalid_sample = true;
    } else if (unwrapped_rtp_timestamp - old_rtp_timestamp > (1 << 25)) {
      // Sanity check. No jumps too far into the future in rtp.
      invalid_sample = true;
    }
  }

  if (invalid_sample) {
    ++consecutive_invalid_samples_;
    if (consecutive_invalid_samples_ < kMaxInvalidSamples) {
      return false;
    }
    RTC_LOG(LS_WARNING) << "Multiple consecutively invalid RTCP SR reports, "
                           "clearing measurements.";
    measurements_.clear();
    params_ = absl::nullopt;
  }
  consecutive_invalid_samples_ = 0;

  // Insert new RTCP SR report.
  if (measurements_.size() == kNumRtcpReportsToUse)
    measurements_.pop_back();
	// 将度量信息进行保存
  measurements_.push_front(new_measurement);
  *new_rtcp_sr = true;

  // List updated, calculate new parameters.
  UpdateParameters(); // 
  return true;
}


void RtpToNtpEstimator::UpdateParameters() {
  if (measurements_.size() < 2)
    return;

  std::vector<double> x;
  std::vector<double> y;
  x.reserve(measurements_.size());
  y.reserve(measurements_.size());
  for (auto it = measurements_.begin(); it != measurements_.end(); ++it) {
    x.push_back(it->unwrapped_rtp_timestamp);
    y.push_back(it->ntp_time.ToMs());
  }
  double slope, offset;
	// 线性回归
  if (!LinearRegression(x, y, &slope, &offset)) {
    return;
  }
	// 保存斜率和截距
  params_.emplace(1 / slope, offset);
}

3 相关类图

主要看上图的右上角。

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值