Qt与FFmpeg联合开发指南(四)-- 封装解码音视频线程

代码封装实际是一个见仁见智的工作,可能不同的人对代码结构的理解不同,实现的封装方式也会存在差异。

为什么用线程封装解码音视频?

在使用QT做窗体程序时有一些占用时间较长的函数在运行时会使QT的窗体控件无法得到响应,也就是常说的程序假死,其实程序还是在运行的,只是你得不到反馈而已,这种情况,可以使用线程来把运行时间长的函数与窗体主线成分开了。

线程类的创建

 以下是线程类的代码:

class DecodeThread : public QThread
{
public:
    DecodeThread();
    void run();
    void stop();

    bool m_stop;
};

 使用线程

怎么使用线程,具体的看代码注释 

DecodeThread::DecodeThread()
{
    this->m_stop = false;
}
void DecodeThread::run()
{
    qDebug() << "线程开始";
    while(!m_stop)
    {
        //线程处理函数
        for(int i = 0; i < 1000; i++)
        {
           qDebug() << i ;
        }
        sleep(1000);
    }
    qDebug() << "线程结束";
}

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

 读者可根据音视频解码流程进行封装函数。经分析,第六步:循环读取视频帧,进行循环解码 操作应放入线程执行函数中进行调用。偷个懒我把前面五个步骤统一封装成一个函数,把音视频参数分别放入对应结构体进行管理,以视频解码线程封装伟例子进行分析:

  • 第一步:注册所有组件
    第二步:打开视频输入文件
    第三步:查找视频文件信息
    第四步:查找解码器
    第五步:打开解码器
    第六步:循环读取视频帧,进行循环解码
    第七步:关闭解码组件
    

 视频参数结构体

struct VideoState{
    QString cintFilePath;                  //输入文件
    QString coutFilePath;                  //输出文件
    AVFormatContext *pFormatContext;      //封装格式上下文      
    AVStream *pvideoStream;               //视频流
    AVCodecContext *pCodecContext;       //视频解码器上下文
    AVCodec *pCodec;                      //解码器

    int av_stream_index;

    AVPacket* pPacket;
    AVFrame* pFramein;
    AVFrame* pFrameyuv420p;
    uint8_t *pOutbuffer;
    SwsContext* pSwsContext; //转码视频
    FILE* pyuvfp;

};

视频解码线程类结构如下

class VideoDecoding : public QThread
{
public:
    VideoDecoding(QString &cinfilename);
    ~VideoDecoding();
    bool createVideoState(QString &cinfilename);
    void run();
    void stop();

private:
    VideoState *pVideoState;
    int ret;
    bool m_stop;
};

部分核心代码如下:

1、创建解码视频对象操作

//创建视频对象
bool VideoDecoding::createVideoState(QString &cinfilename)
{

    qDebug()<<"第一步:注册所有组件";
    av_register_all();

    qDebug()<<"第二步:打开视频输入文件";
    pVideoState->pFormatContext = avformat_alloc_context();
    ret = avformat_open_input(&pVideoState->pFormatContext,cinfilename.toStdString().c_str(),NULL,NULL);
    if (ret != 0)
    {
        return false;
    }

    qDebug()<<"第三步:查找视频文件信息";
    ret = avformat_find_stream_info(pVideoState->pFormatContext, NULL);
    if (ret < 0)
    {
        return false;
    }
    qDebug()<<"第四步:查找解码器";
    int av_stream_index = -1;
    for (int i = 0; i < pVideoState->pFormatContext->nb_streams; ++i) {
        //循环遍历每一流
        //视频流、音频流、字幕流等等...
        if (pVideoState->pFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            //找到了
            av_stream_index = i;
            break;
        }
    }
    if (av_stream_index == -1 )
    {
        qDebug()<查找到视频解码器上下文->视频压缩数据
    pVideoState->pCodecContext = pVideoState->pFormatContext->streams[av_stream_index]->codec;

    //第三点:根据解码器上下文->获取解码器ID
    pVideoState->pCodec = avcodec_find_decoder(pVideoState->pCodecContext->codec_id);
    if (pVideoState->pCodec == NULL)
    {
        qDebug()<pCodecContext,pVideoState->pCodec,NULL);
    if (ret != 0)
    {
        return false;
    }
    //输出视频信息
    //输出:文件格式
    qDebug()<pFormatContext->iformat->name);
    //输出:解码器名称
    qDebug()<pCodec->name);
    qDebug()<pCodecContext->width).arg(pVideoState->pCodecContext->height);

    //此函数打印输入或输出的详细信息
    av_dump_format(pVideoState->pFormatContext, 0, cinfilename.toStdString().c_str(), 0);

    return true;
}

2、解码操作

void VideoDecoding::run()
{
    qDebug() << "线程开始";
    char savePath[32];
    QDateTime current_date_time =QDateTime::currentDateTime();
    QString current_date =current_date_time.toString("yyyyMMddhh");
    sprintf(savePath,"%s.yuv",current_date.toStdString().c_str());

    pVideoState->pyuvfp = fopen(savePath,"wb+");
    if (pVideoState->pyuvfp == NULL)
    {
        qDebug()<输出YUV420P视频->格式:yuv格式
        qDebug()<<"第六步:循环读取视频帧,进行循环解码";
        //准备读取
        //AVPacket用于存储一帧一帧的压缩数据(H264)
        //缓冲区,开辟空间
        pVideoState->pPacket = (AVPacket*)av_malloc(sizeof(AVPacket));

        //输入->环境一帧数据->缓冲区->类似于一张图
        pVideoState->pFramein = av_frame_alloc();
        //输出->帧数据->视频像素数据格式->yuv420p
        pVideoState->pFrameyuv420p = av_frame_alloc();
        //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
        //缓冲区分配内存
        pVideoState->pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pVideoState->pCodecContext->width, pVideoState->pCodecContext->height));
        //初始化缓冲区
        avpicture_fill((AVPicture *)pVideoState->pFrameyuv420p, pVideoState->pOutbuffer, AV_PIX_FMT_YUV420P, pVideoState->pCodecContext->width, pVideoState->pCodecContext->height);

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

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

        //>=0:说明有数据,继续读取
        //<0:说明读取完毕,结束
        while (av_read_frame(pVideoState->pFormatContext,pVideoState->pPacket) >= 0){
            //解码什么类型流(视频流、音频流、字幕流等等...)
            if (pVideoState->pPacket->stream_index == pVideoState->av_stream_index){

                //新的API操作
                //发送一帧数据->接收一帧数据
                //发送一帧数据
                avcodec_send_packet(pVideoState->pCodecContext, pVideoState->pPacket);

                //接收一帧数据->解码一帧
                ret = avcodec_receive_frame(pVideoState->pCodecContext, pVideoState->pFramein);

                //解码出来的每一帧数据成功之后,将每一帧数据保存为YUV420格式文件类型(.yuv文件格式)
                if ( ret == 0 ){
                    //sws_scale:作用将视频像素数据格式->yuv420p格式
                    //输出.yuv文件->视频像素数据格式文件->输出到文件API
                    //参数一:视频像素数据格式->上下文
                    //参数二:输入数据
                    //参数三:输入画面每一行的大小
                    //参数四:输入画面每一行的要转码的开始位置
                    //参数五:每一帧数据高
                    //参数六:输出画面数据
                    //参数七:输出画面每一行的大小
                    sws_scale(pVideoState->pSwsContext,
                              (const uint8_t *const*)pVideoState->pFramein->data,
                              pVideoState->pFramein->linesize,
                              0,
                              pVideoState->pCodecContext->height,
                              pVideoState->pFrameyuv420p->data,
                              pVideoState->pFrameyuv420p->linesize);


                    //Y代表:亮度
                    //UV代表:色度
                    //第二点:分析yuv420规则->计算机图像原理 ->直播技术
                    //yuv420规则一:Y结构表示一个像素点
                    //yuv420规则二:四个Y对应一个U和一个V(也就是四个像素点,对应一个U和一个V)
                    //第三点:分析Y和U、V大小计算原理
                    // y = 宽 * 高
                    // u = y / 4
                    // v = y / 4
                    y_size = pVideoState->pCodecContext->width * pVideoState->pCodecContext->height;
                    u_size = y_size / 4;
                    v_size = y_size / 4;

                    //第四点:写入文件
                    //写入->Y
                    //pVideoState->pFramein->data[0]:表示Y
                    fwrite(pVideoState->pFramein->data[0], 1, y_size, pVideoState->pyuvfp);
                    //写入->U
                    //pVideoState->pFramein->data[1]:表示U
                    fwrite(pVideoState->pFramein->data[1], 1, u_size, pVideoState->pyuvfp);
                    //写入->V
                    //pVideoState->pFramein->data[2]:表示V
                    fwrite(pVideoState->pFramein->data[2], 1, v_size, pVideoState->pyuvfp);

                    current_frame_index++;
                    qDebug()<pPacket);
    //关闭流
    fclose(pVideoState->pyuvfp);
    av_frame_free(&pVideoState->pFramein);
    av_frame_free(&pVideoState->pFrameyuv420p);
    avcodec_close(pVideoState->pCodecContext);
    avformat_free_context(pVideoState->pFormatContext);
    qDebug() << "线程结束";
}

 

你可能感兴趣的:(ffmpeg,音视频编解码,ffmpeg)