​​Android平台GB28181历史视音频文件回放规范解读及技术实现

news2024/11/21 11:00:38

技术背景

在实现GB28181历史视音频文件回放之前,我们已完成了历史视音频文件检索和下载,历史视音频回放,在GB28181平台非常重要,比如执法记录仪等前端设备,默认录像数据存储在前端设备侧,如果需要上传到平台统一保存,除了到工作站拷贝外,还可以通过GB28181的历史视音频文件下载到指挥中心。如果指挥中心需要直接看历史视音频文件,也可以通过GB28181历史视音频回放实现。

GB28181历史视音频文件回放基本要求:

  • 需采用 SIP 协议中的 Invite 方法实现会话连接;
  • 采用SIP扩展协议Info方法的消息体携带视音频回放控制命令;
  • 采用 RTP/RTCP 协议实现媒体传输;
  • 媒体回放控制命令引用MANSRTSP协议中的 PlayPause、Teardown 的请求消息和应答消息;
  • 历史视音频的回放宜支持媒体流保活机制。

基本流程如下:

本文结合Android平台GB28181设备接入侧和GB28181国标平台侧,理下基本流程:

1、GB28181平台侧向Android平台GB28181设备接入侧发送Invite消息,消息头域携带Subject字段,表明点播的视频源ID、发送方媒体流序列号、媒体流接受者ID、接收端媒体流序列号标志等参数。消息中携带SDP信息,s字段为“Playback”代表历史回放,u字段代表回放通道ID和回放类型,t字段代表回放时间段,增加y字段描述SSRC值;

2、Android GB28181设备接入侧收到国标平台侧的Invite请求后,回复200OK,并携带SDP消息体, SDP中描述了安卓设备发送媒体流的IP、端口、媒体格式、SSRC字段等内容;

3、国标平台侧收到Android国标设备侧返回的200OK响应后,向Android国标设备侧发送ACK请求,请求中不携带消息体,完成与Android国标设备侧的Invite会话建立过程;

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

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

6、Android GB28181设备侧在文件回放结束后发送会话内Message消息,通知SIP服务器回放已结束;

7、国标平台侧收到媒体通知消息后做相应的处理,之后国标服务侧向Android国标设备侧发送BYE消息;

8、Android平台GB28181设备侧收到BYE消息后,回复200 OK,会话断开,并释放相关资源。 

这里聊下媒体回放控制命令:

媒体回放控制命令由客户端到服务器的请求消息和由服务器到客户端的应答消息完成,请求和应
答引用 RTSP(IETFRFC2326)协议中的部分请求和应答消息格式。

媒体播放命令:

客户端发送 PLAY 请求消息,请求服务器发送媒体。应支持 Range 头,在 Range 头中给出播放时间范围,播放指定时间段的媒体,时间范围应支持npt、smpte相对时间戳范围。

Range 头取值为“ntp=now-”,不携带Scale头,表示从暂停位置以原倍速恢复播放。

PLAY RTSP/1.0
CSeq: 2
Range: npt= now-

暂停播放命令示例:

PAUSERTSP/1.0
CSeq:1
PauseTime:now

快进慢进命令示例:

PLAYRTSP/1.0
CSeq:3
Scale:2.0

随机拖放命令示例:

PLAYRTSP/1.0
CSeq:4
Range:npt=100-

停止命令:

客户端发送 TEARDOWN 请求消息,停止发送指定流,结束会话,并释放资源。

应答命令:

客户端、服务器端应支持应答命令的状态码200、4xx以及5xx。见IETFRFC2326。

Scale和 Range头域取值范围

Scale头应支持的基本取值为0.25、0.5、1、2、4。
Range 头的值为播放录像起点的相对值,取值范围为 0 到播放录像的终点时间,参数以s为单位,不能为负值。比如Range 头的值为0,则表示从起点开始播放,Range头的值为100,则表示从录像起点后的100s处开始播放,Range 头的取值为now表示从当前位置开始播放。

技术实现

本文以大牛直播SDK的Android平台GB28181设备接入侧为例,目前我们实现的功能如下:
  •  [视频格式]H.264/H.265(Android H.265硬编码);
  •  [音频格式]G.711 A律、AAC;
  •  [音量调节]Android平台采集端支持实时音量调节;
  •  [H.264硬编码]支持H.264特定机型硬编码;
  •  [H.265硬编码]支持H.265特定机型硬编码;
  •  [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
  •  [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
  •  支持横屏、竖屏推流;
  •  Android平台支持后台service推送屏幕(推送屏幕需要5.0+版本);
  • 支持纯视频、音视频PS打包传输;
  • 支持RTP OVER UDP和RTP OVER TCP被动模式(TCP媒体流传输客户端);
  • 支持信令通道网络传输协议TCP/UDP设置;
  • 支持注册、注销,支持注册刷新及注册有效期设置;
  • 支持设备目录查询应答;
  • 支持心跳机制,支持心跳间隔、心跳检测次数设置;
  • 支持移动设备位置(MobilePosition)订阅和通知;
  •  适用国家标准:GB/T 28181—2016;
  • 支持语音广播;
  • 支持语音对讲;
  • 支持历史视音频文件检索;
  • 支持历史视音频文件下载;
  • 支持历史视音频文件回放;
  • 支持云台控制和预置位查询;
  •  [实时水印]支持动态文字水印、png水印;
  •  [镜像]Android平台支持前置摄像头实时镜像功能;
  •  [实时静音]支持实时静音/取消静音;
  •  [实时快照]支持实时快照;
  •  [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
  •  [外部编码前视频数据对接]支持YUV数据对接;
  •  [外部编码前音频数据对接]支持PCM对接;
  •  [外部编码后视频数据对接]支持外部H.264数据对接;
  •  [外部编码后音频数据对接]外部AAC数据对接;
  •  [扩展录像功能]支持和录像模块组合使用,录像相关功能。

信令交互示例

Android平台GB28181设备侧,收到录像检索:
    <?xml version="1.0" encoding="GB2312"?>
    <Query>
      <CmdType>RecordInfo</CmdType>
      <SN>564849544</SN>
      <DeviceID>34020000001380000001</DeviceID>
      <StartTime>2023-11-05T06:00:00</StartTime>
      <EndTime>2023-11-05T12:00:00</EndTime>
      <Type>all</Type>
    </Query>

国标平台侧列表显示检索到的录像文件信息:

Android平台GB28181设备侧收到国标平台侧的Invite:

    v=0
    o=34020000001380000001 0 0 IN IP4 192.168.0.108
    s=Playback
    u=34020000001380000001:0
    c=IN IP4 192.168.0.108
    t=1699159500 1699161303
    m=video 30014 RTP/AVP 96 97 98 99
    a=recvonly
    a=rtpmap:96 PS/90000
    a=rtpmap:97 MPEG4/90000
    a=rtpmap:98 H264/90000
    a=rtpmap:99 H265/90000
    y=1200000006

Android平台GB28181设备侧回复国标平台侧:

    v=0
    o=34020000011310000039 0 0 IN IP4 192.168.0.104
    s=Playback
    c=IN IP4 192.168.0.104
    t=0 0
    m=video 55584 RTP/AVP 96
    a=rtpmap:96 PS/90000
    a=filesize:199500000
    a=sendonly
    y=1200000006

如需2倍速快进播放:

    PLAY RTSP/1.0
    CSeq: 785427390
    Scale: 2.000000

Android平台GB28181设备侧发送会话内Message消息,通知时间类型为“121”,表示历史媒体文件发送结束:

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

接口实现

/*
* Author: daniusdk.com
*/

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
     */
    @Override
    public 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 异常
     */
    @Override
    public 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请求
     */
    @Override
    public 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
     */
    @Override
    public 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
     */
    @Override
    public 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();
    }
 
    /*
     * 播放命令
     */
    @Override
    public 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));
    }
 
    /*
     * 暂停命令
     */
    @Override
    public 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));
    }
 
    /*
     * 快进/慢进命令
     */
    @Override
    public 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));
    }
 
    /*
     * 随机拖动命令
     */
    @Override
    public 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));
    }
 
    /*
     * 停止命令
     */
    @Override
    public 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
     */
    @Override
    public 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,才可能会出发
    收到这个, 请做相关清理处理
    */
    @Override
    public 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();
    }
}

总结

Android平台GB28181历史视音频文件回放,除了上述信令交互外,还需要处理RTP打包发送等,相对其他功能实现更复杂,感兴趣的开发者,可以尝试看看。

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

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

相关文章

光模块厂家如何实现千兆和万兆的大规模量产

随着网络需求的不断增长&#xff0c;千兆光模块和万兆光模块成为了网络通信中不可或缺的组件。但是&#xff0c;如何实现这些高速光模块的量产却是厂家们面临的难题。本文将介绍千兆光模块和万兆光模块的生产工艺差异和技术挑战&#xff0c;并探讨厂家如何实现千兆和万兆的大规…

JWT简介 JWT结构 JWT示例 前端添加JWT令牌功能 后端程序

目录 1. JWT简述 1.1 什么是JWT 1.2 为什么使用JWT 1.3 JWT结构 1.4 验证过程 2. JWT示例 2.1 后台程序 2.2 前台加入jwt令牌功能 1. JWT简述 1.1 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7…

44A751101-G01 469-P1-HI-A20-E 489-P5-LO-A20

44A751101-G01 469-P1-HI-A20-E 489-P5-LO-A20 在整合IIoT技术的同时&#xff0c;IT/oT融合实施团队将继续就OT网络与公司网络的隔离、OT网络与互联网的隔离、站点和流程级别的最低权限访问控制以及跨站点通信限制做出决策。与此同时&#xff0c;网络安全领域的专业知识正在发…

C++使用serial串口通信 + ROS2程序示例

目录 一、通信协议二、串口调试工具三、serial库的使用3.1 安装serial3.2 serial的使用3.3 绑定端口 四、串口程序示例 串行接口 &#xff08;Serial Interface&#xff09;简称串口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方式的扩展接口&#xff0c…

纷享销客获评中小企业数字化转型优质服务商

近日&#xff0c;纷享销客成功入选长沙市工信局评定的【中小企业数字化转型优质服务商】&#xff0c;专业服务实力得到官方认可&#xff01; 今年6月&#xff0c;财政部、工信部联合印发《关于开展中小企业数字化转型城市试点工作的通知》&#xff0c;长沙市成功入选首批中小企…

【2021研电赛】基于ADRC的双轴反作用轮自平衡杆

本作品介绍参与极术社区的有奖征集|分享研电赛作品扩大影响力&#xff0c;更有重磅电子产品免费领取! 论文题目&#xff1a; 基于ADRC的双轴反作用轮自平衡杆 Dual-axis reaction wheel self-balancing rod based on ADRC 参赛单位&#xff1a; 广西科技大学 队伍名称&#x…

使用VMware安装centos虚拟机

下载资源 VMware下载&#xff1a; vmware下载地址 centos镜像下载地址&#xff1a; 下载地址 创建虚拟机 根据流程进行安装&#xff0c; 其中所设定的内存和磁盘大小&#xff0c;可根据需求来进行设置。 需要注意的是&#xff1a; 所定义的磁盘大小&#xff0c; 不会直接…

Java系列之:字符串的截取及分割 split() 和 substring()

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 1、字符串的截取 split() 1.1 使用说明 1.…

群晖7.2版本安装Jellyfin

一、添加三方套件库 打开桌面【套件中心】&#xff0c;点击右上角的【设置】-【套件来源】-【新增】&#xff0c;添加矿神库 名称&#xff1a;我不是矿神 位置&#xff1a;https://spk7.imnks.com/ 二、安装Jellyfin 【套件中心】-搜索【Jellyfin】-【安装套件】 集显版群…

LoRaWAN通信协议物联网应用

LoRaWAN&#xff08;Long Range Wide Area Network&#xff0c;长距离广域网&#xff09;是由LoRa联盟推出的一种低功耗广域网标准&#xff0c;定义了网络的通讯协议和系统架构。该标准提供智能设备间的互联互通服务&#xff0c;无需复杂配置&#xff0c;便于用户、开 发者和企…

[Kettle] Excel输入

Excel文件采用表格的形式&#xff0c;数据显示直观&#xff0c;操作方便 Excel文件采用工作表存储数据&#xff0c;一个文件有多张不同名称的工作表&#xff0c;分别存放相同字段或不同字段的数据 数据源 物理成绩(Kettle数据集2).xls https://download.csdn.net/download/H…

SQL审计是什么意思?目的是什么?有什么好处?

很多刚入行的运维小伙伴对于SQL审计不是很了解&#xff0c;不知道其是什么意思&#xff1f;使用SQL审计的目的是什么&#xff1f;使用SQL审计的好处有哪些&#xff1f;这里我们大家就来一起聊聊&#xff0c;仅供参考哈&#xff01; SQL审计是什么意思&#xff1f; 【回答】&…

研发管理用什么软件?

研发管理用什么软件 研发管理用的软件有&#xff1a;1、JIRA&#xff1b;2、Confluence&#xff1b;3、彩虹PDM软件。彩虹PDM软件 是由南宁市二零二五科技有限公司 自主研发&#xff0c;为用户提供“产品全生命周期管理解决方案”。产品结构管理、BOD管理、零部件管理、工艺管理…

Go 定时任务实现

定时任务简介 定时任务是指按照预定的时间间隔或特定时间点自动执行的计划任务或操作。这些任务通常用于自动化重复性的工作&#xff0c;以减轻人工操作的负担&#xff0c;提高效率。在计算机编程和应用程序开发中&#xff0c;定时任务是一种常见的编程模式&#xff0c;用于周…

【Nginx篇】Nginx轻松上手

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

解决Docker启动之npm版本不兼容问题

报错内容&#xff1a; npm WARN read-shrinkwrap This version of npm is compatible with lockfileVersion1, but package-lock.json was generated for lockfileVersion2. Ill try to do my best with it! npm WARN tar ENOENT: no such file or directory, open /home/wvp-…

2023北京1024城市开发者聚会总结

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【LeetCode:2586. 统计范围内的元音字符串数 | 模拟】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

华为ICT——第六章:深度学习和卷积神经网络/详篇

目录 1&#xff1a;深度学习卷积的重要概念&#xff1a; 2&#xff1a;CNN核心思想——局部感知&#xff1a; CNN核心思想——参数共享&#xff1a; 3&#xff1a;卷积层的功能&#xff1a; 4&#xff1a;不同深度的卷积层提取的特征&#xff1a; 5&#xff1a;卷积效果——…

docker常用命令解析+Mysql基本操作和语法+Navicat的基本使用及常见报错

一、docker常用命令 1.查看docker容器的列表 语法&#xff1a;docker ps 2. 查看容器里的内容 语法&#xff1a;docker exec -it 容器名 容器内执行的命令 例子&#xff1a;docker exec -it mysql bash 含义&#xff1a;在mysql容器里&#xff0c;启动一个bash shell。 进…