前面提到了YV12转RGB的各种实现方法和优化方法,主要是CPU上的实现。本文主要介绍基于GPU的YV12转RGB的实现。

1. 基于OpenGL的实现

利用OpenGL shader实现将YV12转RGB,将Y、U、V分量数据作为纹理数据,并构造YUV转RGB的shader代码,最终纹理数据在shader代码作用下,实现YV12转RGB。该方法适合于将YV12转RGB后直接显示,若YV12转化成RGB后,还需要进行图像处理操作,则利用OpenGL进行纹理数据的图像处理操作不方便。说明:由于本文着重于基于Cuda的实现,因而未验证基于OpenGL的代码实现。

具体资料可参考:

http://blog.csdn.net/xiaoguaihai/article/details/8672631

http://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c

2. 基于Cuda的实现

YV12转RGB的过程是逐一获取像素的Y、U、V分量,然后通过转换公式计算得RGB。基于CUDA的实现关键在于两个步骤:Y、U、V分量的获取,RGB的计算。Y、U、V分量的获取与YUV的内存布局有关,RGB的计算公式一般是固定不变。具体的代码实现如下所示,主要参考NV12ToARGB.cu的代码,在该代码的基础上,保持RGB的计算方法不变,修改了Y、U、V分量的获取方法。

#include "cuda.h"

#include "cuda_runtime_api.h"

#define COLOR_COMPONENT_BIT_SIZE 10

#define COLOR_COMPONENT_MASK     0x3FF

__constant__ float constHueColorSpaceMat[9]={1.1644f,0.0f,1.596f,1.1644f,-0.3918f,-0.813f,1.1644f,2.0172f,0.0f};

__device__ staticvoid YUV2RGB(constint* yuvi,float* red,float* green,float* blue)

{

float luma, chromaCb, chromaCr;

// Prepare for hue adjustment

luma     =(float)yuvi[0];

chromaCb =(float)((int)yuvi[1]-512.0f);

chromaCr =(float)((int)yuvi[2]-512.0f);

// Convert YUV To RGB with hue adjustment

*red   =(luma     * constHueColorSpaceMat[0])+

(chromaCb * constHueColorSpaceMat[1])+

(chromaCr * constHueColorSpaceMat[2]);

*green =(luma     * constHueColorSpaceMat[3])+

(chromaCb * constHueColorSpaceMat[4])+

(chromaCr * constHueColorSpaceMat[5]);

*blue  =(luma     * constHueColorSpaceMat[6])+

(chromaCb * constHueColorSpaceMat[7])+

(chromaCr * constHueColorSpaceMat[8]);

}

__device__ staticint RGBA_pack_10bit(float red,float green,float blue,int alpha)

{

int ARGBpixel =0;

// Clamp final 10 bit results

red   =::fmin(::fmax(red,   0.0f),1023.f);

green =::fmin(::fmax(green,0.0f),1023.f);

blue  =::fmin(::fmax(blue,  0.0f),1023.f);

// Convert to 8 bit unsigned integers per color component

ARGBpixel =(((int)blue  >>2)|

(((int)green >>2)<<8)  |

(((int)red   >>2)<<16)|

(int)alpha);

return ARGBpixel;

}

__global__ void YV12ToARGB_FourPixel(constunsignedchar* pYV12,unsignedint* pARGB,int width,int height)

{

// Pad borders with duplicate pixels, and we multiply by 2 because we process 4 pixels per thread

constint x = blockIdx.x *(blockDim.x <<1)+(threadIdx.x <<1);

constint y = blockIdx.y *(blockDim.y <<1)+(threadIdx.y <<1);

if((x +1)>= width ||(y +1)>= height)

return;

// Read 4 Luma components at a time

int yuv101010Pel[4];

yuv101010Pel[0]=(pYV12[y * width + x    ])<<2;

yuv101010Pel[1]=(pYV12[y * width + x +1])<<2;

yuv101010Pel[2]=(pYV12[(y +1)* width + x    ])<<2;

yuv101010Pel[3]=(pYV12[(y +1)* width + x +1])<<2;

constunsignedint vOffset = width * height;

constunsignedint uOffset = vOffset +(vOffset >>2);

constunsignedint vPitch = width >>1;

constunsignedint uPitch = vPitch;

constint x_chroma = x >>1;

constint y_chroma = y >>1;

int chromaCb = pYV12[uOffset + y_chroma * uPitch + x_chroma];      //U

int chromaCr = pYV12[vOffset + y_chroma * vPitch + x_chroma];      //V

yuv101010Pel[0]|=(chromaCb <<( COLOR_COMPONENT_BIT_SIZE       +2));

yuv101010Pel[0]|=(chromaCr <<((COLOR_COMPONENT_BIT_SIZE <<1)+2));

yuv101010Pel[1]|=(chromaCb <<( COLOR_COMPONENT_BIT_SIZE       +2));

yuv101010Pel[1]|=(chromaCr <<((COLOR_COMPONENT_BIT_SIZE <<1)+2));

yuv101010Pel[2]|=(chromaCb <<( COLOR_COMPONENT_BIT_SIZE       +2));

yuv101010Pel[2]|=(chromaCr <<((COLOR_COMPONENT_BIT_SIZE <<1)+2));

yuv101010Pel[3]|=(chromaCb <<( COLOR_COMPONENT_BIT_SIZE       +2));

yuv101010Pel[3]|=(chromaCr <<((COLOR_COMPONENT_BIT_SIZE <<1)+2));

// this steps performs the color conversion

int yuvi[12];

float red[4], green[4], blue[4];

yuvi[0]=(yuv101010Pel[0]&   COLOR_COMPONENT_MASK    );

yuvi[1]=((yuv101010Pel[0]>>  COLOR_COMPONENT_BIT_SIZE)       & COLOR_COMPONENT_MASK);

yuvi[2]=((yuv101010Pel[0]>>(COLOR_COMPONENT_BIT_SIZE <<1))& COLOR_COMPONENT_MASK);

yuvi[3]=(yuv101010Pel[1]&   COLOR_COMPONENT_MASK    );

yuvi[4]=((yuv101010Pel[1]>>  COLOR_COMPONENT_BIT_SIZE)       & COLOR_COMPONENT_MASK);

yuvi[5]=((yuv101010Pel[1]>>(COLOR_COMPONENT_BIT_SIZE <<1))& COLOR_COMPONENT_MASK);

yuvi[6]=(yuv101010Pel[2]&   COLOR_COMPONENT_MASK    );

yuvi[7]=((yuv101010Pel[2]>>  COLOR_COMPONENT_BIT_SIZE)       & COLOR_COMPONENT_MASK);

yuvi[8]=((yuv101010Pel[2]>>(COLOR_COMPONENT_BIT_SIZE <<1))& COLOR_COMPONENT_MASK);

yuvi[9]=(yuv101010Pel[3]&   COLOR_COMPONENT_MASK    );

yuvi[10]=((yuv101010Pel[3]>>  COLOR_COMPONENT_BIT_SIZE)       & COLOR_COMPONENT_MASK);

yuvi[11]=((yuv101010Pel[3]>>(COLOR_COMPONENT_BIT_SIZE <<1))& COLOR_COMPONENT_MASK);

// YUV to RGB Transformation conversion

YUV2RGB(&yuvi[0],&red[0],&green[0],&blue[0]);

YUV2RGB(&yuvi[3],&red[1],&green[1],&blue[1]);

YUV2RGB(&yuvi[6],&red[2],&green[2],&blue[2]);

YUV2RGB(&yuvi[9],&red[3],&green[3],&blue[3]);

pARGB[y * width + x     ]= RGBA_pack_10bit(red[0], green[0], blue[0],((int)0xff<<24));

pARGB[y * width + x +1]= RGBA_pack_10bit(red[1], green[1], blue[1],((int)0xff<<24));

pARGB[(y +1)* width + x     ]= RGBA_pack_10bit(red[2], green[2], blue[2],((int)0xff<<24));

pARGB[(y +1)* width + x +1]= RGBA_pack_10bit(red[3], green[3], blue[3],((int)0xff<<24));

}

bool YV12ToARGB(unsignedchar* pYV12,unsignedchar* pARGB,int width,int height)

{

unsignedchar* d_src;

unsignedchar* d_dst;

unsignedint srcMemSize =sizeof(unsignedchar)* width * height *3/2;

unsignedint dstMemSize =sizeof(unsignedchar)* width * height *4;

cudaMalloc((void**)&d_src,srcMemSize);

cudaMalloc((void**)*d_dst,dstMemSize);

cudaMemcpy(d_src,pYV12,srcMemSize,cudaMemcpyHostToDevice);

dim3 block(32,8);

int gridx =(width +2*block.x -1)/(2*block.x);

int gridy =(height +2*block.y -1)/(2*block.y);

dim3 grid(gridx,gridy);

YV12ToARGB<<<grid,block>>>(d_src,(unsignedint*)d_dst,width,height);

cudaMemcpy(pARGB,d_dst,dstMemSize,cudaMemcpyDeviceToHost);

returntrue;

}

  线程内存访问示意图如下所示,每个线程访问4个Y、1个U、1个V,最终转换得到4个ARGB值。由于YV12属于YUV4:2:0采样,每四个Y共用一组UV分量,即Y(0,0)、Y(0,1)、Y(1,0)、Y(1,1)共用V(0,0)和U(0,0),如红色框标注所示。


3. 基于Cuda的实现优化

优化主要关注于两个方面:单个线程处理像素粒度和数据传输。单个线程处理粒度分为:OnePixelPerThread,TwoPixelPerThread,FourPixelPerThread。数据传输优化主要采用Pageable Memory,Pinned Memory,Mapped Memory(Zero Copy)。经测试,不同实现版本的转换效率如下表所示,测试序列:1920*1080,时间统计包括内核函数执行时间和数据传输时间,单位为ms。

OnePixel

TwoPixel

FourPixel

Pageable

6.91691

6.64319

6.2873

Pinned

5.31999

5.01890

4.71937

Mapped

3.39043

48.5298

23.8327

由上表可知,不使用Mapped Memory(Zero Copy)时,单个线程处理像素的粒度越大,内核函数执行的时间越小,转换效率越好。使用Mapped Memory(Zero Copy)时,单线程处理单像素时,转换效率最好。

单个线程处理四个像素时,内核函数执行时间最少;使用Pinned Memory会减少数据传输时间;使用Mapped Memory消除数据传输过程,但会增加内核函数执行时间,最终优化效果与内核函数访问内存的方式有关。建议使用Pinned Memory+FourPixelPerThread的优化版本。

  利用NVIDIA提供的性能分析工具,分析Pinned Memory+FourPixelPerThread版本程序,分析结果如下图所示,内核计算时间占1/4左右,数据传输时间占3/4左右,总体而言,内核计算任务过少,导致并行优化的效果无法抵消数据传输的开销。

【视频处理】YV12ToARGB的更多相关文章

  1. 【腾讯bugly干货分享】HTML 5 视频直播一站式扫盲

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1277 视频直 ...

  2. premere cs4绿色版 安装 并且 视频导出 讲解

    最近室友,开始在玩视频剪辑,用的是 premere cs4 绿色版.让他遇到的最大问题也是我之前遇到的最大问题,就是视频导出. 所以我在这里上传一套自己的一点点经验吧. 接下来,我就总结一下 我是怎么 ...

  3. Power BI官方视频(3) Power BI Desktop 8月份更新功能概述

    Power BI Desktop 8月24日发布了更新版本.现将更新内容翻译整理如下,可以根据后面提供的链接下载最新版本使用. 1.主要功能更新 1.1 数据钻取支持在线版 以前的desktop中进行 ...

  4. 视频 - 在 VirtualBox 中部署 OpenStack

    大家新年好,CloudMan 今天给大家带来一件新年礼物. 一直以来大家都反馈 OpenStack 学习有两大障碍:1. 实验环境难搭2. 体系复杂,难道大今天我就先帮大家解决环境问题.前两天我抽空在 ...

  5. canvas与html5实现视频截图功能

    这段时间一直在研究canvas,突发奇想想做一个可以截屏视频的功能,然后把图片拉去做表情包,哈哈哈哈哈哈~~ 制作方法: 1.在页面中加载视频 在使用canvas制作这个截图功能时,首先必须保证页面上 ...

  6. html5 与视频

    1.视频支持格式. 有3种视频格式被浏览器广泛支持:.ogg,.mp4,.webm. Theora+Vorbis=.ogg  (Theora:视频编码器,Vorbis:音频编码器) H.264+$$$ ...

  7. 基于RN开发的一款视频配音APP(开源)

    在如今React.ng.vue三分天下的格局下,不得不让自己加快学习的脚步.虽然经常会陷入各种迷茫,学得越多会发现不会的东西也被无限放大,不过能用新的技术作出一些小项目小Demo还是会给自己些许自信与 ...

  8. 脑洞大开之采用HTML5+SignalR2.0(.Net)实现原生Web视频

    目录 对SignalR不了解的人可以直接移步下面的目录 SignalR系列目录 前言 - -,我又来了,今天废话不多说,我们直接来实现Web视频聊天. 采用的技术如下: HTML5 WebRTC Si ...

  9. duang~免费的学习视频来啦:学霸君之全栈测试

    学霸君向童鞋们推荐一款 同名学霸学习 视频教程 重点是完全免费收看学习噢!!! 今天 学霸君推荐腾讯课堂的学霸君之全栈测试 复制下方链接至腾讯课堂中报名学习 https://ke.qq.com/cou ...

随机推荐

  1. 2.Redis常用命令

    setget 匹配Keykeys *keys na*keys na?e 判断指定的Key是否存在exists key 删除一个或者多个keydel key//删除一个del key1 key2//删除 ...

  2. KnockoutJS 3.X API 第四章 数据绑定(4) 控制流with绑定

    with绑定的目的 使用with绑定的格式为data-bind="with:attribute",使用with绑定会将其后所跟的属性看作一个新的上下文进行绑定.with绑定内部的所 ...

  3. java接口中多继承的问题

    java中支撑多继承吗? 支持->接口啊 为什么接口支持多继承呢?因为接口中没有方法体!即使可能两个接口中有一样的抽象方法,但是 只会调用子类中覆盖该同样抽象方法的具体方法!不会引起调用的歧义! ...

  4. 10分钟学会理解和解决MySQL乱码问题

    在阅读本文之前,强烈建议对字符集编码概念还比较模糊的同学 阅读下博主之前对相关概念的一篇科普:十分钟搞清字符集和字符编码 本博客已经迁移至: http://cenalulu.github.io/ 为了 ...

  5. 使用Javascript监控前端相关数据

    项目开发完成外发后,没有一个监控系统,我们很难了解到发布出去的代码在用户机器上执行是否正确,所以需要建立前端代码性能相关的监控系统. 所以我们需要做以下的一些模块: 一.收集脚本执行错误 functi ...

  6. placeholder的兼容处理(jQuery下)

    这是一个老问题,结合前辈们的经验,需要处理的问题有一下几个. 1.只有输入框(input/textarea)下的palaceholder属性存在的时候才需要处理这类兼容 2.处理好输入框上焦点和是焦点 ...

  7. 第24/24周 数据库维护(Database Maintenance)

    哇哦,光阴似箭!欢迎回到性能调优培训的最后一期.今天我会详细讲下SQL Server里的数据库维护,尤其是索引维护操作,还有如何进行数据库维护. 索引维护 作为一个DBA,数据库维护是你工作中非常重要 ...

  8. CentOS上安装SQL Server vNext CTP1

    今天微软正式发布上SQL Server 2016 SP1,根据以往的SP1定律,可以在生产环境上使用了.打了SP1的标准版将具有企业版几乎所有的的功能.只有RAM 超过128GB或者超过24核心或者超 ...

  9. mysql 二进制日志后缀数字最大为多少

    之前看到mysql二进制日志后面会加一个以数字递增为结尾的后缀,一直在想当尾数到达999999后会发生什么情况,先查了一下官网,对后缀有这样一句介绍:The server creates binary ...

  10. Android Studio添加PNG图片报错原因

    今天在网上看到一个关于Splash Activity的Android帖子,博主在一通讲解之后也给出了代码.于是果断下载下来了看看怎么实现的.一步步照着流程把这个功能实现了一遍.一切都没有大问题,但是在 ...