<三> H264编码

    H.264是一种数字视频压缩方式,因为摄像头原始帧数据一般比较大,比如一帧YUV(640*480)格式的图像,大小为640*480*12/8,约460KB,如果一秒取20帧,进行实时视频传输,每秒需要传输9M左右,比较占用宽带资源,而且视频过大不利于存储。利用H264进行编码后传输是一种比较好的解决方案。

    这里我对上一篇摄像头应用程序进行部分修改,并添加h264编码部分。实现效果是,在程序中不断获取摄像头原始帧数据,然后对其进行编码后写入到输出文件中,最后输出文件可以用VLC播放器来查看编码效果。

    首先看main函数,代码如下。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>
#include <malloc.h>
#include <time.h>
#include <iostream>
#include "camera.h"
#include"mfc.h"

#define CAM_NAME	"/dev/video0"
#define	OUT_PATH	"out.h264"

int main(void)
{
    int width=640;
    int height=480;
    int qb=20;
	int frames=300;

	std::cout << std::endl;
	std::cout << "The frames you wish to be encode:" << std::endl;
	std::cin >> frames;
	printf("frames :%d\n", frames);

	// 打开输出文件
	FILE *fp_strm = fopen(OUT_PATH, "wb");
	if (fp_strm == NULL) {
		printf("Cannot open output stream file.(%s)\n", OUT_PATH);
		return -1;
	}
	// 打开摄像头
	Camera *camera = new Camera(CAM_NAME,width,height);
    if(!camera->OpenDevice()){
		printf("Cam Open error\n");
		return -1;
	}
    // 打开编码器
    MFC *mfc=new MFC();
    if(mfc->InitMFC(width,height,qb) != MFC_RET_OK){
    	printf("Mfc init error\n");
    	return -1;
    }

	clock_t starttime, endtime;
	double totaltime;
	starttime = clock();

	// 写H264文件头信息
	unsigned char *header;
	int headerSize=mfc->GetHeader(&header);
    fwrite(header,1,headerSize,fp_strm);

    void* Y; void *CbCr; void *h264;
    int readSize;
    mfc->GetInputBuf(&Y,&CbCr);

    // 编码
    for(int i=0;i<frames;i++){

    	if(!camera->GetBuffer(Y, CbCr)){
			break;
		}
        readSize=mfc->Encode(&h264);
        if(readSize == 0){
        	printf("framd_%d error!!!\n", i);
        }else{
        	fwrite(h264, 1, readSize, fp_strm);
        	if(i%10 == 0) printf("enc %d\n", i);
        }
    }

    // 统计编码所用时间
    endtime = clock();
	totaltime = (double)( (endtime - starttime)/(double)CLOCKS_PER_SEC );
	printf("time :%f, rate :%f\n",totaltime,frames/totaltime);

	camera->CloseDevice();
    mfc->CloseMFC();

    fclose(fp_strm);
    return 0;
}

    程序中,取得摄像头的一帧原始数据后,将Y分量(帧数据前2/3)拷贝到编码器的Y输入buf,UV分量(帧数据后1/3)拷贝到编码器的CbCr输入buf。一帧编码结束后,将数据拷贝给指针h264,将其写入到输出文件即可。

    下面看编码器这部分程序,首先是编码器的初始化。

SSBSIP_MFC_ERROR_CODE MFC::InitMFC(int w, int h, int qb)
{
    SSBSIP_MFC_ERROR_CODE ret;
    unsigned int buf_type = NO_CACHE;

    width=w;
    height=h;

    param = (SSBSIP_MFC_ENC_H264_PARAM *)malloc(sizeof(*param));
    param->codecType = H264_ENC;
    param->SourceWidth = width;
    param->SourceHeight = height;
    ...
    param->FrameQp = qb; //<=51, the bigger the lower quality
    param->FrameQp_P = param->FrameQp+1;
    param->FrameMap = 0; // encoding input mode (0=linear, 1=tiled)
	...
	
    hOpen = SsbSipMfcEncOpen(&buf_type);
    if(hOpen == NULL)
    {
        printf("SsbSipMfcEncOpen Failed\n");
        ret = MFC_RET_FAIL;
        return ret;
    }else {
		printf("MfcEncOpen succeeded\n");
	}

    if(SsbSipMfcEncInit(hOpen, param) != MFC_RET_OK){

        printf("SsbSipMfcEncInit Failed\n");
        ret = MFC_RET_FAIL;
        goto out;
    }else {
        printf("SsbSipMfcEncInit succeeded\n");
    }

    memset(&input_info,0,sizeof(input_info));
    if(SsbSipMfcEncGetInBuf(hOpen, &input_info) != MFC_RET_OK)
    {
        printf("SsbSipMfcEncGetInBuf Failed\n");
        ret = MFC_RET_FAIL;
        goto out;
    }else {

        printf("SsbSipMfcEncGetInBuf succeeded\n");
    }

out:
    SsbSipMfcEncClose(hOpen);
    return ret;
}


    程序中显示对编码的参数进行配置,然后打开编码设备,对编码设备进行初始化,在SsbSipMfcEncInit(hOpen, param)函数中配置了存储h264编码的文件头信息的地址已经数据长度。之后通过SsbSipMfcEncGetInBuf(hOpen, &input_info)函数获取输入buf的地址,存储在input_info结构体中。

SSBSIP_MFC_ERROR_CODE SsbSipMfcEncInit(void *openHandle, void *param)
{
	...
	pCTX = (_MFCLIB *)openHandle;
	...
	pCTX->virStrmBuf = EncArg.args.enc_init_mpeg4.out_u_addr.strm_ref_y;
	pCTX->encodedHeaderSize = EncArg.args.enc_init_mpeg4.out_header_size;
	...
}
SSBSIP_MFC_ERROR_CODE SsbSipMfcEncGetInBuf(void *openHandle, SSBSIP_MFC_ENC_INPUT_INFO *input_info)
{
    ...
    pCTX = (_MFCLIB *)openHandle;
	...
    input_info->YVirAddr = (void*)pCTX->virFrmBuf.luma;
    input_info->CVirAddr = (void*)pCTX->virFrmBuf.chroma;
	...
}

     回头看main函数,其中获取文件头信息以及配置输入buf时就是用到上面两个函数中配置的变量。

int MFC::GetHeader(unsigned char **p)
{
    *p= (unsigned char *)((_MFCLIB *)hOpen)->virStrmBuf;
    return ((_MFCLIB *)hOpen)->encodedHeaderSize;
}

void MFC::GetInputBuf(void **Y,void **UV)
{
    *Y=input_info.YVirAddr;
    *UV=input_info.CVirAddr;
}

    编码过程的程序大致是,在输入缓冲区里填充好要编码的数据,然后给编码驱动发命令,执行完编码后,在输出缓冲区中拷贝编码完的数据就可以了。

...
memcpy(Y, buffers[0].start, cap_image_size*2/3);
memcpy(CbCr, buffers[0].start+cap_image_size*2/3, cap_image_size/3);
...
ret_code = ioctl(pCTX->hMFC, IOCTL_MFC_ENC_EXE, &EncArg);
...
SsbSipMfcEncGetOutBuf(hOpen, &output_info);
*h264=output_info.StrmVirAddr;
return output_info.dataSize;
    具体程序可以在 http://download.csdn.net/detail/westlor/9390663下载。






你可能感兴趣的:(编码,h264,摄像头,s5pv210)