WebRTC 之音视频同步

news2024/9/25 21:19:17

在网络视频会议中, 我们常会遇到音视频不同步的问题, 我们有一个专有名词 lip-sync 唇同步来描述这类问题,当我们看到人的嘴唇动作与听到的声音对不上的时候,不同步的问题就出现了

而在线会议中, 听见清晰的声音是优先级最高的, 人耳对于声音的延迟是很敏感的

根据 T-REC-G.114-200305 中的描述

  • 大于~280ms 有些用户就会不满意
  • 大于~380ms 多数用户就会不满意
  • 大于~500ms 几乎所有用户就会不满意

我们就尽量使得声音的延迟在 280 ms 之内,这是解决 lip-sync 问题的前提, 声音不好的严重程序超过音视频不同步。

我们可以定义一个 sync_diff 值 来表示音频帧和视频帧之间的时间差

  • 正值表示音频领先于视频
  • 负值表示音频落后于视频

ITU 对此给出以下的阈值:

  • 不可感知 Undetectability (-100ms, +25ms)
  • 可感知 Detectability: (-125ms, +45ms)
  • 可接受 Acceptability: (–185ms, +90 ms)
  • 影响用户 Impact user experience (-∞, -185ms) ∪ (+90ms,∞)

(ITU-R BT.1359-1, Relative Timing of Sound and Vision for Broadcasting" 1998. Retrieved 30 May 2015)

当我们在播放一个视频帧及对应的音频帧的时候,要计算一下这个 sync_diff

sync_diff = audio_frame_time - video_frame_time

如果这个 sync_diff 大于 90ms, 也就是音频包到得过早,就会有音视频不同步的问题 - 声音听到了,嘴巴没跟上.

如果这个 sync_diff 小于 -185ms, 也就是视频包到得过早,就会有音视频不同步的问题 - 嘴巴在动,声音没跟上.

不同步的原因

lip sync 1

这个问题的原因主要在于音频的采集, 编码,传输, 解码, 播放与视频的采集,编码,传输,解码以及渲染一般是分开进行的,因为音频和视频采集自不同的设备,即它们的来源不同,在网络上传输也会有延迟,也由不同的设备进行播放,这样如果在接收方不采取措施进行时间同步,就会极有可能看到口型和听到的声音对不上的情况。

由此派生出 3 个小问题:

  1. 如何将来自同一个人或设备的多路 audio 及 video stream关联起来?
  2. 如何将 RTP 中的时间戳 timestamp 映射到发送方的音视频采集时间
  3. 如何调整音频或者视频帧的播放时间,让它们怎么之间相对同步?

解决方案

1. 如何将来自同一个人或设备的音视频流关联起来?

对于多媒体会话,每种类型的媒体(例如音频或视频)一般会在单独的 RTP 会话中发送,发送方会在 RTCP SDES 消息中指明
接收方通过 CNAME 项关联要同步的RTP流, 而这个 CNAME 包含在发送方所发送的 RTCP SDES 中

SDES 数据包包含常规包头,有效负载类型为 202,项目计数等于数据包中 SSRC/CSRC 块的数量,后跟零个或多个 SSRC/CSRC 块,其中包含有关特定 SSRC 或 CSRC,每个都与 32 位边界对齐。

0               1               2               3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |V=2|P|    SC   |  PT=SDES=202  |            length L           |
    +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
    |                          SSRC/CSRC_1                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                           SDES items                          |
    |                              ...                              |
    +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
    |                          SSRC/CSRC_2                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                           SDES items                          |
    |                              ...                              |
    +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

CNAME 项在每个 SDES 数据包中都是必需的,而 SDES 数据包又是每个复合 RTCP 数据包中的必需部分。

与 SSRC 标识符一样,CNAME 必须与其他会话参与者的 CNAME 不同。 但 CNAME 不应随机选择 CNAME 标识符,而应允许个人或程序通过 CNAME 内容来定位其来源。

0               1               2               3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |    CNAME=1    |     length    | user and domain name         ...
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

例如 Alice 向外发送一路音频流,一路视频流, 这两路流会使用不同的 SSRC, 但是在其所发送的 RTCP SDES 消息会使用相同的 CNAME.

  • RTP SSRC 1 ~ CNAME 1
  • RTP SSRC 2 ~ CNAME 1

2. 同步的时间如何计算

来自同一个终端用户的音频和视频, 在编码发送的 RTP 包中有一个 timestamp, 这个时间戳表示媒体流的捕捉时间。
同时, 作为发送者也会发送 RTCP Sender Report, 其中包含发送的 RTP timestamp 和 NTP timestamp 的映射关系,这样我们在接收方就可以把 RTP 包里的

lip sync flow

对于每个 RTP 流,发送方定期发出 RTCP SR, 其中包含一对时间戳:

NTP 时间戳以及与该 RTP 流关联的相应 RTP 时间戳。

这对时间戳传达每个媒体流的 NTP 时间和 RTP 时间之间的关系。

先回顾一下 RTP packet 和 RTCP sender report

  • RTP 包结构
0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                           timestamp                           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           synchronization source (SSRC) identifier            |
   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   |            contributing source (CSRC) identifiers             |
   |                             ....                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • RTCP Sender Report 结构
0                   1                   2                   3
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   header |V=2|P|    RC   |   PT=SR=200   |             length            |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                         SSRC of sender                        |
         +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   sender |              NTP timestamp, most significant word             |
   info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |             NTP timestamp, least significant word             |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                         RTP timestamp                         |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                     sender's packet count                     |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                      sender's octet count                     |
         +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   report |                 SSRC_1 (SSRC of first source)                 |
   block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   1    | fraction lost |       cumulative number of packets lost       |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |           extended highest sequence number received           |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                      interarrival jitter                      |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                         last SR (LSR)                         |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                   delay since last SR (DLSR)                  |
         +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   report |                 SSRC_2 (SSRC of second source)                |
   block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   2    :                               ...                             :
         +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
         |                  profile-specific extensions                  |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

通过 NTP timestamp 和 RTP timestamp 之间的映射, 我们可以知道 audio 包的时间和 video 包的时间。

具体的计算可以参见 WebRTC 的 RtpToNtpEstimator 类, 它将收到的若干 SR 中的 NTP time 和 RTP timestamp 保存下来,然后 应用最小二乘法来估算后续 RTP timestamp 所对应的 NTP timestamp, 大致为用最近 N=20 个 RTCP SR 包的 ntp timestamp 和 rtp timestamp 的构造出线性关系 y = ax + b, 通过最小二乘法来计算收到的 RTP 包对应的 ntp timestamp.

// Converts an RTP timestamp to the NTP domain.
// The class needs to be trained with (at least 2) RTP/NTP timestamp pairs from
// RTCP sender reports before the convertion can be done.
class RtpToNtpEstimator {
      public:
            //...

            enum UpdateResult { kInvalidMeasurement, kSameMeasurement, kNewMeasurement };
            // Updates measurements with RTP/NTP timestamp pair from a RTCP sender report.
            UpdateResult UpdateMeasurements(NtpTime ntp, uint32_t rtp_timestamp);

            // Converts an RTP timestamp to the NTP domain.
            // Returns invalid NtpTime (i.e. NtpTime(0)) on failure.
            NtpTime Estimate(uint32_t rtp_timestamp) const;

            // Returns estimated rtp_timestamp frequency, or 0 on failure.
            double EstimatedFrequencyKhz() const;

      private:
            // Estimated parameters from RTP and NTP timestamp pairs in `measurements_`.
            // Defines linear estimation: NtpTime (in units of 1s/2^32) =
            //   `Parameters::slope` * rtp_timestamp + `Parameters::offset`.
            struct Parameters {
                  double slope;
                  double offset;
            };

            // RTP and NTP timestamp pair from a RTCP SR report.
            struct RtcpMeasurement {
                  NtpTime ntp_time;
                  int64_t unwrapped_rtp_timestamp;
            };

            void UpdateParameters();

            int consecutive_invalid_samples_ = 0;
            std::list<RtcpMeasurement> measurements_;
            absl::optional<Parameters> params_;
            mutable RtpTimestampUnwrapper unwrapper_;
};

3. 调整播放和渲染时间

一般我们会以 audio 为主, video 向 audio 靠拢, 两者时间一致也就会达到 lip sync 音视频同步

  1. audio 包先来, video 包后来: audio 包放在 jitter buffer 时等一会儿, 但是这个时间是有限的, 音频的流畅是首先要保证的, 视频跟不上可以降低视频的码率
  2. video 包先来, audio 包后来: video 包要等 audio 包来, 这是为了让音视频同步要付出的代价

一般以音频为主流 master stream,视频为从流 slave stream。 一般方法是接收方维护音频流的缓冲区的管理,并通过将视频 RTP 时间戳转换为正确从属于音频流的时间戳来调整视频流的播放。

当带有RTP时间戳 RTPv的视频帧到达接收器时,接收器通过四个步骤将RTP时间戳 RTPv 映射到视频设备时间戳VTB( Video Time Base),如图所示。

  1. 使用 Video RTCP SR 中的 RTP/NTP 时间戳对建立的映射,将视频 RTP 时间戳 RTPv 映射到发送方 NTP 时间。

  2. 根据该 NTP 时间戳,使用 Audio RTCP SR 中的 RTP/NTP 时间戳对建立的映射,计算来自发送方的相应音频 RTPa 时间戳。
    此时,视频RTP时间戳被映射到音频RTP 包的相同时间基准。

  3. 根据该音频 RTP 时间戳,使用卡尔曼滤波的方法计算音频设备时间基准中的相应时间戳。 结果是音频设备时间基准 ATB(Audio Time Base) 中的时间戳。

  4. 根据 ATB,使用偏移量 AtoV 计算视频设备时基 VTB 中的相应时间戳。

接收方需要确保带有 RTP 时间戳 RTPv 的视频帧使用所计算出的发送方视频设备时间基准 VTB 播放。

AtoV = V_time - A_Time/(audio sample rate)

注:

  • AtoV: 音频相较视频的偏移量
  • ATB: Audio device Time Base 音频设备的时间基准
  • VTB: Video device Time Base 视频设备的时间基准

具体方法可以参见 https://www.ccexpert.us/video-conferencing/using-rtcp-for-media-synchronization.html)

WebRTC 的做法原理上差不多,实现略有不同,可以参见 WebRTC 的源代码 StreamSynchronization 类和 RtpStreamsSynchronizer 类

大致上它会计算出 video 的延迟

current_delay_ms = max(min_playout_delay_ms, jitter_delay_ms + decode_time _ms + render_delay_ms)

然后再计算视频相对于音频的延迟 relative_delay_ms,

  • 如果它大于0, 视频比音频慢,减小视频延迟(主要是调整 jitter buffer delay),或者是增大音频延迟, 取决于阈值 base_target_delay_ms
  • 如果它小于0, 音频比视频慢,减小音频延迟,或者是增大视频延迟, 取决于阈值base_target_delay_ms

base_target_delay_ms 的比较逻辑参见StreamSynchronization::ComputeDelays,

if (diff_ms > 0) {
      // The minimum video delay is longer than the current audio delay.
      // We need to decrease extra video delay, or add extra audio delay.
      if (video_delay_.extra_ms > base_target_delay_ms_) {
            // We have extra delay added to ViE. Reduce this delay before adding
            // extra delay to VoE.
            video_delay_.extra_ms -= diff_ms;
            audio_delay_.extra_ms = base_target_delay_ms_;
      } else {  // video_delay_.extra_ms > 0
            // We have no extra video delay to remove, increase the audio delay.
            audio_delay_.extra_ms += diff_ms;
            video_delay_.extra_ms = base_target_delay_ms_;
      }
      } else {  // if (diff_ms > 0)
      // The video delay is lower than the current audio delay.
      // We need to decrease extra audio delay, or add extra video delay.
      if (audio_delay_.extra_ms > base_target_delay_ms_) {
            // We have extra delay in VoiceEngine.
            // Start with decreasing the voice delay.
            // Note: diff_ms is negative; add the negative difference.
            audio_delay_.extra_ms += diff_ms;
            video_delay_.extra_ms = base_target_delay_ms_;
      } else {  // audio_delay_.extra_ms > base_target_delay_ms_
            // We have no extra delay in VoiceEngine, increase the video delay.
            // Note: diff_ms is negative; subtract the negative difference.
            video_delay_.extra_ms -= diff_ms;  // X - (-Y) = X + Y.
            audio_delay_.extra_ms = base_target_delay_ms_;
      }
}

更多细节在 WebRTC 的代码中

  • class StreamSynchronization
  • class RtpStreamsSynchronizer

通过StreamSynchronization::ComputeDelays计算出音频和视频的相对延迟,如果相对延迟很小( < 30ms), 则无需调整音视频的播放时间,如果相对延迟很大, 则以 80ms 的幅度进行逐步调整。 与传统的只调视频延迟,不调音频延迟, WebRTC 会两边都调点,使得音视频的时间彼此靠近,前提是音频的延迟是在上面提到的可接受范围之内。

参考资料

  • https://www.ciscopress.com/articles/article.asp?p=705533&seqNum=6
  • https://www.ccexpert.us/video-conferencing/using-rtcp-for-media-synchronization.html
  • https://testrtc.com/docs/how-do-you-find-lip-sync-issues-in-webrtc/
  • https://en.wikipedia.org/wiki/Audio-to-video_synchronization
  • https://www.simplehelp.net/2018/05/29/how-to-fix-out-of-sync-audio-video-in-an-mkv-mp4-or-avi/
    *RFC6051: Rapid Synchronisation of RTP Flows

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

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

相关文章

pygame贪吃蛇游戏

pygame贪吃蛇游戏 贪吃蛇游戏通过enter键启动&#xff0c;贪吃蛇通过WSAD进行上下左右移动&#xff0c;每次在游戏区域中随机生成一个食物&#xff0c;每次吃完食物后&#xff0c;蛇变长并且获得积分&#xff1b;按空格键暂停。 贪吃蛇 import random, sys, time, pygame from …

SQL 表别名 和 列别名

列表名 列表名之后 order by 可以用别名 也可以用原名&#xff0c; where 中不能用别名的 SQL语句执行顺序&#xff1a; from–>where–>group by -->having — >select --> order 第一步&#xff1a;from语句&#xff0c;选择要操作的表。 第二步&#xff1…

SolidWorks 3D Interconnect介绍

目前市面上有的三维设计软件有很多&#xff0c;如UG、Pro/E、CATIA等&#xff0c;而且每个三维设计软件都会生成自己文件格式。由于产品设计的原因&#xff0c;我们避免不了的会需要去使用不同三维设计软件的文件&#xff0c;这对于工程师来说其实是一件比较麻烦的事。 为什么…

性能测试JMeter学习笔记(脚本增强和命令行操作)

HTTP的Cookie管理器 在根节点下&#xff0c;添加“HTTP Cookie管理器”&#xff08;注意是根节点&#xff09; Cookie管理器作用&#xff1a;只要响应里有Cookie&#xff0c;就自动存进Cookie管理器&#xff0c;而后在后续的请求中&#xff0c;自动携带cookie JMeter脚本增强…

奥威BI系统:零编程建模、开发报表,提升决策速度

奥威BI是一款非常实用的、易用、高效的商业智能工具&#xff0c;可以帮助企业快速获取数据、分析数据、展示数据。值得特别注意的一点是奥威BI系统支持零编程建模、开发报表&#xff0c;是一款人人都能用的大数据分析系统&#xff0c;有助于全面提升企业的数据分析挖掘效率&…

教资学习笔记总结

科目一 科目二 第一章 教育基础知识和基本原理 第一节 教育的认识 1.教育的概念 教育的词源&#xff1a;教育一词最早出现于《孟子尽心上》&#xff1a;“得天下英才而教育之”许慎在《说文解字》中最早解释教育&#xff1a;“教&#xff0c;上所施&#xff0c;下所效也”…

DNS部署与安全详解(上)

文章目录 一、DNS二、域名组成1. 域名组成概述2. 域名组成 三、监听端口四、DNS解析种类1. 按照查询方式分类&#xff1a;2. 按照查询内容分类&#xff1a; 五、DNS服务器搭建过程1. 先确保服务器的IP地址是固定的2. 安装DNS软件 一、DNS DNS全称Domain Name Service&#xff0…

组合总和——力扣39

文章目录 题目描述回溯题目描述 回溯 class Solution {public:vector<vector<

Qt--动态链接库的创建和使用

写在前面 在Qt的实际开发中&#xff0c;免不了使用和创建动态链接库&#xff0c;因此熟悉Qt中动态链接库的创建和使用对后续的库开发或使用是非常用必要的。 在之前的文章https://blog.csdn.net/SNAKEpc12138/article/details/126189926?spm1001.2014.3001.5501中已经对导入…

Swish - Mac 触控板手势窗口管理工具[macOS]

Swish for Mac是一款Mac触控板增强工具&#xff0c;借助直观的两指轻扫&#xff0c;捏合&#xff0c;轻击和按住手势&#xff0c;就可以从触控板上控制窗口和应用程序。 Swish for Mac又不仅仅只是一个窗口管理器&#xff0c;Swish具有28个易于使用的标题栏&#xff0c;停靠栏…

基于ffmpeg与SDL的视频播放库

由于工作需要&#xff0c;自己封装的基于ffmpeg的视频编解码库&#xff0c;显示采用了SDL库。可以播放本地文件或网络流&#xff0c;支持多端口播放&#xff0c;支持文字叠加&#xff0c;截图、视频录制等等。 头文件代码&#xff1a; #pragma once #ifdef __DLLEXPORT #defin…

Visual Studio在Debug模式下,MFC工程中包含Eigen库时的定义冲突的问题

Visual Studio在Debug模式下&#xff0c;MFC工程中包含Eigen库时的定义冲突的问题 报错信息 Eigen\src\Core\PlainObjectBase.h(143,5): error C2061: 语法错误: 标识符“THIS_FILE” Eigen\src\Core\PlainObjectBase.h(143,1): error C2333: “Eigen::PlainObjectBase::opera…

Opencv-C++笔记 (15) : 像素重映射 与 图像扭曲

文章目录 一、重映射简介二、图像扭曲 一、重映射简介 重映射&#xff0c;就是把一幅图像中某位置的像素放置到另一图像指定位置的过程。即&#xff1a; 在重映射过程中&#xff0c;图像的大小也可以同时发生改变。此时像素与像素之间的关系就不是一一对应关系&#xff0c;因…

[MAUI]模仿微信“按住-说话”的交互实现

今天使用这个控件&#xff0c;做一个模仿微信“按住-说话”的小功能&#xff0c;最终效果如下&#xff1a; 使用.NET MAUI实现跨平台支持&#xff0c;本项目可运行于Android、iOS平台。 创建页面布局 新建.NET MAUI项目&#xff0c;命名HoldAndSpeak MainPage.xaml中创建一个…

【敏捷开发】测试驱动开发(TDD)

测试驱动开发&#xff08;Test-Driven Development&#xff0c;简称TDD&#xff09;是敏捷开发模式中的一项核心实践和技术&#xff0c;也是一种设计方法论。TDD有别于以往的“先编码&#xff0c;后测试”的开发模式&#xff0c;要求在设计与编码之前&#xff0c;先编写测试脚本…

windows部署springboot项目 jar项目 (带日志监听和开机自起脚本)

windows部署springboot项目 jar项目 &#xff08;带日志监听&#xff09; 1.把项目打包成jar包&#xff0c;本例演示打包后的jar文件名为demo.jar ———————————————— 2.需要装好java环境&#xff0c;配置好JAVA_HOME&#xff0c;CLASSPATH&#xff0c;PATH等…

Idea中侧面栏不见了,如何设置?

一、打开idea点击File然后点击Setting 二、点击Appearance,然后划到最下面&#xff0c;勾选Show tool windows bars和Side-by-side layout on the left 三、侧面栏目正常显示

在java中如何使用openOffice进行格式转换,word,excel,ppt,pdf互相转换

1.首先需要下载并安装openOffice,下载地址为&#xff1a; Apache OpenOffice download | SourceForge.net 2.安装后&#xff0c;可以测试下是否可用&#xff1b; 3.build.gradle中引入依赖&#xff1a; implementation group: com.artofsolving, name: jodconverter, version:…

避免安装这5种软件,手机广告频繁弹窗且性能下降

在我们使用手机的日常生活中&#xff0c;选择合适的应用软件对于保持良好的使用体验至关重要。然而&#xff0c;有些软件可能会给我们带来不必要的麻烦和困扰。特别是那些频繁弹窗广告、导致手机性能下降的应用程序&#xff0c;我们应该尽量避免安装它们。 首先第一种&#xf…

STM32H5开发(5)----串口打印配置

STM32H5开发----4.开发板介绍 概述样品申请硬件准备生成例程配置调试口代码生成配置项目配置调试配置串口重定向打印测试结果 概述 在使用STM32CUBEIDE开发STM32H5项目时&#xff0c;串口打印被证明是一项极其有益的调试工具&#xff0c;能够在开发过程中实时输出信息和调试数…