天天看點

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播放器播放。

繼續閱讀