Android GB28181历史视音频回放

  作为GB28181安卓客户端,实时视音频点播是必须支持的功能,对于历史视音频回放功能,不支持的话可以从设备上拷贝录像文件再播放,但有些场景没法拷贝,安卓支持回放还是需要的。

  历史视音频的回放和实时视音频点播信令上很相似,音视频数据都是通过RTP传输,信令回放要处理SIP INFO消息,解析MANSRTSP协议,实现快进、慢放、暂停、停止、位置拖动等远程控制命令。

  GB28181文档详细定义了历史视音频的回放的信令流程, 对于安卓设备端的实现, 需要实现媒体流发送者相关的信令:

1:SIP服务器向安卓设备发送Invite请求,请求中携带SDP信息,SDP中的s字段为“Playback”代表历史回放,u字段代表回放通道ID和回放类型,t字段代表回放时间段,增加y字段描述 SSRC 值,f字段描述媒体参数。

2:安卓设备收到SIP服务器的Invite请求后,回复200OK响应(回复200 OK等最终响应前,也可先回复一个临时响应,比如180 Ringing等),携带SDP消息体, SDP中描述了安卓设备发送媒体流的IP、端口、媒体格式、SSRC字段等内容。

3:SIP服务器收到安卓设备返回的200OK响应后,向安卓设备发送ACK请求,请求中不携带消息体,完成与安卓设备的Invite会话建立过程。

4:安卓设备按Invite SDP中给出的IP地址和端口等信息,发送音视频RTP包(推荐PS RTP包)到媒体服务器。

5:在回放过程中,播放端通过向SIP服务器发送会话内Info+MANSRTSP消息(SIP服务器再转发给安卓设备端)进行回放控制,包括视频的暂停、播放、快放、慢放、随机拖放播放等操作。

6:安卓设备端在文件回放结束后发送会话内Message消息,通知SIP服务器回放已结束(会话内消息请参考RFC3261-12.2 Requests within a Dialog)。

7:SIP服务器收到媒体通知消息后做相应的处理,之后SIP服务器向安卓端发送BYE消息。

8:安卓设备收到BYE消息后回复200OK响应,会话断开,释放相关资源。 

  为方便快速上手代码实现,下面给出信令的具体实例:

  安卓设备收到的INVITE SDP:

v=0
o=64010000041310000137 0 0 IN IP4 192.168.0.193
s=Playback
u=64010000041310000137:0
c=IN IP4 192.168.0.193
t=1698218951 1698219270
m=video 20072 RTP/AVP 96

a=recvonly
a=rtpmap:96 PS/90000
y=1900000005

  s=Playback代表历史回放,SSRC是:1900000005(SSRC第1位为历史或实时媒体流的标识位,0为实时,1为历史).

  安卓设备回复200 OK携带的SDP:

v=0
o=64010000041310000137 0 0 IN IP4 192.168.0.151
s=MyAndroidPlaybackTest
c=IN IP4 192.168.0.151
t=0 0
m=video 36010 RTP/AVP 96
a=rtpmap:96 PS/90000
a=sendonly
y=1900000005 

  回放过程中通过SIP INFO消息+MANSRTSP协议将正常播放速度变为4倍快进播放:  

PLAY RTSP/1.0
CSeq: 110379
Scale: 4.000000

  回放过程中使用SIP INFO消息+MANSRTSP协议把播放位置改为60.27秒的位置(考虑到安卓一般使用微秒或纳秒单位,转换到微秒是:60270000): 

PLAY RTSP/1.0
CSeq: 110392
Range:npt=60.27-

  文件回放结束后安卓设备端发送会话内Message消息: 

<?xml version="1.0" encoding="GB2312"?>
<Notify>
<CmdType>MediaStatus</CmdType>
<SN>1738772385</SN>
<DeviceID>64010000041310000137</DeviceID>
<NotifyType>121</NotifyType>
</Notify>

  通知事件类型是"121", 表示历史媒体文件发送结束。 

  相关实现代码:

/*
* Copyright (C) 1130758427@qq.com. All rights reserved.
*//**
* 部分信令接口
*/package com.gb.ntsignalling;public interface GBSIPAgent {void addPlaybackListener(GBSIPAgentPlaybackListener playbackListener);void removePlaybackListener(GBSIPAgentPlaybackListener playbackListener);/**响应Invite Playback 200 OK*/boolean respondPlaybackInviteOK(long id, String deviceId, String startTime, String stopTime, MediaSessionDescription localMediaDescription);/**响应Invite Playback 其他状态码*/boolean respondPlaybackInvite(int statusCode, long id, String deviceId);/** 媒体流发送者在回放结束后发Message消息通知SIP服务器回放文件已发送完成* notifyType 必须是"121"*/boolean notifyPlaybackMediaStatus(long id, String deviceId, String notifyType);/**终止Playback会话*/void terminatePlayback(long id, String deviceId, boolean isSendBYE);/**终止所有Playback会话*/void terminateAllPlaybacks(boolean isSendBYE);
}/**
* 信令Playback Listener
*/
package com.gb.ntsignalling;public interface GBSIPAgentPlaybackListener {/**收到s=Playback的历史回放Invite*/void ntsOnInvitePlayback(long id, String deviceId, SessionDescription sessionDescription);/**发送Playback invite response 异常*/void ntsOnPlaybackInviteResponseException(long id, String deviceId, int statusCode, String errorInfo);/** 收到CANCEL Playback INVITE请求*/void ntsOnCancelPlayback(long id, String deviceId);/** 收到Ack*/void ntsOnAckPlayback(long id, String deviceId);/** 播放命令*/void ntsOnPlaybackMANSRTSPPlayCommand(long id, String deviceId);/** 暂停命令*/void ntsOnPlaybackMANSRTSPPauseCommand(long id, String deviceId);/** 快进/慢进命令*/void ntsOnPlaybackMANSRTSPScaleCommand(long id, String deviceId, double scale);/** 随机拖动命令*/void ntsOnPlaybackMANSRTSPSeekCommand(long id, String deviceId, double position_sec);/** 停止命令*/void ntsOnPlaybackMANSRTSPTeardownCommand(long id, String deviceId);/** 收到Bye*/void ntsOnByePlayback(long id, String deviceId);/** 不是在收到BYE Message情况下, 终止Playback*/void ntsOnTerminatePlayback(long id, String deviceId);/** Playback会话对应的对话终止, 一般不会触发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发收到这个, 请做相关清理处理*/void ntsOnPlaybackDialogTerminated(long id, String deviceId);
}/**
* 部分JNI接口, rtp ps 打包发送等代码C++实现
*/public class SmartPublisherJniV2 {/*** Open publisher(启动推送实例)** @param ctx: get by this.getApplicationContext()* * @param audio_opt:* if 0: 不推送音频* if 1: 推送编码前音频(PCM)* if 2: 推送编码后音频(aac/pcma/pcmu/speex).* * @param video_opt:* if 0: 不推送视频* if 1: 推送编码前视频(NV12/I420/RGBA8888等格式)* if 2: 推送编码后视频(AVC/HEVC)* if 3: 层叠加模式** <pre>This function must be called firstly.</pre>** @return the handle of publisher instance*/public native long SmartPublisherOpen(Object ctx, int audio_opt, int video_opt,  int width, int height);/*** 设置流类型* @param type: 0:表示 live 流, 1:表示 on-demand 流, SDK默认为0(live流)* 注意: 流类型设置当前仅对GB28181媒体流有效* @return {0} if successful*/public native int SetStreamType(long handle, int type);/*** 投递视频 on demand包, 当前只用于GB28181推送, 注意ByteBuffer对象必须是DirectBuffer** @param codec_id: 编码id, 当前支持H264和H265, 1:H264, 2:H265** @param packet: 视频数据, 包格式请参考H264/H265 Annex B Byte stream format, 例如:*                0x00000001 nal_unit 0x00000001 ...*                H264 IDR: 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....*                H265 IDR: 0x00000001 vps 0x00000001 sps 0x00000001 pps 0x00000001 IDR_nal_unit .... 或 0x00000001 IDR_nal_unit ....** @param offset: 偏移量* @param size: packet size* @param pts_us: 时间戳, 单位微秒* @param is_pts_discontinuity: 是否时间戳间断,0:未间断,1:间断* @param is_key: 是否是关键帧, 0:非关键帧, 1:关键帧* @param codec_specific_data: 可选参数,可传null, 对于H264关键帧包, 如果packet不含sps和pps, 可传0x00000001 sps 0x00000001 pps*                    ,对于H265关键帧包, 如果packet不含vps,sps和pps, 可传0x00000001 vps 0x00000001 sps 0x00000001 pps* @param codec_specific_data_size: codec_specific_data size* @param width: 图像宽, 可传0* @param height: 图像高, 可传0** @return {0} if successful*/public native int PostVideoOnDemandPacketByteBuffer(long handle, int codec_id,ByteBuffer packet, int offset, int size, long pts_us, int is_pts_discontinuity, int is_key,byte[] codec_specific_data, int codec_specific_data_size,int width, int height);/*** 投递音频on demand包, 当前只用于GB28181推送, 注意ByteBuffer对象必须是DirectBuffer** @param codec_id: 编码id, 当前支持PCMA和AAC, 65536:PCMA, 65538:AAC* @param packet: 音频数据* @param offset:packet偏移量* @param size: packet size* @param pts_us: 时间戳, 单位微秒* @param is_pts_discontinuity: 是否时间戳间断,0:未间断,1:间断* @param codec_specific_data: 如果是AAC的话,需要传 Audio Specific Configuration* @param codec_specific_data_size: codec_specific_data size* @param sample_rate: 采样率* @param channels: 通道数** @return {0} if successful*/public native int PostAudioOnDemandPacketByteBuffer(long handle, int codec_id,ByteBuffer packet, int offset, int size, long pts_us, int is_pts_discontinuity,byte[] codec_specific_data, int codec_specific_data_size,int sample_rate, int channels);/*** on demand source完成seek后, 请调用* @return {0} if successful*/public native int OnSeekProcessed(long handle);/*** 启动 GB28181 媒体流** @return {0} if successful*/public native int StartGB28181MediaStream(long handle);/*** 停止 GB28181 媒体流** @return {0} if successful*/public native int StopGB28181MediaStream(long handle);/*** 关闭推送实例,结束时必须调用close接口释放资源** @return {0} if successful*/public native int SmartPublisherClose(long handle);}/**
* Listener部分实现代码
*/public class PlaybackListenerImpl implements com.gb.ntsignalling.GBSIPAgentPlaybackListener {/**收到s=Playback的文件下载Invite*/@Overridepublic void ntsOnInvitePlayback(long id, String deviceId, SessionDescription sdp) {if (!post_task(new PlaybackListenerImpl.OnInviteTask(this.context_, this.is_exit_, this.senders_map_, deviceId, sdp, id))) {Log.e(TAG, "ntsOnInvitePlayback post_task failed, " + RecordSender.make_print_tuple(id, deviceId, sdp.getTime().getStartTime(),  sdp.getTime().getStopTime()));// 这里不发488, 等待事务超时也可以的GBSIPAgent agent = this.context_.get_agent();if (agent != null)agent.respondPlaybackInvite(488, id, deviceId);}}/**发送Playback invite response 异常*/@Overridepublic void ntsOnPlaybackInviteResponseException(long id, String deviceId, int statusCode, String errorInfo) {Log.i(TAG, "ntsOnPlaybackInviteResponseException, status_code:" + statusCode + ", "+ RecordSender.make_print_tuple(id, deviceId) + ",  error_info:" + errorInfo);RecordSender sender = senders_map_.remove(id);if (null == sender)return;PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);if (!post_task(task))task.run();}/** 收到CANCEL Playback INVITE请求*/@Overridepublic void ntsOnCancelPlayback(long id, String deviceId) {Log.i(TAG, "ntsOnCancelPlayback, " + RecordSender.make_print_tuple(id, deviceId));RecordSender sender = senders_map_.remove(id);if (null == sender)return;PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);if (!post_task(task))task.run();}/** 收到Ack*/@Overridepublic void ntsOnAckPlayback(long id, String deviceId) {Log.i(TAG, "ntsOnAckPlayback, "+ RecordSender.make_print_tuple(id, deviceId));RecordSender sender = senders_map_.get(id);if (null == sender) {Log.e(TAG, "ntsOnAckPlayback get sender is null, " + RecordSender.make_print_tuple(id, deviceId));GBSIPAgent agent = this.context_.get_agent();if (agent != null)agent.terminatePlayback(id, deviceId, false);return;}PlaybackListenerImpl.StartTask task = new PlaybackListenerImpl.StartTask(sender, this.senders_map_);if (!post_task(task))task.run();}/** 收到Bye*/@Overridepublic void ntsOnByePlayback(long id, String deviceId) {Log.i(TAG, "ntsOnByePlayback, "+ RecordSender.make_print_tuple(id, deviceId));RecordSender sender = this.senders_map_.remove(id);if (null == sender)return;PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);if (!post_task(task))task.run();}/** 播放命令*/@Overridepublic void ntsOnPlaybackMANSRTSPPlayCommand(long id, String deviceId) {RecordSender sender = this.senders_map_.get(id);if (null == sender) {Log.e(TAG, "ntsOnPlaybackMANSRTSPPlayCommand can not get sender " + RecordSender.make_print_tuple(id, deviceId));return;}sender.post_play_command();Log.i(TAG, "ntsOnPlaybackMANSRTSPPlayCommand " + RecordSender.make_print_tuple(id, deviceId));}/** 暂停命令*/@Overridepublic void ntsOnPlaybackMANSRTSPPauseCommand(long id, String deviceId) {RecordSender sender = this.senders_map_.get(id);if (null == sender) {Log.e(TAG, "ntsOnPlaybackMANSRTSPPauseCommand can not get sender " + RecordSender.make_print_tuple(id, deviceId));return;}sender.post_pause_command();Log.i(TAG, "ntsOnPlaybackMANSRTSPPauseCommand " + RecordSender.make_print_tuple(id, deviceId));}/** 快进/慢进命令*/@Overridepublic void ntsOnPlaybackMANSRTSPScaleCommand(long id, String deviceId, double scale) {if (scale < 0.01) {Log.e(TAG, "ntsOnPlaybackMANSRTSPScaleCommand invalid scale:" + scale  + " " + RecordSender.make_print_tuple(id, deviceId));return;}RecordSender sender = this.senders_map_.get(id);if (null == sender) {Log.e(TAG, "ntsOnPlaybackMANSRTSPScaleCommand can not get sender, scale:" + scale  + " " + RecordSender.make_print_tuple(id, deviceId));return;}sender.post_scale_command(scale);Log.i(TAG, "ntsOnPlaybackMANSRTSPScaleCommand, scale:" + scale + " " + RecordSender.make_print_tuple(id, deviceId));}/** 随机拖动命令*/@Overridepublic void ntsOnPlaybackMANSRTSPSeekCommand(long id, String device_id, double position_sec) {if (position_sec < 0.0) {Log.e(TAG, "ntsOnPlaybackMANSRTSPSeekCommand invalid seek pos:" + position_sec  + ", " + RecordSender.make_print_tuple(id, device_id));return;}RecordSender sender = this.senders_map_.get(id);if (null == sender) {Log.e(TAG, "ntsOnPlaybackMANSRTSPSeekCommand can not get sender " + RecordSender.make_print_tuple(id, device_id));return;}long offset_ms = sender.get_file_start_time_offset_ms();position_sec += (offset_ms/1000.0);sender.post_seek_command(position_sec);Log.i(TAG, "ntsOnPlaybackMANSRTSPSeekCommand seek pos:" + RecordSender.out_point_3(position_sec) + "s, " + RecordSender.make_print_tuple(id, device_id));}/** 停止命令*/@Overridepublic void ntsOnPlaybackMANSRTSPTeardownCommand(long id, String device_id) {CallTerminatePlaybackTask call_terminate_task =  new CallTerminatePlaybackTask(this.context_, id, device_id, true);post_task(call_terminate_task);RecordSender sender = this.senders_map_.remove(id);if (null == sender) {Log.w(TAG, "ntsOnPlaybackMANSRTSPTeardownCommand can not remove sender " + RecordSender.make_print_tuple(id, device_id));return;}Log.i(TAG, "ntsOnPlaybackMANSRTSPTeardownCommand " + RecordSender.make_print_tuple(id, device_id));PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);if (!post_task(task))task.run();}/** 不是在收到BYE Message情况下, 终止Playback*/@Overridepublic void ntsOnTerminatePlayback(long id, String deviceId) {Log.i(TAG, "ntsOnTerminatePlayback, "+ RecordSender.make_print_tuple(id, deviceId));RecordSender sender = this.senders_map_.remove(id);if (null == sender)return;PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);if (!post_task(task))task.run();}/** Playback会话对应的对话终止, 一般不会触发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发收到这个, 请做相关清理处理*/@Overridepublic void ntsOnPlaybackDialogTerminated(long id, String deviceId) {Log.i(TAG, "ntsOnPlaybackDialogTerminated, "+ RecordSender.make_print_tuple(id, deviceId));RecordSender sender = this.senders_map_.remove(id);if (null == sender)return;PlaybackListenerImpl.StopDisposeTask task = new PlaybackListenerImpl.StopDisposeTask(sender);if (!post_task(task))task.run();}
}

  安卓GB28181历史视音频的回放实现代码较多,大体上分为三块,信令部分,rtp打包发送部分,文件检索发送等逻辑,  有些用Java实现有些用C++, 这里为了方便说明信令流程和关键细节, 只给出部分接口定义和实现代码, 如有不清楚的地方请联系qq: 1130758427。

  安卓设备录像文件远程回放实现代码细节较多, 处理起来也繁琐,建议能把录像文件从设备中拷出来播就拷吧。另外回放的音视频传输强烈推荐使用RTP over TCP(具体请参考RFC 4571)。

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

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

相关文章

HTTP/HTTPS、SSL/TLS、WS/WSS 都是什么?

有同学问我&#xff0c;HTTP/HTTPS、SSL/TLS、WS/WSS 这都是些什么&#xff1f;那我们就先从概念说起&#xff1a; HTTP 是超文本传输协议&#xff0c;信息是通过明文传输。HTTPS 是在 HTTP 的基础上信息通过加密后再传输。SSL 是实现 HTTPS 信息传输加密的算法。TLS 是 SSL 的…

基于C语言实现扫雷小游戏

扫雷游戏 1. 扫雷游戏分析和设计1.1 扫雷游戏的功能说明1.2 游戏的分析和设计1.2.1 数据结构的分析 2. 扫雷游戏的代码实现3. 扫雷游戏的扩展 1. 扫雷游戏分析和设计 1.1 扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏 游戏可以通过菜单实现继续玩或者退出游戏 扫雷的棋…

Linux yum 没有可用软件包 fping。 错误:无须任何处理 的解决办法

yum install fping -y 报错解决&#xff1a; [rootcpcs-node-d4n591 ~]# yum install fping -y 已加载插件&#xff1a;fastestmirror Determining fastest mirrors* base: mirrors.aliyun.com* extras: mirrors.aliyun.com* updates: mirrors.aliyun.com base …

React使用富文本CKEditor 5,上传图片并可设置大小

上传图片 基础使用&#xff08;标题、粗体、斜体、超链接、缩进段落、有序无序、上传图片&#xff09; 官网查看&#xff1a;https://ckeditor.com/docs/ckeditor5/latest/installation/integrations/react.html 安装依赖 npm install --save ckeditor/ckeditor5-react cked…

k8s系列文章一:安装指南

前言 k8s是docker的升级版&#xff0c;可用于docker集群配置管理微服务 一、更新ubuntu系统版本 sudo apt update sudo apt upgrade二、添加GPG密钥(阿里源) 尽管我不知道gpg是个什么东西&#xff0c;反正跟着做就完了 curl https://mirrors.aliyun.com/kubernetes/apt/do…

【Echarts】玫瑰饼图数据交互

在学习echarts玫瑰饼图的过程中&#xff0c;了解到三种数据交互的方法&#xff0c;如果对您也有帮助&#xff0c;不胜欣喜。 一、官网教程 https://echarts.apache.org/examples/zh/editor.html?cpie-roseType-simple &#xff08;该教程数据在代码中&#xff09; import *…

elasticsearch无法访问9200端口

近期部署elasticsearch后&#xff0c;启动时发现一直报如下错误: curl: (7) Failed connect to localhost:9200&#xff1b; Connection refused 部署的版本为elasticsearch-7.13.2,排查原因是因为开启了ssl认证。 解决方法: 在/opt/software/elasticsearch-7.13.2/config下…

echarts 画散点图, x周,y周在指定位置标志一下

文章目录 echarts 画散点图&#xff0c; x周&#xff0c;y周在指定位置标志一下示例一例子二示例三 echarts 画散点图&#xff0c; x周&#xff0c;y周在指定位置标志一下 示例一 let scatterData {data: [[[-0.2, -0.6],[0.4, 0.3],[0.1, 0.4],[0.3, 0.5],[0.09, 0.1],[0.7,…

【React】【react-globe.gl】3D Objects效果

目录 想要实现的效果实现过程踩坑安装依赖引入页面 想要实现的效果 示例地址 实现过程 踩坑 示例是通过script引入的依赖&#xff0c;但本人需要在react项目中实现该效果。按照react-globe.gl官方方法引入总是报错 Cant import the named export AmbientLight from non EcmaS…

kibana显示时间数据时的时区问题及时间显示格式设置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

数据中心如何散热?

数据中心的散热是一个非常重要的问题&#xff0c;因为数据中心内运行的服务器、存储设备以及网络设备等都会产生大量的热量&#xff0c;如果不能有效地进行散热&#xff0c;将会导致设备故障和性能下降。下面是一些常见的数据中心散热方法&#xff1a; 空调系统&#xff1a;数据…

JS功能实现

目录 轮播图移动端轮播图按下回车发表评论tab栏切换全选按钮 轮播图 <style>* {box-sizing: border-box;}.slider {width: 560px;height: 400px;overflow: hidden;}.slider-wrapper {width: 100%;height: 320px;}.slider-wrapper img {width: 100%;height: 100%;display:…

【华为OD机试python】非严格递增连续数字序列【2023 B卷|100分】

【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述: 输入一个字符串仅包含大小写字母和数字,求字符串中包含的最长的非严格 递增连续数字序列的长度(比如12234属于非严格递增连续数字序列)。 输入描述: 输入一个字符串仅包含大小写字母和…

重定向-缓冲区

1.重定向 文件描述符对应的分配规则是什么? 尝试用这个代码 关闭0,1&#xff0c;2文件描述符&#xff0c;看看有什么现象&#xff1f;关闭哪个&#xff0c;你打开的文件fd应该就是哪个 结论&#xff1a; 从0下标开始&#xff0c;寻找最小的没有没使用的数组位置&#xff0c;它…

python 双目相机矫正代码

关于双目相机的矫正功能&#xff0c;博主有c和python 的代码片。 python的比较简洁&#xff0c;一目了然。 整理一下放在这里。 需要的人可以自取&#xff0c;希望已经造好的轮子可以帮助大家节省开发时间&#xff0c;去做更多的事情。 如果搬运代码到自己的博客&#xff0c;希…

构建mono-repo风格的脚手架库

前段时间阅读了 https://juejin.cn/post/7260144602471776311#heading-25 这篇文章&#xff1b;本文做一个梳理和笔记&#xff1b; 主要聚焦的知识点如下&#xff1a; 如何搭建脚手架工程如何开发调试如何处理命令行参数如何实现用户交互如何拷贝文件夹或文件如何动态生成文件…

Linux内核input子系统详解

目录 1 input子系统整体架构 2 input子系统驱动框架分析 2.1 怎么添加input_dev 2.2 input_dev和input_handler匹配后&#xff0c;connec函数做了什么 3 input子系统读数据流程 3.1 open输入设备流程 3.2 read读取输入事件流程 4 应用程序读取的输入数据是怎样的 4.1 …

chap认证带客户端IP分配案例

PPP协议两边的网段可以不在同一个网段&#xff0c;因为数据链路帧用0xff表示帧&#xff0c;不用arp&#xff0c;所以可以不同网段。 R1&#xff1a; aaa local-user test password cipher admin local-user test service-type ppp interface Serial4/0/0 link-protocol ppp pp…

Git 标签(Tag)实战:打标签和删除标签的步骤指南

目录 前言使用 Git 打本地和远程标签&#xff08;Tag&#xff09;删除本地和远程 Git 标签&#xff08;Tag&#xff09;开源项目标签&#xff08;Tag&#xff09;实战打标签删除标签 结语开源微服务商城项目前后端分离项目 前言 在开源项目中&#xff0c;版本控制是至关重要的…

在钣金加工领域,迅镭激光切割机广泛使用的原因和优点何在?

激光切割工艺和激光切割设备正在被广泛的板材加工企业逐渐理解并接受&#xff0c;凭借其高效率的加工、高精度的加工、优质的切割断面、三维切割能力等诸多优势&#xff0c;逐步取代了传统的钣金切割设备。 苏州迅镭激光科技有限公司推出的激光切割设备的柔性化程度高&#xff…