/**
** Linux系统中提供的视频设备驱动程序V4L2可以操作视频设备,比如摄像头,来获取视频数据。
** 然后可以通过开源的ffmpeg库中的函数将所采集的视频数据进行压缩编码,生成高效可播放的视频格式。
** 如下的代码是将USB摄像头所采集的视频数据最后压缩封装成FLV格式进行播放,这里所采用的ffmpeg的版本是1.1.2.
**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/videodev2.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <libavutil/mathematics.h>
#define VIDEO_WIDTH 640
#define VIDEO_HEIGHT 480
#define VIDEO_FORMAT V4L2_PIX_FMT_YUYV
#define BUFFER_COUNT 4
#define URL_WRONLY 1
struct fimc_buffer {
int length;
void *start;
} framebuf[BUFFER_COUNT];
int fd;
unsigned char yuv4200[1000000] = { 0 };
unsigned char yuv4220[1000000] = { 0 };
AVFormatContext* pFormatCtxEnc;
AVCodecContext* pCodecCtxEnc;
AVFrame* pFrameEnc;
void register_init();
int open_device();
int capability();
int set_v4l2_format();
int request_buffers();
int get_camera_data();
void unregister_all();
void video_encode_init();
int yuv422_2_yuv420(unsigned char* yuv420, unsigned char* yuv422, int width,
int height);
void register_init() {
avcodec_register_all();
av_register_all();
}
int open_device() {
char camera_device[20];
struct stat buf;
int i;
for (i = 0; i < 10; i++) {
sprintf(camera_device, "/dev/video%i", i);
if (stat(camera_device, &buf) == 0) {
break;
}
}
fd = open(camera_device, O_RDWR, 0); //设备以非阻塞方式打开
if (fd < 0) {
printf("Cannot open camera_device\n");
return -1;
}
}
int set_v4l2_format() {
int ret;
struct v4l2_format fmt; //设置视频制式和帧格式
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = VIDEO_WIDTH;
fmt.fmt.pix.height = VIDEO_HEIGHT;
fmt.fmt.pix.pixelformat = VIDEO_FORMAT;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
if (ret < 0) {
printf("VIDIOC_S_FMT failed\n");
return ret;
}
ret = ioctl(fd, VIDIOC_G_FMT, &fmt); //获取视频制式和帧格式的实际值,看是否设置正确
if (ret < 0) {
printf("VIDIOC_G_FMT failed (%d)/n", ret);
return ret;
}
}
int request_buffers() {
int ret;
int i;
struct v4l2_requestbuffers reqbuf; //向驱动申请帧缓冲
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
reqbuf.count = BUFFER_COUNT;
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf);
if (ret < 0) {
printf("VIDIOC_REQBUFS failed \n");
return ret;
}
struct v4l2_buffer buf; //获取帧缓冲地址
for (i = 0; i < BUFFER_COUNT; i++) {
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
if (ret < 0) {
printf("VIDIOC_QUERYBUF failed\n");
return ret;
}
framebuf[i].length = buf.length;
framebuf[i].start = (char *) mmap(0, buf.length, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, buf.m.offset); //将申请到的帧缓冲映射到用户空间,>就能直接操作采集的帧
if (framebuf[i].start == MAP_FAILED) {
printf("mmap (%d) failed: %s/n", i, strerror(errno));
return -1;
}
ret = ioctl(fd, VIDIOC_QBUF, &buf); //将申请到的帧缓冲全部入队列,以便存放数据
if (ret < 0) {
printf("VIDIOC_QBUF (%d) failed (%d)/n", i, ret);
return -1;
}
}
}
int get_camera_data() {
int ret;
int i, k;
struct v4l2_buffer buf; //获取帧缓冲地址
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //开始视频采集
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if (ret < 0) {
printf("VIDIOC_STREAMON failed (%d)\n", ret);
return ret;
}
video_encode_init();
i = 0;
while (1) {
static int delayFrame = 0;
int got_packet = 0;
printf("-----------seconds = %d----------\n", ++i);
for (k = 0; k < 25; k++) {
ret = ioctl(fd, VIDIOC_DQBUF, &buf); //出队列以取得已采集数据的帧缓冲,取得原始数据
if (ret < 0) {
printf("VIDIOC_DQBUF failed (%d)/n", ret);
return ret;
}
strncpy(yuv4220, framebuf[buf.index].start,
framebuf[buf.index].length);
yuv422_2_yuv420(yuv4200, yuv4220, 640, 480);
av_image_alloc(pFrameEnc->data, pFrameEnc->linesize,
pCodecCtxEnc->width, pCodecCtxEnc->height,
pCodecCtxEnc->pix_fmt, 1);
pFrameEnc->data[0] = yuv4200;
pFrameEnc->data[1] = pFrameEnc->data[0]
+ pCodecCtxEnc->width * pCodecCtxEnc->height;
pFrameEnc->data[2] = pFrameEnc->data[1]
+ pCodecCtxEnc->width * pCodecCtxEnc->height / 4;
pFrameEnc->linesize[0] = pCodecCtxEnc->width;
pFrameEnc->linesize[1] = pCodecCtxEnc->width / 2;
pFrameEnc->linesize[2] = pCodecCtxEnc->width / 2;
pFrameEnc->pts = (k + (i - 1) * 25) * 40;
pFrameEnc->width = 640;
pFrameEnc->height = 480;
if (!pFormatCtxEnc->nb_streams) {
printf("output file does not contain any stream\n");
exit(0);
}
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
printf("encoding frame %d-------", k);
ret = avcodec_encode_video2(pCodecCtxEnc, &pkt, pFrameEnc,
&got_packet);
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "Video encoding failed\n");
}
if (got_packet) {
printf("output frame %d size = %d\n", k - delayFrame, pkt.size);
ret = av_interleaved_write_frame(pFormatCtxEnc, &pkt);
if (ret != 0) {
fprintf(stderr, "write frame into file is failed\n");
} else {
printf("encode and write one frame success\n");
}
} else {
delayFrame++;
printf("no frame output\n");
}
av_free_packet(&pkt);
ret = ioctl(fd, VIDIOC_QBUF, &buf); //将缓冲重新入对尾,可以循环采集
if (ret < 0) {
printf("VIDIOC_QBUF failed (%d)\n", ret);
return ret;
}
}
/* get the delayed frames */
for (got_packet = 1; got_packet; k++) {
fflush(stdout);
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
ret = avcodec_encode_video2(pCodecCtxEnc, &pkt, NULL, &got_packet);
if (ret < 0) {
fprintf(stderr, "error encoding frame\n");
exit(1);
}
if (got_packet) {
printf("output delayed frame %3d (size=%5d)\n", k - delayFrame,
pkt.size);
av_interleaved_write_frame(pFormatCtxEnc, &pkt);
av_free_packet(&pkt);
}
}
}
av_write_trailer(pFormatCtxEnc);
if (!(pFormatCtxEnc->flags & AVFMT_NOFILE))
avio_close(pFormatCtxEnc->pb);
for (i = 0; i < BUFFER_COUNT; i++) {
munmap(framebuf[i].start, framebuf[i].length); //取消映射,释放内存
}
close(fd);
return 0;
}
int capability() {
int ret;
struct v4l2_capability cap;
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); //摄像头主要获取功能
if (ret < 0) {
printf("VIDIOC_QUERYCAP failed \n");
return ret;
}
}
int yuv422_2_yuv420(unsigned char* yuv420, unsigned char* yuv422, int width,
int height) {
int imgSize = width * height * 2;
int widthStep422 = width * 2;
unsigned char* p422 = yuv422;
unsigned char* p420y = yuv420;
unsigned char* p420u = yuv420 + imgSize / 2;
unsigned char* p420v = p420u + imgSize / 8;
int i, j;
for (i = 0; i < height; i += 2) {
p422 = yuv422 + i * widthStep422;
for (j = 0; j < widthStep422; j += 4) {
*(p420y++) = p422[j];
*(p420u++) = p422[j + 1];
*(p420y++) = p422[j + 2];
}
p422 += widthStep422;
for (j = 0; j < widthStep422; j += 4) {
*(p420y++) = p422[j];
*(p420v++) = p422[j + 3];
*(p420y++) = p422[j + 2];
}
}
return 0;
}
void unregister_all() {
int i;
for (i = 0; i < BUFFER_COUNT; i++) {
munmap(framebuf[i].start, framebuf[i].length); //取消映射,释放内存
}
close(fd);
printf("Camera test Done.\n");
}
void video_encode_init() {
char* filename = "./264.flv";
AVCodec* pCodecEnc;
AVOutputFormat* pOutputFormat;
AVStream* video_st;
int i;
int ret;
av_register_all();
pOutputFormat = av_guess_format(NULL, filename, NULL);
if (pOutputFormat == NULL) {
fprintf(stderr, "Could not guess the format from file\n");
exit(0);
} else {
printf("guess the format from file success\n");
}
pFormatCtxEnc = avformat_alloc_context();
if (pFormatCtxEnc == NULL) {
fprintf(stderr, "could not allocate AVFormatContex\n");
exit(0);
} else {
printf("allocate AVFormatContext success\n");
}
pFormatCtxEnc->oformat = pOutputFormat;
sprintf(pFormatCtxEnc->filename, "%s", filename);
printf("filename is %s\n", pFormatCtxEnc->filename);
video_st = avformat_new_stream(pFormatCtxEnc, 0);
if (!video_st) {
fprintf(stderr, "could not allocate AVstream\n");
exit(0);
} else {
printf("allocate AVstream success\n");
}
pCodecCtxEnc = video_st->codec;
pCodecCtxEnc->codec_id = pOutputFormat->video_codec;
pCodecCtxEnc->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtxEnc->bit_rate = 1000000;
pCodecCtxEnc->bit_rate_tolerance = 300000000; //表示有多少bit的视频流可以偏移出目前的设定.这里的"设定"是指的cbr或者vbr.
pCodecCtxEnc->width = 640;
pCodecCtxEnc->height = 480;
pCodecCtxEnc->time_base = (AVRational) {1,25};
//pCodecCtxEnc->time_base.num = 1;
//pCodecCtxEnc->time_base.den = 25;
pCodecCtxEnc->pix_fmt = PIX_FMT_YUV420P;
pCodecCtxEnc->gop_size = 10;
pCodecCtxEnc->max_b_frames = 0;
av_opt_set(pCodecCtxEnc->priv_data, "preset", "superfast", 0);
av_opt_set(pCodecCtxEnc->priv_data, "tune", "zerolatency", 0);
pCodecCtxEnc->pre_me = 2;
pCodecCtxEnc->lmin = 10;
pCodecCtxEnc->lmax = 50;
pCodecCtxEnc->qmin = 20;
pCodecCtxEnc->qmax = 80;
pCodecCtxEnc->qblur = 0.0;
pCodecCtxEnc->spatial_cplx_masking = 0.3;
pCodecCtxEnc->me_pre_cmp = 2;
pCodecCtxEnc->rc_qsquish = 1;
pCodecCtxEnc->b_quant_factor = 4.9;
pCodecCtxEnc->b_quant_offset = 2;
pCodecCtxEnc->i_quant_factor = 0.1;
pCodecCtxEnc->i_quant_offset = 0.0;
pCodecCtxEnc->rc_strategy = 2;
pCodecCtxEnc->b_frame_strategy = 0;
pCodecCtxEnc->dct_algo = 0;
pCodecCtxEnc->lumi_masking = 0.0;
pCodecCtxEnc->dark_masking = 0.0;
if (!strcmp(pFormatCtxEnc->oformat->name, "flv")) {
pCodecCtxEnc->flags |= CODEC_FLAG_GLOBAL_HEADER;
} else {
printf("output format is %s\n", pFormatCtxEnc->oformat->name);
}
pCodecEnc = avcodec_find_encoder(pCodecCtxEnc->codec_id);
if (!pCodecEnc) {
fprintf(stderr, "could not find suitable video encoder\n");
exit(0);
} else {
printf("find the encoder success\n");
}
if (avcodec_open2(pCodecCtxEnc, pCodecEnc, NULL) < 0) {
fprintf(stderr, "could not open video codec\n");
exit(0);
} else {
printf("open the video codec success\n");
}
pFrameEnc = avcodec_alloc_frame();
if (pFrameEnc == NULL) {
fprintf(stderr, "could not allocate pFrameEnc\n");
exit(0);
} else {
printf("allocate pFrameEnc success\n");
}
ret = avio_open(&pFormatCtxEnc->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "could not open '%s': %s\n", filename, av_err2str(ret));
exit(0);
} else {
printf("open filename = %s success\n", filename);
}
ret = avformat_write_header(pFormatCtxEnc, NULL);
if (ret < 0) {
fprintf(stderr, "error occurred when opening outputfile: %s\n",
av_err2str(ret));
exit(0);
} else {
printf("write the header success\n");
}
}
int main() {
register_init();
open_device();
capability();
set_v4l2_format();
request_buffers();
get_camera_data();
unregister_all();
return 0;
}