OpenCV,ffmpeg与libx264进行H.264编码(CPU与GPU版)

CPU版:

目前opencv中做h264编码实际上是向下调用ffmpeg的,而ffmpeg中则又是调用libx264,于是这也就涉及到了libx264,ffmpeg和opencv三者之间的联合编译


一、libx264


我用的系统是centos6.8,系统内默认安装的有libx264的,但是版本比较低,与ffmpeg不兼容,这里需要先卸载掉原先的x264再安装较新的版本,我用的版本是0.148


二、ffmpeg


系统同样默认安装了低版本的ffmpeg,这里也需要提前卸载掉,然后安装新版本,我用的版本是3.1.3

configuration: --prefix=/home/softwares/ffmpeg-3.1.3 --disable-yasm --enable-shared --enable-pic --enable-vaapi --enable-gpl --enable-libx264 --extra-cflags=-I/usr/local/x264/include --extra-ldflags=-L/usr/local/x264/lib

这个--enable-libx264很重要!!!

三、opencv

opencv我用的版本是2.4.13,别的版本(差的不多的话)应该都是好用的

configuration:

cmake -D CMAKE_BUILD_TYPE=release -D CMAKE_INSTALL_PREFIX=/home/softwares/opencv-2.4.13 -D WITH_CUDA=ON -D WITH_NVCUVID=ON ..

我们一般在调用VideoWriter的时候,都会用X264的FOURCC

writer.open("output_cpu.mp4", CV_FOURCC('X', '2', '6', '4'), FPS, frame.size());

但是会有如下错误

[libx264 @ 0x8d6220] broken ffmpeg default settings detected
[libx264 @ 0x8d6220] use an encoding preset (e.g. -vpre medium)
[libx264 @ 0x8d6220] preset usage: -vpre  -vpre 
[libx264 @ 0x8d6220] speed presets are listed in x264 --help
[libx264 @ 0x8d6220] profile is optional; x264 defaults to high
去网上搜索相关问题的话,基本给出的答案是这样的,在x264中源码如下

/* Detect default ffmpeg settings and terminate with an error. */  
    {  
        int score = 0;  
        score += h->param.analyse.i_me_range == 0;  
        score += h->param.rc.i_qp_step == 3;  
        score += h->param.i_keyint_max == 12;  
        score += h->param.rc.i_qp_min == 2;  
        score += h->param.rc.i_qp_max == 31;  
        score += h->param.rc.f_qcompress == 0.5;  
        score += fabs(h->param.rc.f_ip_factor - 1.25) < 0.01;  
        score += fabs(h->param.rc.f_pb_factor - 1.25) < 0.01;  
        score += h->param.analyse.inter == 0 && h->param.analyse.i_subpel_refine == 8;  
        if( score >= 5 )  
        {  
            x264_log( h, X264_LOG_ERROR, "broken ffmpeg default settings detected\n" );  
            x264_log( h, X264_LOG_ERROR, "use an encoding preset (vpre)\n" );  
            return -1;  
        }  
    }


We must at least set 4 param of the AVCodecContext before open it.
/*default settings for x264*/
        ctx->me_range = 16;
        ctx->max_qdiff = 4;
        ctx->qmin = 10;
        ctx->qmax = 51;
        ctx->qcompress = 0.6;

于是我们找到opencv根目录下,modules/highgui/src/cap_ffmpeg_impl.hpp找到相应调用ffmpeg的模块,在153~157行加上那些默认参数

bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
                                 double fps, int width, int height, bool is_color )
{
    CV_CODEC_ID codec_id = CV_CODEC(CODEC_ID_NONE);
    int err, codec_pix_fmt;
    double bitrate_scale = 1;

    close();

    // check arguments
    if( !filename )
        return false;
    if(fps <= 0)
        return false;

    // we allow frames of odd width or height, but in this case we truncate
    // the rightmost column/the bottom row. Probably, this should be handled more elegantly,
    // but some internal functions inside FFMPEG swscale require even width/height.
    width &= -2;
    height &= -2;
    if( width <= 0 || height <= 0 )
        return false;

    /* auto detect the output format from the name and fourcc code. */

#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 2, 0)
    fmt = av_guess_format(NULL, filename, NULL);
#else
    fmt = guess_format(NULL, filename, NULL);
#endif

    if (!fmt)
        return false;

    /* determine optimal pixel format */
    if (is_color) {
        input_pix_fmt = AV_PIX_FMT_BGR24;
    }
    else {
        input_pix_fmt = AV_PIX_FMT_GRAY8;
    }

    /* Lookup codec_id for given fourcc */
#if LIBAVCODEC_VERSION_INT<((51<<16)+(49<<8)+0)
    if( (codec_id = codec_get_bmp_id( fourcc )) == CV_CODEC(CODEC_ID_NONE) )
        return false;
#else
    const struct AVCodecTag * tags[] = { codec_bmp_tags, NULL};
    if( (codec_id = av_codec_get_id(tags, fourcc)) == CV_CODEC(CODEC_ID_NONE) )
        return false;
#endif

    // alloc memory for context
#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 2, 0)
    oc = avformat_alloc_context();
#else
    oc = av_alloc_format_context();
#endif
    assert (oc);

    /* set file name */
    oc->oformat = fmt;
    snprintf(oc->filename, sizeof(oc->filename), "%s", filename);

    /* set some options */
    oc->max_delay = (int)(0.7*AV_TIME_BASE);  /* This reduces buffer underrun warnings with MPEG */

    // set a few optimal pixel formats for lossless codecs of interest..
    switch (codec_id) {
#if LIBAVCODEC_VERSION_INT>((50<<16)+(1<<8)+0)
    case CV_CODEC(CODEC_ID_JPEGLS):
        // BGR24 or GRAY8 depending on is_color...
        codec_pix_fmt = input_pix_fmt;
        break;
#endif
    case CV_CODEC(CODEC_ID_HUFFYUV):
        codec_pix_fmt = AV_PIX_FMT_YUV422P;
        break;
    case CV_CODEC(CODEC_ID_MJPEG):
    case CV_CODEC(CODEC_ID_LJPEG):
        codec_pix_fmt = AV_PIX_FMT_YUVJ420P;
        bitrate_scale = 3;
        break;
    case CV_CODEC(CODEC_ID_RAWVIDEO):
        codec_pix_fmt = input_pix_fmt == AV_PIX_FMT_GRAY8 ||
                        input_pix_fmt == AV_PIX_FMT_GRAY16LE ||
                        input_pix_fmt == AV_PIX_FMT_GRAY16BE ? input_pix_fmt : AV_PIX_FMT_YUV420P;
        break;
    default:
        // good for lossy formats, MPEG, etc.
        codec_pix_fmt = AV_PIX_FMT_YUV420P;
        break;
    }

    double bitrate = MIN(bitrate_scale*fps*width*height, (double)INT_MAX/2);

    // TODO -- safe to ignore output audio stream?
    video_st = icv_add_video_stream_FFMPEG(oc, codec_id,
                                           width, height, (int)(bitrate + 0.5),
                                           fps, codec_pix_fmt);

    /* set the output parameters (must be done even if no
   parameters). */
#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 2, 0)
    if (av_set_parameters(oc, NULL) < 0) {
        return false;
    }
#endif

#if 0
#if FF_API_DUMP_FORMAT
    dump_format(oc, 0, filename, 1);
#else
    av_dump_format(oc, 0, filename, 1);
#endif
#endif

    /* now that all the parameters are set, we can open the audio and
     video codecs and allocate the necessary encode buffers */
    if (!video_st){
        return false;
    }

    AVCodec *codec;
    AVCodecContext *c;

#if LIBAVFORMAT_BUILD > 4628
    c = (video_st->codec);
#else
    c = &(video_st->codec);
#endif

    c->codec_tag = fourcc;
    /* find the video encoder */
    codec = avcodec_find_encoder(c->codec_id);
    if (!codec) {
        fprintf(stderr, "Could not find encoder for codec id %d: %s", c->codec_id, icvFFMPEGErrStr(
        #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(53, 2, 0)
                AVERROR_ENCODER_NOT_FOUND
        #else
                -1
        #endif
                ));
        return false;
    }

    int64_t lbit_rate = (int64_t)c->bit_rate;
    lbit_rate += (bitrate / 2);
    lbit_rate = std::min(lbit_rate, (int64_t)INT_MAX);
    c->bit_rate_tolerance = (int)lbit_rate;
    c->bit_rate = (int)lbit_rate;

        c->me_range = 16;
	c->max_qdiff = 4;
	c->qcompress = 0.6;
	c->qmin = 10;
	c->qmax = 51;

 /* open the codec */
    if ((err=
#if LIBAVCODEC_VERSION_INT >= ((53<<16)+(8<<8)+0)
         avcodec_open2(c, codec, NULL)
#else
         avcodec_open(c, codec)
#endif
         ) < 0) {
        fprintf(stderr, "Could not open codec '%s': %s", codec->name, icvFFMPEGErrStr(err));
        return false;
    }

    outbuf = NULL;

    if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
        /* allocate output buffer */
        /* assume we will never get codec output with more than 4 bytes per pixel... */
        outbuf_size = width*height*4;
        outbuf = (uint8_t *) av_malloc(outbuf_size);
    }

    bool need_color_convert;
    need_color_convert = (c->pix_fmt != input_pix_fmt);

    /* allocate the encoded raw picture */
    picture = icv_alloc_picture_FFMPEG(c->pix_fmt, c->width, c->height, need_color_convert);
    if (!picture) {
        return false;
    }

    /* if the output format is not our input format, then a temporary
   picture of the input format is needed too. It is then converted
   to the required output format */
    input_picture = NULL;
    if ( need_color_convert ) {
        input_picture = icv_alloc_picture_FFMPEG(input_pix_fmt, c->width, c->height, false);
        if (!input_picture) {
            return false;
        }
    }

    /* open the output file, if needed */
    if (!(fmt->flags & AVFMT_NOFILE)) {
#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 2, 0)
        if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0)
#else
            if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0)
#endif
            {
            return false;
        }
    }

#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(52, 111, 0)
    /* write the stream header, if any */
    err=avformat_write_header(oc, NULL);
#else
    err=av_write_header( oc );
#endif

    if(err < 0)
    {
        close();
        remove(filename);
        return false;
    }
    frame_width = width;
    frame_height = height;
    frame_idx = 0;
    ok = true;

    return true;
}



CvCapture_FFMPEG* cvCreateFileCapture_FFMPEG( const char* filename )
{
    CvCapture_FFMPEG* capture = (CvCapture_FFMPEG*)malloc(sizeof(*capture));
    capture->init();
    if( capture->open( filename ))
        return capture;

    capture->close();
    free(capture);
    return 0;
}

这里给AVCodecContext加入相应参数即可,接着重新编译OpenCV,在调用VideoWriter即可成功啦

GPU版:

在OpenCV较高的版本中,OpenCV提供了CUDA的h264编码相关的API,然而不幸的是

OpenCV,ffmpeg与libx264进行H.264编码(CPU与GPU版)_第1张图片

取而代之的是NVIDIA提供了自己的VideoCodecSDK,在CUDA Developer的网站里可以下载到相关的SDK的,当然这里对GPU的版本也有一些限制啦

OpenCV,ffmpeg与libx264进行H.264编码(CPU与GPU版)_第2张图片

使用的GPU架构至少是GM10X以上的

OpenCV,ffmpeg与libx264进行H.264编码(CPU与GPU版)_第3张图片

当然对CUDA的驱动也是有要求的,具体的文档里也是有说明的



你可能感兴趣的:(图像处理-OpenCV,语言-c/c++,语言-CUDA,OpenCV,H264,FFmpeg,libx264,CUDA)