继上一讲音视频解码,接下来分析音视频同步,可以参照 FFmpeg源码中FFplay中的音视频同步的具体代码。
音视频同步方式:
- 将视频根据音频同步(以音频为主)
- 以视频为主
- 以一个外部时间进度为主
这里采用第一种方式完成音视频同步。
获取一个音频frame的播放时间,通过 frame->pts拿到 pts用来度量音频帧什么时候被播放出来。time_base是pts转成秒的单位,获取time_base, 传给VideoChannel,AudioChannel。
//每个画面 刷新的间隔 单位:秒
double frame_delays = 1.0 / fps;
//单位
AVRational time_base = stream->time_base;
//在getPCM()中拿到播放 一个音声frame的时间
//获取一个frame的一个相对播放时间
//获得播放这段数据的秒速(时间机), clock的时间单位是秒
double clock = frame->pts * av_q2d(time_base);
//视频通过best_effort_timestamp,而不是pts
//获取当前 一个画面播放的时间
double clock = frame->best_effort_timestamp * av_q2d(time_base);
//拿到两个的时间戳后,需要两者的clock进行比较,比较的场所在Video中需要把AudioChannel传入到videoChannel中。
//比较音频与视频
double audioClock = audioChannel->clock;
//间隔 音视频相差的间隔
double diff = clock - audioClock;
if (diff > 0) {
//大于0 表示视频比较快
//LOGE("视频快了:%lf", diff);
av_usleep((delays + diff) * 1000000);
} else if (diff < 0) {
//不睡了,快点赶上音频
//LOGE("音频快了:%lf",diff);
// 视频包积压的太多了 (丢包)
if (fabs(diff) >= 0.05) {
releaseAvFrame(&frame);
//丢包
frames.sync();
continue;//每次丢一帧,循环丢,重新计算延迟时间。
}else{
//不睡了 快点赶上 音频
}
}
frame本身自己有个延迟时间
/**
* When decoding, this signals how much the picture must be delayed.
* extra_delay = repeat_pict / (2*fps)
*/
int repeat_pict;
double extra_delay = frame->repeat_pict / (2 * fps);
double delays = extra_delay + frame_delays;
//所以真正需要延迟的是 delays 这个时间。
当视频落下太多的时候,需要丢包。Video 中有packets跟Frames两个queue,所以这里可以选择丢弃packets或者Frames。 根据H.264原则,Frame分为 I,B, P帧,这里不能丢弃I帧,因为与之相关的B帧,P帧没法获取其相关联的数据。
//丢包, 因为其他地方需要去拿,所以要同步操作,加锁
frames.sync();
void sync() {
pthread_mutex_lock(&mutex);
//同步代码
syncHandle(q);
pthread_mutex_unlock(&mutex);
}
void setSyncHandle(SyncHandle s) {
syncHandle = s;
}
VideoChannel::VideoChannel(int id, AVCodecContext *avCodecContext, AVRational time_base, int fps)
: BaseChannel(id, avCodecContext, time_base) {
this->fps = fps;
//用于同步操作队列的一个函数指针
//packets.setSyncHandle(dropAvPacket);
//丢画面
frames.setSyncHandle(dropAvFramev);
}
//丢包,知道下一个关键帧
void dropAvPacket(queue<AVPacket *> &q) {
while (!q.empty()) {
AVPacket *packet = q.front();
if (packet->flags != AV_PKT_FLAG_KEY) {//还没有解码,需要判断它不是I帧,否则会影响后续的B帧,P帧的解码过程
BaseChannel::releaseAvPacket(&packet);
q.pop();
} else {
break;
}
}
}
//丢帧
void dropAvFramev(queue<AVFrame *> &q) {
if (!q.empty()) {
AVFrame *frame = q.front();
BaseChannel::releaseAvFrame(&frame);
q.pop();
}
}
至此音视频同步的整个过程也讲完了。需要源码的,可前往GitHub获取, 顺便点个star呗