一、環境介紹
操作系統: ubuntu18.04 64位。
X264版本: x264-snapshot-20181217-2245
博客的下載地址: https://download.csdn.net/download/xiaolong1126626497/12339693
二、X264庫編譯安裝
參考這裏: https://blog.csdn.net/xiaolong1126626497/article/details/104919095
三、核心代碼
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <linux/videodev2.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/mman.h>
#include <string.h>
#include "include/x264.h"
/*攝像頭相關的全局變量聲明區域*/
#define UVC_VIDEO_DEVICE "/dev/video0" /*UVC攝像頭設備節點*/
int uvc_video_fd; /*存放攝像頭設備節點的文件描述符*/
unsigned char *video_memaddr_buffer[4]; /*存放的是攝像頭映射出來的緩衝區首地址*/
int Image_Width; /*圖像的寬度*/
int Image_Height; /*圖像的高度*/
/*X264編碼器相關的全局變量聲明區域*/
unsigned char *h264_buf=NULL;
typedef struct
{
x264_param_t *param;
x264_t *handle;
x264_picture_t *picture; //說明一個視頻序列中每幀特點
x264_nal_t *nal;
}Encoder;
Encoder en;
FILE *h264_fp; /*存放視頻的文件*/
/*函數聲明區域*/
void X264_close_encoder(void); //關閉解碼器
/*
函數功能: 處理退出的信號
*/
void exit_sighandler(int sig)
{
/*關閉視頻文件*/
fclose(h264_fp);
//關閉攝像頭
close(uvc_video_fd);
//釋放緩衝區
free(h264_buf);
//退出進程
exit(1);
}
/*設置視頻錄製相關參數*/
static int x264_param_apply_preset(x264_param_t *param, const char *preset)
{
char *end;
int i = strtol( preset, &end, 10 );
if( *end == 0 && i >= 0 && i < sizeof(x264_preset_names)/sizeof(*x264_preset_names)-1 )
preset = x264_preset_names[i];
/*快4*/
if( !strcasecmp( preset, "ultrafast" ) )
{
param->i_frame_reference = 1;
param->i_scenecut_threshold = 0;
param->b_deblocking_filter = 0;
param->b_cabac = 0;
param->i_bframe = 0;
param->analyse.intra = 0;
param->analyse.inter = 0;
param->analyse.b_transform_8x8 = 0;
param->analyse.i_me_method = X264_ME_DIA;
param->analyse.i_subpel_refine = 0;
param->rc.i_aq_mode = 0;
param->analyse.b_mixed_references = 0;
param->analyse.i_trellis = 0;
param->i_bframe_adaptive = X264_B_ADAPT_NONE;
param->rc.b_mb_tree = 0;
param->analyse.i_weighted_pred = X264_WEIGHTP_NONE;
param->analyse.b_weighted_bipred = 0;
param->rc.i_lookahead = 0;
}
/*快3*/
else if( !strcasecmp( preset, "superfast" ) )
{
param->analyse.inter = X264_ANALYSE_I8x8|X264_ANALYSE_I4x4;
param->analyse.i_me_method = X264_ME_DIA;
param->analyse.i_subpel_refine = 1;
param->i_frame_reference = 1;
param->analyse.b_mixed_references = 0;
param->analyse.i_trellis = 0;
param->rc.b_mb_tree = 0;
param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
param->rc.i_lookahead = 0;
}
/*快2*/
else if( !strcasecmp( preset, "veryfast" ) )
{
param->analyse.i_me_method = X264_ME_HEX;
param->analyse.i_subpel_refine = 2;
param->i_frame_reference = 1;
param->analyse.b_mixed_references = 0;
param->analyse.i_trellis = 0;
param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
param->rc.i_lookahead = 10;
}
/*快1*/
else if( !strcasecmp( preset, "faster" ) )
{
param->analyse.b_mixed_references = 0;
param->i_frame_reference = 2;
param->analyse.i_subpel_refine = 4;
param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
param->rc.i_lookahead = 20;
}
/*快速*/
else if( !strcasecmp( preset, "fast" ) )
{
param->i_frame_reference = 2;
param->analyse.i_subpel_refine = 6;
param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE;
param->rc.i_lookahead = 30;
}
/*中等*/
else if( !strcasecmp( preset, "medium" ) )
{
/* Default is medium */
}
/*慢速*/
else if( !strcasecmp( preset, "slow" ) )
{
param->analyse.i_me_method = X264_ME_UMH;
param->analyse.i_subpel_refine = 8;
param->i_frame_reference = 5;
param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO;
param->rc.i_lookahead = 50;
}
else if( !strcasecmp( preset, "slower" ) )
{
param->analyse.i_me_method = X264_ME_UMH;
param->analyse.i_subpel_refine = 9;
param->i_frame_reference = 8;
param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO;
param->analyse.inter |= X264_ANALYSE_PSUB8x8;
param->analyse.i_trellis = 2;
param->rc.i_lookahead = 60;
}
else if( !strcasecmp( preset, "veryslow" ) )
{
param->analyse.i_me_method = X264_ME_UMH;
param->analyse.i_subpel_refine = 10;
}
else
{
return -1;
}
return 0;
}
/*開始視頻壓縮*/
void compress_begin(Encoder *en, int width, int height)
{
en->param = (x264_param_t *) malloc(sizeof(x264_param_t));
en->picture = (x264_picture_t *) malloc(sizeof(x264_picture_t));
x264_param_default(en->param); //編碼器默認設置
/*訂製編碼器壓縮的性能*/
x264_param_apply_preset(en->param,"medium");
en->param->i_width = width; //設置圖像寬度
en->param->i_height = height; //設置圖像高度
if((en->handle = x264_encoder_open(en->param)) == 0)
{
return;
}
x264_picture_alloc(en->picture, X264_CSP_I420, en->param->i_width,en->param->i_height);
en->picture->img.i_csp = X264_CSP_I420;
en->picture->img.i_plane = 3;
}
/*結束壓縮*/
void compress_end(Encoder *en)
{
if (en->picture)
{
x264_picture_clean(en->picture);
free(en->picture);
en->picture = 0;
}
if(en->param)
{
free(en->param);
en->param = 0;
}
if(en->handle)
{
x264_encoder_close(en->handle);
}
free(en);
}
/*初始化編碼器*/
void X264_init_encoder(int width,int height)
{
compress_begin(&en,width,height);
h264_buf=(uint8_t *)malloc(sizeof(uint8_t)*width*height*3);
if(h264_buf==NULL)printf("X264緩衝區申請失敗!\n");
}
/*壓縮一幀數據*/
int compress_frame(Encoder *en, int type, uint8_t *in, uint8_t *out) {
x264_picture_t pic_out;
int nNal = 0;
int result = 0;
int i = 0 , j = 0 ;
uint8_t *p_out = out;
en->nal=NULL;
uint8_t *p422;
char *y = en->picture->img.plane[0];
char *u = en->picture->img.plane[1];
char *v = en->picture->img.plane[2];
//////////////////////////////////////////////////////////////////////////////////////
int widthStep422 = en->param->i_width * 2;
for(i = 0; i < en->param->i_height; i += 2)
{
p422 = in + i * widthStep422;
for(j = 0; j < widthStep422; j+=4)
{
*(y++) = p422[j];
*(u++) = p422[j+1];
*(y++) = p422[j+2];
}
p422 += widthStep422;
for(j = 0; j < widthStep422; j+=4)
{
*(y++) = p422[j];
*(v++) = p422[j+3];
*(y++) = p422[j+2];
}
}
switch (type) {
case 0:
en->picture->i_type = X264_TYPE_P;
break;
case 1:
en->picture->i_type = X264_TYPE_IDR;
break;
case 2:
en->picture->i_type = X264_TYPE_I;
break;
default:
en->picture->i_type = X264_TYPE_AUTO;
break;
}
/*開始264編碼*/
if (x264_encoder_encode(en->handle, &(en->nal), &nNal, en->picture,
&pic_out) < 0) {
return -1;
}
en->picture->i_pts++;
for (i = 0; i < nNal; i++) {
memcpy(p_out, en->nal[i].p_payload, en->nal[i].i_payload);
p_out += en->nal[i].i_payload;
result += en->nal[i].i_payload;
}
return result;
//return nNal;
}
//編碼並寫入一幀數據
void encode_frame(uint8_t *yuv_frame)
{
int h264_length = 0;
//壓縮一幀數據
h264_length = compress_frame(&en, -1, yuv_frame, h264_buf);
if(h264_length > 0)
{
printf("h264_length=%d\n",h264_length);
//寫入視頻文件
fwrite(h264_buf, h264_length,1,h264_fp);
}
}
/*
函數功能: UVC攝像頭初始化
返回值: 0表示成功
*/
int UVCvideoInit(void)
{
/*1. 打開攝像頭設備*/
uvc_video_fd=open(UVC_VIDEO_DEVICE,O_RDWR);
if(uvc_video_fd<0)
{
printf("%s 攝像頭設備打開失敗!\n",UVC_VIDEO_DEVICE);
return -1;
}
/*2. 設置攝像頭的屬性*/
struct v4l2_format format;
memset(&format,0,sizeof(struct v4l2_format));
format.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*表示視頻捕獲設備*/
format.fmt.pix.width=320; /*預設的寬度*/
format.fmt.pix.height=240; /*預設的高度*/
format.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; /*預設的格式*/
format.fmt.pix.field=V4L2_FIELD_ANY; /*系統自動設置: 幀屬性*/
if(ioctl(uvc_video_fd,VIDIOC_S_FMT,&format)) /*設置攝像頭的屬性*/
{
printf("攝像頭格式設置失敗!\n");
return -2;
}
Image_Width=format.fmt.pix.width;
Image_Height=format.fmt.pix.height;
printf("攝像頭實際輸出的圖像尺寸:x=%d,y=%d\n",format.fmt.pix.width,format.fmt.pix.height);
if(format.fmt.pix.pixelformat==V4L2_PIX_FMT_YUYV)
{
printf("當前攝像頭支持YUV格式圖像輸出!\n");
}
else
{
printf("當前攝像頭不支持YUV格式圖像輸出!\n");
return -3;
}
/*3. 請求緩衝區: 申請攝像頭數據採集的緩衝區*/
struct v4l2_requestbuffers req_buff;
memset(&req_buff,0,sizeof(struct v4l2_requestbuffers));
req_buff.count=4; /*預設要申請4個緩衝區*/
req_buff.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設備*/
req_buff.memory=V4L2_MEMORY_MMAP; /*支持mmap內存映射*/
if(ioctl(uvc_video_fd,VIDIOC_REQBUFS,&req_buff)) /*申請緩衝區*/
{
printf("申請攝像頭數據採集的緩衝區失敗!\n");
return -4;
}
printf("攝像頭緩衝區申請的數量: %d\n",req_buff.count);
/*4. 獲取緩衝區的詳細信息: 地址,編號*/
struct v4l2_buffer buff_info;
memset(&buff_info,0,sizeof(struct v4l2_buffer));
int i;
for(i=0;i<req_buff.count;i++)
{
buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設備*/
buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap內存映射*/
if(ioctl(uvc_video_fd,VIDIOC_QUERYBUF,&buff_info)) /*獲取緩衝區的詳細信息*/
{
printf("獲取緩衝區的詳細信息失敗!\n");
return -5;
}
/*根據攝像頭申請緩衝區信息: 使用mmap函數將內核的地址映射到進程空間*/
video_memaddr_buffer[i]=mmap(NULL,buff_info.length,PROT_READ|PROT_WRITE,MAP_SHARED,uvc_video_fd,buff_info.m.offset);
if(video_memaddr_buffer[i]==NULL)
{
printf("緩衝區映射失敗!\n");
return -6;
}
}
/*5. 將緩衝區放入採集隊列*/
memset(&buff_info,0,sizeof(struct v4l2_buffer));
for(i=0;i<req_buff.count;i++)
{
buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設備*/
buff_info.index=i; /*緩衝區的節點編號*/
buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap內存映射*/
if(ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info)) /*根據節點編號將緩衝區放入隊列*/
{
printf("根據節點編號將緩衝區放入隊列失敗!\n");
return -7;
}
}
/*6. 啓動攝像頭數據採集*/
int Type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(uvc_video_fd,VIDIOC_STREAMON,&Type))
{
printf("啓動攝像頭數據採集失敗!\n");
return -8;
}
return 0;
}
/*
函數功能: 採集攝像頭的數據,並進行處理
*/
void *pthread_video_Data_Handler(void *dev)
{
/*循環採集攝像頭的數據*/
struct pollfd fds;
fds.fd=uvc_video_fd;
fds.events=POLLIN;
struct v4l2_buffer buff_info;
memset(&buff_info,0,sizeof(struct v4l2_buffer));
int index=0; /*表示當前緩衝區的編號*/
printf("攝像頭開始傳輸數據.......\n");
while(1)
{
/*1. 等待攝像頭採集數據*/
poll(&fds,1,-1);
/*2. 取出一幀數據: 從採集隊列裏面取出一個緩衝區*/
buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設備*/
ioctl(uvc_video_fd,VIDIOC_DQBUF,&buff_info); /*從採集隊列取出緩衝區*/
index=buff_info.index;
//printf("採集數據的緩衝區的編號:%d\n",index);
/*3. 處理數據: 進行H264編碼*/
//video_memaddr_buffer[index]; /*當前存放數據的緩衝區地址*/
/*編碼一幀數據*/
encode_frame(video_memaddr_buffer[index]);
/*4. 將緩衝區再次放入採集隊列*/
buff_info.memory=V4L2_MEMORY_MMAP; /*支持mmap內存映射*/
buff_info.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; /*視頻捕獲設備*/
buff_info.index=index; /*緩衝區的節點編號*/
ioctl(uvc_video_fd,VIDIOC_QBUF,&buff_info); /*根據節點編號將緩衝區放入隊列*/
}
}
int main(int argc,char **argv)
{
if(argc!=2)
{
printf("./app <視頻文件名稱>\n");
return 0;
}
pthread_t thread;
/*綁定將要捕獲的信號*/
signal(SIGINT,exit_sighandler);
signal(SIGSEGV,exit_sighandler);
signal(SIGPIPE,SIG_IGN);
/*1. 初始化攝像頭*/
if(UVCvideoInit()!=0)
{
printf("攝像頭數據採集客戶端:初始化攝像頭失敗!\n");
exit(1);
}
/*2. 初始化編碼器*/
X264_init_encoder(Image_Width,Image_Height);
/*3.創建存放視頻的文件*/
h264_fp=fopen(argv[1],"wa+");
if(h264_fp==NULL)
{
printf("文件創建失敗!\n");
exit(1);
}
/*4. 創建線程採集攝像頭數據並編碼*/
pthread_create(&thread,NULL,pthread_video_Data_Handler,NULL);
//設置線程的分離屬性
pthread_detach(thread);
while(1)
{
}
}
四、編譯方法
CC=gcc
all:
$(CC) x264_VideoEncode.c -o x264_video_encode -lx264 -L./lib -lpthread -lm -ldl
使用的是靜態庫鏈接編譯。
五、運行示例
$ ./x264_video_encode 123.x264
在本地生成一個123.x264文件,可以使用mplayer或者vlc播放器播放。