简介
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;
}