視音頻數(shù)據(jù)處理入門:RGB咐旧、YUV像素數(shù)據(jù)處理

   有段時間沒有寫博客了驶鹉,這兩天寫起博客來竟然感覺有些興奮,仿佛找回了原來的感覺休偶。

前一陣子在梳理以前文章的時候梁厉,發(fā)現(xiàn)自己雖然總結(jié)了各種視音頻應(yīng)用程序,卻還缺少一個適合無視音頻背景人員學(xué)習(xí)的“最基礎(chǔ)”的程序踏兜。因此抽時間將以前寫過的代碼整理成了一個小項目词顾。
這個小項目里面包含了一系列簡單的函數(shù),可以對RGB/YUV視頻像素數(shù)據(jù)碱妆、PCM音頻采樣數(shù)據(jù)肉盹、H.264視頻碼流、AAC音頻碼流疹尾、FLV封裝格式數(shù)據(jù)上忍、UDP/RTP協(xié)議數(shù)據(jù)進(jìn)行簡單處理骤肛。這個項目的一大特點就是沒有使用任何的第三方類庫,完全借助于C語言的基本函數(shù)實現(xiàn)了功能窍蓝。
通過對這些代碼的學(xué)習(xí)腋颠,可以讓初學(xué)者迅速掌握視音頻數(shù)據(jù)的基本格式。有關(guān)上述幾種格式的介紹可以參考文章《視音頻編解碼技術(shù)零基礎(chǔ)學(xué)習(xí)方法》吓笙。

從這篇文章開始打算寫6篇文章分別記錄上述6種不同類型的視音頻數(shù)據(jù)的處理方法淑玫。本文首先記錄第一部分即RGB/YUV視頻像素數(shù)據(jù)的處理方法。視頻像素數(shù)據(jù)在視頻播放器的解碼流程中的位置如下圖所示面睛。

20160118002918690.png

本文分別介紹如下幾個RGB/YUV視頻像素數(shù)據(jù)處理函數(shù):
分離YUV420P像素數(shù)據(jù)中的Y絮蒿、U、V分量
分離YUV444P像素數(shù)據(jù)中的Y叁鉴、U土涝、V分量
將YUV420P像素數(shù)據(jù)去掉顏色(變成灰度圖)
將YUV420P像素數(shù)據(jù)的亮度減半
將YUV420P像素數(shù)據(jù)的周圍加上邊框
生成YUV420P格式的灰階測試圖
計算兩個YUV420P像素數(shù)據(jù)的PSNR
分離RGB24像素數(shù)據(jù)中的R、G幌墓、B分量
將RGB24格式像素數(shù)據(jù)封裝為BMP圖像
將RGB24格式像素數(shù)據(jù)轉(zhuǎn)換為YUV420P格式像素數(shù)據(jù)
生成RGB24格式的彩條測試圖

本文中的RGB/YUV文件需要使用RGB/YUV播放器才能查看但壮。YUV播放器種類比較多,例如YUV Player Deluxe克锣,或者開源播放器(參考文章《修改了一個YUV/RGB播放器》)等茵肃。

函數(shù)列表

(1) 分離YUV420P像素數(shù)據(jù)中的Y、U袭祟、V分量

本程序中的函數(shù)可以將YUV420P數(shù)據(jù)中的Y、U捞附、V三個分量分離開來并保存成三個文件巾乳。函數(shù)的代碼如下所示。

  1. /**

    • Split Y, U, V planes in YUV420P file.
    • @param url Location of Input YUV file.
    • @param w Width of Input YUV file.
    • @param h Height of Input YUV file.
    • @param num Number of frames to process.
  2. */

  3. int simplest_yuv420_split(char *url, int w, int h,int num){

  4. FILE *fp=fopen(url,"rb+");

  5. FILE *fp1=fopen("output_420_y.y","wb+");

  6. FILE *fp2=fopen("output_420_u.y","wb+");

  7. FILE *fp3=fopen("output_420_v.y","wb+");

  8. unsigned char pic=(unsigned char )malloc(wh3/2);

  9. for(int i=0;i<num;i++){

  10. fread(pic,1,wh3/2,fp);

  11. //Y

  12. fwrite(pic,1,w*h,fp1);

  13. //U

  14. fwrite(pic+wh,1,wh/4,fp2);

  15. //V

  16. fwrite(pic+wh5/4,1,w*h/4,fp3);

  17. }

  18. free(pic);

  19. fclose(fp);

  20. fclose(fp1);

  21. fclose(fp2);

  22. fclose(fp3);

  23. return 0;

  24. }

調(diào)用上面函數(shù)的方法如下所示鸟召。

  1. simplest_yuv420_split("lena_256x256_yuv420p.yuv",256,256,1);

從代碼可以看出胆绊,如果視頻幀的寬和高分別為w和h,那么一幀YUV420P像素數(shù)據(jù)一共占用wh3/2 Byte的數(shù)據(jù)欧募。其中前wh Byte存儲Y压状,接著的wh1/4 Byte存儲U,最后wh*1/4 Byte存儲V跟继。上述調(diào)用函數(shù)的代碼運行后种冬,將會把一張分辨率為256x256的名稱為lena_256x256_yuv420p.yuv的YUV420P格式的像素數(shù)據(jù)文件分離成為三個文件:

output_420_y.y:純Y數(shù)據(jù),分辨率為256x256舔糖。

output_420_u.y:純U數(shù)據(jù)娱两,分辨率為128x128。
output_420_v.y:純V數(shù)據(jù)金吗,分辨率為128x128十兢。

注:本文中像素的采樣位數(shù)一律為8bit趣竣。由于1Byte=8bit,所以一個像素的一個分量的采樣值占用1Byte旱物。

程序輸入的原圖如下所示遥缕。


lena_256x256_yuv420p.png

程序輸出的三個文件的截圖如下圖所示。在這里需要注意輸出的U宵呛、V分量在YUV播放器中也是當(dāng)做Y分量進(jìn)行播放的单匣。

output_420_u.y.png
output_420_v.y.png
output_420_y.y.png

(2)分離YUV444P像素數(shù)據(jù)中的Y、U烤蜕、V分量

本程序中的函數(shù)可以將YUV444P數(shù)據(jù)中的Y封孙、U、V三個分量分離開來并保存成三個文件讽营。函數(shù)的代碼如下所示虎忌。

/**

  • Split Y, U, V planes in YUV444P file.
  • @param url Location of YUV file.
  • @param w Width of Input YUV file.
  • @param h Height of Input YUV file.
  • @param num Number of frames to process.

*/
int simplest_yuv444_split(char *url, int w, int h,int num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_444_y.y","wb+");
FILE *fp2=fopen("output_444_u.y","wb+");
FILE *fp3=fopen("output_444_v.y","wb+");
unsigned char pic=(unsigned char )malloc(wh3);

for(int i=0;i<num;i++){  
    fread(pic,1,w*h*3,fp);  
    //Y  
    fwrite(pic,1,w*h,fp1);  
    //U  
    fwrite(pic+w*h,1,w*h,fp2);  
    //V  
    fwrite(pic+w*h*2,1,w*h,fp3);  
}  

free(pic);  
fclose(fp);  
fclose(fp1);  
fclose(fp2);  
fclose(fp3);  

return 0;  

}

調(diào)用上面函數(shù)的方法如下所示。

simplest_yuv444_split("lena_256x256_yuv444p.yuv",256,256,1);

從代碼可以看出橱鹏,如果視頻幀的寬和高分別為w和h膜蠢,那么一幀YUV444P像素數(shù)據(jù)一共占用wh3 Byte的數(shù)據(jù)。其中前wh Byte存儲Y莉兰,接著的wh Byte存儲U挑围,最后w*h Byte存儲V。上述調(diào)用函數(shù)的代碼運行后糖荒,將會把一張分辨率為256x256的名稱為lena_256x256_yuv444p.yuv的YUV444P格式的像素數(shù)據(jù)文件分離成為三個文件:
output_444_y.y:純Y數(shù)據(jù)杉辙,分辨率為256x256。
output_444_u.y:純U數(shù)據(jù)捶朵,分辨率為256x256蜘矢。
output_444_v.y:純V數(shù)據(jù),分辨率為256x256综看。
輸入的原圖如下所示品腹。

20160117232650727.png

輸出的三個文件的截圖如下圖所示。


output_444_u.y.png
output_444_v.y.png
output_444_y.y.png

(3) 將YUV420P像素數(shù)據(jù)去掉顏色(變成灰度圖)

本程序中的函數(shù)可以將YUV420P格式像素數(shù)據(jù)的彩色去掉红碑,變成純粹的灰度圖舞吭。函數(shù)的代碼如下。

/**

  • Convert YUV420P file to gray picture

  • @param url Location of Input YUV file.

  • @param w Width of Input YUV file.

  • @param h Height of Input YUV file.

  • @param num Number of frames to process.
    */
    int simplest_yuv420_gray(char *url, int w, int h,int num){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_gray.yuv","wb+");
    unsigned char pic=(unsigned char )malloc(wh3/2);

    for(int i=0;i<num;i++){
    fread(pic,1,wh3/2,fp);
    //Gray
    memset(pic+wh,128,wh/2);
    fwrite(pic,1,wh3/2,fp1);
    }

    free(pic);
    fclose(fp);
    fclose(fp1);
    return 0;
    }

調(diào)用上面函數(shù)的方法如下所示析珊。
simplest_yuv420_gray("lena_256x256_yuv420p.yuv",256,256,1);

從代碼可以看出羡鸥,如果想把YUV格式像素數(shù)據(jù)變成灰度圖像,只需要將U唾琼、V分量設(shè)置成128即可兄春。這是因為U、V是圖像中的經(jīng)過偏置處理的色度分量锡溯。色度分量在偏置處理前的取值范圍是-128至127赶舆,這時候的無色對應(yīng)的是“0”值哑姚。經(jīng)過偏置后色度分量取值變成了0至255贸辈,因而此時的無色對應(yīng)的就是128了志鹃。上述調(diào)用函數(shù)的代碼運行后,將會把一張分辨率為256x256的名稱為lena_256x256_yuv420p.yuv的YUV420P格式的像素數(shù)據(jù)文件處理成名稱為output_gray.yuv的YUV420P格式的像素數(shù)據(jù)文件隘庄。輸入的原圖如下所示九串。


20160117232856722.png

處理后的圖像如下所示绞佩。


20160117232913602.png

(4)將YUV420P像素數(shù)據(jù)的亮度減半

本程序中的函數(shù)可以通過將YUV數(shù)據(jù)中的亮度分量Y的數(shù)值減半的方法,降低圖像的亮度猪钮。函數(shù)代碼如下所示品山。

/**

  • Halve Y value of YUV420P file

  • @param url Location of Input YUV file.

  • @param w Width of Input YUV file.

  • @param h Height of Input YUV file.

  • @param num Number of frames to process.
    */
    int simplest_yuv420_halfy(char *url, int w, int h,int num){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_half.yuv","wb+");

    unsigned char pic=(unsigned char )malloc(wh3/2);

    for(int i=0;i<num;i++){
    fread(pic,1,wh3/2,fp);
    //Half
    for(int j=0;j<wh;j++){
    unsigned char temp=pic[j]/2;
    //printf("%d,\n",temp);
    pic[j]=temp;
    }
    fwrite(pic,1,w
    h*3/2,fp1);
    }

    free(pic);
    fclose(fp);
    fclose(fp1);

    return 0;
    }

調(diào)用上面函數(shù)的方法如下所示。
simplest_yuv420_halfy("lena_256x256_yuv420p.yuv",256,256,1);

從代碼可以看出烤低,如果打算將圖像的亮度減半肘交,只要將圖像的每個像素的Y值取出來分別進(jìn)行除以2的工作就可以了。圖像的每個Y值占用1 Byte扑馁,取值范圍是0至255涯呻,對應(yīng)C語言中的unsigned char數(shù)據(jù)類型。上述調(diào)用函數(shù)的代碼運行后腻要,將會把一張分辨率為256x256的名稱為lena_256x256_yuv420p.yuv的YUV420P格式的像素數(shù)據(jù)文件處理成名稱為output_half.yuv的YUV420P格式的像素數(shù)據(jù)文件复罐。輸入的原圖如下所示。

20160117233038584.png

處理后的圖像如下所示雄家。


20160117233049307.png

(5)將YUV420P像素數(shù)據(jù)的周圍加上邊框

本程序中的函數(shù)可以通過修改YUV數(shù)據(jù)中特定位置的亮度分量Y的數(shù)值效诅,給圖像添加一個“邊框”的效果。函數(shù)代碼如下所示趟济。
/**

  • Add border for YUV420P file

  • @param url Location of Input YUV file.

  • @param w Width of Input YUV file.

  • @param h Height of Input YUV file.

  • @param border Width of Border.

  • @param num Number of frames to process.
    */
    int simplest_yuv420_border(char *url, int w, int h,int border,int num){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_border.yuv","wb+");

    unsigned char pic=(unsigned char )malloc(wh3/2);

    for(int i=0;i<num;i++){
    fread(pic,1,wh3/2,fp);
    //Y
    for(int j=0;j<h;j++){
    for(int k=0;k<w;k++){
    if(k<border||k>(w-border)||j<border||j>(h-border)){
    pic[jw+k]=255;
    //pic[j
    w+k]=0;
    }
    }
    }
    fwrite(pic,1,wh3/2,fp1);
    }

    free(pic);
    fclose(fp);
    fclose(fp1);

    return 0;
    }

調(diào)用上面函數(shù)的方法如下所示填帽。

  1. simplest_yuv420_border("lena_256x256_yuv420p.yuv",256,256,20,1);

從代碼可以看出,圖像的邊框的寬度為border咙好,本程序?qū)⒕嚯x圖像邊緣border范圍內(nèi)的像素的亮度分量Y的取值設(shè)置成了亮度最大值255。上述調(diào)用函數(shù)的代碼運行后褐荷,將會把一張分辨率為256x256的名稱為lena_256x256_yuv420p.yuv的YUV420P格式的像素數(shù)據(jù)文件處理成名稱為output_border.yuv的YUV420P格式的像素數(shù)據(jù)文件勾效。輸入的原圖如下所示。

20160117233158395.png

處理后的圖像如下所示叛甫。


20160117233215510.png

(6) 生成YUV420P格式的灰階測試圖

本程序中的函數(shù)可以生成一張YUV420P格式的灰階測試圖层宫。函數(shù)代碼如下所示。
/**

  • Generate YUV420P gray scale bar.

  • @param width Width of Output YUV file.

  • @param height Height of Output YUV file.

  • @param ymin Max value of Y

  • @param ymax Min value of Y

  • @param barnum Number of bars

  • @param url_out Location of Output YUV file.
    */
    int simplest_yuv420_graybar(int width, int height,int ymin,int ymax,int barnum,char *url_out){
    int barwidth;
    float lum_inc;
    unsigned char lum_temp;
    int uv_width,uv_height;
    FILE *fp=NULL;
    unsigned char *data_y=NULL;
    unsigned char *data_u=NULL;
    unsigned char *data_v=NULL;
    int t=0,i=0,j=0;

    barwidth=width/barnum;
    lum_inc=((float)(ymax-ymin))/((float)(barnum-1));
    uv_width=width/2;
    uv_height=height/2;

    data_y=(unsigned char )malloc(widthheight);
    data_u=(unsigned char )malloc(uv_widthuv_height);
    data_v=(unsigned char )malloc(uv_widthuv_height);

    if((fp=fopen(url_out,"wb+"))==NULL){
    printf("Error: Cannot create file!");
    return -1;
    }

    //Output Info
    printf("Y, U, V value from picture's left to right:\n");
    for(t=0;t<(width/barwidth);t++){
    lum_temp=ymin+(char)(tlum_inc);
    printf("%3d, 128, 128\n",lum_temp);
    }
    //Gen Data
    for(j=0;j<height;j++){
    for(i=0;i<width;i++){
    t=i/barwidth;
    lum_temp=ymin+(char)(t
    lum_inc);
    data_y[jwidth+i]=lum_temp;
    }
    }
    for(j=0;j<uv_height;j++){
    for(i=0;i<uv_width;i++){
    data_u[j
    uv_width+i]=128;
    }
    }
    for(j=0;j<uv_height;j++){
    for(i=0;i<uv_width;i++){
    data_v[juv_width+i]=128;
    }
    }
    fwrite(data_y,width
    height,1,fp);
    fwrite(data_u,uv_widthuv_height,1,fp);
    fwrite(data_v,uv_width
    uv_height,1,fp);
    fclose(fp);
    free(data_y);
    free(data_u);
    free(data_v);
    return 0;
    }

調(diào)用上面函數(shù)的方法如下所示其监。
simplest_yuv420_graybar(640, 360,0,255,10,"graybar_640x360.yuv");

從源代碼可以看出萌腿,本程序一方面通過灰階測試圖的亮度最小值ymin,亮度最大值ymax抖苦,灰階數(shù)量barnum確定每一個灰度條中像素的亮度分量Y的取值毁菱。另一方面還要根據(jù)圖像的寬度width和圖像的高度height以及灰階數(shù)量barnum確定每一個灰度條的寬度米死。有了這兩方面信息之后,就可以生成相應(yīng)的圖片了贮庞。上述調(diào)用函數(shù)的代碼運行后峦筒,會生成一個取值范圍從0-255,一共包含10個灰度條的YUV420P格式的測試圖窗慎。測試圖的內(nèi)容如下所示物喷。


20160117233318275.png
image.png

(7)計算兩個YUV420P像素數(shù)據(jù)的PSNR

PSNR是最基本的視頻質(zhì)量評價方法。本程序中的函數(shù)可以對比兩張YUV圖片中亮度分量Y的PSNR遮斥。函數(shù)的代碼如下所示峦失。

/**

  • Calculate PSNR between 2 YUV420P file

  • @param url1 Location of first Input YUV file.

  • @param url2 Location of another Input YUV file.

  • @param w Width of Input YUV file.

  • @param h Height of Input YUV file.

  • @param num Number of frames to process.
    */
    int simplest_yuv420_psnr(char *url1,char *url2,int w,int h,int num){
    FILE *fp1=fopen(url1,"rb+");
    FILE *fp2=fopen(url2,"rb+");
    unsigned char *pic1=(unsigned char )malloc(wh);
    unsigned char *pic2=(unsigned char )malloc(wh);

    for(int i=0;i<num;i++){
    fread(pic1,1,wh,fp1);
    fread(pic2,1,w
    h,fp2);

     double mse_sum=0,mse=0,psnr=0;  
     for(int j=0;j<w*h;j++){  
         mse_sum+=pow((double)(pic1[j]-pic2[j]),2);  
     }  
     mse=mse_sum/(w*h);  
     psnr=10*log10(255.0*255.0/mse);  
     printf("%5.3f\n",psnr);  
    
     fseek(fp1,w*h/2,SEEK_CUR);  
     fseek(fp2,w*h/2,SEEK_CUR);  
    

    }

    free(pic1);
    free(pic2);
    fclose(fp1);
    fclose(fp2);
    return 0;
    }

調(diào)用上面函數(shù)的方法如下所示。

  1. simplest_yuv420_psnr("lena_256x256_yuv420p.yuv","lena_distort_256x256_yuv420p.yuv",256,256,1);

對于8bit量化的像素數(shù)據(jù)來說术吗,PSNR的計算公式如下所示尉辑。

image

上述公式中mse的計算公式如下所示。

image

其中M藐翎,N分別為圖像的寬高材蹬,xij和yij分別為兩張圖像的每一個像素值。PSNR通常用于質(zhì)量評價吝镣,就是計算受損圖像與原始圖像之間的差別堤器,以此來評價受損圖像的質(zhì)量。本程序輸入的兩張圖像的對比圖如下圖所示末贾。其中左邊的圖像為原始圖像闸溃,右邊的圖像為受損圖像。

image

經(jīng)過程序計算后得到的PSNR取值為26.693拱撵。PSNR取值通常情況下都在20-50的范圍內(nèi)辉川,取值越高,代表兩張圖像越接近拴测,反映出受損圖像質(zhì)量越好乓旗。

(8) 分離RGB24像素數(shù)據(jù)中的R、G集索、B分量

本程序中的函數(shù)可以將RGB24數(shù)據(jù)中的R屿愚、G、B三個分量分離開來并保存成三個文件务荆。函數(shù)的代碼如下所示妆距。

/**

  • Split R, G, B planes in RGB24 file.
  • @param url Location of Input RGB file.
  • @param w Width of Input RGB file.
  • @param h Height of Input RGB file.
  • @param num Number of frames to process.

*/
int simplest_rgb24_split(char *url, int w, int h,int num){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen("output_r.y","wb+");
FILE *fp2=fopen("output_g.y","wb+");
FILE *fp3=fopen("output_b.y","wb+");

unsigned char *pic=(unsigned char *)malloc(w*h*3);  

for(int i=0;i<num;i++){  

    fread(pic,1,w*h*3,fp);  

    for(int j=0;j<w*h*3;j=j+3){  
        //R  
        fwrite(pic+j,1,1,fp1);  
        //G  
        fwrite(pic+j+1,1,1,fp2);  
        //B  
        fwrite(pic+j+2,1,1,fp3);  
    }  
}  

free(pic);  
fclose(fp);  
fclose(fp1);  
fclose(fp2);  
fclose(fp3);  

return 0;  

}

調(diào)用上面函數(shù)的方法如下所示。
simplest_rgb24_split("cie1931_500x500.rgb", 500, 500,1);

從代碼可以看出函匕,與YUV420P三個分量分開存儲不同娱据,RGB24格式的每個像素的三個分量是連續(xù)存儲的。一幀寬高分別為w盅惜、h的RGB24圖像一共占用wh3 Byte的存儲空間中剩。RGB24格式規(guī)定首先存儲第一個像素的R忌穿、G、B咽安,然后存儲第二個像素的R伴网、G、B…以此類推妆棒。類似于YUV420P的存儲方式稱為Planar方式澡腾,而類似于RGB24的存儲方式稱為Packed方式。上述調(diào)用函數(shù)的代碼運行后糕珊,將會把一張分辨率為500x500的名稱為cie1931_500x500.rgb的RGB24格式的像素數(shù)據(jù)文件分離成為三個文件:

output_r.y:R數(shù)據(jù)动分,分辨率為256x256。
output_g.y:G數(shù)據(jù)红选,分辨率為256x256澜公。
output_b.y:B數(shù)據(jù),分辨率為256x256喇肋。

輸入的原圖是一張標(biāo)準(zhǔn)的CIE 1931色度圖坟乾。該色度圖右下為紅色,上方為綠色蝶防,左下為藍(lán)色甚侣,如下所示。

image

R數(shù)據(jù)圖像如下所示间学。

image

G數(shù)據(jù)圖像如下所示殷费。

image

B數(shù)據(jù)圖像如下所示。

image

(9)將RGB24格式像素數(shù)據(jù)封裝為BMP圖像

BMP圖像內(nèi)部實際上存儲的就是RGB數(shù)據(jù)低葫。本程序?qū)崿F(xiàn)了對RGB像素數(shù)據(jù)的封裝處理详羡。通過本程序中的函數(shù),可以將RGB數(shù)據(jù)封裝成為一張BMP圖像嘿悬。

/**

  • Convert RGB24 file to BMP file

  • @param rgb24path Location of input RGB file.

  • @param width Width of input RGB file.

  • @param height Height of input RGB file.

  • @param url_out Location of Output BMP file.
    */
    int simplest_rgb24_to_bmp(const char *rgb24path,int width,int height,const char *bmppath){
    typedef struct
    {
    long imageSize;
    long blank;
    long startPosition;
    }BmpHead;

    typedef struct
    {
    long Length;
    long width;
    long height;
    unsigned short colorPlane;
    unsigned short bitColor;
    long zipFormat;
    long realSize;
    long xPels;
    long yPels;
    long colorUse;
    long colorImportant;
    }InfoHead;

    int i=0,j=0;
    BmpHead m_BMPHeader={0};
    InfoHead m_BMPInfoHeader={0};
    char bfType[2]={'B','M'};
    int header_size=sizeof(bfType)+sizeof(BmpHead)+sizeof(InfoHead);
    unsigned char *rgb24_buffer=NULL;
    FILE fp_rgb24=NULL,fp_bmp=NULL;

    if((fp_rgb24=fopen(rgb24path,"rb"))==NULL){
    printf("Error: Cannot open input RGB24 file.\n");
    return -1;
    }
    if((fp_bmp=fopen(bmppath,"wb"))==NULL){
    printf("Error: Cannot open output BMP file.\n");
    return -1;
    }

    rgb24_buffer=(unsigned char )malloc(widthheight3);
    fread(rgb24_buffer,1,width
    height*3,fp_rgb24);

    m_BMPHeader.imageSize=3widthheight+header_size;
    m_BMPHeader.startPosition=header_size;

    m_BMPInfoHeader.Length=sizeof(InfoHead);
    m_BMPInfoHeader.width=width;
    //BMP storage pixel data in opposite direction of Y-axis (from bottom to top).
    m_BMPInfoHeader.height=-height;
    m_BMPInfoHeader.colorPlane=1;
    m_BMPInfoHeader.bitColor=24;
    m_BMPInfoHeader.realSize=3widthheight;

    fwrite(bfType,1,sizeof(bfType),fp_bmp);
    fwrite(&m_BMPHeader,1,sizeof(m_BMPHeader),fp_bmp);
    fwrite(&m_BMPInfoHeader,1,sizeof(m_BMPInfoHeader),fp_bmp);

    //BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2
    //It saves pixel data in Little Endian
    //So we change 'R' and 'B'
    for(j =0;j<height;j++){
    for(i=0;i<width;i++){
    char temp=rgb24_buffer[(jwidth+i)3+2];
    rgb24_buffer[(jwidth+i)3+2]=rgb24_buffer[(jwidth+i)3+0];
    rgb24_buffer[(jwidth+i)3+0]=temp;
    }
    }
    fwrite(rgb24_buffer,3widthheight,1,fp_bmp);
    fclose(fp_rgb24);
    fclose(fp_bmp);
    free(rgb24_buffer);
    printf("Finish generate %s!\n",bmppath);
    return 0;
    return 0;
    }
    調(diào)用上面函數(shù)的方法如下所示实柠。

simplest_rgb24_to_bmp("lena_256x256_rgb24.rgb",256,256,"output_lena.bmp");

通過代碼可以看出,該程序完成了主要完成了兩個工作:
1)將RGB數(shù)據(jù)前面加上文件頭善涨。
2)將RGB數(shù)據(jù)中每個像素的“B”和“R”的位置互換主到。
BMP文件是由BITMAPFILEHEADER、BITMAPINFOHEADER躯概、RGB像素數(shù)據(jù)共3個部分構(gòu)成,它的結(jié)構(gòu)如下圖所示畔师。

BITMAPFILEHEADER
BITMAPINFOHEADER
RGB像素數(shù)據(jù)

其中前兩部分的結(jié)構(gòu)如下所示娶靡。在寫入BMP文件頭的時候給其中的每個字段賦上合適的值就可以了。
typedef struct tagBITMAPFILEHEADER
{
unsigned short int bfType; //位圖文件的類型看锉,必須為BM
unsigned long bfSize; //文件大小姿锭,以字節(jié)為單位
unsigned short int bfReserverd1; //位圖文件保留字塔鳍,必須為0
unsigned short int bfReserverd2; //位圖文件保留字,必須為0
unsigned long bfbfOffBits; //位圖文件頭到數(shù)據(jù)的偏移量呻此,以字節(jié)為單位
}BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER
{
long biSize; //該結(jié)構(gòu)大小轮纫,字節(jié)為單位
long biWidth; //圖形寬度以象素為單位
long biHeight; //圖形高度以象素為單位
short int biPlanes; //目標(biāo)設(shè)備的級別,必須為1
short int biBitcount; //顏色深度焚鲜,每個象素所需要的位數(shù)
short int biCompression; //位圖的壓縮類型
long biSizeImage; //位圖的大小掌唾,以字節(jié)為單位
long biXPelsPermeter; //位圖水平分辨率,每米像素數(shù)
long biYPelsPermeter; //位圖垂直分辨率忿磅,每米像素數(shù)
long biClrUsed; //位圖實際使用的顏色表中的顏色數(shù)
long biClrImportant; //位圖顯示過程中重要的顏色數(shù)
}BITMAPINFOHEADER;

BMP采用的是小端(Little Endian)存儲方式糯彬。這種存儲方式中“RGB24”格式的像素的分量存儲的先后順序為B、G葱她、R撩扒。由于RGB24格式存儲的順序是R、G吨些、B搓谆,所以需要將“R”和“B”順序作一個調(diào)換再進(jìn)行存儲。

下圖為輸入的RGB24格式的圖像lena_256x256_rgb24.rgb豪墅。

image

下圖分封裝為BMP格式后的圖像output_lena.bmp泉手。封裝后的圖像使用普通的看圖軟件就可以查看。

image

(10)將RGB24格式像素數(shù)據(jù)轉(zhuǎn)換為YUV420P格式像素數(shù)據(jù)

本程序中的函數(shù)可以將RGB24格式的像素數(shù)據(jù)轉(zhuǎn)換為YUV420P格式的像素數(shù)據(jù)但校。函數(shù)的代碼如下所示螃诅。

unsigned char clip_value(unsigned char x,unsigned char min_val,unsigned char max_val){
if(x>max_val){
return max_val;
}else if(x<min_val){
return min_val;
}else{
return x;
}
}

//RGB to YUV420
bool RGB24_TO_YUV420(unsigned char RgbBuf,int w,int h,unsigned char yuvBuf)
{
unsigned char
ptrY, ptrU, ptrV, ptrRGB;
memset(yuvBuf,0,w
h
3/2);
ptrY = yuvBuf;
ptrU = yuvBuf + w
h;
ptrV = ptrU + (w
h1/4);
unsigned char y, u, v, r, g, b;
for (int j = 0; j<h;j++){
ptrRGB = RgbBuf + w
j*3 ;
for (int i = 0;i<w;i++){

        r = *(ptrRGB++);  
        g = *(ptrRGB++);  
        b = *(ptrRGB++);  
        y = (unsigned char)( ( 66 * r + 129 * g +  25 * b + 128) >> 8) + 16  ;            
        u = (unsigned char)( ( -38 * r -  74 * g + 112 * b + 128) >> 8) + 128 ;            
        v = (unsigned char)( ( 112 * r -  94 * g -  18 * b + 128) >> 8) + 128 ;  
        *(ptrY++) = clip_value(y,0,255);  
        if (j%2==0&&i%2 ==0){  
            *(ptrU++) =clip_value(u,0,255);  
        }  
        else{  
            if (i%2==0){  
            *(ptrV++) =clip_value(v,0,255);  
            }  
        }  
    }  
}  
return true;  

}

/**

  • Convert RGB24 file to YUV420P file

  • @param url_in Location of Input RGB file.

  • @param w Width of Input RGB file.

  • @param h Height of Input RGB file.

  • @param num Number of frames to process.

  • @param url_out Location of Output YUV file.
    */
    int simplest_rgb24_to_yuv420(char *url_in, int w, int h,int num,char *url_out){
    FILE *fp=fopen(url_in,"rb+");
    FILE *fp1=fopen(url_out,"wb+");

    unsigned char pic_rgb24=(unsigned char )malloc(wh3);
    unsigned char pic_yuv420=(unsigned char )malloc(wh3/2);

    for(int i=0;i<num;i++){
    fread(pic_rgb24,1,wh3,fp);
    RGB24_TO_YUV420(pic_rgb24,w,h,pic_yuv420);
    fwrite(pic_yuv420,1,wh3/2,fp1);
    }

    free(pic_rgb24);
    free(pic_yuv420);
    fclose(fp);
    fclose(fp1);

    return 0;
    }

調(diào)用上面函數(shù)的方法如下所示。

simplest_rgb24_to_yuv420("lena_256x256_rgb24.rgb",256,256,1,"output_lena.yuv");

從源代碼可以看出状囱,本程序?qū)崿F(xiàn)了RGB到Y(jié)UV的轉(zhuǎn)換公式:

Y= 0.299R+0.587G+0.114B*

U=-0.147R-0.289G+0.463B*

V= 0.615R-0.515G-0.100B*

在轉(zhuǎn)換的過程中有以下幾點需要注意:

  1. RGB24存儲方式是Packed术裸,YUV420P存儲方式是Packed。
  2. U亭枷,V在水平和垂直方向的取樣數(shù)是Y的一半

轉(zhuǎn)換前的RGB24格式像素數(shù)據(jù)lena_256x256_rgb24.rgb的內(nèi)容如下所示袭艺。

image

轉(zhuǎn)換后的YUV420P格式的像素數(shù)據(jù)output_lena.yuv的內(nèi)容如下所示。

image

(11)生成RGB24格式的彩條測試圖

本程序中的函數(shù)可以生成一張RGB24格式的彩條測試圖叨粘。函數(shù)代碼如下所示猾编。

/**

  • Generate RGB24 colorbar.

  • @param width Width of Output RGB file.

  • @param height Height of Output RGB file.

  • @param url_out Location of Output RGB file.
    */
    int simplest_rgb24_colorbar(int width, int height,char *url_out){

    unsigned char *data=NULL;
    int barwidth;
    char filename[100]={0};
    FILE *fp=NULL;
    int i=0,j=0;

    data=(unsigned char )malloc(widthheight*3);
    barwidth=width/8;

    if((fp=fopen(url_out,"wb+"))==NULL){
    printf("Error: Cannot create file!");
    return -1;
    }

    for(j=0;j<height;j++){
    for(i=0;i<width;i++){
    int barnum=i/barwidth;
    switch(barnum){
    case 0:{
    data[(jwidth+i)3+0]=255;
    data[(jwidth+i)3+1]=255;
    data[(jwidth+i)3+2]=255;
    break;
    }
    case 1:{
    data[(jwidth+i)3+0]=255;
    data[(jwidth+i)3+1]=255;
    data[(jwidth+i)3+2]=0;
    break;
    }
    case 2:{
    data[(jwidth+i)3+0]=0;
    data[(jwidth+i)3+1]=255;
    data[(jwidth+i)3+2]=255;
    break;
    }
    case 3:{
    data[(jwidth+i)3+0]=0;
    data[(jwidth+i)3+1]=255;
    data[(jwidth+i)3+2]=0;
    break;
    }
    case 4:{
    data[(jwidth+i)3+0]=255;
    data[(jwidth+i)3+1]=0;
    data[(jwidth+i)3+2]=255;
    break;
    }
    case 5:{
    data[(jwidth+i)3+0]=255;
    data[(jwidth+i)3+1]=0;
    data[(jwidth+i)3+2]=0;
    break;
    }
    case 6:{
    data[(jwidth+i)3+0]=0;
    data[(jwidth+i)3+1]=0;
    data[(jwidth+i)3+2]=255;

             break;  
                }  
         case 7:{  
             data[(j*width+i)*3+0]=0;  
             data[(j*width+i)*3+1]=0;  
             data[(j*width+i)*3+2]=0;  
             break;  
                }  
         }  
    
     }  
    

    }
    fwrite(data,widthheight3,1,fp);
    fclose(fp);
    free(data);

    return 0;
    }

調(diào)用上面函數(shù)的方法如下所示。
simplest_rgb24_colorbar(640, 360,"colorbar_640x360.rgb");

從源代碼可以看出升敲,本程序循環(huán)輸出“白黃青綠品紅藍(lán)黑”8種顏色的彩條答倡。這8種顏色的彩條的R、G驴党、B取值如下所示瘪撇。

|

(255, 255, 255)

|

(255, 255, 0)

|

( 0, 255, 255)

|

( 0, 255, 0)

|

(255, 0, 255)

|

(255, 0, 0)

藍(lán)

|

( 0, 0, 255)

|

( 0, 0, 0)

|

生成的圖像截圖如下所示。

image

下載

Simplest mediadata test

項目主頁

SourceForge:https://sourceforge.net/projects/simplest-mediadata-test/

Github:https://github.com/leixiaohua1020/simplest_mediadata_test

開源中國:http://git.oschina.net/leixiaohua1020/simplest_mediadata_test
CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/9422409

本項目包含如下幾種視音頻數(shù)據(jù)解析示例:
(1)像素數(shù)據(jù)處理程序。包含RGB和YUV像素格式處理的函數(shù)倔既。
(2)音頻采樣數(shù)據(jù)處理程序恕曲。包含PCM音頻采樣格式處理的函數(shù)。
(3)H.264碼流分析程序渤涌∨逡ィ可以分離并解析NALU。
(4)AAC碼流分析程序实蓬∪准螅可以分離并解析ADTS幀。
(5)FLV封裝格式分析程序瞳秽“曷模可以將FLV中的MP3音頻碼流分離出來。

(6)UDP-RTP協(xié)議分析程序练俐⌒溆可以將分析UDP/RTP/MPEG-TS數(shù)據(jù)包。

雷霄驊 (Lei Xiaohua)
leixiaohua1020@126.com
http://blog.csdn.net/leixiaohua1020

尊重雷神腺晾,原版還是雷神的燕锥,我只是來學(xué)習(xí)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悯蝉,一起剝皮案震驚了整個濱河市归形,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鼻由,老刑警劉巖暇榴,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蕉世,居然都是意外死亡蔼紧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門狠轻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奸例,“玉大人,你說我怎么就攤上這事向楼〔榈酰” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵湖蜕,是天一觀的道長逻卖。 經(jīng)常有香客問我,道長昭抒,這世上最難降的妖魔是什么箭阶? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任虚茶,我火速辦了婚禮,結(jié)果婚禮上仇参,老公的妹妹穿的比我還像新娘。我一直安慰自己婆殿,他們只是感情好诈乒,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著婆芦,像睡著了一般怕磨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上消约,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天肠鲫,我揣著相機(jī)與錄音,去河邊找鬼或粮。 笑死导饲,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的氯材。 我是一名探鬼主播渣锦,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氢哮!你這毒婦竟也來了袋毙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冗尤,失蹤者是張志新(化名)和其女友劉穎听盖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裂七,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡皆看,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碍讯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悬蔽。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖捉兴,靈堂內(nèi)的尸體忽然破棺而出蝎困,到底是詐尸還是另有隱情,我是刑警寧澤倍啥,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布禾乘,位于F島的核電站,受9級特大地震影響虽缕,放射性物質(zhì)發(fā)生泄漏始藕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伍派。 院中可真熱鬧江耀,春花似錦、人聲如沸诉植。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晾腔。三九已至舌稀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灼擂,已是汗流浹背壁查。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留剔应,地道東北人睡腿。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像领斥,于是被迫代替她去往敵國和親嫉到。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容