如何设计开发RTSP直播播放器?

技术背景

我们在对接RTSP直播播放器相关技术诉求的时候,好多开发者,除了选用成熟的RTSP播放器外,还想知其然知其所以然,对RTSP播放器的整体开发有个基础的了解,方便方案之作和技术延伸。本文抛砖引玉,做个大概的介绍。

技术实现

技术难点

在探讨RTSP直播播放器技术实现之前,我们先来看,为什么RTSP播放器的开发看似简单,实则复杂,或者说做播放器容易,做个好的播放器,为什么就那么难?

协议复杂性
  1. 理解 RTSP 协议规范
    • RTSP 协议本身比较复杂,包含多种请求方法(如 OPTIONS、DESCRIBE、SETUP、PLAY、PAUSE、TEARDOWN 等)、状态码和头部字段。开发者需要深入理解这些规范,以便正确地与服务器进行交互。
    • 例如,在处理 DESCRIBE 请求时,需要解析服务器返回的媒体描述信息,其中可能包含复杂的 SDP(Session Description Protocol)格式,包括媒体类型、编码方式、帧率、分辨率等参数的描述。理解和解析这些信息需要对协议规范有深入的了解。
  2. 处理不同的协议变种和扩展
    • 在实际应用中,可能会遇到不同的 RTSP 服务器实现,它们可能会有一些协议变种或自定义的扩展。开发者需要能够处理这些差异,确保播放器能够与各种服务器兼容。
    • 例如,某些服务器可能会在 RTSP 响应中添加自定义的头部字段,或者对标准的请求方法有不同的处理方式。开发者需要能够识别这些差异,并进行相应的处理,以保证播放器的兼容性。
网络环境的不确定性
  1. 适应不同的网络条件
    • RTSP 播放器需要在各种网络环境下工作,包括不同的带宽、延迟、丢包率等。开发者需要考虑如何适应这些不同的网络条件,以确保视频的流畅播放。
    • 例如,在低带宽环境下,可能需要采用自适应比特率技术,根据网络状况动态调整视频的码率,以避免卡顿和缓冲。在高延迟网络中,需要优化播放控制算法,减少播放延迟,提高用户体验。
  2. 处理网络错误和异常情况
    • 网络环境中可能会出现各种错误和异常情况,如连接中断、服务器故障、丢包等。开发者需要能够处理这些情况,进行适当的错误恢复和重试机制,以保证播放器的稳定性。
    • 例如,当连接中断时,播放器需要能够自动尝试重新连接服务器,并在重新连接成功后继续播放。当出现丢包情况时,需要采用适当的错误隐藏技术,如帧间插值或重复上一帧,以减少视频的卡顿和花屏现象。
视频解码和播放的复杂性
  1. 支持多种视频编码格式
    • RTSP 流可以使用多种视频编码格式,如 H.264、H.265、MPEG-4 等。开发者需要选择合适的视频解码器,并确保播放器能够支持各种常见的编码格式。
    • 不同的编码格式具有不同的特点和复杂性,需要对其进行深入了解和处理。例如,H.265 编码具有更高的压缩率,但解码复杂度也更高。开发者需要选择高效的解码器,并进行优化,以确保在不同设备上的性能表现。
  2. 处理视频同步问题
    • 在播放视频时,需要确保音频和视频的同步播放。这涉及到处理视频和音频的时间戳、帧率、采样率等参数,以及进行适当的同步调整。
    • 视频和音频的同步是一个复杂的问题,需要考虑多种因素,如网络延迟、解码时间、播放设备的性能等。开发者需要采用适当的同步算法,确保音频和视频的同步播放,提高用户体验。
跨平台开发的挑战
  1. 适应不同的操作系统和设备
    • RTSP 播放器需要在不同的操作系统和设备上运行,如 Windows、Linux、Android、iOS 等。开发者需要考虑如何进行跨平台开发,确保播放器在各种平台上都能正常工作。
    • 不同的平台具有不同的开发环境、编程语言和多媒体框架,需要进行相应的适配和优化。例如,在 Android 平台上,可能需要使用 Java 或 Kotlin 进行开发,并利用 Android 的多媒体框架;在 iOS 平台上,可能需要使用 Objective-C 或 Swift 进行开发,并利用 iOS 的 AVFoundation 框架。
  2. 处理不同的硬件特性
    • 不同的设备具有不同的硬件特性,如处理器性能、内存大小、图形处理能力等。开发者需要考虑如何优化播放器的性能,以适应不同设备的硬件特性。
    • 例如,在性能较低的设备上,可能需要采用更高效的解码算法和播放控制策略,以减少资源占用和提高播放流畅性。在具有硬件加速功能的设备上,可以利用硬件加速来提高解码和播放性能。

技术选型

编程语言和平台
  • 选择适合的编程语言和开发平台。常见的选择包括 C++、Java、Python等编程语言,以及 Android、iOS、Windows、Linux 等操作系统平台。
  • 例如,在 Android平台上可以使用 Java 或 Kotlin 进行开发,利用 Android SDK 提供的多媒体框架和网络功能来实现 RTSP 播放器。,也可以通过jni接口封装,核心业务在底层,对上提供jni调用接口。
多媒体框架和库
  • 选择合适的多媒体框架和库来实现视频解码和播放功能。一些常用的多媒体框架和库包括 FFmpeg、GStreamer、VLC 等。
  • 这些框架和库提供了丰富的功能,如视频解码、音频解码、流媒体协议支持等,可以大大简化 RTSP 播放器的开发过程。例如,FFmpeg 是一个广泛使用的开源多媒体框架,支持众多的视频和音频格式以及流媒体协议,可以在多个平台上使用。
了解RTSP协议
  1. 协议结构和工作原理
    • 深入了解 RTSP 协议的结构和工作原理。RTSP 是一个应用层协议,用于控制实时流媒体的传输。它使用 TCP 或 UDP 作为传输层协议,通过发送请求和接收响应来实现对媒体流的控制。
    • RTSP 协议的主要功能包括媒体流的播放、暂停、快进、快退等操作,以及媒体流的描述、设置和传输控制等。了解 RTSP 协议的请求和响应格式、状态码、方法等内容,对于开发 RTSP 播放器至关重要。
  2. 协议交互过程
    • 熟悉 RTSP 协议的交互过程。当播放器连接到 RTSP 服务器时,首先发送 OPTIONS 请求以获取服务器支持的方法列表。然后,播放器发送 DESCRIBE 请求获取媒体流的描述信息,包括媒体格式、编码方式、帧率等。
    • 根据媒体流的描述信息,播放器选择合适的解码器进行视频和音频解码。接下来,播放器发送 SETUP 请求建立媒体流的传输连接,并发送 PLAY 请求开始播放媒体流。在播放过程中,播放器可以发送 PAUSE、TEARDOWN 等请求来控制媒体流的播放状态。

实现播放器功能

网络连接和数据接收
  • 实现与 RTSP 服务器的网络连接和数据接收功能。使用所选编程语言的网络编程库,建立与 RTSP 服务器的 TCP 或 UDP 连接,并接收服务器发送的媒体流数据。
  • 在接收数据时,需要处理网络错误、丢包等情况,确保数据的完整性和准确性。可以使用缓冲区来存储接收到的数据,以便后续的解码和播放操作。
视频解码和播放
  • 选择合适的视频解码器对接收的媒体流数据进行解码,并将解码后的视频帧显示在屏幕上。根据所选的多媒体框架和库,配置解码器参数,如视频格式、分辨率、帧率等。
  • 对于视频播放,可以使用图形库或多媒体框架提供的显示功能,将解码后的视频帧绘制在窗口或视图中。同时,需要处理视频的同步问题,确保音频和视频的同步播放。
音频解码和播放
  • 对接收的媒体流数据中的音频部分进行解码,并通过音频设备播放出来。选择合适的音频解码器,配置解码器参数,如音频格式、采样率、声道数等。
  • 使用音频输出库或多媒体框架提供的音频播放功能,将解码后的音频数据发送到音频设备进行播放。同样,需要处理音频的同步问题,确保音频和视频的同步播放。
播放控制和用户界面
  • 实现播放控制功能,如播放、暂停、快进、快退等操作。通过发送相应的 RTSP 请求来控制媒体流的播放状态,并在用户界面上提供相应的控制按钮。
  • 设计用户界面,包括视频显示区域、播放控制按钮、进度条等。使用图形用户界面库或开发平台提供的界面设计工具,创建直观、易用的用户界面。

SmartPlayer设计实现

以大牛直播SDK的SmartPlayer RTSP直播播放模块为例,我们来看看,如何实现低延迟的RTSP播放器。大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。先说功能设计,如不单独说明,Windows、Linux(x86_64|aarch64架构)、Android、iOS全平台支持。

  •  [多实例播放]支持多实例播放;
  •  [事件回调]支持网络状态、buffer状态等回调;
  •  [视频格式]支持H.265、H.264,此外,还支持RTSP MJPEG播放;
  •  [音频格式]支持AAC/PCMA/PCMU;
  •  [H.264/H.265软解码]支持H.264/H.265软解;
  •  [H.264硬解码]Windows/Android/iOS支持特定机型H.264硬解;
  •  [H.265硬解]Windows/Android/iOS支持特定机型H.265硬解;
  •  [H.264/H.265硬解码]Android支持设置Surface模式硬解和普通模式硬解码;
  •  [RTSP模式设置]支持RTSP TCP/UDP模式设置;
  •  [RTSP TCP/UDP自动切换]支持RTSP TCP、UDP模式自动切换;
  •  [RTSP超时设置]支持RTSP超时时间设置,单位:秒;
  •  [RTSP 401认证处理]支持上报RTSP 401事件,如URL携带鉴权信息,会自动处理;
  •  [缓冲时间设置]支持buffer time设置;
  •  [首屏秒开]支持首屏秒开模式;
  •  [复杂网络处理]支持断网重连等各种网络环境自动适配;
  •  [快速切换URL]支持播放过程中,快速切换其他URL,内容切换更快;
  •  [音视频多种render机制]Android平台,视频:surfaceview/OpenGL ES,音频:AudioTrack/OpenSL ES;
  •  [实时静音]支持播放过程中,实时静音/取消静音;
  •  [实时音量调节]支持播放过程中实时调节音量;
  •  [实时快照]支持播放过程中截取当前播放画面;
  •  [只播关键帧]Windows平台支持实时设置是否只播放关键帧;
  •  [渲染角度]支持0°,90°,180°和270°四个视频画面渲染角度设置;
  •  [渲染镜像]支持水平反转、垂直反转模式设置;
  •  [等比例缩放]支持图像等比例缩放绘制(Android设置surface模式硬解模式不支持);
  •  [实时下载速度更新]支持当前下载速度实时回调(支持设置回调时间间隔);
  •  [解码前视频数据回调]支持H.264/H.265数据回调;
  •  [解码后视频数据回调]支持解码后YUV/RGB数据回调;
  •  [解码前音频数据回调]支持AAC/PCMA/PCMU数据回调;
  •  [音视频自适应]支持播放过程中,音视频信息改变后自适应;
  •  [扩展录像功能]完美支持和录像SDK组合使用。
RTSP播放器设计要点

1. 低延迟:大多数RTSP的播放都面向直播场景,所以,如果延迟过大,严重影响体验,所以,低延迟是衡量一个好的RTSP播放器非常重要的指标,目前大牛直播SDK的RTSP直播播放延迟比开源播放器更优异,而且长时间运行下,不会造成延迟累积;

2. 音视频同步处理有些播放器为了追求低延迟,甚至不做音视频同步,拿到audio video直接播放,导致a/v不同步,还有就是时间戳乱跳等各种问题,大牛直播SDK提供的播放器,具备好的时间戳同步和异常时间戳矫正机制;

3. 支持多实例:大牛直播SDK提供的播放器支持同时播放多路音视频数据,比如4-8-9窗口,大多开源播放器对多实例支持不太友好;

4. 支持buffer time设置:在一些有网络抖动的场景,播放器需要支持buffer time设置,一般来说,以毫秒计,开源播放器对此支持不够友好;

5. TCP/UDP模式设定自动切换:考虑到好多服务器仅支持TCP或UDP模式,一个好的RTSP播放器需要支持TCP/UDP模式设置,如链接不支持TCP或UDP,大牛直播SDK可自动切换,,开源播放器不具备自动切换TCP/UDP能力;

6. 实时静音:比如,多窗口播放RTSP流,如果每个audio都播放出来,体验非常不好,所以实时静音功能非常必要,开源播放器不具备实时静音功能;

7. 视频view旋转:好多摄像头由于安装限制,导致图像倒置,所以一个好的RTSP播放器应该支持如视频view实时旋转(0° 90° 180° 270°)、水平反转、垂直反转,开源播放器不具备此功能;

8. 支持解码后audio/video数据输出:大牛直播SDK接触到好多开发者,希望能在播放的同时,获取到YUV或RGB数据,进行人脸匹配等算法分析,开源播放器不具备此功能;

9. 实时快照:感兴趣或重要的画面,实时截取下来非常必要,一般播放器不具备快照能力,开源播放器不具备此功能;

10. 网络抖动处理(如断网重连):稳定的网络处理机制、支持如断网重连等,开源播放器对网络异常处理支持较差;

11. 长期运行稳定性:不同于市面上的开源播放器,大牛直播SDK提供的Windows平台RTSP直播播放SDK适用于数天长时间运行,开源播放器对长时间运行稳定性支持较差;

12. log信息记录:整体流程机制记录到LOG文件,确保出问题时,有据可依,开源播放器几无log记录。

13. 实时下载速度反馈:大牛直播SDK提供音视频流实时下载回调,并可设置回调时间间隔,确保实时下载速度反馈,以此来监听网络状态,开源播放器不具备此能力;

14. 异常状态处理Event状态回调如播放的过程中,断网、网络抖动、等各种场景,大牛直播SDK提供的播放器可实时回调相关状态,确保上层模块感知处理,开源播放器对此支持不好;

15. 关键帧/全帧播放实时切换:特别是播放多路画面的时候,如果路数过多,全部解码、绘制,系统资源占用会加大,如果能灵活的处理,可以随时只播放关键帧,全帧播放切换,对系统性能要求大幅降低。

Android平台RTSP播放示例

下面以Android平台多实例RTSP播放为例,探讨下接口设计和调用说明。

我们针对的功能展示,主要是播放和录像这块,先说播放:

/** SmartPlayer.java* Author: https://daniusdk.com* WeChat: xinsheng120* Created by DaniuLive on 2015/09/26.*/
class ButtonPlayback1Listener implements View.OnClickListener {public void onClick(View v) {if (stream_player_1_.is_playing()) {Log.i(TAG, "Stop player1..");boolean iRet = stream_player_1_.StopPlayer();if (!iRet) {Log.e(TAG, "Call StopPlayer failed..");return;}stream_player_1_.try_release();btn_playback1.setText("开始播放1");SetViewVisibility(surface_view_1_);} else {Log.i(TAG, "Start playback stream1++");int play_buffer = 0;int is_using_tcp = 0;if(!stream_player_1_.OpenPlayerHandle(playback_url_1_, play_buffer, is_using_tcp))return;stream_player_1_.SetView(surface_view_1_);boolean is_mute = false;boolean iPlaybackRet = stream_player_1_.StartPlayer(isHardwareDecoder, is_enable_hardware_render_mode, is_mute);if (!iPlaybackRet) {Log.e(TAG, "Call StartPlayer failed..");return;}btn_playback1.setText("停止播放1");}}
}

对应的OpenPlayerHandle()实现如下:

/** LibPlayerWrapper.java.java* Author: https://daniusdk.com*/
public boolean OpenPlayerHandle(String playback_url, int play_buffer, int is_using_tcp) {if (check_native_handle())return true;if(!isValidRtspOrRtmpUrl(playback_url))return false;long handle = lib_player_.SmartPlayerOpen(application_context());if (0==handle) {Log.e(TAG, "sdk open failed!");return false;}lib_player_.SetSmartPlayerEventCallbackV2(handle, new EventHandleV2());lib_player_.SmartPlayerSetBuffer(handle, play_buffer);// set report download speed(默认2秒一次回调 用户可自行调整report间隔)lib_player_.SmartPlayerSetReportDownloadSpeed(handle, 1, 4);boolean isFastStartup = true;lib_player_.SmartPlayerSetFastStartup(handle, isFastStartup ? 1 : 0);//设置RTSP超时时间int rtsp_timeout = 10;lib_player_.SmartPlayerSetRTSPTimeout(handle, rtsp_timeout);//设置RTSP TCP/UDP模式自动切换int is_auto_switch_tcp_udp = 1;lib_player_.SmartPlayerSetRTSPAutoSwitchTcpUdp(handle, is_auto_switch_tcp_udp);lib_player_.SmartPlayerSaveImageFlag(handle, 1);// It only used when playback RTSP stream..lib_player_.SmartPlayerSetRTSPTcpMode(handle, is_using_tcp);lib_player_.DisableEnhancedRTMP(handle, 0);lib_player_.SmartPlayerSetUrl(handle, playback_url);set(handle);return true;
}

对应的开始播放、停止播放设计:

/** LibPlayerWrapper.java* Author: https://daniusdk.com*/
public boolean StartPlayer(boolean is_hardware_decoder, boolean is_enable_hardware_render_mode, boolean is_mute) {if (is_playing()) {Log.e(TAG, "already playing, native_handle:" + get());return false;}SetPlayerParam(is_hardware_decoder, is_enable_hardware_render_mode, is_mute);int ret = lib_player_.SmartPlayerStartPlay(get());if (ret != OK) {Log.e(TAG, "call StartPlay failed, native_handle:" + get() + ", ret:" + ret);return false;}write_lock_.lock();try {this.is_playing_ = true;} finally {write_lock_.unlock();}Log.i(TAG, "call StartPlayer OK, native_handle:" + get());return true;
}public boolean StopPlayer() {if (!check_native_handle())return false;if (!is_playing()) {Log.w(TAG, "it's not playing, native_handle:" + get());return false;}boolean is_need_call = false;write_lock_.lock();try {if (this.is_playing_) {this.is_playing_ = false;is_need_call = true;}} finally {write_lock_.unlock();}if (is_need_call)lib_player_.SmartPlayerStopPlay(get());return true;
}

录像设计:

/** SmartPlayer.java* Author: https://daniusdk.com*/
class ButtonRecorder1Listener implements View.OnClickListener {public void onClick(View v) {if (stream_player_1_.is_recording()) {Log.i(TAG, "Stop recorder1..");boolean iRet = stream_player_1_.StopRecorder();if (!iRet) {Log.e(TAG, "Call StopRecorder failed..");return;}stream_player_1_.try_release();btn_recorder1.setText("开始录像1");} else {Log.i(TAG, "Start recorder stream1++");int play_buffer = 0;int is_using_tcp = 0;if(!stream_player_1_.OpenPlayerHandle(playback_url_1_, play_buffer, is_using_tcp))return;stream_player_1_.ConfigRecorderParam(recDir, 400, 1, 1, 1);boolean iRecRet = stream_player_1_.StartRecorder();if (!iRecRet) {Log.e(TAG, "Call StartRecorder failed..");return;}btn_recorder1.setText("停止录像1");}}
}

录像参数配置选项:

/** LibPlayerWrapper.java* Author: https://daniusdk.com*/
public boolean ConfigRecorderParam(String rec_dir, int file_max_size, int is_transcode_aac,int is_record_video, int is_record_audio) {if(!check_native_handle())return false;if (null == rec_dir || rec_dir.isEmpty())return false;int ret = lib_player_.SmartPlayerCreateFileDirectory(rec_dir);if (ret != 0) {Log.e(TAG, "Create record dir failed, path:" + rec_dir);return false;}if (lib_player_.SmartPlayerSetRecorderDirectory(get(), rec_dir) != 0) {Log.e(TAG, "Set record dir failed , path:" + rec_dir);return false;}if (lib_player_.SmartPlayerSetRecorderFileMaxSize(get(),file_max_size) != 0) {Log.e(TAG, "SmartPlayerSetRecorderFileMaxSize failed.");return false;}lib_player_.SmartPlayerSetRecorderAudioTranscodeAAC(get(), is_transcode_aac);// 更细粒度控制录像的, 一般情况无需调用lib_player_.SmartPlayerSetRecorderVideo(get(), is_record_video);lib_player_.SmartPlayerSetRecorderAudio(get(), is_record_audio);return true;
}

开始录像、结束录像:

/** LibPlayerWrapper.java* Author: https://daniusdk.com*/
public boolean StartRecorder() {if (is_recording()) {Log.e(TAG, "already recording, native_handle:" + get());return false;}int ret = lib_player_.SmartPlayerStartRecorder(get());if (ret != OK) {Log.e(TAG, "call SmartPlayerStartRecorder failed, native_handle:" + get() + ", ret:" + ret);return false;}write_lock_.lock();try {this.is_recording_ = true;} finally {write_lock_.unlock();}Log.i(TAG, "call SmartPlayerStartRecorder OK, native_handle:" + get());return true;
}public boolean StopRecorder() {if (!check_native_handle())return false;if (!is_recording()) {Log.w(TAG, "it's not recording, native_handle:" + get());return false;}boolean is_need_call = false;write_lock_.lock();try {if (this.is_recording_) {this.is_recording_ = false;is_need_call = true;}} finally {write_lock_.unlock();}if (is_need_call)lib_player_.SmartPlayerStopRecorder(get());return true;
}

总结

做RTSP播放器容易,做个可以稳定用于实际场景的低延迟RTSP播放器,真的非常困难,首先,RTSP协议本身的复杂度,如果不涉及底层协议栈,只是开源的项目编译调试小修小改,遇到问题,很难处理。还有就是网络环境的不确定性,视频解码和播放的复杂性,视频同步问题的复杂性及考虑因素。最后在跨平台开发的挑战,不同操作系统和设备以及处理不同硬件特性,都需要考虑。以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【环境搭建】更换电脑后的开发环境怎么重建

目录 🍸前言 🍻一、系统配置检查 🍺二、开发环境搭建 🍹三、章末 🍸前言 小伙伴们大家好,这次文章跟技术没有关联,因为最近刚更换了装备,开发环境啥的残缺不全,也不能…

Java基础:面向对象编程5

1 Java内部类 1.1 概念 在 Java 中,内部类是指定义在另一个类内部或方法内部的类。内部类可以分为以下几种类型: 成员内部类局部内部类匿名内部类静态内部类 1.2 成员内部类 定义:成员内部类是最常见的内部类,它定义在外部类…

深度解析 Redis 存储结构及其高效性背后的机制

目录 1. Redis 存储结构存储结构存储转换 2. 字典实现数据结构冲突处理负载因子 3. 扩容扩容步骤影响与优化 4. 缩容缩容步骤优化策略 5. 渐进式 Rehash**渐进式 Rehash 的工作原理**Rehash 规则优势 6. SCAN 命令SCAN 的实现原理遍历顺序避免重复和遗漏使用场景 7. 过期&#…

电子商务网站维护技巧:保持WordPress、主题和插件的更新

在这个快节奏的数字时代,维护一个电子商务网站的首要任务之一是保持WordPress、主题和插件的最新状态。过时的软件不仅可能导致功能故障,还可能带来安全风险。本文将深入探讨如何有效地更新和维护您的WordPress网站,以确保其安全性和性能。 …

工业物联网关-ModbusTCP

Modbus-TCP模式把网关视作Modbus从端设备,主端设备可以通过Modbus-TCP协议访问网关上所有终端设备。用户可以自定义多条通道,每条通道可以配置为TCP Server或者TCP Slave。注意,该模式需要指定采集通道,采集通道可以是串口和网口通…

简述微服务高可用之Sentinel、Seate

简述微服务高可用之Sentinel、Seate使用 下文主要讲述使用sentinel,如何降级限流熔断及如何使用seata管理分布式事务 sentinel服务端安装与使用 1、下载 进入https://github.com/alibaba/Sentinel/releases 根据你的需求进行下载对应版本 我这里是JDK17 下载的1.8.8版本&am…

【数据结构与算法】链表(上)

记录自己所学&#xff0c;无详细讲解 无头单链表实现 1.项目目录文件 2.头文件 Slist.h #include <stdio.h> #include <assert.h> #include <stdlib.h> struct Slist {int data;struct Slist* next; }; typedef struct Slist Slist; //初始化 void SlistI…

【C#】WPF MVVM 简单示例代码

1. 目录结构 2. 代码 2.1 DelegateCommand.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input;namespace MVVSSample.Commands {class DelegateCommand : ICommand{publ…

信息安全工程师(52)网络安全审计系统组成与类型

前言 网络安全审计系统是一种用于监控、分析和报告网络环境中安全事件的系统。其组成与类型均体现了对网络安全性的全面考虑和细致划分。 一、网络安全审计系统的组成 网络安全审计系统一般由以下几个关键部分组成&#xff1a; 审计数据采集系统&#xff1a;负责采集被审计系统…

shell案例之一键部署kafka

Shell案例之一键部署kafka 一、案例问题 &#xff08;1&#xff09;Kafka是用Java编写的&#xff0c;需要配置jdk环境变量 &#xff08;2&#xff09;Kafka配置文件数目多 &#xff08;3&#xff09;命令安装繁琐 二、案例分析&#xff1a; &#xff08;1&#xff09;检查…

elementUI,设置日期,只能选择过去的和今天的日期

在 el-date-picker 组件中加&#xff1a;:picker-options"pickerOptions" <el-form-item label"票据生成日期&#xff1a;"> <el-date-picker v-model"date1" type"daterange" range-separator"至" value-format&…

chatgpt搭建大模型技术知识解读与总结

搭建大型语言模型&#xff08;如ChatGPT&#xff09;的技术知识涉及多个领域&#xff0c;包括机器学习、自然语言处理&#xff08;NLP&#xff09;、深度学习、数据处理等。下面是一些关键概念和步骤的总结&#xff1a; ### 1. **基础知识** #### a. **自然语言处理 (NLP)** …

基于Qt/QChart实现折线图和散点图的绘制示例程序解析

1. 项目简介 本文讲解的是一个基于Qt框架的QChart模块实现的折线图与散点图结合的绘制程序。程序通过自定义类LineChartWithGradient实现折线图、散点图以及带有渐变填充的区域图&#xff0c;最终形成一个美观的数据可视化效果。 2. 类构造函数 LineChartWithGradient::LineC…

天锐绿盾VS Ping32数据安全新选择,用户体验分享

随着网络威胁日益严重&#xff0c;如何保护个人和企业的网络安全成为了一个迫在眉睫的问题。天锐绿盾和Ping32作为市场上两款备受欢迎的网络安全软件&#xff0c;各自拥有独特的特点和功能。本文将对这两款软件进行深入的使用体验分享&#xff0c;帮助用户做出最佳选择。 防护性…

Docker 拉取镜像时配置可用镜像源(包含国内可用镜像源)

文章目录 写在前面一、Docker 官方源二、更换Docker 国内可用镜像源 &#xff08;推荐使用&#xff09;参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04&#xff0c;docker-27.3.1 一、Docker 官方源 打开 /etc/docker/daemon.json文件&#xff1a; sudo gedit …

3.Three.js程序基本框架结构和API说明

Three.js程序基本框架结构和API说明 1.基本框架结构代码 一个基本的Three.js程序&#xff0c;基本都需要设置场景、渲染器、相机、灯光等等通用操作&#xff0c;因而我们可以把Three.js基本程序框架进行整理&#xff0c;如下。其中&#xff0c;我们可以用Three.js提供的Orbit…

JAVA 中的克隆对象

克隆对象就是复制一个一模一样的对象&#xff0c;但是复制出来的对象和原对象不是同一个对象&#xff0c;是两个对象&#xff0c;只不过复制过来的对象和原对象除了内存地址之外&#xff0c;其它的属性一模一样。 在超类 Object 中有一个 clone() 方法&#xff1a; protected…

NC 单据模板自定义项 设置参照(自定义参照)

NC 单据模板自定义项 设置参照&#xff08;自定义参照&#xff09; 如图下图&#xff0c;NC 单据模板自定义项 设置参照&#xff1a; 1、选择需要设置参照的自定义字段&#xff0c;选择高级属性页签&#xff0c;在类型设置中&#xff0c;数据类型选择参照信息&#xff0c;即bd…

Ubuntu-Ubuntu22.04下Anacodna3的qmake和Qt的qmake冲突问题

Ubuntu22.04下Anacodna3的qmake和Qt的qmake冲突问题 一、问题描述二、原因分析三、解决办法 一、问题描述 Ubuntu22.04下Anacodna3的qmake和Qt的qmake冲突问题 zhyzhy-HP:~/Sources/mpv-examples/libmpv/qt$ make g -c -pipe -g -Wall -Wextra -D_REENTRANT -fPIC -DQT_WIDGET…

python 基础笔记(其实有点内容的)

print(math.gamma(n)) # 求 (n-1) 的阶乘 数值, 数值计算 format(50, “b”) bin(50)[2:]&#xff0c; 这个“b” 就代表的是 binary format(14, ‘b’) ------> ‘1110’ 去除 0b 去掉前导零 str(000001) # 只适合python2.x ‘1’ “00000001”.lstrip(“0”) # python3…