本文受了https://blog.csdn.net/fanyun_01/article/details/100068351 的启发。但是该文章有一处谬误:YUV420的内存排列不是按照下图所示的。
正确的排列应如下(来源维基百科yuv条目):
根据正确的内存排列,写出相应的代码:
YUV2RGB.h
#pragma once
#include <qglobal.h>
void vFillY_U_V_Buff(const quint32 & ui32Width, const quint32 & ui32Height, unsigned char * pSrcData,
unsigned char * pUData, unsigned char * pVData);
void vFillBGRA(unsigned char * pBGRA, const quint32 & ui32Width, const quint32 & ui32Height, unsigned char * pSrcData,
unsigned char * pUData, unsigned char * pVData);
void vInitLookupTable(void);
YUV2RGB.cpp
#include "YUV2RGB.h"
namespace {
float fRGBYUV1402[256];
float fRGBYUV0344[256];
float fRGBYUV0714[256];
float fRGBYUV1772[256];
}
void vInitLookupTable(void)
{
for(int k = 0; k < 256; k++)
{
fRGBYUV1402[k] = (float)(1.402 * (k - 128));
fRGBYUV0344[k] = (float)(0.34414 * (k - 128));
fRGBYUV0714[k] = (float)(0.71414 * (k - 128));
fRGBYUV1772[k] = (float)(1.772 * (k - 128));
}
}
void vFillY_U_V_Buff(const quint32 & ui32Width, const quint32 & ui32Height, unsigned char * pSrcData,
unsigned char * pUData, unsigned char * pVData)
{
//YBuff不需要填,因为pSrcData的首字节就是Y分量的第一字节
unsigned char * pDstU1, * pDstU2, * pDstV1, * pDstV2, * pu, * pv;
unsigned char * pUV = pSrcData + ui32Width * ui32Height;
quint32 ui32HalfHeight = ui32Height/2, ui32HalfWidth = ui32Width / 2;
for(quint32 m = 0; m < ui32HalfHeight; m++)
{
pDstU1 = pUData + 2 * m * ui32Width;
pDstU2 = pUData + (2 * m + 1) * ui32Width;
pDstV1 = pVData + 2 * m * ui32Width;
pDstV2 = pVData + (2 * m + 1) * ui32Width;
pu = pUV + m * ui32HalfWidth;
pv = pUV + ui32HalfHeight * ui32HalfWidth + m * ui32HalfWidth;
for(quint32 n = 0; n < ui32HalfWidth; n++)
{
*pDstU1 = *pu; *pDstU2 = *pu;
*pDstV1 = *pv; *pDstV2 = *pv;
pDstU1++; pDstU2++;
pDstV1++; pDstV2++;
*pDstU1 = *pu; *pDstU2 = *pu;
*pDstV1 = *pv; *pDstV2 = *pv;
pDstU1++; pDstU2++;
pDstV1++; pDstV2++;
pu++; pv++;
}
}
}
void vFillBGRA(unsigned char * pBGRA, const quint32 & ui32Width, const quint32 & ui32Height, unsigned char * pYData,
unsigned char * pUData, unsigned char * pVData)
{
unsigned char * pB = pBGRA, *pG, *pR, *pA;
float fR, fG, fB;
for(quint32 m = 0; m < ui32Height; m++)
{
for(quint32 n = 0; n < ui32Width; n++)
{
pG = pB + 1;
pR = pB + 2;
pA = pB + 3;
fR = *pYData + fRGBYUV1402[*pVData];
fG = *pYData - fRGBYUV0344[*pUData] - fRGBYUV0714[*pVData];
fB = *pYData + fRGBYUV1772[*pUData];
*pR = fR > 255 ? 255 : (fR < 0 ? 0 : fR);
*pG = fG > 255 ? 255 : (fG < 0 ? 0 : fG);
*pB = fB > 255 ? 255 : (fB < 0 ? 0 : fB);
*pA = 255;
pYData++;
pUData++;
pVData++;
pB += 4;
}
}
}
具体原理不再赘述。这里只介绍使用以上代码进行YUV->RGBA的转化方法。
第一步,调用vInitLookupTable()计算转化系数:
namespace {
float fRGBYUV1402[256];
float fRGBYUV0344[256];
float fRGBYUV0714[256];
float fRGBYUV1772[256];
}
转化的第二步,是把压缩过的U、V数据还原成没压缩的格式。调用vFillY_U_V_Buff()。其第一个输入参数是图片的宽度,第二个参数是图片高度,第三个参数是YUV数据的首地址。由于YUV数据的开头是Y数据,而且没有被压缩,所以也可以看成是Y分量的首地址。第四个参数是U分量的首地址。这块地址占用(宽度*高度)个像素,在调用vFillY_U_V_Buff之前就应该被分配。vFillY_U_V_Buff执行完毕后,这块内存就被填入相应的U分量数据。第五个参数是V分量的首地址。这块地址占用(宽度*高度)个像素,在调用vFillY_U_V_Buff之前就应该被分配。vFillY_U_V_Buff执行完毕后,这块内存就被填入相应的V分量数据。
第三步,将YUV数据转化为RGBA。qt的RGBA格式,以B为第一个分量,后面依次为GRA。所以vFillBGRA()函数填充的每一个像素,其次序都是BGRA。vFillBGRA()的第一个输入参数是一个地址,就是用来存储BGRA数据的,在调用vFillBGRA之前,应该预先给这个地址开辟一块内存,大小为(4 * 宽度 * 高度)。第二个参数是图片宽度,第三个参数是图片高度。第四个参数是Y分量的首地址,可以直接采用前面vFillY_U_V_Buff的第三个参数;第五个输入参数是U分量的首地址,应采用vFillY_U_V_Buff的第四个参数;第六个输入参数是V分量的首地址,应采用vFillY_U_V_Buff的第五个地址。最终的结果保存在第一个参数pBGRA中。
示例:
extern const quint32 cnst_ui32Width = 1920;
extern const quint32 cnst_ui32Height = 1080;
unsigned char * m_pBuffU, * m_pBuffV;
{
......
vInitLookupTable();
vInitBuff();
unsigned char * pBuffYUV = new unsigned char[cnst_ui32Width * cnst_ui32Height*3/2];
unsigned char * pBuffBGRA = new unsigned char[cnst_ui32Width * cnst_ui32Height * 4];
......
vFillY_U_V_Buff(cnst_ui32Width, cnst_ui32Height, pBuffYUV, m_pBuffU, m_pBuffV);
vFillBGRA(pBuffBGRA, cnst_ui32Width, cnst_ui32Height, pBuffYUV, m_pBuffU, m_pBuffV);
......
vReleaseBuff();
}
void vInitBuff(void)
{
m_pBuffU = new unsigned char[cnst_ui32Width * cnst_ui32Height];
m_pBuffV = new unsigned char[cnst_ui32Width * cnst_ui32Height];
}
void vReleaseBuff(void)
{
delete [] m_pBuffU;
delete [] m_pBuffV;
}