说明
开发中有视频播放中抓拍或截屏等相关需求,需要将视频中的某一帧图片保存到本地,一般为jpg格式图片。常用的有libjpeg这个库,但会引入更多三方库增加复杂性。既然用ffmpeg做了音视频解析播放,还是用ffmpeg的方式实现抓图保存。
实现方式还是固定的ffmpeg编码流程,并不复杂,看代码注释即可。已经经过多次测试,一秒抓拍10张以内的1080P的图片效果还是不错的,可以根据自己需求灵活修改使用。
代码
/*
* @功能 AVFrame保存为本地jpg图片
* @参数 frame yuv420P格式的AVFrame
* width frame图像宽
* height frame图像高
* @返回 true成功 false失败
*/
bool VideoCapture::encodeJPGFromAVFrame(AVFrame *frame, int width, int height)
{
if(frame == nullptr)
return false;
//按时间命名jpg文件
QString time = QTime::currentTime().toString("hh-mm-ss-zzz");
QString picPath = QString("./%1.jpg").arg(time);
std::string path = picPath.toStdString();
//创建输出上下文
AVFormatContext *pFormatCtx = avformat_alloc_context();
int ret = avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, path.c_str());
if (ret < 0)
{
qDebug() << "avformat_alloc_output_context2 error";
return false;
}
//由输出文件jpg格式自动推导编码器类型
AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);
pCodecCtx->codec_id = pFormatCtx->oformat->video_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
pCodecCtx->width = width;
pCodecCtx->height = height;
pCodecCtx->time_base = AVRational{1, 25};
pCodecCtx->gop_size = 25;
pCodecCtx->max_b_frames = 0;
//重要参数:压缩效果、码率 设置为高质量
pCodecCtx->qcompress = 1.0;
pCodecCtx->bit_rate = 1024 * 1024 * 3;
//寻找并打开编码器
AVCodec *pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec)
{
qDebug() << "avcodec_find_encoder() Failed";
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
avcodec_free_context(&pCodecCtx);
return false;
}
ret = avcodec_open2(pCodecCtx, pCodec, NULL);
if (ret < 0)
{
qDebug() << "avcodec_open2() Failed";
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
avcodec_free_context(&pCodecCtx);
return false;
}
//进行编码
ret = avcodec_send_frame(pCodecCtx, frame);
if (ret < 0)
{
qDebug() << "avcodec_send_frame() Failed";
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
avcodec_free_context(&pCodecCtx);
return false;
}
//得到编码jpeg数据pkt 由外部使用者释放
AVPacket *pkt = av_packet_alloc();
ret = avcodec_receive_packet(pCodecCtx, pkt);
if (ret < 0)
{
qDebug() << "avcodec_receive_packet() Failed";
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
avcodec_free_context(&pCodecCtx);
return false;
}
//创建流生成本地jpg文件
AVStream *videoStream = avformat_new_stream(pFormatCtx, 0);
if (videoStream == NULL)
{
qDebug() << "avformat_new_stream() Failed";
avcodec_free_context(&pCodecCtx);
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
av_packet_free(&pkt);
return false;
}
//写文件头、数据体、文件尾
avformat_write_header(pFormatCtx, NULL);
av_write_frame(pFormatCtx, pkt);
av_write_trailer(pFormatCtx);
//释放资源
av_packet_free(&pkt);
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
avcodec_free_context(&pCodecCtx);
return true;
}