得到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中可以查看
這裏來分析下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
這裏有個疑惑,如果是把原本rgb24的轉換成yuv420, 原理上講應該是有丟失信息的,在把它從yuv420轉換回來rgb24, 應該是有所差別的吧。個人按照網上找的給類yuv轉rgb的公式,也寫了一個demo(方便對照原理看的一個代碼):
#include<stdio.h>
#include<stdlib.h>
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<min) return min;
else return v;
}
int main(int argc, const char*argv[])
{
#define WIDTH 3840
#define HEIGHT 2160
if(argc<2)
{
printf("usage:yuv420p_file\n");
return 0;
}
FILE *fp_in = fopen(argv[1],"r");
if(fp_in == NULL)
{
printf("fopen erro:%s\n",argv[1]);
return -1;
}
FILE *fp_out = fopen("out.rgb","w+");
if(fp_out == NULL)
{
printf("fopen out.rgb erro");
fclose(fp_in);
return -1;
}
unsigned char *buffer_in = malloc((WIDTH*HEIGHT*1.5));
unsigned char *buffer_out = malloc(WIDTH*HEIGHT*3);
unsigned char y[4]={0};
unsigned char u=0;
unsigned char v=0;
unsigned char r[4]={0};
unsigned char g[4]={0};
unsigned char b[4]={0};
fread(buffer_in,1,WIDTH*HEIGHT*1.5,fp_in);
int u_base_offset = WIDTH*HEIGHT;
int v_base_offset = u_base_offset+WIDTH*HEIGHT/4;
int i =0;
int j =0;
for(i=0;i<HEIGHT;i+=2)
{
for(j=0;j<WIDTH;j+=2)
{//每次處理 相鄰 4x4塊 4個像素
y[0]=buffer_in[i*WIDTH+j];
y[1]=buffer_in[i*WIDTH+j+1];
y[2]=buffer_in[(i+1)*WIDTH+j];
y[3]=buffer_in[(i+1)*WIDTH+j+1];
u=buffer_in[u_base_offset+j/2+i/2*WIDTH/2];
v=buffer_in[v_base_offset+j/2+i/2*WIDTH/2];
int k =0;
for(k=0;k<4;k++)
{
double rd=0,gd=0,bd=0;
// rd = 1.164*(y[k]-16)+1.596*(v-128);
// gd = 1.164*(y[k]-16)-0.813*(u-128) - 0.392*(v-128);
// bd = 1.164*(y[k]-16)+2.017*(u-128);
rd = y[k] + (1.370705*(v-128));
gd = y[k] - (0.698001*(v-128)) - (0.337633*(v-128));
bd = y[k] + (1.732446*(u-128));
r[k] = clamp((unsigned char)rd,0,255);
g[k] = clamp((unsigned char)gd,0,255);
b[k] = clamp((unsigned char)bd,0,255);
}
char *dest_pix_ptr[4] = {buffer_out+(i*WIDTH+j)*3, buffer_out+(i*WIDTH+j+1)*3, buffer_out+((i+1)*WIDTH+j)*3,buffer_out+((i+1)*WIDTH+j+1)*3};
//給4個像素點賦值
for(k=0;k<sizeof(dest_pix_ptr)/sizeof(dest_pix_ptr[0]);k++)
{
*(dest_pix_ptr[k]) = r[k];
*(dest_pix_ptr[k]+1) = g[k];
*(dest_pix_ptr[k]+2)= b[k];
}
}
}
fwrite(buffer_out,1,WIDTH*HEIGHT*3,fp_out);
free(buffer_in);
free(buffer_out);
fclose(fp_out);
fclose(fp_in);
}
在虛擬機 ubuntu上轉換得到的圖像某些像素就有問題:左爲利用ffmpeg命令將yuv轉rgb24的輸出,右爲上述公式計算得到的輸出。?????究竟是浮點計算的誤差?還是另有蹊蹺?
將rgb24存儲爲bmp格式
這裏對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