本文为像素数据(yuv)编码生成码流数据(h264)的代码实现。
目录
1、编码流程分析
/*编码流程分析:
* 1、注册
* 2、猜测使用什么样子的编码器 
* 3、查找需要的编码器,是否存在
* 4、如果存在,打开编码器,进行编码
* 5、打开文件流,新建一个视频流,用来保存我们编码得到的码流数据
* 6、如何给视频流保存数据呢?
* 7、像素数据可以使用视频解码得到的像素数据
* 8、解码——得到yuv像素数据——然后进行编码
* 9、得到了码流数据
* 10、写入对象的文件中
* */2、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():关闭流
3、创建一个类专门用来编码
.h文件
#ifndef FCODE_H
#define FCODE_H
#include <QObject>
extern "C"
{
    #include <libavcodec/avcodec.h>
    #include <libavdevice/avdevice.h>
    #include <libavformat/avformat.h>
    #include <libavutil/avconfig.h>
    #include <libswscale/swscale.h>
    #include <libswresample/swresample.h>
    #include <libpostproc/postprocess.h>
}
class fCode
{
    Q_OBJECT
public:
    fCode();
    void codecInit(int width, int height);//初始化
    void codecFrame(AVFrame *frame);//编码
    void writeEndFrame();//写入结束帧
private:
    AVOutputFormat * avoutput_format;
    AVFormatContext* avformat_context;//封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
    AVCodecContext* avcodec_context;//编码器上下文结构体,保存了视频(音频)编解码相关信息
    AVCodec* avcodec;//每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
    AVStream *newStream;//新建视频流
    AVPacket *pkt;//保存一帧码流数据
    int pkt_index;//表示帧的顺序
};
#endif // FCODE_H构造函数
fCode::fCode()
{
    //注册组件
    av_register_all();
    qDebug()<<"————注册组件————";
    qDebug()<<"注册成功!";
}初始化
void fCode::codecInit(int width, int height)
{
    //width和height为每一帧图片的宽高
    QDate date = QDateTime::currentDateTime().date();
    QTime time = QTime::currentTime();
    QString file_out = QString("%1_%2_%3_%4_%5.h264").arg(date.year()).arg(date.month()).
            arg(date.day()).arg(time.hour()).arg(time.minute());
    //猜测    file_out 为输出h264文件的文件名
    avoutput_format = av_guess_format(nullptr, file_out.toStdString().c_str(), nullptr);
    //判断有没有匹配到格式
    if(avoutput_format == nullptr)
    {
        qDebug()<<"没有匹配到!";
        return;
    }
    //打开编码器之前要做设置输出格式
    //用来保存视频相关信息,有输入和输出的相关信息
    avformat_context = avformat_alloc_context();
    avformat_context->oformat = avoutput_format;
    //打开视频流
    /*
    参数一:AVIOContent:输入输出上下文对象
    参数二:文件流的路径
    参数三:文件打开的方式,以写入的方式打开
    @return >= 0 in case of success, a negative value corresponding to an
    */
    int res = avio_open(&avformat_context->pb, file_out.toStdString().c_str(), AVIO_FLAG_WRITE);
    if(res < 0)
    {
        qDebug()<<"avio_open error!";
        return;
    }
    //新建视频流 参数一:保存视频信息的结构体
    newStream = avformat_new_stream(avformat_context, nullptr);
    if(newStream == nullptr)
    {
        qDebug()<<"新建视频流失败!";
        return;
    }
    //设置编码器的相关参数
    avcodec_context = newStream->codec;//保存视频流对应的AVCodecContext信息
    //其他参数设置
    avcodec_context->width = width;
    avcodec_context->height = height;
    //帧率设置
    avcodec_context->time_base = {1, 25};//一秒25帧
    //码率设置:每一秒存多少比特
    avcodec_context->bit_rate = 400000;//4后面5个0
    //设置显示的率
    avcodec_context->framerate = {25, 1};
    //每一组有几张图片 IPB帧
    avcodec_context->gop_size = 10;
    //量化参数设置(影响视频清晰度) 越小越清晰
    avcodec_context->qmax = 51;
    avcodec_context->qmin = 10;
    //B帧为0
    avcodec_context->max_b_frames = 0;
    //编码格式设置
    avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
    //流的设置
    avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
    //编码器id的设置
    avcodec_context->codec_id = avoutput_format->video_codec;
    //查找对应的编码器,打开编码器 @return An encoder if one was found, NULL otherwise.
    avcodec = avcodec_find_encoder(avcodec_context->codec_id);
    if(avcodec == nullptr)
    {
        qDebug()<<"没有对应的编码器!";
        return;
    }
    //打开编码器  @return zero on success, a negative value on error
    res = avcodec_open2(avcodec_context, avcodec, nullptr);
    if(res != 0)
    {
        qDebug()<<"打开编码器失败!";
        return;
    }
    //进行编码,数据写入文件中
    //写入编码的头部信息
    res = avformat_write_header(avformat_context, nullptr);
    if(res < 0)
    {
        qDebug()<<"文件头初始化失败!";
        return;
    }
    pkt = av_packet_alloc();//初始化开空间
    pkt_index = 0;//初始化为第0帧
    qDebug()<<"初始化成功!";
    
}
开始编码
void fCode::codecFrame(AVFrame *frame)
{
    qDebug()<<"————开始编码————";
    //编码:发送像素数据到编码器上下文结构体中 @return 0 on success, otherwise negative error code:
    int res = avcodec_send_frame(avcodec_context, frame);
    if(res < 0)
    {
        qDebug()<<"发送像素数据失败!";
        return;
    }
    //发送成功
    while(res >= 0)
    {
        frame->pts = pkt_index;//显示时间基,保证视频播放顺序的准确性
        pkt_index++;
        res = avcodec_receive_packet(avcodec_context, pkt);
        if(res < 0)//打包失败
        {
            qDebug()<<"打包失败!";
            return;
        }
        pkt->stream_index = 0;//0表示视频流
        //码流数据写入文件
        av_interleaved_write_frame(avformat_context, pkt);
        //清空
        av_packet_unref(pkt);
    }
}写入尾巴帧
void fCode::writeEndFrame()
{
    av_write_trailer(avformat_context);
    qDebug()<<"写入尾帧成功!";
}编码用到的yuv像素数据可以从视频解码那边获取,如下图所示的out_frame
循环读取每一帧并生成.h264、.yuv文件

4、如何获取yuv像素数据
方案一
在解码类内添加一个编码类对象的数据成员,在解码的构造进行一系列的初始化。
//假设解码类有个编码类对象的数据成员    fCode *code;
code = new fCode;
code.codecInit(int width, int height);//宽高自己设定
//然后在读取每一帧的时候将每一帧的yuv像素数据进行编码
code.codecFrame(out_frame);//yuv像素数据编码
//全部像素数据都编码完成以后,最后写入尾巴帧
code.writeEndFrame();//写入结束帧方案二
采用信号与槽的方式,解码得到一帧yuv像素数据便发送一次信号,在另一个窗口新建槽,然后连接信号与槽。
解码类的.h添加如下信号
signals:
    //将解码得到的一帧yuv像素数据进行发送
    
    void sendFrame(AVFrame *frame);解码一帧便发送一次信号
emit sendFrame(out_frame);另一个窗口.h添加如下槽
public slots:
    //接收信号发送过来的yuv像素数据
    void receiveFrame(AVFrame *frame);信号连接槽
//创建解码对象
decode = new fDecode;
//连接信号与槽
connect(decode, SIGNAL(sendFrame(AVFrame*)), this, SLOT(receiveFrame(AVFrame*)));槽函数实现
void playWidget::receiveFrame(AVFrame *frame)
{
    this->frame = frame;
    code->codecFrame(frame);
}记得写入尾巴帧。
一系列操作之后在当前目录下会生成.h264码流数据文件。











