AVFrame 与 yuv420那些事

13 篇文章 0 订阅

AVFrame 与 yuv420那些事

用ffmpeg编写播放器,本来一直是在解码后使用sws_scale转换一下格式,将yuv420p(或者其它格式)转换成AV_PIX_FMT_RGB32,然后保存到图片类中或者直接显示。
后来发现opengl能直接渲染yuv格式的数据,因此就将格式转换这一步给删了。简化了流程,降低了cpu占用率。

yuv420p介绍

来源

RGB的介绍
  1. 视网膜存在三种视锥细胞,分别含有对红、绿、蓝三种光线敏感的视色素,当一定波长的光线作用于视网膜时,以一定的比例使三种视锥细胞分别产生不同程度的兴奋,这样的信息传至中枢,就产生某一种颜色的感觉。

  2. 在彩色显示器发明之前,人类已经懂得使用三原色光调配出所有颜色的光。并不是说三原色混合后产生了新的频率的光,而是给人眼睛的感觉是这样。

  3. 在显示器发明之后,从黑白显示器发展到彩色显示器,人们开始使用发出不同颜色的光的荧光粉(CRT,等离子体显示器),或者不同颜色的滤色片(LCD),或者不同颜色的半导体发光器件(OLED和LED大型全彩显示牌)来形成色彩,无一例外的选择了Red,Green,Blue这3种颜色的发光体作为基本的发光单元。通过控制他们发光强度,组合出了人眼睛能够感受到的大多数的自然色彩。

  4. 先说说RGB,这个格式很直观。我们一眼就能明白它所表示的意义。
    RGB格式一般画面数据最终呈现的时候才会使用它。对于视频捕获和编解码等应用来讲,这样的表示方式数据量太大了。需要想办法在不太影响感觉的情况下,对原始数据的表示方法进行更改,减少数据量。

  5. 只要最终能呈现的是RGB,我们中间使用什么格式都无所谓。 于是我们使用Y,Cb,Cr模型来表示颜色。
    还有就是,这些颜色模型RGB,RGB565,YUV420I,YUV420P,YUV422…都只是一种类型而已,是对颜色的不同表示方法,就是物体桌子,我们用汉语是”桌子”,英语是”desk”,日语是”テーブル”。它们都有自己特定的应用领域,雷达、x光图片、电视屏幕、计算机显示器、嵌入式显示屏等等,跟显示驱动还有关系,不用太纠结于这些,纠结了也没用。

  6. 为什么电视行业用这个格式。
    YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
    与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。

为什么用它

  1. 空间占用少。
    RGB24一帧的大小size=width×heigth×3 Byte
    RGB32一帧的大小size=width×heigth×4 Byte
    YUV420一帧的大小size=width×heigth×1.5 Byte
    (这是在内存中展开的大小)

  2. 行业惯性
    (这个是我猜的)
    有些东西,并不是出来新的就用抛弃旧的了。这个不像是软件,重装一下就好了,一大堆硬件呢。
    例如:yuv420是为了适配广播电视而产生的。难道说有了别的更高级的编码我就抛弃它,那么那些老电视都没用了。

用途

YUV型颜色空间/电视系统颜色空间:由广播电视需求的推动而开发的颜色空间,主要目的是通过压缩色度信息以有效地播送彩色电视图像。例如,YUV,YIQ,ITU-R BT.601 Y’CbCr, ITU-R BT.709 Y’CbCr和SMPTE-240M Y’PbPr等颜色空间。

一般视频文件(mp4、avi啥的用mediainfo查看),解码后的颜色空间都是yuv。
一般来说yuv格式的图片是不能直接显示,需要转换成RGB格式才行(也就是一般的图片类不支持yuv二进制数据的直接读入,至少QImage类是这样的)。

yuv420p在AVFrame中的结构保存

如何得到

网上有很多yuv420p的文件,通常我是从这里下载的。
这些视频文件需要专门的yuv420p播放器播放才行(这个我就不贴链接了)。

结构分析

YUV表示图片的方式

三个图来直观地表示采集的方式,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。
格式排布
1. YUV 4:4:4采样,每一个Y对应一组UV分量。
2. YUV 4:2:2采样,每两个Y共用一组UV分量。
3. YUV 4:2:0采样,每四个Y共用一组UV分量。
详细

YUV转换成RGB算法

先说明,有很多不同转换,算法相似,参数不同。
网上有哥们总结了一下。
我这里只贴两个。注意不要纠结于这些参数的细节,可能你只要知道,更改这些参数,颜色会变化就好了。
也不要自己试着去实现算法,因为早就有现成的了,ffmpeg中的SwsContext就是为了完成这个事的。如果你是学生另算。
向那些折腾颜色的参数的家伙们致敬。

应用:模拟领域
Y’= 0.299*R’ + 0.587*G’ + 0.114*B’
U’= -0.147*R’ - 0.289*G’ + 0.436*B’ = 0.492*(B’- Y’)
V’= 0.615*R’ - 0.515*G’ - 0.100*B’ = 0.877*(R’- Y’)
R’ = Y’ + 1.140*V’
G’ = Y’ - 0.394*U’ - 0.581*V’
B’ = Y’ + 2.032*U’

YCbCr模型来源于YUV模型。YCbCr是 YUV 颜色空间的偏移版本.
应用:数字视频,ITU-R BT.601建议
Y’ = 0.257*R’ + 0.504*G’ + 0.098*B’ + 16
Cb’ = -0.148*R’ - 0.291*G’ + 0.439*B’ + 128
Cr’ = 0.439*R’ - 0.368*G’ - 0.071*B’ + 128
R’ = 1.164*(Y’-16) + 1.596*(Cr’-128)
G’ = 1.164*(Y’-16) - 0.813*(Cr’-128) - 0.392*(Cb’-128)
B’ = 1.164*(Y’-16) + 2.017*(Cb’-128)

上面各个符号都带了一撇,表示该符号在原值基础上进行了伽马校正,伽马校正有助于弥补在抗锯齿的过程中,线性分配伽马值所带来的细节损失,使图像细节更加丰富。在没有采用伽马校正的情况下,暗部细节不容易显现出来,而采用了这一图像增强技术以后,图像的层次更加明晰了。
H264里面的YUV应属于YCbCr, 也可以点这里获得其他一些信息。
上段主要来自这里

AVFrame中的数据存放
yuv420p与yuv420的区别

区别只是在于数据在内存中的排布:
yuv420p:yyyyyyyy uuuu vvvvv
yuv420: yuv yuv yuv yuv

yuv420p在AVFrame中的存储

YUV420P(planar格式)在ffmpeg中存储是在struct AVFrame的data[]数组中
data[0]——-Y分量
data[1]——-U分量
data[2]——-V分量
linesize[]数组中保存的是对应通道的数据宽度
linesize[0]——-Y分量的宽度
linesize[1]——-U分量的宽度
linesize[2]——-V分量的宽度

注意:
linesize[0]的值并不一定等于图片的宽度。我将一张1366*768的图片编码后,linesize[0]的值为1376,大概是为了内存对齐的缘故,我就没深究了。

将yuv数据保存起来

这里是将yuv数据保存到文件(或者自己申请内存的char*)中。

uchar *pY = t_pVideoFrame->data[0];
uchar* pU = t_pVideoFrame->data[1];
uchar* pV = t_pVideoFrame->data[2];
int t_ySize = t_pVideoFrame->linesize[0];
int t_uSize = t_pVideoFrame->linesize[1];
int t_vSize = t_pVideoFrame->linesize[2];

QByteArray t_willSendData;
t_willSendData.append((char*)pY,t_ySize * t_pVideoFrame->height);
t_willSendData.append((char*)pU,t_ySize * t_pVideoFrame->height / 4);
t_willSendData.append((char*)pV,t_ySize * t_pVideoFrame->height / 4);

QByteArray中保存的所有的数据。

上面的方法有问题:
下面是改进的方法:

void saveAVFrame_YUV_ToTempFile(AVFrame *pFrame)
{
    int t_frameWidth = pFrame->width;
    int t_frameHeight = pFrame->height;
    int t_yPerRowBytes = pFrame->linesize[0];
    int t_uPerRowBytes = pFrame->linesize[1];
    int t_vPerRowBytes = pFrame->linesize[2];
    qDebug()<<"robin:saveAVFrame_YUV_ToTempFile info:"<<t_frameWidth<<t_frameHeight<<"||"<<t_yPerRowBytes<<t_uPerRowBytes<<t_vPerRowBytes;
    QFile t_file("E:\\receive_Frame.yuv");
    t_file.open(QIODevice::WriteOnly);
    //t_file.write((char *)pFrame->data[0],t_frameWidth * t_frameHeight);
    //t_file.write((char *)pFrame->data[1],(t_frameWidth/2) * t_frameHeight / 2);
    //t_file.write((char *)pFrame->data[2],(t_frameWidth/2) * t_frameHeight / 2);

    for(int i = 0;i< t_frameHeight ;i++)
    {
        t_file.write((char*)(pFrame->data[0]+i*t_yPerRowBytes),t_frameWidth);
    }

    for(int i = 0;i< t_frameHeight/2 ;i++)
    {
        t_file.write((char*)(pFrame->data[1]+i*t_uPerRowBytes),t_frameWidth/2);
    }

    for(int i = 0;i< t_frameHeight/2 ;i++)
    {
        t_file.write((char*)(pFrame->data[2]+i*t_vPerRowBytes),t_frameWidth/2);
    }

    t_file.flush();

}

总结

主要介绍了图片格式YUV420p的基础知识和YUV420p在AVFrame中的保存。


  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值