0
点赞
收藏
分享

微信扫一扫

Qt结合FFmpeg编码像素数据(yuv)生成码流数据(h264)

前端王祖蓝 2022-01-31 阅读 122

本文为像素数据(yuv)编码生成码流数据(h264)的代码实现。

 

目录

1、编码流程分析

2、FFmpeg相关编码函数

3、创建一个类专门用来编码

.h文件

构造函数

初始化

开始编码

写入尾巴帧

4、如何获取yuv像素数据

方案一

方案二


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码流数据文件。

 

举报

相关推荐

0 条评论