与解码相关的主要代码在上一篇博客中已经做了介绍,本篇我们会先讨论一下如何控制解码速度再提供一个我个人的封装思路。最后回归到界面设计环节重点看一下如何保证播放器界面在缩放和拖动的过程中保证视频画面的宽高比例。

一、解码速度

播放器播放媒体文件的时候播放进度需要我们自己控制。基本的控制方法有两种:

  1. 根据FPS控制视频的播放帧率,让音频跟随。
  2. 控制音频的播放解码速度,让视频跟随。

媒体文件在编码的时候,正常情况下视频数据和音频输出是交替写入的。换句话说,解码每一帧视频数据伴随需要播放的音频数据也应该被解码。所以,方案一的实现就比较简单和直接。但是在有些情况下也可能会出现音视频编码不同步的问题,大部分情况是视频提前于音频。万一遇到这样的情况,如果需要让我们的播放器带有一定纠错功能就必须采用第二种方案。方案二的设计思路是当遇到音频数据时正常播放,遇到视频数据时先缓冲起来,再根据pts参数同步。

方案一

QTime t;
QIODevice ioDevice;
t.restart();
AVPacket *pkt = readPacket();
if (pkt->stream_index == videoIndex) { // 当前为视频帧,计算视频播放每帧的间隔时间(1000/fps) - 解码消耗的时间(毫秒) = 实际解码间隔时间interval
    codecPacket(pkt);int el = t.elapsed();int interval = 1000 / fps - el > 0 ? 1000 / fps - el : 1;QThread::msleep(interval);
}
else if (pkt->stream_index == audioIndex) { // 当前为音频帧,直接让Qt的音频播放器播放
    codecPacket(pkt);char data[10000] = { 0 };int len = toPCM(data);ioDevice->write(data, len);
}

方案二

AVPacket *pkt = readPacket();if (pkt->stream_index == audioIndex) {codecPacket(pkt);char data[AUDIO_IODEVICE_WRITE_SIZE] = { 0 };int len = toPCM(data);ioDevice->write(data, len);
}
else if (pkt->stream_index == videoIndex) {videoPacketList.push_back(pkt);
}while (videoPacketList.size() > 0 && videoPts < audioPts) {AVPacket *pkt = videoPacketList.front();videoPacketList.pop_front();codecPacket(pkt);
}

这个方案遇到的另外一个问题是我们如何获取videoPts和audioPts这两个值。我个人的解决思路是在解码环节进行,即,每次对pkt进行一次解码就根据pkt的stream_index值分别记录解码后的AVFrame的pts。不过音频的pts和视频的pts不能直接比较。我们还需要根据各自的AVRational做一次换算。算法如下:

AVRational r;
frame->pts * (double)r.num / (double)r.den;

二、封装思路讨论

代码封装实际是一个见仁见智的工作,可能不同的人对代码结构的理解不同,实现的封装方式也会存在差异。包括我们的解决方案到底针对哪些需求也会按照不同的思路做封装。在这里插一句题外话,大家认为程序开发到底是一种什么样的工作性质?是仅仅为了实现客户的需求吗?如果你只能理解到这一层,那恐怕还远远不够!客户需求只能算是抛给你的一个问题,而你反馈给客户的应该是一套合理的解决方案。从这个观点出发我们进行再抽象,程序开发应该是一种从问题空间到解空间的映射。既然如此,我们就不能将自己的工作仅仅停留在功能实现这个层面,我们还应该提供更好的解决思路——最佳实践。

基本上,如果我们只需要设计一个简单的播放器。大概需要三个模块的支持:

界面模块(av_player):包括了界面的样式和基础互动功能

解码模块(Decoder):这个部分主要通过对FFmpeg的功能二次封装,并对外提供接口支持

播放器模块(PlayerWidget):负责界面和解码模块的连接,界面中嵌入播放器模块,视频显示和音频播放都由播放器模块独立负责。

下面看一下我设计的解码模块对外提供的接口:Decoder.h

class Decoder : protected QThread
{
public:Decoder();virtual ~Decoder();bool open(const char *filename);void close();// 从文件中读取一个压缩报文AVPacket* readPacket();// 解码报文并释放空间,返回值为当前解码报文的pts时间(毫秒)int codecPacket(AVPacket* pkt);// 将解码帧Frame转码为RGB或PCMint toRGB(char *outData, int outWidth, int outHeight);int toPCM(char *outData);int durationMsec; // 文件时长int fps; // 视频FPSint srcWidth; // 视频宽度int srcHeight; // 视频高度int videoIndex; // 视频通道int audioIndex; // 音频通道int sampleRate; // 音频采样率int channels; // 声道int sampleSize; // 样本位数bool endFlag; // 线程结束标志bool pauseFlag; // 线程暂停标志// 记录当前的音视频所处在的pts时间戳(毫秒)int videoPts;int audioPts;// 记录音视频的编解码格式int sampleFmt;int pixFmt;/************************************************************************//* default: CD音质(16bit 44100Hz stereo)                              *//************************************************************************/int dstSampleRate = 44100; // 采样率int dstSampleSize = 16; // 采样大小int dstChannels = 2; // 通道数// 线程启动的代理方法void start();// 音频输出QAudioOutput *audioOutput = NULL;
protected:void run();
private:QMutex mtx;AVFormatContext *pFormatCtx = NULL;SwsContext *videoSwsCtx = NULL;AVFrame *yuv = NULL;SwrContext *audioSwrCtx = NULL;AVFrame *pcm = NULL;QIODevice *ioDevice = NULL;std::list<AVPacket*> videoPacketList;AVInputTypeEnum avType = AVInputTypeEnum::NOTYPE;QString fileName;
};

乍一看很复杂,我们稍微理一下思路。首先Decoder继承了QThread,并重写了start()方法。重写的好处是,在对调用者完全透明的情况下,我们可以在这个函数中做一些初始化工作。在设计模式中,它数据代理模式。其他方法介绍:

  • bool open(const char *filename):开发多媒体文件
  • void close():关闭和析构所有编码,这个步骤在音视频编解码的开发中非常重要
  • AVPacket* readPacket():读取一帧数据并返回
  • int codecPacket(AVPacket* pkt):解码之前读取到的一帧数据,返回该帧数据表示的pts值并将传入的pkt析构释放内存空间
  • int toRGB(char *outData, int outWidth, int outHeight):转码视频帧,将yuv转换为rgb
  • int toPCM(char *outData):转码音频帧

播放器模块:PlayerWidget.h

class PlayerWidget : public QOpenGLWidget
{
public:PlayerWidget(Decoder *dec, QWidget *parent, int interval);virtual ~PlayerWidget();/************************************************************************//* default: 720p 25fps                                                  *//************************************************************************/int videoWidth = 720;int videoHeight = 480;int m_interval = 40;/************************************************************************//* default: CD音质(16bit 44100Hz stereo)                              *//************************************************************************/int sampleRate = 44100; // 采样率int sampleSize = 16; // 采样大小int channels = 2; // 通道数
protected:void timerEvent(QTimerEvent *e);void paintEvent(QPaintEvent *e);
private:Decoder *decoder = NULL;QAudioOutput *out;QIODevice *io;
};

这个模块继承自QOpenGLWidget,并包含了QAudioOutput。这两个Qt类分别代表了视频播放和音频播放。

界面模块:在这个模块中有一个重要的工作就是当我们在播放视频的时候放大和缩小播放器窗口如何保证视频画面依然保持正确的宽高比,为此我写了一个静态函数:

struct AspectRatio {double width;double height;
};static AspectRatio* fitRatio(int outWidth, int outHeight, int inWidth, int inHeight) {double r1 = ((double)outWidth / (double)outHeight);double r2 = ((double)inWidth / (double)inHeight);AspectRatio *ar = new AspectRatio;if (r1 > r2) {int newWidth = (double)(outHeight * inWidth) / (double)inHeight;ar->width = newWidth;ar->height = outHeight;return ar;}else {int newHeight = (double)(inHeight * outWidth) / (double)inWidth;ar->width = outWidth;ar->height = newHeight;return ar;}
}

最后附上我自己设计的播放器界面

项目源码:https://gitee.com/learnhow/ffmpeg_studio/tree/master/_64bit/src/av_player

转载于:https://www.cnblogs.com/learnhow/p/8970893.html

Qt与FFmpeg联合开发指南(二)——解码(2):封装和界面设计相关推荐

  1. 《Qt 5.9 C++开发指南》一书特点总结

    来源:https://blog.csdn.net/HongAndYi/article/details/80445620 <Qt 5.9 C++开发指南>已正式出版销售快一个月了,期间也写了 ...

  2. 好教程推荐系列:《Qt 5.9 C++开发指南》

    官方源码(配套资源): 作者:王维波 栗宝鹃 侯春望 出版社:人民邮电出版社 出版时间:2018年05月 https://www.epubit.com/bookDetails?id=N25171 官方 ...

  3. Bentley ORD(openroads designer) 二次开发(BIM)第四节 界面设计数据绑定

    Bentley ORD(openroads designer) 二次开发(BIM)第四节 界面设计 导语 1.窗体创建 1.1 选择控件 1.2 编辑控件 1.3 代码控制 2.界面风格Ord化 3. ...

  4. Android开发入门 - 简易开心消消乐界面设计

    Android开发入门 - 简易开心消消乐界面设计 第一步,点击File->NEW->new module,进入以下界面,选择第一个,即运行在手机和平板电脑上.点击next. 第二步,在第 ...

  5. WindowsPhone7开发简单豆瓣网应用程序之界面设计

    WindowsPhone7开发简单豆瓣网应用程序之界面设计 最近自学了一点WindowsPhone7的知识,自己就利用豆瓣网API开发了一个小的搜索程序.好了!先看看程序运行效果吧! (启动界面) ( ...

  6. VB亲身开发一个Windows软件(三)界面设计

    现在,我们已经准备好了,要开始开发了.首先我们要进行界面设计.从某种方面来说,这比写代码更重要,因为用户只会感觉到界面,而不会在乎代码,所以一个好的用户界面,是一个优秀软件的基础. 那么,我们要开始了 ...

  7. tkinter项目实战_Python GUI项目实战(二)主窗体的界面设计与实现

    前言 上一节我们介绍了Python GUI项目实战(一)登录窗体的设计与实现,实现了该项目登录窗体的GUI的搭建,用户的账号和密码校验完成后应当跳转到主窗体界面,这一节我们将具体介绍主窗体界面的设计与 ...

  8. python界面设计实例-Python GUI项目实战(二)主窗体的界面设计与实现

    前言 上一节我们介绍了登录窗体的GUI设计与功能实现,用户的账号和密码校验完成后应当跳转到主窗体内容,这一节我们将具体介绍主窗体界面的设计与功能实现! 一.基础界面设计 我们新建一个900x640的窗 ...

  9. 零基础学Python【二十三、图形化界面设计 】(基础一篇全,欢迎认领)

    1.图形化界面设计的基本理解 当前流行的计算机桌面应用程序大多数为图形化用户界面(Graphic User Interface,GUI). 即通过鼠标对菜单.按钮等图形化元素触发指令,并从标签.对话框 ...

最新文章

  1. 823专业课计算机,辽宁科技大学823计算机专业基础综合(含数据结构、计算机组成原理、操作系统和计算机网络)考研复习经验...
  2. Fail to find the dnn implementation. [Op:CudnnRNN]解决办法
  3. 电脑鼠标自己乱跳乱点_无线鼠标VS有线鼠标,二者区别何在?你可知晓?不妨来了解一下...
  4. 【牛客 - 373C】抓捕盗窃犯(连通图,思维,dfs 或 并查集)
  5. linux下 c语言 用write open二进制写文件,Linux下用C语言fopen、fread和fwrite函数对二进制文件的操作-Go语言中文社区...
  6. Ghost 2.18.3 发布,基于 Markdown 的在线写作平台
  7. Docker系列(九)Docker的远程访问
  8. 《剑指offer》面试题31——连续子数组的最大和
  9. android服务器数据交互,Android手机访问服务器一种数据交互方法_刘平.pdf
  10. iOS-深拷贝和浅拷贝
  11. 自动弹琴助手使用及制谱教程
  12. c语言超市,C语言超市收银系统
  13. 3DMAX 卸载工具,完美彻底卸载清除干净3dmax各种残留注册表和文件
  14. 有奖推荐|BSRC发布IoT安全专家招募令
  15. 污染土壤修复可以采取哪些方式
  16. Python 爬虫:抓取豆瓣top250电影数据
  17. 达梦V8归档备份参数NOT BACKED UP的使用
  18. HDU6287 口算训练(唯一分解定理+二分)
  19. linux sendmail漏洞,linux-sendmail的安全-029
  20. 远程直播服务器连接失败,哔哩哔哩直播姬连接失败如何解决 直播姬连接失败解决方法攻略大全...

热门文章

  1. c++ linux 线程等待与唤醒_C++ Linux线程同步机制:POSIX信号量,互斥锁,条件变量...
  2. mysql作为kafka生产者_Kafka之生产者
  3. android px pd sp区别,【求助】我用北京索莱宝质粒小量提取试剂盒提的质粒,电泳图...
  4. php模拟登录qq邮箱_PHP 利用QQ邮箱发送邮件的实现
  5. 有向图强连通分量SCC(全网最好理解)
  6. [开源]基于姿态估计的运动计数APP开发(三)
  7. (转)如何选择合适的射频模块
  8. Lua中的函数环境、_G及_ENV
  9. 机器学习 文本分类 代码_无需担心机器学习-如何在少于10行代码中对文本进行分类
  10. ceshiceshicesoooof