上一篇我们介绍了X264的编码,但是只能对本地文件编码,这里讨论怎么实时的读取摄像头文件并编码。
利用opencv实时读取摄像头,可以参考博客:点击打开链接。这里不做过多介绍。
至于编码流程,和上篇一样,没有改变。我们了解下X264编码器怎么直接读取摄像头的一帧图像就行。
opencv打开摄像头时,一帧一帧的播放,每一帧都由frame指定:capture >> frame
当然这里还有问题,X264对输入的要求是YUV格式,但是经过opencv采集后一帧frame是RGB格式,所以在编码之前我们要先将格式转换下,利用opencv自带的函数cvtColor。
cv::cvtColor(frame, yuvImg, CV_BGR2YUV_I420)
函数原型:
void cvCvtColor(const CvArr* src,CvArr* dst,int code) src:输入; dst:输出; code:转换的模式;
转换后的数据保存在yuvImg中,并通过fwrite函数写出,这里写出的数据是YUV格式,直接将其送到X264编码器的接口处就能开始编码。
最后给出完整代码:
#include
#include
#include
#include
#include "stdafx.h"
#include "stdint.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
extern "C"
{
#include "x264.h"
#include "x264_config.h"
};
using namespace cv;
using namespace std;
int MyYuvtoH264(int width, int height, string filename)
{
int fps = 25;
size_t yuv_size = width * height * 3 / 2;
x264_t *encoder;
x264_picture_t pic_in, pic_out;
uint8_t *yuv_buffer;
x264_param_t param;
x264_param_default_preset(¶m, "veryfast", "zerolatency");
param.i_threads = 1;
param.i_width = width;
param.i_height = height;
param.i_fps_num = fps;
param.i_fps_den = 1;
param.i_keyint_max = 25;
param.b_intra_refresh = 1;
param.b_annexb = 1;
x264_param_apply_profile(¶m, "baseline");
encoder = x264_encoder_open(¶m);
x264_picture_alloc(&pic_in, X264_CSP_I420, width, height);
yuv_buffer = (uint8_t*)malloc(yuv_size);
pic_in.img.plane[0] = yuv_buffer;
pic_in.img.plane[1] = pic_in.img.plane[0] + width * height;
pic_in.img.plane[2] = pic_in.img.plane[1] + width * height / 4;
int64_t i_pts = 0;
x264_nal_t *nals;
int nnal;
FILE *inf = fopen(filename.c_str(), "rb");
FILE *outf = fopen("test.h264", "wb");
if (NULL == inf)
{
return -1;
}
while (fread(yuv_buffer, 1, yuv_size, inf) > 0)
{
pic_in.i_pts = i_pts++;
x264_encoder_encode(encoder, &nals, &nnal, &pic_in, &pic_out);
x264_nal_t *nal;
for (nal = nals; nal < nals + nnal; nal++)
{
fwrite(nal->p_payload, 1, nal->i_payload, outf);
}
}
x264_encoder_close(encoder);
fclose(inf);
fclose(outf);
free(yuv_buffer);
return 0;
}
int main(int argc, char* argv[])
{
VideoCapture capture(0);
if (!capture.isOpened())
{
cout << "Cannot open the video cam" << endl;
return -1;
}
int w = capture.get(CV_CAP_PROP_FRAME_WIDTH);
int h = capture.get(CV_CAP_PROP_FRAME_HEIGHT);
int yuv_bufLen = w * h * 3 / 2;
unsigned char* pYuvBuf = new unsigned char[yuv_bufLen];
FILE* pFileOut = fopen("result.yuv", "w+");
if (!pFileOut)
{
printf("pFileOut open error \n");
system("pause");
exit(-1);
}
printf("pFileOut open ok \n");
cout << "Frame size : " << w << " x " << h << endl;
namedWindow("opencamera", CV_WINDOW_AUTOSIZE);
while (1)
{
Mat frame;
capture >> frame;
imshow("opencamera", frame);
if (waitKey(30) == 27) break;
cv::Mat yuvImg;
cv::cvtColor(frame, yuvImg, CV_BGR2YUV_I420);
memcpy(pYuvBuf, yuvImg.data, yuv_bufLen*sizeof(unsigned char));
fwrite(pYuvBuf, yuv_bufLen*sizeof(unsigned char), 1, pFileOut);
MyYuvtoH264(w, h, "result.yuv");
}
waitKey(0);
fclose(pFileOut);
delete[] pYuvBuf;
Sleep(100);
return 0;
}
运行效果图:
后面的命令行工具里可以看出正在进行编码。
上面介绍的代码是读取了一帧数据,然后对一帧图片编码,没有很好的利用帧间压缩。而且编码器在每次读取一张图片时,都要重新打开,重新关闭,这样明显是错误的,压缩效率不够。所以现在修改下代码,编码器打开后一直运行,等待图片的输入,修改参数在编码前缓存几张,打开帧间编码,提高压缩效率。
下面是修改后的完整代码:
#include <stdio.h>
#include <iostream>
#include "stdafx.h"
#include "stdint.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
extern "C"
{
#include "x264.h"
#include "x264_config.h"
};
using namespace cv;
using namespace std;
int main(int argc, char* argv[])
{
VideoCapture capture(0);
if (!capture.isOpened())
{
cout << "Cannot open the video cam" << endl;
return -1;
}
int w = capture.get(CV_CAP_PROP_FRAME_WIDTH);
int h = capture.get(CV_CAP_PROP_FRAME_HEIGHT);
int yuv_bufLen = w * h * 3 / 2;
size_t yuv_size = w * h * 3 / 2;
x264_t *encoder;
x264_picture_t pic_in, pic_out;
uint8_t *yuv_buffer;
x264_param_t param;
x264_param_default_preset(¶m, "veryfast", "animation");
param.i_width = w;
param.i_height = h;
x264_param_apply_profile(¶m, "baseline");
encoder = x264_encoder_open(¶m);
x264_picture_alloc(&pic_in, X264_CSP_I420, w, h);
yuv_buffer = (uint8_t*)malloc(yuv_size);
pic_in.img.plane[0] = yuv_buffer;
pic_in.img.plane[1] = pic_in.img.plane[0] + w * h;
pic_in.img.plane[2] = pic_in.img.plane[1] + w * h / 4;
int64_t i_pts = 0;
x264_nal_t *nals;
int nnal;
FILE *fp_out = fopen("test.h264", "wb");
if (!fp_out)
{
printf("Could not open output 264 file\n");
return -1;
}
FILE* pFileOut = fopen("test.yuv", "w+");
if (!pFileOut)
{
printf("Could not open input yuv file\n");
return -1;
}
cout << "Frame size : " << w << " x " << h << endl;
namedWindow("opencamera", CV_WINDOW_AUTOSIZE);
Mat frame;
while (1)
{
capture >> frame;
imshow("opencamera", frame);
waitKey(1);
cv::Mat yuvImg;
cv::cvtColor(frame, yuvImg, CV_BGR2YUV_I420);
memcpy(yuv_buffer, yuvImg.data, yuv_bufLen*sizeof(unsigned char));
fwrite(yuv_buffer, yuv_bufLen*sizeof(unsigned char), 1, pFileOut);
pic_in.i_pts = i_pts++;
x264_encoder_encode(encoder, &nals, &nnal, &pic_in, &pic_out);
x264_nal_t *nal;
for (nal = nals; nal < nals + nnal; nal++)
{
fwrite(nal->p_payload, 1, nal->i_payload, fp_out);
}
}
x264_encoder_close(encoder);
free(yuv_buffer);
return 0;
}
代码的运行效果图:
可以发现,编码器在打开后一直没有关闭,持续等待图片的输入。
完整工程下载: 点击打开链接
修改后的工程下载: 点击打开链接