HEVC的测试代码HM中给出了一个解码器示例,但是该解码器只能输出结果到yuv文件,不能跟放电影一样阅览。所以离实际应用总有一定距离,我现在刚开始学习HEVC,也想深入了解HEVC的编解码原理和具体过程,所以创建了这样一个播放器。该播放器和以前所做的一个基于ffmpeg的播放器基本框架几乎完全一样,所不同的是解码环节。

下面我们开始着手分析构建这样一个真实的播放器的流程。

一、基本流程分析

最简单的,一个播放器应该具有的基本功能是让用户选择一个文件并且播放,暂时不支持复杂的功能的问题是考虑到界面设计带来的问题(能力有限就要集中能力做主要的事情嘛)。

要想播放视频,首先必须要得到视频的每一帧图像。怎么得到一帧一帧的图像呢,我们可以借助于HM的示例代码予以分析。

我们启动和调试TAppDecoder项目,该项目中演示了一个HEVC解码器所做的基本工作。界面显示应该归于应用程序,而不是我们的解码器,所以HM中没有考虑。示例程序的解码流程如图 1所示。

1  解码器的流程图

我们需要做的工作就是,把原来解码器的解码结果输出到文件(依赖于程序的运行参数),修改成把解码的每一帧图像送给我们的应用程序,由应用程序显示每一帧图像。

稍微细致地走一遍代码,设置断点并且跟踪执行,发现了一个重要突破点,在于TAppDecoder::decode函数的末尾部分,有一个函数调用,即xWriteOutput(字面意思是写入输出,解码器要输出什么,当然是解码的结果,yuv图像啦),该函数的调用表明了一帧图像的读取完毕,程序尝试把该帧图像写入到文件。

知道这点之后,我们的工作便大致有了方向,便是修改该函数(函数框架也需要修改),改成在这里做更新界面的操作。

二、一帧图像的获取

自己再细致地阅读一下这个 TAppDecTop::xWriteOutput函数的内容,发现后来在while循环中有这么一句话。

m_cTVideoIOYuvReconFile.write( pcPic->getPicYuvRec(),crop.getPicCropLeftOffset(), crop.getPicCropRightOffset(),crop.getPicCropTopOffset(), crop.getPicCropBottomOffset() );

对该函数的流程的把握和理解可以帮助自己认识,上面这段代码完成了解码的图像输出操作,其实其变量名也很显著啦(Video IO Recon File即视频IO记录文件),再加上后面的函数名字为write可以确定是该函数完成了解码出来的图像的写文件操作。

仔细地分析,发现它传递的参数不再是TComPic类型的指针,而变成了它的成员,TComPicYuv类型的指针。再进一步跟进去看,突然发现了有 getWidth(), getHeight()之类的函数,还有copyToPic, WritePlane等等函数调用。那么,传递进来的指针应该是表示了一帧图像。

我们再看看 TComPic的函数成员,类似 getPicYuvRec()的函数还有不少,大家不妨自己看一下,我就不列举了。从代码理解上来看,这个应该就是解码器重建的图像帧(Rec是Reconstruction的缩写)。

好了,现在大体确定了如何获得一帧图像。仿照那个xWriteOutput函数写就好了,下面我们需要做的就是把pcPic->getPicYuvRec()转换成我们可以显示的图像。

三、YUV图像显示

在MFC中绘图常用的是显示RGB格式的图像,而HEVC编码解码出来的结果一般都是YUV格式的图像,关于两者的区别大家可以百度或谷歌查一查,嫌麻烦的话戳这里:

http://blog.csdn.net/chen825919148/article/details/7921475

一个YUV颜色转换成RGB颜色值的公式为:

R = Y + 1.4075 *(V-128)

G = Y – 0.3455 *(U –128) – 0.7169 *(V –128)

B = Y + 1.779 *(U – 128)

如果我们得到了一个像素的YUV值,那么通过这个公式是很容易得到该点的RGB值的,那么问题在于什么地方呢?

A)HEVC的YUV格式

综合一二中的分析我们知道了解码出来的一帧图像实际上是由 TComPicYuv 类的对象再维护,那么该类的对象中YUV是啥格式呢,不妨看看该类的声明和实现代码。

TComPicYuv::create这个函数比较关键,中间显示了指针指向内存区域的分配,可以见到这样的一段代码:

 m_apiPicBufY      = (Pel*)xMalloc( Pel, ( m_iPicWidth      + (m_iLumaMarginX  <<1)) * (m_iPicHeight       + (m_iLumaMarginY  <<1)));m_apiPicBufU      = (Pel*)xMalloc( Pel, ((m_iPicWidth >> 1) +(m_iChromaMarginX<<1)) * ((m_iPicHeight >> 1) +(m_iChromaMarginY<<1)));m_apiPicBufV      = (Pel*)xMalloc( Pel, ((m_iPicWidth >> 1) +(m_iChromaMarginX<<1)) * ((m_iPicHeight >> 1) +(m_iChromaMarginY<<1)));m_piPicOrgY       = m_apiPicBufY +m_iLumaMarginY   * getStride()  + m_iLumaMarginX;m_piPicOrgU       = m_apiPicBufU +m_iChromaMarginY * getCStride() + m_iChromaMarginX;m_piPicOrgV       = m_apiPicBufV + m_iChromaMarginY *getCStride() + m_iChromaMarginX;

再结合各个变量之间的关系(再该函数的前面一部分声明并赋值),可以发现m_apiPicBufY 所分配的内存空间是 m_apiPicBufU 和 m_apiPicBufV 的四倍,具体而言是长度和宽度都小一倍。也就是说没一个U像素(或V像素)对应着四个Y像素。怎么是这样子呢?

我查了一下HEVC的官方文档(JCT-VC_L1003_v28.doc),里面有这样一副图,编号为 Figure 6-1,在第20页,不同版本的可以搜索 YCbCr 找找。

图 2  YCbCr采样点位置示例图

这样子看来不知道大家有没有什么想法,就是U点和V点对应了图中的小圆圈,也就是说周围四个像素都采用这样一个值。

B)代码的实现

上面讲述了TComPicYuv中YUV图像的格式,这里我们出于界面显示的需要,需要把它转化为RGB格式,用bmp图像贴图的方式显示。

代码如下。

TComPic* pcPic = *(iterPic);if ( pcPic->getOutputMark() && (not_displayed >  pcPic->getNumReorderPics(tId) && pcPic->getPOC() > m_iPOCLastDisplay)){// write to filenot_displayed--;width = pcPic->getPicYuvRec()->getWidth();height = pcPic->getPicYuvRec()->getHeight();if ( NULL == lpDiBits ){lpDiBits = new char[width * height * 3];      // 一般来说视频的图像宽度为 4 的倍数,所以不用管对齐了memset( lpDiBits, 0, width * height * 3 );}Pel * y = (Pel *)pcPic->getPicYuvRec()->getLumaAddr(),   // 重建图像的 Y 颜色开始地址*u = (Pel *)pcPic->getPicYuvRec()->getCbAddr(),     // 重建图像的 Cb 颜色开始地址*v = (Pel *)pcPic->getPicYuvRec()->getCrAddr();        // 重建图像的 Cr 颜色开始地址byte *pRGB = (byte *)lpDiBits;       // bmp 的颜色开始for ( int j = 0; j < height; j++ ){for ( int i = 0; i < width; i+=2 ){// 三个像素值依次为GBR,而不是RGB*pRGB++ = static_cast<byte>(*y + 1.772*(*u-128) + 0);  // B*pRGB++ = static_cast<byte>(*y - 0.34413*(*u-128) - 0.71414*(*v-128)); // G*pRGB++ = static_cast<byte>(*y + 8 + 1.402*(*v-128));    // Ry++;*pRGB++ = static_cast<byte>(*y + 1.772*(*u-128) + 0);  // B*pRGB++ = static_cast<byte>(*y - 0.34413*(*u-128) - 0.71414*(*v-128)); // G*pRGB++ = static_cast<byte>(*y + 8 + 1.402*(*v-128));    // Ry++;u++;    // 横向两个点才变化一次,因为其表示了 2 * 2个点的像素值v++;}y += ( pcPic->getStride() - width);          // 像素值的偏移位置修正if ( j % 2 == 1 ){u -= width / 2;   // 奇数行(从0开始编号)才变化,也就是到下一个偶数像素点时改变了v -= width / 2 ;}else{u += (pcPic->getCStride() - width / 2);  // 像素值偏移的修正v += (pcPic->getCStride() - width / 2); // 像素值偏移的修正}}
/ / 后续部分省略。

代码中间还有一些细节的东西,即偏移位置的修正,这里可以参考一下之前所说的TComPicYuv::create 函数的内容,实际上几个图像(org、rec等等)所分配的内存空间是重叠在一起的,所以需要有上述修正才能正常显示。

四、项目编译中出现的问题

首先比较严重的是说error LNK2038RuntimeLibrary不匹配的解决

解决办法:

在工程上右键-》属性-》c/c++-》代码生成-》运行库

改成(release为MT,debug为MTD)即可解决:(所有项目要一样才行)

error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MT_StaticRelease”不匹配值“MD_DynamicRelease”

参考:http://blog.csdn.net/wpc320/article/details/8496957

此外还有怎么使用静态库,我是直接在工程中加入了几个lib文件。

参考:http://blog.csdn.net/liugy1126/article/details/1541466

五、代码及工程下载

该工程基本和上一篇中所讲述的ffmpeg构建播放器类似,只是替换了解码器的部分。

请看:http://blog.csdn.net/luofl1992/article/details/8293405

稍后会更新,把源代码(VS2012版,怎么用VS2010,自己改一下工程文件就行了)上传到CSDN供大家下载研究。

全部源代码下载:http://download.csdn.net/detail/luofl1992/5238393

用MFC构建HEVC码流播放器相关推荐

  1. 我的HEVC码流分析工具MFC小笔记:树形控件使用及窗口缩放

    大约1个半月前,发布了自己写的HEVC码流分析工具.当时的版本显示语法元素使用是的Edit控件.很多主流分析工具都使用树形控件,看上去觉得比较高端,于是在纠结一天后下定决心自己也实现树形的HEVC语法 ...

  2. 原始 H.264 码流播放

    我们平时遇到的视频文件各式各样,五花八门.通常它们会根据格式的不同,而有着不同的扩展名,比如 avi,rmvb,mkv,mp4 等等等.这些格式代表的都是 封装格式. 这些文件通常产生的过程是这样的: ...

  3. 国标MPEG-PS实时流播放器开发(附例子)

    公安部制定的GBT 28181标准广泛应用于安防领域,这个标准规定了传输的视音频数据要封装成PS流格式.PS格式(原名叫MPEG-PS)在很多领域已经应用了很长一段时间,特别是在安防.广播电视.影音制 ...

  4. Android可以使用的直播流播放器Vitamio5.2.3

    yixia大神的git上有两个Android直播流播放器的项目:GitHub - yixia/VitamioBundle: Vitamio for Android  弄了1天没弄出来,过! GitHu ...

  5. MFC入门-MCI简单音乐播放器实现

    MFC入门-MCI简单音乐播放器实现 实现功能: 1)        播放 2)        暂停/恢复 3)        停止 4)        音量调节 实现步骤: 1创建工程 打开VS20 ...

  6. 完成一个H.265/HEVC码流分析工具

    经过大约一个月左右的业余时间,终于初步完成一个H.265/HEVC码流分析工具.时间包括平时的周末.晚上,以及调休的集中时间.当然,中秋回家过节不写代码.截至今天,经过多种H.265序列测试,也有各种 ...

  7. 一个H.265/HEVC码流分析工具

    经过大约一个月左右的业余时间,终于初步完成一个H.265/HEVC码流分析工具.时间包括平时的周末.晚上,以及调休的集中时间.当然,中秋回家过节不写代码.截至今天,经过多种H.265序列测试,也有各种 ...

  8. C++ RTMP直播流播放器

    抛开flash,自己开发实现C++ RTMP直播流播放器 众所周知,RTMP是以flash为客户端播放器的直播协议,主要应用在B/S形式的场景中.本人研究并用C++开发实现了RTMP直播流协议的播放器 ...

  9. linux串流windows,【新品速递】高阶计算机讯源:SOtM sMS-1000SQ Windows Edition串流播放器...

    原标题:[新品速递]高阶计算机讯源:SOtM sMS-1000SQ Windows Edition串流播放器 SOtM来自韩国,旗下产品以串流播放与音响级网络周边配备为主,这部sMS-1000SQ W ...

  10. Android 直播 直播测试拉流播放器和地址

    Android 直播 直播测试拉流播放器和地址 直播拉流播放器 直播拉流测试地址 测试类抖音视频 直播拉流播放器 推荐VLC 直播拉流测试地址 香港财经 *****:rtmp://202.69.69. ...

最新文章

  1. (cljs/run-at (JSVM. :all) Metadata就这样哦)
  2. 在spring boot 配置actuator
  3. servlet指定时间到现在过了多久_就喝一瓶啤酒,多久能开车?交警:过了这个时间,100%没问题...
  4. (Mybaitis)分页
  5. Request_获取请求参数中文乱码问题处理
  6. Android之You need to use a Theme.AppCompat theme (or descendant) with this activity.
  7. iqooneo系统要不要更新_IQOOZ1即将发布,和IQOOneo3、OPPORenoACE对比,谁更值得入手?...
  8. 滴答定时器的计数模式_【高手私藏】STM32学习笔记:SysTick滴答时钟
  9. 超分辨率算法大战!AI in RTC 创新挑战赛——20万巨奖等你来拿!
  10. 第二章 在Linux上部署.net core
  11. servlet ---- 案例(简单)优化
  12. IE各浏览器HACK
  13. VMware系列序列号
  14. 图像处理:理想低通滤波器、butterworth滤波器(巴特沃斯)、高斯滤波器实现(python)
  15. 相机的光圈、快门、ISO到底是什么鬼?
  16. Python的Open CV学习三
  17. Echarts Y轴遮挡解决方案
  18. 打怪升级,看俄罗斯小哥是如何从互联网转行到自动驾驶行业?
  19. 回顾 | 女性 AI 专场 - 女性眼中的 ChatGPT
  20. 多表操作-外键级联操作

热门文章

  1. 手把手带你开发豆瓣FM(vue)
  2. Axure 8图标样式库
  3. 前端大佬们都在使用的JavaScript工具函数宝典-内含95个工具函数方法
  4. RemObjects SDK 简介
  5. MPPT “最大功率点跟踪”
  6. 地图数据下载方法与商业地图下载器介绍
  7. Ms visio 下载
  8. Oracle 中文转五笔码
  9. 16S多样性组成谱研究,9.13分的Water Research轻松二连发!
  10. 数字通信原理的几个理解