当前位置: 首页 > news >正文

【WebRTC-13】是在哪,什么时候,创建编解码器?

Android-RTC系列软重启,改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性,方便形成肌肉记忆。同时不分种类、不分难易程度,在线征集问题切入点。

问题:编解码器的关键实体类是什么?在哪里&什么时候创建的?

这个问题是在分析webrtc如何增加第三方外置的编解码库时 额外提出来的,在找答案的过程中领略webrtc内部代码结构组织的划分。废话不多,这个问题的关键可以想到之前的一个问题  webrtc是如何确定双端的编解码类型?  是在sdp交换信息后,local和remote的description两者结合确认。那么可以在这基础上继续寻找,也就是去SdpOfferAnswerHandler找答案。

我们直接定位到 SdpOfferAnswerHandler:: ApplyLocalDescription / ApplyRemoteDescription。

RTCError SdpOfferAnswerHandler::ApplyLocalDescription(std::unique_ptr<SessionDescriptionInterface> desc,const std::map<std::string, const cricket::ContentGroup*>&  bundle_groups_by_mid) 
{pc_->ClearStatsCache();RTCError error = PushdownTransportDescription(cricket::CS_LOCAL, type);if (IsUnifiedPlan()) {UpdateTransceiversAndDataChannels(...)} else {... ...}UpdateSessionState(type, cricket::CS_LOCAL,local_description()->description(),bundle_groups_by_mid);// Now that we have a local description, we can push down remote candidates.UseCandidatesInRemoteDescription();... ...
}

大致的逻辑如上,这里关注 UpdateSessionState,继续深入。

RTCError SdpOfferAnswerHandler::UpdateSessionState(SdpType type,  cricket::ContentSource source,const cricket::SessionDescription* description,const std::map<std::string, const cricket::ContentGroup*>&  bundle_groups_by_mid) {// If this is answer-ish we're ready to let media flow.if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {EnableSending();}// Update the signaling state according to the specified state machine (see// https://w3c.github.io/webrtc-pc/#rtcsignalingstate-enum).if (type == SdpType::kOffer) {ChangeSignalingState(source == cricket::CS_LOCAL? PeerConnectionInterface::kHaveLocalOffer: PeerConnectionInterface::kHaveRemoteOffer);} else if (type == SdpType::kPrAnswer) {ChangeSignalingState(source == cricket::CS_LOCAL? PeerConnectionInterface::kHaveLocalPrAnswer: PeerConnectionInterface::kHaveRemotePrAnswer);} else {RTC_DCHECK(type == SdpType::kAnswer);ChangeSignalingState(PeerConnectionInterface::kStable);if (ConfiguredForMedia()) {transceivers()->DiscardStableStates();}}// Update internal objects according to the session description's media descriptions.return PushdownMediaDescription(type, source, bundle_groups_by_mid);
}

根据输入的type改变信令状态。注意最后的 PushdownMediaDescription,这里看函数名字有点奇怪,其核心功能是检索新的sdp信息,更新 rtp_transceivers 的channel

RTCError SdpOfferAnswerHandler::PushdownMediaDescription(SdpType type,  cricket::ContentSource source,const std::map<std::string, const cricket::ContentGroup*>&  bundle_groups_by_mid) 
{const SessionDescriptionInterface* sdesc =(source == cricket::CS_LOCAL ? local_description() : remote_description());// Push down the new SDP media section for each audio/video transceiver.auto rtp_transceivers = transceivers()->ListInternal();std::vector<std::pair<cricket::ChannelInterface*, const MediaContentDescription*>> channels;for (const auto& transceiver : rtp_transceivers) {const ContentInfo* content_info =FindMediaSectionForTransceiver(transceiver, sdesc);cricket::ChannelInterface* channel = transceiver->channel();const MediaContentDescription* content_desc = content_info->media_description();channels.push_back(std::make_pair(channel, content_desc));}for (const auto& entry : channels) {std::string error;bool success = context_->worker_thread()->BlockingCall([&]() {return (source == cricket::CS_LOCAL)? entry.first->SetLocalContent(entry.second, type, error): entry.first->SetRemoteContent(entry.second, type, error);});}return RTCError::OK();
}

(这里的channel以后介绍)伪代码如上。可以看到关于ChannelInterface的关键方法 SetLocalContent 、SetRemoteContent。

文件位置  src/pc/channel.cc
bool BaseChannel::SetLocalContent(const MediaContentDescription* content,SdpType type, std::string& error_desc) {RTC_DCHECK_RUN_ON(worker_thread());TRACE_EVENT0("webrtc", "BaseChannel::SetLocalContent");return SetLocalContent_w(content, type, error_desc);
}
bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,SdpType type, std::string& error_desc) {RTC_DCHECK_RUN_ON(worker_thread());TRACE_EVENT0("webrtc", "BaseChannel::SetRemoteContent");return SetRemoteContent_w(content, type, error_desc);
}

SetLocalContent_w / SetRemoteContent_w又到具体的媒体通道类VoiceChannel / VideoChannel实现,以VideoChannel为例,精简核心代码如下。

bool VideoChannel::SetLocalContent_w(const MediaContentDescription* content,SdpType type, std::string& error_desc) 
{RtpHeaderExtensions header_extensions =GetDeduplicatedRtpHeaderExtensions(content->rtp_header_extensions());media_send_channel()->SetExtmapAllowMixed(content->extmap_allow_mixed());VideoReceiverParameters recv_params = last_recv_params_;VideoSenderParameters send_params = last_send_params_;MediaChannelParametersFromMediaDescription(content, header_extensions,webrtc::RtpTransceiverDirectionHasRecv(content->direction()),&recv_params);media_receive_channel()->SetReceiverParameters(recv_params);media_send_channel()->SetSenderParameters(send_params);UpdateLocalStreams_w(content->streams(), type, error_desc)UpdateMediaSendRecvState_w();
}

这里的UpdateLocalStreams_w又回到了BaseChannel。这里有一大段注释比较关键,主要描述了:在媒体协商的过程中SSRC与StreamParams相关联,构成安全的 local_stream_成员对象。

bool BaseChannel::UpdateLocalStreams_w(const std::vector<StreamParams>& streams,SdpType type, std::string& error_desc) 
{// In the case of RIDs (where SSRCs are not negotiated), this method will// generate an SSRC for each layer in StreamParams. That representation will// be stored internally in `local_streams_`.// In subsequent offers, the same stream can appear in `streams` again// (without the SSRCs), so it should be looked up using RIDs (if available)// and then by primary SSRC.// In both scenarios, it is safe to assume that the media channel will be// created with a StreamParams object with SSRCs. However, it is not safe to// assume that `local_streams_` will always have SSRCs as there are scenarios// in which niether SSRCs or RIDs are negotiated.... ...media_send_channel()->AddSendStream(new_stream);
}

到这里我们先停顿一下,因为发现这里出现了众多 Channel 对象,ChannelInterface、BaseChannel、VoiceChannel/VideoChannel、media_send_channel/media_receive_channel。它们究竟是什么关系?我绘制了一张简易的UML图,这张图概括了Channel 以及之后要介绍的 Stream的内部关系,提取了核心代码的常见方法。大家一定要放大仔细看看!

小结:这部分阐述了 webrtc如何从sdp提取信息,根据ssrc创建并绑定网络传输通器中的 Channel。并通过代码,由BaseChannel->Video/VoiceChannel承接rtp数据包。


有了上面的UML图预热,在进入 media_send_channel()->AddSendStream的流程之前,我们要搞清楚这个 media_send_channel是怎么来的。

回到文章最开始的 SdpOfferAnswerHandler::ApplyLocalDescription 的UpdateTransceiversAndDataChannels。关键代码逻辑如下,可以看到涉及ChannelInterface的通道,是由RtpTransceiver内部创建的。

RTCError SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels(cricket::ContentSource source,const SessionDescriptionInterface& new_session,const SessionDescriptionInterface* old_local_description,const SessionDescriptionInterface* old_remote_description,const std::map<std::string, const cricket::ContentGroup*>& bundle_groups_by_mid) 
{const ContentInfos& new_contents = new_session.description()->contents();for (size_t i = 0; i < new_contents.size(); ++i) {const cricket::ContentInfo& new_content = new_contents[i];auto transceiver_or_error =AssociateTransceiver(source, new_session.GetType(), i, new_content,old_local_content, old_remote_content);auto transceiver = transceiver_or_error.Value();RTCError error= UpdateTransceiverChannel(transceiver, new_content, bundle_group);}
}RTCError SdpOfferAnswerHandler::UpdateTransceiverChannel(rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> transceiver,const cricket::ContentInfo& content,const cricket::ContentGroup* bundle_group) 
{cricket::ChannelInterface* channel = transceiver->internal()->channel();if (!channel) {auto error = transceiver->internal()->CreateChannel(...);}
}

RtpTransceiver的CreateChannel内部,其核心是调用media_engine去创建对应的SendChannel / ReceiveChannel,最终组成RtpTransceiver的 VideoChannel / VoiceChannel。

RTCError RtpTransceiver::CreateChannel(absl::string_view mid,Call* call_ptr,const cricket::MediaConfig& media_config,bool srtp_required,CryptoOptions crypto_options,const cricket::AudioOptions& audio_options,const cricket::VideoOptions& video_options,VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,std::function<RtpTransportInternal*(absl::string_view)> transport_lookup) 
{std::unique_ptr<cricket::ChannelInterface> new_channel;if (media_type() == cricket::MEDIA_TYPE_VIDEO) {std::unique_ptr<cricket::VideoMediaSendChannelInterface>media_send_channel = media_engine()->video().CreateSendChannel(...);std::unique_ptr<cricket::VideoMediaReceiveChannelInterface>media_receive_channel = media_engine()->video().CreateReceiveChannel(...);new_channel = std::make_unique<cricket::VideoChannel>(worker_thread(), network_thread(), signaling_thread(), std::move(media_send_channel), std::move(media_receive_channel), ...);} else {// media_type() == cricket::MEDIA_TYPE_AUDIO}SetChannel(std::move(new_channel), transport_lookup);return RTCError::OK();
}

根据以往的文章,我们就可以快速定位到 src/media/engine/webrtc_video_engine / webrtc_audio_engine,找到SendChannel ReceiveChannel。至此,我们正式定位到了media_send_channel()的具体实现。

// 以media_type==video为例
std::unique_ptr<VideoMediaSendChannelInterface>
WebRtcVideoEngine::CreateSendChannel(webrtc::Call* call,const MediaConfig& config,const VideoOptions& options,const webrtc::CryptoOptions& crypto_options,webrtc::VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) {return std::make_unique<WebRtcVideoSendChannel>(call, config, options, crypto_options, encoder_factory_.get(),decoder_factory_.get(), video_bitrate_allocator_factory);
}
std::unique_ptr<VideoMediaReceiveChannelInterface>
WebRtcVideoEngine::CreateReceiveChannel(webrtc::Call* call,const MediaConfig& config,const VideoOptions& options,const webrtc::CryptoOptions& crypto_options) {return std::make_unique<WebRtcVideoReceiveChannel>(call, config, options, crypto_options, decoder_factory_.get());
}
小结:根据m=session创建RtpTransceiver,Video/VoiceChannel由webrtc_medie_engine创建,并保存在RtpTransceiver网络传器者的成员变量。Video/VoiceChannel 内包含SendChannel和ReceiveChannel。


回头再看media_send_channel()->AddSendStream(new_stream),即WebRtcVideoSendChannel::AddSendStream,其核心逻辑很简单:

bool WebRtcVideoSendChannel::AddSendStream(const StreamParams& sp) 
{WebRtcVideoSendStream* stream = new WebRtcVideoSendStream(call_, sp, std::move(config), default_send_options_,video_config_.enable_cpu_adaptation, bitrate_config_.max_bitrate_bps,send_codec(), send_rtp_extensions_, send_params_);uint32_t ssrc = sp.first_ssrc();send_streams_[ssrc] = stream;
}

WebRtcVideoSendStream 的构造函数内容比较多,但都是属性赋值。我们这里只关心文章提出的问题,也就是构造函数里唯一调用的成员函数SetCodec。

// src/media/engine/webrtc_video_engine.cc
void WebRtcVideoSendChannel::WebRtcVideoSendStream::SetCodec(const VideoCodecSettings& codec_settings) 
{parameters_.encoder_config = CreateVideoEncoderConfig(codec_settings.codec);parameters_.config.rtp = ...parameters_.codec_settings = codec_settings;// TODO(bugs.webrtc.org/8830): Avoid recreation, it should be enough to call  ReconfigureEncoder.RTC_LOG(LS_INFO) << "RecreateWebRtcStream (send) because of SetCodec.";RecreateWebRtcStream();
}void WebRtcVideoSendChannel::WebRtcVideoSendStream::RecreateWebRtcStream() 
{if (stream_ != NULL) {call_->DestroyVideoSendStream(stream_);}stream_ = call_->CreateVideoSendStream(std::move(config),parameters_.encoder_config.Copy());// Attach the source after starting the send stream to prevent frames from// being injected into a not-yet initializated video stream encoder.// rtc::VideoSourceInterface<webrtc::VideoFrame>* source_if (source_) {stream_->SetSource(source_, GetDegradationPreference());}
}

具体实现还要到Call::CreateVideoSendStream。这里有个细节,stream_->SetSource(webrtc::VideoFrame )出现了VideoFrame,显然路是找对了,继续往下。

//  src/call/call.cc
webrtc::VideoSendStream* Call::CreateVideoSendStream(webrtc::VideoSendStream::Config config,VideoEncoderConfig encoder_config,std::unique_ptr<FecController> fec_controller) 
{VideoSendStreamImpl* send_stream = new VideoSendStreamImpl(...);for (uint32_t ssrc : ssrcs) {RTC_DCHECK(video_send_ssrcs_.find(ssrc) == video_send_ssrcs_.end());video_send_ssrcs_[ssrc] = send_stream;}video_send_streams_.insert(send_stream);video_send_streams_empty_.store(false, std::memory_order_relaxed);
}// src/video/video_send_stream_impl.cc
VideoSendStreamImpl::VideoSendStreamImpl(RtcEventLog* event_log,VideoSendStream::Config config,VideoEncoderConfig encoder_config,std::unique_ptr<FecController> fec_controller,const FieldTrialsView& field_trials,std::unique_ptr<VideoStreamEncoderInterface> video_stream_encoder_for_test)
//构造赋值
: video_stream_encoder_(video_stream_encoder_for_test? std::move(video_stream_encoder_for_test): CreateVideoStreamEncoder(...) ), ... ...
//构造VideoStreamEncoder
std::unique_ptr<VideoStreamEncoderInterface> CreateVideoStreamEncoder() {std::unique_ptr<TaskQueueBase, TaskQueueDeleter> encoder_queue =task_queue_factory->CreateTaskQueue("EncoderQueue",TaskQueueFactory::Priority::NORMAL);TaskQueueBase* encoder_queue_ptr = encoder_queue.get();return std::make_unique<VideoStreamEncoder>(...std::move(encoder_queue), ...);
}

到这里我们就找到了webrtc的视频编码实体类VideoStreamEncoder(src/video/video_stream_encoder.cc),相对应的解码实体类VideoStreamDecoder。这里放一些关键方法,此类是个宝库,任何关于视频编码功能的细节,都可以在这找到参考。


总结答案:整篇文章跟踪的代码逻辑如下,归纳了从Sdp->RtpTransceiver->VideoChannel/VoiceChannel->Send&ReceiveChannel-> 然后根据sdp::ssrc创建VideoSendStream-> VideoStreamEncoder。

还待挖掘的细节非常多,奈何篇幅有限有所侧重。在写本文章的时候,其实是在研究icecandidate,stun服务端已经搭建起来了。希望有兴趣的同学联系我,一起深入成长。

SdpOfferAnswerHandler:: ApplyLocalDescription / ApplyRemoteDescription(sdp信息)
SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels -> UpdateTransceiverChannel(创建RtpTransceiver->Video/VoiceChannel)
SdpOfferAnswerHandler::UpdateSessionState
SdpOfferAnswerHandler::PushdownMediaDescription
BaseChannel::SetLocalContent(const MediaContentDescription* content, ..)
VoiceChannel/VideoChannel::SetLocalContent_w
BaseChannel::UpdateLocalStreams_w(const std::vector<StreamParams>& streams, ..)WebRtcVideoSendChannel::AddSendStream
WebRtcVideoSendChannel::WebRtcVideoSendStream::WebRtcVideoSendStream(Constructor)
WebRtcVideoSendChannel::WebRtcVideoSendStream::SetCodec|::RecreateWebRtcStream|::SetSenderParameters|::ReconfigureEncoder
Call::CreateVideoSendStream
VideoSendStreamImpl() -> VideoStreamEncoder(Interface)

相关文章:

【WebRTC-13】是在哪,什么时候,创建编解码器?

Android-RTC系列软重启&#xff0c;改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性&#xff0c;方便形成肌肉记忆。同时不分种类、不分难易程度&#xff0c;在线征集问题切入点。 问题&#xff1a;编解码器的关键实体类是什么&#xff1f;在哪里&什么时候…...

Kuikly 安装环境篇

1、安装版本号为2024.1.1 的Android studio&#xff08;如使用高版本的Android studio需要更改JDK版本号为17&#xff09; 2、JDK版本使用17&#xff08;如需要修改JDK&#xff1a;Android Studio -> Settings -> Build,Execution,Deployment -> Build Tools -> Gr…...

npm create vite@latest my-vue-app 解读

背景发荧光的样式。 filter属性的学习&#xff1a;filter - CSS&#xff1a;层叠样式表 | MDN 复习一下em 组件的调用: 是msg让“ViteVue”显示出来的&#xff01;&#xff01; a标签的targte属性&#xff1a; 组件之间怎么传值的&#xff1a; &#xff0c;没看懂code标签怎么…...

【本地搭建npm私服】使用Verdaccio

使用Verdaccio搭建本地NPM私服及私有包管理指南 一、Verdaccio安装与基础配置 1. 安装Verdaccio # 全局安装Verdaccio npm install -g verdaccio# 检查版本 verdaccio --version2. 启动服务 verdaccio启动后默认监听4873端口&#xff0c;访问 http://localhost:4873 3. 配…...

Chroma:一个开源的8.9B文生图模型

Chroma 模型讲解 一、模型概述 Chroma 是一个基于 FLUX.1-schnell 的 8.9B 参数模型。它采用了 Apache 2.0 许可证&#xff0c;完全开源&#xff0c;允许任何人使用、修改和在其基础上进行开发&#xff0c;不存在企业限制。该模型目前正在训练中&#xff0c;训练数据集从 20M…...

量子通信技术及其在信息安全中的应用:开启无条件安全通信的新时代

前言 在数字化时代&#xff0c;信息安全是全球关注的焦点。随着传统加密技术面临量子计算等新兴技术的挑战&#xff0c;量子通信作为一种基于量子力学原理的新型通信技术&#xff0c;因其无条件安全的特性而备受关注。量子通信不仅能够有效抵御量子计算的威胁&#xff0c;还能为…...

【杂谈】Godot 2D游戏窗口设置

如切如磋&#xff0c;如琢如磨。 目录 一、引言二、设置&#xff08;一&#xff09;基本尺寸&#xff08;二&#xff09;拉伸&#xff08;三&#xff09;手持设备朝向&#xff08;四&#xff09;​​窗口模式​​ 一、引言 在开发2D游戏时&#xff0c;​​窗口尺寸的设定是游戏…...

MySQL 8.0 OCP认证考试题库持续更新

MySQL是属于甲骨文Oracle公司的一个世界知名的免费数据库产品&#xff0c;使用的范围广、企业多、人员也多&#xff0c;所以对MySQL认证关注的人也不少&#xff0c;MySQL的证书与Oracle的证书使用的是同一个模板&#xff0c;只是在内部的介绍上稍有不同&#xff0c;MySQL认证考…...

C++GO语言微服务基础技术②

目录 01 protobuf语法回顾 02 protobuf的编译、和其他序列化比较 03 查看protoc编译文件对比自定义封装 04 grpc安装简介 05 grpc服务远程调用作业布置 06 作业-grpc-server端 07 作业-grpc-client端 01 protobuf语法回顾 ## 编译 protobuf> 回顾&#xff1a;C 编译 …...

【使用switch结构输出季节】2021-11-23

缘由用switch语句设计程序一年有12个月-编程语言-CSDN问答 void 使用switch结构输出季节(int y) {//缘由https://ask.csdn.net/questions/7577096?spm1005.2025.3001.5141std::cout << y << "\t";switch (y){case 3: case 4: case 5:std::cout <<…...

【Bootstrap V4系列】学习入门教程之 组件-下拉菜单(Dropdowns)

Bootstrap V4系列 学习入门教程之 组件-下拉菜单&#xff08;Dropdowns&#xff09; 下拉菜单&#xff08;Dropdowns&#xff09;一、Overview 概述二、Accessibility 可访问性三、Examples3.1 Single button 单按钮3.2 Split button 分割按钮 四、Sizing 尺寸 下拉菜单&#x…...

基础编程题目集 6-8 简单阶乘计算

本题要求实现一个计算非负整数阶乘的简单函数。 函数接口定义&#xff1a; int Factorial( const int N ); 其中N是用户传入的参数&#xff0c;其值不超过12。如果N是非负整数&#xff0c;则该函数必须返回N的阶乘&#xff0c;否则返回0。 裁判测试程序样例&#xff1a; #in…...

解决word里插入公式后打不开的问题

小铃铛最近在写毕业论文了&#xff0c;需要在文档里插入公式&#xff0c;然鹅一插入就卡死了&#xff0c;直接关闭文档后就再也打不开了&#xff0c;报错什么确定磁盘有空间&#xff08;&#xff1f;总之就是文档损坏的意思&#xff09; 这个时候不要慌&#xff0c;先把word后缀…...

android studio开发:设置屏幕朝向为竖屏,强制应用的包体始终以竖屏(纵向)展示

在 Android 中&#xff0c;要强制应用的包体始终以竖屏&#xff08;纵向&#xff09;展示&#xff0c;可以通过以下几种方式来实现&#xff1a; ✅ 方式一&#xff1a; 在 AndroidManifest.xml 中设置 Activity 的方向&#xff0c;这是最常用的方法。对所有需要强制竖屏的 Ac…...

中国自动驾驶研发解决方案,第一!

4月28日&#xff0c;IDC《中国汽车云市场(2024下半年)跟踪》报告发布&#xff0c;2024下半年中国汽车云市场整体规模达到65.1亿元人民币&#xff0c;同比增长27.4%。IDC认为&#xff0c;自动驾驶技术深化与生成式AI的发展将为汽车云打开新的成长天花板&#xff0c;推动云计算在…...

OpenCv实战笔记(3)基于opencv实现调用摄像头并实时显示画面

一、实现效果 二、实现原理 使用 OpenCV 打开摄像头&#xff0c;持续捕获视频帧&#xff0c;并在一个窗口中实时显示这些帧&#xff0c;直到用户按下 ESC 键退出。整体流程&#xff1a;打开摄像头&#xff08;cv::VideoCapture&#xff09;>创建图像显示窗口&#xff08;cv…...

SpringBoot3 + Druid + DynamicDataSource + PgSQL 连接池优化方案

问题描述 使用 SpringBoot Druid DynamicDataSource PgSQL 时遇到的连接过多未释放问题&#xff0c;一方面是升级DB Core对DB做Tuning&#xff0c;另一方面是优化Druid数据库连接池方案。 关键优化方向 连接泄漏检测与回收 连接有效性验证 合理的连接池大小配置 闲置连接…...

C++ 深入解析 数据结构中的 AVL树的插入 涉及的旋转规则

欢迎来到干货小仓库 "普通程序员Google 超级程序员" 1、AVL 树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。 例如在这种情况下&…...

c++中的引用

1&#xff0c;引用的基本使用&#xff1a; 作用&#xff1a;给变量起别名 语法&#xff1a;数据类型 &别名 原名 #include<iostream> using namespace std; int main() {int a10;int &ba;cout<<"a "<<a<<endl;cout<<&quo…...

零拷贝的简单复习

PageCache PageCache是内核缓冲区 DMA 没有DMA前的IO&#xff1a;整个数据的传输过程&#xff0c;都需要 CPU 亲自参与搬运数据的过程&#xff0c;而且这个过程中CPU 是不能做其他事情的 CPU发起IO 磁盘将数据放到磁盘缓冲区 CPU将磁盘缓冲区数据放到内核缓冲区 CPU将内核缓…...

CCF编程能力等级认证 一级 第一次课

介绍 CCF 编程能力等级认证&#xff08;GESP&#xff09;为青少年计算机和编程学习者提供学业能力验证的规则和平台&#xff0c;由中国计算机学会发起并主办。 每年考试分四次&#xff0c;时间是每年的3月、6月、9月、12月&#xff0c;以当年每期公布的时间为准。 GESP适用年…...

芯片测试之Open-Short Test全解析:从原理到实战

大家好&#xff0c;我是硅言。最近在开发NORD的AC、DC测试程序&#xff0c;准备和大家聊聊NOR Flash一套完整的AC、DC测试&#xff0c;要测哪些参数&#xff0c;如何测试等等。​​OS测试&#xff08;Open-Short Test&#xff0c;开短路测试&#xff09;​​作为芯片测试"…...

文件包含 任意文件读取

文件处理漏洞--文件包含 - wizard骑士 - 博客园 1&#xff0c;什么是文件包含 程序开发人员一般会吧重复使用的函数写道单个文件中&#xff0c;需要使用某个函数时直接调用此文件&#xff0c;无需再次编写&#xff0c;文件调用的过程就是文件包含&#xff0c;所以将包含的文件…...

string[字符串中第一个的唯一字符][蓝桥杯]

使用哈希表解决 class Solution { public:int firstUniqChar(string s) {int arr[26];for(int i0;i<s.size();i){arr[s[i]-a];}for(int i0;i<s.size();i){if(arr[s[i]-a]1)return i;}return -1;} };...

sql server限制用户只能访问特定表

一个老系统的sqlserver 数据库需要新建一个用户&#xff0c;并限制这个新用户只能访问指定的几个数据表。 1.夺权 创建用户简单&#xff0c;但是登录用户没有管理权&#xff0c;windows管理员登录用户也没有管理权限&#xff0c;这就需要夺权&#xff0c;在单用户模式下&…...

中级网络工程师知识点2

1.netstat -r:显示路由表信息 netstat -a:显示所有活动的TCP连接数以及计算机正在监听的TCP和UDP端口 netstat -e:显示以太网统计信息 netstat -n:以数字形式显示网络地址和端口号 2.display multicast forwarding-table 命令的作用是查看组播转发表信息。 display multic…...

SQL常用操作大全:复制表、跨库查询、删除重复数据

大家好&#xff0c;欢迎来到程序视点&#xff01;我是你们的老朋友.小二&#xff01; SQL常用操作精华总结 表结构与数据操作 复制表结构&#xff1a; SELECT * INTO b FROM a WHERE 1<>1 (SQL Server专用) SELECT TOP 0 * INTO b FROM a (更通用) 拷贝表数据&#…...

辰鳗科技朱越洋:紧扣时代契机,全力投身能源转型战略赛道

国家能源局于4月28日出台的《关于促进能源领域民营经济发展若干举措的通知》&#xff08;以下简称《通知》&#xff09;&#xff0c;是继2月民营企业座谈会后深化能源领域市场化改革的关键政策&#xff0c;标志着民营经济在“双碳”目标引领下正式进入能源转型的核心赛道。 自…...

java 破解aspose.words 18.6 使用

资源包&#xff1a;https://download.csdn.net/download/qq_36598111/90787167 jar包是破解过的&#xff0c;直接可以使用。 引入jar&#xff0c;要引入本地的&#xff0c;不要直接引入仓库的 <dependency><groupId>com.aspose</groupId><artifactId>…...

基于鸢尾花(Iris)数据集的分类模型

本文适合初学者入门&#xff0c;涵盖了数据加载、预处理、建模、训练和评估的全过程。 &#x1f3af; 项目目标 使用机器学习算法&#xff08;如K近邻KNN&#xff09;对鸢尾花的品种进行分类。 &#x1f9f0; 所需工具 Python 3.x 安装的库&#xff1a; pip install scikit…...

电容知识小结

1.同样是电容&#xff0c;1uf的陶瓷电容和1uf的铝电解电容是不一样的&#xff1b; 2.实际的电容等效为ESR C ESL;ESR等效电阻和ESL等效电感&#xff1b; 3.铝电解电容&#xff0c;瓷片电容和钽电容。 4.电容是容纳和释放电荷的电子器件&#xff1b; 5.电容的工作&#xff1a;…...

基于springboot+vue的校园部门资料管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系统展示 系统登录 学生组…...

LeetCode 热题 100 131. 分割回文串

LeetCode 热题 100 | 131. 分割回文串 大家好&#xff0c;今天我们来解决一道经典的回溯算法问题——分割回文串。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求将一个字符串 s 分割成若干个子串&#xff0c;使得每个子串都是回文串&#xff0c;并返回所有可能的分割…...

PostgreSQL 的 pg_start_backup 函数

PostgreSQL 的 pg_start_backup 函数 pg_start_backup 是 PostgreSQL 中用于执行物理备份的关键函数&#xff0c;它通过创建备份模式为数据库文件的一致性备份做准备。以下是该函数的全面解析&#xff1a; 一、函数基础 1. 函数语法 pg_start_backup(label text [, fast bo…...

理解 WKWebView 的 handlesURLScheme: 方法:判断 URL 协议是否由 WebView 默认处理

Overview 在 iOS 开发中&#xff0c;WKWebView 是一个强大的控件&#xff0c;用于在应用内嵌入网页内容。随着 iOS 11.0 的推出&#xff0c;WKWebView 支持自定义 URL 协议处理&#xff0c;这使得开发者可以更灵活地控制网页内的链接请求。今天&#xff0c;我们将详细介绍 han…...

400种行业劳动合同模板

400种行业劳动合同模板 内容介绍 这是一套包含400种行业的专业劳动合同模板合集&#xff0c;涵盖IT、建筑、教育、医疗等多个行业。所有模板均符合最新劳动法规定&#xff0c;可直接下载使用或根据需求修改。 全日制劳动合同 退休返聘协议 劳务合同 劳务&#xff0c;兼职&a…...

MyBatis 动态 SQL 完整笔记

MyBatis 动态 SQL 完整笔记 一、核心动态 SQL 标签与用法 1. 条件控制类标签 <if>&#xff1a;基础条件判断 示例&#xff1a;根据 name 和 age 动态筛选用户 <if test"name ! null and name ! ">AND user_name #{name} </if><choose>/<…...

npm包之serve-favicon

serve-favicon 是一个用于 Node.js 的中间件&#xff0c;它是 Express 框架的一部分。这个中间件的主要作用是提供网站的 favicon&#xff08;网站图标&#xff09;&#xff0c;通常是一个小的图标文件&#xff08;如 .ico&#xff09;&#xff0c;显示在浏览器的标签页、书签栏…...

软件工程(五):设计模式

创建型模式&#xff08;Creational Patterns&#xff09; 关注对象的创建过程&#xff0c;封装创建逻辑&#xff0c;避免代码耦合。 模式名称作用简述单例模式&#xff08;Singleton&#xff09;确保一个类只有一个实例&#xff0c;并提供全局访问点工厂方法模式&#xff08;…...

雷军「去执行化」与小米汽车更名:一场关乎安全与战略的双向奔赴|创客匠人热点评述

一、智驾更名背后的安全觉醒 小米汽车将 SU7 详情页的「智驾」更名为「辅助驾驶」&#xff0c;表面上是监管压力下的措辞调整&#xff0c;实则是一场关乎生命安全的认知革命。当工信部明确要求车企禁用「自动驾驶」「智驾」等易误导词汇&#xff0c;小米的快速响应不仅是对政策…...

python24-匿名函数

课程&#xff1a;B站大学 记录python学习&#xff0c;直到学会基本的爬虫&#xff0c;使用python搭建接口自动化测试就算学会了&#xff0c;在进阶webui自动化&#xff0c;app自动化 匿名函数 匿名函数实践是检验真理的唯一标准 匿名函数 匿名函数是指没有名字的函数&#xff…...

WiseAD:基于视觉-语言模型的知识增强型端到端自动驾驶——论文阅读

《WiseAD: Knowledge Augmented End-to-End Autonomous Driving with Vision-Language Model》2024年12月发表&#xff0c;来自新加坡国立和浙大的论文。 在快速发展的视觉语言模型&#xff08;VLM&#xff09;中&#xff0c;一般人类知识和令人印象深刻的逻辑推理能力的出现&a…...

汽车为什么需要以太网?带宽?实时?

一、传统总线“堵车”&#xff1a;为什么CAN、LIN扛不住了&#xff1f; 1. 带宽危机 案例&#xff1a;一辆L3级自动驾驶汽车每秒产生约4GB数据&#xff08;激光雷达摄像头&#xff09;&#xff0c;而CAN FD总线最高仅8Mbps。若用CAN传输&#xff0c;需 500秒才能传完1秒的数据—…...

人工智能在网络安全中的重要性

介绍 在当今世界,技术比以往任何时候都更加重要。随着物联网 (IoT) 和云计算等新兴技术的广泛应用,海量数据被创建和收集。尽管可以利用数据更好地满足相应的业务需求,但黑客攻击往往会带来巨大的障碍。网络攻击通常是个人或组织蓄意且有组织地试图入侵其他个人或组织的信息…...

【Windows 环境安装ollama,通过ollama拉起大模型】

Windows 环境安装ollama&#xff0c;通过ollama拉起大模型 背景&#xff1a;开始&#xff1a;1.访问ollama官方地址&#xff1a;https://ollama.com/2.下载3.安装ollama4.安装完成5.ollama 官网找到对应版本的大模型6.下载完成后 会自动拉起大模型&#xff0c;进行对话并观察任…...

英语六级---2024.12 卷二 仔细阅读2

文章 Statements, like "beauty is in the eye of the beholder (观看者),” are rarely questioned(51). Theyve become so embedded in our consciousness that people think its absurd to think otherwise. It might be useful, however, to at least push back on t…...

编译原理头歌实验:词法分析程序设计与实现(C语言版)

编译原理头歌实验&#xff1a;词法分析程序设计与实现&#xff08;C语言版&#xff09; 1.实验描述 任务描述 本关任务&#xff1a;加深对词法分析器的工作过程的理解&#xff1b;加强对词法分析方法的掌握&#xff1b;能够采用一种编程语言实现简单的词法分析程序&#xff…...

DeepSeek 赋能金融:从智能分析到高效服务的全链路革新

目录 一、引言二、DeepSeek 技术优势剖析2.1 低成本与高效率2.2 强大的推理能力2.3 开源与可定制性 三、DeepSeek 在金融资讯分析中的应用3.1 实时资讯监测与分析3.2 市场趋势预测 四、DeepSeek 在金融产品介绍文案撰写中的应用4.1 产品特点提炼与呈现4.2 个性化文案生成 五、D…...

IDEA 2024 版本配置热部署

前言 idea 2024.3.3版本配置热部署&#xff0c;实现修改代码验证结果不重启项目服务 步骤 1.在IDEA中settings中搜索Debbger&#xff0c;修改HotSwap 2.编辑启动类的参数&#xff0c;新增实时更新类和资源 3.检查compiler编辑器&#xff0c;是否勾选&#xff1a;build proje…...

LaTeX插入图片

在LaTeX中插入图片的基本代码如下&#xff1a; 1. 单图&#xff1a; \documentclass{article} \usepackage{graphicx} % 插入图片的宏包\begin{document}\begin{figure}[h!]\centering\includegraphics[width0.8\textwidth]{image.png} % 图片路径\caption{这是图片的标题}…...