微信公众号:幼儿园的学霸
个人的学习笔记,关于OpenCV,关于机器学习, …。问题或建议,请公众号留言;
之前写的ADAS客户端软件和ADAS程序之间的视频传输采用了cv::imencode和cv::imdecode
函数实现编解码,最近偶然间发现可以利用H.264对视频进行编解码,并且效果还不错,特此记录。
代码如下.使用时,首先进行编码器的初始化,然后在while循环中调用编码函数实现编码;如果需要进行网络传输,发送编码后的数据即可,在接收端,采用H264进行解码,恢复视频信息。
//
// Created by liheng on 19-12-9.
//https://www.cnblogs.com/ziyu-trip/p/7075003.html#ifndef ADAS_X264_ENCODER_H
#define ADAS_X264_ENCODER_H#include
#include "x264.h"
#include "opencv/cv.h"
#include "opencv/highgui.h"struct x264_encoder{x264_param_t param;char preset[20];char tune[20];char profile[20];x264_t* h;x264_picture_t pic_in;x264_picture_t pic_out;long colorspace;x264_nal_t* nal;int iframe;int iframe_size;int inal;
};class x264Encoder
{
public:x264Encoder();x264Encoder(int videoWidth, int videoHeight, int channel, int fps);~x264Encoder();/** 创建X264编码器* @param[in] videoWidth 视频宽度* @param[in] videoHeight 视频高度* @param[in] fps 帧率* @return 成功返回true, 失败返回false.*/bool Create(int videoWidth, int videoHeight, int channel = 3, int fps = 30);/** 编码一帧* @param[in] frame 输入的一帧图像* @return 返回编码后数据尺寸, 0表示编码失败*/int EncodeOneFrame(const cv::Mat& frame);/** 获取编码后的帧数据* 说明: EncodeOneFrame 后调用* @return 返回裸x264数据*/uchar* GetEncodedFrame() const;/** 销毁X264编码器*/void Destory();// 编码器是否可用bool IsValid() const;private:void Init();public:int m_width;int m_height;int m_channel;int m_fps;protected:int m_widthstep;int m_lumaSize;int m_chromaSize;x264_encoder* m_encoder;
};#endif //ADAS_X264_ENCODER_H
//
// Created by liheng on 19-12-9.
//#include "x264_encoder.h"
#include
#include
#include
#include
#include "opencv2/imgproc.hpp"#define ENCODER_TUNE "zerolatency"
#define ENCODER_PROFILE "baseline"
#define ENCODER_PRESET "veryfast"//"superfast"
#define ENCODER_COLORSPACE X264_CSP_I420
#define CLEAR(x) (memset((&x),0,sizeof(x)))x264Encoder::x264Encoder()
{Init();
}x264Encoder::x264Encoder(int videoWidth, int videoHeight, int channel, int fps)
{Init();Create(videoWidth, videoHeight, channel, fps);
}x264Encoder::~x264Encoder()
{Destory();
}void x264Encoder::Init()
{m_width = 0;m_height = 0;m_channel = 0;m_widthstep = 0;m_fps = 30;m_lumaSize = 0;m_chromaSize = 0;m_encoder = NULL;
}bool x264Encoder::Create(int videoWidth, int videoHeight, int channel, int fps)
{int ret;int imgSize;if (videoWidth <&#61; 0 || videoHeight <&#61; 0 || channel <0 || fps <&#61; 0){printf("wrong input param\n");return false;}m_width &#61; videoWidth;m_height &#61; videoHeight;m_channel &#61; channel;m_fps &#61; fps;m_widthstep &#61; videoWidth * channel;m_lumaSize &#61; m_width * m_height;m_chromaSize &#61; m_lumaSize / 4;imgSize &#61; m_lumaSize * channel;m_encoder &#61; (x264_encoder *)malloc(sizeof(x264_encoder));if (!m_encoder){printf("cannot malloc x264_encoder !\n");return false;}CLEAR(*m_encoder);m_encoder->iframe &#61; 0;m_encoder->iframe_size &#61; 0;strcpy(m_encoder->preset, ENCODER_PRESET);strcpy(m_encoder->tune, ENCODER_TUNE);/*初始化编码器*/CLEAR(m_encoder->param);x264_param_default(&m_encoder->param);ret &#61; x264_param_default_preset(&m_encoder->param, m_encoder->preset, m_encoder->tune);if (ret <0){printf("x264_param_default_preset error!\n");return false;}/*cpuFlags 去空缓冲区继续使用不死锁保证*/m_encoder->param.i_threads &#61; X264_SYNC_LOOKAHEAD_AUTO;/*视频选项*/m_encoder->param.i_csp &#61; X264_CSP_I420;m_encoder->param.i_width &#61; m_width; // 要编码的图像的宽度m_encoder->param.i_height &#61; m_height; // 要编码的图像的高度m_encoder->param.i_frame_total &#61; 0; // 要编码的总帧数&#xff0c;不知道用0m_encoder->param.i_keyint_max &#61; 10*fps;// 关键帧间隔/*流参数*/m_encoder->param.i_bframe &#61; 5;m_encoder->param.b_open_gop &#61; 0;m_encoder->param.i_bframe_pyramid &#61; 0;m_encoder->param.i_bframe_adaptive &#61; X264_B_ADAPT_TRELLIS;/*log参数&#xff0c;不需要打印编码信息时直接注释掉*/m_encoder->param.i_log_level &#61; X264_LOG_NONE;m_encoder->param.i_fps_num &#61; fps;//码率分子m_encoder->param.i_fps_den &#61; 1; //码率分母m_encoder->param.b_intra_refresh &#61; 1;m_encoder->param.b_annexb &#61; 1;m_encoder->param.rc.f_rf_constant &#61; 24;m_encoder->param.rc.i_rc_method &#61; X264_RC_CRF;/strcpy(m_encoder->profile, ENCODER_PROFILE);ret &#61; x264_param_apply_profile(&m_encoder->param, m_encoder->profile);if (ret <0){printf("x264_param_apply_profile error!\n");return false;}/*打开编码器*/m_encoder->h &#61; x264_encoder_open(&m_encoder->param);m_encoder->colorspace &#61; ENCODER_COLORSPACE;/*初始化pic*/ret &#61; x264_picture_alloc(&m_encoder->pic_in, m_encoder->colorspace, m_width, m_height);if ( ret <0 ){printf("x264_picture_alloc error! ret&#61;%d\n", ret);return false;}m_encoder->pic_in.img.i_csp &#61; m_encoder->colorspace;m_encoder->pic_in.img.i_plane &#61; 3;m_encoder->pic_in.i_type &#61; X264_TYPE_AUTO;m_encoder->inal &#61; 0;m_encoder->nal &#61; (x264_nal_t *)calloc(2, sizeof(x264_nal_t));if (!m_encoder->nal){printf("malloc x264_nal_t error!\n");return false;}CLEAR(*(m_encoder->nal));return true;
}int x264Encoder::EncodeOneFrame(const cv::Mat& frame)
{if (frame.empty()){return 0;}cv::Mat bgr(frame), yuv;if(1 &#61;&#61; frame.channels()){cv::cvtColor(frame, bgr, CV_GRAY2BGR);}cv::cvtColor(bgr, yuv, CV_BGR2YUV_I420);memcpy(m_encoder->pic_in.img.plane[0], yuv.data, m_lumaSize);memcpy(m_encoder->pic_in.img.plane[1], yuv.data &#43; m_lumaSize, m_chromaSize);memcpy(m_encoder->pic_in.img.plane[2], yuv.data &#43; m_lumaSize &#43; m_chromaSize, m_chromaSize);m_encoder->pic_in.i_pts &#61; m_encoder->iframe &#43;&#43;;m_encoder->iframe_size &#61; x264_encoder_encode(m_encoder->h, &m_encoder->nal, &m_encoder->inal, &m_encoder->pic_in, &m_encoder->pic_out);return m_encoder->iframe_size;
}uchar* x264Encoder::GetEncodedFrame() const
{return m_encoder->nal->p_payload;
}void x264Encoder::Destory()
{if (m_encoder){if (m_encoder->h){x264_encoder_close(m_encoder->h);m_encoder->h &#61; NULL;}free(m_encoder);m_encoder &#61; NULL;}
}bool x264Encoder::IsValid() const
{return ((m_encoder !&#61; NULL) && (m_encoder->h !&#61; NULL));
}
H264对Mat进行编码
代码如下&#xff0c;代码来源见代码说明。在原代码基础上进行了微调&#xff0c;以保证视频质量无损。
#ifndef _AV_H264_H_
#define _AV_H264_H_/***********************************************************
** Author:kaychan
** Data:2019-11-29
** Mail:1203375695&#64;qq.com
** Explain:a h264 codec
***********************************************************/#include
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
#include
#include
#include
#ifdef __cplusplus
};
#endiftypedef struct AvH264EncConfig_T {int width &#61; 320;int height &#61; 240;int frame_rate &#61; 25;//int64_t bit_rate &#61; 320000;int gop_size &#61; 250;int max_b_frames &#61; 0;
}AvH264EncConfig;class h264Encoder {public:h264Encoder();~h264Encoder();int Init(AvH264EncConfig h264_config);AVPacket *encode(const cv::Mat& mat);void Destory();
private:AVCodec *cdc_;AVCodecContext *cdc_ctx_;AVFrame *avf_;AVPacket *avp_;int frame_size_;int pts_;
};#endif
#include "h264encoder.h"h264Encoder::h264Encoder() {cdc_ &#61; NULL;cdc_ctx_ &#61; NULL;avf_ &#61; NULL;avp_ &#61; NULL;
}h264Encoder::~h264Encoder()
{Destory();
}int h264Encoder::Init(AvH264EncConfig h264_config) {pts_ &#61; 0;cdc_ &#61; avcodec_find_encoder(AV_CODEC_ID_H264);if (!cdc_) {return -1;}cdc_ctx_ &#61; avcodec_alloc_context3(cdc_);if (!cdc_ctx_) {return -1;}//cdc_ctx_->bit_rate &#61; h264_config.bit_rate;//导致画面模糊cdc_ctx_->width &#61; h264_config.width;cdc_ctx_->height &#61; h264_config.height;cdc_ctx_->time_base &#61; { 1, h264_config.frame_rate };cdc_ctx_->framerate &#61; { h264_config.frame_rate, 1 };cdc_ctx_->gop_size &#61; h264_config.gop_size;cdc_ctx_->max_b_frames &#61; h264_config.max_b_frames;cdc_ctx_->pix_fmt &#61; AV_PIX_FMT_YUV420P;cdc_ctx_->codec_id &#61; AV_CODEC_ID_H264;cdc_ctx_->codec_type &#61; AVMEDIA_TYPE_VIDEO;//cdc_ctx_->qmin &#61; 10;//cdc_ctx_->qmax &#61; 51;//cdc_ctx_->qcompress &#61; 0.6;AVDictionary *dict &#61; nullptr;//av_dict_set(&dict, "preset", "slow", 0);av_dict_set(&dict, "preset", "veryfast", 0);av_dict_set(&dict, "tune", "zerolatency", 0);av_dict_set(&dict, "profile", "main", 0);avf_ &#61; av_frame_alloc();avp_ &#61; av_packet_alloc();if (!avf_ || !avp_) {return -1;}av_init_packet(avp_);frame_size_ &#61; cdc_ctx_->width * cdc_ctx_->height;avf_->format &#61; cdc_ctx_->pix_fmt;avf_->width &#61; cdc_ctx_->width;avf_->height &#61; cdc_ctx_->height;// alloc memoryint r &#61; av_frame_get_buffer(avf_, 0);if (r <0) {return -1;}r &#61; av_frame_make_writable(avf_);if (r <0) {return -1;}return avcodec_open2(cdc_ctx_, cdc_, &dict);
}void h264Encoder::Destory() {if(cdc_ctx_) avcodec_free_context(&cdc_ctx_);if (avf_) av_frame_free(&avf_);if (avp_) av_packet_free(&avp_);
}AVPacket *h264Encoder::encode(const cv::Mat& mat) {if (mat.empty()) return NULL;cv::resize(mat, mat, cv::Size(cdc_ctx_->width, cdc_ctx_->height));cv::Mat yuv;cv::cvtColor(mat, yuv, cv::COLOR_BGR2YUV_I420);unsigned char *pdata &#61; yuv.data;// fill yuv420// yyy yyy yyy yyy// uuu// vvvavf_->data[0] &#61; pdata;avf_->data[1] &#61; pdata &#43; frame_size_;avf_->data[2] &#61; pdata &#43; frame_size_ * 5 / 4;avf_->pts &#61; pts_&#43;&#43;;int r &#61; avcodec_send_frame(cdc_ctx_, avf_);if (r >&#61; 0) {r &#61; avcodec_receive_packet(cdc_ctx_, avp_);if (r &#61;&#61; 0) {avp_->stream_index &#61; avf_->pts;return avp_;}if (r &#61;&#61; AVERROR(EAGAIN) || r &#61;&#61; AVERROR_EOF) {return NULL;}}return NULL;
}
H264对x264/H264编码数据进行解码
#ifndef CH264DECODER_H
#define CH264DECODER_H#include
#include
//C&#43;&#43;引用C语言的头文件
extern "C"
{
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"
#include "libavutil/channel_layout.h"
#include "libavutil/parseutils.h"
#include "libavutil/samplefmt.h"
#include "libavutil/fifo.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/dict.h"
#include "libavutil/mathematics.h"
#include "libavutil/pixdesc.h"
#include "libavutil/avstring.h"
#include "libavutil/imgutils.h"
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersrc.h"
#include "libavfilter/buffersink.h"
}class CH264Decoder
{
public:CH264Decoder();~CH264Decoder();/*************************************************Function:initialDescription:初始化Input:无Output:无Return:错误代码Others:无*************************************************/int initial();/*************************************************Function:decodeDescription:解码Input:pDataIn-待解码数据&#xff0c;nInSize-待解码数据长度Output:pDataOut-解码后的数据&#xff0c;nWidth-解码后的图像宽度&#xff0c;nHeight-解码后的图像高度Return:错误代码Others:解码后的数据为RGB16格式*************************************************/int decode(uint8_t *pDataIn, int nInSize, cv::Mat& res);/*************************************************Function:unInitialDescription:销毁Input:无Output:无Return:无Others:无*************************************************/void unInitial();private:int avframe_to_cvmat(AVFrame *frame,cv::Mat& res);AVFrame *cvmat2avframe(cv::Mat mat);
private:AVCodec *codec;AVCodecContext *context;AVFrame *frame;AVPacket packet;};#endif // CH264DECODER_H
#include "h264decoder.h"CH264Decoder::CH264Decoder()
{initial();
}CH264Decoder::~CH264Decoder()
{unInitial();
}int CH264Decoder::initial()
{//avcodec_register_all();//新版本应该不需要这句话av_init_packet(&packet);codec &#61; avcodec_find_decoder(AV_CODEC_ID_H264);if (!codec){printf("avcodec_find_encoder failed");return -1;}context &#61; avcodec_alloc_context3(codec);if (!context){printf("avcodec_alloc_context3 failed");return -2;}context->codec_type &#61; AVMEDIA_TYPE_VIDEO;context->pix_fmt &#61; AV_PIX_FMT_YUV420P;if (avcodec_open2(context, codec, NULL) <0){printf("avcodec_open2 failed");return -3;}frame &#61; av_frame_alloc();if (!frame){return -4;}return 0;
}void CH264Decoder::unInitial()
{avcodec_close(context);av_free(context);av_frame_free(&frame);
}int CH264Decoder::decode(uint8_t *pDataIn, int nInSize, cv::Mat& res)
{
// av_init_packet(&packet);packet.size &#61; nInSize;packet.data &#61; pDataIn;if (packet.size > 0){int got_picture&#61;0;//int ret&#61; avcodec_decode_video2(context, frame, &got_picture, &packet);//新版用法int ret &#61; avcodec_send_packet(context, &packet);if (ret &#61;&#61; 0) got_picture &#61; avcodec_receive_frame(context, frame); //got_picture &#61; 0 success, a frame was returnedif (ret <0){printf("avcodec_encode_video2 failed");return -2;}if (got_picture&#61;&#61;0)//采用avcodec_decode_video2时,此处为if (got_picture){avframe_to_cvmat(frame,res);}}else{printf("no data to decode");return -1;}return 0;
}int CH264Decoder::avframe_to_cvmat(AVFrame *frame,cv::Mat& res)
{int width &#61; frame->width, height &#61; frame->height;res.create(height*3/2, width, CV_8UC1);memcpy( res.data, frame->data[0], width*height );memcpy( res.data &#43; width*height, frame->data[1], width*height/4 );memcpy( res.data &#43; width*height*5/4, frame->data[2], width*height/4 );//cv::imshow( "yuv_show", res );//yuv格式cv::cvtColor( res, res, cv::COLOR_YUV2BGR_I420 );//bgr格式//cv::imshow( "bgr_show", bgr );return 0;}
AVFrame * CH264Decoder::cvmat2avframe(cv::Mat mat) {// alloc avframeAVFrame *avframe &#61; av_frame_alloc();if (avframe && !mat.empty()) {avframe->format &#61; AV_PIX_FMT_YUV420P;avframe->width &#61; mat.cols;avframe->height &#61; mat.rows;av_frame_get_buffer(avframe, 0);av_frame_make_writable(avframe);cv::Mat yuv; // convert to yuv420p firstcv::cvtColor(mat, yuv, cv::COLOR_BGR2YUV_I420);// calc frame sizeint frame_size &#61; mat.cols * mat.rows;unsigned char *pdata &#61; yuv.data;// fill yuv420// yyy yyy yyy yyy// uuu// vvvavframe->data[0] &#61; pdata; // fill yavframe->data[1] &#61; pdata &#43; frame_size; // fill uavframe->data[2] &#61; pdata &#43; frame_size * 5 / 4; // fill v}return avframe;
}
编码&#43;解码示例
X264EncoderDemo.cpp
//
// Created by liheng on 19-12-9.
//#include "x264_encoder.h"
#include "h264decoder.h"
#include
#include
{cv::Mat frame;cv::VideoCapture videoCapture("/home/liheng/ADAS_Video/1120/ADAS_Video-20191120-151849.mp4");for (int i &#61; 0; i <10; &#43;&#43;i){videoCapture >> frame;}//x264 encodex264Encoder m_x264Encoder;m_x264Encoder.Create(1280,720,3,30);cv::Mat x264_dst;//h264 decodeCH264Decoder m_h264Decoder;m_h264Decoder.initial();//cv encodeint jpeg_quality &#61; 75;std::vector
}
上面代码中对比了cv::encode
和x264对Mat进行编码&#xff0c;在编码后数据量的大小&#xff0c;和视频质量两个方面的效果对比。从数据量上来看&#xff0c;cv::encode
编码后数据量远大于x264编码数据量&#xff0c;即使采用75%的压缩率&#xff0c;cv::encode
的数据量仍远高于x264&#xff0c;并且压缩后视频质量不如x264压缩后视频质量。
H264EncoderDemo.cpp
//
// Created by liheng on 19-12-9.
//#include "h264encoder.h"
#include "h264decoder.h"
#include
#include
{cv::Mat frame;cv::Mat dst;cv::VideoCapture videoCapture("/home/liheng/ADAS_Video/1120/ADAS_Video-20191120-151849.mp4");for (int i &#61; 0; i <10; &#43;&#43;i){videoCapture >> frame;}h264Encoder h264;AvH264EncConfig conf;conf.width &#61; 1280;conf.height &#61; 720;conf.gop_size &#61; 10;conf.max_b_frames &#61; 0;conf.frame_rate &#61; 30;h264.Init(conf);CH264Decoder m_h264Decoder;m_h264Decoder.initial();int jpeg_quality &#61; 75;std::vector
}
以上两个.cpp
文件对应的CMakeLists.txt
文件内容&#xff1a;
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(H264Demo)set(CMAKE_CXX_STANDARD 11)if (CMAKE_BUILD_TYPE STREQUAL "")message(STATUS "CMAKE_BUILD_TYPE not defined, &#39;Release&#39; will be used")set(CMAKE_BUILD_TYPE "Release")
endif()find_package( OpenMP REQUIRED)
if(OPENMP_FOUND)message("OPENMP FOUND")set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif()set(OpenCV_DIR /usr/local/opencv3.4.1/share/OpenCV)
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)message(STATUS "OPENCV found")
else (OpenCV_FOUND)message(WARNING "OPENCV is disabled or not found")return()
endif()add_executable(X264EncoderDemo X264EncoderDemo.cpp x264_encoder.cpp h264encoder.cpp h264decoder.cpp)
target_link_libraries(X264EncoderDemo ${OpenCV_LIBRARIES}x264avformat avdevice avcodec avutil avfilter postproc swresample swscale)add_executable(H264EncoderDemo H264EncoderDemo.cpp h264decoder.cpp h264encoder.cpp)
target_link_libraries(H264EncoderDemo ${OpenCV_LIBRARIES}avformat avdevice avcodec avutil avfilter postproc swresample swscale)
针对x264编码&#xff0c;需要安装libx264库&#xff0c;安装命令为:sudo apt install libx264-dev
,H264编解码需要安装ffmpeg库&#xff0c;同样采用apt命令进行安装即可。
我在ADAS程序中将视频发送部分修改为采用x264进行编码后&#xff0c;能够在不缩放视频的情况下进行传输&#xff0c;效果满足使用要求&#xff0c;非常满意。不过&#xff0c;随后&#xff0c;我发现&#xff0c;利用gstreamer-rtsp-sever
也能够实现同样的效果&#xff0c;该方式采用rtsp视频流的形式进行视频传输&#xff0c;在接收端&#xff0c;可以直接利用cv::VideoCapture
进行视频接收&#xff0c;不用编写额外的解码代码&#xff0c;非常方便&#xff0c;此外&#xff0c;也可以利用VLC media 进行视频接收&#xff0c;此时甚至不用专门编写视频接收代码了&#xff1b;建议有需求的可以尝试一下该方法&#xff0c;代码也是非常简单&#xff0c;此处就不贴出了&#xff08;主要是感觉我的这个代码并未写完善&#xff0c;有缺陷&#xff09;。
下面的是我的公众号二维码图片&#xff0c;欢迎关注。