一、音视频同步原理
如果简单的按照音频的采样率与视频的帧率去播放,由于机器运行速度,解码效率等种种造成时间差异的因素影响,很难同步,音视频时间差将会呈现线性增长。所以要做音视频的同步,有三种方式:
参考一个外部时钟,将音频与视频同步至此时间。我首先想到这种方式,但是并不好,由于某些生物学的原理,人对声音的变化比较敏感,但是对视觉变化不太敏感。所以频繁的去调整声音的播放会有些刺耳或者杂音吧影响用户体验。(ps:顺便科普生物学知识,自我感觉好高大上_)。
-
以视频为基准,音频去同步视频的时间。不采用,理由同上。
-
以音频为基准,视频去同步音频的时间。 所以这个办法了。
所以,原理就是以音频时间为基准,判断视频快了还是慢了,从而调整视频速度。其实是一个动态的追赶与等待的过程。
二、一些概念
音视频中都有DTS与PTS。
DTS ,Decoding Time Stamp,解码时间戳,告诉解码器packet的解码顺序。 PTS ,Presentation Time Stamp,显示时间戳,指示从packet中解码出来的数据的显示顺序。 音频中二者是相同的,但是视频由于B帧(双向预测)的存在,会造成解码顺序与显示顺序并不相同,也就是视频中DTS与PTS不一定相同。
时间基 :看FFmpeg源码
/** * This is the fundamental unit of time (in seconds) in terms * of which frame timestamps are represented. For fixed-fps content, * timebase should be 1/framerate and timestamp increments should be * identically 1. * This often, but not always is the inverse of the frame rate or field rate * for video. * - encoding: MUST be set by user. * - decoding: the use of this field for decoding is deprecated. * Use framerate instead. */ AVRational time_base; /** * rational number numerator/denominator */ typedef struct AVRational{ int num; ///< numerator int den; ///< denominator } AVRational;
个人理解,其实就是ffmpeg中的用分数表示时间单位,num为分子,den为分母。并且ffmpeg提供了计算方法:
/** * Convert rational to double. * @param a rational to convert * @return (double) a */ static inline double av_q2d(AVRational a){ return a.num / (double) a.den; }
所以 视频中某帧的显示时间 计算方式为(单位为妙):
time = pts * av_q2d(time_base);
三、同步代码 1、 音频部分 clock 为音频的播放时长(从开始到当前的时间)
if (packet->pts != AV_NOPTS_VALUE) { audio->clock = av_q2d(audio->time_base) * packet->pts; }
然后加上此packet中数据需要播放的时间
double time = datalen/((double) 44100 *2 * 2); audio->clock = audio->clock +time;
datalen为数据长度。采样率为44100,采样位数为16,通道数为2。所以 数据长度 / 每秒字节数。
ps:此处计算方式不是很完美,有很多问题,回头研究在再补上。
四、 视频部分 先定义几个值:
double last_play //上一帧的播放时间 ,play //当前帧的播放时间 , last_delay // 上一次播放视频的两帧视频间隔时间 ,delay //两帧视频间隔时间 ,audio_clock //音频轨道 实际播放时间 ,diff //音频帧与视频帧相差时间 ,sync_threshold //合理的范围 ,start_time //从第一帧开始的绝对时间 ,pts ,actual_delay//真正需要延迟时间 start_time = av_gettime() / 1000000.0; // 获取pts if ((pts = av_frame_get_best_effort_timestamp(frame)) == AV_NOPTS_VALUE) { pts = 0; } play = pts * av_q2d(vedio->time_base); // 纠正时间 play = vedio->synchronize(frame, play); delay = play - last_play; if (delay <= 0 || delay > 1) { delay = last_delay; } audio_clock = vedio->audio->clock; last_delay = delay; last_play = play; //音频与视频的时间差 diff = vedio->clock - audio_clock; // 在合理范围外 才会延迟 加快 sync_threshold = (delay > 0.01 ? 0.01 : delay); if (fabs(diff) < 10) { if (diff <= -sync_threshold) { delay = 0; } else if (diff >= sync_threshold) { delay = 2 * delay; } } start_time += delay; actual_delay = start_time - av_gettime() / 1000000.0; if (actual_delay < 0.01) { actual_delay = 0.01; } // 休眠时间 ffmpeg 建议这样写 为什么 要这样写 有待研究 av_usleep(actual_delay * 1000000.0 + 6000); 纠正play (播放时间)的方法 repeat_pict / (2 * fps) 是ffmpeg注释里教的 synchronize(AVFrame *frame, double play) { //clock是当前播放的时间位置 if (play != 0) clock=play; else //pst为0 则先把pts设为上一帧时间 play = clock; //可能有pts为0 则主动增加clock //需要求出扩展延时: double repeat_pict = frame->repeat_pict; //使用AvCodecContext的而不是stream的 double frame_delay = av_q2d(codec->time_base); //fps double fps = 1 / frame_delay; //pts 加上 这个延迟 是显示时间 double extra_delay = repeat_pict / (2 * fps); double delay = extra_delay + frame_delay; clock += delay; return play; }
原创作者:Learner_9a9e 原文链接:https://www.jianshu.com/p/3578e794f6b5 校验:逆流的鱼yuiop