从一个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中可以查看
这里来分析下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
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
这里有个疑惑,如果是把原本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的输出,右为上述公式计算得到的输出。?????究竟是浮点计算的误差?还是另有蹊跷?
这里对bmp格式不做太多的解析,对于rgb24,即每一个像素点用三个字节 分别存储 r g b三个值,就是24bit,颜色深度为24bit, 简单的存储方式,就是存储一个bmp文件格式的头,然后存储 rgb raw数据就可以了,(对于不是24bit的存储方式还有调色板什么的,这里不去掺和了,简单起见。)
比如 我们在photoshop中创建一个4x4像素的图片,如下:
每一个像素给它一个不同的颜色值,存储为bmp位图的话,bmp文件源数据是这样的:
bmp文件格式,主要四部分组成:
文件头,信息头,调色板,数据。 这里rgb24的话,就不需要调色板了。
借博客一用,bitmap格式讲的很清楚:https://blog.csdn.net/u013066730/article/details/82625158