当前位置: 首页 > news >正文

FFplay 音视频同步机制解析:以音频为基准的时间校准与动态帧调整策略

1.⾳视频同步基础

1.2 简介

看视频时,要是声音和画面不同步,体验会大打折扣。之所以会出现这种情况,和音视频数据的处理过程密切相关。音频和视频的输出不在同一个线程,就像两个工人在不同车间工作,而且不一定会同时 “生产” 出同一时间点(pts,Presentation Time Stamp,即显示时间戳)的音频帧和视频帧。更麻烦的是,在编码或封装阶段,pts 可能不连续,甚至存在错误。所以,在播放音视频时,必须对它们的播放速度、播放时刻进行精准控制,这就是音视频同步的关键所在。​
在 ffplay 这个常用的音视频播放工具中,音频和视频分别有自己的输出线程。音频的输出线程是 sdl 的音频输出回调线程,好比是音频专属的 “快递员”,负责把处理好的音频数据送到播放设备;视频的输出线程则是程序的主线程,就像视频的 “大管家”,统筹着视频数据的播放流程。​
为了实现音视频同步,常见的策略有以下几种:

  • **以音频为基准同步视频(AV_SYNC_AUDIO_MASTER)**​
    此策略将音频作为整个音视频同步的时间基准。音频数据在解码后,其时间戳(PTS)被视为标准时间轴。视频播放进程紧密围绕音频时间轴进行动态调整。​
    当系统检测到视频播放进度滞后于音频,即视频的 PTS 大于音频的 PTS 时,为了让视频尽快追赶音频的节奏,会选择性地丢弃部分视频帧。这种 “跳帧” 处理在视觉上可能表现为画面轻微跳跃,但相较于声音的异常,人眼对这种画面变化的敏感度较低,能在一定程度上保证音画同步。​
    反之,若视频播放速度快于音频,系统则会继续渲染上一帧,延长该帧的显示时间,从而降低视频的整体播放速度,使其与音频播放进度保持一致。这种以
    音频为主导的同步方式,在大多数常规音视频播放场景中被广泛应用
    ,能有效利用人耳对声音变化更为敏感的特性,为用户带来相对自然流畅的视听体验。​

  • 以视频为基准同步音频(AV_SYNC_VIDEO_MASTER)​
    该策略以视频的播放进度和时间戳作为同步的核心依据。当音频播放进度落后于视频,即音频的 PTS 小于视频的 PTS 时,系统会加快音频的播放速度,或者选择丢弃部分音频帧来追赶视频。然而,丢弃音频帧极易导致声音出现断音现象,严重影响听觉体验,因此这种处理方式需谨慎使用。​
    当音频播放进度超前于视频时,系统会放慢音频的播放速度,或重复播放上一帧音频。在调整音频播放速度的过程中,涉及到音频重采样技术,即对音频样本的采样率、采样精度等参数进行重新调整,以保证音频在变速播放后仍能保持较好的音质,避免出现声音失真、变调等问题 。但这种同步方式由于人耳对声音变化的高敏感度**,在实际应用中相对较少,仅适用于一些对视频画面呈现要求极高、对音频同步精度要求相对宽松的特殊场景。​**

  • 以外部时钟为基准同步(AV_SYNC_EXTERNAL_CLOCK)​
    该策略引入一个高精度的外部时钟源作为统一的时间基准,整个音视频播放系统中的音频和视频播放时序均参照此外部时钟进行校准。外部时钟可以是网络时间协议(NTP)服务器提供的时间,或是专业硬件设备(如时钟发生器)产生的稳定时钟信号。​
    音视频数据在解码后,其时间戳(PTS)会与外部时钟进行实时比对。系统通过精确计算两者的时间偏差,对音频和视频的播放时刻进行调整,确保音频采样点的输出与视频帧的显示在时间维度上严格对齐。这种同步方式尤其适用于对同步精度要求极高的场景,如远程视频会议、大型多机位直播等。在这些场景中,多个终端设备同时接收并播放音视频流,只有依赖统一的外部时钟,才能实现跨设备的精准音视频同步,避免出现音画错位的现象。​

  • 结合外部时钟调整播放速度​
    此策略是在以外部时钟为基准同步的基础上进行的优化升级。它不仅依据外部时钟来校准音视频的播放起始时刻,还会实时监测外部时钟与音视频播放进度之间的差异,并动态调整音视频的播放速度。​
    系统持续计算音视频播放进度与外部时钟的时间差,当发现音频或视频播放进度超前于外部时钟时,通过延长音频样本的播放间隔、增加视频帧的显示时长等方式降低播放速度;当播放进度滞后时,则通过缩短音频样本播放间隔、跳过部分视频帧等手段加快播放速度。这种动态调整机制类似于闭环控制系统,能够有效应对网络延迟波动、设备性能差异等因素导致的音视频播放节奏变化,在复杂的网络环境或异构设备组成的播放系统中,为用户提供稳定、流畅的音视频同步体验。​

ffplay实现三种

{ "sync", HAS_ARG | OPT_EXPERT, { .func_arg = opt_sync }, "set audio-video sync. type (type=audio/video/ext)", "type" },# ffplay -sync audio input.mp4 使用办法

1.2 ⾳视频同步基本概念

基本概念
DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这⼀帧的数据。
PTS(Presentation Time Stamp):即显示时间戳,这个时间戳⽤来告诉播放器该在什么时候显示
这⼀帧的数据。
timebase 时基:pts的值的真正单位
ffplay中的pts,ffplay在做⾳视频同步时使⽤秒为单位,使⽤double类型去标识pts,在ffmpeg内部不会⽤浮点数去标记pts。
Clock 时钟

AVRational 表示分数

typedef struct AVRational{int num; ///< Numeratorint den; ///< Denominator} AVRational;

timebase={1, 1000} 表示千分之⼀秒(毫秒),那么pts=1000,即为pts*1/1000 = 1秒
将AVRatioal结构转换成double

static inline double av_q2d(AVRational a)return a.num / (double) a.den;
}

计算时间戳

timestamp() = pts * av_q2d(st->time_base)

计算帧时⻓

time() = st->duration * av_q2d(st->time_base)

不同时间基之间的转换

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

补充

对比项时间戳(Timestamp)帧时长(Duration)
计算对象单个帧或数据包的显示时间点整个流(或文件)的总播放时间
公式输入pts(帧的时间戳)st->duration(流的总时长)
结果含义例如:第 10 秒显示的帧例如:视频总时长为 120 分钟
单位秒(相对于流的开始时间)秒(整个流的持续时间)
用途同步、进度显示、帧定位总时长显示、处理时间估算

示例说明
假设一个视频流:

  • 时间基 time_base = {1, 1000}(即 1 个时间单位 = 0.001 秒)。
  • 某帧的 pts = 5000,则该帧的时间戳为:
    5000 * 0.001 = 5 秒(即该帧在第 5 秒显示)。
  • 流的总时长 duration = 20000,则整个视频的时长为:
    20000 * 0.001 = 20 秒(即视频总时长为 20 秒)。

Clock

typedef struct Clock {double	pts;            // 时钟基础, 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧// 当前pts与当前系统时钟的差值, audio、video对于该值是独立的double	pts_drift;      // clock base minus time at which we updated the clock// 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间double	last_updated;   // 最后一次更新的系统时钟double	speed;          // 时钟速度控制,用于控制播放速度// 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列int	serial;             // clock is based on a packet with this serialint	paused;             // = 1 说明是暂停状态// 指向packet_serialint *queue_serial;      /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;
static void set_clock_at(Clock *c, double pts, int serial, double time);

参数详解

  1. Clock *c
    作用:指定要操作的时钟对象。
  2. double pts
    作用:设置时钟的时间戳值。
  3. int serial
    作用:标识时间戳的有效性,避免使用已过时的时间戳。
  4. double time
    作用:记录设置时钟的系统时间基准,用于后续时钟漂移计算。

工作原理

  • 不断 “对时”
    就像我们平时给手表对时间一样,这个时钟也需要定期校准。它有个叫 set_clock_at 的方法,要校准的时候,得告诉它三个信息:

    • pts :可以把它理解成某个事件(比如视频里某一帧该出现的时间)的时间标记。
    • serial :可以看作是一种编号,用来区分不同的 “东西”(比如不同的视频流之类的 ),不过这里重点先在时间上。
    • time :就是系统当前实实在在过了多久的时间,像从电脑开机到现在过了多少秒。用这三个信息就能给时钟校准啦。
  • 估算时间
    这个时钟显示的时间不是绝对精准的,而是个估算值。这里面最关键的设计就是 pts_drift 。

在这里插入图片描述

关键操作与计算

  • set_clock 操作:在 PTS1 对应的时间点,进行了 set_clock 操作 。此时计算 pts_drift,公式为 pts_drift = PTS1 - time1 ,也就是用当前帧的 PTS1 减去此时的系统时间 time1 ,得到两者的时间差值 。
  • 时间推进与 get_clock 操作:随着时间推进,系统时间到了 time2 ,此时进行 get_clock 操作 。为了估算当前系统时间 time2 对应的 PTS 时间(即参考时间 ),利用之前计算的 pts_drift 来计算:
    首先将 pts_drift + time2 ,即 PTS1 - time1 + time2 。
    进一步变形为 PTS1 + (time2 - time1) ,而 time2 - time1 就是这段时间的时长,用 duration 表示,所以最终得到 PTS1 + duration ,这就是当前系统时间对应的参考 PTS 时间 。

1.3 不同时间基

AVFormatContext
duration:表示整个码流的时长。在获取正常时长时,需要将其除以 AV_TIME_BASE ,得到的结果单位为秒。这一数值为了解整个媒体文件的时长提供了关键信息,在诸如播放器展示总时长等场景中发挥作用。
AVStream 的 time_base 设置
AVStream 的 time_base 是在解复用器(demuxer)或复用器(muxer)内进行设置的,以下以常见的 TS、FLV、MP4 格式为例:

  • TS 格式:在 mpegts.c 和 mpegtsenc.c 中,通过 avpriv_set_pts_info(st, 33, 1, 90000) 进行设置。这里的参数设置,决定了 TS 流后续处理中时间相关计算的基准。在解码 TS 流时,解码器依据这个设定的 time_base ,准确地将时间戳信息转化为实际的播放时间,以确保音视频同步播放。
  • FLV 格式:在 flvdec.c 中使用 avpriv_set_pts_info(st, 32, 1, 1000) ,在 flvenc.c 中使用 avpriv_set_pts_info(s->streams[i], 32, 1, 1000) 。在 FLV 流的解码过程中,time_base 参与到对音视频帧时间戳的解析和处理中。比如,对于视频帧,根据 time_base 可以准确计算出每一帧应该在何时显示,对于音频帧,能确定其采样点的播放时刻,从而保障解码后的音视频能正确同步播放。
  • MP4 格式:在 mov.c 中通过 avpriv_set_pts_info(st, 64, 1, sc->time_scale) ,在 movenc.c 中通过 avpriv_set_pts_info(st, 64, 1, track->timescale) 进行设置。在 MP4 解码时,time_base 如同一个精准的 “时间指挥官”,协调着音视频帧的解码和输出顺序。例如,视频解码线程依据 time_base 来决定何时输出一帧画面,音频解码线程也基于此来控制音频样本的播放节奏,以实现音视频在播放时的完美同步。

AVPacket 结构体
在 AVPacket 中,各时间相关字段均以 所属 AVStream 的 time_base 为单位,具体如下:

  • pts(Presentation Timestamp,显示时间戳):用于标识数据包对应内容在正常播放顺序下的显示时间点,其数值需结合 AVStream->time_base 换算为实际时间(秒),例如 实际时间(秒) = pts × AVStream->time_base.num / AVStream->time_base.den。
  • dts(Decoding Timestamp,解码时间戳):指示数据包应被解码的时间点,在编码存在 B 帧等复杂场景时,dts 与 pts 可能不同,同样以 AVStream->time_base 为度量基准 。
  • duration(持续时间):表示该数据包所包含内容的持续时长,同样基于 AVStream->time_base 进行量化,用于计算相邻数据包的时间间隔或解码后帧的显示时长。

AVFrame 结构体
AVFrame 中的时间相关字段涉及继承与转换,与 AVStream->time_base 紧密关联,具体规则如下:

  • pts:代表解码后帧的显示时间戳,通常从对应 AVPacket 拷贝而来,同样以 AVStream->time_base 为单位。若原始 AVPacket 未提供有效 pts,则需通过其他逻辑(如推算)确定。
  • pkt_pts 和 pkt_dts:直接拷贝自生成该帧的 AVPacket 中的 pts 和 dts,因此也以 AVStream->time_base 为单位 。这两个字段保留了数据包层面的时间戳信息,便于追溯帧的原始时间属性。
  • duration:表示该帧的持续时长,与 AVPacket 类似,同样以 AVStream->time_base 为度量单位,用于精确控制帧的显示时长,在音视频同步计算中发挥重要作用。

1.4 ffplay的实际操作

typedef struct Frame {AVFrame		*frame;         // 指向数据帧AVSubtitle	sub;            // 用于字幕int		serial;             // 帧序列,在seek的操作时serial会变化double		pts;            // 时间戳,单位为秒double		duration;       // 该帧持续时间,单位为秒int64_t		pos;            // 该帧在输入文件中的字节位置int		width;              // 图像宽度int		height;             // 图像高读int		format;             // 对于图像为(enum AVPixelFormat),// 对于声音则为(enum AVSampleFormat)AVRational	sar;            // 图像的宽高比(16:9,4:3...),如果未知或未指定则为0/1int		uploaded;           // 用来记录该帧是否已经显示过?int		flip_v;             // =1则垂直翻转, = 0则正常播放
} Frame;

视频帧 PTS 的获取与校正

  • best_effort_timestamp 的作用:
    代码中通过 frame->pts = frame->best_effort_timestamp; 校正视频帧的 pts。尽管多数情况下 AVFrame 的 pts 与 best_effort_timestamp 值相同,但 best_effort_timestamp 是通过多种启发式方法(如解码时的综合逻辑)估计出的时间戳,由 libavcodec 设置。

  • 优势:在某些复杂场景(如输入流时间戳不规范、部分编码场景缺失 pts 时),best_effort_timestamp 能提供更可靠的时间参考,确保视频帧的显示时序正确,避免因原始 pts 异常导致的播放错乱或同步问题。
    与 AVFrame->pts 的关系:AVFrame->pts 通常依赖于输入流的时间基,而 best_effort_timestamp 是经过解码层逻辑处理后的 “最佳估计”,在时间表示上可能更直接、准确,因此 FFplay 选择以此作为 Frame->pts 的来源。

在 FFplay 中,音频帧的 PTS(Presentation Time Stamp,显示时间戳)处理是实现音频同步的关键环节。这个过程涉及三次时间基转换缓冲区延迟补偿,下面我将逐步拆解其逻辑:

Audio Frame PTS的获取
1. 第一次转换:从 AVStream->time_base1/采样率

frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
  • 目的:将原始的时间戳(以 AVStream->time_base 为单位)转换为以 样本数 为单位。
  • 转换逻辑
    • d->avctx->pkt_timebase 通常等于 AVStream->time_base,是输入流的时间基(例如 {1, 90000})。
    • tb 是目标时间基 {1, 采样率}(例如 {1, 44100})。
    • av_rescale_q 函数将 ptspkt_timebase 转换为 tb
  • 意义:后续音频处理(如重采样、缓冲区操作)通常以样本数为单位,这一步为精确控制音频数据的时序奠定基础。

2. 第二次转换:从 1/采样率 到秒

af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
  • 目的:将以样本数为单位的 pts 转换为以 为单位的浮点数,便于与时钟同步。
  • 转换逻辑
    • av_q2d(tb) 将时间基 {1, 采样率} 转换为小数值(例如 1/44100 ≈ 0.0000226757)。
    • frame->pts * av_q2d(tb) 直接将样本数转换为秒数(例如 44100 个样本 → 1.0 秒)。
  • 特殊处理:若 frame->ptsAV_NOPTS_VALUE(无效时间戳),则设为 NAN,表示时间未知。

3. 第三次调整:补偿音频缓冲区延迟

audio_pts = is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec;
  • 目的:修正 audio_clock,使其反映 实际播放时间(而非缓冲区填充时间)。
  • 延迟来源
    • 2 * is->audio_hw_buf_size:SDL 音频驱动通常维护两个缓冲区,这部分数据已提交但尚未播放。
    • is->audio_write_buf_size:当前 audio_buf 中未提交给驱动的剩余数据。
  • 计算逻辑
    • is->audio_tgt.bytes_per_sec 表示每秒播放的字节数(例如 44100Hz × 2 字节/样本 × 2 声道 = 176400 B/s)。
    • 总延迟字节数除以 bytes_per_sec 得到延迟秒数,从 audio_clock 中减去该值,得到实际播放位置的时间戳。

完整流程总结

  1. 从容器到样本数:通过 av_rescale_qAVPacketpts 转换为样本数,消除不同媒体流时间基的差异。
  2. 从样本数到秒:将样本数转换为秒,便于与时钟系统(如视频时钟)直接比较。
  3. 缓冲区延迟补偿:考虑音频驱动缓冲区的延迟,修正时钟以反映真实播放进度。

2 以⾳频为基准

2.1 音频主流程

 /* Let's assume the audio driver that is used by SDL has two periods. */if (!isnan(is->audio_clock)) {set_clock_at(&is->audclk, is->audio_clock -(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size)/ is->audio_tgt.bytes_per_sec,is->audio_clock_serial,audio_callback_time / 1000000.0);sync_clock_to_slave(&is->extclk, &is->audclk);}

在这里插入图片描述

  • 橙色段:表示 SDL 内部的 audio_hw_buf_size(音频硬件缓冲区大小),代表已在 SDL 音频驱动内部、但尚未播放的音频数据占用空间。
  • 绿色段:表示 SDL 外部 sdl_audio_callback 处理的 audio_hw_buf_size,即当前回调正在处理或准备填充到驱动的音频数据部分。
  • 蓝色段:表示 audio_buf 剩余的 audio_write_buf_size,即解码后未被填充到 SDL 音频驱动缓冲区的剩余音频数据量。

流程分析

1 is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
audio_clock 代表的是当前帧最后一个样本的显示时间,即 audio_buf 结束位置的时间戳
物理意义:
若 af->pts=2.0 秒,帧持续时间为 0.023 秒,则 audio_clock=2.023 秒。这意味着当播放到 audio_buf 的末尾时,时间应推进到 2.023 秒。

2 剩余数据的时间戳修正
当 audio_buf 中有剩余数据(长度为 audio_write_buf_size 字节)时,实际播放数据的 pts 需要调整:

实际数据的pts = is->audio_clock - (double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec;

修正原因:
audio_clock 是整个 audio_buf 的结束时间,但当前可能只播放了其中一部分,剩余数据尚未播放。
需要从 audio_clock 中减去剩余数据的播放时间,以得到当前正在播放的数据的实际时间戳。
修正公式解析:
is->audio_tgt.bytes_per_sec 表示每秒播放的字节数(例如 44100Hz × 2 字节 / 样本 × 2 声道 = 176400 B/s)。
(double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec 计算剩余数据的播放时间(秒)。
例如,若剩余数据为 88200 字节,则播放时间为 88200 / 176400 = 0.5 秒。若 audio_clock=3.0 秒,则实际播放数据的 pts=3.0 - 0.5 = 2.5 秒。

这里的 2 * is->audio_hw_buf_size 表示 SDL 驱动中两个未播放的硬件缓冲区,加上 audio_write_buf_size 得到总延迟,进一步修正时钟,确保与实际播放位置精确匹配。

因此

is->audio_clock -(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size)/ is->audio_tgt.bytes_per_sec,

2.2 视频主流程

ffplay中将视频同步到⾳频的主要⽅案是,如果视频播放过快,则重复播放上⼀帧,以等待⾳频;如果视频播放过慢,则丢帧追赶⾳频。

这⼀部分的逻辑实现在视频输出函数 video_refresh 中在这里插入图片描述
重点如何计算上一帧时长

这⾥与系统时刻的对⽐,引⼊了另⼀个概念——frame_timer。可以理解为帧显示时刻,如更新前,是上⼀帧lastvp的显示时刻;对于更新后( is->frame_timer += delay ),则为当前帧vp显示时刻。
上⼀帧显示时刻加上delay(还应显示多久(含帧本身时⻓))即为上⼀帧应结束显示的时刻

在这里插入图片描述
time1:系统时刻⼩于lastvp结束显示的时刻(frame_timer+dealy),即虚线圆圈位置。此时应该继续显示lastvp
time2:系统时刻⼤于lastvp的结束显示时刻,但⼩于vp的结束显示时刻(vp的显示时间开始于虚线圆圈,结束于⿊⾊圆圈)。此时既不重复显示lastvp,也不丢弃vp,即应显示vp
time3:系统时刻⼤于vp结束显示时刻(⿊⾊圆圈位置,也是nextvp预计的开始显示时刻)。此时应该丢弃vp。

计算delay

delay = compute_target_delay(last_duration, is);

在这里插入图片描述
坐标轴与参数定义:
坐标轴表示视频时钟(video clock)与音频时钟(audio clock)的差值 diff。diff = 0 代表两者完全同步。
坐标轴下方色块表示根据 diff 计算后返回的值,其中 delay 为传入参数,即上一帧(lastvp)的显示时长(frame duration)。sync_threshold 定义了一个允许的同步误差范围,在该范围内认为是 “准同步”,无需调整 lastvp 的显示时长。

  1. delay >AV_SYNC_THRESHOLD_MAX=0.1秒,则sync_threshold = 0.1秒
  2. delay <AV_SYNC_THRESHOLD_MIN=0.04秒,则sync_threshold = 0.04秒
  3. AV_SYNC_THRESHOLD_MIN = 0.0.4秒 <= delay <= AV_SYNC_THRESHOLD_MAX=0.1秒,则sync_threshold为delay本身

同步精度最好的范围是:-0.0.4秒~+0.04秒;
同步精度最差的范围是:-0.1秒~+0.1秒

同步逻辑分析:

  • diff <= -sync_threshold:视频播放速度慢于音频,需适当丢帧。返回值为 MAX(0, delay + diff),确保至少更新画面为当前帧(vp),以追赶音频进度。
  • diff >= sync_threshold 且 delay > AV_SYNC_FRAMEDUP_THRESHOLD(0.1 秒):视频播放快于音频,且当前帧显示时长超过 0.1 秒。返回 delay + diff,此时总时长 delay + diff >= 0.2 秒,具体显示时长由 diff 决定,确保视频与音频逐步对齐。
  • diff >= sync_threshold 且 delay <= 0.1 秒:视频播放快于音频,且当前帧显示时长较短。返回 2 * delay,即重复显示 lastvp 一帧,通过延长该帧显示时间等待音频,使总显示时长不超过 0.2 秒。
  • -sync_threshold < diff < +sync_threshold:处于允许的同步误差范围内,按正常 frame duration 显示视频,直接返回 delay,维持当前播放节奏。

同步策略总结:
该机制通过动态调整视频帧的显示方式(丢帧或重复帧)实现音视频同步。若视频过快,重复上一帧以等待音频;若视频过慢,丢弃部分帧以追赶音频。通过引入 frame_timer 标记帧的显示时刻和应结束显示时刻,并与系统时刻对比,决定具体操作。lastvp 的应结束显示时刻不仅考虑自身显示时长,还纳入了音视频时钟差值。此策略并非要求每时每刻完全同步,而是通过 “准同步” 差值区域(-sync_threshold 至 +sync_threshold)平衡同步精度与系统资源,提升同步效率与稳定性,确保用户感知上的音画协调。

相关文章:

FFplay 音视频同步机制解析:以音频为基准的时间校准与动态帧调整策略

1.⾳视频同步基础 1.2 简介 看视频时&#xff0c;要是声音和画面不同步&#xff0c;体验会大打折扣。之所以会出现这种情况&#xff0c;和音视频数据的处理过程密切相关。音频和视频的输出不在同一个线程&#xff0c;就像两个工人在不同车间工作&#xff0c;而且不一定会同时…...

【Linux笔记】——进程信号的捕捉——从中断聊聊OS是怎么“活起来”的

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;Linux &#x1f339;往期回顾&#x1f339;&#xff1a;【Linux笔记】——进程信号的保存 &#x1f516;流水不争&#xff0c;争的是滔滔不息 一、信号捕捉的流程二、…...

VCS X-PROP建模以及在方针中的应用

VCS X-PROP建模以及在方针中的应用 摘要&#xff1a;VCS X-Prop&#xff08;X-Propagation&#xff09;是 Synopsys VCS 仿真工具中的一种高级功能&#xff0c;用于增强 X 态&#xff08;未知态&#xff09;和 Z 态&#xff08;高阻态&#xff09;在 RTL 仿真中的建模和传播能力…...

OpenSHMEM 介绍和使用指南

OpenSHMEM 介绍和使用指南 什么是 OpenSHMEM&#xff1f; OpenSHMEM 是一个用于并行计算的标准化 API&#xff0c;它提供了一种分区全局地址空间 (PGAS) 编程模型。OpenSHMEM 最初由 Cray 公司开发&#xff0c;后来成为一个开源项目&#xff0c;旨在为高性能计算提供高效的通…...

Electron入门指南:用前端技术打造桌面应用

&#x1f31f; 目录速览 什么是Electron&#xff1f;为什么要用Electron&#xff1f;核心概念三分钟掌握快速创建第一个应用典型应用场景开发注意事项常见问题解答 一、什么是Electron&#xff1f;&#x1f914; Electron就像魔法转换器&#xff0c;它能将你熟悉的&#xff1…...

机器学习第十讲:异常值检测 → 发现身高填3米的不合理数据

机器学习第十讲&#xff1a;异常值检测 → 发现身高填3米的不合理数据 资料取自《零基础学机器学习》。 查看总目录&#xff1a;学习大纲 关于DeepSeek本地部署指南可以看下我之前写的文章&#xff1a;DeepSeek R1本地与线上满血版部署&#xff1a;超详细手把手指南 一、幼儿…...

【Redis】缓存穿透、缓存雪崩、缓存击穿

1.缓存穿透 是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;导致请求直接穿透缓存到达数据库&#xff0c;给数据库带来压力的情况。 常见的解决方案有两种&#xff1a; 缓存空对象&#xff1a;实现简单&#xff0c;维护方便&am…...

科学养生指南:打造健康生活

在快节奏的现代生活中&#xff0c;健康养生成为人们关注的焦点。科学养生无需复杂理论&#xff0c;掌握以下几个关键要素&#xff0c;就能为身体构筑坚实的健康防线。​ 合理饮食是健康的基础。世界卫生组织建议&#xff0c;每天应摄入至少 5 份蔬菜和水果&#xff0c;保证维生…...

解锁健康生活:现代养生实用方案

早上被闹钟惊醒后匆忙灌下咖啡&#xff0c;中午用外卖应付一餐&#xff0c;深夜刷着手机迟迟不肯入睡 —— 这样的生活模式&#xff0c;正在不知不觉侵蚀我们的健康。科学养生并非遥不可及的目标&#xff0c;只需从生活细节入手&#xff0c;就能逐步改善身体状态。​ 饮食管理…...

深入解析JVM字节码解释器执行流程(OpenJDK 17源码实现)

一、核心流程概述 JVM解释器的核心任务是将Java字节码逐条翻译为本地机器指令并执行。其执行流程可分为以下关键阶段&#xff1a; 方法调用入口构建&#xff1a;生成栈帧、处理参数、同步锁等。 字节码分派&#xff08;Dispatch&#xff09;&#xff1a;根据字节码跳转到对应…...

【HCIA】BFD

前言 前面我们介绍了浮动路由以及出口路由器的默认路由配置&#xff0c;可如此配置会存在隐患&#xff0c;就是出口路由器直连的网络设备并不是运营商的路由器&#xff0c;而是交换机。此时我们就需要感知路由器的存活状态&#xff0c;这就需要用到 BFD&#xff08;Bidirectio…...

vue使用路由技术实现登录成功后跳转到首页

文章目录 一、概述二、使用步骤安装vue-router在src/router/index.js中创建路由器&#xff0c;并导出在vue应用实例中使用router声明router-view标签&#xff0c;展示组件内容 三、配置登录成功后跳转首页四、参考资料 一、概述 路由&#xff0c;决定从起点到终点的路径的进程…...

用户模块 - IP归属地框架吞吐测试

一、引言 在很多用户系统中&#xff0c;我们常常需要知道一个IP地址来自哪里&#xff0c;比如判断一个用户是否来自国内、识别异常登录等。而实现这个功能&#xff0c;通常会使用一个“IP归属地解析框架”&#xff0c;它可以根据IP地址返回国家、省份、城市等信息。 不过&#…...

生活实用小工具-手机号归属地查询

一、接口定义 手机号码归属地接口&#xff08;又称手机号查询API&#xff09;是一种通过输入手机号码&#xff0c;快速返回其归属地信息&#xff08;如省份、城市、运营商、区号等&#xff09;的应用程序接口。其数据基础来源于运营商&#xff08;移动、联通、电信&#xff09;…...

鸿蒙-5.1.0-release源码下载

源码获取 前提条件 注册码云gitee帐号。注册码云SSH公钥&#xff0c;请参考码云帮助中心。安装git客户端和git-lfs并配置用户信息。 git config --global user.name "yourname" # 这得和gitee的账号对的上 git config --global user.email "your-email-ad…...

2020年下半年试题三:论云原生架构及其应用

论文库链接&#xff1a;系统架构设计师论文 论文题目 近年来&#xff0c;随着数字化转型不断深入&#xff0c;科技创新与业务发展不断融合&#xff0c;各行各业正在从大工业时代的固化范式进化成面向创新型组织与灵活型业务的崭新模式。在这一背景下&#xff0c;以容器盒微服务…...

Flutter到HarmonyOS Next 的跨越:memory_info库的鸿蒙适配之旅

Flutter到鸿蒙的跨越&#xff1a;memory_info库的鸿蒙适配之旅 本项目作者&#xff1a;kirk/坚果 您可以使用这个Flutter插件来更改应用程序图标上的角标 作者仓库&#xff1a;https://github.com/MrOlolo/memory_info/tree/master/memory_info 在数字化浪潮的推动下&#…...

昆士兰科技大学无人机自主导航探索新框架!UAVNav:GNSS拒止与视觉受限环境中的无人机导航与目标检测

作者&#xff1a; Sebastien Boiteau, Fernando Vanegas, Felipe Gonzalez 单位&#xff1a;昆士兰科技大学电气工程与机器人学院&#xff0c;昆士兰科技大学机器人中心 论文标题&#xff1a;Framework for Autonomous UAV Navigation and Target Detection in Global-Naviga…...

uniapp设置 overflow:auto;右边不显示滚动条的问题

设置了overflow&#xff1a;auto;或者其它overflow的属性不显示滚动条是因为在uniapp中默认隐藏了滚动条 解决方法&#xff1a; //强制显示滚动条 ::-webkit-scrollbar {width: 8px !important;background: #ccc !important;display: block !important;}//设置滚动条颜色.cu-…...

基于SIP协议的VOIP话机认证注册流程分析与抓包验证

话机的认证注册报文怎么看&#xff1f; 在SIP协议中&#xff0c;当VOIP话机首次启动的时候&#xff0c;他会向SIP服务器发送一个Register请求来注册自己的信息地址&#xff0c;&#xff0c;告诉服务器 话机当前在线话机的IP地址和端口是什么话机希望接收通话的联系方式 认证注…...

JS,ES,TS三者什么区别

Java Script(JS)、ECMAScript(ES)、TypeScript(TS) 的核心区别与关联的详细解析,结合技术背景、设计目标及应用场景展开说明: 一、核心定义与关系 JavaScript(JS) 定义:一种动态类型、基于原型的脚本语言,由 Netscape 公司于 1995 年首次开发,用于网页交互功能。角…...

深度理解指针(2)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 深入理解指…...

笔记本/台式机加装PCIe 5.0固态硬盘兼容性与安装方法详解 —— 金士顿Kingston FURY Renegade G5装机指南

在2025年&#xff0c;存储设备市场迎来了革命性的升级浪潮。作为最高性能PCIe 5.0固态硬盘的代表&#xff0c;Kingston FURY Renegade G5 PCIe 5.0 NVMe M.2 固态硬盘不仅刷新了读写速度新高&#xff0c;更以优异的能耗和温控表现成为高端PC、游戏本和工作站升级的“定心丸”。…...

使用libUSB-win32的简单读写例程参考

USB上位机程序的编写&#xff0c;函数的调用过程. 调用 void usb_init(void); 进行初始化 调用usb_find_busses、usb_find_devices和usb_get_busses这三个函数&#xff0c;获得已找到的USB总线序列&#xff1b;然后通过链表遍历所有的USB设备&#xff0c;根据已知的要打开USB设…...

Tailwind CSS 实战教程:从入门到精通

Tailwind CSS 实战教程&#xff1a;从入门到精通 前言 在Web开发的世界里&#xff0c;CSS框架层出不穷。从早期的Bootstrap到现在的Tailwind CSS&#xff0c;前端开发者们总是在寻找更高效、更灵活的样式解决方案。今天&#xff0c;我们就来深入探讨这个被称为"实用优先…...

【IC】如何获取良好的翻转数据来改进dynamic IR drop分析?

动态电压降分析是一个复杂的过程。为了成功执行适当的分析&#xff0c;需要组合多个输入文件和不同的配置设置。 切换场景是任何动态压降分析的关键。设计中的所有门电路和实例不会同时处于活动状态。此外&#xff0c;对于更复杂的单元&#xff0c;可能的切换模式会非常多。这…...

WebGL知识框架

一、WebGL 基础概念 1. WebGL 简介 是什么&#xff1f; 基于 OpenGL ES 的浏览器 3D 图形 API&#xff0c;直接操作 GPU 渲染。 核心特点 底层、高性能、需手动控制渲染管线。 依赖 JavaScript 和 GLSL&#xff08;着色器语言&#xff09;。 与 Three.js 的关系 Three.js…...

集成 ONLYOFFICE 与 AI 插件,为您的服务带来智能文档编辑器

在数字化办公浪潮中&#xff0c;文档处理效率对企业发展具有关键意义。但许多办公平台仅支持基础流程&#xff0c;查阅、批注和修改需借助外部工具&#xff0c;增加了操作复杂性和沟通成本。本文将为开发者介绍如何集成 ONLYOFFICE 文档并利用其中的 AI 插件&#xff0c;智能处…...

Simulink模型回调

Simulink 模型回调函数是一种特殊的 MATLAB 函数&#xff0c;可在模型生命周期的特定阶段自动执行。它们允许用户自定义模型行为、执行初始化任务、验证参数或记录数据。以下是各回调函数的详细说明&#xff1a; 1. PreLoadFcn 触发时机&#xff1a;Simulink 模型加载到内存之…...

网络协议分析 实验五 UDP-IPv6-DNS

文章目录 实验5.1 UDP(User Datagram Protocol)练习二 UDP单播通信练习三 利用仿真编辑器编辑UDP数据包&#xff0c;利用工具接收练习四 UDP受限广播通信练习六 利用仿真编辑器编辑IPV6的UDP数据包并发送实验5.2 DNS(Domain Name System)练习二 仿真编辑DNS查询报文&#xff08…...

共享代理IP vs 动态IP:企业级业务场景的选型深度解析

在数字化转型加速的今天&#xff0c;IP地址管理已成为企业网络架构中的核心命题。无论是跨境电商的多账号运营、大数据采集的精准度保障&#xff0c;还是网络安全的纵深防御&#xff0c;IP解决方案的选择直接关系到业务效能与合规风险。本文将从技术底层逻辑出发&#xff0c;结…...

鸿蒙OSUniApp制作一个小巧的图片浏览器#三方框架 #Uniapp

利用UniApp制作一个小巧的图片浏览器 最近接了个需求&#xff0c;要求做一个轻量级的图片浏览工具&#xff0c;考虑到多端适配的问题&#xff0c;果断选择了UniApp作为开发框架。本文记录了我从0到1的开发过程&#xff0c;希望能给有类似需求的小伙伴一些参考。 前言 移动互联…...

Java并发编程面试题:并发工具类(10题)

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

【Oracle专栏】扩容导致数据文件 dbf 丢失,实操

Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.背景 同事检查扩容情况,发现客户扩容后数据盘后,盘中原有文件丢失,再检查发现数据库没有启动。通过检查发现数据盘中丢失的是oracle的 dbf 表空间文件。数据库无法启动。 检查情况:1)没有rman备份 …...

【Linux】Linux 的管道与重定向的理解

目录 一、了解Linux目录配置标准FHS 二、Linux数据重定向的理解与操作 2.1基本背景 2.2重定向的理解 2.3Linux管道命令的理解与操作 三、Linux 环境变量与PATH 3.1环境变量PATH 一、了解Linux目录配置标准FHS FHS本质&#xff1a;是一套规定Linux目录结构&#xff0c;软…...

【交互 / 差分约束】

题目 代码 #include <bits/stdc.h> using namespace std; using ll long long;const int N 10510; const int M 200 * 500 10; int h[N], ne[M], e[M], w[M], idx; ll d[N]; int n, m; bool st[N]; int cnt[N];void add(int a, int b, int c) {w[idx] c, e[idx] b…...

1. Go 语言环境安装

&#x1f451; 博主简介&#xff1a;高级开发工程师 &#x1f463; 出没地点&#xff1a;北京 &#x1f48a; 人生目标&#xff1a;自由 ——————————————————————————————————————————— 版权声明&#xff1a;本文为原创文章&#xf…...

灰度图像和RGB图像在数据大小和编码处理方式差别

技术背景 好多开发者对灰度图像和RGB图像有些认知差异&#xff0c;今天我们大概介绍下二者差别。灰度图像&#xff08;Grayscale Image&#xff09;和RGB图像在编码处理时&#xff0c;数据大小和处理方式的差别主要体现在以下几个方面&#xff1a; 1. 通道数差异 图像类型通道…...

Lord Of The Root: 1.0.1通关

Lord Of The Root: 1.0.1 来自 <Lord Of The Root: 1.0.1 ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182&#xff0c;靶场IP192.168.23.247 3&…...

虚拟机安装CentOS7网络问题

虚拟机安装CentOS7网络问题 1. 存在的问题1.1 CentOS7详细信息 2. 解决问题3.Windows下配置桥接模式 1. 存在的问题 虽然已经成功在虚拟机上安装了CentOS7&#xff0c;但是依旧不能上网。 1.1 CentOS7详细信息 [fanzhencentos01 ~]$ hostnamectlStatic hostname: centos01Ic…...

AI-02a5a6.神经网络-与学习相关的技巧-批量归一化

批量归一化 Batch Normalization 设置合适的权重初始值&#xff0c;则各层的激活值分布会有适当的广度&#xff0c;从而可以顺利的进行学习。那么&#xff0c;更进一步&#xff0c;强制性的调整激活值的分布&#xff0c;是的各层拥有适当的广度呢&#xff1f;批量归一化&#…...

matlab实现蚁群算法解决公交车路径规划问题

使用蚁群算法解决公交车路径规划问题的MATLAB代码实现&#xff0c;包含详细注释和仿真流程。该算法以站点间行驶时间或距离为优化目标&#xff0c;寻找最优公交路线。 1. 问题建模与参数设置 1.1 输入数据 站点坐标&#xff1a;假设有20个公交站点&#xff0c;随机生成位置。…...

Agent Builder API - Agent Smith 扩展的后端服务(开源代码)

​一、软件介绍 文末提供程序和源码下载 Agent Builder API - Agent Smith 扩展的后端服务&#xff08;开源代码&#xff09;手动设置&#xff1a;在本地计算机中克隆此存储库并启动 python FAST API 服务器。&#xff08;可选&#xff09;安装并设置 Mongo DB。Dev Container…...

C++类和对象之相关特性

C 一.类型转换隐式类型转换 二.static成员一、static成员变量二、static成员函数三、static成员的存储位置四、总结 三.友元一、友元函数二、友元类三、友元成员函数四、总结 四.内部类五.匿名对象六.new 一.类型转换 在C中&#xff0c;类类型转换是指将一个类的对象转换为另一…...

容器编排的革命:Kubernetes如何引领IT的云原生时代

文章目录 Kubernetes的本质&#xff1a;容器世界的智能指挥家Kubernetes的核心功能包括&#xff1a; Kubernetes的演进&#xff1a;从谷歌的实验到全球标准核心技术&#xff1a;Kubernetes的基石与生态1. Pod与容器&#xff1a;最小调度单位2. Deployment&#xff1a;无状态应用…...

2025视频协作工具全景解析:技术跃迁与场景重构

一、技术演进:从功能工具到智能生态 2025年视频协作软件的核心竞争力已从基础功能升级为技术生态的构建。以分秒帧为例,其音视频生产协作系统,可帮助创作者在云端构建工作流,让跨地域、跨终端、跨团队的协作组可以在统一的安全平台上管理所有媒体资源、任务、反馈信息,从而更高…...

保持视频二维码不变,更新视频的内容

视频替换功能允许用户在保持视频二维码、观看地址和调用代码不变的情况下更新视频内容&#xff0c;从而节省重新印刷物料的成本。这一功能适用于多种场景&#xff0c;如营销宣传、产品操作手册、设备说明书等&#xff0c;当视频内容需要修改或更新时&#xff0c;用户只需上传新…...

Linux常用命令40——alias设置命令别名

在使用Linux或macOS日常开发中&#xff0c;熟悉一些基本的命令有助于提高工作效率&#xff0c;alias命令来自英文单词alias&#xff0c;中文译为“别名”&#xff0c;其功能是设置命令别名信息。我们可以使用alias将一些较长的命令进行简写&#xff0c;往往几十个字符的命令会变…...

Java大师成长计划之第22天:Spring Cloud微服务架构

&#x1f4e2; 友情提示&#xff1a; 本文由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;平台gpt-4o-mini模型辅助创作完成&#xff0c;旨在提供灵感参考与技术分享&#xff0c;文中关键数据、代码与结论建议通过官方渠道验证。 随着企业应用的不断扩展&#xff0c…...

为什么go语言中返回的指针类型,不需要用*取值(解引用),就可以直接赋值呢?

Go 中返回的是指针&#xff0c;但你却能直接用“.”访问字段&#xff0c;看起来像是“没有解引用”&#xff0c;其实是 Go 帮你自动处理了“指针解引用”的语法糖。 在 Go 中&#xff0c;如果你有一个结构体指针&#xff08;例如 *FileMeta&#xff09;&#xff0c;你可以直接…...