mp4视频文件截图--h264解码成yuv再转存为bmp图片

得到yuv序列

从一个mp4文件中取出一张截图,这个直接用ffmpeg命令行就可以完成,这里想分析下原理,大致的流程原理如标题,将mp4文件解复用得到一个视频流,这里就以h264编码的文件为例,当然实际的视频流不一定是h264,可能是mpeg4 、hevc(h265)、vp8 vp9 av1等等。 和一个音频流,aac mp3等。  现在这里只讨论视频流, 将这个h264视频流解码,解码为yuv, 然后将yuv序列中指定的一帧图片内容转换为rgb,再存储为bmp位图。

前面的步骤,解复用,解码,直接用ffmpeg来完成了:

#ffmpeg -i tfdf.mp4 -ss 00:00:10 -t 10 -pix_fmt yuyv422 672x378_yuyv422.yuv
-i tfdf.mp4  输入文件
-ss 00:00:10 从文件的第00时00分10秒开始
-t 取10s时间的长度
-pix_fmt yuyv422  输出格式yuyv422
672x378_yuyv422.yuv 输出文件名

得到yuyv422文件序列,也可以输出yuv420格式

#ffmpeg -i tfdf.mp4 -ss 00:00:10 -t 10 -pix_fmt yuv420p 672x378_yuv420p.yuv

具体哪些输出格式,ffmpeg 源码 /libavutil/pixdesc.c 中结构体av_pix_fmt_descriptors中可以查看
mp4视频文件截图--h264解码成yuv再转存为bmp图片_第1张图片
这里来分析下yuv420, yuv421, yuv422, yuyv422的存储方式:
Y 表示亮度信号,即灰阶值。
UV  色度 和 饱和度。两者一般一起成对表示。也有的叫YCrCb,统一都可叫做 YUV . 
这里是基于模拟信号来表示其含义的,所谓420,即N1:N2:N3 ,
 N1表示Y奇数行加上偶数行Y样本的个数。 为4
 N2 表示奇数行 Cr加上Cb样本的个数(这两个成对,才有意义),为2
 N3 表示偶数行 Cr加上Cb样本的个数 为0.
所以就是YUV420。 每四个像素,共用一个uv分量。而Y分量是一个像素对应一个,所以这个420相比于422来说,利用的是人眼对于亮度的敏感度比对色度的敏感度要高的原理,适当减小色度的信息量,但是人的视觉对色度信息被阉割之后的视频看起来差别不大,这样可以达到节省带宽和存储空间的目的。
那么yuv420 和yuyv422? 名称上也可以看出来,前者是把一帧画面的y分量先连续存储在一起,然后在分别把u分量和v分量存储,一帧图像分成了三个部分存储,如果我们从文件顺序读取显示的话,看到的应该是先从上到下刷出一个黑白图(y), 然后再加上颜色,要把它转换为RGB, 也是应该读完整个图之后在转换。这种存储方式称为 planar 平面存储。 即yuv420p
yuyv422, 就是交叉的方式存储,Y0 Cb Y1 Cr , Y U Y V ,也称为packed打包格式。
这两种存储方式,很明显交叉存储的方式它的容错能力是要强一些的,即使读到半帧数据,也是可以显示半帧的,但是yuv平面存储的方式是要读完整帧,比如下面的 672x378 yuyv422格式的元数据,播放的时候把它的高度故意设置错误,图像也是可以分辨出来的:
 而yuv420p的数据,如果高度设置错误,yuv 分量就都错位了,整个播放的一片浑浊。

取出一个yuv帧来,转为rgb,然后存储为BMP格式图片。
得到yuv帧: 1.0 上述的从视频文件中取yuv序列。
                   2.0 从 bmp/jpg/png 图片中取 

ffmpeg -i jianbian.jpg  -pix_fmt yuv420p jianbian.yuv
ffmpeg -i jianbian.bmp  -pix_fmt yuv420p jianbian.yuv
ffmpeg -i jianbian.bmp  -pix_fmt rgb24 jianbian.rgb

将yuv420p转为rgb:


1.0 利用ffmpeg  (ffmpeg  /libswscale/yuv2rgb.c 源码对应)

ffmpeg -s 3840x2160 -pix_fmt yuv420p -i jianbian.yuv  -pix_fmt rgb24 jianbia_yuv2rgb.rgb

2.0 这里从android7 的源码中,有关于 yuv2rgb的功能,在 https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/av/media/libstagefright/yuv/YUVImage.cpp

void YUVImage::yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue,
        uint8_t *r, uint8_t *g, uint8_t *b) const {
    int rTmp = yValue + (1.370705 * (vValue-128));
    int gTmp = yValue - (0.698001 * (vValue-128)) - (0.337633 * (uValue-128));
    int bTmp = yValue + (1.732446 * (uValue-128));

    *r = clamp(rTmp, 0, 255);
    *g = clamp(gTmp, 0, 255);
    *b = clamp(bTmp, 0, 255);
}

这里借一张图,说明一下 yuv转换公式中,uv的对应值关系,yuv420p中因为 uv的个数是不够的,所以是每四个y公用一组uv,当然不是在一行的连续4个像素的y值,而是如下4x4块状,在实际图中相邻的四个像素公用一组uv
mp4视频文件截图--h264解码成yuv再转存为bmp图片_第2张图片
这里有个疑惑,如果是把原本rgb24的转换成yuv420,  原理上讲应该是有丢失信息的,在把它从yuv420转换回来rgb24, 应该是有所差别的吧。个人按照网上找的给类yuv转rgb的公式,也写了一个demo(方便对照原理看的一个代码):

#include
#include
unsigned char clamp(unsigned char v, unsigned char min, unsigned char max)
{
	if(max<=min)
	{
		printf("erro");
		return 0;
	}
	if(v>max) return max;
	else if(v

在虚拟机 ubuntu上转换得到的图像某些像素就有问题:左为利用ffmpeg命令将yuv转rgb24的输出,右为上述公式计算得到的输出。?????究竟是浮点计算的误差?还是另有蹊跷?
mp4视频文件截图--h264解码成yuv再转存为bmp图片_第3张图片

将rgb24存储为bmp格式


这里对bmp格式不做太多的解析,对于rgb24,即每一个像素点用三个字节 分别存储 r g b三个值,就是24bit,颜色深度为24bit, 简单的存储方式,就是存储一个bmp文件格式的头,然后存储 rgb raw数据就可以了,(对于不是24bit的存储方式还有调色板什么的,这里不去掺和了,简单起见。)
比如 我们在photoshop中创建一个4x4像素的图片,如下:
mp4视频文件截图--h264解码成yuv再转存为bmp图片_第4张图片
每一个像素给它一个不同的颜色值,存储为bmp位图的话,bmp文件源数据是这样的:
mp4视频文件截图--h264解码成yuv再转存为bmp图片_第5张图片
bmp文件格式,主要四部分组成:
文件头,信息头,调色板,数据。 这里rgb24的话,就不需要调色板了。
借博客一用,bitmap格式讲的很清楚:https://blog.csdn.net/u013066730/article/details/82625158

 

你可能感兴趣的:(mp4视频文件截图--h264解码成yuv再转存为bmp图片)