简介

       QSV 全称:Quick Sync Video Acceleratio ,是Intel媒体开发库(The Intel® Media Software Development Kit)提供了一个对数字视频的通用解决方案,该解决方案支持多种图形平台(graphics platforms),实现了通用功能,能对数字视频进行预处理、编解码、以及不同编码格式的转换。

环境配置  

       1.安装intel media sdk ,官网下载后直接双击安装即可

       2.下载mfx源码:https://github.com/lu-zero/mfx_dispatch.git,下载完成后使用msys2进行编译,分别执行以下命令

autoreconf -i
./configure --prefix=/mingw64
make -j$(nproc) install

       3.下载ffmpeg源码 :https://github.com/FFmpeg/FFmpeg.git,下载完成后使用msys2进行编译,添加--enable-libmfx选项。设置PKG_CONFIG_PATH变量(默认安装的话  export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig)具体路径时编译intel media sdk 时生成的pkgconfig/libmfx.pc 所在位置

添加以下参数:

--enable-libmfx

  --enable-encoder=h264_qsv

  --enable-decoder=h264_qsv

(注意使用硬解码的时候应该关闭多路复解--disable-demuxers)

完整命令(prefix 指定安装目录,执行make install 后的结果会放在prefix目录下 工 bin 、include 、lib 、 share 4个文件夹,make - 8 指定多核编译):

./configure --enable-shared 
            --prefix=./build  
            --enable-libmfx 
            --enable-encoder=h264_qsv 
            --enable-decoder=h264_qsv 
            --disable-demuxers

make -j8 
make install
make clean

 

代码调用

//加入头文件,由于ffmpeg是C语言完成的,导入头文件要加入extern "C" {}
extern "C" {


#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavutil/time.h"

}
//准备ffmpeg解码

    av_register_all(); // 注册支持的文件格式及对应的codec
    avformat_network_init();
    //m_url 表示视频源,可以是rtsp地址,也可以是文件路径(文件路径不能存在中文)
    QByteArray byte = m_url.toLatin1();
    char *m_rtsp = byte.data();

   
    pFormatCtx = nullptr;
    options = NULL;
    firstFramePts = 0;
    packet = nullptr;
    qDebug()<<"start "<<m_rtsp;
    AVDictionary* options = NULL;
    // 设置连接方式
    av_dict_set(&options, "rtsp_transport", "tcp", 0);
    //设置buffer_size,提高画质,减少花屏现象
    av_dict_set(&options, "buffer_size", "2048000", 0);
    //如设置20s超时:
    av_dict_set(&options, "stimeout", "20000000", 0);
    //设置超时断开连接时间
    av_dict_set(&options, "max_delay", "500000", 0);
    av_dict_set(&options, "genpts", "10", 0);

    // 读取文件头,将格式相关信息存放在AVFormatContext结构体中
    if (avformat_open_input(&pFormatCtx, m_rtsp, nullptr, &options) != 0)
    {
        qDebug()<<"open input failed:"<<m_rtsp;
        if(pTimer)
        {
            pTimer->stop();
        }
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // 打开失败
    }

    // 检测文件的流信息

    QFFmpegThreadMutexManager::instance()->lock();
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
        QFFmpegThreadMutexManager::instance()->unlock();
        qDebug()<<"avformat_find_stream_info filed";
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // 没有检测到流信息 stream infomation
    }
    QFFmpegThreadMutexManager::instance()->unlock();

    // 在控制台输出文件信息
    av_dump_format(pFormatCtx, 0, m_rtsp, 0);

    //查找视频流 video stream
    videoStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1)
    {
        qDebug()<<"not fint video stream";
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // 没有找到视频流video stream
    }
    pCodecCtxOrg = nullptr;
    pCodecCtx = nullptr;
    pCodec = nullptr;
    pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
    // 找到video stream的 decoder
    QFFmpegThreadMutexManager::instance()->lock();
//    pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);//h264_qsv h264_dxva2
     pCodec = avcodec_find_decoder_by_name("h264_qsv");
    QFFmpegThreadMutexManager::instance()->unlock();
    if (!pCodec)
    {
        qDebug()<<"not find dcoder";
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ;
    }
    // 不直接使用从AVFormatContext得到的CodecContext,要复制一个
    pCodecCtx = avcodec_alloc_context3(pCodec);
    av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
    if (avcodec_copy_context(pCodecCtx, pCodecCtxOrg) != 0)
    {
        qDebug() << "Could not copy codec context!" ;
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ;
    }

    if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
    {
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // Could open codec
    }
    pFrame = nullptr;
    pFrameRGB = nullptr;

    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();
    // 使用的缓冲区的大小
    int numBytes = 0;
    buffer = nullptr;

    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);
    buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));

    avpicture_fill((AVPicture*)pFrameRGB, buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);

    qDebug()<<"pCodecCtx->pix_fmt:"<<pCodecCtx->pix_fmt;
    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, /* pCodecCtx->pix_fmt,*/ AV_PIX_FMT_NV12, //输入格式
                             pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BILINEAR, nullptr, nullptr, nullptr);

    packet = av_packet_alloc();



    // 拉流解码
    while(1)
    {
        QFFmpegThreadMutexManager::instance()->lock();//如果不加锁,多视频流时可能会导致内存崩溃
        int ret = av_read_frame(pFormatCtx, packet);
        QFFmpegThreadMutexManager::instance()->unlock();
        if(ret >= 0)
        {
             if (packet->stream_index == videoStream)
            {
                QFFmpegThreadMutexManager::instance()->lock();
                int frameFinished = 0;
                avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, packet);
                QFFmpegThreadMutexManager::instance()->unlock();
                //暂时没做显卡直接显示视频帧,所以拷贝到内存后显示
                if (frameFinished)
                {
                    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0,
                              pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
    
                    QImage img((uchar *)buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
    
                    if(!img.isNull())
                    {
                        emit sigShowFrame(img);
                    }else{
                        qDebug()<<"get img error";
                    }
                }
                else{
                    qDebug()<<"no output frame";
                }
            }
            else
            {
                if(player::PLAY_FILE == playType)
                    onReadFrame();
            }
            av_free_packet(packet);
    
        }
        else
        {
    
            av_packet_unref(packet);
            emit signalPlayStatus(player::ERROR);
            free_player();
            return break;
    
        }
    }

注意:ffmpeg使用qsv硬解码出来的视频帧格式是AV_PIX_FMT_NV12格式的,在调用sws_getContext函数时第三个参数必须强制传入AV_PIX_FMT_NV12,否则会导致视频数据转换rgb时失败(也是没做显卡显示,否则不会有这一步),此版本完成后的效果是可以运用GPU进行解码(从GPU-Z中集显得系统内存使用可以看出,GPU负载一直是0),但是CPU消耗反而更严重了,初步判定是从GPU向CPU拷贝数据占用了大量资源。

完整代码(代码是从项目中截取的,可能有些结构体或函数无效):

playerutils.hpp
#ifndef PLAYERUTILS_HPP
#define PLAYERUTILS_HPP
extern "C" {


#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavutil/time.h"

}
#include <QPushButton>
namespace MediaTool {
enum TOOLBTN{
    NOT,
    // rtsp
    RECORD_START,     //开始录制
    RECORD_STOP,      //结束录制
    SCREENSHOT,       //截图拍照
    CLOSE,            //关闭
    //file

    PLAY,              //播放
    STOP,              //停止
    PAUSE,             //暂停
    REPLAY,            //继续播放
    SPEEDON,           //加速播放
    SPEEDDOWN,         //减速播放
    SIGLEFRAME,        //单帧(下一帧)
    PLAYSEEK,          //跳转
};
struct ToolBtn{
    QPushButton* btn;
    bool isDoubleStatusBtn; // 是否是双态按钮
    int uuid0;
    QString text0;
    QString resource0;
    QString resourcePress0;
    // 普通按钮以下无效
    int uuid1;
    QString text1;
    QString resource1;
    QString resourcePress1;
    int selectID;

};
}
namespace player {

enum PlayType{
    NOT,
    PLAY_RTSP,   //rtsp直播流
    PLAY_FILE,   //file 回放

};
enum PlayStatus{  //播放状态
    PLAYING,
    FINISH,
    ERROR
 };
}
#endif // PLAYERUTILS_HPP

 QFFmpegThreadMutexManager.h

#ifndef QFFMPEGTHREADMUTEXMANAGER_H
#define QFFMPEGTHREADMUTEXMANAGER_H

#include <QObject>
#include <QMutex>
#include <QMutex>

/// \brief The QFFmpegThreadMutexManager class
///   单例模式
///   了解到ffmpeg 如果一个线程调用了avcodec_open(),
///   但还没有调用avcodec_close(),
///   此时再有一个线程来调用avcodec_open(),就可能会发生错误
///   需要给使用ffmpeg的地方使用同一个线程锁
///
class QFFmpegThreadMutexManager : public QObject
{
    Q_OBJECT
private:
    explicit QFFmpegThreadMutexManager();
    static QFFmpegThreadMutexManager* p;
public:
    ~QFFmpegThreadMutexManager();
    static QFFmpegThreadMutexManager* instance();
    void initMutex();
    void lock();
    void unlock();
    bool trylock();
    QMutex* getMutex();

private:
    QMutex* mutex;
};

#endif // QFFMPEGTHREADMUTEXMANAGER_H

 QFFmpegThreadMutexManager.cpp

#include "QFFmpegThreadMutexManager.h"

QFFmpegThreadMutexManager *QFFmpegThreadMutexManager:: p = new QFFmpegThreadMutexManager();
QFFmpegThreadMutexManager::QFFmpegThreadMutexManager()
{
    mutex = nullptr;
    initMutex();
}

QFFmpegThreadMutexManager::~QFFmpegThreadMutexManager()
{
    if(mutex)
        delete mutex;
}

QFFmpegThreadMutexManager *QFFmpegThreadMutexManager::instance()
{
    return p;
}

void QFFmpegThreadMutexManager::initMutex()
{
    if(!mutex)
        mutex = new QMutex;
}

void QFFmpegThreadMutexManager::lock()
{
    if(mutex)
        mutex->lock();
}

void QFFmpegThreadMutexManager::unlock()
{
    if(mutex)
        mutex->unlock();
}

bool QFFmpegThreadMutexManager::trylock()
{
    if(mutex)
        return mutex->tryLock();
    else
        return false;
}

QMutex *QFFmpegThreadMutexManager::getMutex()
{
    initMutex();
    return mutex;
}
FFmpegDecoder.h
#ifndef FFMPEGDECODERFILE_H
#define FFMPEGDECODERFILE_H

#include <QObject>
#include <QThread>
#include <QTimer>
#include <QMutex>
#include <QDateTime>
#include <QImage>
#include <QQueue>
#include <playerutils.hpp>
#include <QFFmpegThreadMutexManager.h>
#include <Recording.h>


#define  MAXFRAMEBUFFER 5 //最大缓存帧
typedef struct DecodeContext {
    AVBufferRef *hw_device_ref;
} DecodeContext;

class FFmpegDecoder : public QObject
{
    Q_OBJECT
public:
    explicit FFmpegDecoder(QObject *parent = 0);
    ~FFmpegDecoder();
    void SetUrl(QString url);
    void Start(); // 开始播放
    void setPlayType(player::PlayType type); //设置播放类型 rtsp/file
    // 仅对播放文件有效
    void Stop();  // 停止播放
    void Pause(); //暂停
    void Starting();// 用于暂停后的继续播放
    void SetSpeed(double speed);//设置播放速度
    void NextFrame();
    void Seek(int pts);//跳转


private slots:
    void slotSetUrl(QString url);
    void slotStartPlay();
    void slotStop();
    void slotSetPlayType(player::PlayType type);

    bool onReadFrame();
    void onPause(); //暂停
    void onStarting();// 用于暂停后的继续播放
    void onSetSpeed(double speed);//设置播放速度
    void onNextFrame();
    void onSeek(int pts);//跳转



signals:
    void signalTimerStart();
    void signalTimerStop();

    void signalSetUrl(QString);
    void signalStart();
    void signalSetPlayType(player::PlayType);
    void signalStop();  // 停止播放
    void signalPause(); //暂停
    void signalStarting();// 用于暂停后的继续播放
    void signalSetSpeed(double speed);//设置播放速度
    void signalNextFrame();
    void signalSeek(int pts);//跳转
    //解码出来的视频帧
    void sigShowFrame(QImage);

    //更新界面播放value 为播放位置 maxValue 为视频长度(默认为-1 表示播放过程中,不用设置)
    void signalPlayProgress(int value,int maxValue = -1); //
    void signalRecordFinished();//录制完成,界面刷新历史视频列表
    void signalPlayStatus(player::PlayStatus status); // 播放状态


private:
    void StartReadFrame();
    void free_player(); //释放播放器资源
    void saveH264Stream(AVPacket* pkg);//保存h264纯码流
    bool mkSavePath(QString path);// 根据传入文件名判断路径是否存在,不存在则创建

private:
    int videoControl(); //读取视频帧前每次去读取参数,实现视频控制(rtsp),返回0表示继续播放 返回1 表示结束播放
public:            // 播放rtsp视频时控制视频播放
    MediaTool::TOOLBTN ctrlParam; //控制视频参数
    player::PlayType playType;

    bool isRecord; // 是否录制
    QString recordFileName; //录制视频保存路径


private:
    QThread* pThread;
    QString         m_url;
    AVFormatContext* pFormatCtx;
    AVOutputFormat* pOutFormat;
    AVDictionary* options;
    AVCodecContext* pCodecCtxOrg;
    AVCodecContext* pCodecCtx;
    AVCodec* pCodec;
    AVFrame* pFrame;
    AVFrame* pFrameRGB;
    uint8_t* buffer;
    AVStream *pStream;
    AVPacket* packet;
    SwsContext* sws_ctx = nullptr;
    int videoStream;
    QTimer* pTimer;

    qint64 duration; //视频长度
    int firstFramePts; //第一帧时间

    double frameRate;
    int num = 0;
    Recording* pRecord;


};

#endif // FFmpegDecoderFile_H
FFmpegDecoder.cpp
#include "FFmpegDecoder.h"
#include <QDebug>
#include <QDir>

FFmpegDecoder::FFmpegDecoder(QObject *parent) :
    QObject(parent),
    playType(player::PLAY_RTSP),
    pFrameRGB(nullptr),
    pFrame(nullptr),
    packet(nullptr),
    pCodecCtx(nullptr),
    pCodecCtxOrg(nullptr),
    pFormatCtx(nullptr),
    pTimer(nullptr),
    pRecord(nullptr),
    isRecord(false),
    firstFramePts(0),
    pThread(new QThread)
{
    qRegisterMetaType<player::PlayStatus>("player::PlayStatus");
    qRegisterMetaType<player::PlayType>("player::PlayType");
    connect(this,&FFmpegDecoder::signalSetUrl,this,&FFmpegDecoder::slotSetUrl);
    connect(this,&FFmpegDecoder::signalStart,this,&FFmpegDecoder::slotStartPlay);
    connect(this,&FFmpegDecoder::signalSetPlayType,this,&FFmpegDecoder::slotSetPlayType);
    connect(this,&FFmpegDecoder::signalStop,this,&FFmpegDecoder::slotStop);
    connect(this,&FFmpegDecoder::signalPause,this,&FFmpegDecoder::onPause);
    connect(this,&FFmpegDecoder::signalStarting,this,&FFmpegDecoder::onStarting);
    connect(this,&FFmpegDecoder::signalSetSpeed,this,&FFmpegDecoder::onSetSpeed);
    connect(this,&FFmpegDecoder::signalNextFrame,this,&FFmpegDecoder::onNextFrame);
    connect(this,&FFmpegDecoder::signalSeek,this,&FFmpegDecoder::onSeek);
    this->moveToThread(pThread);
    pThread->start();
}

FFmpegDecoder::~FFmpegDecoder()
{

    free_player();
    if(pTimer)
    {
        delete pTimer;
        pTimer = nullptr;
    }

    pThread->quit();
    pThread->wait();
    delete pThread;
}

void FFmpegDecoder::SetUrl(QString url)
{
    emit  signalSetUrl(url);
}

void FFmpegDecoder::Start()
{
    emit signalStart();
}

void FFmpegDecoder::setPlayType(player::PlayType type)
{
    emit signalSetPlayType(type);
}

void FFmpegDecoder::Stop()
{
    emit signalStop();
}

void FFmpegDecoder::Pause()
{
    emit signalPause();
}

void FFmpegDecoder::Starting()
{
    emit signalStarting();
}

void FFmpegDecoder::SetSpeed(double speed)
{
    emit signalSetSpeed(speed);
}

void FFmpegDecoder::NextFrame()
{
    emit signalNextFrame();
}

void FFmpegDecoder::Seek(int pts)
{
    emit signalSeek(pts);
}


void FFmpegDecoder::slotSetUrl(QString url)
{
    m_url = url;
}

void FFmpegDecoder::slotStartPlay()
{


    av_register_all(); // 注册支持的文件格式及对应的codec
    avformat_network_init();

    QByteArray byte = m_url.toLatin1();
    char *m_rtsp = byte.data();

    // 打开audio文件
    pFormatCtx = nullptr;
    options = NULL;
    firstFramePts = 0;
    packet = nullptr;
    qDebug()<<"start "<<m_rtsp;
    AVDictionary* options = NULL;
    // 设置连接方式
    av_dict_set(&options, "rtsp_transport", "tcp", 0);
    //设置buffer_size,提高画质,减少花屏现象
    av_dict_set(&options, "buffer_size", "2048000", 0);
    //如设置20s超时:
    av_dict_set(&options, "stimeout", "20000000", 0);
    //设置超时断开连接时间
    av_dict_set(&options, "max_delay", "500000", 0);
    av_dict_set(&options, "genpts", "10", 0);

    // 读取文件头,将格式相关信息存放在AVFormatContext结构体中
    if (avformat_open_input(&pFormatCtx, m_rtsp, nullptr, &options) != 0)
    {
        qDebug()<<"open input failed:"<<m_rtsp;
        if(pTimer)
        {
            pTimer->stop();
        }
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // 打开失败
    }

    // 检测文件的流信息

    QFFmpegThreadMutexManager::instance()->lock();
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
        QFFmpegThreadMutexManager::instance()->unlock();
        qDebug()<<"avformat_find_stream_info filed";
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // 没有检测到流信息 stream infomation
    }
    QFFmpegThreadMutexManager::instance()->unlock();

    // 在控制台输出文件信息
    av_dump_format(pFormatCtx, 0, m_rtsp, 0);

    //查找视频流 video stream
    videoStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1)
    {
        qDebug()<<"not fint video stream";
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // 没有找到视频流video stream
    }
    pCodecCtxOrg = nullptr;
    pCodecCtx = nullptr;
    pCodec = nullptr;
    pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
    // 找到video stream的 decoder
    QFFmpegThreadMutexManager::instance()->lock();
//     pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);
     pCodec = avcodec_find_decoder_by_name("h264_qsv");

    QFFmpegThreadMutexManager::instance()->unlock();
    if (!pCodec)
    {
        qDebug()<<"not find dcoder";
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ;
    }
    // 不直接使用从AVFormatContext得到的CodecContext,要复制一个
    pCodecCtx = avcodec_alloc_context3(pCodec);
    av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
    if (avcodec_copy_context(pCodecCtx, pCodecCtxOrg) != 0)
    {
        qDebug() << "Could not copy codec context!" ;
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ;
    }

    if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
    {
        emit signalPlayStatus(player::ERROR);
        free_player();
        return ; // Could open codec
    }
    pFrame = nullptr;
    pFrameRGB = nullptr;

    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();
    // 使用的缓冲区的大小
    int numBytes = 0;
    buffer = nullptr;

    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);
    buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));

    avpicture_fill((AVPicture*)pFrameRGB, buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height);

    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_NV12,
                             pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BILINEAR, nullptr, nullptr, nullptr);

    packet = av_packet_alloc();
    av_read_play(pFormatCtx);
    //    AVStream *stream=pFormatCtx->streams[videoStream];
    //    qDebug()<<"pts:"<<stream->codec->framerate.num<<stream->codec->framerate.den;
    pStream=pFormatCtx->streams[videoStream];
    printf("---------------- File Information ---------------\n");
    av_dump_format(pFormatCtx, 0, m_rtsp, 0);
    printf("-------------------------------------------------\n");

    StartReadFrame();

}

void FFmpegDecoder::slotStop()
{
    if(player::PLAY_FILE == playType)
    {
        emit signalTimerStop();
        emit signalPlayStatus(player::FINISH);
        free_player();
    }
}

void FFmpegDecoder::slotSetPlayType(player::PlayType type)
{
    playType = type;
}

bool FFmpegDecoder::onReadFrame()
{
    if(!pFormatCtx || !packet || !pCodecCtx || !pFrameRGB || !pFrame)  return false;
    QFFmpegThreadMutexManager::instance()->lock();
    int ret = av_read_frame(pFormatCtx, packet);
    QFFmpegThreadMutexManager::instance()->unlock();
    if(ret >= 0)
    {
        if(isRecord)
        {
            AVPacket* pkt = av_packet_clone(packet);
            saveH264Stream(pkt);
        }else{
            if(pRecord)
            {
                qDebug()<<"finished record!";
                pRecord->Finished();
                QThread::msleep(10);
                delete pRecord;
                pRecord = nullptr;
                isRecord = false;
                emit signalRecordFinished();
            }
        }
        if (packet->stream_index == videoStream)
        {
            if(player::PLAY_FILE == playType)
            {
                int pts = packet->pts * av_q2d(pStream->time_base) * 1000;
                if(firstFramePts == 0)
                {
                    firstFramePts = pts;
                 }
                 // 发送播放进度
                emit signalPlayProgress(pts-firstFramePts);
            }
            //保存
            QFFmpegThreadMutexManager::instance()->lock();
            int frameFinished = 0;

             avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, packet);
            QFFmpegThreadMutexManager::instance()->unlock();
            if (frameFinished)
            {
 
                sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0,
                          pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

                QImage img((uchar *)buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);

                if(!img.isNull())
                {
                    emit sigShowFrame(img);
                }else{
                    qDebug()<<"get img error";
                }
            }
            else{
                qDebug()<<"no output frame";
            }
        }
        else
        {
            if(player::PLAY_FILE == playType)
                onReadFrame();
        }
        av_free_packet(packet);

    }
    else
    {

        qDebug()<<"play error..."<<playType;
        if(player::PLAY_FILE == playType)
        {
            emit signalTimerStop();
        }

        av_packet_unref(packet);
        emit signalPlayStatus(player::ERROR);
        free_player();
        return false;

    }
    return true;
}

void FFmpegDecoder::onPause()
{
    emit signalTimerStop();
}

void FFmpegDecoder::onStarting()
{
    emit signalTimerStart();
}

void FFmpegDecoder::onSetSpeed(double speed)
{
    onPause();

    if(pTimer)
    {
        pTimer->setInterval((1000.0/frameRate)/speed);
    }

    onStarting();
}

void FFmpegDecoder::onNextFrame()
{
    onPause();
    onReadFrame();
}

void FFmpegDecoder::onSeek(int pts)
{
    //    av_seek_frame().
    qDebug()<<"seek:"<<pts;
    int ret = av_seek_frame(pFormatCtx,-1,(int64_t)pts*1000 ,AVSEEK_FLAG_BACKWARD);

}



void FFmpegDecoder::StartReadFrame()
{

    emit signalPlayStatus(player::PLAYING);
    if(player::PLAY_FILE == playType)
    {
        //定时器的方式读流


        qDebug()<<"video duration:"<<pFormatCtx->duration;
        emit signalPlayProgress(0,pFormatCtx->duration/1000);

        double tmp = (double)pStream->avg_frame_rate.num/(double)pStream->avg_frame_rate.den;

        if(tmp >0)
        {
            frameRate = tmp;
        }
        qDebug()<<"frameRate:"<<frameRate;

        if(pTimer)
        {
            delete pTimer;
            pTimer= nullptr;
        }

        pTimer = new QTimer;
        connect(pTimer,SIGNAL(timeout()),this,SLOT(onReadFrame()),Qt::QueuedConnection);
        connect(this,SIGNAL(signalTimerStart()),pTimer,SLOT(start()),Qt::QueuedConnection);
        connect(this,SIGNAL(signalTimerStop()),pTimer,SLOT(stop()),Qt::QueuedConnection);

        pTimer->setTimerType(Qt::PreciseTimer);
        pTimer->setInterval(1000/frameRate);

        emit signalTimerStart();

    }else{
        // 迭代的方式读流

        ctrlParam = MediaTool::NOT;
        while(1)
        {
            if(!onReadFrame()) break;
            if(1 == videoControl())
            {

                break;
            }

        }
        qDebug()<<"play finish;";
        emit signalPlayStatus(player::FINISH);
        free_player();
    }
}
void FFmpegDecoder::free_player()
{

    if(pRecord)
    {
        pRecord->Finished();
        delete pRecord;
        pRecord = nullptr;
        isRecord = false;
    }
    qDebug()<<"free_player";
    QFFmpegThreadMutexManager::instance()->lock();
    if(pFrameRGB)
        av_frame_free(&pFrameRGB);
    if(pFrame)
        av_frame_free(&pFrame);
    if(packet)
        av_packet_free(&packet);
    if(pCodecCtx)
        avcodec_close(pCodecCtx);
    if(pCodecCtxOrg)
        avcodec_close(pCodecCtxOrg);
    if(pFormatCtx)
        avformat_close_input(&pFormatCtx);


    pFrameRGB = nullptr;
    pFrame = nullptr;
    packet = nullptr;
    pCodecCtx = nullptr;
    pCodecCtxOrg = nullptr;
    pFormatCtx = nullptr;
    QFFmpegThreadMutexManager::instance()->unlock();
    qDebug()<<"free_player finish";

}

void FFmpegDecoder::saveH264Stream(AVPacket* pkg)
{
    if(!pRecord)
    {
        pRecord = new Recording;
        qDebug()<<"start save video";
        if(!mkSavePath(recordFileName))
        {
            delete pRecord;
            pRecord = nullptr;
            return;
        }
        pRecord->Start(pFormatCtx,recordFileName);
    }
    pRecord->AddPacket(pkg);
}

bool FFmpegDecoder::mkSavePath(QString  path)
{
    QString dirPath = path.left(path.lastIndexOf("/"));
    QString realPath;
    if(dirPath.left(1) == ".")
    {
        QStringList lists = (QDir::currentPath()+"/"+dirPath).split("/");
        while(1)
        {
            for(int i=0;i<lists.size();i++)
            {
                if(lists.at(i) == ".." && i > 0)
                {
                    lists.removeAt(i);
                    lists.removeAt(i-1);
                }
            }
            bool flag = false;
            for(int i=0;i<lists.size();i++)
            {
                if(lists.at(i) == "..")
                    flag = true;
            }
            if(!flag)
                break;
        }
        for(int i=0;i<lists.size();i++)
        {
            if(i != lists.size()-1)
                realPath += lists.at(i)+"/";
            else
                realPath += lists.at(i);
        }
    }else{
        realPath = dirPath;
    }
    QDir dir(realPath);
    if(!dir.exists())
    {
        if(!dir.mkpath(realPath))
        {
            return false;
        }else
            return true;
    }
    return true;
}

int FFmpegDecoder::videoControl()
{
    int ret = 0;
    switch(ctrlParam){
    case MediaTool::NOT:
        ret = 0;
        break;
    case MediaTool::RECORD_START:
        ret = 0;
        break;
    case MediaTool::RECORD_STOP:
        ret = 0;
        break;
    case MediaTool::CLOSE:
        ret = 1;
        break;
    }
    ctrlParam = MediaTool::NOT;
    return ret;
}