FFMPEG 3.4.2 - ffplay源代碼分析 (三)

1. 數(shù)據(jù)結(jié)構(gòu)之VideoState

  • VideoState是所有其他數(shù)據(jù)結(jié)構(gòu)的母體。
  • main 線程啟動(dòng)新線程read_thread,初始化VideoState。
    • AVFormatContext保存與“讀文件””和“demux”有關(guān)的上下文。
    • 在io_open_default()中,遍歷protocol列表,根據(jù)url(filename)格式找出對(duì)應(yīng)的處理函數(shù)集。這里假設(shè)打開(kāi)的文件名是“/avm.mp4”,這是一個(gè)本地文件的地址,所以找到函數(shù)集file_xxx()。
    • AVIOContext的函數(shù)集io_xxx()是對(duì)下層的URLContext操作的一層簡(jiǎn)單包裝。
    • 用AVIOContext讀取文件頭部的probe數(shù)據(jù)段。然后遍歷input format列表,使用infput format的probe()函數(shù)檢查probe數(shù)據(jù)段,分析與input format的匹配度。匹配度用一個(gè)整型變量score表示。在匹配的infput format中選擇score值最高的。這里mov格式文件,所以找出demux函數(shù)集mov_xxx()。
    • 調(diào)用mov_read_header(),得到文件的流信息,保存在AVStream[]數(shù)組中。流信息包括流的數(shù)目和類型,以及對(duì)應(yīng)decoder的codec_id等。這里只有一條video stream,codec_id是h264 decoder的id。
    • 查找decoder列表,根據(jù)AVStream中的codec_id找到decoder。這里對(duì)應(yīng)的decode函數(shù)集是h264_xxx()。
  • read_thread線程啟動(dòng)新的video_thread線程。在Video_thread中做decode。然后read_thread線程繼續(xù)做demux。

2. 數(shù)據(jù)結(jié)構(gòu)之PacketQueue和FrameQueue

  • demux解析出來(lái)的AVPacket保存在PacketQueue中。
  • AVCodec讀取PacketQueue中的AVPacket并decode,得到的Frame保存在FrameQueue中。
  • 在SDL設(shè)備上顯示Frame。顯示Frame前要進(jìn)行時(shí)間同步。Clock是協(xié)助做時(shí)間同步的工具類。

3. 線程

  • 上圖是ffplay的線程模型。
  • read_thread線程讀文件并demux,將packet放入PacketQueue中;video_thread從PacketQueue取出packet并decode,將frame放入FrameQueue;main thread從FrameQueue中取出frame,顯示在SDL顯示設(shè)備上。
  • SDL_TimerThread可能是在早先的版本中用于frame的時(shí)間同步?,F(xiàn)在已經(jīng)不用了。

4. FrameQueue

  • FrameQueue是一個(gè)環(huán)形buffer。它的成員變量中,windex是寫指針,rindex是讀指針。
  • video stream與audio stream不同的一點(diǎn)是,video需要保留最后一個(gè)frame以便隨時(shí)刷新顯示。FrameQueue的keep_last和rindex_shown就是為了這個(gè)準(zhǔn)備的。
  • 當(dāng)keep_last為1時(shí),F(xiàn)rameQueue總是保留最后一個(gè)frame。 這個(gè)frame還在FrameQueue中,但調(diào)用者得到的queue大小不包含它。調(diào)用者也不從FrameQueue中移除它。

5. 時(shí)間同步之struct Clock

  • Sample/Frame的時(shí)間戳可能有兩種來(lái)源:錄制時(shí)標(biāo)記,或者decoder根據(jù)sample rate附加。播放frame時(shí)要參考一個(gè)本地時(shí)鐘。時(shí)鐘同步的實(shí)質(zhì),是在一系列的本地時(shí)間點(diǎn)顯示與之對(duì)應(yīng)的frame。
  • 如下圖,Sample是frame 時(shí)間,Local Clock為本地時(shí)間。正常播放速度下,如果在本地時(shí)間2000(c1)時(shí)開(kāi)始播放第一個(gè)frame(frame時(shí)間s1),那么我們應(yīng)該在本地時(shí)間2040(c2)播放frame時(shí)間為140(s2)的frame,在本地時(shí)間2080(c3)播放frame時(shí)間為180(s3)的frame。
  • 還要考慮快進(jìn)(播放速度speed > 1)或者慢進(jìn)(播放速度0 < speed < 1)的情況。
  • 綜上所述,時(shí)間同步的實(shí)質(zhì)問(wèn)題是:在s1,c1,和speed是確定常量的條件下,如何從c2得到s2?答案可以表述為以下等式:

s2 = s1 + (c2 - c1 ) * speed

如果引入新的變量重新表述:

drift = s1 - c1,last_upt = c1, cur_time = c2,

則新的等式如下:

S2 = drift + c1 + (c2 - c1) * speed
= drift + last_upt + ( cur_time - last_upt) * speed
= drift + last_upt + (cur_time - last_upt) * (1 - (1 - speed) )
= drift + cur_time - (cur_time - last_upt)*(1 - speed)

這就是ffplay中g(shù)et_clock()函數(shù)使用的等式:

double get_clock(Clock *c)
{
  if (*c->queue_serial != c->serial)
      return NAN;
  if (c->paused) {
      return c->pts;
  } else {
      double time = av_gettime_relative() / 1000000.0;
      return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed); // << 這里!
  }
}
  • 注意:如果播放時(shí)用戶暫停又繼續(xù)播放,或者直接調(diào)整了frame的播放位置,則必須重置s1和c1。如果用戶切換到快進(jìn)或慢進(jìn)模式,則必須重置speed。

6. 時(shí)間同步之refresh_loop_wait_event()

  • 前面說(shuō)過(guò),在ffplay早前的版本中,使用SDL_TimerThread提供的定時(shí)回調(diào)做同步,當(dāng)前版本(ffmpeg 3.4)已經(jīng)不再用了。現(xiàn)在的基本策略是:main_thread線程每隔0.01秒(定義為常量REFRESH_RATE)刷新一次顯示。
  • Frame delay以0.01的間隔減少,最后一段剩余時(shí)間會(huì)比0.01短,這時(shí)下一次刷新時(shí)間會(huì)不到0.01秒。
  • 按照正常24 frame/秒的頻率,每frame的delay是0.042秒,所以0.01秒夠短,不至于跳過(guò)某一個(gè)frame的顯示。
  • refresh_loop_wait_event() 大約每隔0.01秒調(diào)用一次video_refresh()。

7. 時(shí)間同步之video_refresh()

  • Frame有一個(gè)serial整型成員。這個(gè)成員初始值為0,每次開(kāi)始新video段時(shí)遞增1,所以serial可以用來(lái)判斷時(shí)間同步是否需要重新開(kāi)始。
    • 用戶調(diào)整播放位置時(shí)會(huì)開(kāi)始新段,video初始播放時(shí)也會(huì)開(kāi)始新段。
    • Serial值由decoder設(shè)置。PacketQueue和FrameQueue也各有一個(gè)serial成員,標(biāo)記Queue中最新packet/frame的serial。開(kāi)始新段時(shí)調(diào)用decoder_start(),往PacketQueue中放入一個(gè)特殊的flush packet,并遞增PacketQueue.serial。后面產(chǎn)生的packet會(huì)用新的serial標(biāo)記。FrameQueue及其frame也同樣處理。
  • 播放video有兩種模式:
    • 一種是只管video stream的時(shí)鐘就好了,不用與其他時(shí)鐘同步,運(yùn)行時(shí)帶參數(shù)-sync video,如:

ffplay -sync video avm.mp4

  • 另一種是video需要參考其他時(shí)鐘,可能是audio stream的時(shí)鐘,也可能是其他外部時(shí)鐘,運(yùn)行時(shí)指定-sync audio或-sync ext。-sync ext是默認(rèn)選項(xiàng)。

  • 第一種模式下的邏輯很簡(jiǎn)單,用Clock就可以。但是ffplay沒(méi)有用Clock。

    • 如果新段開(kāi)始,則設(shè)置一個(gè)變量frame_timer記錄當(dāng)前時(shí)間;
    • 根據(jù)前后兩個(gè)frame的pts差值,計(jì)算當(dāng)前顯示的frame的delay。
    • 如果frame沒(méi)超時(shí)(current_time < frame_timer + delay),則繼續(xù)顯示當(dāng)前frame,否則顯示下一個(gè)frame;
    • frame_timer累加delay。
  • 第二種的模式需要使用術(shù)語(yǔ)“master時(shí)鐘”和“slave時(shí)鐘”。video時(shí)鐘是slave時(shí)鐘,被參考的audio時(shí)鐘或ext 時(shí)鐘是master時(shí)鐘。這種模式中,不但frame的delay要根據(jù)slave與master的差異再做修正,slave時(shí)鐘本身也要修正。

    • 如果開(kāi)始新段,則設(shè)置一個(gè)變量frame_timer記錄當(dāng)前時(shí)間;
    • 根據(jù)前后兩個(gè)frame的pts差值計(jì)算當(dāng)前顯示的frame的delay。
    • 如果frame沒(méi)超時(shí)(current_time < frame_timer + delay),則繼續(xù)顯示當(dāng)前frame,否則顯示下一個(gè)frame;
    • 比較Slave和Master,修正delay。如下圖,如果本地時(shí)間為10000,并算出對(duì)應(yīng)master的pts為100.0。

Slave算出對(duì)應(yīng)的pts可能落在如下區(qū)間。
a. (~, 100-delay),則slave播放落后了,所以要減少delay;
b. (100-delay, 100+delay ),這是正常偏移,不做修正;
c. (100+delay, 100.1),則slave超前了,所以要小幅增加delay;
d. (100.1, ~),則slave太超前了,所以大幅增加delay。

  • frame_timer累加delay。

  • 根據(jù)當(dāng)前frame的pts重置slave時(shí)鐘。

  • Frame_timer是一個(gè)與frame關(guān)聯(lián)的累加量,而步驟h)可能丟棄frame,所以導(dǎo)致frame_timer值偏小。如果偏差太大,則將frame_timer修正到本地時(shí)間。

  • 調(diào)整后的delay和framer_timer可能導(dǎo)致連續(xù)的frame超時(shí)。這時(shí)應(yīng)該跳過(guò)前面的frame,只顯示最后一個(gè)frame。

  • 從兩種模式的分析看,ffplay的frame_timer沒(méi)有考慮speed,所以它不支持快進(jìn)和慢進(jìn)!后一種模式用了Clock修正slave與master的差異,也不影響frame_timer的speed特性。

    • 從Show_help_default()的選項(xiàng)看,ffplay的播放控制操作也沒(méi)有快進(jìn)慢進(jìn)控制。

+下圖是video_refresh的流程圖:

8. calculate_display_rect()

這個(gè)函數(shù)涉及到一個(gè)一般碰不到的主題: aspect ratio

DAR = SAR × PAR*

DAR = Display Aspect Ratio, SAR = Storage Aspect Ratio, PAR = Pixel Aspect Ratio

詳細(xì)信息可以參考:
https://en.wikipedia.org/wiki/Aspect_ratio_(image)

Distinctions
Further information: Pixel aspect ratio

This article primarily addresses the aspect ratio of images as displayed, which is more formally referred to as the display aspect ratio (DAR). In digital images, there is a distinction with the storage aspect ratio (SAR), which is the ratio of pixel dimensions. If an image is displayed with square pixels, then these ratios agree; if not, then non-square, "rectangular" pixels are used, and these ratios disagree. The aspect ratio of the pixels themselves is known as the pixel aspect ratio (PAR) – for square pixels this is 1:1 – and these are related by the identity:

SAR × PAR = DAR.

Rearranging (solving for PAR) yields:

PAR = DAR/SAR.

For example, a 640 × 480 VGA image has a SAR of 640/480 = 4:3, and if displayed on a 4:3 display (DAR = 4:3), has square pixels, hence a PAR of 1:1. By contrast, a 720 × 576 D-1 PAL image has a SAR of 720/576 = 5:4, but is displayed on a 4:3 display (DAR = 4:3), so by this formula it would have a PAR of (4:3)/(5:4) = 16:15.

However, because standard definition digital video was originally based on digitally sampling analog television, the 720 horizontal pixels actually capture a slightly wider image to avoid loss of the original analog picture. In actual images, these extra pixels are often partly or entirely black, as only the center 704 horizontal pixels carry actual 4:3 or 16:9 image. Hence, the actual pixel aspect ratio for PAL video is a little different from that given by the formula, specifically 12:11 for PAL and 10:11 for NTSC. For consistency, the same effective pixel aspect ratios are used even for standard definition digital video originated in digital form rather than converted from analog. For more details refer to the main article.

In analog images such as film there is no notion of pixel, nor notion of SAR or PAR, and "aspect ratio" refers unambiguously to DAR. Actual displays do not generally have non-square pixels, though digital sensors might; they are rather a mathematical abstraction used in resampling images to convert between resolutions.

Non-square pixels arise often in early digital TV standards, related to digitalization of analog TV signals – whose horizontal and vertical resolutions differ and are thus best described by non-square pixels – and also in some digital videocameras and computer display modes, such as Color Graphics Adapter (CGA). Today they arise particularly in transcoding between resolutions with different SARs.

DAR is also known as image aspect ratio and picture aspect ratio, though the latter can be confused with pixel aspect ratio.

相關(guān)鏈接

FFMPEG 3.4.2 - ffmpeg源代碼分析 (一)
FFMPEG 3.4.2 - ffmpeg源代碼分析 (二)
FFMPEG 3.4.2 - ffmpeg源代碼分析 (三)
FFMPEG 3.4.2 - ffmpeg源代碼分析 (四)- x264
FFMPEG 3.4.2 - ffplay源代碼分析 (一)
FFMPEG 3.4.2 - ffplay源代碼分析 (二)
FFMPEG 3.4.2 - ffplay源代碼分析 (三)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容