BPlay1.0系列(6:视频播放)
前面我们已经完成了工程的构建、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:视频播放)相关推荐
- 积少成多 Flash(ActionScript 3.0 Flex 3.0) 系列文章索引
[源码下载] 积少成多 Flash(ActionScript 3.0 & Flex 3.0) 系列文章索引 作者:webabcd Flash 之 ActionScript 3.0 1.积少成 ...
- SpringBoot 2.0 系列001 -- 入门介绍以及相关概念
为什么80%的码农都做不了架构师?>>> SpringBoot 2.0 系列001 -- 入门介绍以及相关概念 什么是SpringBoot? 项目地址:http://proje ...
- 黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级)
原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级) 本章介绍的是企业库加密应用程序模块 ...
- vSphere 5.0系列之二 vSphere Client的安装
1.安装包如下载所示,解压之后,可选择安装不同语言 2.开始安装 3.专利说明 4.直接同意 5.两个都要填写 6.安装目录自己可以更改 7.单击安装 8.开始安装的过程 9.到这一步已基本安装完成了 ...
- Elasticsearch-6.7.0系列(六)ES设置集群密码
感谢此老兄:<手把手教你搭建一个 Elasticsearch 集群> 前提准备 安装kibana-6.7.0: <Elasticsearch-6.7.0系列(三)5601端口 kib ...
- Asp.net MVC2.0系列文章-编辑和删除新闻操作
上一篇文章,我们简单地完成了新闻内容的展示功能(Asp.net MVC2.0系列文章-显示列表和详细页面操作),此篇文章,我们使用Asp.net MVC2.0实现新闻记录的编辑和删除功能. 创建Vie ...
- 基于vue2.0实现音乐/视频播放进度条组件的思路及具体实现方法+代码解释
基于vue2.0实现音乐/视频播放进度条组件的方法及代码解释 需求分析: ①:进度条随着歌曲的播放延长,歌曲播放完时长度等于黑色总进度条长度:时间实时更新. ②:当滑动按钮时,实时更新播放时间,橙色进 ...
- ESRI ArcGIS 9.0系列软件报价(转)
ESRI ArcGIS 9.0系列软件报价 PartCode Product Description产品描述 单价 (人民币) A/I-1 ArcInfo-1 180,000.00 ...
- 实战Spring Boot 2.0系列(一) - 使用Gradle构建Docker镜像
前言 通常我们使用 Dockerfile 来构建项目的 Docker 镜像.但是也有使用 gradle 在编译项目的时候一起把镜像给 构建 并 上传 的需求.本文将会讲解如何使用 gradle 编写并 ...
最新文章
- 指示灯组与3个复位按钮的介绍Arduino Yun快速入门教程
- 2.11 linux的软防火墙apf安装配置
- Installation error: INSTALL_FAILED_CONTAINER_ERROR
- Openbox 3.3
- 小学计算机键盘的初步认识教案,教学设计——小小键盘真神奇
- Android简单闹钟设置
- linux vnc共享时权限设置,用VNC实现远程桌面共享(支持Windows, Linux, ...) - 易水博客...
- 0x8000FFFF 错误的解决方式
- TensorFlow1.x入门(2)——变量的定义及其操作
- Python 字符串重复判断
- 《人人都是产品经理》——第二章笔记(上)
- Thinkphp中的 I 函数(Thinkphp3.2.3版本)
- 使用 paddlehub的人物识别 对游戏人物识别 绘制方框
- Spring Festival
- PHP hypot,hypot - [ C语言中文开发手册 ] - 在线原生手册 - php中文网
- 2022美赛F题题目及思路--人人为我,我(空间)为人人
- java操作word,自动更新目录/域
- 苹果 IOS 早期版本 NDEF的读写问题 C#
- 智能停车场ARM工控主板应用
- PHP+MYSQL 可视化Echarts 完整源码,小白总结