YUV420P转RGB32

YUV420P转RGB32上一节讲解了YUV420P格式的内容。我说过,我们不是为了做研究。平白无故讲了YUV420P的理论知识,要是不做点什么总说不过去吧。那么,我们就练练刀,写个播放YUV420P的程序吧,将前面保存的YUV420P图像用自己写的播放器播放出来。这里我们还是一样使用Qt来显示图像。之前做播放器的时候,是将YUV420P转换成RGB32,然后放到QI

大家好,欢迎来到IT知识分享网。

上一节讲解了YUV420P格式的内容。

我说过,我们不是为了做研究。 平白无故讲了YUV420P的理论知识,要是不做点什么总说不过去吧。 那么,我们就练练刀,写个播放YUV420P的程序吧,将前面保存的YUV420P图像用自己写的播放器播放出来。


这里我们还是一样使用Qt来显示图像。


之前做播放器的时候,是将YUV420P转换成RGB32,然后放到QImage里面显示出来。

由于Qt不支持直接显示YUV的图像。

同样,这里我们也是先将YUV420P转成RGB32。

不过,这次不是用FFMPEG来转,毕竟我们的根本目的不是为了写播放器,只是为了更加了解yuv420p,然后顺带写下这个播放器。 


同样一件事,目的不同,做法也就不同。

因此,我们通过自己写代码来实现YUV420P转RGB32。


YUV420P已经了解了,现在还差RGB32,那就先看看RGB32吧:


R代表red、G代表green、B代表blue。 他们的取值都是0~255。

所以每一个分量刚好又可以用一个字节来记录了。


这里还需要介绍下ARGB32,RGB就是代表RGB了,A代表的是Alpha(透明度)。

因此一个ARGB32就是有4个分量:A R G B。刚好就是4×8=32位了。


可能是为了兼容性,RGB32的存储方式和ARGB32是一样的,只是RGB32的A分量不存数据,

换句话说就是: ARGB32就是带Alpha通道的RGB32。


下面是我百度到的RGB32的介绍:

    RGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32就是带Alpha通道的RGB32。)注意在内存中RGB各分量的排列顺序为:BGRA BGRA BGRA…。通常可以使用RGBQUAD数据结构来操作一个像素,它的定义为:

typedef struct tagRGBQUAD {

  BYTE    rgbBlue;      // 蓝色分量
  BYTE    rgbGreen;     // 绿色分量
  BYTE    rgbRed;       // 红色分量
  BYTE    rgbReserved;  // 保留字节(用作Alpha通道或忽略)
} RGBQUAD;


这里需要注意的就是:他的排列顺序是  B G R A。

YUV转RGB有一个公式,如下:

YUV420P转RGB32

YUV(256 级别) 可以从8位 RGB 直接计算:

Y = 0.299 R + 0.587 G + 0.114 B

U = – 0.1687 R – 0.3313 G + 0.5 B + 128

V = 0.5 R – 0.4187 G – 0.0813 B + 128

反过来,RGB 也可以直接从YUV (256级别) 计算:

R = Y + 1.402 (V-128)

G = Y – 0.34414 (U-128) – 0.71414 (V-128)

B = Y + 1.772 (U-128)


当然,百度一下会发现YUV转RGB的公式还有好多,至于这些公式是怎么得出来的,本人也是没有头绪,经过测试,上面这个公式效果相对好一些。

现在已经掌握了YUV420P的存储方式和RGB32的存储方式,同时也知道了YUV转RGB的公式,要转换也就不难了。


先来回顾下YUV420P的格式:

YUV420P转RGB32


好了,现在对照这个图,应该很容易就能写出来转换的代码了:

1.为了更加方便的写代码,我们把RGB定义成一个结构体:

1
2
3
4
5
6
7
8
typedef 
unsigned 
char 
BYTE
;
 
typedef 
struct 
RGB32 {
  
BYTE    
rgbBlue;      
// 蓝色分量
  
BYTE    
rgbGreen;     
// 绿色分量
  
BYTE    
rgbRed;       
// 红色分量
  
BYTE    
rgbReserved;  
// 保留字节(用作Alpha通道或忽略)
} RGB32;

2.转换思路也很清晰了,只需要读取出每一个YUV的像素,然后转换成RGB就行了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void 
Yuv2Rgb::Yuv420p2Rgb32(
const 
BYTE 
*yuvBuffer_in,
const 
BYTE 
*rgbBuffer_out,
int 
width,
int 
height)
{
    
BYTE 
*yuvBuffer = (
BYTE 
*)yuvBuffer_in;
    
RGB32 *rgb32Buffer = (RGB32 *)rgbBuffer_out;
 
    
for 
(
int 
y = 0; y < height; y++)
    
{
        
for 
(
int 
x = 0; x < width; x++)
        
{
            
int 
index = y * width + x;
 
            
int 
indexY = y * width + x;
            
int 
indexU = width * height + y / 2 * width / 2 + x / 2;
            
int 
indexV = width * height + width * height / 4 + y / 2 * width / 2 + x / 2;
 
            
BYTE 
Y = yuvBuffer[indexY];
            
BYTE 
U = yuvBuffer[indexU];
            
BYTE 
V = yuvBuffer[indexV];
 
            
RGB32 *rgbNode = &rgb32Buffer[index];
 
            
///这转换的公式 百度有好多 下面这个效果相对好一些
 
            
rgbNode->rgbRed = Y + 1.402 * (V-128);
            
rgbNode->rgbGreen = Y - 0.34413 * (U-128) - 0.71414*(V-128);
            
rgbNode->rgbBlue = Y + 1.772*(U-128);
        
}
    
}
}


注:上述代码仅仅是为了实现功能,并没有考虑任何效率问题。

同时为了提高可读性,将各种Index都分开计算了。



main函数中如下调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <windows.h>
using 
namespace 
std;
 
#include "yuv2rgb.h"
 
int 
main()
{
    
cout << 
"Hello World!" 
<< endl;
 
 
    
FILE 
*fp_yuv = 
fopen
(
"in.yuv"
,
"rb"
);
    
FILE 
*fp_rgb = 
fopen
(
"out.rgb32"
,
"wb"
);
 
    
int 
width = 1920;
    
int 
height = 1080;
 
    
int 
yuvSize = width * height * 3 /2;
    
int 
rgbSize = width * height * 
sizeof
(RGB32);
 
    
BYTE 
*yuvBuffer = (
BYTE 
*)
malloc
(yuvSize);
    
BYTE 
*rgbBuffer = (
BYTE 
*)
malloc
(rgbSize);
 
    
Yuv2Rgb yuv2Rgb;
 
    
DWORD 
start = GetTickCount();
 
    
for
(
int 
i=0;;i++)
    
{
        
if 
(
feof
(fp_yuv)) 
break
;
 
        
int 
readedsize = 
fread
(yuvBuffer,1,yuvSize,fp_yuv);
 
        
DWORD 
t1 = GetTickCount();
        
yuv2Rgb.Yuv420p2Rgb32(yuvBuffer,rgbBuffer,width,height);
        
DWORD 
t2 = GetTickCount();
 
        
fprintf
(stderr,"%d use 
time 
= %ld ms
",i,t2-t1);
 
        
fwrite
(rgbBuffer,1,rgbSize,fp_rgb);
 
    
}
 
    
DWORD 
end = GetTickCount();
 
    
fprintf
(stderr,"Finished! use 
time 
= %ld ms
",end-start);
 
    
return 
0;
}

稍等片刻,转换就能结束。

将生成的out.rgb32用yuvplayer打开:

yuvplayer下载地址:http://download.csdn.net/detail/qq214517703/9637191


注:记得在选择文件的时候,将右下角的类型改成所有格式,否则会看不到这个文件:

YUV420P转RGB32


文件打开之后,如下设置:

YUV420P转RGB32

发现可以正常播放了,说明转换成功了。

还别高兴的太早,再仔细看下我们的代码,上面加了打印转换每张图片耗费的时间,我的电脑执行后得到如下结果:

YUV420P转RGB32

可以看出,每张图片都发了大概三四十毫秒的时间,100张图片总共发了5秒钟!

这个速度也太慢了,如果后面再加上编码,那还了得。

显然这个速度必须要优化下。

回过头看下我们转换部分的代码:

发现嵌套了2个循环,因为需要读取每一个像素然后转换成RGB,所以这个循环优化的空间就不大了。

再看看循环里面的内容:

1
2
3
rgbNode->rgbRed = Y + 1.402 * (V-128);            
rgbNode->rgbGreen = Y - 0.34413 * (U-128) - 0.71414*(V-128);
rgbNode->rgbBlue = Y + 1.772*(U-128);

全是乘法运算,还是浮点数。看来这个就是罪魁祸首了!

虽然一次乘法运算对于电脑的CPU根本不算什么,但是这里是1920×1080次,这个就相当可怕了!看来积少成多还是有点道理的。


自古以来,就流行牺牲空间换取时间的传统,这里当然也不例外。

前面说过Y U V每个分量的取值都是0~255。那么他们与某个小数相乘之后的结果也就只有256种结果。既然乘法操作是非常耗时的,那么我们就把相乘的结果先计算好,放入一个表中,要用的时候从这个表里面去取,这样就可以快很多了。


首先定义一个数组,用来存放计算好的结果:

1
2
3
unsigned 
short 
R_Y[COLORSIZE],R_U[COLORSIZE],R_V[COLORSIZE];    
unsigned 
short 
G_Y[COLORSIZE],G_U[COLORSIZE],G_V[COLORSIZE];
unsigned 
short 
B_Y[COLORSIZE],B_U[COLORSIZE],B_V[COLORSIZE];


然后在写一个初始化的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//表的初始化
void 
Yuv2Rgb::table_init()
{
    
int 
i;
    
for
(i = 0; i < COLORSIZE; i++)
    
{
        
///R = Y + 1.402 * (V-128);
        
//R_Y[i] = 0; //没有
        
//R_U[i] = 0; //没有
        
R_V[i] = 1.402 * (i-128);
 
        
///G = Y - 0.34413 * (U-128) - 0.71414*(V-128);
        
//G_Y[i] = 0;
        
G_U[i] = 0.34413 * (i-128);
        
G_V[i] = 0.71414 * (i-128);
 
        
/// = Y + 1.772*(U-128);
        
//B_Y[i] = 0;
        
B_U[i] = 1.772 * (i-128);
        
//B_V[i] = 0;
    
}
}

这里采用代码的方式来初始化表,而不是直接将最终的结果写入。是因为:

  1. 这样就几句代码,可读性强。

  2. 初始化函数我们只需要执行一次,因此这个时间是可以接受的。

  3. 后期要修改转换的算法也简单方便。

转换函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
///这个是优化的版本
void 
Yuv2Rgb::Yuv420p2Rgb32(
const 
BYTE 
*yuvBuffer_in,
const 
BYTE 
*rgbBuffer_out,
int 
width,
int 
height)
{
    
BYTE 
*yuvBuffer = (
BYTE 
*)yuvBuffer_in;
    
RGB32 *rgb32Buffer = (RGB32 *)rgbBuffer_out;
 
    
int 
w_h = width * height;
//width * height的值
    
int 
w_h_4 = width * height / 4;
//width * height / 4 的值
 
    
for 
(
int 
y = 0; y < height; y++)
    
{
        
int 
y_width = y * width; 
//y乘以width的值
        
int 
y_width_2_2 = y / 2 * width / 2; 
//y / 2 * width / 2
 
        
for 
(
int 
x = 0; x < width; x++)
        
{
            
int 
index = y_width + x;
 
            
int 
x_2 = x/2;
 
            
int 
indexU = w_h + y_width_2_2 + x_2;
            
int 
indexV = w_h + w_h_4 + y_width_2_2 + x_2;
 
            
BYTE 
Y = yuvBuffer[index];
            
BYTE 
U = yuvBuffer[indexU];
            
BYTE 
V = yuvBuffer[indexV];
 
            
RGB32 *rgbNode = &rgb32Buffer[index];
 
            
///这转换的公式 百度有好多 下面这个效果相对好一些
 
            
rgbNode->rgbRed = Y + R_V[V];
            
rgbNode->rgbGreen = Y - G_U[U] - G_V[V];
            
rgbNode->rgbBlue = Y + B_U[U];
 
        
}
    
}
}

调用的方式和之前一样,但别忘了初始化表:

1
2
Yuv2Rgb yuv2Rgb;    
yuv2Rgb.table_init(); 
//执行一次表的初始化

这样优化后的效果比直接稍微快了一些(100张图片大概快了一秒多一点),虽然还不是很理想,不过仔细一想1000张就能快10秒了,勉强有点用。

目前我只能想到这样的优化方法了,如果要效率高些,可以直接使用ffmpeg来转换,我们实际中基本上也是直接使用ffmpeg来操作的。

FFMPEG转换的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    
int 
yuvSize = width * height * 3 /2;    
    
BYTE 
*yuvBuffer = (
BYTE 
*)
malloc
(yuvSize);
 
    
AVFrame *pFrame = av_frame_alloc();
    
AVFrame *pFrameRGB = av_frame_alloc();
    
int 
numBytes = avpicture_get_size(PIX_FMT_RGB32, width,height);
 
    
uint8_t * rgbBuffer = (uint8_t *) av_malloc(numBytes * 
sizeof
(uint8_t));
    
avpicture_fill((AVPicture *) pFrameRGB, rgbBuffer, PIX_FMT_RGB32,width, height);
 
    
avpicture_fill((AVPicture *) pFrame, yuvBuffer, AV_PIX_FMT_YUV420P, width, height);
 
    
SwsContext *img_convert_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV420P, width, height, PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
 
    
int 
rgbSize = numBytes;
     
     
    
for
(
int 
i=0;;i++)
    
{
        
if 
(
feof
(fp_yuv)) 
break
;
 
        
int 
readedsize = 
fread
(yuvBuffer,1,yuvSize,fp_yuv);
 
        
DWORD 
t1 = GetTickCount();
 
        
sws_scale(img_convert_ctx,
                
(uint8_t 
const 

const 
*) pFrame->data,
                
pFrame->linesize, 0, height, pFrameRGB->data,
                
pFrameRGB->linesize);
 
 
        
DWORD 
t2 = GetTickCount();
 
        
fprintf
(stderr,"%d use 
time 
= %ld ms
",i,t2-t1);
 
        
fwrite
(rgbBuffer,1,rgbSize,fp_rgb);
    
}

会发现FFMPEG速度快好多,并且转换后的效果也好很多。。哈哈。。果然还是FFMPEG好用。

本文的目的主要是为了加深对YUV420P的认识,因此我们自己的代码就不去完善它了。

yuv420p转rgb完整工程:http://download.csdn.net/detail/qq214517703/9642041

加上了Qt显示功能的完整工程(Qt播放YUV420P文件):http://download.csdn.net/detail/qq214517703/9642365

学习音视频技术欢迎访问 http://blog.yundiantech.com  

音视频技术交流讨论欢迎加 QQ群 121376426

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/23667.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信