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)平衡同步精度與系統資源,提升同步效率與穩定性,確保用戶感知上的音畫協調。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/905461.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905461.shtml
英文地址,請注明出處:http://en.pswp.cn/news/905461.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

車載網關--- 職責邊界劃分與功能解耦設計

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 鈍感力的“鈍”,不是木訥、遲鈍,而是直面困境的韌勁和耐力,是面對外界噪音的通透淡然。 生活中有兩種人,一種人格外在意別人的眼光;另一種人無論…

最優化方法Python計算:有約束優化應用——近似線性可分問題支持向量機

二分問題的數據集 { ( x i , y i ) } \{(\boldsymbol{x}_i,y_i)\} {(xi?,yi?)}&#xff0c; i 1 , 2 , ? , m i1,2,\cdots,m i1,2,?,m中&#xff0c;特征數據 { x i } \{\boldsymbol{x}_i\} {xi?}未必能被一塊超平面按其標簽值 y i ∈ { ? 1 , 1 } y_i\in\{-1,1\} yi?∈…

aardio - 將文本生成CSS格式顯示

import win.ui; /*DSG{{*/ var winform win.form(text"aardio form";right759;bottom469) winform.add( button{cls"button";text"Button";left340;top130;right430;bottom180;z3}; edit{cls"edit";text"我是一串文本";lef…

數字IC后端設計實現 | 如何自動刪除Innovus 中冗余的hold buffer?

我們都知道在postCTS階段做optDesign時序優化時需要進行hold violation的fixing。所以這個過程勢必要通過插hold buffer來解決hold violation。這類hold buffer的名字帶有"PHC"的關鍵詞。 select_obj [dbGet top.insts.name PHC] llength [dbGet top.insts.name PH…

c# 倒序方法

在C#中&#xff0c;有幾種方法可以對List進行倒序排列&#xff1a; 1. 使用List的Reverse()方法&#xff08;原地反轉&#xff09; List<int> numbers new List<int> { 1, 2, 3, 4, 5 };numbers.Reverse(); // 直接修改原列表// 結果&#xff1a;5, 4, 3, 2, 1 …

【內網滲透】——S4u2擴展協議提權以及KDC欺騙提權

【內網滲透】——S4u2擴展協議提權以及KDC欺騙提權 文章目錄 【內網滲透】——S4u2擴展協議提權以及KDC欺騙提權[toc]一&#xff1a;Kerberos 委派攻擊原理之 S4U2利用1.1原理1.2兩種擴展協議**S4U2Self (Service for User to Self)****S4U2Proxy (Service for User to Proxy)*…

AD 間距規則與布線規則

1. 打開在線規則檢查 2. 間距規則 一般來說最小間距設為6mil 注意&#xff1a; AD22 也提供類似低版本那樣的、多個間距規則疊加的方法&#xff0c;通過選擇第一個適配對象和第二個適配對象來篩選對象和范圍。 ① Where The First Object Matches &#xff1a;選擇規則第一個…

Android Studio 安裝與配置完全指南

文章目錄 第一部分&#xff1a;Android Studio 簡介與安裝準備1.1 Android Studio 概述1.2 系統要求Windows 系統&#xff1a;macOS 系統&#xff1a;Linux 系統&#xff1a; 1.3 下載 Android Studio 第二部分&#xff1a;安裝 Android Studio2.1 Windows 系統安裝步驟2.2 mac…

springboot踩坑記錄

之前運行好端端的項目&#xff0c;今天下午打開只是添加了一個文件之后 再運行都報Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured.Reason: Failed to determine a suitable driver class Action: Conside…

【計算機視覺】OpenCV實戰項目:Deep Machine Learning Tutors:基于OpenCV的實時面部識別系統深度解析

Deep Machine Learning Tutors&#xff1a;基于OpenCV的實時面部識別系統深度解析 1. 項目概述2. 技術原理2.1 面部識別流程2.2 關鍵技術組件2.2.1 Haar級聯分類器2.2.2 深度特征提取 3. 項目實現細節3.1 系統架構3.2 核心算法實現3.2.1 人臉檢測3.2.2 實時處理流水線 4. 項目運…

Flutter在鍵盤的上方加一個完成按鈕

有些情況下&#xff0c;輸入框在輸入鍵盤彈出后&#xff0c; 需要在鍵盤的上方顯示一個toolbar &#xff0c; 然后 toolbar 上面一個完成按鈕&#xff0c;點完成按鈕把鍵盤關閉。 如圖&#xff1a; 直接上代碼&#xff0c;這樣寫的好處是&#xff0c;把 TextField 給封裝了&…

Flink SQL 將kafka topic的數據寫到另外一個topic里面

-- 創建源表&#xff0c;使用 RAW 格式接收原始 JSON 數據 CREATE TABLE source_kafka ( id STRING, data STRING ) WITH ( connector kafka, topic source_kafka-topic, properties.bootstrap.servers master01:9092, properties.group.id flink-kafka-group, scan.startu…

618開售僅1小時,李佳琦直播間加購同增超10%

5月13日晚8點&#xff0c;天貓618大促正式拉開帷幕&#xff0c;李佳琦直播間首日“爆款美妝節”公布首輪戰報&#xff1a;首小時加購GMV同比增長超10%&#xff0c;可復美、珀萊雅等品牌超60萬件國貨爆品秒售罄。 據統計&#xff0c;今年李佳琦直播間618首日預售共上架近500件爆…

【輕松學 C:編程小白的大冒險】— 16 函數的定義與調用

在編程的藝術世界里&#xff0c;代碼和靈感需要尋找到最佳的交融點&#xff0c;才能打造出令人為之驚嘆的作品。而在這座秋知葉i博客的殿堂里&#xff0c;我們將共同追尋這種完美結合&#xff0c;為未來的世界留下屬于我們的獨特印記。 【輕松學 C&#xff1a;編程小白的大冒險…

多模態大語言模型arxiv論文略讀(七十四)

UniQA: Unified Vision-Language Pre-training for Image Quality and Aesthetic Assessment ?? 論文標題&#xff1a;UniQA: Unified Vision-Language Pre-training for Image Quality and Aesthetic Assessment ?? 論文作者&#xff1a;Hantao Zhou, Longxiang Tang, Ru…

Flutter - UIKit開發相關指南 - 線程和異步

線程和異步 編寫異步代碼 Dart采用單線程執行模型,支持Isolates(在另一個線程上運行Dart代碼)、事件循環和異步編程。除非生成一個Isolates&#xff0c;否則Dart代碼將在主UI線程中運行&#xff0c;并由事件循環驅動。Flutter的事件循環相當于iOS的主線程上的RunLoop。 Dart…

【愚公系列】《Manus極簡入門》038-數字孿生設計師:“虛實映射師”

&#x1f31f;【技術大咖愚公搬代碼&#xff1a;全棧專家的成長之路&#xff0c;你關注的寶藏博主在這里&#xff01;】&#x1f31f; &#x1f4e3;開發者圈持續輸出高質量干貨的"愚公精神"踐行者——全網百萬開發者都在追更的頂級技術博主&#xff01; &#x1f…

西門子WinCC Unified PC的GraphQL使用手冊

TIA V20版本&#xff1a;添加用戶 添加角色&#xff0c;并充分授權&#xff0c;尤其是GraphQL的讀寫權限。 通過SIMATIC Runtime Manager啟動wincc unifi工程。 打開瀏覽器&#xff0c;訪問本地的https://localhost/graphql/&#xff0c;運行正常如圖&#xff1a; 連接外…

開源長期主義:淺談DeepSeek技術主張與早期論文

開源、長期主義與DeepSeek的技術愿景 ©作者|格林 來源|神州問學 導入&#xff1a;Deepseek在早期就開源了許多優秀的指令模型與對話模型&#xff0c;并發布了多篇論文。以下&#xff0c;我們將基于Deepseek在早期發布的6篇論文&#xff0c;來梳理Deepseek公司的技術路徑與…

TTS-Web-Vue系列:Vue3實現內嵌iframe文檔顯示功能

&#x1f5bc;? 本文是TTS-Web-Vue系列的新篇章&#xff0c;重點介紹如何在Vue3項目中優雅地實現內嵌iframe功能&#xff0c;用于加載外部文檔內容。通過Vue3的響應式系統和組件化設計&#xff0c;我們實現了一個功能完善、用戶體驗友好的文檔嵌入方案&#xff0c;包括加載狀態…