Android平台GB28181设备接入模块之按需编码和双码流编码

技术背景

我们在做执法记录仪或指挥系统的时候,会遇到这样的情况,大多场景下,我们是不需要把设备端的数据,实时传给国标平台端的,默认只需要本地录像留底,如果指挥中心需要查看前端设备实时数据的时候,发起视频播放请求,设备侧再推送数据到平台侧,如需语音广播,只要发起语音广播(broadcast),GB28181设备接入侧响应,然后发送INVITE请求等,完成语音广播和语音对讲。此外,考虑到设备侧的上行带宽瓶颈,一般来说,本地录像需要尽可能清晰(比如1920*1080分辨率),上传视频数据,传输1280*720分辨率,也就是我们传统意义提到的双码流编码。

技术实现

带着这些问题,以Android平台设备接入模块为例,我们来逐一分析解决:

按需编码

按需编码,只需要Android平台GB28181设备接入端,完成设备到平台的注册(register),然后平台侧发起catalog查询设备,并维持心跳信息,如果订阅了实时位置信息,按照设定间隔,实时上报位置信息即可。

在需要录像或指挥中心需要播放前端设备实时音视频数据的时候,我们才编码音视频数据,这样保证,待机时,最小化的资源占用。

以上图为例,只需点击“启动GB28181”即可,对应的代码实现如下:

    class ButtonGB28181AgentListener implements View.OnClickListener {
        public void onClick(View v) {
            stopAudioPlayer();
            destoryRTPReceiver();

            gb_broadcast_source_id_ = null;
            gb_broadcast_target_id_ = null;
            btnGB28181AudioBroadcast.setText("GB28181语音广播");
            btnGB28181AudioBroadcast.setEnabled(false);

            stopGB28181Stream();
            destoryRTPSender();

            if (null == gb28181_agent_ ) {
                if( !initGB28181Agent() )
                    return;
            }

            if (gb28181_agent_.isRunning()) {
                gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
                gb28181_agent_.stop();
                btnGB28181Agent.setText("启动GB28181");
            }
            else {
                if ( gb28181_agent_.start() ) {
                    btnGB28181Agent.setText("停止GB28181");
                }
            }
        }
    }

 initGB28181Agent()实现如下:

    private boolean initGB28181Agent() {
        if ( gb28181_agent_ != null )
            return  true;

        getLocation(context_);

        String local_ip_addr = IPAddrUtils.getIpAddress(context_);
        Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr);

        if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
            Log.e(TAG, "initGB28181Agent local ip is empty");
            return  false;
        }

        gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
        if ( gb28181_agent_ == null ) {
            Log.e(TAG, "initGB28181Agent create agent failed");
            return false;
        }

        gb28181_agent_.addListener(this);
        gb28181_agent_.addPlayListener(this);
        gb28181_agent_.addAudioBroadcastListener(this);
        gb28181_agent_.addDeviceControlListener(this);
        gb28181_agent_.addQueryCommandListener(this);

        // 必填信息
        gb28181_agent_.setLocalAddress(local_ip_addr);
        gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_domain_);
        gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_);
        //gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_username_, gb28181_sip_password_);

        // 可选参数
        gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_);
        gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");

        // GB28181配置
        gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);

        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); // 设置支持移动位置上报
        }

        gb28181_agent_.addDevice(gb_device);

        if (!gb28181_agent_.createSipStack()) {
            gb28181_agent_ = null;
            Log.e(TAG, "initGB28181Agent gb28181_agent_.createSipStack failed.");
            return  false;
        }

        boolean is_bind_local_port_ok = false;

        // 最多尝试5000个端口
        int try_end_port = gb28181_sip_local_port_base_ + 5000;
        try_end_port = try_end_port > 65536 ?65536: try_end_port;

        for (int i = gb28181_sip_local_port_base_; i < try_end_port; ++i) {
            if (gb28181_agent_.bindLocalPort(i)) {
                is_bind_local_port_ok = true;
                break;
            }
        }

        if (!is_bind_local_port_ok) {
            gb28181_agent_.releaseSipStack();
            gb28181_agent_ = null;
            Log.e(TAG, "initGB28181Agent gb28181_agent_.bindLocalPort failed.");
            return  false;
        }

        if (!gb28181_agent_.initialize()) {
            gb28181_agent_.unBindLocalPort();
            gb28181_agent_.releaseSipStack();
            gb28181_agent_ = null;
            Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
            return  false;
        }

        return true;
    }

注册和心跳异常处理如下:

    @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 :""));
    }

    @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() {
            @Override
            public void run() {
                Log.i(TAG, "gb28281_heart_beart_timeout");

                stopAudioPlayer();
                destoryRTPReceiver();

                if (gb_broadcast_source_id_ != null && gb_broadcast_target_id_ != null && gb28181_agent_ != null)
                    gb28181_agent_.byeAudioBroadcast(gb_broadcast_source_id_, gb_broadcast_target_id_);

                gb_broadcast_source_id_ = null;
                gb_broadcast_target_id_ = null;
                btnGB28181AudioBroadcast.setText("GB28181语音广播");
                btnGB28181AudioBroadcast.setEnabled(false);

                stopGB28181Stream();
                destoryRTPSender();

                if (gb28181_agent_ != null) {
                    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);
                    }

                    Log.i(TAG, "gb28281_heart_beart_timeout sip start");
                    gb28181_agent_.start();
                }
            }

        },0);
    }

ntsOnAckPlay()的时候,我们才开始编码:

   @Override
    public void ntsOnAckPlay(String deviceId) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);

                if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
                    InitAndSetConfig();
                }

                libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);

                //libPublisher.SetGBTCPConnectTimeout(publisherHandle, 10*60*1000);
                //libPublisher.SetGBInitialTCPReconnectInterval(publisherHandle, 1000);
                //libPublisher.SetGBInitialTCPMaxReconnectAttempts(publisherHandle, 3);

                int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
                if (startRet != 0) {

                    if (!isRTSPPublisherRunning && !isPushingRtmp  && !isRecording) {
                        if (publisherHandle != 0) {
                            long handle = publisherHandle;
                            publisherHandle = 0;
                            libPublisher.SmartPublisherClose(handle);
                        }
                    }

                    destoryRTPSender();

                    Log.e(TAG, "Failed to start GB28181 service..");
                    return;
                }

                if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
                    CheckInitAudioRecorder();
                }

                startLayerPostThread();
                isGB28181StreamRunning = true;
            }

            private String device_id_;

            public Runnable set(String device_id) {
                this.device_id_ = device_id;
                return this;
            }

        }.set(deviceId),0);
    }

其中,InitAndSetConfig()完成基础参数设定,比如数据源类型、软硬编码设置、帧率关键帧间隔码率等参数设置:

    private void InitAndSetConfig() {

        int audio_opt = 1;

        int fps = 18;
        int gop = fps * 2;

        Log.i(TAG, "InitAndSetConfig video_width: " + video_width_ + " cur_video_height" + video_height_ + " imageRotationDegree:" + cameraImageRotationDegree_);

        publisherHandle = libPublisher.SmartPublisherOpen(context_, audio_opt, 3,  video_width_, video_height_);

        if (publisherHandle == 0) {
            Log.e(TAG, "sdk open failed!");
            return;
        }

        Log.i(TAG, "publisherHandle=" + publisherHandle);

        if(videoEncodeType == 1)  {
            int h264HWKbps = setHardwareEncoderKbps(true, video_width_, video_height_);
            h264HWKbps = h264HWKbps*fps/25;

            Log.i(TAG, "h264HWKbps: " + h264HWKbps);

            int isSupportH264HWEncoder = libPublisher
                    .SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);

            if (isSupportH264HWEncoder == 0) {
                libPublisher.SetNativeMediaNDK(publisherHandle, 0);
                libPublisher.SetVideoHWEncoderBitrateMode(publisherHandle, 1); // 0:CQ, 1:VBR, 2:CBR
                libPublisher.SetVideoHWEncoderQuality(publisherHandle, 39);
                libPublisher.SetAVCHWEncoderProfile(publisherHandle, 0x08); // 0x01: Baseline, 0x02: Main, 0x08: High

                // libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x200); // Level 3.1
                // libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x400); // Level 3.2
                // libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x800); // Level 4
                libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x1000); // Level 4.1 多数情况下,这个够用了
                //libPublisher.SetAVCHWEncoderLevel(publisherHandle, 0x2000); // Level 4.2

                // libPublisher.SetVideoHWEncoderMaxBitrate(publisherHandle, ((long)h264HWKbps)*1300);

                Log.i(TAG, "Great, it supports h.264 hardware encoder!");
            }
        }
        else if (videoEncodeType == 2) {
            int hevcHWKbps = setHardwareEncoderKbps(false, video_width_, video_height_);
            hevcHWKbps = hevcHWKbps*fps/25;
            Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);

            int isSupportHevcHWEncoder = libPublisher
                    .SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);

            if (isSupportHevcHWEncoder == 0) {
                libPublisher.SetNativeMediaNDK(publisherHandle, 0);
                libPublisher.SetVideoHWEncoderBitrateMode(publisherHandle, 0); // 0:CQ, 1:VBR, 2:CBR
                libPublisher.SetVideoHWEncoderQuality(publisherHandle, 39);

                // libPublisher.SetVideoHWEncoderMaxBitrate(publisherHandle, ((long)hevcHWKbps)*1200);

                Log.i(TAG, "Great, it supports hevc hardware encoder!");
            }
        }

        boolean is_sw_vbr_mode = true;
        if(is_sw_vbr_mode)	//H.264 software encoder
        {
            int is_enable_vbr = 1;
            int video_quality = CalVideoQuality(video_width_, video_height_, true);
            int vbr_max_bitrate = CalVbrMaxKBitRate(video_width_, video_height_);

            libPublisher.SmartPublisherSetSwVBRMode(publisherHandle, is_enable_vbr, video_quality, vbr_max_bitrate);
        }

        if (is_pcma_) {
            libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 3);
        } else {
            libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1);
        }

        libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandlerPublisherV2().set(handler_, recorder_io_executor_));

        libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, 3);

        libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, 2);

        libPublisher.SmartPublisherSetGopInterval(publisherHandle, gop);

        libPublisher.SmartPublisherSetFPS(publisherHandle, fps);

        // libPublisher.SmartPublisherSetSWVideoBitRate(publisherHandle, 600, 1200);

        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);

        libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1);
    }

双码流编码

以采集摄像头采集为例,如果需要双码流编码,采集数据源时,以大分辨率作为采集基准分辨率,如采集1920*1080的,那么如果需要上传实时视频数据的时候,只需要缩放,得到1280*720分辨率的编码数据:

    @Override
    public void onCameraImageData(Image image) {
        if (null == libPublisher)
            return;

        if (isPushingRtmp || isRTSPPublisherRunning || isGB28181StreamRunning || isRecording) {
            if (0 == publisherHandle)
                return;

            Image.Plane[] planes = image.getPlanes();

            int w = image.getWidth(), h = image.getHeight();
            int y_offset = 0, u_offset = 0, v_offset = 0;

            Rect crop_rect = image.getCropRect();
            if (crop_rect != null && !crop_rect.isEmpty()) {

                w = crop_rect.width();
                h = crop_rect.height();
                y_offset += crop_rect.top * planes[0].getRowStride() + crop_rect.left * planes[0].getPixelStride();
                u_offset += (crop_rect.top / 2) * planes[1].getRowStride() + (crop_rect.left / 2) * planes[1].getPixelStride();
                v_offset += (crop_rect.top / 2) * planes[2].getRowStride() + (crop_rect.left / 2) * planes[2].getPixelStride();
                ;

                // Log.i(TAG, "crop w:" + w + " h:" + h + " y_offset:"+ y_offset + " u_offset:" + u_offset + " v_offset:" + v_offset);
            }

            int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
            scale_filter_mode = 3;

            int rotation_degree = cameraImageRotationDegree_;
            if (rotation_degree < 0) {
                Log.i(TAG, "onCameraImageData rotation_degree < 0, may need to set orientation_ to 0, 90, 180 or 270");
                return;
            }

            if (!post_image_lock_.tryLock()) {
                Log.i(TAG, "post_image_lock_.tryLock return false");
                return;
            }

            try {
                if (publisherHandle != 0) {
                    if (isPushingRtmp || isRTSPPublisherRunning || isGB28181StreamRunning || isRecording) {
                        libPublisher.PostLayerImageYUV420888ByteBuffer(publisherHandle, 0, 0, 0,
                                planes[0].getBuffer(), y_offset, planes[0].getRowStride(),
                                planes[1].getBuffer(), u_offset, planes[1].getRowStride(),
                                planes[2].getBuffer(), v_offset, planes[2].getRowStride(), planes[1].getPixelStride(),
                                w, h, 0, 0,
                                scale_w, scale_h, scale_filter_mode, rotation_degree);
                    }
                }
            }catch (Exception e) {
                Log.e(TAG, "onCameraImageData Exception:", e);
            }finally {
                post_image_lock_.unlock();
            }
        }
    }

PostLayerImageYUV420888ByteBuffer()设计如下:

    /*
     * SmartPublisherJniV2.java
     * SmartPublisherJniV2
     *
     * Author: https://daniusdk.com
     * Created by DaniuLive on 2015/09/20.
     */	
    /**
	 * 投递层YUV420888图像, 专门为android.media.Image的android.graphics.ImageFormat.YUV_420_888格式提供的接口
	 *
	 * @param index: 层索引, 必须大于等于0
	 *
	 * @param left: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param top: 层叠加的左上角坐标, 对于第0层的话传0
	 *
	 * @param y_plane: 对应android.media.Image.Plane[0].getBuffer()
	 *
	 * @param y_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param y_row_stride: 对应android.media.Image.Plane[0].getRowStride()
	 *
	 * @param u_plane: android.media.Image.Plane[1].getBuffer()
	 *
	 * @param u_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param u_row_stride: android.media.Image.Plane[1].getRowStride()
	 *
	 * @param v_plane: 对应android.media.Image.Plane[2].getBuffer()
	 *
	 * @param v_offset: 图像偏移, 这个主要目的是用来做clip的,一般传0
	 *
	 * @param v_row_stride: 对应android.media.Image.Plane[2].getRowStride()
	 *
	 * @param uv_pixel_stride: 对应android.media.Image.Plane[1].getPixelStride()
	 *
	 * @param width: width, 必须大于1, 且必须是偶数
	 *
	 * @param height: height, 必须大于1, 且必须是偶数
	 *
	 * @param  is_vertical_flip: 是否垂直翻转, 0不翻转, 1翻转
	 *
	 * @param  is_horizontal_flip:是否水平翻转, 0不翻转, 1翻转
	 *
	 * @param  scale_width: 缩放宽,必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_height: 缩放高, 必须是偶数, 0或负数不缩放
	 *
	 * @param  scale_filter_mode: 缩放质量, 传0使用默认速度,可选等级范围是:[1,3],值越大缩放质量越好, 但速度越慢
	 *
	 * @param  rotation_degree: 顺时针旋转, 必须是0, 90, 180, 270, 注意:旋转是在缩放, 垂直/水品反转之后再做, 请留意顺序
	 *
	 * @return {0} if successful
	 */
	public native int PostLayerImageYUV420888ByteBuffer(long handle, int index, int left, int top,
														 ByteBuffer y_plane, int y_offset, int y_row_stride,
												   		 ByteBuffer u_plane, int u_offset, int u_row_stride,
														 ByteBuffer v_plane, int v_offset, int v_row_stride, int uv_pixel_stride,
														 int width, int height, int is_vertical_flip,  int is_horizontal_flip,
														 int scale_width,  int scale_height, int scale_filter_mode,
												   		 int rotation_degree);

上述接口参数,scale_width和scale_height可以指定缩放宽高,甚至如果摄像头采集的方向不对,可以设置rotation_degree接口,来实现视频数据的旋转。

接口参数第一个是实例句柄,如果需要两路编码,势必对应两个推送实例,也就是两个handle,一个用来录像,一个用来gb28181上行数据推送。

需要注意的是,如果需要同时两个实例编码,需要投递数据的时候,两个实例,分别调用PostLayerImageYUV420888ByteBuffer()实现数据源到底层模块的投递。

本地录像操作如下:

    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;
        }
    }

停止录像:

    //停止录像
    private void stopRecorder() {
        if(!isRecording)
            return;

        isRecording = false;

        if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning)
            stopLayerPostThread();

        if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {
            if (audioRecord_ != null) {
                Log.i(TAG, "stopRecorder, call audioRecord_.StopRecording..");

                audioRecord_.Stop();

                if (audioRecordCallback_ != null) {
                    audioRecord_.RemoveCallback(audioRecordCallback_);
                    audioRecordCallback_ = null;
                }

                audioRecord_ = null;
            }
        }

        if (null == libPublisher || 0 == publisherHandle)
            return;

        libPublisher.SmartPublisherStopRecorder(publisherHandle);

        if (!isPushingRtmp && !isRTSPPublisherRunning && !isGB28181StreamRunning) {
            releasePublisherHandle();
        }
    }

技术总结

按需编码,可以只是本地录像或上行数据推送,对应一个实例完成,如果双码流编码,势必需要两个实例,对应不同的编码参数,输出不同的分辨率的H.264/H.265数据。此外,音频数据回调的地方,两个实例也调用音频投递接口,传下去。需要注意的是,两路视频编码,尽管可以硬编码,对设备性能依然提了更高的要求。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

区分能带图

能带结构是目前采用第一性原理&#xff08;从头abinitio&#xff09;计算所得到的常用信息&#xff0c;可用来结合解释金属、半导体和绝缘体的区别。能带可分为价带、禁带和导带三部分&#xff0c;倒带和价带之间的空隙称为能隙。 如果能隙很小或为0 &#xff0c;则固体为金属…

文件或文件夹名称中有空格如何批量去除

平时在工作中会经常碰到文件或文件夹里面有特殊符号&#xff0c;那么要如何批量去除文件名中的特殊符号&#xff1f;其实去符号也不是困难的事&#xff0c;可以使用《文件批量改名高手》对文件名进行批量去除特殊符号&#xff0c;操作步骤如下。 先打开《文件批量改名高手》&a…

vscode设置java -Xmx最大堆内存

如果在vscode中直接运行java程序&#xff0c;想要改下每次运行的最大堆内存&#xff0c;按照如下修改 一、vscode安装java插件 当然前提是vscode在应用管理中已经安装了java语言的插件&#xff0c;Debugger for Java,如下图所示 二、CommandShiftP打开配置搜索框 三、搜索…

电脑C盘空间大小调整 --- 扩容(扩大/缩小)--磁盘分区大小调整/移动

概述&#xff1a; 此方法适合C盘右边没有可分配空间&#xff08;空闲空间&#xff09;的情况&#xff0c;D盘有数据不方便删除D盘分区的情况下&#xff0c;可以使用傲梅分区助手软件进行跨分区调整分区大小&#xff0c;不会损坏数据。反之可直接使用系统的磁盘管理工具进行调整…

树状数组笔记

数组、前缀和、树状数组的区别&#xff1a; 数组&#xff1a;修改某点O&#xff08;1&#xff09;&#xff0c;求区间O&#xff08;n&#xff09; 前缀和&#xff1a;修改某点O&#xff08;n&#xff09;&#xff0c;求区间O&#xff08;1&#xff09; 树状数组&#xff1a;修改…

React Dva项目中.roadhogrc.mock.js直接自动导入mock目录下所有文件方式

上文 React Dva项目中模仿网络请求数据方法 中&#xff0c;我们书写了Dva项目模拟后端数据的方式 但是 我们.roadhogrc.mock.js中的这个处理其实并不好用 我们还需要一个一个的引入 我们可以直接靠一段代码 import fs from fs; import path from path; const mock {} fs.re…

Git 快速入门

在客户端操作之前&#xff0c;需要安装git&#xff0c;可以查看连接→→git的下载安装 一、客户端操作 1.1 界面说明 这边有三个选项&#xff1a; Clone a repository from the Internet... 从互联网复制仓库到本地。 由于Git是一个分布式版本控制软件&#xff0c;中央服务…

后处理材质球:黄金螺旋分割线和参考图

后处理材质球&#xff1a;黄金螺旋分割线和参考图 Begin Object Class/Script/UnrealEd.MaterialGraphNode Name"MaterialGraphNode_0"Begin Object Class/Script/Engine.MaterialExpressionLinearInterpolate Name"MaterialExpressionLinearInterpolate_1&qu…

机器学习深度学习——图像分类数据集

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——softmax回归&#xff08;下&#xff09; &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习…

交换机和终端设备的基本配置

1 IOS访问 1.1 操作系统 所有终端设备和网络设备都需要有操作系统 (OS)。如图所示&#xff0c;操作系统中直接与计算机硬件交互的部分称为内核。与应用程序和用户连接的部分则称为外壳。用户可以使用命令行界面 (CLI) 或图形用户界面 (GUI) 与外壳交互。 使用 CLI 时&#xf…

图像处理之Hough变换检测直线

hough变换-直线检测 一、 前言二、Hough 变换三、直线检测四、代码实现1.hough检测2.画直线代码3.画hough空间代码4.检测结果 一、 前言 霍夫变换是一种特征检测(feature extraction)&#xff0c;被广泛应用在图像分析&#xff08;image analysis&#xff09;、计算机视觉(com…

从风控系统看架构设计原型图分析

目录 一、对架构与架构图的理解 &#xff08;一&#xff09;架构的本质 &#xff08;二&#xff09;软件设计中架构域的划分 &#xff08;三&#xff09;架构图设计 架构图设计的必要性 如何画架构图 二、实践业务架构与产品架构设计 &#xff08;一&#xff09;列出问…

Linux Mint 21.2 “Victoria “现已可供下载

Linux Mint 21.2 “Victoria “发行版今天出现在该项目全球稳定镜像上&#xff0c;这意味着开发者将很快发布官方公告&#xff0c;通知想要下载最新Linux Mint版本的用户。 Linux Mint 21.2从2023年6月21日开始进行公开测试&#xff0c;这给了开发者足够的时间来修复剩余的问题…

文心一言 VS 讯飞星火 VS chatgpt (63)-- 算法导论6.5 2题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;63&#xff09;-- 算法导论6.5 2题 二、试说明 MAX-HEAP-INSERT(A&#xff0c;10)在堆A(15&#xff0c;13&#xff0c;9&#xff0c;5&#xff0c;12&#xff0c;8&#xff0c;7&#xff0c;4&#xff0c;0&#xff0c;6&#xf…

从零开始制作婚礼策划展示小程序

随着移动互联网的发展&#xff0c;小程序已经成为各行各业展示和推广自己的重要工具之一。对于婚礼策划行业来说&#xff0c;制作一个专属的婚礼策划展示小程序&#xff0c;不仅能提升服务的专业性和便利性&#xff0c;还能吸引更多的客户。下面将介绍从零开始制作婚礼策划展示…

解决Element-Plus中Swtich @change自动被触发的问题

如图所示 这个switchChange事件在初始化的时候会被自动触发 烦得很 解决方法 如图所示 第471行 通过判断是否还有其它某元素再往下执行 如果你有其它方法 请你在评论区教我下哈 3q

【梦辛工作室】IF判断优化、责任链模式 IfChain

大家好哇&#xff0c;我是梦辛工作室的灵&#xff0c;在最近的开发中&#xff0c;有许多需要判断的分支处理&#xff0c;且处理内容较多且复杂&#xff0c;代码就容易越写越复杂&#xff0c;导致后期无法继续更新跌打&#xff0c;然后基于这个环境&#xff0c;我用责任链模式写…

leetcode 面试题 判定是否互为字符重排

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;判定是否互为字符重排 思路&#xff1a; 两个字符串的每个字母和数量都相等。那么 s2 一定可以排成 s1 字符串。 代码&#xff1a; bool CheckPermutation(char* s1, char* s2){char hash1[26] {0};char hash2[26] {…

【ICCV2023】Scale-Aware Modulation Meet Transformer

Scale-Aware Modulation Meet Transformer, ICCV2023 论文&#xff1a;https://arxiv.org/abs/2307.08579 代码&#xff1a;https://github.com/AFeng-x/SMT 解读&#xff1a;ICCV2023 &#xff5c; 当尺度感知调制遇上Transformer&#xff0c;会碰撞出怎样的火花&#xff1…

三,创建订单微服务消费者 第三章

4.3 修改pom添加依赖 <dependencies><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--监控--><dependency><groupId&g…