android万能播放器,Android万能视频播放器05-音视频同步

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);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值