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

1. 数据结构之VideoState

  • VideoState是所有其他数据结构的母体。
  • main 线程启动新线程read_thread,初始化VideoState。
    • AVFormatContext保存与“读文件””和“demux”有关的上下文。
    • 在io_open_default()中,遍历protocol列表,根据url(filename)格式找出对应的处理函数集。这里假设打开的文件名是“/avm.mp4”,这是一个本地文件的地址,所以找到函数集file_xxx()。
    • AVIOContext的函数集io_xxx()是对下层的URLContext操作的一层简单包装。
    • 用AVIOContext读取文件头部的probe数据段。然后遍历input format列表,使用infput format的probe()函数检查probe数据段,分析与input format的匹配度。匹配度用一个整型变量score表示。在匹配的infput format中选择score值最高的。这里mov格式文件,所以找出demux函数集mov_xxx()。
    • 调用mov_read_header(),得到文件的流信息,保存在AVStream[]数组中。流信息包括流的数目和类型,以及对应decoder的codec_id等。这里只有一条video stream,codec_id是h264 decoder的id。
    • 查找decoder列表,根据AVStream中的codec_id找到decoder。这里对应的decode函数集是h264_xxx()。
  • read_thread线程启动新的video_thread线程。在Video_thread中做decode。然后read_thread线程继续做demux。

2. 数据结构之PacketQueue和FrameQueue

  • demux解析出来的AVPacket保存在PacketQueue中。
  • AVCodec读取PacketQueue中的AVPacket并decode,得到的Frame保存在FrameQueue中。
  • 在SDL设备上显示Frame。显示Frame前要进行时间同步。Clock是协助做时间同步的工具类。

3. 线程

  • 上图是ffplay的线程模型。
  • read_thread线程读文件并demux,将packet放入PacketQueue中;video_thread从PacketQueue取出packet并decode,将frame放入FrameQueue;main thread从FrameQueue中取出frame,显示在SDL显示设备上。
  • SDL_TimerThread可能是在早先的版本中用于frame的时间同步。现在已经不用了。

4. FrameQueue

  • FrameQueue是一个环形buffer。它的成员变量中,windex是写指针,rindex是读指针。
  • video stream与audio stream不同的一点是,video需要保留最后一个frame以便随时刷新显示。FrameQueue的keep_last和rindex_shown就是为了这个准备的。
  • 当keep_last为1时,FrameQueue总是保留最后一个frame。 这个frame还在FrameQueue中,但调用者得到的queue大小不包含它。调用者也不从FrameQueue中移除它。

5. 时间同步之struct Clock

  • Sample/Frame的时间戳可能有两种来源:录制时标记,或者decoder根据sample rate附加。播放frame时要参考一个本地时钟。时钟同步的实质,是在一系列的本地时间点显示与之对应的frame。
  • 如下图,Sample是frame 时间,Local Clock为本地时间。正常播放速度下,如果在本地时间2000(c1)时开始播放第一个frame(frame时间s1),那么我们应该在本地时间2040(c2)播放frame时间为140(s2)的frame,在本地时间2080(c3)播放frame时间为180(s3)的frame。
  • 还要考虑快进(播放速度speed > 1)或者慢进(播放速度0 < speed < 1)的情况。
  • 综上所述,时间同步的实质问题是:在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中get_clock()函数使用的等式:

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); // << 这里!
  }
}
  • 注意:如果播放时用户暂停又继续播放,或者直接调整了frame的播放位置,则必须重置s1和c1。如果用户切换到快进或慢进模式,则必须重置speed。

6. 时间同步之refresh_loop_wait_event()

  • 前面说过,在ffplay早前的版本中,使用SDL_TimerThread提供的定时回调做同步,当前版本(ffmpeg 3.4)已经不再用了。现在的基本策略是:main_thread线程每隔0.01秒(定义为常量REFRESH_RATE)刷新一次显示。
  • Frame delay以0.01的间隔减少,最后一段剩余时间会比0.01短,这时下一次刷新时间会不到0.01秒。
  • 按照正常24 frame/秒的频率,每frame的delay是0.042秒,所以0.01秒够短,不至于跳过某一个frame的显示。
  • refresh_loop_wait_event() 大约每隔0.01秒调用一次video_refresh()。

7. 时间同步之video_refresh()

  • Frame有一个serial整型成员。这个成员初始值为0,每次开始新video段时递增1,所以serial可以用来判断时间同步是否需要重新开始。
    • 用户调整播放位置时会开始新段,video初始播放时也会开始新段。
    • Serial值由decoder设置。PacketQueue和FrameQueue也各有一个serial成员,标记Queue中最新packet/frame的serial。开始新段时调用decoder_start(),往PacketQueue中放入一个特殊的flush packet,并递增PacketQueue.serial。后面产生的packet会用新的serial标记。FrameQueue及其frame也同样处理。
  • 播放video有两种模式:
    • 一种是只管video stream的时钟就好了,不用与其他时钟同步,运行时带参数-sync video,如:

ffplay -sync video avm.mp4

  • 另一种是video需要参考其他时钟,可能是audio stream的时钟,也可能是其他外部时钟,运行时指定-sync audio或-sync ext。-sync ext是默认选项。

  • 第一种模式下的逻辑很简单,用Clock就可以。但是ffplay没有用Clock。

    • 如果新段开始,则设置一个变量frame_timer记录当前时间;
    • 根据前后两个frame的pts差值,计算当前显示的frame的delay。
    • 如果frame没超时(current_time < frame_timer + delay),则继续显示当前frame,否则显示下一个frame;
    • frame_timer累加delay。
  • 第二种的模式需要使用术语“master时钟”和“slave时钟”。video时钟是slave时钟,被参考的audio时钟或ext 时钟是master时钟。这种模式中,不但frame的delay要根据slave与master的差异再做修正,slave时钟本身也要修正。

    • 如果开始新段,则设置一个变量frame_timer记录当前时间;
    • 根据前后两个frame的pts差值计算当前显示的frame的delay。
    • 如果frame没超时(current_time < frame_timer + delay),则继续显示当前frame,否则显示下一个frame;
    • 比较Slave和Master,修正delay。如下图,如果本地时间为10000,并算出对应master的pts为100.0。

Slave算出对应的pts可能落在如下区间。
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。

  • 根据当前frame的pts重置slave时钟。

  • Frame_timer是一个与frame关联的累加量,而步骤h)可能丢弃frame,所以导致frame_timer值偏小。如果偏差太大,则将frame_timer修正到本地时间。

  • 调整后的delay和framer_timer可能导致连续的frame超时。这时应该跳过前面的frame,只显示最后一个frame。

  • 从两种模式的分析看,ffplay的frame_timer没有考虑speed,所以它不支持快进和慢进!后一种模式用了Clock修正slave与master的差异,也不影响frame_timer的speed特性。

    • 从Show_help_default()的选项看,ffplay的播放控制操作也没有快进慢进控制。

+下图是video_refresh的流程图:

8. calculate_display_rect()

这个函数涉及到一个一般碰不到的主题: aspect ratio

DAR = SAR × PAR*

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

详细信息可以参考:
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.

相关链接

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源代码分析 (三)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容