/**
** 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; }