【作业】RGB/BMP转YUV格式及YUV视频拼接

简介

RGB文件

RGB文件是原始的没有压缩的包含红绿蓝三种颜色的图像文件。
常见的RGB格式例如RGB24,也就是一组RGB像素中的R、G、B各占8比特,即一个字节,一组RGB一共是24个比特。
这里只以RGB24为例。

YUV文件

YUV文件是与RGB文件同样属于原始的没有压缩的图像文件,只是YUV格式存储的是亮度Y、色差信号U和V。
和YUV不同的是,由大面积着色原理,人眼对于亮度信号的敏感度远强于颜色信号的敏感度。因此色度信号可以比亮度信号取得更少,而视觉上差别不大。
因此YUV又有多种取样结构,如4:4:4、4:2:2、4:2:0等。
显然4:4:4的画面质量最好。数字演播室采用的取样结构为4:2:2。
RGB与YUV的转换公式如下:
Y = 0.299 R + 0.587 G + 0.114 B R − Y = 0.701 R − 0.587 G − 0.114 B B − Y = − 0.299 R − 0.587 G + 0.886 B U = 0.713 ∗ ( R − Y ) + 128 V = 0.713 ∗ ( B − Y ) + 128 Y = 0.299R + 0.587G + 0.114B\\ R-Y = 0.701R - 0.587G-0.114B\\ B-Y = -0.299R - 0.587G + 0.886B\\\\ U = 0.713 *(R-Y) + 128\\ V = 0.713 * (B-Y) + 128 Y=0.299R+0.587G+0.114BRY=0.701R0.587G0.114BBY=0.299R0.587G+0.886BU=0.713(RY)+128V=0.713(BY)+128
YUV的Y、U、V的信号在YUV格式中是分开存放的。

BMP文件

BMP文件有两种颜色表示方式

  1. 24bit以下,带调色板的图像,从调色板里选取颜色
  2. 24bitRGB图像
    这里只考虑后者,以后有时间的话会同时实现。
    BMP文件有文件头,带有图片的一些元信息,如图片大小、长宽、调色版数量等等。
    BMP开头是一个两字节的标识(Signature)“BM"。
    接下来是文件头信息,带有文件基本的信息:文件大小和像素数据开始位置:
typedef struct 
{
	Char Signature[2];
    DWORD FileSize;
    DWORD _reserved;
    DWORD DataOffset;
} BITMAPFILEHEADER;

("BM"标识也应该是BITMAPFILEHEADER的一部分,但是这样声明结构体,在C语言下会引起内存对齐的问题,不方便直接把文件头读进结构体,因此实际编码的时候没有把它放进结构体)

再接下来是文件信息头,包含了图片的元数据信息。

typedef struct
{
    DWORD Size;
    int Width;
    int Height;
    WORD Planes;
    WORD BitCount;
    DWORD Compression;
    DWORD ImageSize;
    DWORD XpixelsPerM;
    DWORD YpixelsPerM;
    DWORD CorlorsUsed;
    DWORD ColorsImaportant;
} BITMAPINFOHEADER;

对于24bit像素的Bitmap文件,像素以RGB的形式直接存放在文件信息头之后,也就是BITMAPFILEHEADER的DataOffset指示的地方。
需要注意的是,BMP像素排列有两种方式,

  • 从左上开始,从上到下
  • 从左下开始,从下到上

判断方法是根据BITMAPINFOHEADER中Height的正负,当Height为正时,从上到下扫描,当Height为负时,从下到上扫描。

因此Height在结构体里应该声明成有符号整型。

实验内容

【作业】RGB/BMP转YUV格式及YUV视频拼接_第1张图片
思路很简单,首先,无论输入的是什么格式,都一律解码为RGB格式。

RGB格式的数据进行转码比YUV格式更方便。

不同BMP的大小可能不一,因此在转换成YUV前需要根据输出尺寸进行裁剪。

在最终输出阶段,全部转换成YUV格式,然后按照要求的帧数重复写入到文件。

代码分析

一些定义

这些定义是放在头文件里的

typedef unsigned char BYTE;
typedef unsigned int DWORD;
typedef unsigned short WORD;
#ifndef _YUV_H_
#define _YUV_H_
typedef struct {
    BYTE Y;
    BYTE U;
    BYTE V;
} YUV;

typedef struct {
    BYTE R;
    BYTE G;
    BYTE B;
} RGB;

typedef struct {
    int Y;
    int U;
    int V;
}YUV_FORMAT;

typedef enum  {FILE_BMP, FILE_RGB} INPUT_TYPE;

typedef struct
{
    FILE *fp;
    const char * file_path;
    INPUT_TYPE type;
    DWORD height;
    DWORD width;
    YUV * yuv_raw;
    DWORD yuv_size;
    RGB * rgb_raw;
    DWORD rgb_size;
    DWORD start;
    int duration;
} ImgFile;

RGB转YUV格式

void RGB2YUV(RGB * rgb_img, YUV * yuv_img, int size) {
    int i=0;
    for(i=0;i<size;i++){
        RGB rgb = rgb_img[i];
        double dr = rgb.R, dg = rgb.G, db = rgb.B;
        double y = 0.299 * dr + .587 * dg + .114 * db;
        double u = 0.713 * (dr - y) + 128;
        double v = 0.713 * (db - y) + 128;
        yuv_img[i].Y = (BYTE) y;
        yuv_img[i].U = (BYTE) u;
        yuv_img[i].V = (BYTE) v;
    }
}

BMP文件的读取

#include "BMP.h"
// #include "YUV.h"
#include "stdlib.h"
#include "string.h"
void printBMPFileHeader(BITMAPFILEHEADER *fhp)
{
    printf("Signature: %.2s\r\n"
           "FileSize: %d\r\n"
           "DataOffset: %d\r\n",
           "BM", fhp->FileSize, fhp->DataOffset);
}

void printBMPINFOHeader(BITMAPINFOHEADER *bih)
{
    printf("Size: %d\r\n"
           "Width: %d\r\n"
           "Height: %d\r\n"
           "Planes: %d\r\n"
           "BitCount: %d\r\n",
           bih->Size, bih->Width, bih->Height, bih->Planes, bih->BitCount);
}

void getBMPHeader(FILE *fp, BITMAPFILEHEADER *file_header, BITMAPINFOHEADER *info_header)
{
    fread(file_header, 2, 1, fp);
    fread(file_header, sizeof(BITMAPFILEHEADER), 1, fp);
    printBMPFileHeader(file_header);
    fread(info_header, sizeof(BITMAPINFOHEADER), 1, fp);
    printBMPINFOHeader(info_header);
    // validing header info
    fseek(fp, 0, SEEK_END);
    int size = ftell(fp);
    printf("FS:%d == HD:%d\r\n", size, file_header->FileSize);
    fseek(fp, 0, SEEK_SET);
}

int BMP2YUV(FILE *fp, BYTE **yuv_out)
{
    return 0;
}

int BMP2RGB(FILE *fp, RGB **rgb_out)
{
    fseek(fp, 2, SEEK_SET); // Rollback to start, jump off the signature
    BITMAPFILEHEADER file_header; // bitmap file header
    BITMAPINFOHEADER info_header; // bitmap info header
    fread(&file_header, sizeof(BITMAPFILEHEADER), 1, fp); // read file header
    fread(&info_header, sizeof(BITMAPINFOHEADER), 1, fp); // read info header
    DWORD data_offset = file_header.DataOffset;
    fseek(fp, data_offset, SEEK_SET); // Seek to data

    int height = info_header.Height;
    int width = info_header.Width;
    int pixels = height * width;
    RGB * rgbs = (RGB *)malloc(pixels * sizeof(RGB));
    * rgb_out = rgbs;
    fread(rgbs, sizeof(RGB), pixels, fp);
    if(info_header.Height>0) { 
        // bitmap is bottom-up DIB(Start at lower left) when height>0. rotate it.
        for(int i=0;i<info_header.Height/2;i++){
            for(int j=0;j<info_header.Width;j++){
                RGB temp = rgbs[info_header.Width* (info_header.Height - 1 - i) + j];
                rgbs[info_header.Width* (info_header.Height - 1 - i) + j] = rgbs[i * info_header.Width + j];
                rgbs[i * info_header.Width + j] = temp;
            }
        }
    }
    return pixels;
}

RGB文件的读取

#include 
#include "RGB.h"


int RGB2RGB(FILE *fp, RGB**rgb_out_bp)
{
    fseek(fp, 0, SEEK_END);
    int size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    int pixels = size/3; // caculating file size
    RGB * rgbs = (RGB *) malloc(size);
    fread(rgbs, 1, size, fp);
    *rgb_out_bp = rgbs;
    return pixels;
}

将4:4:4格式的YUV数据转换为4:x:x格式

int save_yuv(YUV * yuv_img, BYTE ** bufp, YUV_FORMAT format, int height, int width)
{
    int i;
    int size = height * width;
    int file_size = (format.Y + format.U + format.V) * size / 4;
    int flag = 10*(format.U) + format.V;
    int h_inter=0, v_inter=0;
    BYTE * output = (BYTE*) malloc(file_size);
    *bufp = output;
    BYTE * start_addr = output;
    for(i=0;i<size;i++){
        *output = yuv_img[i].Y;
        output++;
    }
    if(flag==44) {
        v_inter = h_inter = 1;
    }else if(flag==22){
        v_inter = 1;
        h_inter = 2;
    }else if(flag==20){
        v_inter = h_inter = 2;
    }else {
        return size;
    }
    for(int y=0;y<height;y++){
        for(int x=0;x<width;x++){
            i = y * width + x;
            if(y%v_inter==0 && x%h_inter ==0){
                *output = yuv_img[i].U;
                output++;
            }
        }
    }
    for(int y=0;y<height;y++){
        for(int x=0;x<width;x++){
            i = y * width + x;
            if(y%v_inter==0 && x%h_inter ==0){
                *output = yuv_img[i].V;
                output++;
            }
        }
    }
    printf("Convert size: %ld\n", output - start_addr);
    return file_size;
}

主函数

主函数包含了解析命令行参数和主要处理流程


int main(int argc, char const *argv[])
{

    if(argc<5){
        HelpMessage();
        return 0;
    }
    int input_count = 0;
    int argc_temp= argc;
    int argn = 1;
    int file_count = 0;
    INPUT_TYPE TYPE;
    ImgFile *input_file_list;
    assert(argv[argn++], "-c", " not defined.");
    // Read input file count
    sscanf(argv[argn++], "%d", &file_count);
    // Read file list
    input_file_list = (ImgFile *) malloc(file_count * sizeof(ImgFile));
    for(int i=0;i<file_count;i++){
        assert(argv[argn++], "-i", " not defined");
        const char * input_file = argv[argn];
        const char * file_path =  input_file + 4;
        FILE *fp = fopen(file_path, "rb");
        if(fp==NULL) printf("\tFile %s point is null\r\n", file_path);
        input_file_list[i].fp = fp;
        input_file_list[i].file_path = file_path;
        input_file_list[i].height = -1;
        input_file_list[i].width = -1;
        argn++;
        if(memcmp(input_file, "RGB", 3)==0){
            int h, w;
            assert(argv[argn++], "-s", " not defined.");
            const char * size_str = argv[argn++];
            sscanf(size_str, "%dx%d", &h, &w);
            input_file_list[i].height = h;
            input_file_list[i].width = w;
            input_file_list[i].type = FILE_RGB;
            input_file_list[i].start = 0;
        }
        else if(memcmp(input_file, "BMP", 3)==0){
            // read file size;
            BITMAPFILEHEADER file_header;
            BITMAPINFOHEADER info_header;
            getBMPHeader(input_file_list[i].fp, &file_header, &info_header);
            input_file_list[i].type = FILE_BMP;
            input_file_list[i].height = info_header.Height;
            input_file_list[i].width = info_header.Width;
        }
        assert(argv[argn++], "-d", " not defined.");
        int dura;
        sscanf(argv[argn++], "%d", &dura);
        input_file_list[i].duration = dura;
    }
    // Read output options
    assert(argv[argn++], "-o", " not defined.");
    FILE *foutp = fopen(argv[argn++], "wb");
    if(foutp==NULL) printf("Output file pointer is NULL.\r\n");
    assert(argv[argn++], "-s", " not defined.");
    int output_height, output_width;
    sscanf(argv[argn++], "%dx%d", &output_height, &output_width);
    printf("Output\r\n\tOutput \tfile #0\r\n\t\tSize: %dx%d\r\n", output_height, output_width);
    //  List all file
    PrintFileList(input_file_list, file_count);
    // Convert to raw rgb
    printf("Convert to raw rgb\t\r\n");
    for(int i=0;i<file_count;i++){
        ImgFile * file = input_file_list + i;
        printf("\tConvert %s\r\n", file->file_path);
        if(file->type==FILE_BMP){
            file->rgb_size = BMP2RGB(file->fp, &(file->rgb_raw));
        }
        else if(file->type=FILE_RGB)
        {
            file->rgb_size = RGB2RGB(file->fp, &(file->rgb_raw));
        }
    }

    // crop to certain size
    // read 
    for(int i=0;i<file_count;i++){
        resize(&(input_file_list[i].rgb_raw), 
               input_file_list[i].height,
               input_file_list[i].width,
               output_height,
               output_width,
               "C");
        input_file_list[i].rgb_size = output_height * output_width;
    }

    YUV_FORMAT yuv_format;
    yuv_format.Y = 4;
    yuv_format.U = 2;
    yuv_format.V = 0;
    assert(argv[argn++], "-f", " not defined.");
    const char * format_str = argv[argn++];
    yuv_format.Y = format_str[0] - '0';
    yuv_format.U = format_str[1] - '0';
    yuv_format.V = format_str[2] - '0';
    for(int i=0;i<file_count;i++){
        // convert to YUV 444
        input_file_list[i].yuv_raw = (YUV *) malloc(sizeof(YUV) *( input_file_list[i].rgb_size));
        memset(input_file_list[i].yuv_raw, 127, input_file_list[i].rgb_size);
        input_file_list[i].yuv_size = input_file_list[i].rgb_size;
        RGB2YUV(input_file_list[i].rgb_raw, input_file_list[i].yuv_raw,
                input_file_list[i].rgb_size);
    }
    BYTE *bufp;
    for(int i=0;i<file_count;i++){
        ImgFile * temp = input_file_list + i;
        // convert YUV 444 to YUV 4xx
        int output_size = save_yuv(temp->yuv_raw,
                    &(bufp),
                    yuv_format,
                    output_height,
                    output_width);

        printf("Output size: %d\r\n", output_size);
        for(int j=0;j<temp->duration;j++)
        {
            fwrite(bufp, 1, output_size, foutp);
        }
    }
    fclose(foutp);
    //  List all file
    PrintFileList(input_file_list, file_count);
    return 0;
}


void HelpMessage() {
    printf("Help\r\n");
    printf(" -c  -i : -d  [-s x][] -o  -s x -f \r\n");
    printf("  |          |                |          |                      |            |                   +-> YUV format 420/422/444.\r\n");
    printf("  |          |                |          |                      |            +-> Output height and width.\r\n");
    printf("  |          |                |          |                      +-> Output file path.\r\n");
    printf("  |          |                |          +-> File size, required when input format is RGB.\r\n");
    printf("  |          |                +-> Duration.\r\n");
    printf("  |          +-> Type (Support BMP and RGB): FilePath\r\n");
    printf("  +-> Input files count\r\n");
}

int assert(const char * left,const char * right, const char *msg){
    printf("ASSERT \"%s\"==\"%s\"\r\n", left , right);
    if(strcmp((char *) left,(char *) right)!=0){
        printf("[Assert Error]: %s\r\n", msg);
        exit(1);
        return 0;
    }else return 1;
}

void PrintImgFile(ImgFile * imgFile) {
    printf("\t\tFile path: %s\r\n"
           "\t\tType: %s\r\n"
           "\t\tHeight: %d\r\n"
           "\t\tWidth: %d\r\n"
           "\t\tRGB Size: %d\t\n"
           "\t\tYUV Size: %d\r\n"
           "\t\tDuration: %d\r\n",
           imgFile->file_path,
           imgFile->type==FILE_BMP?"BMP":"RGB",
           imgFile->height,
           imgFile->width,
           imgFile->rgb_size,
           imgFile->yuv_size,
           imgFile->duration
           );
}

void PrintFileList(ImgFile *imgFiles, int file_count)
{
    //  List all file
    printf("Input %d files\t\r\n", file_count);
    for(int i=0;i<file_count;i++){
        printf("\tInput file #%d\r\n", i);
        PrintImgFile(imgFiles + i);
    }
}

// void ReadSizeOption(char * s, )

测试

测试图片:
【作业】RGB/BMP转YUV格式及YUV视频拼接_第2张图片
将5张图片转换成YUV,分别重复50次,再输出到文件中。

./RGB2YUV
Help
 -c <Count> -i <Type>:<File> -d <Dura> [-s <Height>x<Width>][] -o <YUVFile> -s <Height>x<Width> -f <Format>
  |          |                |          |                      |            |                   +-> YUV format 420/422/444.
  |          |                |          |                      |            +-> Output height and width.
  |          |                |          |                      +-> Output file path.
  |          |                |          +-> File size, required when input format is RGB.
  |          |                +-> Duration.
  |          +-> Type (Support BMP and RGB): FilePath
  +-> Input files count
./RGB2YUV -c 5 -i BMP:./imgs/pic1_r.bmp -d 50 -i BMP:./imgs/pic2_r.bmp -d 50 -i BMP:./imgs/pic3_r.bmp -d 50 -i BMP:./imgs/pic4_r.bmp -d 50 -i BMP:./imgs/pic5_r.bmp -d 50 -o ./test2.yuv -s 2048x2048 -f 422

使用ffplay播放yuv文件:

ffplay -f rawvideo -pixel_format yuv422p -i test2.yuv -s 2048x2048

你可能感兴趣的:(c++,音视频)