X264编解码器开发: Linux下读取摄像头数据,通过X264压缩编码为X264格式裸流视频文件

简介: X264编解码器开发: Linux下读取摄像头数据,通过X264压缩编码为X264格式裸流视频文件

一、环境介绍

操作系统:  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播放器播放。

目录
相关文章
|
30天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
82 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
存储 Linux 索引
Linux 下最主流的文件系统格式——ext
【9月更文挑战第8天】硬盘被划分为若干相同大小的块(Block),默认大小为4K,便于灵活管理文件数据。文件数据分散存放于这些块中,提高了数据添加、删除和插入的便利性。
|
2月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
43 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
49 5
|
2月前
|
存储 Linux 编译器
Linux内核编码风格 【ChatGPT】
Linux内核编码风格 【ChatGPT】
|
3月前
|
Linux 调度
在Linux中,任务计划格式中,前面5个数字分表表示什么含义?
在Linux中,任务计划格式中,前面5个数字分表表示什么含义?
|
Linux 算法 安全
19、Linux编码规范
一、排版 1.相对独立的程序块之间、变量声明之后必须加空行。 int          conn_fd; int          ret;      conn_fd = socket(AF_INET, SOCK_STREAM,0); if (conn_fd < 0) {     perror("socket create"); } 2.程序块要采用缩进风格编写,缩进为4个空格或一个Tab键。
831 0
|
4天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
21 3
|
4天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2