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中可以查看

這裏來分析下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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章