【Qt+FFmpeg】给视频添加时间水印

ffmpeg编解码中,给本地视频加上时间水印,并保存到本地,使用到的技术是ffmpeg中的avfilter库;

具体效果如下

yuv:

 mp4

 本方法不适合摄像头解码,解码出来糊得不行,本地视频的话会好得多;

具体代码如下:

int video::waterMark(AVFrame *frame_in,AVFrame *frame_out,int w,int h,const char *str)
{
    int ret;
    /*根据名字获取ffmegding定义的filter*/
    const AVFilter *buffersrc=avfilter_get_by_name("buffer");//原始数据
    const AVFilter *buffersink=avfilter_get_by_name("buffersink");//处理后的数据
    /*动态分配AVFilterInOut空间*/
    outputs=avfilter_inout_alloc();
    inputs=avfilter_inout_alloc();
    /*创建AVFilterGraph,分配空间*/
    filter_graph = avfilter_graph_alloc();
    enum AVPixelFormat pix_fmts[]={AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE};//设置格式
    /*过滤器参数:解码器的解码帧将被插入这里。*/
    char args[256];
    snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             w,h,AV_PIX_FMT_YUV420P,1,25,1,1);//图像宽高,格式,帧率,画面横纵比

    qDebug()<<args;
    /*创建过滤器上下文,源数据AVFilterContext*/
    AVFilterContext *buffersrc_ctx = nullptr;
    ret=avfilter_graph_create_filter(&buffersrc_ctx,buffersrc,"in",args,NULL,filter_graph);
    if(ret<0)
    {
        qDebug()<<"创建过滤器上下文失败AVFilterContext";
        return -1;
    }
    /*创建过滤器上下文,处理后数据buffersink_params*/
    AVBufferSinkParams *buffersink_params;
    buffersink_params=av_buffersink_params_alloc();
    buffersink_params->pixel_fmts=pix_fmts;//设置格式
    AVFilterContext *buffersink_ctx;
    ret=avfilter_graph_create_filter(&buffersink_ctx,buffersink,"out",NULL,buffersink_params,filter_graph);
    av_free(buffersink_params);
    if(ret<0)
    {
        qDebug()<<"创建sink过滤器上下文失败AVFilterContext";
        return -2;
    }
    /*过滤器链输入/输出链接列表*/
    outputs->name       =av_strdup("in");
    outputs->filter_ctx =buffersrc_ctx;
    outputs->pad_idx    =0;
    outputs->next		=NULL;

    inputs->name		=av_strdup("out");
    inputs->filter_ctx	=buffersink_ctx;
    inputs->pad_idx    =0;
    inputs->next		=NULL;
    char filter_desrc[200]={0};//要添加的水印数据
    snprintf(filter_desrc,sizeof(filter_desrc),"drawtext=fontfile=arial.ttf:fontcolor=green:fontsize=20:x=450:y=50:text='%s'",str);
    qDebug()<<filter_desrc;
    if(avfilter_graph_parse_ptr(filter_graph,filter_desrc,&inputs,&outputs, NULL)<0)//设置过滤器数据内容
    {
        qDebug()<<avfilter_graph_parse_ptr(filter_graph,filter_desrc,&inputs,&outputs, NULL);
        qDebug()<<"添加字符串信息失败";
        return -3;
    }
    qDebug()<<"添加字符串信息成功";
    /*检测配置信息是否正常*/
    if(avfilter_graph_config(filter_graph,NULL)<0)
    {
        qDebug()<<"配置信息有误";
        return -4;
    }
    qDebug()<<"配置信息成功";
    /*往源滤波器buffer中输入待处理数据*/

    if(av_buffersrc_add_frame(buffersrc_ctx,frame_in)<0)
    {
        qDebug()<<"往源滤波器buffer中输入待处理数据有误";
        return -5;
    }
    qDebug()<<"往源滤波器buffer中输入待处理数据成功";
    /*从滤波器中输出处理数据*/
    if(av_buffersink_get_frame(buffersink_ctx, frame_out)<0)
    {
        qDebug()<<"从滤波器中输出处理数据失败";
        return -6;
    }
    qDebug()<<"从滤波器中输出处理数据成功";

    avfilter_inout_free(&outputs);
    avfilter_inout_free(&inputs);
    avfilter_graph_free(&filter_graph);
    return 0;

}

上述的函数我是放入解码线程中进行循环操作,解码部分可以参考我之前的【Qt+FFmpeg】解码播放本地视频(一)_logani的博客-CSDN博客

部分有些出入,所以这边还是贴一下代码

void video::run()
{   
    in_width=avcodec_context->width;
    in_height=avcodec_context->height;
    qDebug()<<"6.循环解码";
    //packet开空间
    av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));

    //输入->环境一帧数据->缓冲区->类似于一张图
    pFramein = av_frame_alloc();
    //输出->帧数据->数据格式->RGB
    pFrameRGB = av_frame_alloc();
    pictureYUV=av_frame_alloc();
    pFrameOUT=av_frame_alloc();

    pictureYUV->format = avcodec_context->pix_fmt;    //获取像素格式
    pFrameRGB->format = avcodec_context->pix_fmt;    //获取像素格式
    //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
    //缓冲区分配内存RGB
    pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(
                                          AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height));
    //缓冲区分配内存YUV
    buffer = (uint8_t *)av_malloc(avpicture_get_size(
                                      AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height));
    bufferOUT= (uint8_t *)av_malloc(avpicture_get_size(
                                        AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height));
    //初始化缓冲区 类似于memset
    avpicture_fill((AVPicture *)pFrameRGB, pOutbuffer,
                   AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height);
    //初始化缓冲区 类似于memset
    avpicture_fill((AVPicture *)pictureYUV, buffer,
                   AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);
    avpicture_fill((AVPicture *)pFrameOUT, bufferOUT,
                   AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);

    //解码的状态类型(0:表示解码完毕,非0:表示正在解码)
    int y_size, u_size, v_size=0;

    //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
    //准备一个视频像素数据格式上下文
    //参数一:输入帧数据宽
    //参数二:输入帧数据高
    //参数三:输入帧数据格式
    //参数四:输出帧数据宽
    //参数五:输出帧数据高
    //参数六:输出帧数据格式->AV_PIX_FMT_RGB32
    //参数七:视频像素数据格式转换算法类型
    //参数八:字节对齐类型(C/C++里面)->提高读取效率
    SwsContext* pSwsContext = sws_getContext(avcodec_context->width,
                                             avcodec_context->height,
                                             avcodec_context->pix_fmt,
                                             avcodec_context->width,
                                             avcodec_context->height,
                                             AV_PIX_FMT_RGB32,
                                             SWS_BICUBIC,NULL,NULL,NULL);

    SwsContext* ySwsContent = sws_getContext(avcodec_context->width,
                                             avcodec_context->height,
                                             avcodec_context->pix_fmt,
                                             avcodec_context->width,
                                             avcodec_context->height,
                                             AV_PIX_FMT_YUV420P,
                                             SWS_BICUBIC,NULL,NULL,NULL);
    //保存 yuv 像素数据的文件
    FILE *fpyuv=fopen("../videofile/saveYUV.yuv","wb+");
    int ret=0;
    while (m_stop == false)
    {
        //>=0:说明有数据,继续读取   <0:说明读取完毕,结束
        //从视频文件上下文中读取包--- 有数据就一直读取
        if (av_read_frame(avformat_context,av_packet) >= 0)
        {
            //解码什么类型流(视频流、音频流、字幕流等等...)
            if (av_packet->stream_index == av_stream_index)
            {
                //发送一个包数据进行解码
                avcodec_send_packet(avcodec_context, av_packet);
                //接收一个包数据,解压成一帧
                ret = avcodec_receive_frame(avcodec_context,pFramein);
                if (ret == 0)
                {

                    sws_scale(ySwsContent,(const unsigned char* const*)pFramein->data,pFramein->linesize, 0,avcodec_context->height,
                              pictureYUV->data, pictureYUV->linesize);
                    sws_scale(ySwsContent,(const unsigned char* const*)pFramein->data,pFramein->linesize, 0,avcodec_context->height,
                              pFrameOUT->data, pictureYUV->linesize);

                    pictureYUV->width=in_width;
                    pictureYUV->height=in_height;
                    pictureYUV->format=AV_PIX_FMT_YUV420P;
                    //图片格式的转换  输入 输出
                    sws_scale(pSwsContext, (const unsigned char* const*)pFramein->data, pFramein->linesize, 0, avcodec_context->height,
                              pFrameRGB->data,  pFrameRGB->linesize);

                    QImage image(pOutbuffer,avcodec_context->width,avcodec_context->height,QImage::Format_RGB32);

                    qDebug()<<"接收图片信号"<<image;

                    sec=time(NULL);
                    if(sec!=sec2)
                    {
                        sec2=sec;
                        struct tm* today = localtime(&sec2);
                        strftime(sys_time, sizeof(sys_time), "%Y/%m/%d %H\\:%M\\:%S", today);
                    }

                    waterMark(pictureYUV,pFrameOUT,in_width,in_height,sys_time);//添加水印
                    //yuv420规则一:Y结构表示一个像素点
                    //yuv420规则二:四个Y对应一个U和一个V(也就是四个像素点,对应一个U和一个V)
                    // y = 宽 * 高
                    // u = y / 4
                    // v = y / 4
                    y_size = avcodec_context->width * avcodec_context->height;
                    u_size = y_size / 4;
                    v_size = y_size / 4;
                    fwrite(pFrameOUT->data[0],1,y_size,fpyuv);//Y  UV 个数是 Y 的 1/4
                    fwrite(pFrameOUT->data[1],1,u_size,fpyuv);//U
                    fwrite(pFrameOUT->data[2],1,v_size,fpyuv);//V

                    //计数第几张图片
                    current_frame_index++;
                    //发送图片信号
                    emit sigGetOneFrame(image);
                    emit SendOneData(current_frame_index);
                    video::YUVQueue.enqueue(pFrameOUT);//YUV像素数据存入队列中
                    video::ImageQueue.enqueue(image);//RGB存入队列
                    //pCoding->codingFrame(pFramein);//发送YUV
                    //延时操作  1秒显示25帧--1000/25=40
                    QThread::msleep(timeSpeed);
                    //获取的视频信息
                    qDebug()<<QString("当前遍历第 %1 帧").arg(current_frame_index);
                }
            }
        }
        else
        {
            qDebug()<<"播放完毕";
            emit sigPlayOver();//读取完视频发出信号
            this->current_frame_index=0;//清空计数
            this->stop();
            //关闭文件
            fclose(fpyuv);
        }
        av_free_packet(av_packet);
    }
}

做完这些,可能会存在的一些问题:

1. avfilter_graph_create_filter返回值小于0,打印值为-22;

原因:没有注册avfilter组件

解决方法:注册一下环境

2. avfilter_graph_parse_ptr返回值小于0,添加字符信息失败;

红字字体错误:[Parsed_drawtext_0 @ 0x70fc22fe00] Cannot find a valid font for the family Sans

[AVFilterGraph @ 0x71031fc980] Error initializing filter 'drawtext'

原因:没有本地字体文件

解决方法:网上下载一个

3. 红色字体错误Changing video/audio frame properties on the fly is not supported by all filters

原因:可能是输入流没有初始化大小和格式?

添加了下面代码就没有报错

 

本文参考了

基于FFMPEG水印添加---avfilter库_IT_阿水的博客-CSDN博客_ffmpeg 动态水印

感谢观看!!!!

以上就是全部内容,如果对您有帮助,欢迎点赞评论,或者发现有哪里写错的,欢迎指正!

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

logani

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值