Android平台GB28181设备接入模块开发填坑指南
技术背景
为什么要开发Android平台GB28181设备接入模块?这个问题不再赘述,在做Android平台GB28181客户端的时候,媒体数据这块,我们已经有了很好的积累,因为在此之前,我们就开发了非常成熟的RTMP推送、轻量级RTSP服务、录像模块、针对音视频的对接处理单元。这让我们在做Android平台GB28181设备接入模块的时候,可以有更多的精力在信令交互和国标平台对接。
好多开发者会觉得,GB28181设备接入模块有啥好做的?不就找个开源的SIP信令,视频编码ps打包下投递到国标平台就好了吗?
事实上,当回头看看开发的功能时,就会觉得,一两个月的东西,仅就可以作为项目交付或demo使用,并不会有多大的商业价值,因为需要解决的问题实在太多了。
- [视频格式]H.264/H.265(Android H.265硬编码);
- [音频格式]G.711 A律、AAC;
- [音量调节]Android平台采集端支持实时音量调节;
- [H.264硬编码]支持H.264特定机型硬编码;
- [H.265硬编码]支持H.265特定机型硬编码;
- [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
- [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
- 支持纯视频、音视频PS打包传输;
- 支持RTP OVER UDP和RTP OVER TCP被动模式(TCP媒体流传输客户端);
- 支持信令通道网络传输协议TCP/UDP设置;
- 支持注册、注销,支持注册刷新及注册有效期设置;
- 支持设备目录查询应答;
- 支持心跳机制,支持心跳间隔、心跳检测次数设置;
- 支持移动设备位置(MobilePosition)订阅和通知;
- 支持语音广播;
- 支持语音对讲;
- 支持历史视音频文件检索;
- 支持历史视音频文件下载;
- 支持历史视音频文件回放;
- 支持云台控制和预置位查询;
- [实时水印]支持动态文字水印、png水印;
- [镜像]Android平台支持前置摄像头实时镜像功能;
- [实时静音]支持实时静音/取消静音;
- [实时快照]支持实时快照;
- [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
- [外部编码前视频数据对接]支持YUV数据对接;
- [外部编码前音频数据对接]支持PCM对接;
- [外部编码后视频数据对接]支持外部H.264数据对接;
- [外部编码后音频数据对接]外部AAC数据对接;
- [扩展录像功能]支持和录像模块组合使用,录像相关功能。
技术实现

[视频格式]H.264/H.265(Android H.265硬编码)
目前GB28181-2022已经明确表示支持H.265,GB28181设备接入这块,如果需要有好的画质,编码算法这块,一定需要做好,Android端除了低分辨率软编外,超过1280*720,一般建议硬编码。
/*** Set Video H.264 HW Encoder, if support HW encoder, it will return 0(设置H.264硬编码)* * @param kbps: the kbps of different resolution.* * @return {0} if successful*/public native int SetSmartPublisherVideoHWEncoder(long handle, int kbps);/*** Set Video H.265(hevc) hardware encoder, if support H.265(hevc) hardware encoder, it will return 0(设置H.265硬编码)** @param kbps: the kbps of different resolution.** @return {0} if successful*/public native int SetSmartPublisherVideoHevcHWEncoder(long handle, int kbps);
硬编码参数设置
/** 设置视频硬编码码率控制模式* @param hw_bitrate_mode: -1表示使用默认值, 不设置也会使用默认值, 0:CQ, 1:VBR, 2:CBR, 3:CBR_FD, 请参考:android.media.MediaCodecInfo.EncoderCapabilities* 注意硬编码和手机硬件有关,多数手机只支持部分码率模式, 另外硬编码设备差异很大,不同设备同一码率控制模式效果可能不一样* @return {0} if successful*/public native int SetVideoHWEncoderBitrateMode(long handle, int hw_bitrate_mode);/** 设置视频硬编码复杂度, 安卓5.0及以上支持* @param hw_complexity: -1表示不设置, 请参考:android.media.MediaCodecInfo.EncoderCapabilities.getComplexityRange() 和 android.media.MediaFormat.KEY_COMPLEXITY* 注意硬编码和手机硬件有关,部分手机可能不支持此设置* @return {0} if successful*/public native int SetVideoHWEncoderComplexity(long handle, int hw_complexity);/** 设置视频硬编码质量, 安卓9及以上支持, 仅当硬编码器码率控制模式(BitrateMode)是CQ(constant-quality mode)时才有效* @param hw_quality: -1表示不设置, 请参考:android.media.MediaCodecInfo.EncoderCapabilities.getQualityRange() 和 android.media.MediaFormat.KEY_QUALITY* 注意硬编码和手机硬件有关,部分手机可能不支持此设置* @return {0} if successful*/public native int SetVideoHWEncoderQuality(long handle, int hw_quality);/** 设置H.264硬编码Profile, 安卓7及以上支持* @param hw_avc_profile: 0表示使用默认值, 0x01: Baseline, 0x02: Main, 0x08: High, 0x10000: ConstrainedBaseline, 0x80000: ConstrainedHigh;* 注意: ConstrainedBaseline 和 ConstrainedHigh 可能多数设备不支持,* H.264推荐使用 High 或者 ConstrainedHigh, 如果您使用的手机硬解码解不了,那还是设置Baseline* 如果设置的Profile硬编码器不支持,应编码器会使用默认值* 具体参考:android.media.MediaCodecInfo.CodecProfileLevel* @return {0} if successful*/public native int SetAVCHWEncoderProfile(long handle, int hw_avc_profile);/** 设置H.264硬编码Level, 这个只有在设置了Profile的情况下才有效, 安卓7及以上支持* @param hw_avc_level: 0表示使用默认值, 0x100: Level3, 0x200: Level3.1, 0x400: Level3.2,* 0x800: Level4, 0x1000: Level4.1, 0x2000: Level4.2,* 0x4000: Level5, 0x8000: Level5.1, 0x10000: Level5.2,* 0x20000: Level6, 0x40000: Level6.1, 0x80000: Level6.2,* 如果设置的level太高硬编码器不支持,SDK内部会做相应调整* 注意: 640*480@25fps最小支持的是Level3, 720p最小支持的是Level3.1, 1080p最小支持的是Level4* 具体参考:android.media.MediaCodecInfo.CodecProfileLevel* @return {0} if successful*/public native int SetAVCHWEncoderLevel(long handle, int hw_avc_level);/** 设置视频硬编码最大码率, 安卓没有相关文档说明, 所以不建议设置,* @param hw_max_bitrate: 每秒最大码率, 单位bps* @return {0} if successful*/public native int SetVideoHWEncoderMaxBitrate(long handle, long hw_max_bitrate);
[音频格式]G.711 A律、AAC
/*** Set audio encoder type(设置音频编码类型)* * @param type: if with 1:AAC, if with 2: SPEEX, if with 3: PCMA* * @return {0} if successful*/public native int SmartPublisherSetAudioCodecType(long handle, int type);/*** Set audio encoder bit-rate(设置音频编码码率), 当前只对AAC编码有效** @param kbit_rate: 码率(单位是kbps), 如果是0的话将使用默认码率, 必须大于等于0** @return {0} if successful*/public native int SmartPublisherSetAudioBitRate(long handle, int kbit_rate);
[音量调节]Android平台采集端支持实时音量调节
/*** 设置输入音量, 这个接口一般不建议调用, 在一些特殊情况下可能会用, 一般不建议放大音量** @param index: 一般是0和1, 如果没有混音的只用0, 有混音的话, 0,1分别设置音量** @param volume: 音量,默认是1.0,范围是[0.0, 5.0], 设置成0静音, 1音量不变** @return {0} if successful*/public native int SmartPublisherSetInputAudioVolume(long handle, int index, float volume);
[软硬编码参数配置]支持gop间隔、帧率、bit-rate设置,支持软编码profile、软编码速度、可变码率设置
/*** Set software encode vbr mode(软编码可变码率).** <pre>please set before SmartPublisherStart while after SmartPublisherOpen.</pre>** is_enable_vbr: if 0: NOT enable vbr mode, 1: enable vbr** video_quality: vbr video quality, range with (1,50), default 23** vbr_max_kbitrate: vbr max encode bit-rate(kbps)** @return {0} if successful*/public native int SmartPublisherSetSwVBRMode(long handle, int is_enable_vbr, int video_quality, int vbr_max_kbitrate);/*** Set gop interval(设置I帧间隔)** <pre>please set before SmartPublisherStart while after SmartPublisherOpen.</pre>** gopInterval: encode I frame interval, the value always > 0** @return {0} if successful*/public native int SmartPublisherSetGopInterval(long handle, int gopInterval);/*** Set software encode video bit-rate(设置视频软编码bit-rate)** <pre>please set before SmartPublisherStart while after SmartPublisherOpen.</pre>** avgBitRate: average encode bit-rate(kbps)* * maxBitRate: max encode bit-rate(kbps)** @return {0} if successful*/public native int SmartPublisherSetSWVideoBitRate(long handle, int avgBitRate, int maxBitRate);/*** Set fps(设置帧率)** <pre>please set before SmartPublisherStart while after SmartPublisherOpen.</pre>** fps: the fps of video, range with (1,25).** @return {0} if successful*/public native int SmartPublisherSetFPS(long handle, int fps);/*** Set software video encoder profile(设置视频编码profile).** <pre>please set before SmartPublisherStart while after SmartPublisherOpen.</pre>** profile: the software video encoder profile, range with (1,3).* * 1: baseline profile* 2: main profile* 3: high profile** @return {0} if successful*/public native int SmartPublisherSetSWVideoEncoderProfile(long handle, int profile);/*** Set software video encoder speed(设置视频软编码编码速度)* * <pre>please set before SmartPublisherStart while after SmartPublisherOpen.</pre>* * @param speed: range with(1, 6), the default speed is 6. * * if with 1, CPU is lowest.* if with 6, CPU is highest.* * @return {0} if successful*/public native int SmartPublisherSetSWVideoEncoderSpeed(long handle, int speed);
信令通道网络传输协议TCP/UDP设置
gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");
支持注册、注销,支持注册刷新及注册有效期设置
private int gb28181_reg_expired_ = 3600; // 注册有效期时间最小3600秒// GB28181配置
gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);@Override
public void ntsRegisterOK(String dateString) {Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));
}@Override
public void ntsRegisterTimeout() {Log.e(TAG, "ntsRegisterTimeout");
}@Override
public void ntsRegisterTransportError(String errorInfo) {Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));
}
支持心跳机制,支持心跳间隔、心跳检测次数设置
private int gb28181_heartbeat_interval_ = 20; // 心跳间隔GB28181默认是60, 目前调整到20秒
private int gb28181_heartbeat_count_ = 3; // 心跳间隔3次失败,表示和服务器断开了@Override
public void ntsOnHeartBeatException(int exceptionCount, String lastExceptionInfo) {Log.e(TAG, "ntsOnHeartBeatException heart beat timeout count reached, count:" + exceptionCount+", exception info:" + (lastExceptionInfo!=null?lastExceptionInfo:""));// 停止信令, 然后重启handler_.postDelayed(new Runnable() {@Overridepublic void run() {Log.i(TAG, "gb28281_heart_beart_timeout");record_executor_.cancel_tasks();stopRecordDownloads(true);stopPlaybacks(true);stopAudioPlayer();destoryRTPReceiver();gb_broadcast_source_id_ = null;gb_broadcast_target_id_ = null;btnGB28181AudioBroadcast.setText("GB28181语音广播");btnGB28181AudioBroadcast.setEnabled(false);stopGB28181Stream();destoryRTPSender();if (gb28181_agent_ != null) {gb28181_agent_.terminateAllAudioBroadcasts(true);gb28181_agent_.terminateAllPlays(true);Log.i(TAG, "gb28281_heart_beart_timeout sip stop");gb28181_agent_.stop();String local_ip_addr = IPAddrUtils.getIpAddress(context_);if (local_ip_addr != null && !local_ip_addr.isEmpty() ) {Log.i(TAG, "gb28281_heart_beart_timeout get local ip addr: " + local_ip_addr);gb28181_agent_.setLocalAddress(local_ip_addr);}record_executor_.cancel_tasks();initPlaybacks(null);initRecordDownloads(null);Log.i(TAG, "gb28281_heart_beart_timeout sip start");gb28181_agent_.start();}}},0);
}
支持移动设备位置(MobilePosition)订阅和通知
com.gb.ntsignalling.Device gb_device = new com.gb.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,"宇宙","火星1","火星", true);if (mLongitude != null && mLatitude != null) {com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();device_pos.setTime(mLocationTime);device_pos.setLongitude(mLongitude);device_pos.setLatitude(mLatitude);gb_device.setPosition(device_pos);gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报
}@Override
public void ntsOnDevicePositionRequest(String deviceId, int interval) {handler_.postDelayed(new Runnable() {@Overridepublic void run() {getLocation(context_);Log.v(TAG, "ntsOnDevicePositionRequest, deviceId:" + this.device_id_ + ", Longitude:" + mLongitude+ ", Latitude:" + mLatitude + ", Time:" + mLocationTime);if (mLongitude != null && mLatitude != null) {com.gb.ntsignalling.DevicePosition device_pos = new com.gb.ntsignalling.DevicePosition();device_pos.setTime(mLocationTime);device_pos.setLongitude(mLongitude);device_pos.setLatitude(mLatitude);if (gb28181_agent_ != null ) {gb28181_agent_.updateDevicePosition(device_id_, device_pos);}}}private String device_id_;private int interval_;public Runnable set(String device_id, int interval) {this.device_id_ = device_id;this.interval_ = interval;return this;}}.set(deviceId, interval),0);
}
支持语音广播和语音对讲
@Override
public void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID) {handler_.postDelayed(new Runnable() {@Overridepublic void run() {Log.i(TAG, "ntsOnAudioBroadcastPlay, fromFromUserName:" + command_from_user_name_+ " FromUserNameAtDomain:" + command_from_user_name_at_domain_+ " sourceID:" + source_id_ + ", targetID:" + target_id_);stopAudioPlayer();destoryRTPReceiver();if (gb28181_agent_ != null ) {String local_ip_addr = IPAddrUtils.getIpAddress(context_);boolean is_tcp = true; // 考虑到跨网段, 默认用TCP传输rtp包rtp_receiver_handle_ = lib_player_.CreateRTPReceiver(0);if (rtp_receiver_handle_ != 0 ) {lib_player_.SetRTPReceiverTransportProtocol(rtp_receiver_handle_, is_tcp?1:0);lib_player_.SetRTPReceiverIPAddressType(rtp_receiver_handle_, 0);if (0 == lib_player_.CreateRTPReceiverSession(rtp_receiver_handle_, 0) ) {int local_port = lib_player_.GetRTPReceiverLocalPort(rtp_receiver_handle_);boolean ret = gb28181_agent_.inviteAudioBroadcast(command_from_user_name_,command_from_user_name_at_domain_,source_id_, target_id_, "IP4", local_ip_addr, local_port, is_tcp?"TCP/RTP/AVP":"RTP/AVP");if (!ret ) {destoryRTPReceiver();btnGB28181AudioBroadcast.setText("GB28181语音广播");}else {btnGB28181AudioBroadcast.setText("GB28181语音广播呼叫中");}} else {destoryRTPReceiver();btnGB28181AudioBroadcast.setText("GB28181语音广播");}}}}private String command_from_user_name_;private String command_from_user_name_at_domain_;private String source_id_;private String target_id_;public Runnable set(String command_from_user_name, String command_from_user_name_at_domain, String source_id, String target_id) {this.command_from_user_name_ = command_from_user_name;this.command_from_user_name_at_domain_ = command_from_user_name_at_domain;this.source_id_ = source_id;this.target_id_ = target_id;return this;}}.set(commandFromUserName, commandFromUserNameAtDomain, sourceID, targetID),0);
}@Override
public void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo) {handler_.postDelayed(new Runnable() {@Overridepublic void run() {Log.i(TAG, "ntsOnInviteAudioBroadcastException, sourceID:" + source_id_ + ", targetID:" + target_id_);destoryRTPReceiver();btnGB28181AudioBroadcast.setText("GB28181语音广播");}private String source_id_;private String target_id_;public Runnable set(String source_id, String target_id) {this.source_id_ = source_id;this.target_id_ = target_id;return this;}}.set(sourceID, targetID),0);
}@Override
public void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID) {handler_.postDelayed(new Runnable() {@Overridepublic void run() {Log.i(TAG, "ntsOnInviteAudioBroadcastTimeout, sourceID:" + source_id_ + ", targetID:" + target_id_);destoryRTPReceiver();btnGB28181AudioBroadcast.setText("GB28181语音广播");}private String source_id_;private String target_id_;public Runnable set(String source_id, String target_id) {this.source_id_ = source_id;this.target_id_ = target_id;return this;}}.set(sourceID, targetID),0);
}
支持历史视音频文件检索、支持历史视音频文件下载和回放
/*** Author: daniusdk.com*/
package com.gb.ntsignalling;public interface GBSIPAgent {void addDownloadListener(GBSIPAgentDownloadListener downloadListener);void removeDownloadListener(GBSIPAgentDownloadListener removeListener);/**响应Invite Download 200 OK*/boolean respondDownloadInviteOK(long id, String deviceId, String startTime, String stopTime, MediaSessionDescription localMediaDescription);/**响应Invite Download 其他状态码*/boolean respondDownloadInvite(int statusCode, long id, String deviceId, String startTime, String stopTime);/** 媒体流发送者在文件下载结束后发Message消息通知SIP服务器回文件已发送完成* notifyType 必须是"121“*/boolean notifyDownloadMediaStatus(long id, String deviceId, String startTime, String stopTime, String notifyType);/**终止Download会话*/void terminateDownload(long id, String deviceId, String startTime, String stopTime, boolean isSendBYE);/**终止所有Download会话*/void terminateAllDownloads(boolean isSendBYE);}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();}
}
支持云台控制和预置位查询
@Override
public void ntsOnDevicePresetQueryCommand(String fromUserName, String fromUserNameAtDomain, String sn, String deviceId) {handler_.postDelayed(new Runnable() {@Overridepublic void run() {Log.i(TAG, "DaniuSDK ntsOnDevicePresetQueryCommand from_user_name:" + from_user_name_ + ", sn:" + sn_ + ", device_id:" + device_id_);List<com.gb.ntsignalling.PresetItem> preset_list = new LinkedList<>();preset_list.add(new com.gb.ntsignalling.PresetItem("1", "Android PreSet1"));preset_list.add(new com.gb.ntsignalling.PresetItem("2", "Android PreSet2"));if (gb28181_agent_ != null )gb28181_agent_.respondDevicePresetQueryCommand(this.from_user_name_, this.from_user_name_at_domain_, this.sn_, this.device_id_, preset_list);}private String from_user_name_;private String from_user_name_at_domain_;private String sn_;private String device_id_;public Runnable set(String from_user_name, String from_user_name_at_domain,String sn, String device_id) {this.from_user_name_ = from_user_name;this.from_user_name_at_domain_ = from_user_name_at_domain;this.sn_ = sn;this.device_id_ = device_id;return this;}}.set(fromUserName, fromUserNameAtDomain, sn, deviceId),0);
}@Override
public void ntsOnDeviceControlPTZCmd(String deviceId, String typeValue) {handler_.postDelayed(new Runnable() {@Overridepublic void run() {Log.i(TAG, "DaniuSDK ntsOnDeviceControlPTZCmd device_id:" + device_id_ + " PTZType:" + ptz_type_);if (null == ptz_type_)return;ptz_type_ = ptz_type_.trim();if (ptz_type_.length() != 16)return;int instruction = hexStringToInt(ptz_type_.substring(6, 8));int combination_code2 = hexStringToInt(ptz_type_.substring(12, 14));//Android平台GB28181设备接入端,针对性的解析处理即可,这里不再赘述private String device_id_;private String ptz_type_;public Runnable set(String device_id, String ptz_type) {this.device_id_ = device_id;this.ptz_type_ = ptz_type;return this;}}.set(deviceId, typeValue),0);}
[实时水印]支持动态文字水印、png水印
watermarkSelctor = (Spinner) findViewById(R.id.watermarkSelctor);final String[] watermarks = new String[]{"图片水印", "全部水印", "文字水印", "不加水印"};ArrayAdapter<String> adapterWatermark = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, watermarks);adapterWatermark.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);watermarkSelctor.setAdapter(adapterWatermark);watermarkSelctor.setSelection(3,true);
watemarkType = 3; //默认不加水印watermarkSelctor.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {@Overridepublic void onItemSelected(AdapterView<?> parent, View view,int position, long id) {watemarkType = position;Log.i(TAG, "[水印类型]Currently choosing: " + watermarks[position] + ", watemarkType: " + watemarkType);if (layer_post_thread_ != null) {layer_post_thread_.enableText(isHasTextWatermark());layer_post_thread_.enablePicture(isHasPictureWatermark());}}@Overridepublic void onNothingSelected(AdapterView<?> parent) {}
});
[实时快照]支持实时快照
class ButtonCaptureImageListener implements View.OnClickListener {@SuppressLint("SimpleDateFormat")public void onClick(View v) {if(isPushingRtmp || isRecording || isRTSPPublisherRunning || isGB28181StreamRunning){String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());String imageFileName = "dn_" + timeStamp; //创建以时间命名的文件名称String imagePath = imageSavePath + "/" + imageFileName + ".png";Log.i(TAG, "imagePath:" + imagePath);libPublisher.SmartPublisherSaveCurImage(publisherHandle, imagePath);}else{Log.e(TAG, "快照失败,请确保在推送、录像或内置RTSP服务发布状态..");}}
}
[降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测
boolean is_noise_suppression = true;
libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_noise_suppression ? 1 : 0);boolean is_agc = false;
libPublisher.SmartPublisherSetAGC(publisherHandle, is_agc ? 1 : 0);int echo_cancel_delay = 0;
libPublisher.SmartPublisherSetEchoCancellation(publisherHandle, 1, echo_cancel_delay);
外部编码前后视频数据对接
- 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型),其中,Android平台前后摄像头数据,或者屏幕数据,或者Unity拿到的数据,均属编码前数据;
- 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);
[扩展录像功能]支持和录像SDK组合使用,录像相关功能
class ButtonStartRecorderListener implements View.OnClickListener {public void onClick(View v) {if (isRecording) {stopRecorder();if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {ConfigControlEnable(true);}btnStartRecorder.setText("实时录像");btnPauseRecorder.setText("暂停录像");btnPauseRecorder.setEnabled(false);isPauseRecording = true;return;}Log.i(TAG, "onClick start recorder..");if (libPublisher == null)return;if (!isPushingRtmp && !isRTSPPublisherRunning&& !isGB28181StreamRunning) {InitAndSetConfig();}ConfigRecorderParam();int startRet = libPublisher.SmartPublisherStartRecorder(publisherHandle);if (startRet != 0) {if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {if (publisherHandle != 0) {long handle = publisherHandle;publisherHandle = 0;libPublisher.SmartPublisherClose(handle);}}Log.e(TAG, "Failed to start recorder.");return;}if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {CheckInitAudioRecorder();ConfigControlEnable(false);}startLayerPostThread();btnStartRecorder.setText("停止录像");isRecording = true;btnPauseRecorder.setEnabled(true);isPauseRecording = true;}
}class ButtonPauseRecorderListener implements View.OnClickListener {public void onClick(View v) {if (isRecording) {if(isPauseRecording){int ret = libPublisher.SmartPublisherPauseRecorder(publisherHandle, 1);if (ret == 0){isPauseRecording = false;btnPauseRecorder.setText("恢复录像");}else if(ret == 3){Log.e(TAG, "Pause recorder failed, please re-try again..");}else{Log.e(TAG, "Pause recorder failed..");}}else{int ret = libPublisher.SmartPublisherPauseRecorder(publisherHandle, 0);if (ret == 0){isPauseRecording = true;btnPauseRecorder.setText("暂停录像");}else if(ret == 3){Log.e(TAG, "Resume recorder failed, please re-try again..");}else{Log.e(TAG, "Resume recorder failed..");}}}}
}
总结
Android平台GB28181设备接入侧模块,如果需要做的更好,上述提到的技术层面的问题解决了还不够,还需要针对各类国标平台适配对接,只有这样,才能更好的为执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等场景服务。
相关文章:
Android平台GB28181设备接入模块开发填坑指南
技术背景 为什么要开发Android平台GB28181设备接入模块?这个问题不再赘述,在做Android平台GB28181客户端的时候,媒体数据这块,我们已经有了很好的积累,因为在此之前,我们就开发了非常成熟的RTMP推送、轻量…...
我叫:希尔排序【JAVA】
1.我兄弟存在的问题 2.毛遂自荐 希尔排序提希尔(Donald Shell)于1959年提出的一种排序算法。 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 希尔排序是基于插入排序的以下两点性质而提出改进方法的&…...
Spring Cloud Gateway 网关跨域问题解决
0、版本说明 Spring Cloud Version:Spring Cloud 2021.0.4 Spring Cloud Gateway Version:3.1.4 Spring Boot Version:2.6.11 1、网关跨域问题说明 关于跨域的相关原理和理论,网上有大量文章对此进行说明,因此博主在这…...
C++局域网从服务器获取已连接用户的列表(linux to linux)
目录 服务器端 代码 客户端 代码解析 服务器端 原理 遇到的阻碍以及解决办法 客户端 原理 遇到的阻碍以及解决办法 运行结果截图 总结 服务器端 代码 #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet…...
c++11新特性篇-可调用对象包装器, 绑定器
可调用对象包装器, 绑定器 可调用对象 可调用对象是指在 C 中能够像函数一样被调用的实体。它包括了多种类型的对象,使得它们能够像函数一样被调用,可以是函数、函数指针、函数对象、Lambda 表达式等。在C中,具有以下特征之一的实体都被认为…...
论文阅读:“Appearance Capture and Modeling of Human Teeth”
文章目录 AbstractIntroductionMethod OverviewTeeth Appearance ModelEnamelDentinGingiva and oral cavity Data AcquisitionImage captureGeometry capture ResultsReferences Abstract 如果要为电影,游戏或其他类型的项目创建在虚拟环境中显示的人类角色&#…...
初学vue3与ts:路由跳转带参数
index-router <!-- 路由跳转 --> <template><div><div class"title-sub flex"><div>1、用router-link跳转带参数id1:</div><router-link to"./link?id1"><button>点我跳转</button>&…...
JAVAEE---多线程
线程安全 这段代码执行结果就就是一个不确定的数,就存在线程安全问题。 为了解决这样的问题我们可以对count进行打包,我们知道count本质上应该是由三个指令完成,我们可以对其打包。 这样的代码结果就是正确的。我们对对象就进行了加锁&#…...
提示工程-Prompt Engineering
提示工程 提示工程 1、概述 Prompt Engineering: 提示工程 通过自然语言(英语、汉语等)来给AI下达指示,从而让AI完成你指定给他的工作的过程都可以称之为提示工程。(面向自然语言编程) 提示词要素 指令&…...
JetLinks设备接入的认识与理解【woodwhales.cn】
为了更好的阅读体验,建议移步至笔者的博客阅读:JetLinks设备接入的认识与理解 1、认识 JetLinks 1.1、官网文档 官网:https://www.jetlinks.cn/ JetLinks 有两个产品:JetLinks-lot和JetLinks-view 官方文档: JetLi…...
机器人开发的选择
喷涂机器人 码垛机器人 纸箱码垛机器人 焊接机器人 跳舞机器人 管道清理机器人 工地巡检机器人 点餐机器人 化工巡检机器人 装箱机器人 安防巡检机器人 迎宾机器人好像有点像软银那个 污水管道检测机器人 大酒店用扫地机器人 家用扫地机器人 工厂用(…...
LeetCode Hot100 102.二叉树的层序遍历
题目: 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 方法:迭代 class Solution {public List<List<Integer>> levelOrder(TreeNode root) {if …...
【Kotlin】类与接口
文章目录 类的定义创建类的实例构造函数主构造函数次构造函数init语句块 数据类的定义数据类定义了componentN方法 继承AnyAny:非空类型的根类型Any?:所有类型的根类型 覆盖方法覆盖属性覆盖 抽象类接口:使用interface关键字函数:funUnit:让…...
Wagtail-基于Python Django的内容管理系统CMS如何实现公网访问
Wagtail-基于Python Django的内容管理系统CMS实现公网访问 文章目录 Wagtail-基于Python Django的内容管理系统CMS实现公网访问前言1. 安装并运行Wagtail1.1 创建并激活虚拟环境 2. 安装cpolar内网穿透工具3. 实现Wagtail公网访问4. 固定的Wagtail公网地址 前言 Wagtail是一个…...
什么是LASSO回归,怎么看懂LASSO回归的结果
随着机器学习的发展,越来越多SCI文章都使用了更多有趣、高效的统计方法来进行分析,LASSO回归就是其中之一。很多小伙伴听说过LASSO,但是对于LASSO是什么,有什么用,怎么才能实现,大家可能一头雾水。今天的文…...
python树长子兄弟链存储结构(孩子兄弟链存储结构)
长子兄弟链存储结构(孩子兄弟链存储结构)解释: 长子兄弟链存储结构是一种树的存储结构,它使用孩子兄弟表示法(也称作左孩子右兄弟表示法)来表示树的结构。这种表示方法主要用于存储一般的树,而不…...
开源和闭源软件对开发的影响
开源软件的优势: 开源性:开源软件允许任何人查看、修改和发布源代码,这促进了代码的共享和集体学习。透明性:开源软件提高了软件的透明度,使用户可以更好地理解软件的工作原理,增加对软件的信任。社区支持…...
centos无法进入系统之原因解决办法集合
前言 可爱的小伙伴们,由于精力有限,暂时整理了两类。如果没有你遇到的问题也没有关系,欢迎底下留言评论或私信,小编看到后第一时间帮助解决 一. Centos 7 LVM xfs文件系统修复 情况1: [sda] Assuming drive cache:…...
【Linux】系统初始化配置
CentOS 7 的虚拟机安装后必须要做的几个操作,记录以下,网络配置修改、yum源安装、基础工具安装: 1、先修改权限,新建普通用户,并授权普通用户apps 的sudo权限; useradd apps password apps visudo apps A…...
使用VC++设计程序对一幅256级灰度图像进行全局固定阈值分割、自适应阈值分割
图像分割–全局固定阈值分割、自适应阈值分割 获取源工程可访问gitee可在此工程的基础上进行学习。 该工程的其他文章: 01- 一元熵值、二维熵值 02- 图像平移变换,图像缩放、图像裁剪、图像对角线镜像以及图像的旋转 03-邻域平均平滑算法、中值滤波算法、…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
