记录 FFmpeg生成本地JPG图片

说明

开发中有视频播放中抓拍或截屏等相关需求,需要将视频中的某一帧图片保存到本地,一般为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;
}

猜你喜欢

转载自blog.csdn.net/T__zxt/article/details/127782887