ffmpeg视频播放器相关

视频播放思路

和播放音频一样,采用生产者消费者模型。AvPacket入队,然后AvPacket出队伍解码。

视频解码渲染

软解码:如果解码之后的数据格式是AV_PIX_FMT_YUV420P直接使用采用OpenGLES渲染,如果不是AV_PIX_FMT_YUV420P采用sws_scale转为AV_PIX_FMT_YUV420P在采用OpenGLES渲染。将YUV数据转换RGB的操作放在OpenGLES里面,使用GPU提升效率。软解码容易造成容易造成音视频不同步。 硬解码:在解码之前判断是否支持硬解码,如果支持硬解码就直接通过ffmpeg处理视频数据H264 H265等,为其加上头信息,然后硬解码交其OpenGLES渲染。

音视频同步问题

  1. 音频线性播放,视频同步到音频上。
  2. 视频线性播放,音频同步到视频上。
  3. 用一个外部线性时间,音频和视频都同步到这个外部时间上。

由于人们对声音更敏感,视频画面的一会儿快一会儿慢是察觉不出来的。而 声音的节奏变化是很容易察觉的。所以我们这里采用第一种方式来同步音视频。 这里需要计算当前视频帧的播放时间和当前音频的播放时间来进行比较,然后计算出睡眠时间来让视频不渲染还是延迟渲染,保持音视频尽量同步。

音视频同步相关计算
  • 计算当前视频帧播放的时间
double clock = 0;

if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
    time_base = pFormatCtx->streams[i]->time_base;
}

//...

 double pts = av_frame_get_best_effort_timestamp(avFrame);
    if(pts == AV_NOPTS_VALUE)
    {
        pts = 0;
    }
    pts *= av_q2d(time_base);

    if(pts > 0)
    {
        clock = pts;
    }
  • 计算音视频播放时间差值
//如果>0表示音频播放在前,视频渲染慢了,需要加速渲染  <0表示音频播放在后,视频渲染快了,需要延迟渲染
double getFrameDiffTime(AVFrame *avFrame) {
    double pts = av_frame_get_best_effort_timestamp(avFrame);
    if(pts == AV_NOPTS_VALUE)
    {
        pts = 0;
    }
    pts *= av_q2d(time_base);

    if(pts > 0)
    {
        clock = pts;
    }

    double diff = audio->clock - clock;
    return diff;
}
  • 计算渲染睡眠时间
 //延时时间 单位秒
double delayTime = 0;
//默认的延时时间 通过当前帧的AVRational计算fps所得  单位秒
double defaultDelayTime = 0.04;

if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
    int num = pFormatCtx->streams[i]->avg_frame_rate.num;
    int den = pFormatCtx->streams[i]->avg_frame_rate.den;
    if(num != 0 && den != 0)
    {
        int fps = num / den;//[25 / 1]
        defaultDelayTime = 1.0 / fps;
    }
}

double getDelayTime(double diff) {
    //如果音频的播放时间超过了30ms 视频需要加速渲染 慢慢的缩小睡眠时间 达到平缓的效果
    if (diff > 0.003) {
        delayTime = delayTime * 2 / 3;

        if (delayTime < defaultDelayTime / 2) {
            delayTime = defaultDelayTime * 2 / 3;
        } else if (delayTime > defaultDelayTime * 2) {
            delayTime = defaultDelayTime * 2;
        }
    } else if (diff < -0.003) { //如果音频的播放时间慢了30ms  视频需要延迟渲染
        delayTime = delayTime * 3 / 2;
        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;
}
播放暂停,停止,继续播放

解码渲染之前用一个标识判断即可

seek

和音频播放类似,解码之前采用标识判断,当调用seek的时候设置标识,清除缓冲队列, 调用

avcodec_flush_buffers(&AVCodecContext);

进行seek,接着清空队列,并调用

avformat_seek_file(pFormatCtx, -1, INT64_MIN, rel, INT64_MAX, 0);

清空ffmpeg的缓存。

注意

  1. 这里有一个线程在使用AVFormatContext获取AvPacket,有一个线程在使用AVCodecContext在进行解码,需要为AVFormatContextAVCodecContext添加锁。防止同步问题造成其他问题。
  2. 可能在seek之前,我们的数据已经读取完了存储在缓冲队列里面,这里seek清空缓冲队列,就会播放完毕,所以我们需要在读取不到数据的时候也加上seek标识判断。比如
//这里是读取数据完毕的时候
while(playstatus != NULL && !playstatus->exit){
    if(audio->queue->getQueueSize() > 0){
        av_usleep(1000 * 100);
        continue;
    } else{
        if(!playstatus->seek){
            av_usleep(1000 * 100);
            playstatus->exit = true;
        }
        break;
    }
}
release内存回收

这里需要特别注意的是线程退出的问题

单个线程退出

使用return 代替 pthread_exit();

多个线程退出

使用pthread_join(thread_t, NULL),会阻塞当前线程,直到thread_t退出完。退出的时候需要理清楚线程的退出顺序。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 开发者

Android Jetpack 助推应用开发 | 中文字幕视频介绍

27750
来自专栏IMWeb前端团队

RAIL简介:一个以用户为中心的性能模型

本文作者:IMWeb huscot 原文出处:IMWeb社区 未经同意,禁止转载 web性能优化,这是大家耳熟能详的东西了。 一说到性能优化,大家可能...

26870
来自专栏java一日一条

10 个最适合 Web 和 APP 开发的 NodeJS 框架

在浏览器以外运行 JavaScript 对于 JavaScript 爱好者来说非常神奇,同时也肯定是 web 应用程序开发界最受欢迎的进步之一。全球各地的开发者...

68720
来自专栏ThoughtWorks

它比微信小程序早出现半年,却不曾引爆技术圈|TW洞见

汪志成 ThoughtWorks 作为一个程序员,这两天的朋友圈被微信小应用刷屏了吧?想试了吧?没拿到邀请吧?没关系,我来帮你! 当然,我没法帮你拿到邀请码 —...

37980
来自专栏王二麻子IT技术交流园地

一、VueJs 填坑日记之基础概念知识解释

概述 在最开始听说vuejs这个词是在2016年,当时天真的认为自己是个后端开发工程师不需要学习太多的前端知识,不过紧接着在2017年在公司就用到了vuejs。...

23680
来自专栏Data Analysis & Viz

(送福利)BDP绘制微博转发动态热力图

先把效果图放上来,酷炫压场。看完本文,你就能轻松实现这个动态效果,全程只需几分钟!

17520
来自专栏无题

分库分表基本思想和实施策略

本文着重介绍sharding的基本思想和理论上的切分策略 参考地址:http://blog.csdn.net/bluishglc/article/details...

44060
来自专栏FreeBuf

Apple iOS 9.3 S/Plus – 触摸密码绕过漏洞

? 介绍 iOS是苹果公司开发的手机操作系统,发布于2007年,使用在iPhone 和 iPod Touch上,并且已经开始延伸至其他苹果设备如iPad和A...

22050
来自专栏哲学驱动设计

OEA中的AutoUI重构(2)- 评审会议前的总体设计

    之前已经写了一篇关于其中Command模块的重构:《OEA中AutoUI重构(1) - Command自动生成》。Command自动生成的重构作为本次重...

20390
来自专栏企鹅号快讯

从网站结构出发,提升搜索引擎的友好度

搜索引擎爬取网站内容都是从网站的结构出发,为了提升搜索引擎的友好的,优化网站结构是重点。和对待用户一样,对待搜索引擎也需要投其所好,才能获取它的友好度。 ? 1...

22960

扫码关注云+社区

领取腾讯云代金券