最近在看ffmpeg相关的一些东西,以及一些播放器相关资料和代码。

然后对于ffmpeg-2.0.1版本下的ffplay进行了大概的代码阅读,其中这里把里面的音视频同步,按个人的理解,暂时在这里作个笔记。

在ffplay2.0.1版本里面,视频的刷新不再直接使用SDL里面的定时器了,而是在主的循环中event_loop中,通过调用函数refresh_loop_wait_event来等待事件,

同时在这个refresh_loop_wait_event函数里面,通过使用休眠函数av_usleep 来进行定时刷新视频。

调用视频更新的代码:

 static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
double remaining_time = 0.0;
SDL_PumpEvents();/*不停的循环内部更新消息*/
while (!SDL_PeepEvents(event, , SDL_GETEVENT, SDL_ALLEVENTS)) {/*check the event queue for messages*/
if (!cursor_hidden && av_gettime() - cursor_last_shown > CURSOR_HIDE_DELAY) {
SDL_ShowCursor();
cursor_hidden = ;
}
9 if (remaining_time > 0.0)
10 av_usleep((int64_t)(remaining_time * 1000000.0));/*使用这个函数来休眠,取代之前版本中的定时器*/
11 remaining_time = REFRESH_RATE;/*10ms*/
12 if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
13 video_refresh(is, &remaining_time);
SDL_PumpEvents();
}
}

然后接下来,我们来看看video_refresh函数里面做了些什么事情吧!

代码如下:

 /* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
VideoState *is = opaque;
VideoPicture *vp;
double time; SubPicture *sp, *sp2; if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)/*如果用外部时钟同步的话*/
check_external_clock_speed(is); if (!display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) {
time = av_gettime() / 1000000.0;
if (is->force_refresh || is->last_vis_time + rdftspeed < time) {/*强制刷新视频*/
video_display(is);
is->last_vis_time = time;/*记录本次的时间*/
}
*remaining_time = FFMIN(*remaining_time, is->last_vis_time + rdftspeed - time);
} if (is->video_st) {
int redisplay = ;
if (is->force_refresh)
redisplay = pictq_prev_picture(is);
retry:
if (is->pictq_size == ) {/*如果缓冲区没有数据*/
SDL_LockMutex(is->pictq_mutex);
if (is->frame_last_dropped_pts != AV_NOPTS_VALUE && is->frame_last_dropped_pts > is->frame_last_pts) {
update_video_pts(is, is->frame_last_dropped_pts, is->frame_last_dropped_pos, is->frame_last_dropped_serial);
is->frame_last_dropped_pts = AV_NOPTS_VALUE;
}
SDL_UnlockMutex(is->pictq_mutex);
// nothing to do, no picture to display in the queue
} else {
double last_duration, duration, delay;
/* dequeue the picture */
vp = &is->pictq[is->pictq_rindex]; if (vp->serial != is->videoq.serial) {
pictq_next_picture(is);
redisplay = ;
goto retry;
} if (is->paused)
goto display; 49 /* compute nominal last_duration *//*通过计算当前要显示的帧和上一帧pts的差来预测当期帧显示时间---预测--->下一帧的到来时间*/
50 last_duration = vp->pts - is->frame_last_pts;/*计算上一帧的显示时间(名义上)*/
51 if (!isnan(last_duration) && last_duration > 0 && last_duration < is->max_frame_duration) {/*判断上一帧显示的时间是否在范围内*/
52 /* if duration of the last frame was sane, update last_duration in video state */
53 is->frame_last_duration = last_duration;/*更新一帧的持续显示时间*/
54 }
55 if (redisplay)
56 delay = 0.0;
57 else
58 delay = compute_target_delay(is->frame_last_duration, is);/*通过上一帧的情况来预测本次的情况,这样可以得到下一帧的到来时间*/
59
60 time= av_gettime()/1000000.0;
61 if (time < is->frame_timer + delay && !redisplay) {/**/
62 *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
63 return;
64 }
65
66 is->frame_timer += delay;
67 if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)
68 is->frame_timer = time;
69
70 SDL_LockMutex(is->pictq_mutex);
71 if (!redisplay && !isnan(vp->pts))
72 update_video_pts(is, vp->pts, vp->pos, vp->serial);/*更新当前帧pts和pos*/
73 SDL_UnlockMutex(is->pictq_mutex);
74
75 if (is->pictq_size > 1) {/*如果缓冲中帧数比较多的时候,例如下一帧也已经到了*/
76 VideoPicture *nextvp = &is->pictq[(is->pictq_rindex + 1) % VIDEO_PICTURE_QUEUE_SIZE];
77 duration = nextvp->pts - vp->pts;/*这个时候,应该用已经在缓存中的下一帧pts-当前pts来真实计算当前持续显示时间*/
78 if(!is->step && (redisplay || framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){/*如果延迟时间超过一帧了,就采取丢掉当前帧*/
79 if (!redisplay)
80 is->frame_drops_late++;
81 pictq_next_picture(is);/*采取丢帧策略,丢弃迟来的帧,取下一帧*/
82 redisplay = 0;
83 goto retry;
84 }
} if (is->subtitle_st) {
while (is->subpq_size > ) {
sp = &is->subpq[is->subpq_rindex]; if (is->subpq_size > )
sp2 = &is->subpq[(is->subpq_rindex + ) % SUBPICTURE_QUEUE_SIZE];
else
sp2 = NULL; if (sp->serial != is->subtitleq.serial
|| (is->vidclk.pts > (sp->pts + ((float) sp->sub.end_display_time / )))
|| (sp2 && is->vidclk.pts > (sp2->pts + ((float) sp2->sub.start_display_time / ))))
{
free_subpicture(sp); /* update queue size and signal for next picture */
if (++is->subpq_rindex == SUBPICTURE_QUEUE_SIZE)
is->subpq_rindex = ; SDL_LockMutex(is->subpq_mutex);
is->subpq_size--;
SDL_CondSignal(is->subpq_cond);
SDL_UnlockMutex(is->subpq_mutex);
} else {
break;
}
}
} display:
/* display picture */
if (!display_disable && is->show_mode == SHOW_MODE_VIDEO)
video_display(is); pictq_next_picture(is); if (is->step && !is->paused)
stream_toggle_pause(is);
}
}
is->force_refresh = ;
if (show_status) {/*显示状态*/
static int64_t last_time;
int64_t cur_time;
int aqsize, vqsize, sqsize;
double av_diff; cur_time = av_gettime();
if (!last_time || (cur_time - last_time) >= ) {
aqsize = ;
vqsize = ;
sqsize = ;
if (is->audio_st)
aqsize = is->audioq.size;
if (is->video_st)
vqsize = is->videoq.size;
if (is->subtitle_st)
sqsize = is->subtitleq.size;
av_diff = ;
if (is->audio_st && is->video_st)
av_diff = get_clock(&is->audclk) - get_clock(&is->vidclk);
else if (is->video_st)
av_diff = get_master_clock(is) - get_clock(&is->vidclk);
else if (is->audio_st)
av_diff = get_master_clock(is) - get_clock(&is->audclk);
av_log(NULL, AV_LOG_INFO,
"%7.2f %s:%7.3f fd=%4d aq=%5dKB vq=%5dKB sq=%5dB f=%"PRId64"/%"PRId64" \r",
get_master_clock(is),
(is->audio_st && is->video_st) ? "A-V" : (is->video_st ? "M-V" : (is->audio_st ? "M-A" : " ")),
av_diff,
is->frame_drops_early + is->frame_drops_late,
aqsize / ,
vqsize / ,
sqsize,
is->video_st ? is->video_st->codec->pts_correction_num_faulty_dts : ,
is->video_st ? is->video_st->codec->pts_correction_num_faulty_pts : );
fflush(stdout);
last_time = cur_time;
}
}
}

首先说明一下,这里在ffplay里面默认模式是使用音频做主时钟源。

其中上面加红色的代码是主要的策略:

  他通过计算当前这一帧vp->pts和前面那一帧的pts之差来得到上一帧的显示时间。

  然后再根据这个上面计算得到的上一帧的显示时间来估算预测计算当前这一帧的显示时间,这样就可以得到预测下一帧的pts时间了。

  这里预测下一帧的出现时间,刷新时间,调用了compute_target_delay来进行处理:

代码如下:compute_target_delay

 static double compute_target_delay(double delay, VideoState *is)
{
double sync_threshold, diff; /* update delay to follow master synchronisation source */
if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {
/* if video is slave, we try to correct big delays by
duplicating or deleting a frame *//*我们通过复制和删除一帧来纠正大的延时*/
diff = get_clock(&is->vidclk) - get_master_clock(is); /* skip or repeat frame. We take into account the
delay to compute the threshold. I still don't know
if it is the best guess */
sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
if (!isnan(diff) && fabs(diff) < is->max_frame_duration) {
if (diff <= -sync_threshold)/*当前视频帧落后于主时钟源*/
{
delay = FFMAX(, delay + diff);
}
else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)/*视频帧超前,但If a frame duration is longer than this, it will not be duplicated to compensate AV sync*/
{ /*大概意思是:本来当视频帧超前的时候,
我们应该要选择重复该帧或者下面的2倍延时(即加重延时的策略),
但因为该帧的显示时间大于显示更新门槛,
所以这个时候不应该以该帧做同步*/
delay = delay + diff;
}
else if (diff >= sync_threshold)
{
delay = * delay;/*采取加倍延时*/
}
}
} av_dlog(NULL, "video: delay=%0.3f A-V=%f\n",
delay, -diff); return delay;
}

  在这个函数里面通过得带视频时间和参考时间之间的差值diff,然后再结合diff的情况来处理delay。

ffplay(2.0.1)中的音视频同步的更多相关文章

  1. 如何理解直播APP源码开发中的音视频同步

    视频 直播APP源码的视频的播放过程可以简单理解为一帧一帧的画面按照时间顺序呈现出来的过程,就像在一个本子的每一页画上画,然后快速翻动的感觉. 但是在实际应用中,并不是每一帧都是完整的画面,因为如果直 ...

  2. WebRTC 音视频同步原理与实现

    所有的基于网络传输的音视频采集播放系统都会存在音视频同步的问题,作为现代互联网实时音视频通信系统的代表,WebRTC 也不例外.本文将对音视频同步的原理以及 WebRTC 的实现做深入分析. 时间戳 ...

  3. ffmpeg 2.3版本号, 关于ffplay音视频同步的分析

    近期学习播放器的一些东西.所以接触了ffmpeg,看源代码的过程中.就想了解一下ffplay是怎么处理音视频同步的,之前仅仅大概知道通过pts来进行同步,但对于怎样实现却不甚了解,所以想借助这个机会, ...

  4. ffplay的音视频同步分析

    以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmpeg,他在各 ...

  5. (转)ffplay的音视频同步分析之视频同步到音频

          以前工作中参与了一些音视频程序的开发,不过使用的都是芯片公司的SDK,没有研究到更深入一层,比如说音视频同步是怎么回事.只好自己抽点时间出来分析开源代码了,做音视频编解码的人都知道ffmp ...

  6. FFmpeg简易播放器的实现-音视频同步

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10284653.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...

  7. ffmpeg转码MPEG2-TS的音视频同步机制分析

    http://blog.chinaunix.net/uid-26000296-id-3483782.html 一.FFmpeg忽略了adaptation_field()数据 FFmpeg忽略了包含PC ...

  8. Android 音视频同步机制

    一.概述 音视频同步(avsync),是影响多媒体应用体验质量的一个重要因素.而我们在看到音视频同步的时候,最先想到的就是对齐两者的pts,但是实际使用中的各类播放器,其音视频同步机制都比这些复杂的多 ...

  9. Android 音视频同步(A/V Sync)

    1.  音视频同步原理 1)时间戳 音视频同步主要用于在音视频流的播放过程中,让同一时刻录制的声音和图像在播放的时候尽可能的在同一个时间输出. 解决音视频同步问题的最佳方案就是时间戳:首先选择一个参考 ...

随机推荐

  1. 用ansible 完成一次性的工作(ad-Hoc)工作

    ansible 真正强大的功能是它的playbook,但是在日常的工作中通过会遇到一些工作,它们只是需要我们偶尔操作一下:比较说重启一下 操作系统:像这样的工作就用不着ansible-playbook ...

  2. Centos网络配置小工具

    之前在CentOS 6下编辑网卡,直接使用setup工具就可以了. 但在新版的CentOS 7里,setuptool已经没有网络编辑组件了,取而代之的是NetworkManager Text User ...

  3. 怎么使用 ab.exe 测试多个url。 how to use ab.exe test many url

    from a commandline in windows: for /F %q in (list.txt) DO ab -n 1000 https://test.com/search?%q   I ...

  4. Android Gradle 引用本地 AAR 的几种方式

    折衷方案: 1.方式2 - 不完美解决办法2 2.再使用"自定义Gradle代码"来减轻重复设置的问题. 自定义Gradle代码如下: repositories { flatDir ...

  5. 模拟多级复选框效果的jquery代码

    jquery做了个多级复选框的效果,代码总共就20+行就over了. 我又想用js来做一个看看,才写了几个方法就写不动了,兼容性要考虑很多,而且代码量直线上升. 主要分享下jquery的这个效果的实现 ...

  6. CentOS 64位下安装Postfix+Dovecot 配置邮件server笔记

    Postfix 和Dovecot功能确实非常强大,支持各种认证方式, 配置非常灵活, 就由于太过于灵活, 反而安装配置的过程中,easy有各种各样的陷阱,碰到问题了. 日志是最好的解决的方法了.    ...

  7. c#编写远程控制的核心被控端操作类

    首先定义一个全局,上线地址,上线端口等 using Control_Client; using Microsoft.Win32; using System; using System.Collecti ...

  8. 每日英语:Can Going In and Out of Air Conditioning Cause Colds?

    For most people, summer involves numerous daily shifts between scorching outdoor heat and frosty air ...

  9. Docker学习总结之Docker与Vagrant之间的特点比较

    以下内容均出自Vagrant作者(Mitchell Hashimoto)与Docker作者(Solomon Hykes)在stackoverflow上面一个问题讨论.在这个问题中,双方阐述了vagra ...

  10. stock article

    stock 征服星辰大海 http://www.tianya.cn/95789158 论坛_悟道股市_天涯社区 http://www.tianya.cn/108318593/bbs?t=post 当下 ...