【Qt+FFmpeg】编码视频详细流程

视频编码,就是把非压缩的像素数据编码为压缩的视频码流数据;

编码YUV,格式从YUV→H.264

一、FFmpeg编码函数

av_register_all():注册所有组件

av_guess_format(): 已经注册的最合适的输出格式

avcodec_find_encoder(): 查找一个已经注册的音视频编码器

avcode_open2():打开编码码器

avformat_write_header():把流头信息写入到媒体文件中

av_read_frame():读取一帧压缩数据

avcodec_send_frame():发送一帧像素数据

avcodec_receive_packet():接受一帧编码数据

av_packet_rescale_ts():时间基转换

av_write_frame():写一帧数据

flush_encoder():将最后一帧写入文件

av_write_trailer():把流尾信息写入文件

av_code_close():关闭流

二、视频编码流程图 

 三、编码数据结构介绍

AVFormatContext
封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
AVInputFormat

每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体

AVStream

视频文件中每个视频(音频)流对应一个该结构体

          AVCodecContext     

          编码器上下文结构体,保存了视频(音频)编解码相关信息

AVCodec

每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。

AVPacket

存储一帧压缩编码数据。

AVFrame

          存储一帧解码后像素(采样)数据

四、实现代码

头文件

#ifndef CODING_H
#define CODING_H

#include <QObject>
#include "mythread.h"
#include <QDebug>
#include "video.h"

extern "C"
{
//avcodec:编解码(最重要的库)
#include <libavcodec/avcodec.h>
//avformat:封装格式处理
#include <libavformat/avformat.h>
//swscale:视频像素数据格式转换
#include <libswscale/swscale.h>
//avdevice:各种设备的输入输出
#include <libavdevice/avdevice.h>
//avutil:工具库(大部分库都需要这个库的支持)
#include <libavutil/avutil.h>
#include "libswresample/swresample.h"
}
class coding : public myThread//自定义线程,可直接继承QThread
{
    Q_OBJECT
public:
    coding(QString filename);
    //初始化
    void codingInit(QString filename);
    //编码过程
    void codingFrame(AVFrame *frameYUV);
    //线程
    void run();
    void writeTailer();//写入尾部帧
    void stop();
    ~coding();
private:
    AVFormatContext *formatContent;//保存视频信息
    AVCodecContext *codecContent;//保存编码器相关信息
    int pkt_index;//帧序列
    AVPacket *pkt;//码流包
    bool m_stop;

};

源文件 

#include "coding.h"

coding::coding(QString filename)
{
    this->m_stop = false;
    codingInit(filename);
}

void coding::codingInit(QString filename)
{
    //1.注册组件
    av_register_all();
    avdevice_register_all();//硬件初始化
    //2.编码前的系列准备-猜测输出格式
    formatContent=avformat_alloc_context();
    /*猜测输出的格式是否存在:
    参数 1:用 null;
    参数 2:输出的文件名;
    参数 3:注册格式的类型,用null
    如果没有猜测到返回NULL*/
    AVOutputFormat *outformat=av_guess_format(nullptr,filename.toStdString().c_str(),nullptr);
    if(outformat==nullptr)
    {
        qDebug()<<"猜测格式失败";
        return;
    }
    //3.打开视频文件流
    formatContent->oformat=outformat;
    /*打开文件流:
    参数 1:AVIOContext--输入输出的上下文对象(视频流信息结构体中的一个成员pb);
    参数 2:文件名;
    参数 3:文件以什么形式打开,编码是以写的方式打开;
    返回值:成功与否,<0 为失败
    */
    int res=avio_open(&formatContent->pb,filename.toStdString().c_str(),AVIO_FLAG_WRITE);
    if(res<0)
    {
        qDebug()<<"打开视频文件失败";
        return;
    }
    //4.新建视频流
    /*打开成功就可以新建视频流
    参数 1:视频流信息结构体;
    参数 2:NULL
    返回值:创建好的视频流
    */
    AVStream *newStream=avformat_new_stream(formatContent,nullptr);
    if(newStream==nullptr)
    {
        qDebug()<<"新建视频流失败";
        return;
    }
    //5.编码器的参数设置:格式、宽、高、码率、帧率等
    //编码器上下文->编码器 id
    codecContent=newStream->codec;
    //设置视频宽度高度
    codecContent->width=640;
    codecContent->height=360;
    //设置码率,每一秒存的比特,这个值的设置也不要随意;码率太大,视频也会变大;
    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;
    //6.根据编码器id找到对应编码器
    //查找编码器
    AVCodec *codec=avcodec_find_encoder(codecContent->codec_id);
    if(codec==nullptr)
    {
        qDebug()<<"没找到对应的编码器";
        return;
    }
    //7.打开编码器
    res=avcodec_open2(codecContent,codec,nullptr);
    if(res<0)
    {
        qDebug()<<"打开编码器失败";
        return;
    }
    //8.写入头部信息,完成初始化工作
    res=avformat_write_header(formatContent,nullptr);
    if(res<0)
    {
        qDebug()<<"写入头部信息失败";
        return;
    }
    this->pkt_index=0;
    pkt=av_packet_alloc();
    qDebug()<<"初始化工作完成";
}

void coding::codingFrame(AVFrame *frameYUV)
{
    //9.开始编码
    /*发送一帧像素数据到编码器中的上下文对象中
    * 参数 1: 编码器的上下文对象;
    * 参数 2:发送的一帧解码线程传入的YUV像素数据
    */
    int res=avcodec_send_frame(codecContent,frameYUV);
    //判断发送成功与否
    if(res<0)
    {
        qDebug()<<"发送给编码器失败";
        return;
    }
    //10.循环处理 接收码流数据
    /*发送成功
    * 解码后可能一个 AVFrame 存不下像素数据,同样的,传进来的一帧 yuv 数据,
    * 可能一个 AVPacket 够放,也有可能需要两个 AVPacket 来存,所以做循环来处理
    */
    while(res>=0)
    {
        /*显示时间基,保证显示的顺序,定义一个变量,每次加+1,每次开始编码的时候赋值为 0*/
        frameYUV->pts=pkt_index++;
        /*发送像素数据与接收压缩数据是配合使用的哦!合起来统称为编码
        参数 1:是编码器上下文对象;
        参数 2:用来保存压缩数据的 AVPacket
        返回值:读完或者出错
        */
        res=avcodec_receive_packet(codecContent,pkt);
        //没处理完
        if(res<0)
        {
            qDebug()<<"打包失败";
            return;
        }
        //否则的话,就可以写入一帧数据
        //设置一下 pkt 的流确保是视频流
        pkt->stream_index=0;
        /* 写入数据到视频信息结构体中
        * 参数 1:视频流的基本信息结构体(AVFormatContext);
        * 参数 2:压缩数据
        */
        av_interleaved_write_frame(formatContent,pkt);
    }
    qDebug()<<"write success";
    //这里的 pkt 使用完之后一定要记得重置
    av_packet_unref(pkt);
}

void coding::run()
{
    while(m_stop==false)
    {
        while(!video::YUVQueue.isEmpty())//如果队列不为空
        {
            qDebug()<<"出队列前大小"<<video::YUVQueue.size();
            this->codingFrame(video::YUVQueue.dequeue());//出队列
            qDebug()<<"出队列后大小"<<video::YUVQueue.size();
        }
    }
}

void coding::writeTailer()
{
    av_write_trailer(formatContent);
}

void coding::stop()
{
    m_stop=true;
}

coding::~coding()
{
    qDebug()<<"关闭所有编码组件";
    //关闭输入流
    avio_close(formatContent->pb);
    //释放视频信息
    avformat_free_context(formatContent);
}

编码完可以看到输出了.h264文件

 用ESEyE软件可进行播放

感谢观看!!!!

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

猜你喜欢

转载自blog.csdn.net/logani/article/details/127336149
今日推荐