如何在Android上实现RTSP服务器
技术背景
在Android上实现RTSP服务器确实是一个不太常见的需求,因为Android平台主要是为客户端应用设计的。在一些内网场景下,我们更希望把安卓终端或开发板,作为一个IPC(网络摄像机)一样,对外提供个拉流的rtsp url,然后把摄像头麦克风甚至屏幕采集的数据,共享出去,轻量级RTSP的设计理念脱颖而出。
轻量级RTSP服务设计初衷,就是避免用户单独部署RTSP或者RTMP服务,实现本地的音视频数据(如摄像头、麦克风),编码后,汇聚到内置RTSP服务,对外提供可供拉流的RTSP URL,轻量级RTSP服务,适用于内网环境下,对并发要求不高的场景,支持H.264/H.265,支持RTSP鉴权、单播、组播模式,考虑到单个服务承载能力,我们支持同时创建多个RTSP服务,并支持获取当前RTSP服务会话连接数。

如何实现RTSP服务器
如果你找不到合适的库,或者需要更高级的功能,你可以考虑编写自己的RTSP服务器:
- 了解RTSP协议:首先,你需要深入了解RTSP协议的工作原理,包括其消息格式、会话管理和流控制机制。
- 网络编程:在Android上,你可以使用Java的Socket API来处理网络通信,也可以直接底层实现,然后对上提供jni的接口。你需要编写代码来监听端口、接收RTSP请求、解析请求并发送响应。
- 媒体处理:RTSP服务器需要能够捕获、编码和传输媒体数据。你可以使用Android的Camera或Camera2的API来捕获视频,并使用FFmpeg或Android的MediaCodec API来实现音视频数据编码。
- 多线程和并发:RTSP服务器需要处理多个并发客户端连接。你可以使用Java的线程或并发API来管理这些连接。
功能设计
一个完整的RTSP服务,需要设计的功能如下:
- [视频格式]H.264/H.265(Android H.265硬编码);
- [音频格式]G.711 A律、AAC;
- 协议:RTSP;
- [音量调节]Android平台采集端支持实时音量调节;
- [H.264硬编码]支持H.264特定机型硬编码;
- [H.265硬编码]支持H.265特定机型硬编码;
- [音视频]支持纯音频/纯视频/音视频;
- [摄像头]支持采集过程中,前后摄像头实时切换;
- 支持帧率、关键帧间隔(GOP)、码率(bit-rate)设置;
- [实时水印]支持动态文字水印、png水印;
- [实时快照]支持实时快照;
- [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
- [外部编码前视频数据对接]支持YUV数据对接;
- [外部编码前音频数据对接]支持PCM对接;
- [外部编码后视频数据对接]支持外部H.264、H.265数据对接;
- [外部编码后音频数据对接]外部AAC数据对接;
- [扩展录像功能]支持和录像SDK组合使用,录像相关功能。
- 支持RTSP端口设置;
- 支持RTSP鉴权用户名、密码设置;
- 支持获取当前RTSP服务会话连接数;
- 支持Android 5.1及以上版本。
接口设计
| Android内置轻量级RTSP服务SDK接口详解 | ||
| 调用描述 | 接口 | 接口描述 |
| SmartRTSPServerSDK | ||
| 初始化RTSP Server | InitRtspServer | Init rtsp server(和UnInitRtspServer配对使用,即便是启动多个RTSP服务,也只需调用一次InitRtspServer,请确保在OpenRtspServer之前调用) |
| 创建一个rtsp server | OpenRtspServer | 创建一个rtsp server,返回rtsp server句柄 |
| 设置端口 | SetRtspServerPort | 设置rtsp server 监听端口, 在StartRtspServer之前必须要设置端口 |
| 设置鉴权用户名、密码 | SetRtspServerUserNamePassword | 设置rtsp server 鉴权用户名和密码, 这个可以不设置,只有需要鉴权的再设置 |
| 获取rtsp server当前会话数 | GetRtspServerClientSessionNumbers | 获取rtsp server当前的客户会话数, 这个接口必须在StartRtspServer之后再调用 |
| 启动rtsp server | StartRtspServer | 启动rtsp server |
| 停止rtsp server | StopRtspServer | 停止rtsp server |
| 关闭rtsp server | CloseRtspServer | 关闭rtsp server |
| UnInit rtsp server | UnInitRtspServer | UnInit rtsp server(和InitRtspServer配对使用,即便是启动多个RTSP服务,也只需调用一次UnInitRtspServer) |
| SmartRTSPServerSDK供Publisher调用的接口 | ||
| 设置rtsp的流名称 | SetRtspStreamName | 设置rtsp的流名称 |
| 给要发布的rtsp流设置rtsp server | AddRtspStreamServer | 给要发布的rtsp流设置rtsp server, 一个流可以发布到多个rtsp server上,rtsp server的创建启动请参考OpenRtspServer和StartRtspServer接口 |
| 清除设置的rtsp server | ClearRtspStreamServer | 清除设置的rtsp server |
| 启动rtsp流 | StartRtspStream | 启动rtsp流 |
| 停止rtsp流 | StopRtspStream | 停止rtsp流 |
调用逻辑
以大牛直播SDK的Android平台Camera2对接为例,先初始化RTSP Server,启动RTSP服务,然后发布RTSP流即可,如果需要停止RTSP服务,那么先停止RTSP流,再停止RTSP服务即可:

/** MainActivity.java* Author: daniusdk.com* WeChat: xinsheng120*/
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);...context_ = this.getApplicationContext();libPublisher = new SmartPublisherJniV2();libPublisher.InitRtspServer(context_); //和UnInitRtspServer配对使用,即便是启动多个RTSP服务,也只需调用一次InitRtspServer,请确保在OpenRtspServer之前调用
}
启动、停止RTSP服务:
//启动/停止RTSP服务
class ButtonRtspServiceListener implements View.OnClickListener {public void onClick(View v) {if (isRTSPServiceRunning) {stopRtspService();btnRtspService.setText("启动RTSP服务");btnRtspPublisher.setEnabled(false);isRTSPServiceRunning = false;return;}Log.i(TAG, "onClick start rtsp service..");rtsp_handle_ = libPublisher.OpenRtspServer(0);if (rtsp_handle_ == 0) {Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");} else {int port = 8554;if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");}if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {Log.i(TAG, "启动rtsp server 成功!");} else {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");}btnRtspService.setText("停止RTSP服务");btnRtspPublisher.setEnabled(true);isRTSPServiceRunning = true;}}
}
stopRtspService()实现如下:
//停止RTSP服务
private void stopRtspService() {if(!isRTSPServiceRunning){return;}if (libPublisher != null && rtsp_handle_ != 0) {libPublisher.StopRtspServer(rtsp_handle_);libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;}
}
发布、停止RTSP流:
//发布/停止RTSP流
class ButtonRtspPublisherListener implements View.OnClickListener {public void onClick(View v) {if (stream_publisher_.is_rtsp_publishing()) {stopRtspPublisher();btnRtspPublisher.setText("发布RTSP流");btnGetRtspSessionNumbers.setEnabled(false);btnRtspService.setEnabled(true);return;}Log.i(TAG, "onClick start rtsp publisher..");InitAndSetConfig();String rtsp_stream_name = "stream1";stream_publisher_.SetRtspStreamName(rtsp_stream_name);stream_publisher_.ClearRtspStreamServer();stream_publisher_.AddRtspStreamServer(rtsp_handle_);if (!stream_publisher_.StartRtspStream()) {stream_publisher_.try_release();Log.e(TAG, "调用发布rtsp流接口失败!");return;}startAudioRecorder();startLayerPostThread();btnRtspPublisher.setText("停止RTSP流");btnGetRtspSessionNumbers.setEnabled(true);btnRtspService.setEnabled(false);}
}
stopRtspPublisher()实现如下:
//停止发布RTSP流
private void stopRtspPublisher() {stream_publisher_.StopRtspStream();stream_publisher_.try_release();if (!stream_publisher_.is_publishing())stopAudioRecorder();
}
其中,InitAndSetConfig()实现如下,通过调研SmartPublisherOpen()接口,生成推送实例句柄。
/** MainActivity.java* Author: daniusdk.com*/
private void InitAndSetConfig() {if (null == libPublisher)return;if (!stream_publisher_.empty())return;Log.i(TAG, "InitAndSetConfig video width: " + video_width_ + ", height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_);int audio_opt = 1;long handle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3, video_width_, video_height_);if (0==handle) {Log.e(TAG, "sdk open failed!");return;}Log.i(TAG, "publisherHandle=" + handle);int fps = 25;int gop = fps * 3;initialize_publisher(libPublisher, handle, video_width_, video_height_, fps, gop);stream_publisher_.set(libPublisher, handle);
}
对应的initialize_publisher()实现如下,设置软硬编码、帧率、关键帧间隔等。
private boolean initialize_publisher(SmartPublisherJniV2 lib_publisher, long handle, int width, int height, int fps, int gop) {if (null == lib_publisher) {Log.e(TAG, "initialize_publisher lib_publisher is null");return false;}if (0 == handle) {Log.e(TAG, "initialize_publisher handle is 0");return false;}if (videoEncodeType == 1) {int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, true);Log.i(TAG, "h264HWKbps: " + kbps);int isSupportH264HWEncoder = lib_publisher.SetSmartPublisherVideoHWEncoder(handle, kbps);if (isSupportH264HWEncoder == 0) {lib_publisher.SetNativeMediaNDK(handle, 0);lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBRlib_publisher.SetVideoHWEncoderQuality(handle, 39);lib_publisher.SetAVCHWEncoderProfile(handle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High// lib_publisher.SetAVCHWEncoderLevel(handle, 0x200); // Level 3.1// lib_publisher.SetAVCHWEncoderLevel(handle, 0x400); // Level 3.2// lib_publisher.SetAVCHWEncoderLevel(handle, 0x800); // Level 4lib_publisher.SetAVCHWEncoderLevel(handle, 0x1000); // Level 4.1 多数情况下,这个够用了//lib_publisher.SetAVCHWEncoderLevel(handle, 0x2000); // Level 4.2// lib_publisher.SetVideoHWEncoderMaxBitrate(handle, ((long)h264HWKbps)*1300);Log.i(TAG, "Great, it supports h.264 hardware encoder!");}} else if (videoEncodeType == 2) {int kbps = LibPublisherWrapper.estimate_video_hardware_kbps(width, height, fps, false);Log.i(TAG, "hevcHWKbps: " + kbps);int isSupportHevcHWEncoder = lib_publisher.SetSmartPublisherVideoHevcHWEncoder(handle, kbps);if (isSupportHevcHWEncoder == 0) {lib_publisher.SetNativeMediaNDK(handle, 0);lib_publisher.SetVideoHWEncoderBitrateMode(handle, 1); // 0:CQ, 1:VBR, 2:CBRlib_publisher.SetVideoHWEncoderQuality(handle, 39);// libPublisher.SetVideoHWEncoderMaxBitrate(handle, ((long)hevcHWKbps)*1200);Log.i(TAG, "Great, it supports hevc hardware encoder!");}}boolean is_sw_vbr_mode = true;//H.264 software encoderif (is_sw_vbr_mode) {int is_enable_vbr = 1;int video_quality = LibPublisherWrapper.estimate_video_software_quality(width, height, true);int vbr_max_kbps = LibPublisherWrapper.estimate_video_vbr_max_kbps(width, height, fps);lib_publisher.SmartPublisherSetSwVBRMode(handle, is_enable_vbr, video_quality, vbr_max_kbps);}if (is_pcma_) {lib_publisher.SmartPublisherSetAudioCodecType(handle, 3);} else {lib_publisher.SmartPublisherSetAudioCodecType(handle, 1);}lib_publisher.SetSmartPublisherEventCallbackV2(handle, new EventHandlerPublisherV2().set(handler_, record_executor_));lib_publisher.SmartPublisherSetSWVideoEncoderProfile(handle, 3);lib_publisher.SmartPublisherSetSWVideoEncoderSpeed(handle, 2);lib_publisher.SmartPublisherSetGopInterval(handle, gop);lib_publisher.SmartPublisherSetFPS(handle, fps);// lib_publisher.SmartPublisherSetSWVideoBitRate(handle, 600, 1200);boolean is_noise_suppression = true;lib_publisher.SmartPublisherSetNoiseSuppression(handle, is_noise_suppression ? 1 : 0);boolean is_agc = false;lib_publisher.SmartPublisherSetAGC(handle, is_agc ? 1 : 0);int echo_cancel_delay = 0;lib_publisher.SmartPublisherSetEchoCancellation(handle, 1, echo_cancel_delay);return true;
}
发布RTSP流成功后,会回调上来可供拉流的RTSP URL:
private static class EventHandlerPublisherV2 implements NTSmartEventCallbackV2 {@Overridepublic void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {switch (id) {...case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:publisher_event = "RTSP服务URL: " + param3;break;}}
}
获取RTSP Session会话数:
//获取RTSP会话数
class ButtonGetRtspSessionNumbersListener implements View.OnClickListener {public void onClick(View v) {if (libPublisher != null && rtsp_handle_ != 0) {int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);PopRtspSessionNumberDialog(session_numbers);}}
}//当前RTSP会话数弹出框
private void PopRtspSessionNumberDialog(int session_numbers) {final EditText inputUrlTxt = new EditText(this);inputUrlTxt.setFocusable(true);inputUrlTxt.setEnabled(false);String session_numbers_tag = "RTSP服务当前客户会话数: " + session_numbers;inputUrlTxt.setText(session_numbers_tag);AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);builderUrl.setTitle("内置RTSP服务").setView(inputUrlTxt).setNegativeButton("确定", null);builderUrl.show();
}
onDestroy() 的时候,调研UnInitRtspServer()即可:
@Override
protected void onDestroy() {Log.i(TAG, "activity destory!");stopAudioRecorder();stopRtspPublisher();stopRtspService();isRTSPServiceRunning = false;stream_publisher_.release();if (libPublisher != null)libPublisher.UnInitRtspServer(); //如已启用内置服务功能(InitRtspServer),调用UnInitRtspServer, 注意,即便是启动多个RTSP服务,也只需调用UnInitRtspServer一次stopLayerPostThread();if (camera2Helper != null) {camera2Helper.release();}super.onDestroy();
}
总结
Android上实现RTSP服务器是一个极具挑战的任务,功能设计这块,除了需要支持接编码前音视频数据外,还需要支持对接编码后音视频数据,并实现本地录像、快照等功能组合使用。需要注意的是,就像海康、大华的摄像头一样,对外的并发,一般限于4-8个,Android设备的性能一般来说,可能不足以处理高负载的RTSP服务器,但是小并发模式下,能稳定的运行,就达到设计初衷了,感兴趣的开发者,可以单独跟单独探讨。
相关文章:
如何在Android上实现RTSP服务器
技术背景 在Android上实现RTSP服务器确实是一个不太常见的需求,因为Android平台主要是为客户端应用设计的。在一些内网场景下,我们更希望把安卓终端或开发板,作为一个IPC(网络摄像机)一样,对外提供个拉流的…...
代理导致的git错误
问题: 今天在clone时出现如下错误: fatal: unable to access https://github.com/NirDiamant/RAG_Techniques.git/: Failed to connect to 127.0.0.1 port 10089 after 2065 ms: Couldnt connect to server真是让人感到奇怪!就在前天&#…...
Ready Go
本文首发在这里 温馨提示 XX年,指的是20XX年,后跟以前、以后之类,均包含本数链接较多,只是想言之有物,已拒绝相同外链,仅看关心的即可已尽量只引用自己的东西,16年后仓库(11/13),2…...
Matlab simulink建模与仿真 第十三章(信号通路库)
参考视频:simulink1.1simulink简介_哔哩哔哩_bilibili 一、信号通路库中的模块概览 1、信号通路组 注:部分模块在第二章中有介绍,本章不再赘述。 2、信号存储和访问组 二、总线分配模块 Bus Assignment模块接受总线作为输入,并…...
Java中接口和抽象类的区别(语法层面的区别、设计理念层面的区别)
文章目录 1. 语法层面的区别1.1 成员属性1.2 成员方法1.3 关系 2. 设计理念层面的区别(重点)3. 举例理解抽象类和接口在设计理念层面的区别3.1 例一:门和警报3.2 例二:招聘3.3 例三:装修房子 4. 总结 1. 语法层面的区别…...
Leetcode面试经典150题-20.有效的括号
给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类型的左括…...
Git常用指令大全详解
Git常用指令大全详解 Git,作为目前最流行的分布式版本控制系统,其强大的功能和灵活性为开发者提供了极大的便利。无论是个人项目还是团队协作,Git都扮演着不可或缺的角色。本文将详细总结Git的常用指令,帮助大家更好地掌握这一工…...
面试真题-TCP的三次握手
TCP的基础知识 TCP头部 面试题:TCP的头部是多大? TCP(传输控制协议)的头部通常是固定的20个字节长,但是根据TCP选项(Options)的不同,这个长度可以扩展。TCP头部包含了许多关键的字…...
LabVIEW多语言支持优化
遇到的LabVIEW多语言支持问题,特别是德文显示乱码以及系统区域设置导致的异常,可能是由编码问题或区域设置不匹配引起的。以下是一些可能的原因及解决方案: 问题原因: 编码问题:LabVIEW内部使用UTF-8编码,但…...
身份证阅读器API模式 VUE Dorado7
VUE 新框架 // 身份证扫描 readIdCard(type) {// 1.连接axios.get(http://localhost:19196/openDevice).then(res > {if (res.data.resultFlag 0) {// 2.读卡axios.get(http://localhost:19196/readCard).then((res) > {if (res.data.resultFlag 0) {// this.$message…...
北京通州自闭症学校推荐:打造和谐学习氛围,助力孩子成长
在北京通州,寻找一所能够全面关注自闭症儿童成长、提供高效康复服务的学校,星贝育园无疑是众多家庭的首选。作为全国知名的广泛性发育障碍全托寄宿制儿童康复训练机构,星贝育园以其专业的康复方法、强大的师资力量和贴心的服务,为…...
openstack之cinder介绍
概念 cinder 为虚拟机提供管理块存储服务。支持的文件系统:lvm、iscsi、nfs、san、RBD 组件构成及功能介绍 cinder api:在控制节点运行,管理服务的接口,被命令行、其他组件调用; cinder scheduler:类似n…...
第k个排列 - 华为OD统一考试(E卷)
2024华为OD机试(E卷D卷C卷)最新题库【超值优惠】Java/Python/C合集 题目描述 给定参数n,从1到n会有n个整数:1,2,3,.,n,这n个数字共有 n!种排列。按大小顺序升序列出所有排列情况,并-一标记,当n3时,所有排列…...
清理C盘缓存,电脑缓存清理怎么一键删除,操作简单的教程
清理C盘缓存是维护电脑性能、释放磁盘空间的重要步骤。以下是一个详细且操作简单的教程,旨在帮助用户通过一键或几步操作完成C盘缓存的清理。 1.使用Windows系统自带工具 磁盘清理 1.打开磁盘清理工具: -按下“WinE”打开文件资源管理器…...
网络安全-ssrf
目录 一、环境 二、漏洞讲解 三、靶场讲解 四、可利用协议 4.1 dict协议 4.2 file协议 4.3 gopher协议 五、看一道ctf题吧(长亭的比赛) 5.1环境 5.2开始测试 编辑 一、环境 pikachu,这里我直接docker拉取的,我只写原…...
c++刷题
17.电话号码的组合 来源于题解思路: 继承 CC14 KiKi设计类继承 #include <iostream> #include <memory> using namespace std; class Shape{ private:int x;int y; };class Rectangle:public Shape { public:Rectangle(int length,int width):Shape…...
艾丽卡的区块链英语小课堂
系列文章目录 复习昨日 文章目录 系列文章目录前言1.opaque2.deduplicates3.references4,intermix5.serializing6.streamline7.robust8.flexibility9.exotic10.nevertheless11. realize12.flavor13.subtract14.attach15.award 前言 欢迎来到艾丽卡的区块链英语小课堂&#x…...
计算机毕业设计 公寓出租系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...
eclipse使用 笔记02
创建一个项目: 【File-->New-->Dynamic Web Project】 进入页面: Project name为项目命名 Target runtime:选择自己所对应的版本 finish创建成功: 创建成功后的删除操作: 创建前端界面: 【注意&a…...
基于C++实现(MFC)职工工作量统计系统
题目:职工工作量统计系统设计 1、问题描述 职工包括姓名、职工号、性别、年龄、所在部门、联系方式等信息。 工作量包括职工号、完成的产品数量等信息。 该设计系统能够对职工的工作量进行统计,并排出名次。注意,一个职工的工作量是可以多次…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
