1、概念
由于视频播放器中音频和视频是分别播放和渲染的,就会出现声音和画面不同步的现象。为了使同一时刻声音和画面的一致性,我们就需要音视频同步来实现,这就是音视频同步。
2、播放时间
2.1、音频播放时间
音频播放的时长是PCM数据决定的,根据数据大小和采样率、通道数和位深度就能计算出播放的时长。只要采样率、通道数、位深度不变,扬声器播放同一段PCM数据的时间就是固定不变的。
2.2、视频播放时间
视频其实没有播放时长的概念,只有相邻视频画面帧直接的时间间隔,调整时间间隔就能改变视频画面的渲染速度,来实现视频的快慢控制。
3、音视频同步方法:
-
第一种:音频线性播放,视频同步到音频上。
-
第二种:视频线性播放,音频同步到视频上。
-
第三种:用一个外部线性时间,音频和视频都同步到这个外部时间上。
由于人们对声音更敏感,视频画面的一会儿快一会儿慢是察觉不出来的。而
声音的节奏变化是很容易察觉的。所以我们这里采用第一种方式来同步音视频。
4、音视频同步实现:
4.1、PTS和time_base
PTS即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
time_base即时间度量单位(时间基),可以类比:米、千克这种单位。
4.2、分别获取音频和视频的PTS(播放时间戳):
PTS = avFrame->pts * av_q2d(avStream->time_base);
4.3、获取音视频PTS差值,根据差值来设置视频的睡眠时间达到和音频的相对同步。
视频快了就休眠久点,视频慢了就休眠少点,来达到同步。
5、实现
5.1、JfVideo.h在Video中拿到Audio对象
class JfVideo {
public:
...
JfAudio *audio;
...
public:
...
};
初始化时赋值
void JfFFmpeg::start() {
if (audio == NULL) {
if (LOG_DEBUG){
LOGE("AUDIO == NULL");
}
}
if (video == NULL) {
if (LOG_DEBUG){
LOGE("VIDEO == NULL");
}
}
video->audio = audio;
audio->play();
video->play();
...
5.2、获取音视频PTS差值
double getFrameDiffTime(AVFrame *avFrame);
/**
* 当前帧的AVFrame
*/
double JfVideo::getFrameDiffTime(AVFrame *avFrame) {
//获取当前帧的pts
double pts = av_frame_get_best_effort_timestamp(avFrame);
if (pts == AV_NOPTS_VALUE){//判断是否有效
pts = 0;
}
double timestamp = pts * av_q2d(time_base);//time_base是流的time_base,用来计算这帧在整个视频中的时间位置
if (timestamp > 0){
clock = timestamp;
}
double diff = audio->clock - clock;
return diff;
}
5.3、实现同步
double JfVideo::getDelayTime(double diff) {
if (diff > 0.003){//音频播放比视频快0.003,让视频播放快点
delayTime = delayTime * 2 / 3;//让视频渲染时sleep略长的时间,但是可能会导致视频播放的越来越快
if (delayTime < defaultDelayTime / 2){
delayTime = defaultDelayTime * 2 / 3;
} else if (delayTime > defaultDelayTime * 2){
delayTime = defaultDelayTime * 2;
}
} else if (diff < -0.003){//音频播放比视频慢0.003,让视频播放慢点
delayTime = delayTime * 3 / 2;//让视频渲染时sleep略长的时间,但是可能会导致视频播放的越来越慢
if (delayTime < defaultDelayTime / 2){
delayTime = defaultDelayTime * 2 / 3;
} else if (delayTime > defaultDelayTime * 2){
delayTime = defaultDelayTime * 2;
}
} else if (diff == 0.003){
}
if (diff >= 0.5){
delayTime = 0;
} else if (diff <= -0.5){
delayTime = defaultDelayTime * 2;
}
if (fabs(diff) >= 10){//音频不存在
delayTime = defaultDelayTime;
}
return delayTime;
}
5.4、视频渲染速度调节
void *playVideo(void *data){
JfVideo *video = (JfVideo *)data;
while (video->playStatus != NULL && !video->playStatus->exit){
if (video->playStatus->seeking){
av_usleep(1000 * 100);
continue;
}
if (video->queue->getQueueSize() == 0){//加载状态
if (!video->playStatus->loading){
video->playStatus->loading = true;
video->callJava->onCallLoading(CHILD_THREAD, true);
LOGD("VIDEO加载状态");
}
av_usleep(1000 * 100);
continue;
} else {
if (video->playStatus->loading){
video->playStatus->loading = false;
video->callJava->onCallLoading(CHILD_THREAD, false);
LOGD("VIDEO播放状态");
}
}
/*AVPacket *avPacket = av_packet_alloc();
if (video->queue->getAVPacket(avPacket) == 0){
//解码渲染
LOGD("线程中获取视频AVPacket");
}
av_packet_free(&avPacket);//AVPacket中的第一个参数,就是引用,减到0才真正释放
av_free(avPacket);
avPacket = NULL;*/
AVPacket *avPacket = av_packet_alloc();
if (video->queue->getAVPacket(avPacket) != 0){
av_packet_free(&avPacket);//AVPacket中的第一个参数,就是引用,减到0才真正释放
av_free(avPacket);
avPacket = NULL;
continue;
}
if (avcodec_send_packet(video->pVCodecCtx,avPacket) != 0){
av_packet_free(&avPacket);//AVPacket中的第一个参数,就是引用,减到0才真正释放
av_free(avPacket);
avPacket = NULL;
continue;
}
AVFrame *avFrame = av_frame_alloc();
if (avcodec_receive_frame(video->pVCodecCtx,avFrame) != 0){
av_frame_free(&avFrame);
av_free(avFrame);
avFrame = NULL;
av_packet_free(&avPacket);//AVPacket中的第一个参数,就是引用,减到0才真正释放
av_free(avPacket);
avPacket = NULL;
continue;
}
if (LOG_DEBUG){
LOGD("子线程解码一个AVFrame成功");
}
if (avFrame->format == AV_PIX_FMT_YUV420P){
//直接渲染
LOGD("YUV420P");
double diff = video->getFrameDiffTime(avFrame);
LOGD("DIFF IS %f",diff);
av_usleep(video->getDelayTime(diff) * 1000000);
video->callJava->onCallRenderYUV(
CHILD_THREAD,
video->pVCodecCtx->width,
video->pVCodecCtx->height,
avFrame->data[0],
avFrame->data[1],
avFrame->data[2]);
} else {
//转成YUV420P
AVFrame *pFrameYUV420P = av_frame_alloc();
int num = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,video->pVCodecCtx->width,video->pVCodecCtx->height,1);
uint8_t *buffer = (uint8_t *)(av_malloc(num * sizeof(uint8_t)));
av_image_fill_arrays(
pFrameYUV420P->data,
pFrameYUV420P->linesize,
buffer,
AV_PIX_FMT_YUV420P,
video->pVCodecCtx->width,
video->pVCodecCtx->height,
1);
SwsContext *sws_ctx = sws_getContext(
video->pVCodecCtx->width,
video->pVCodecCtx->height,
video->pVCodecCtx->pix_fmt,
video->pVCodecCtx->width,
video->pVCodecCtx->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL,NULL,NULL
);
if (!sws_ctx){
av_frame_free(&pFrameYUV420P);
av_free(pFrameYUV420P);
av_free(buffer);
continue;
}
sws_scale(
sws_ctx,
avFrame->data,
avFrame->linesize,
0,
avFrame->height,
pFrameYUV420P->data,
pFrameYUV420P->linesize);//这里得到YUV数据
LOGD("NO_YUV420P");
//渲染
double diff = video->getFrameDiffTime(avFrame);
LOGD("DIFF IS %f",diff);
av_usleep(video->getDelayTime(diff) * 1000000);
video->callJava->onCallRenderYUV(
CHILD_THREAD,
video->pVCodecCtx->width,
video->pVCodecCtx->height,
pFrameYUV420P->data[0],
pFrameYUV420P->data[1],
pFrameYUV420P->data[2]);
av_frame_free(&pFrameYUV420P);
av_free(pFrameYUV420P);
av_free(buffer);
sws_freeContext(sws_ctx);
}
av_frame_free(&avFrame);
av_free(avFrame);
avFrame = NULL;
av_packet_free(&avPacket);//AVPacket中的第一个参数,就是引用,减到0才真正释放
av_free(avPacket);
avPacket = NULL;
}
pthread_exit(&video->thread_play);
}
网友评论