Unity3D下如何实现跨平台低延迟的RTMP、RTSP播放

news2024/11/19 23:37:09

 技术背景

好多开发者,希望我们能探讨下Unity平台RTMP或RTSP直播流数据播放和录制相关的模块,实际上,这块流程我们已经聊过多次,无非就是通过原生的RTMP或者RTSP模块,先从协议层拉取到数据,并解包解码,回调YUV或RGB数据,然后,在Unity创建响应的shader,获取图像数据填充纹理即可,说起来流程很简单,但是每个环节,如果做到极致体验,都非常难。简单来说,多一次拷贝,都会增大性能瓶颈或延迟。

目前,Unity3D下,我们覆盖了以下常用的模块:

  • Windows平台RTMP直播推送模块(采集Unity窗体、摄像头或屏幕);
  • Windows平台轻量级RTSP服务模块(采集Unity窗体、摄像头或屏幕);
  • Windows平台RTMP|RTSP直播播放模块;
  • Linux平台RTMP直播推送模块(采集Unity窗体、Unity声音),也可扩展轻量级RTSP服务模块;
  • Linux平台RTMP|RTSP直播播放模块;
  • Android平台RTMP直播推送模块(采集Unity窗体、麦克风或Unity声音);
  • Android平台轻量级RTSP服务模块(采集Unity窗体、麦克风或Unity声音);
  • Android平台RTMP|RTSP直播播放模块;
  • iOS平台RTMP|RTSP直播播放模块。

下图系Linux平台RTMP播放图,可以看到,延迟非常低。

技术实现

本文以Android平台RTMP、RTSP播放模块为例,介绍下Unity相关接口设置和逻辑处理:

开始播放

    public void Play()
    {
        if (is_running)
        {
            Debug.Log("已经在播放。。");   
            return;
        }

        //获取输入框的url
        string videoUrl = input_url_.text.Trim();

        OpenPlayer();

        if ( player_handle_ == 0 )
            return;

        NT_U3D_Set_Game_Object(player_handle_, game_object_);

        /* ++ 播放前参数配置可加在此处 ++ */
        int is_using_tcp = 0;        //TCP/UDP模式设置
        NT_U3D_SetRTSPTcpMode(player_handle_, is_using_tcp);

        int is_report = 0;
        int report_interval = 1;
        NT_U3D_SetReportDownloadSpeed(player_handle_, is_report, report_interval);  //下载速度回调

        NT_U3D_SetBuffer(player_handle_, play_buffer_time_);                        //设置buffer time

        NT_U3D_SetPlayerLowLatencyMode(player_handle_, is_low_latency_ ? 1 : 0);    //设置是否启用低延迟模式

        NT_U3D_SetMute(player_handle_, is_mute_ ? 1 : 0);                           //是否启动播放的时候静音

        NT_U3D_SetAudioVolume(player_handle_, cur_audio_volume_);                   //设置播放音量

        NT_U3D_SetVideoDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0);          //设置H.264软硬解模式

        NT_U3D_SetVideoHevcDecoderMode(player_handle_, is_hw_decode_ ? 1 : 0);          //设置H.265软硬解模式

        int is_fast_startup = 1;
        NT_U3D_SetFastStartup(player_handle_, is_fast_startup);                     //设置快速启动模式

        int rtsp_timeout = 10;
        NT_U3D_SetRTSPTimeout(player_handle_, rtsp_timeout);                        //设置RTSP超时时间

        int is_auto_switch_tcp_udp = 1;
        NT_U3D_SetRTSPAutoSwitchTcpUdp(player_handle_, is_auto_switch_tcp_udp);    //设置TCP/UDP模式自动切换

        int is_audiotrack = 1;
        NT_U3D_SetAudioOutputType(player_handle_, is_audiotrack);                   //设置音频输出模式: if 0: 自动选择; if with 1: audiotrack模式

        NT_U3D_SetUrl(player_handle_, videoUrl);
        /* -- 播放前参数配置可加在此处 -- */

        int flag = NT_U3D_StartPlay(player_handle_);

        if (flag  == DANIULIVE_RETURN_OK)
        {
            is_need_get_frame_ = true;
            Debug.Log("播放成功");
        }
        else
        {
            is_need_get_frame_ = false;
            Debug.LogError("播放失败");
        }

        is_running = true;  
    }

这里调用了OpenPlayer()设计如下:

    private void OpenPlayer()
    {
        if ( java_obj_cur_activity_ == null )
        {
            Debug.LogError("getApplicationContext is null");
            return;
        }

        player_handle_ = NT_U3D_Open();

        if (player_handle_ != 0)
            Debug.Log("open success");
        else
            Debug.LogError("open fail");
    }

停止播放:

    private void ClosePlayer()
    {
        is_need_get_frame_ = false;
        is_need_init_texture_ = false;
 
        int flag = NT_U3D_StopPlay(player_handle_);
        if (flag == DANIULIVE_RETURN_OK)
        {
            Debug.Log("停止成功");
        }
        else
        {
            Debug.LogError("停止失败");
        }

        flag = NT_U3D_Close(player_handle_);
        if (flag == DANIULIVE_RETURN_OK)
        {
            Debug.Log("关闭成功");
        }
        else
        {
            Debug.LogError("关闭失败");
        }

        player_handle_ = 0;

        NT_U3D_UnInit();

        is_running = false;
        video_width_ = 0;
        video_height_ = 0;
    }

Event状态回调处理:

    /// <summary>
    /// android 传递过来 code
    /// </summary>
    /// <param name="event_message"></param>
    public void onNTSmartEvent(string event_message)
    {
        if (null == event_message || event_message.Length < 1)
            return;

        string[] strs = event_message.Split(',');
        if (null == strs || strs.Length < 6)
            return;

       string player_handle =strs[0];
       string code = strs[1];
       string param1 = strs[2];
       string param2 = strs[3];
       string param3 = strs[4];
       string param4 = strs[5];
        
       Debug.Log("[onNTSmartEvent] code: 0x" + Convert.ToString(Convert.ToInt32(code), 16));

       String player_event = "";

        switch (Convert.ToInt32(code))
        {
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
                player_event = "开始..";
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
                player_event = "连接中..";
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
                player_event = "连接失败..";
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
                player_event = "连接成功..";
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
                player_event = "连接断开..";
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
                player_event = "停止播放..";
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
                player_event = "分辨率信息: width: " + Convert.ToInt32(param1) + ", height: " + Convert.ToInt32(param2);
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
                player_event = "收不到媒体数据,可能是url错误..";
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
                player_event = "切换播放URL..";
                break;

            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
                player_event = "快照: " + param1 + " 路径:" + param3;

                if (Convert.ToInt32(param1) == 0)
                {
                    player_event = "截取快照成功..";
                }
                else
                {
                    player_event = "截取快照失败..";
                }
                break;

            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
                player_event = "[record]开始一个新的录像文件 : " + param3;
                break;
            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
                player_event = "[record]已生成一个录像文件 : " + param3;
                break;

            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
                player_event = "Start_Buffering..";
                break;

            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
                player_event = "Buffering: " + Convert.ToInt32(param1);
                break;

            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
                player_event = "Stop_Buffering..";
                break;

            case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
                player_event = "download_speed:" + param1 + "Byte/s" + ", "
                        + (Convert.ToInt32(param1) * 8 / 1000) + "kbps" + ", " + (Convert.ToInt32(param1) / 1024)
                        + "KB/s";
                break;
        }

        Debug.Log(player_event);

        player_event = null;
        strs = null;
    }

如果想扩展录像,实际上,我们也针对播放端录像做了接口的封装设计,整体接口设计如下:

    /// <summary>
    /// SmartPlayer Unity Interface
    /// Author: daniusdk.com
    /// </summary>

    /// <summary>
    /// Init
    /// </summary>
    public int NT_U3D_Init()
    {
        return player_obj_.Call<int>("Init", java_obj_cur_activity_);
    }


    /// <summary>
    /// 开始
    /// 返回播放句柄
    /// </summary>
    public long NT_U3D_Open()
    {
        return player_obj_.Call<long>("Open");
    }

    /// <summary>
    /// Register Game Object,用于消息传递
    /// </summary>
    public int NT_U3D_Set_Game_Object(long handle, string gameObjectName)
    {
        return player_obj_.Call<int>("SetGameObject", handle, gameObjectName);
    }

    /// <summary>
    /// 设置H.264解码方式 false 软件解码 true 硬件解码 默认为false
    /// </summary>
    /// <param name="isHwDecoder"></param>
    public int NT_U3D_SetVideoDecoderMode(long handle, int isHwDecoder)
    {
        return player_obj_.Call<int>("SetPlayerVideoHWDecoder", handle, isHwDecoder);
    }

    /// <summary>
    /// 设置H.265 解码方式 false 软件解码 true 硬件解码 默认为false
    /// </summary>
    /// <param name="isHevcHwDecoder"></param>
    public int NT_U3D_SetVideoHevcDecoderMode(long handle, int isHevcHwDecoder)
    {
        return player_obj_.Call<int>("SetPlayerVideoHevcHWDecoder", handle, isHevcHwDecoder);
    }

    /// <summary>
    /// 设置音频输出模式: if 0: 自动选择; if with 1: audiotrack模式
    /// </summary>
    /// <param name="use_audiotrack"></param>
    public int NT_U3D_SetAudioOutputType(long handle, int use_audiotrack)
    {
        return player_obj_.Call<int>("SetAudioOutputType", handle, use_audiotrack);
    }

    /// <summary>
    /// 设置播放端缓存大小, 默认200毫秒
    /// </summary>
    /// <param name="buffer"></param>
    public int NT_U3D_SetBuffer(long handle, int buffer)
    {
        return player_obj_.Call<int>("SetBuffer", handle, buffer);
    }

    /// <summary>
    /// 接口可实时调用:设置是否实时静音,1:静音; 0: 取消静音
    /// </summary>
    /// <param name="is_mute"></param>
    public int NT_U3D_SetMute(long handle, int is_mute)
    {
        return player_obj_.Call<int>("SetMute", handle, is_mute);
    }

    /// <summary>
    /// 接口可实时调用:设置播放音量,范围是[0, 100], 0是静音,100是最大音量, 默认是100
    /// </summary>
    /// <param name="audio_volume"></param>
    public int NT_U3D_SetAudioVolume(long handle, int audio_volume)
    {
        return player_obj_.Call<int>("SetAudioVolume", handle, audio_volume);
    }

    /// <summary>
    /// 设置RTSP TCP模式, 1: TCP; 0: UDP
    /// </summary>
    /// <param name="is_using_tcp"></param>
    public int NT_U3D_SetRTSPTcpMode(long handle, int is_using_tcp)
    {
        return player_obj_.Call<int>("SetRTSPTcpMode", handle, is_using_tcp);
    }

    /// <summary>
    /// 设置RTSP超时时间, timeout单位为秒,必须大于0
    /// </summary>
    /// <param name="timeout"></param>
    public int NT_U3D_SetRTSPTimeout(long handle, int timeout)
    {
        return player_obj_.Call<int>("SetRTSPTimeout", handle, timeout);
    }

    /// <summary>
    /// 设置RTSP TCP/UDP自动切换
    /// NOTE: 对于RTSP来说,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式.
    /// 为了方便使用,有些场景下可以开启自动尝试切换开关, 打开后如果udp无法播放,sdk会自动尝试tcp, 如果tcp方式播放不了,sdk会自动尝试udp.
    /// </summary>
    /// <param name="timeout"></param>
    /// timeout:如果设置1的话, sdk将在tcp和udp之间尝试切换播放,如果设置为0,则不尝试切换.
    public int NT_U3D_SetRTSPAutoSwitchTcpUdp(long handle, int is_auto_switch_tcp_udp)
    {
        return player_obj_.Call<int>("SetRTSPAutoSwitchTcpUdp", handle, is_auto_switch_tcp_udp);
    }

    /// <summary>
    /// 设置快速启动该模式,
    /// </summary>
    /// <param name="is_fast_startup"></param>
    public int NT_U3D_SetFastStartup(long handle, int is_fast_startup)
    {
        return player_obj_.Call<int>("SetFastStartup", handle, is_fast_startup);
    }

    /// <summary>
    /// 设置超低延迟模式 false不开启 true开启 默认false
    /// </summary>
    /// <param name="mode"></param>
    public int NT_U3D_SetPlayerLowLatencyMode(long handle, int mode)
    {
        return player_obj_.Call<int>("SetPlayerLowLatencyMode", handle, mode);
    }

    /// <summary>
    /// 设置视频垂直反转
    /// is_flip: 0: 不反转, 1: 反转
    /// </summary>
    /// <param name="is_flip"></param>
    public int NT_U3D_SetFlipVertical(long handle, int is_flip)
    {
        return player_obj_.Call<int>("SetFlipVertical", handle, is_flip);
    }

    /// <summary>
    /// 设置视频水平反转
    /// is_flip: 0: 不反转, 1: 反转
    /// </summary>
    /// <param name="is_flip"></param>
    public int NT_U3D_SetFlipHorizontal(long handle, int is_flip)
    {
        return player_obj_.Call<int>("SetFlipHorizontal", handle, is_flip);
    }

    /// <summary>
    /// 设置顺时针旋转, 注意除了0度之外, 其他角度都会额外消耗性能
    /// degress: 当前支持 0度,90度, 180度, 270度 旋转
    /// </summary>
    /// <param name="degress"></param>
    public int NT_U3D_SetRotation(long handle, int degress)
    {
        return player_obj_.Call<int>("SetRotation", handle, degress);
    }

    /// <summary>
    /// 设置是否回调下载速度
    /// is_report: if 1: 上报下载速度, 0: 不上报.
    /// report_interval: 上报间隔,以秒为单位,>0.
    /// </summary>
    /// <param name="is_report"></param>
    /// <param name="report_interval"></param>
    public int NT_U3D_SetReportDownloadSpeed(long handle, int is_report, int report_interval)
    {
        return player_obj_.Call<int>("SetReportDownloadSpeed", handle, is_report, report_interval);
    }

    /// <summary>
    /// 设置是否需要在播放或录像过程中快照
    /// </summary>
    /// <param name="is_save_image"></param>
    public int NT_U3D_SetSaveImageFlag(long handle, int is_save_image)
    {
        return player_obj_.Call<int>("SetSaveImageFlag", handle, is_save_image);
    }

    /// <summary>
    /// 播放或录像过程中快照
    /// </summary>
    /// <param name="imageName"></param>
    public int NT_U3D_SaveCurImage(long handle, string imageName)
    {
        return player_obj_.Call<int>("SaveCurImage", handle, imageName);
    }

    /// <summary>
    /// 播放或录像过程中,快速切换url
    /// </summary>
    /// <param name="uri"></param>
    public int NT_U3D_SwitchPlaybackUrl(long handle, string uri)
    {
        return player_obj_.Call<int>("SwitchPlaybackUrl", handle, uri);
    }

    /// <summary>
    /// 创建录像存储路径
    /// </summary>
    /// <param name="path"></param>
    public int NT_U3D_CreateFileDirectory(string path)
    {
        return player_obj_.Call<int>("CreateFileDirectory", path);
    }

    /// <summary>
    /// 设置录像存储路径
    /// </summary>
    /// <param name="path"></param>
    public int NT_U3D_SetRecorderDirectory(long handle, string path)
    {
        return player_obj_.Call<int>("SetRecorderDirectory", handle, path);
    }

    /// <summary>
    /// 设置单个录像文件大小
    /// </summary>
    /// <param name="size"></param>
    public int NT_U3D_SetRecorderFileMaxSize(long handle, int size)
    {
        return player_obj_.Call<int>("SetRecorderFileMaxSize", handle, size);
    }

    /// <summary>
    /// 设置录像时音频转AAC编码的开关
    /// aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.
    /// 注意: 转码会增加性能消耗
    /// </summary>
    /// <param name="is_transcode"></param>
    /// is_transcode:设置为1的话,如果音频编码不是aac,则转成aac,如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.
    public int NT_U3D_SetRecorderAudioTranscodeAAC(long handle, int is_transcode)
    {
        return player_obj_.Call<int>("SetRecorderAudioTranscodeAAC", handle, is_transcode);
    }

    /// <summary>
    /// 设置播放路径
    /// </summary>
    public int NT_U3D_SetUrl(long handle, string url)
    {
        return player_obj_.Call<int>("SetUrl", handle, url);
    }

    /// <summary>
    /// 开始播放
    /// </summary>
    public int NT_U3D_StartPlay(long handle)
    {
        return player_obj_.Call<int>("StartPlay", handle);
    }

    /// <summary>
    /// 获取YUV数据
    /// </summary>
    public AndroidJavaObject NT_U3D_GetVideoFrame(long handle)
    {
        return player_obj_.Call<AndroidJavaObject>("GetVideoFrame", handle);
    }

    /// <summary>
    /// 停止播放
    /// </summary>
    public int NT_U3D_StopPlay(long handle)
    {
        return player_obj_.Call<int>("StopPlay", handle);
    }

    /// <summary>
    /// 开始录像
    /// </summary>
    public int NT_U3D_StartRecorder(long handle)
    {
        return player_obj_.Call<int>("StartRecorder", handle);
    }

    /// <summary>
    /// 停止录像
    /// </summary>
    public int NT_U3D_StopRecorder(long handle)
    {
        return player_obj_.Call<int>("StopRecorder", handle);
    }

    /// <summary>
    /// 关闭播放
    /// </summary>
    public int NT_U3D_Close(long handle)
    {
        return player_obj_.Call<int>("Close", handle);
    }

    /// <summary>
    /// UnInit Player
    /// </summary>
    public int NT_U3D_UnInit()
    {
        return DANIULIVE_RETURN_OK;
    }

总结

Unity下实现RTMP或RTSP无论是播放还是录像,甚至快照,说难不难,但是做好真的比较难,特别是移动端,Unity和原生层交互的时候,数据交互效率相对较低,需要尽可能减少拷贝。录像的话,还需要考虑硬件性能瓶颈。此外,还需要逻辑分离,确保播放和录像相互不影响,以上是抛砖引玉,感兴趣的开发者,可以自行参考实现,如果需要单独和我交流的,可以相互交流。

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

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

相关文章

常用的表格检测识别方法——表格结构识别方法(上)

第三章 常用的表格检测识别方法 3.2表格结构识别方法 表格结构识别是表格区域检测之后的任务&#xff0c;其目标是识别出表格的布局结构、层次结构等&#xff0c;将表格视觉信息转换成可重建表格的结构描述信息。这些表格结构描述信息包括&#xff1a;单元格的具体位置、单元格…

子网掩码计算方法

子网掩码是用来划分网络的一种方式&#xff0c;它是一个32位的二进制数&#xff0c;用于将IP地址分成网络地址和主机地址两部分。子网掩码中的1表示网络地址&#xff0c;0表示主机地址。计算子网掩码的方式取决于需要划分的网络数量和主机数量。 以下是一些计算子网掩码的示例…

【LeetCode热题100】打卡第2天:两数相加

两数相加 ⛅前言 大家好&#xff0c;我是知识汲取者&#xff0c;欢迎来到我们的LeetCode热题100刷题专栏&#xff01; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练…

【2】tensorflow基本概念及变量函数

目录 1 tensorflow运行机制 1.1 搭建计算图模型 计算图的概念 计算图的使用 新建计算图 1.2 在会话中执行计算图 会话的启动方式 1.3 指定计算图的运行设备 2 tensorflow数据模型 2.1 认识张量及属性 张量的类型 张量的阶 2.2 张量类型转换和形状变换 张量类型转换 张…

TwinCAT ENI 数据详解

使用倍福TwinCAT工具可以生成ENI&#xff0c;先对ENI的cyclic frame数据进行解释说明 需要提前了解EtherCAT报文格式&#xff0c;可参考下面文章 EtherCAT报文格式详解_ethercat listtype 1_EtherCat技术研究的博客-CSDN博客https://blog.csdn.net/gufuguang/article/details/…

理解HAL_UARTEx_ReceiveToIdle_IT的工作过程

先只看没错误发生, 没开DMA时候的情况 将会面临3种结局, 收满数据时候IDLE正好发生, 数据发多了, 数据已经收满时候IDLE还没发生IDLE发生了数据没收满, 首先: 接收开始 主要的动作是 1. status UART_Start_Receive_IT(huart, pData, Size); 开始中断接口 2.…

【国产虚拟仪器】基于 ZYNQ 的电能质量系统高速数据采集系统设计

随着电网中非线性负荷用户的不断增加 &#xff0c; 电能质量问题日益严重 。 高精度数据采集系统能够为电能质 量分析提供准确的数据支持 &#xff0c; 是解决电能质量问题的关键依据 。 通过对比现有高速采集系统的设计方案 &#xff0c; 主 控电路多以 ARM 微控制器搭配…

1992-2022年经过矫正的夜间灯光数据

夜间灯光数据是我们在各项研究中经常使用的数据&#xff01;我们平时使用的夜间灯光数据主要来源于NPP/VIIRS和DMSP/OLS两种渠道&#xff0c;我们之前也分享过这两种来源的夜间灯光数据&#xff0c;包括&#xff1a; 2012-2021年逐年的NPP/VIIRS夜间灯光数据2012-2021年逐月的…

MySQL查询性能优化之索引覆盖、索引下推、索引潜水、索引合并

索引覆盖 什么是索引覆盖 select的数据列只用从索引中就能够取得&#xff0c;不必读取数据行&#xff0c;换句话说查询列要被所建的索引覆盖。 如何实现索引覆盖&#xff1f; 最常见的方法就是&#xff1a;将被查询的字段&#xff0c;建立到联合索引&#xff08;如果只有一…

python---变量(2)

此处&#xff0c;首次使用“”对a进行设置值&#xff0c;也就是对a的初始化。 后续位置对a使用“”&#xff0c;实际上是对a赋值。 因此两行代码得到的结果显然是不同的&#xff01; 变量的种类 1.整数-int-根据数据大小自动扩容 python中的变量类型不需要显示声明&#…

关于 arduino 中的 constrain(x, a, b)函数

当我们需要将一个变量的值限制在某个范围内时&#xff0c;可以使用 constrain(x, a, b) 函数来实现。该函数可以将参数 x 的值限制在区间 [a, b] 之间&#xff0c;如果 x 小于 a&#xff0c;则返回 a&#xff0c;如果 x 大于 b&#xff0c;则返回 b&#xff0c;否则返回 x。下面…

第五篇、基于Arduino uno,获取超声波(HC04)传感器的距离数据——结果导向

0、结果 说明&#xff1a;先来看看串口调试助手显示的结果&#xff0c;显示的是一个距离值&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;虽然超声波传感器形态各异&#xff0c;但是原理和代码都是适用的。 2、连线 说明&#xff1a;只…

材料力学-剪力和弯矩方向规定及关系

剪力和弯矩的方向规定方法 对水平梁的某一指定截面来说&#xff0c; 剪力&#xff1a;在它左侧的向上外力&#xff0c;或右侧的向下外力&#xff0c;将产生正的剪力&#xff1b;反之&#xff0c;即产生负的剪力。 自己的记法&#xff08;可以不按我的来&#xff09;&#xff1…

ChatGPT:你真的了解网络安全吗?浅谈网络安全攻击防御进行时之网络安全新总结

ChatGPT&#xff1a;你真的了解网络安全吗&#xff1f;浅谈网络安全攻击防御进行时 网络安全新总结 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI 研发的聊天机器人程序&#xff0c;是人工智能技术驱动的自…

ChatGPT在数据分析中的应用

最近&#xff0c;机器学习和人工智能技术在数据分析领域中发挥着越来越大的作用。而chatgpt正是这个领域最受欢迎的仿人聊天 AI 。但是&#xff0c;对于许多数据科学家和分析师来说&#xff0c;chatgpt并不是他们首选的工具。相反&#xff0c;pandas、sk-learn是数据科学家的最…

一起来聊聊ERP

聊聊ERP 哈喽&#xff0c;哈喽&#xff0c;大家好&#xff01;今天开始&#xff0c;我们就来讲ERP了。 什么是ERP ERP是Enterprise Resource Planning 的缩写&#xff0c;中文含义是企业资源计划。它代表了当前在全球范围内应用最广泛、最有效的一种企业管理方法&#xff0c;…

JVM系列-第12章-垃圾回收器

垃圾回收器 GC 分类与性能指标 垃圾回收器概述 垃圾收集器没有在规范中进行过多的规定&#xff0c;可以由不同的厂商、不同版本的JVM来实现。 由于JDK的版本处于高速迭代过程中&#xff0c;因此Java发展至今已经衍生了众多的GC版本。 从不同角度分析垃圾收集器&#xff0c;…

gym不渲染画面的解决方案(gym版本号0.26.2)

确认gym版本号 我安装了新版gym&#xff0c;版本号是0.26.2&#xff0c;不渲染画面的原因是&#xff0c;新版gym需要在初始化env时新增一个实参render_mode‘human’&#xff0c;并且不需要主动调用render方法&#xff0c;官方文档入门教程如下 import gym import numpy as n…

FreeRTOS学习之路,以STM32F103C8T6为实验MCU(第一章——FreeRTOS的基本框架)

学习之路主要为FreeRTOS操作系统在STM32F103&#xff08;STM32F103C8T6&#xff09;上的运用&#xff0c;采用的是标准库编程的方式&#xff0c;使用的IDE为KEIL5。 注意&#xff01;&#xff01;&#xff01;本学习之路可以通过购买STM32最小系统板以及部分配件的方式进行学习…

day15 Servlet-Request-Response

请求对象(request) **请求对象的作用&#xff1a;**封装了所有请求的数据&#xff0c;有服务器实现这个对象&#xff0c;我们直接调用sercive&#xff08;&#xff09;方法 HttpServletRequest对象的常用方法 request请求方法描述request.getMethod()获得请求方式post\getre…