////本文由銹水管原創(chuàng)。
網(wǎng)上有很多YUV到RGB的轉(zhuǎn)化程序,不過他們基本上都是基于CPU進(jìn)行計算,基于CPU計算大體上有一下的一些方法,最原始的肯定是根據(jù)轉(zhuǎn)換公式直接進(jìn)行浮點運算,要想提高速度,可以用左移和右移操作,將浮點運算變成整數(shù)運算,這樣轉(zhuǎn)化的速度會成倍的提高。另外還可以用查表法,因為YUV都是在0~255之間,他們總是有范圍的,先生成一個很大的查找表,直接對每一個YUV分量查找出RGB值,當(dāng)然這個查找表會很大,可以用部分查找的方法來縮小查找表的容量。
用CPU計算比較好的方法就是利用CPU的SSE指令,有一個專門的名詞是來形容SSE的,那就是“單指令流多數(shù)據(jù)流”,意思就是他可以一次對多個數(shù)據(jù)進(jìn)行計算,當(dāng)然速度是非??斓?。
在這里,我主要寫一下我用shader做轉(zhuǎn)化的方法,首先,當(dāng)然是介紹一下YUV:
Windows下YUV格式的介紹在MSDN上介紹的非常全面。YUV的格式有很多種,有444,422,420之分,并且每一種具體的格式也有許多不同的標(biāo)準(zhǔn)。具體在MSDN的以下地址上有詳細(xì)的介紹:
http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/VideoRende8BitYUV.mspx?mfr=true
不過對于寫程序來說,我只關(guān)心每種格式的內(nèi)存布局方式。
YUV分為打包格式(packed)和平面格式(planar),平面格式中YUV的每個分量是分開存儲的,一般先存儲所有的Y分量,再存儲所有的U分量,然后存儲所有的V分量;打包方式則是YUV的每個分量并沒有分開存儲,而是存儲在一起。例如:
打包格式(圖片來自于MSDN),422中的YUY2格式:
我用到的就是YUV420的IMC4格式,是一種平面格式。這種格式的特點是先存儲所有的Y分量,總共有多少像素,就有多少個Y分量。緊接著,存儲U分量,再存儲V分量,U和V分別都只占總像素的1/4,所以,我所用的YUV420格式的一幀所占的內(nèi)存空間可以這樣計算:
m_FrameHeight; //視頻高度
m_FrameWidth; //視頻寬度
m_ImgSize = m_FrameWidth * m_FrameHeight; //寬 * 高
m_FrameSize = m_ImgSize + (m_ImgSize >> 1); //YUV視頻一幀的大小,其中的右移相當(dāng)于/2
所以每個分量的內(nèi)存地址可以按如下方式計算:
unsigned char* cTemp[3];
cTemp[0] = m_yuv + m_FrameSize * n; //y分量地址
cTemp[1] = cTemp[0] + m_ImgSize; //u分量地址
cTemp[2] = cTemp[1] + (m_ImgSize >> 2); //v分量地址
m_yuv是整段視頻的首地址,也就是指向整段視頻的指針;n是當(dāng)前幀的序號。
知道了每一幀的YUV各分量的地址,再進(jìn)行轉(zhuǎn)換就比較容易了。有很多公式可以進(jìn)行轉(zhuǎn)換,我所用到的轉(zhuǎn)換公式是這樣的:
R = y + 1.4022 * (v - 128)
G = y - 0.3456 * (u - 128) - 0.7145 * (v - 128)
B = y + 1.771 * (u - 128)
下面開始利用Shader按照這個轉(zhuǎn)換公式來進(jìn)行計算,由于我也是剛剛開始接觸shader編程,對于shader的工作方式也不是十分熟悉,我也只是按照自己的理解來進(jìn)行編程。
首先,顯卡在運行shader程序的時候,是很多像素并行計算的,所以轉(zhuǎn)換的速度很快,但是我發(fā)現(xiàn)針對每一個像素,shader只知道他自己的像素信息,包括他自己的顏色(RGB)、深度、紋理坐標(biāo)等等。對于其他像素的信息一無所知,例如,一個紅色像素只知道他自己應(yīng)該顯示成紅色,對于他左邊的像素是什么顏色,他不會知道,更不用說隔他更遠(yuǎn)的像素了。于是他僅僅只能利用自己的信息來進(jìn)行運算。這樣就遇到一個問題,由于YUV的每個分量存儲的位置相隔很遠(yuǎn),要在一個shader程序中分別得到一個像素相應(yīng)的YUV分量數(shù)據(jù),就要對內(nèi)存的數(shù)據(jù)排列格式做相應(yīng)的調(diào)整。我所用的方法是將視頻的數(shù)據(jù)格式調(diào)整成按照YUVYUVYUV...一一對應(yīng)的格式來排列,這樣,我可以先把YUV的數(shù)據(jù)假裝是RGB的數(shù)據(jù)送入到紋理中,騙過顯卡,然后再在shader中用轉(zhuǎn)換公式來進(jìn)行計算。由于YUV分量的數(shù)據(jù)范圍也是0~255之間的,所以這種方式是可以成功的。
(這里可能說的不對,我認(rèn)為每一個像素在運行shader的時候,應(yīng)該是有方法能夠得到除自己以外其他像素信息的,但是我學(xué)習(xí)shader還只有一個星期的時間,可能還不知道方法,如果有這樣的方法的話,那么轉(zhuǎn)換的效率會更高,因為我不需要在內(nèi)存中再花時間來做數(shù)據(jù)的排列工作。但這個可能要對shader的工作原理非常清楚,等以后我熟悉了,再來重新寫一個程序。)
下面開始,首先是排列過程,這個過程發(fā)生在內(nèi)存中:
for(y=0; y < m_FrameHeight; y++)
{
for(x=0; x < m_FrameWidth; x++)
{
l = y * m_FrameWidth + x;
m = (y / 2) * (m_FrameWidth / 2) + x / 2;
m_Data[3 * l] = cTemp[0][l]; //y
m_Data[3 * l + 1] = cTemp[1][m]; //u
m_Data[3 * l + 2] = cTemp[2][m]; //v
}
}
排列的方法很簡單,只要理解了上面的那個內(nèi)存布局結(jié)構(gòu),就很容易知道了。
然后將排列好的YUV數(shù)據(jù)送入到紋理中,在每一幀的渲染之前,都運行shader程序。其中shader程序代碼如下:
頂點著色器Vertex Shader:
void main()
{
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = ftransform();
}
片段著色器Fragment Shader:
uniform sampler2D tex;
void main()
{
vec4 yuv = texture2D(tex,gl_TexCoord[0].st);
vec4 color;
color.r =yuv.r + 1.4022 * yuv.b - 0.7011;
color.r = (color.r < 0.0) ? 0.0 : ((color.r > 1.0) ? 1.0 : color.r);
color.g =yuv.r - 0.3456 * yuv.g - 0.7145 * yuv.b + 0.53005;
color.g = (color.g < 0.0) ? 0.0 : ((color.g > 1.0) ? 1.0 : color.g);
color.b =yuv.r + 1.771 * yuv.g - 0.8855;
color.b = (color.b < 0.0) ? 0.0 : ((color.b > 1.0) ? 1.0 : color.b);
gl_FragColor = color;
}
其中為什么會有-0.7011、0.53005、0.8855這樣的常數(shù),那是因為顏色數(shù)據(jù)從著色器中的到的時候,已經(jīng)轉(zhuǎn)化成了浮點數(shù)的形式,范圍為0.0~1.0之間,所以轉(zhuǎn)換公式后面的系數(shù)也要做相應(yīng)的變化。
最終效果,其中程序框架就是上次我寫的MFC的框架:
聯(lián)系客服