后记总结:
音视频同步
音视频同步过程中(将视频同步到音频),最重要的就是pts计算,以及时延判断的处理
音频的pts计算:(音频做参考时钟)
//获取当前AVPacket的pts
if (pPacket.pts != AV_NOPTS_VALUE)
{
audio_clock = av_q2d(stream->time_base) * pPacket.pts;
}
//由于一个packet中可以包含多个帧,packet中的PTS比真正的播放的PTS可能会早很多,
//可以根据Sample Rate 和 Sample Format来计算出该packet中的数据可以播放的时长,
//再次更新Audio clock 。(audio_clock内的时长是先于当前pts的,所以计算时要减去剩余数据的播放时长)
// 每秒钟音频播放的字节数 sample_rate * channels * sample_format(一个sample占用的字节数)
audio_clock += static_cast<double>(data_size) / (2 * stream->codec->channels * stream->codec->sample_rate);
获取当前播放位置的参考时钟
//获取当前音频播放的pts
/*
用audio缓冲区中剩余的数据除以每秒播放的音频数据得到剩余数据的播放时间,
从Audio clock中减去这部分的值就是当前的audio的播放时长。
*/
double CAudio::GetAudioClock()
{
int hw_buf_size = audio_buff_size - audio_buff_index;//剩余数据长度
int bytes_per_sec = stream->codec->sample_rate * audio_ctx->channels * 2;//每秒播放数据长度
double pts = audio_clock - static_cast<double>(hw_buf_size) / bytes_per_sec;//当前pts 单位 s
return pts;
}
视频的pts计算:
//计算pts 以流中的时间为基础预估的时间戳
if ((pts = av_frame_get_best_effort_timestamp(frame)) == AV_NOPTS_VALUE)
pts = 0.0;
pts *= av_q2d(video->stream->time_base);//pts*时间基 = 该frame显示的时间戳。
修正获得最后的pts(pts有可能计算不正确,这里需要进行同步修正)
pts = video->Synchronize(frame, pts);//修正pts
//修正pts的时间戳
double CVideo::Synchronize(AVFrame *srcFrame, double pts)
{
if (pts != 0.0)
video_clock = pts; // 获取到pts,将视频时延设置为pts
else
pts = video_clock; // 未获取到pts,将其近似为上一帧的pts
double frame_delay = av_q2d(stream->codec->time_base);//需要求出扩展延时:
frame_delay += srcFrame->repeat_pict * (frame_delay * 0.5); //repeat_pict / (2 * fps); 其中 fps = 1/frame_delay;
video_clock += frame_delay;//这个延迟 是显示时间
return pts;
}
最后进行视频帧显示的延时计算:
static const double SYNC_THRESHOLD = 0.01;
static const double NOSYNC_THRESHOLD = 10.0;
static const double AV_SYNC_THRESHOLD_MAX = 0.05;
//根据参考时钟计算时延
double CVideo::CalcDelay(std::function<double(void)> GetRefClock)
{
if (!Que_frame.DeQueue(&pFrame))return -1;//获取一帧图像
// 将视频同步到音频上,计算下一帧的延迟时间
double current_pts = *(double*)pFrame->opaque;
double delay = current_pts - frame_last_pts;
if (delay <= 0 || delay >= 1.0)
delay = frame_last_delay;
frame_last_delay = delay;
frame_last_pts = current_pts;
// 当前显示帧的PTS来计算显示下一帧的延迟(diff < 0 => 减速 ,diff > 0 => 加速)
double ref_clock = GetRefClock();
double diff = current_pts - ref_clock;// 误差
double threshold = (delay > SYNC_THRESHOLD) ? delay : SYNC_THRESHOLD;//下限 0.01
if (fabs(diff) < NOSYNC_THRESHOLD) // 不同步 调整
{
if (diff <= -threshold) // 慢了,delay设为0
delay = 0;
else if (diff >= threshold) // 快了,加倍delay
delay *= 2;
}
frame_timer += delay;//总的时延
double cur_timer = static_cast<double>(av_gettime()) / 1000000.0;//ms
//计算当前真正的时延
double actual_delay = frame_timer - cur_timer;
if ((delay > 0) && ((cur_timer - frame_timer) > AV_SYNC_THRESHOLD_MAX))//计时 落后系统时间超过半秒更新参考系统基准时间
{
frame_timer = cur_timer + delay;
actual_delay = delay;
}
if (actual_delay <= 0.010)
actual_delay = 0.010;
return actual_delay;
}
设置时延:
//设置下一帧的时延
int delay = static_cast<int>(actual_delay * 1000 + 0.5));//ms
SDL
1.启用SDL从某窗口构建播放窗口时,如果父窗口不关闭,SDL的destorywindow()函数,会与父窗口造成死锁,从而无法进行窗口销毁导致程序异常。
2.上述采用ffmpeg的 sws_scale()函数对YUV420p的图像进行转码为RGB32,然后又将RGB32转为bmp绘制在窗口上。关于sws_scale的转换效率问题
参考:https://blog.csdn.net/leixiaohua1020/article/details/12029505
流程处理
1.切记不要在线程中递归调用函数等待线程结束,如此会造成线程死锁。
2.线程退出后,最好调用等待线程结束的函数作为标志,开启新的线程,否则线程内存的清理会对新的线程内调用的类库初始化函数造成影响。
小结:练手项目,找找感觉,希望21年对我好点,泪目~~