【FFmpeg+Qt开发】编码流程 普通视频编码+示例详解 一学就会

目录

一、FFMPEG 编码

1.1编码流程

1.2编码示例


本文主要是基于FFMPEG技术解码部分,继续延申,对FFMPEG编码部分进行详细介绍

希望对在学习FFMPEG技术的小伙伴们有所帮助!

一、FFMPEG 编码

1.1编码流程

编码:像素数据编码后➡压缩码流数据。

例如:将YUV的像素数据编码后得到H.264的压缩码流数据。

编码的流程图,如下所示:

【FFmpeg+Qt开发】编码流程 普通视频编码+示例详解 一学就会_第1张图片

编码与解码同理,在使用FFMPEG编码视频文件之前,要确定完成 注册所有组件 这一步骤

其中,包含了相关结构体,本文不做过多阐述,详细可于文末链接,查看解码部分内容。

编码思路分析:

  1. 注册所有的组件
  2. 根据需要的码流数据的格式,来猜测的需要的编码器
  3. 打开目标文件流
  4. 新建视频流
  5. 设置编码器上下结构的一系列参数,为编码做好准备
  6. 查找对应的编码器
  7. 打开编码器
  8. 读取普通视频数据 or 摄像头数据,进行解码,保证得到的是YUV的像素数据
  9. 先写入编码的头部信息
  10. 正式开始,进行编码,将一帧像素数据压缩成码流数据
  11. 保存写入文件中
  12. 得到最终的编码后的码流数据

下面,博主举例说明,对解码后的视频文件来进行编码,究竟是怎么样的操作呢?

1.2编码示例

解码类的定义

class fecodec
{
public:
    fecodec();
    //注册组件
    void registerFFmpeg();
    //编码器 参数初始化
    void condeInit(char *flie_out);
    //编码
    void codecFrame(AVFrame *frame);
    //写入尾巴帧
    void writeEndFrame();
private:
    AVFormatContext *formatContent;//用来保存视频相关信息的结构体
    AVCodecContext *codecContent;//用来保存编解码信息的上下文结构体
    AVPacket *pkt;//码流包
    int pkt_inpex;//每一帧像素序号
    int flag;
};

具体实现如下:

注册所有组件

void fdecodec::registerFFmpeg()
{
    //注册所有的组件
    av_register_all();
}

猜测输出格式

/*猜测输出的格式是否存在:参数详解
  参数1:null
  参数2:输出文件名
  参数3:注册格式类型,用null
  返回值:猜到的转换格式,没匹配到,返回NULL
*/
formatContent=avformat_alloc_context();

//猜测编码器
AVOutputFormat  *outformat = av_guess_format(nullptr,file_out,nullptr);
if(outformat==nullptr)
    {
       qDebug()<<"猜测格式错误!";
       return;
    }

打开视频文件流

    //查找对应的编译器  设置视频信息最终的输出格式
    formatContent->oformat = outformat;//oformat输出的格式

    //打开视频流 文件流
    //参数1:输入输出的上下文对象
    //参数2:文件流路径
    //参数3:文件打开格式 写的方式
    int res=avio_open(&formatContext->pb,file_out,AVIO_FLAG_WRITE);
    if(res<0)
    {
        qDebug()<<"打开文件失败!";
        return;
    }

新建视频流

    //新建视频流
    //参数1:视频信息结构体
    //参数2:新建流 的 返回新建流 的地址
    AVStream *newStream =avformat_new_stream(formatContent,nullptr);
    if(newStream==nullptr)
    {
        qDebug()<<"打开视频流失败";
        return;
    }

编码器的参数设置:格式、宽、高、码率、帧率

/*数据初始化--AVCodecContext 编码器的上下文结构体,
保存视频编解码的相关信息,就可以得到解码器 id*/
    codecContent=newStream->codec;
    /*除了可以得到视频编解码的相关信息,编码的话还需要设置很多的参数
    首先是它的宽度和高度*/
    codecContent->width=640;
    codecContent->height=368;
    //设置码率,每一秒存的比特,这个值的设置也不要随意;码率太大,视频也会变大;
    codecContent->bit_rate=400000;
    //设置帧率-每一秒多少张图片--25 张
    codecContent->time_base={1,25};
    //设置显示的率,也叫码率
    codecContent->framerate={25,1};
/*设置每一组的图片数量,IPB 帧,I 帧存一帧的所有数据,P 帧根据 I 解码,B 帧根据前后的两帧解码;10 帧为一组;
后面的 10 帧解码不会与前 10 帧有关联。*/
    codecContent->gop_size=10;//官方建议 10 帧为一个单位
    //还有两个量化值需要设置:会影响视频的清晰度,越小越清晰,建议默认就可以了
    codecContent->qmax=51;
    codecContent->qmin=10;
    //设置一下 b 帧为 0,这样的话就只有 I 帧和 P 帧
    codecContent->max_b_frames=0;
    //设置编码格式--YUV420P 像素数据
    codecContent->pix_fmt=AV_PIX_FMT_YUV420P;
    //设置流的格式:视频流还是音频流--视频流
    codecContent->codec_type=AVMEDIA_TYPE_VIDEO;
    //设置编码器的 id,根据匹配到的 AVOutputFormat 对应信息来设置
    codecContent->codec_id=outformat->video_codec;

根据猜测到的编码器ID查找编码器

    //根据编码器的id查找有没有这样的编码器存在
    AVCodec *codec = avcodec_find_encoder(codecContent->codec_id);
    if(codec==nullptr)
    {
        qDebug()<<"编码器不存在!";
        return;
    }

找到之后打开编码器

    //打开编码器
    res = avcodec_open2(codecContent,codec,nullptr);
    if(res!=0)
    {
        qDebug()<<"打开编码器失败!";
        return;
    }

写入编码的头部信息,完成编码前的所有初始化工作

    //将编码相关的流信息写入到媒体的文件当中
    res = avformat_write_header(formatContent,nullptr);
    if(res<0)
    {
        qDebug()<<"写入头部信息失败!";
        return;
    }
    this->pkt_index=0;
    pkt=av_packet_alloc();
    qDebug()<<"编码初始化准备完毕!";

开始真正的编码 

    //frame是像素数据
    qDebug()<<"开始编码中!";

    //发送像素数据给编码器上下文结构体
    //参数1:编码器的上下文对象
    //参数2:发送的一帧yuv像素数据
    int res = avcodec_send_frame(codecContent,frame);
    if(res<0)
    {
        qDebug()<<"发送失败!";
        return;
    }

循环处理去接收码流数据、写入视频信息完成一帧编码

PS:编码的一帧像素数据给编码器进行编码的时候,可能一个 AVPacket 放不下,就需要两个
AVPacket。另外,将接收到的 AVPacket 码流数据写入视频信息中,完成一帧像素数据的编码。

    /*发送成功,进行编码的时候要注意:
     *有可能一帧的像素数据压缩之后一个AVPacket可能存不下
    */
    while (res>=0)//多次的打包 每一帧
    {
        //显示时间基 用来保存数据先后顺序的
        frame->pts = pkt_inpex++;
        //发送像素数据与接收压缩数据是配合使用的 合起来统称为编码
        //参数 1:是编码器上下文对象;
        //参数 2:用来保存压缩数据的 AVPacket
        //返回值:读完或者出错

        //接受编码之后的码流数据
        res = avcodec_receive_packet(codecContent,pkt);
        if(res == AVERROR(EAGAIN) || res == AVERROR_EOF)
        {
            //说明这一帧的像素数据一个包没有存完
            qDebug()<<"打包当中!";
        }
        else if(res<0)//失败了
        {
            qDebug()<<"打包失败!";
            return;
        }
        //设置当前这一帧是视频流 索引为0
        pkt->stream_index=0;
        //记得将编码得到的码流数据写入到文件当中
        av_interleaved_write_frame(forContent,pkt);
    }
    qDebug()<<"写入成功!";
    //packet使用完要清空 清空前面的码流->存储新的码流数据
    av_packet_unref(pkt);//清空处理
    qDebug()<<"编码完成一帧";   

写入尾巴帧、关闭输入流、释放空间

void fecodec::writeEndFrame()
{
    //写入尾巴帧信息
    av_write_trailer(formatContent);
    //关闭输入流
    avio_close(formatContent->pb);
    //释放视频信息
    avformat_free_context(formatContent);
}

基本流程已经完成,接下来结合前一篇文章(解码部分),把解码得到的像素数据给编码类去实现编码

fdecodec::fdecodec()
{
    ecode=new fecodec;
    ecode->registerFFmpeg();
    ecode->codecInit("fileout/code_frame.h264");
}
    QImage image((uchar *)pictureRGB->data[0],pictureRGB->width,pictureRGB->height,QImage::Format_RGB32);//像素数据
    emit sendImage(image);//实时播放
    emit SendImage(pictureYUV);//编码yuv像素数据
    ecode->writeEndFrame();//尾巴帧 编码
    qDebug()<<"编码完成!"<

测试主函数,代码如下:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //这边是自己创建的一个播放器界面
    PlayerWidget *play=new PlayerWidget;
    play->show();

    return a.exec();
}

保存的H.264文件及打开效果,如下所示:

H.264文件

生成H.264打开效果

FFMPEG技术---环境配置,详见:

FFmpeg+Qt开发(一):Windows下 环境搭建 详细步骤_猿力猪的博客-CSDN博客_ffmpeg库

​FFMPEG技术---解码流程,详见:

FFmpeg+Qt开发(二):解码流程 详细分析+代码示例 这一篇就够了_猿力猪的博客-CSDN博客_ffmpeg解码教程

FFMPEG技术---转码流程,详见:

FFmpeg+Qt开发(四):转码流程 H.264 转(mov、mp4、avi、flv)等视频格式 示例详解_猿力猪的博客-CSDN博客

✍ 本文主要介绍了FFmpeg技术中的编码部分,如有疑问,欢迎各位评论区学习交流!     

✍  觉得博主写的不错的,麻烦!点赞!评论!收藏!支持一下哈!蟹蟹你们! 

你可能感兴趣的:(Qt学习,FFmpeg音视频编解码,qt5,c++,音视频,实时音视频,视频编解码)