前面我们已经完成了工程的构建、ffmpeg装载以及基本控件添加,现在就开始BPlay1.0核心部分的分析:视频播放。

1、解封装

Bffmpeg::BLoadMediaFile这个方法在 BPlay1.0系列(4:Bffmpeg单例模式设计+媒体流文件检测) 提到过,当时说是用于检测媒体文件的合法性而构建的,其实其本质就是解封装媒体文件,通过解封装来判断文件是否可以成功载入。在讲解解封装之前,需要先了解这一步在整个视频播放流程的作用和地位,以及播放器在运行的时候究竟需要做哪些事。

上图描述了一个封装数据格式文件在播放时的完整步骤,可以看到第一步就是解封装,以MP4文件为例,它是由编码视频+编码音频合成的,解封装(Bffmpeg::BLoadMediaFile)就是将MP4拆成两部分:编码视频和编码音频,并保存相关信息到FormatContext中,同时获取到音/视频对应的索引和解码器,用于后续音视频解码。

2、音视频解码

首先在音视频主控线程里面,需要不断获取帧数据,将视频帧和音频帧分别加到各自的队列里面:

/********************************* void Bffmpeg::run()* 功能:音视频主控线程* *****************************/
void Bffmpeg::run()
{int Ret = 0;AVPacket pkt;int c = 0;while (ffmpegrun) {if (AudioQue.que.size() > 5 && VideoQue.que.size() > 5) {msleep(10);continue;}Ret = av_read_frame(FormatContext, &pkt);if (0 != Ret) {BLOG("av_read_frame fail, Ret[%d]", Ret);return;}if (pkt.stream_index == Audio_index) {/* 音频流入队列 */QMutexLocker locker(&AudioQue.mtx);AudioQue.que.push_back(pkt);} else if (pkt.stream_index == Video_index) {/* 视频流入队列 */QMutexLocker locker(&VideoQue.mtx);VideoQue.que.push_back(pkt);}msleep(1);}
}

接着时视频解码线程Bvideo::run,它不断从VideoQue队列里面取数据(编码),解码后送到frameque(RGB队列):

/********************************* void Bvideo::run()* 功能:视频解码线程* *****************************/
void Bvideo::run()
{while (videorun){AVPacket pkt;if (Bffmpeg::GetInstance()->GetVideoQue().que.size() == 0) {msleep(10);continue;} else {/* 视频流编码数据出队列 */QMutexLocker Locker(&Bffmpeg::GetInstance()->GetVideoQue().mtx);AVPacket pkt = Bffmpeg::GetInstance()->GetVideoQue().que.front();Bffmpeg::GetInstance()->GetVideoQue().que.pop_front();}/* 解码 */AVFrame* frame = Bffmpeg::GetInstance()->Decode(pkt);av_packet_unref(&pkt); if (frame == NULL) {continue;}/* 解码帧数据入RGB队列 */QMutexLocker FrameLocker(&frameque.mtx);frameque.frame.push_back(frame);msleep(1);}return;
}

视频解码线程了负责解码的方法实际是Bffmpeg::GetInstance()->Decode(pkt):

/********************************* AVFrame* Bffmpeg::Decode(AVPacket &pkt)* 功能:音视频解码* *****************************/
AVFrame* Bffmpeg::Decode(AVPacket &pkt)
{int Ret = 0;AVFrame *frame = av_frame_alloc();   /* 存在反复申请释放(转RGB后释放),后续优化 *//* 发送数据到ffmepg,放到解码队列中 */Ret = avcodec_send_packet(FormatContext->streams[pkt.stream_index]->codec, &pkt);if (0 != Ret) {BLOG("avcodec_send_packet fail, ret:%d", Ret);return NULL;}/* 从解码队列中取出frame */Ret = avcodec_receive_frame(FormatContext->streams[pkt.stream_index]->codec, frame);if (0 != Ret) {BLOG("avcodec_send_paavcodec_receive_framecket fail, ret:%d", Ret);return NULL;}return frame;
}

经过这几个关键接口,视频解码数据已经生成,并且通过frameque(RGB队列)转到显示模块(Bwidget)了。

3、视频显示

显示视频帧主要是通过Bwidget的paintEvent实现,通过定时器(update方法)触发画图事件,先从frame队列取出解码数据,再进行格式统一转化(RGBA32),最后就可以在画板上呈现出来了:

/********************************* void Bwidget::paintEvent(QPaintEvent *event)* 功能:绘制一帧图像* *****************************/
void Bwidget::paintEvent(QPaintEvent *event)
{if (Bvideo::GetInstance()->GetFrameque().frame.size() == 0) {return;}/* 解码数据出队列 */QMutexLocker Locker(&Bvideo::GetInstance()->GetFrameque().mtx);AVFrame* frame = Bvideo::GetInstance()->GetFrameque().frame.front();SwsContext *context = NULL;AVCodecContext *codec = Bffmpeg::GetInstance()->GetFormatContext()->streams[Bffmpeg::GetInstance()->GetVideoIndex()]->codec;/* 解码数据转RGB32 */context = sws_getCachedContext(context,                                         /* 转化上下文结构体 */codec->width, codec->height, codec->pix_fmt,     /* 源图像格式和宽高 */Width, Height, AV_PIX_FMT_BGRA,                  /* 目标图像格式和宽高 */SWS_BICUBIC, NULL, NULL, NULL);if (NULL == context) {BLOG("sws_getCachedContext fail");return;}uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };data[0] = (uint8_t *)Image->bits();int linesize[AV_NUM_DATA_POINTERS] = { 0 };linesize[0] = Width * 4;                /* 实际显示一行的宽度,32位4个字节 */sws_scale(context, frame->data,         /* 源视频解码数据 */frame->linesize,              /* 每行大小 */0,                            /* 用不到 */frame->height,                /* 图像高度 */data,                         /* 输出的每个通道数据指针 */linesize);                    /* 每个通道行字节数 *//* 释放解码数据,出队列 */av_frame_free(&frame);Bvideo::GetInstance()->GetFrameque().frame.pop_front();/* 绘制图像 */QPainter painter;painter.begin(this);painter.drawImage(QPoint(X, Y), *Image);painter.end();return;
}

此外,对于这个画板还有两个初始化的关键方法Bwidget::InitMedia(),在加载完媒体文件后调用,主要是相关信息获取、内存申请,画布规划这些:

/********************************* void Bwidget::InitMedia()* 功能:初始化媒体画布相关信息* *****************************/
void Bwidget::InitMedia()
{int WidgetWidth = width();          /* Bwidget宽 */int WidgetHeight = height();        /* Bwidget高 */int VideoIndex = Bffmpeg::GetInstance()->GetVideoIndex();int MediaWidth = Bffmpeg::GetInstance()->GetFormatContext()->streams[VideoIndex]->codec->width;     /* 视频宽 */int MediaHeight = Bffmpeg::GetInstance()->GetFormatContext()->streams[VideoIndex]->codec->height;   /* 视频高 */if (((float)WidgetWidth / (float)WidgetHeight) > ((float)MediaWidth / (float)MediaHeight)) {/* Bwidget宽高比大于视频,左右需要有黑边 */Width = (int)((float)WidgetHeight * (float)MediaWidth / (float)MediaHeight);Height = WidgetHeight;X = (WidgetWidth - Width) / 2;Y = 0;} else {/* Bwidget宽高比小于视频,上下需要有黑边 */Width = WidgetWidth;Height = ((float)WidgetWidth * (float)MediaHeight / (float)MediaWidth);X = 0;Y = (WidgetHeight - Height) / 2;;}if (ImageData) {delete ImageData;}if (Image) {delete Image;}/* 初始化视频区域 */ImageData = new uchar[Width * Height * 4];Image = new QImage(ImageData, Width, Height, QImage::Format_RGB32);return;
}

最后构建工程,效果如下:

BPlay1.0系列(6:视频播放)相关推荐

  1. 积少成多 Flash(ActionScript 3.0 Flex 3.0) 系列文章索引

    [源码下载] 积少成多 Flash(ActionScript 3.0 & Flex 3.0) 系列文章索引 作者:webabcd Flash 之 ActionScript 3.0  1.积少成 ...

  2. SpringBoot 2.0 系列001 -- 入门介绍以及相关概念

    为什么80%的码农都做不了架构师?>>>    SpringBoot 2.0 系列001 -- 入门介绍以及相关概念 什么是SpringBoot? 项目地址:http://proje ...

  3. 黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级)

    原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级) 本章介绍的是企业库加密应用程序模块 ...

  4. vSphere 5.0系列之二 vSphere Client的安装

    1.安装包如下载所示,解压之后,可选择安装不同语言 2.开始安装 3.专利说明 4.直接同意 5.两个都要填写 6.安装目录自己可以更改 7.单击安装 8.开始安装的过程 9.到这一步已基本安装完成了 ...

  5. Elasticsearch-6.7.0系列(六)ES设置集群密码

    感谢此老兄:<手把手教你搭建一个 Elasticsearch 集群> 前提准备 安装kibana-6.7.0: <Elasticsearch-6.7.0系列(三)5601端口 kib ...

  6. Asp.net MVC2.0系列文章-编辑和删除新闻操作

    上一篇文章,我们简单地完成了新闻内容的展示功能(Asp.net MVC2.0系列文章-显示列表和详细页面操作),此篇文章,我们使用Asp.net MVC2.0实现新闻记录的编辑和删除功能. 创建Vie ...

  7. 基于vue2.0实现音乐/视频播放进度条组件的思路及具体实现方法+代码解释

    基于vue2.0实现音乐/视频播放进度条组件的方法及代码解释 需求分析: ①:进度条随着歌曲的播放延长,歌曲播放完时长度等于黑色总进度条长度:时间实时更新. ②:当滑动按钮时,实时更新播放时间,橙色进 ...

  8. ESRI ArcGIS 9.0系列软件报价(转)

    ESRI ArcGIS 9.0系列软件报价  PartCode Product Description产品描述  单价  (人民币)   A/I-1 ArcInfo-1  180,000.00     ...

  9. 实战Spring Boot 2.0系列(一) - 使用Gradle构建Docker镜像

    前言 通常我们使用 Dockerfile 来构建项目的 Docker 镜像.但是也有使用 gradle 在编译项目的时候一起把镜像给 构建 并 上传 的需求.本文将会讲解如何使用 gradle 编写并 ...

最新文章

  1. 指示灯组与3个复位按钮的介绍Arduino Yun快速入门教程
  2. 2.11 linux的软防火墙apf安装配置
  3. Installation error: INSTALL_FAILED_CONTAINER_ERROR
  4. Openbox 3.3
  5. 小学计算机键盘的初步认识教案,教学设计——小小键盘真神奇
  6. Android简单闹钟设置
  7. linux vnc共享时权限设置,用VNC实现远程桌面共享(支持Windows, Linux, ...) - 易水博客...
  8. 0x8000FFFF 错误的解决方式
  9. TensorFlow1.x入门(2)——变量的定义及其操作
  10. Python 字符串重复判断
  11. 《人人都是产品经理》——第二章笔记(上)
  12. Thinkphp中的 I 函数(Thinkphp3.2.3版本)
  13. 使用 paddlehub的人物识别 对游戏人物识别 绘制方框
  14. Spring Festival
  15. PHP hypot,hypot - [ C语言中文开发手册 ] - 在线原生手册 - php中文网
  16. 2022美赛F题题目及思路--人人为我,我(空间)为人人
  17. java操作word,自动更新目录/域
  18. 苹果 IOS 早期版本 NDEF的读写问题 C#
  19. 智能停车场ARM工控主板应用
  20. PHP+MYSQL 可视化Echarts 完整源码,小白总结

热门文章

  1. PIPI OJ 1334: PIPI计数(unordered_map的应用)
  2. 大型机、小型机、x86服务器的区别
  3. 夜数星辰 相忘于边陲桃源
  4. VOL指标-成交量指标
  5. 如何理解git checkout -- file和git reset HEAD -- file
  6. 一步步地分析排序——归并排序
  7. roscore的执行
  8. bing 怎么搜索关键词_Bing更新了网站站长指南,以包含关键字填充
  9. FME-CAD专题之-生成带扩展属性的CAD文件
  10. JAVA日期类的格式转换