QT:window 5.7.0版本
FFmpeg: ffmpeg-20200522-38490cb-win32-dev
注意:这里下载 32位dev版本,要和编译器对应(我的mingw是32位的)
在FFmpeg的解压目录里
- 将 include/ 的头文件拷贝到自己的项目的工程路径下
- 从 lib/ 拷贝所需要的库到自己的项目里
#这里是我所用到的库和 pro里的配置
//avcodec-58.dll
//avdevice-58.dll
//avfilter-7.dll
//avformat-58.dll
//avutil-56.dll
//potproc-55.dll
//swresample-3.dll
//swscale-5.dll
INCLUDEPATH += $$PWD/include/
LIBS += -L$$PWD/lib -lavutil-56 -lavformat-58 -lavcodec-58 -lavdevice-58 -lavfilter-7 -lpostproc-55 -lswresample-3 -lswscale-5
#到这里配置就完成了
源码链接: https://github.com/AutoCatFuuuu/QT/tree/master/ffmpeg
- 音视频播放
- 调节音量,跳转进度
- 支持弹幕功能
[AFFmpeg类] 音视频操作功能封装
操作流程:
- open();
打开音视频文件
读取音视频流
读取音视频的解码器
初始化音视频的缓存区
获取音视频的时长,时基- readVideo(); readAudio();
解码音视频流- getFrame(); getAudio();
读取解码后的音视频流数据- seek();
跳转音视频流- getDeviation();
计算当前音视频帧的时间差
===========================================
在open()成功之后开始两个定时器分别执行
<1> readVideo() getFrame() [视频]
<2> readAudio() getAudio() [音频]
即可。
#ifndef AFFMPEG_H
#define AFFMPEG_H
#ifdef __cplusplus
extern "C"{
#include
#include
#include "libswresample/swresample.h"
#include
#include
#include
}
#endif
#include "afilter.h"
class AFFmpeg
{
public:
AFFmpeg();
~AFFmpeg();
bool open(const char* filepath,bool isaudio = true);//打开文件
int readVideo(); //读取视频
int readAudio(); //读取音频
int seek(float pos); //跳转
AVFrame* getFrame(){ return pVideoFrameRGB;} //读取图像
int getAudio(char **buf); //读取音频
int getDuration(){ return duration;} //获取总时长
double getCDuration(){ return cduration;} //获取当前时长
int getFPS() { return fps.num;} //获取帧率
double getDeviation(); //获取当前视频和音频的时差
void setAFilterOpen() { isAFilteropen = !isAFilteropen; } //弹幕开关
private:
//视频相关
AVFormatContext *pFormatContext; //文件内容相关信息
AVCodecContext *pVideoCodecContext;//编码信息
AVCodec *pVideoCodec; //解码器
AVFrame *pVideoFrame; //一帧视频 源
AVFrame *pVideoFrameRGB; //一帧视频 RGB格式
AVRational fps; //帧率
struct SwsContext *pSwsContext; //转换格式用的结构体
unsigned char *out_buffer; //数据流初始化用的
int videoindex; //视频流索引
AVPacket *pVCPacket; //当前视频解码包
AVRational Vrational; //视频时间基准
//音频相关
AVFormatContext *pAFormatContext; //文件内容相关信息
AVCodecContext *pAudioCodecContext;//编码信息
AVCodec *pAudioCodec; //解码器
AVFrame *pAudioFrame; //一帧音频
struct SwrContext *pSwrContext; //转换格式用的结构体
uint8_t *pAudioBuffer; //音频输出数据
int ABufLen; //pAudioBuffer的长度
int audioindex; //音频流索引
AVPacket *pACPacket; //当前音频解码包
AVRational Arational; //音频时间基准
//字幕相关 加弹幕不是很可靠 20条的时候CPU占用太高了 大概是方法不对吧
AFilter *pAFilter; //字幕类
AVFrame *pFilterFrame; //输出帧
bool isAFilteropen; //弹幕开关
int speed; //弹幕移动速度
//==================================================
bool isopen; //打开标志
bool isstart; //开始解码标志
bool isaudio; //开启音频
int duration; //时长
double cduration; //当前时长
};
#endif // AFFMPEG_H
原本是用FFmpeg的avfilter模块来实现的,但是太卡了 【不是说FFmpeg 这功能做的不行 是因为我没找对方法吧。功能我也保留了 封装在【AFilter类】中
=================================================================
让我们来看看另外一种方法吧
- 使用xml存储弹幕信息 【参考了B站的弹幕】
PS: 无非就是一些增删查改的操作啦- 使用 QPainter 绘制 【在展示窗体里实现】
实现思路:
- 每秒更新一次数据 读取当前一秒内的所有弹幕
PS:用double类型去更新太不准了 而且更新太频繁还是不要吧- 每帧视频更新时更新弹幕的显示坐标 达到滚动的效果
PS: 滚动步进可以根据实际显示宽度自行计算- 删除 第 n 秒前的弹幕数据 滚动完毕的数据清楚掉
PS: n 值可以自行确定- 跳转视频的时候 清空原来的数据再重新获取
#ifndef DANMUKU_H
#define DANMUKU_H
#include
#include
#include
struct DanMuData{
double index; //时间戳索引
int arg[4];
// int type; //弹幕类型 固定位置:滚动 0:1
// int time; //滚动弹幕实时位置
// int x; //X轴
// int y; //Y轴
QStringList list;
// int color; //颜色 rgb值
// char text[256]; //内容
DanMuData *next;
};
class DanMuKu
{
public:
DanMuKu();
~DanMuKu();
void open(); //打开文件
void init(DanMuData **data); //初始化结构体
void read(int timestamp); //读取数据
void insert(DanMuData *data); //插入数据
int insertFile(DanMuData data); //插入数据
void update(double time,int step=1); //更新数据
void del(double timestamp); //删除数据
void clear(); //清空数据
void show(); //打印数据
DanMuData* get(){ return head->next;} //获取数据
private:
QFile *file; //弹幕文件
DanMuData *head; //数据
};
#endif // DANMUKU_H
我把QT播放音频的功能封装 和展示窗体封装再同一个文件了 【XAudio类】
流程:
- 开启两定时器 去解码音视频
#一个在绘制界面
#一个在写入音频数据- 做了一个悬浮窗体来控制相关操作
- 这里写死音视频文件路径了,具体要漂亮的实现就自己去做吧 【在mainwindow.cpp里】
关键代码
//记得在pro文件里加上 QT += multimedia
class XAudio
{
public:
explicit XAudio();
bool start(); //开启
void play(); //播放
void pause(); //暂停
bool write(const char *data,int len); //写入数据
int getfreebytes(){return pOutput->bytesFree();} //获取数据剩余空间大小
void setVolume(double volume){ pOutput->setVolume(volume);} //调节音量
double getVolume(){ return pOutput->volume();} //获取音量
~XAudio();
private:
QAudioOutput *pOutput; //输出
QAudioFormat fmt; //音频参数
QMutex mutex;
QIODevice *pOut;
bool isPlay; //启动标志
};
XAudio::XAudio()
{
fmt.setSampleRate(44100); //采样率
fmt.setSampleSize(16); //样本大小
fmt.setSampleType(QAudioFormat::UnSignedInt); //数据类型
fmt.setChannelCount(2); //通道数
fmt.setCodec("audio/pcm"); //解码格式
fmt.setByteOrder(QAudioFormat::LittleEndian); //端序
pOutput = new QAudioOutput(fmt);
pOutput->setVolume(1);
pOut = NULL;
isPlay = false;
start();
}
XAudio::~XAudio()
{
delete pOutput;
}
bool XAudio::start()
{
if(!isPlay)
{
pOut = pOutput->start();
isPlay = true;
return true;
}
return false;
}
void XAudio::play()
{
if(!pOut && !isPlay)
{
isPlay = true;
mutex.lock();
pOutput->resume();
mutex.unlock();
}
}
void XAudio::pause()
{
if(!pOut && isPlay)
{
isPlay = false;
mutex.lock();
pOutput->suspend();
mutex.unlock();
}
}
void TestWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
if(!img.isNull())
painter.drawImage(0,0,img.scaled(size()));
if(isDanMu)
DrawDanMu(&painter);
}
void TestWidget::DrawDanMu(QPainter *painter)
{
DanMuData *p = dMuKu->get();
if(!p)
return;
QPen pen;
QFont f; f.setPixelSize(22);
painter->setFont(f);
while(1) {
if(!p || p->index > CDuration)
break;
int color = p->list.at(0).toInt(NULL,16);
pen.setColor(QColor(color & 0xFF,((color >> 8) & 0xFF),(color >> 16)));
painter->setPen(pen);
if(p->arg[0] == 0) {
painter->drawText(p->arg[2],p->arg[3],p->list.at(1));
} else {
//根据比例画弹幕
int X = (_DEM_ - p->arg[1]) * width() / _DEM_;
//int X = p->arg[1] * width() / _DEM_;
painter->drawText(X,p->arg[3],p->list.at(1));
}
p = p->next;
}
}
void TestWidget::openvideo()
{
if(!isOpen)
{
m = new AFFmpeg;
//if(m->open("D:/Picture/avi/test.mp4"))
if(m->open(path.toLocal8Bit()))
{
dMuKu->open();
timer->start(1000/m->getFPS());
Atimer->start(10);
isOpen = true;
}
else
qDebug() << "can't open.";
}
}
void TestWidget::videoshow()
{
if(isSPressed)
return;
int POs = m->readVideo();
if(POs > 0)
{
AVFrame *f = m->getFrame();
if(f)
{
img = QImage((uchar*)f->data[0],f->width,f->height,QImage::Format_RGB32);
CDuration = m->getCDuration();
if((int)CDuration > timestamp - 1) {
dMuKu->read((int)CDuration);
dMuKu->del(CDuration - _DANMU_TIME_);
timestamp++;
}
dMuKu->update(CDuration);
int minter = m->getDuration() / 60;
int sec = m->getDuration() % 60;
int cminter = CDuration / 60;
int csec = (int)CDuration % 60;
QString strtime = QString("%1:%2/%3:%4").arg(cminter).arg(csec,2,10,QLatin1Char('0')).arg(minter).arg(sec,2,10,QLatin1Char('0'));
Timelab->setText(strtime);
//qDebug() << m->getCDuration() << m->getDuration();
CurTimeSlider->setValue(CDuration/m->getDuration() * 1000);
update();
}
}
else if(POs == -2)
{
qDebug() << "stop";
timer->stop();
Atimer->stop();
isOpen = false;
isPause = true;
delete m;
}
}
void TestWidget::audioplay()
{
if(isSPressed)
return;
char *buf = NULL;
int len = m->getAudio(&buf);
if(len >= 0 && pAudio->getfreebytes() >= len)
{
//int size =
m->readAudio();
//qDebug() << len << freeByte << size;
pAudio->write(buf,len);
}
}
本作品只是兴趣使然的产物,从零开始花费了大概一个月的时间,说做的多么牛逼那是没有的。但对于初学
者来说当作入门资料还是可以的。如果你想要入音视频这块行业,想要更多地了解,可以去看看雷霄骅的博客。
关于代码里有啥疑惑的可以留言【佛系回复】或者 联系本人QQ :673315140在项目过程中,遇到的问题还蛮多
的,但是并没记录下来,已经不知从何记起了【蛋疼(ˉ▽ˉ;)...】