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下载。