【AndroidRTC-10】webrtc是如何确定双端的编解码类型?
Android-RTC系列软重启,改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性,方便形成肌肉记忆。同时不分种类、不分难易程度,在线征集问题切入点。
问题:webrtc-android是如何确定编解码类型,如何调整视频使用h264编解码?
分析:在早年的理论文章介绍到,rtc双端在正常建立媒体通讯前,需要进行SDP的信息交换,也就是使用Offer-Answer 模型交换 SDP。而在SDP中就包含了媒体信息的描述,进而确定通讯的媒体类型。所以这次我们应该重点关注这一块——SDP。
SDP Structure
SDP 描述分为两部分,分别是会话级别的描述(session level)和媒体级别的描述(media level),其具体的组成可参考 RFC4566,带星号 (*) 的是可选的。常见的结构如下:
Session description(会话级别描述)v= (protocol version)o= (originator and session identifier)s= (session name)c=* (connection information -- not required if included in all media)One or more Time descriptions ("t=" and "r=" lines; see below)a=* (zero or more session attribute lines)Zero or more Media descriptionsTime descriptiont= (time the session is active)Media description(媒体级别描述), if presentm= (media name and transport address)c=* (connection information -- optional if included at session level)a=* (zero or more media attribute lines)
一个真实的sdp描述 (只包含了视频没有开启音频)
v=0o=- 5771495527976275027 2 IN IP4 127.0.0.1s=-t=0 0a=group:BUNDLE 0a=extmap-allow-mixeda=msid-semantic: WMSm=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 35 36 37 38 39 40 41 42 127 103 104 105 106 107 108 43c=IN IP4 0.0.0.0a=rtcp:9 IN IP4 0.0.0.0a=ice-ufrag:e8aca=ice-pwd:J2ER3pB4qDi1l6dExhW5fgmwa=ice-options:trickle renominationa=fingerprint:sha-256 82:EF:59:DD:D4:8B:49:75:F1:6B:54:CF:E4:8B:94:4A:86:C6:21:C0:23:E7:6C:CA:E4:B4:E2:30:E5:F0:38:11a=setup:actpassa=mid:0a=extmap:1 urn:ietf:params:rtp-hdrext:toffseta=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-timea=extmap:3 urn:3gpp:video-orientationa=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delaya=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-typea=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timinga=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-spacea=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mida=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-ida=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-ida=recvonlya=rtcp-muxa=rtcp-rsizea=rtpmap:96 VP8/90000a=rtcp-fb:96 rrtra=rtcp-fb:96 goog-remba=rtcp-fb:96 transport-cca=fmtp:96 x-google-max-bitrate=50000;x-google-min-bitrate=500;x-google-start-bitrate=2000;x-google-huge-frames-sent=0;x-google-packetization-mode=1a=rtcp-fb:96 ccm fira=rtcp-fb:96 nacka=rtcp-fb:96 nack plia=rtpmap:97 rtx/90000a=fmtp:97 apt=96a=rtpmap:98 VP9/90000a=rtcp-fb:98 goog-remba=rtcp-fb:98 transport-cca=rtcp-fb:98 ccm fira=rtcp-fb:98 nacka=rtcp-fb:98 nack plia=fmtp:98 profile-id=0a=rtpmap:99 rtx/90000a=fmtp:99 apt=98a=rtpmap:35 VP9/90000a=rtcp-fb:35 goog-remba=rtcp-fb:35 transport-cca=rtcp-fb:35 ccm fira=rtcp-fb:35 nacka=rtcp-fb:35 nack plia=fmtp:35 profile-id=1a=rtpmap:36 rtx/90000a=fmtp:36 apt=35a=rtpmap:37 VP9/90000a=rtcp-fb:37 goog-remba=rtcp-fb:37 transport-cca=rtcp-fb:37 ccm fira=rtcp-fb:37 nacka=rtcp-fb:37 nack plia=fmtp:37 profile-id=3a=rtpmap:38 rtx/90000a=fmtp:38 apt=37a=rtpmap:39 AV1/90000a=rtcp-fb:39 goog-remba=rtcp-fb:39 transport-cca=rtcp-fb:39 ccm fira=rtcp-fb:39 nacka=rtcp-fb:39 nack plia=rtpmap:40 rtx/90000a=fmtp:40 apt=39a=rtpmap:41 AV1/90000a=rtcp-fb:41 goog-remba=rtcp-fb:41 transport-cca=rtcp-fb:41 ccm fira=rtcp-fb:41 nacka=rtcp-fb:41 nack plia=fmtp:41 profile=1a=rtpmap:42 rtx/90000a=fmtp:42 apt=41a=rtpmap:127 H264/90000a=rtcp-fb:127 goog-remba=rtcp-fb:127 transport-cca=rtcp-fb:127 ccm fira=rtcp-fb:127 nacka=rtcp-fb:127 nack plia=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01fa=rtpmap:103 rtx/90000a=fmtp:103 apt=127a=rtpmap:104 H265/90000a=rtcp-fb:104 goog-remba=rtcp-fb:104 transport-cca=rtcp-fb:104 ccm fira=rtcp-fb:104 nacka=rtcp-fb:104 nack plia=rtpmap:105 rtx/90000a=fmtp:105 apt=104a=rtpmap:106 red/90000a=rtpmap:107 rtx/90000a=fmtp:107 apt=106a=rtpmap:108 ulpfec/90000a=rtpmap:43 flexfec-03/90000a=rtcp-fb:43 goog-remba=rtcp-fb:43 transport-cca=fmtp:43 repair-window=10000000
SDP Line 是顺序相关的,比如 a=rtpmap:96
后面的都是它相关的设置,直到下一个 a=rtpmap
SDP 解析时,每个 SDP Line 都是以 key=...
形式,解析出 key 是 a 后,可能有两种方式
a=<attribute> 或者 a=<attribute>:<value>
比如 c=IN IP4 0.0.0.0,key 为 c。
比如 a=rtcp-mux,key 为 a,attribute 为 rtcp-mux,没有 value。
比如 a=rtpmap:96 VP8/90000,key 为 a,attribute 为 rtpmap,value=96 VP8/90000。
详细介绍请参考链接:WebRTC SDP 详解和剖析-阿里云开发者社区
我们这里直接跳到媒体级别描述。类似下面的 SDP 描述了一个音频和一个视频,它的格式参考 RFC4566:
.........
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=mid:audio
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
.........
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 39 40 127
a=mid:video
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
.........
后面的一串数字 111
和 96 97
就是 fmt,分别代表音频和视频的 Media Codec格式,后面会跟着 rtpmap、rtcp-fb、fmtp 这些属性来做进一步的详细的描述。
- a=mid 属性可以认为是每个 M 描述的唯一 ID。比如 a=mid:audio,那么
audio
这个字符串就是这个 M 描述的 ID。有的时候 mid 属性值也可以用数字表示,比如 a=mid:0,那么 0 也是这个 M 描述的 ID。mid 值一般和 grouping 传输属性的 BUNDLE 策略结合来用,比如 a=group:BUNDLE audio video,代表本次会话将对 mid 为audio
和video
的 M 描述进行复用传输。 - M line 的数字 9 代表该媒体类型的传输端口,在 RTC 场景中都是使用 ICE candidate 的地址信息进行数据传输,所以 M line 的 port 并没有用到。
- RTX 表示是重传,比如 video 的 97,就是 apt=96 的重传。也就是说如果用的是 97 这个编码格式,它是在 96(VP8) 基础上加了重传功能。
如何确定最后的编码?是在localOffer 和 remoteAnswer选取交集的第一个媒体类型。
PlanB and UnifiedPlan
在源代码中可能还会涉及到PlanB and UnifiedPlan,is_unified_plan是WebRTC中标识是否使用Unified Plan SDP语义的标志。Unified Plan是较新的标准,每个媒体流对应独立的m-line,而Plan B则是旧标准,同一类型的媒体流共享一个m-line。从M89版本开始,Chrome默认启用Unified Plan,并逐步废弃Plan B。
在sdp中使用ssrc来指定有多少媒体流,如下所示。
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 39 40 127 103 104 105 106 107 108
a=ssrc-group:FID 61733252 3497130671
a=ssrc:61733252 cname:3pW572Zij3FqEUjL
a=ssrc:61733252 msid:ARDAMS ARDAMSv0
a=ssrc:3497130671 cname:3pW572Zij3FqEUjL
a=ssrc:3497130671 msid:ARDAMS ARDAMSv0
实际上 Audio 和 Video 都有多个 SSRC,每个 SSRC 的编码可能相同但也可能不同。比如互联网视频会议,用移动端接入时,编码可能都是 H.264,但是和其他终端接入时可能会有其他编码。
如果 SSRC 的编码不相同,那么将这些 SSRC 放在同一个 M 描述就会有问题,这就是 PlanB 和 UnifiedPlan 的关键所在。对于 PlanB 只有一个 M(audio) 和 M(video),他们的编码要相同,有多路媒体流时,则根据 SSRC 去区分。UnifiedPlan 则可以有多个 M(audio) 和 M(video),每路流都有自己的 M 描述,这样就可以支持不同的编码。
PlanB 和 UnifiedPlan 其实就是 WebRTC 在多路媒体源(multi media source)场景下的两种不同的 SDP 协商方式。如果引入 Stream 和 Track 的概念,那么一个 Stream 可能包含 AudioTrack 和 VideoTrack,当有多路 Stream 时,就会有更多的 Track,如果每一个 Track 唯一对应一个自己的 M 描述,那么这就是 UnifiedPlan,如果每一个 M line 描述了多个 Track(track id),那么这就是 Plan B。
源代码解读
回到问题上:webrtc-android是如何确定编解码类型,如何调整视频使用h264编解码?
答:因为SDP会话描述协议是基于文本的协议,顺序相关的。所以在sdp交换后通过 localOffer 和 remoteAnswer的媒体类型列表中取交集取第一个共同类型确认本次会话的编解码类型。也就是说local和remote的description两者结合确认。
所以我们需要知道 localOffer究竟是如何产生本地的视频媒体列表的?并看看如何把h264相关的媒体类型,放置到sdp描述中 m=video的第一个选项。
以下createOffer的调用链路:
Java层
|--> PeerConnection.createOffer(sdpObserver, sdpMediaConstraints);
|--> nativeCreateOffersdk\android\src\jni\pc\peer_connection.cc
|--> static void JNI_PeerConnection_CreateOffer( ... )api\peer_connection_interface.h
|--> virtual void CreateOffer(CreateSessionDescriptionObserver* observer,const RTCOfferAnswerOptions& options) = 0;pc\peer_connection.cc
void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,const RTCOfferAnswerOptions& options) {RTC_DCHECK_RUN_ON(signaling_thread());sdp_handler_->CreateOffer(observer, options);
}pc\sdp_offer_answer.cc
void SdpOfferAnswerHandler::DoCreateOffer(const PeerConnectionInterface::RTCOfferAnswerOptions& options,rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) {... ...cricket::MediaSessionOptions session_options;GetOptionsForOffer(options, &session_options);webrtc_session_desc_factory_->CreateOffer(observer.get(), options,session_options);
}
前面的调用链路我就不细说了,我们来重点关心 sdp_hander 和其内部的 webrtc_session_desc_factory_的CreateOffer内部又会调用 InternalCreateOffer。
void WebRtcSessionDescriptionFactory::InternalCreateOffer(CreateSessionDescriptionRequest request) {... ...auto result = session_desc_factory_.CreateOfferOrError(request.options, sdp_info_->local_description()? sdp_info_->local_description()->description(): nullptr);if (!result.ok()) {PostCreateSessionDescriptionFailed(request.observer.get(), result.error());return;}std::unique_ptr<cricket::SessionDescription> desc = std::move(result.value());RTC_CHECK(desc);... ...
}
在WebRtcSessionDescriptionFactory::InternalCreateOffer中看到关键,又跳转到cricket::MediaSessionDescriptionFactory的CreateOfferOrError,如下所示。
webrtc::RTCErrorOr<std::unique_ptr<SessionDescription>>
MediaSessionDescriptionFactory::CreateOfferOrError(const MediaSessionOptions& session_options,const SessionDescription* current_description) const {... ... ...StreamParamsVec current_streams =GetCurrentStreamParams(current_active_contents);AudioCodecs offer_audio_codecs;VideoCodecs offer_video_codecs;GetCodecsForOffer(current_active_contents, &offer_audio_codecs,&offer_video_codecs);auto offer = std::make_unique<SessionDescription>();... ... ...
}
提示:当前变量的关系
std::unique_ptr<SdpOfferAnswerHandler> sdp_handler_
|——> std::unique_ptr<WebRtcSessionDescriptionFactory> webrtc_session_desc_factory_
|——————> cricket::MediaSessionDescriptionFactory session_desc_factory_;
这里看到GetCodecsForOffer,明显就是我们说的,获取当前设备支持的媒体能力列表。这里贴出GetCodecsForOffer的详细代码。可以看到 all_video_codecs_ 就是目标成员变量。
// Getting codecs for an offer involves these steps:
// 1. Construct payload type -> codec mappings for current description.
// 2. Add any reference codecs that weren't already present
// 3. For each individual media description (m= section), filter codecs based
// on the directional attribute (happens in another method).
void MediaSessionDescriptionFactory::GetCodecsForOffer(const std::vector<const ContentInfo*>& current_active_contents,AudioCodecs* audio_codecs,VideoCodecs* video_codecs) const {// First - get all codecs from the current description if the media type is used.// Add them to `used_pltypes` so the payload type is not reused if a// new media type is added.UsedPayloadTypes used_pltypes;MergeCodecsFromDescription(current_active_contents, audio_codecs,video_codecs, &used_pltypes);// Add our codecs that are not in the current description.MergeCodecs(all_audio_codecs_, audio_codecs, &used_pltypes);MergeCodecs(all_video_codecs_, video_codecs, &used_pltypes);
}
all_video_codecs_ 由(video_recv_codecs_, video_send_codecs_);两者取并集,这步操作就是在MediaSessionDescriptionFactory::MediaSessionDescriptionFactory构造函数里发生的。
void MediaSessionDescriptionFactory::ComputeVideoCodecsIntersectionAndUnion() {video_sendrecv_codecs_.clear();// Use ComputeCodecsUnion to avoid having duplicate payload IDsall_video_codecs_ =ComputeCodecsUnion(video_recv_codecs_, video_send_codecs_);// Use NegotiateCodecs to merge our codec lists, since the operation is// essentially the same. Put send_codecs as the offered_codecs, which is the// order we'd like to follow. The reasoning is that encoding is usually more// expensive than decoding, and prioritizing a codec in the send list probably// means it's a codec we can handle efficiently.NegotiateCodecs(video_recv_codecs_, video_send_codecs_,&video_sendrecv_codecs_, true);
}MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(cricket::MediaEngineInterface* media_engine,bool rtx_enabled,rtc::UniqueRandomIdGenerator* ssrc_generator,const TransportDescriptionFactory* transport_desc_factory): ssrc_generator_(ssrc_generator),transport_desc_factory_(transport_desc_factory) {RTC_CHECK(transport_desc_factory_);if (media_engine) {audio_send_codecs_ = media_engine->voice().send_codecs();audio_recv_codecs_ = media_engine->voice().recv_codecs();video_send_codecs_ = media_engine->video().send_codecs(rtx_enabled);video_recv_codecs_ = media_engine->video().recv_codecs(rtx_enabled);}ComputeAudioCodecsIntersectionAndUnion();ComputeVideoCodecsIntersectionAndUnion();
}
到这里看到media_engine就轻松了,也就由应用层创建的PeerConnectionFactory、ConnectionContext 等全局变量延申的媒体引擎,这里我们可以逆向溯源验证是否,并且顺流溯源找到调用堆栈。
逆向溯源验证:
所以media_engine()->video() 对应的就是多年前的文章《Android-RTC-9 PeerConnectionFactory》 介绍的 MediaEngineInterface 中video_decoder_factory/video_encoder_factory,也就是应用层PeerConnectionFactory中的DefaultVideoDecoder/EncoderFactory。
顺流溯源调用的方法:
//文件位置 media\engine\webrtc_video_engine.cc
std::vector<VideoCodec> WebRtcVideoEngine::send_codecs(bool include_rtx) const {return GetPayloadTypesAndDefaultCodecs(encoder_factory_.get(),/*is_decoder_factory=*/false,include_rtx, trials_);
}template <class T>
std::vector<VideoCodec> GetPayloadTypesAndDefaultCodecs(const T* factory, bool is_decoder_factory, bool include_rtx,const webrtc::FieldTrialsView& trials) {if (!factory) {return {};}std::vector<webrtc::SdpVideoFormat> supported_formats =factory->GetSupportedFormats();
}
VideoEncoderFactoryWrapper::VideoEncoderFactoryWrapper(JNIEnv* jni,const JavaRef<jobject>& encoder_factory): encoder_factory_(jni, encoder_factory) {const ScopedJavaLocalRef<jobjectArray> j_supported_codecs =Java_VideoEncoderFactory_getSupportedCodecs(jni, encoder_factory);supported_formats_ = JavaToNativeVector<SdpVideoFormat>(jni, j_supported_codecs, &VideoCodecInfoToSdpVideoFormat);const ScopedJavaLocalRef<jobjectArray> j_implementations =Java_VideoEncoderFactory_getImplementations(jni, encoder_factory);implementations_ = JavaToNativeVector<SdpVideoFormat>(jni, j_implementations, &VideoCodecInfoToSdpVideoFormat);
}std::vector<SdpVideoFormat> VideoEncoderFactoryWrapper::GetSupportedFormats()const {return supported_formats_;
}
主要就是HardwareEncoderFactory的getSupportedCodecs,显然supportedCodecInfos这里,我们把VideoCodecMimeType.H264放在第一个位置,把h264的优先权提到最高。
上面这种方法需要修改源码并重新编译,侵入式比较强。我看最新版本的Demo也支持了多年前的暴力修改法。也就是在应用层等待local_description的onCreateSuccess后,以修改文本的方式修改sdp,把期望的编码类型提前到第一个a=rtpmap。这种方式简单直接,看看大家喜欢哪个方案了。
相关文章:
【AndroidRTC-10】webrtc是如何确定双端的编解码类型?
Android-RTC系列软重启,改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性,方便形成肌肉记忆。同时不分种类、不分难易程度,在线征集问题切入点。 问题:webrtc-android是如何确定编解码类型,如何调整视…...
深度求索(DeepSeek):以AI之力重塑医疗未来
目录 一、智能诊断:打破医疗认知的“分辨率极限” 二、药物研发:重构分子世界的“造物逻辑” 三、医疗资源重构:打造分级诊疗的“神经中枢” 四、健康管理:编织个体化医学的“防护网” 五、伦理与进化:构建医疗AI…...
【HTML 基础教程】HTML 属性
HTML 属性 属性是 HTML 元素提供的附加信息。 属性通常出现在 HTML 标签的开始标签中,用于定义元素的行为、样式、内容或其他特性。 属性总是以 name"value" 的形式写在标签内,name 是属性的名称,value 是属性的值。 HTML 属性 …...
macOS 制作dmg磁盘映像安装包
制作dmg磁盘影像安装包需要准备一下材料: 1. 导出的APP 2. 背景图片 3. 应用程序替身 前两种材料很容易得到。 下面介绍一下 应用程序替身制作过程: Finder —> 选中 应用程序 --> 找到顶部菜单栏中 的 前往 ----> 选择上层文件夹选中应用程…...
Appium中元素定位之一组元素定位API
应用场景 和定位一个元素相同,但如果想要批量的获取某个相同特征的元素,使用定位一组元素的方式更加方便 在 Appium 中定位一组元素的 API 与定位单个元素的 API 类似,但它们返回的是一个元素列表(List<MobileElement>&am…...
webstorm中element-ui标签无法跳转源码
原本用的webstorm2019,之前的项目开发时切实体验过跳转element-ui源码,觉得很香。 更新了webstorm至2024,居然不行了,能弹出来提示,但就是找不到定义。 不知道是不是2024版本的问题,node_moudles不管我是否手动添加exc…...
【蓝桥杯】算法笔记1
1.暴力枚举 给定一个正整数n,请找出所有满足a + b = n的整数对(a, b),其中a和b都是正整数,且a ≤ b。 输入格式:一个正整数n (1 ≤ n ≤ 10⁶) 输出格式:所有符合条件的(a, b)对,每行一对,按a的升序排列。如果没有符合条件的对,输出"No solution"。 问题分…...
Pytorch学习笔记(十一)Learning PyTorch - What is torch.nn really
这篇博客瞄准的是 pytorch 官方教程中 Learning PyTorch 章节的 What is torch.nn really? 部分。主要是教你如何一步一步将最原始的代码进行重构至pytorch标准的代码,如果你已经熟悉了如何使用原始代码以及pytorch标准形式构建模型,可以跳过这一篇。 …...
OpenGL ES 2.0与OpenGL ES 3.1的区别
如果硬件支持且需要更高质量的图形效果,推荐3.1;如果兼容性和开发简便更重要,且效果需求不高,2.0更合适。不过现代车载系统可能越来越多支持3.x版本,所以可能倾向于使用3.1,但具体情况还需调查目标平台的硬…...
【Unity3D脚本与系统设计6】鼠标触摸超时待机实现
实现步骤 在Unity中实现一个功能,当鼠标或触摸超过一定时间没有操作时,自动返回待机界面。 检测输入 首先,我需要检测用户的输入,无论是鼠标还是触摸。Unity的Input系统可以检测到鼠标和触摸事件,比如Input.GetAxis…...
SpringMVC 入门教程
一、SpringMVC 简介 SpringMVC 是基于 MVC 设计模式的轻量级 Web 框架,核心功能包括: 请求分发:通过 DispatcherServlet 统一处理请求。注解驱动:使用 Controller、RequestMapping 简化开发。视图解析:支持 JSP、Thy…...
矿山自动化监测解决方案
1.行业现状 为贯彻落实《中共中央国务院关于推进安全生产领域改革发展的意见》《“十四五”矿山安全生产规划》(应急〔2022〕64号)、《国务院安委会办公室关于加强矿山安全生产工作的紧急通知》(安委办〔2021〕3号)等有关工作部署…...
el-table + el-pagination 前端实现分页操作
el-table el-pagination 前端实现分页操作 后端返回全部列表数据,前端进行分页操作 html代码 <div><el-table :data"tableData" border><el-table-column label"序号" type"index" width"50" /><el…...
NIO ByteBuffer 总结
目录 基本概念创建 ByteBuffer核心属性关键方法切换模式读写操作压缩数据 基本概念 java.nio.ByteBuffer 是 Java NIO 中一个核心类, 用于高效处理二进制数据的读写操作。应用于通道(Channel)的I/O操作。作用: 数据缓冲…...
华为hcia——Datacom实验指南——配置IPv4静态路由,默认路由和浮动静态路由
什么是IPv4 IPv4静态路由,是手动配置的,不会随着网络拓扑的变化而变化,所配置的路由信息也不会在网络中传播,所以它主要运用在小型网络或者作为动态路由的补充。 IPv4的配置 配置的命令很简单 IP route-static (目…...
Git入门——常用指令汇总
以下是一份精心整理的 Git常用指令速查表,基本覆盖日常开发使用场景,建议收藏备用👇 🔧 环境配置 指令作用git config --global user.name "你的名字"设置全局用户名git config --global user.email "你的邮箱&qu…...
深入解析 MyBatis-Plus 批量操作:原理、实现与性能优化
引言 在高并发、大数据量场景下,批量数据库操作是提升系统性能的核心手段之一。本文以 MyBatis-Plus 为例,深入剖析 批量更新 和 自定义批量插入 的实现原理,并结合实战代码与性能测试,揭示其在高性能场景下的应用价值。 批量更新:动态 SQL 的极致运用 原理与 SQL 生成…...
2025年成都市双流区农业科技试验示范基地建设方案申报条件材料和补贴程序、时间安排
成都市双流区2025年农业科技试验示范基地建设方案申报条件材料和补贴程序、时间安排如下,需要申报的可指导! 一、成都市双流区农业科技试验示范基地申报 (一)基地建设数量。2025年建设农业科技试验示范基地2个。 (二…...
8路CXP相机采集系统介绍
8xCXP相机采集系统介绍 目录 1 系统概述 4 2 硬件架构 5 2.1 FPGA处理单元 5 2.2 CXP接口层 6 2.3 CXP相机说明与使用要求 7 2.4 SSI控制器板 8 3 FPGA方案 9 3.1 FPGA实现 9 3.2 Block Design说明 10 4 软件方案 14 4.1 嵌入式层 14 4.2 上位机软件(C…...
vue2前端日志数据存储,推荐(IndexedDB)
前言:首先,我得回忆一下IndexedDB的基本概念和用法,确保自己理解正确。IndexedDB是一个浏览器内置的数据库,允许存储大量结构化数据,支持事务和索引查询,适合需要离线存储的应用场景。 接下来,用…...
onedav一为导航批量自动化导入网址(完整教程)
OneNav作为一个功能强大的导航工具,支持后台管理、加密链接、浏览器书签批量导入等功能,能够帮助用户轻松打造专属的导航页面。今天,我将为大家详细介绍如何实现OneNav导航站的批量自动化导入网址。 1、建立要批量导入的表格 格局需要创建表格,表格的要求是一定要有需要,…...
Ubuntu Linux安装PyQt5并配置Qt Designer
一 安装 PyQt5 借助 apt 包管理器来安装 PyQt5 及其相关的开发工具: sudo apt install python3-pyqt5 pyqt5-dev-tools 假如报错, You might want to run apt --fix-broken install to correct these. 直接执行: sudo apt --fix-…...
无人机螺旋桨平衡标准
螺旋桨平衡是确保无人机(UAV)平稳运行、可靠性和使用寿命的关键过程。螺旋桨的不平衡会导致振动、噪音,并加速关键部件的磨损,从而对飞行性能产生负面影响。 ISO 21940-11:2016标准为旋翼平衡提供了一个广泛引用的框架,定义了可接受的不平衡…...
基于MCP协议的多模态模型优化在医疗3D打印精密人工关节制造中的研究
一、引言 1.1 研究背景与意义 在全球人口老龄化趋势愈发明显的当下,诸如骨关节炎、类风湿性关节炎这类关节疾病的发病率不断攀升,进而使得人工关节置换手术的需求呈现出激增态势。人工关节置换手术作为治疗终末期关节疾病的有效手段,能够显著缓解患者疼痛,提升关节功能与生…...
ESLint报错:Could not find config file.
如果你的ESLint的版本大于 8,同时使用 .eslinrc.js 和 .eslintignore 作为配置文件,且目前用的是 VSCODE ,就有可能遇到报错: Could not find config file. 这个是因为 VSCode 中 ESLint 插件的配置 eslint.useFlatConfig 的问题…...
npm install 卡在创建项目:sill idealTree buildDeps
参考: https://blog.csdn.net/PengXing_Huang/article/details/136460133 或者再执行 npm install -g cnpm --registryhttps://registry.npm.taobao.org 或者换梯子...
drizzleDumper:基于内存搜索的Android脱壳工具
一、工具介绍 drizzleDumper 是一款基于内存搜索的 Android 脱壳工具,主要用于从加固的 Android 应用程序中提取原始的 DEX 文件。它通过分析应用程序运行时的内存,定位并提取被加固的 DEX 文件,从而帮助开发者、安全研究人员进行逆向工程和…...
信号处理中的窗
窗函数(Window Function)是一种在信号处理中常用的工具,用于对信号进行截断和加权处理。它在频谱分析、滤波器设计以及信号处理的许多其他领域中都发挥着重要作用。 窗函数的基本概念 窗函数本质上是一个有限长度的序列,通常用于…...
FFmpeg学习:AVPacket结构体
1.AVPacket结构体 FFmpeg中用于封装一帧的编码数据的结构体(比如H264视频帧或者AAC音频帧),主要用于编解码过程中数据的载体,使用av_read_frame()读取获得,或者使用avcodec_send_packet()进行解码,与AVFra…...
34.[前端开发-JavaScript基础]Day11-王者轮播图-书籍购物车-BOM对象-JSON
1 认识BOM操作 认识BOM 2 全局对象window window对象 window对象的作用 window常见的属性 window常见的方法 3 事件对象event window常见的事件 4 location、history location对象常见的属性 Location对象常见的方法 URLSearchParams history对象常见属性和方法 5 navigato…...
FLEXlm如何通过web调用
FLEXlm 是一种流行的软件许可管理工具,广泛用于各种软件产品的授权管理。它支持多种协议,包括传统的服务器-客户端模式和一些基于网络的解决方案。如果你想通过 Web 接口调用 FLEXlm 许可证服务器,你可以通过以下几种方式实现: 使…...
深度解析Spring Boot可执行JAR的构建与启动机制
一、Spring Boot应用打包架构演进 1.1 传统JAR包与Fat JAR对比 传统Java应用的JAR包在依赖管理上存在明显短板,依赖项需要单独配置classpath。Spring Boot创新的Fat JAR(又称Uber JAR)解决方案通过spring-boot-maven-plugin插件实现了"…...
Zookeeper运维指南:服务端与客户端常用命令详解
#作者:任少近 文章目录 1 Zookeeper服务端常用命令2 Zookeeper客户端常用命令2.1Ls命令2.2创建节点create2.3Get命令2.4删除命令2.5修改命令 1 Zookeeper服务端常用命令 启动ZK服务: bin/zkServer.sh start # ./zkServer.sh startZooKeeper JMX enabled by defau…...
K8S学习之基础五十一:k8s部署jenkins
k8s部署jenkins 创建nfs共享目录, mkdir -p /data/v2 echo /data/v2 *(rw,no_root_squash) > /etc/exports exportfs -arv创建pv、pvc vi pv.yaml apiVersion: v1 kind: PersistentVolume metadata:name: jenkins-k8s-pv spec:capacity:storage: 1GiaccessMod…...
界面控件DevExpress WinForms v25.1 - 人工智能(AI)方面全新升级
DevExpress WinForms拥有180组件和UI库,能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序,无论是Office风格的界面,还是分析处理大批量的业务数据,它都能轻松胜…...
基于动态光影融合的缺陷实时检测和材质量化方法,并且整合EventPS、VMNer和EvDiG
要完成基于动态光影融合的缺陷实时检测和材质量化方法,并且整合EventPS、VMNer和EvDiG,是一个复杂且综合性的任务。以下是一个大致的实现步骤和代码示例,不过要完整完成论文和所有实验还需要大量的细化和调整。 整体思路 数据加载与预处理&…...
关于我对接了deepseek之后部署到本地将数据存储到mysql的过程
写在前面 今天写一下使用nodejs作为服务端,vue作为客户端,mysql的数据库,对接deepseek的全过程,要实现一个很简单的效果就是,可以自由的询问,然后可以将询问的过程存储到mysql的数据库中。 文档对接 deeps…...
Android 中两个 APK 之间切换的几中方法
在 Android 中,两个 APK(应用程序)之间的切换通常是通过 Intent 来实现的。以下是一些常见的方法和注意事项,帮助你实现两个 APK 之间的切换。 一、启动目标 APK 的主 Activity 1、setPackage 方法 使用 Intent 的 setPackage …...
【LeetCode 热题 100】解答汇总
一、哈希表 1. 两数之和 | python【简单】 49. 字母异位词分组 | python【中等】 128. 最长连续序列 | python【中等】 二、双指针 283. 移动零 | python【简单】 盛最多水的容器 三数之和 接雨水 三、滑动窗口 3. 无重复字符的最长子串 | python 【中等】 49. 字母异…...
【RAG综述系列】之 RAG 相关背景和基本原理
系列文章: 【RAG综述系列】之 RAG 相关背景和基本原理 【RAG综述系列】之 RAG 特点与挑战以及方法与评估 【RAG综述系列】之 RAG 先进方法与综合评估 【RAG综述系列】之 RAG 应用和未来方向 正文: 检索增强生成(Retrieval-Augmented Gen…...
Unity 开发休闲手游:M_Studio 实战指南,源码课件全解析
Unity 开发休闲手游:M_Studio 实战指南,源码课件全解析 在手游开发领域,Unity 引擎凭借其强大的跨平台能力和丰富的资源,成为众多开发者的首选。今天,我们深入探讨如何利用 Unity 开发休闲手机游戏,以 M_S…...
HTML5 新的 Input 类型学习笔记
HTML5 引入了多种新的表单输入类型,这些新特性不仅增强了输入控制,还提供了更强大的验证功能,使表单设计更加灵活和便捷。以下是 HTML5 新的 Input 类型的详细学习笔记。 一、color 类型 功能:用于选取颜色。 使用场景ÿ…...
【第23节】windows网络编程模型(WSAEventSelect模型)
目录 引言 一、WSAEventSelect模型概述 二、 WSAEventSelect模型的实现流程 2.1 创建一个事件对象,注册网络事件 2.2 等待网络事件发生 2.3 获取网络事件 2.4 手动设置信号量和释放资源 三、 WSAEventSelect模型伪代码示例 四、完整实践示例代码 引言 在网…...
C# 中实现 跨线程写入
方案核心思路 写入请求队列:使用 ConcurrentQueue 接收来自任意线程的写入请求。 专用写入线程:由独立线程处理队列中的写入操作,确保顺序执行。 双信号机制:通过 ManualResetEventSlim 控制读取线程的暂停与恢复。 线程安全确…...
联合体(Union)的使用与应用场景
引言 在 C/C++ 编程中,联合体(Union)是一个非常独特的数据结构。与结构体(struct)不同,联合体允许不同的数据类型共享同一块内存空间,从而节省内存。在许多需要高效内存管理的场景下,联合体的使用能够显著提高程序的性能与资源利用率。本文将从联合体的基本概念入手,…...
Spark2 之 Expression/Functions
ExpressionConverter src/main/scala/org/apache/gluten/expression/ExpressionConverter.scala TopNTransformer src/main/scala/org/apache/gluten/execution/TopNTransformer.scala...
【Mysql】SQL 优化全解析
文章目录 一、理解执行计划1.1 执行计划的作用1.2 查看执行计划 二、查询优化2.1 避免全表扫描2.2 使用覆盖索引2.3 合理使用 JOIN 三、索引优化3.1 索引设计原则3.2 索引维护 在数据驱动的当今时代,MySQL 作为应用广泛的开源关系型数据库&…...
谈谈对spring IOC的理解,原理和实现
一、IoC 核心概念 1. 控制反转(Inversion of Control) 传统编程中对象自行管理依赖(主动创建),而IoC将控制权转移给容器,由容器负责对象的创建、装配和管理,实现依赖关系的反向控制。 2. 依赖…...
Element UI实现表格全选、半选
制作如图所示的表格全选、半选: 父组件 <template><div id"app"><SelectHost :hostArray"hostArray" /></div> </template><script> import SelectHost from ./components/SelectHost.vue export default…...
Dify实现自然语言生成SQL并执行
目录 一、需求分析 二、解决思路 问题1:文字描述生成SQL语句 问题2:执行生成的SQL语句 完整解决方案 三、最终效果展示 四、具体实现 1.Agent提示词 2.知识库数据 3.sql执行器工作流创建 3.1 节点1 3.2 节点2 3.3 节点3 3.4 最终配置界面预…...