因为项目需要,做了个小工具来做前期准备。
这个需求实现两步:播放和显示波形。
播放方面,一开始选择FMod,小工具快做好的时候偶然发现FMod需要商业授权,所以只能放弃。试了试ffmpeg+SDL又觉得大材小用过于复杂(主要是对编解码这一块有点畏惧)。最后才发现QT自带播放类其实已经满足需求,最后播放就交给QMediaPlayer。
由于显示波形需要放大平移等操作,自己实现起来很费时间,所以选择现有的图表工具。目前可选的有QWT和QCustomPlot。由于QWT配置起来比较麻烦,所以显示方面选择QCustomPlot。
显示波形的数据源在使用FMod的时候,是从FMod函数中获取的,后来发现Wav文件就是文件头+PCM数据,直接解出来就可以了。自己对音视频这一块真的一窍不通。
使用QMediaPlayer播放非常简单,设置文件路径,然后播放就可以了。
m_mediaPlayer.setMedia(QUrl::fromLocalFile(ui->editPath->text()));
m_mediaPlayer.play();
由于需要对Wav文件循环播放,但使用QMediaPlaylist的setPlaybackMode方法并不成功,最终也没有得到解决。所以在QMediaPlayer::stateChanged信号中加里循环控制。
connect(&m_mediaPlayer, &QMediaPlayer::stateChanged, [=](QMediaPlayer::State state) {
if(state == QMediaPlayer::StoppedState)
{
ui->btnPlay->setText("播放");
if(m_isBtnStop)
{
m_isBtnStop = false;
return ;
}
if(ui->checkLoop->isChecked())
{
m_mediaPlayer.setPosition(0);
m_mediaPlayer.play();
}
}
else if(state == QMediaPlayer::PlayingState)
{
ui->btnPlay->setText("暂停");
}
else if(state == QMediaPlayer::PausedState)
{
ui->btnPlay->setText("播放");
}
qDebug()<<"stateChanged"<<state;
});
其中m_isBtnStop用于区分是按键的停止还是文件播放结束的停止。在停止按键按下时,m_isBtnStop先设置为true。
在播放类的选择中,选择QMediaPlayer而不是QSound,是因为QMediaPlayer有更多的播放控制。对音量和进度的控制是QSound没有的。
播放时长和播放进度获取
connect(&m_mediaPlayer, &QMediaPlayer::durationChanged, [=](qint64 duration) {
ui->sliderPosition->setRange(0,duration);
ui->labelCurPosition->setText(formatTime(0));
ui->labelDuration->setText(formatTime(duration));
qDebug()<<"durationChanged"<<duration;
});
connect(&m_mediaPlayer, &QMediaPlayer::positionChanged, [=](qint64 position) {
ui->sliderPosition->setValue(position);
ui->labelCurPosition->setText(formatTime(position));
qDebug()<<"positionChanged"<<position;
});
时间显示部分调用的是Qt将毫秒转化为时分秒格式_ZONGXP的博客-程序员宅基地 (https://blog.csdn.net/zong596568821xp/article/details/79053491)中的函数。因为实际项目中并不会出现时间,就懒得自己写了,小工具里借用一下。
进度信号默认1秒发送一次,对于一些只有几秒的文件,进度条滑动就很不顺畅。可以把发送间隔调整一下,这里是调整为100毫秒。
m_mediaPlayer.setNotifyInterval(100);
播放/暂停、停止、后退一秒、前进一秒、静音、音量调节、进度调节
void Dialog::on_btnPlay_clicked()
{
switch (m_mediaPlayer.state())
{
case QMediaPlayer::PlayingState:
{
m_mediaPlayer.pause();
break;
}
case QMediaPlayer::StoppedState:
{
m_mediaPlayer.setMedia(QUrl::fromLocalFile(ui->editPath->text()));
}
case QMediaPlayer::PausedState:
{
m_mediaPlayer.play();
break;
}
default:break;
}
}
void Dialog::on_btnStop_clicked()
{
m_isBtnStop = true;
m_mediaPlayer.stop();
}
void Dialog::on_btnPre_clicked()
{
int newPosition = m_mediaPlayer.position() - 1000;
m_mediaPlayer.setPosition(newPosition < 0 ? 0 : newPosition);
}
void Dialog::on_btnNext_clicked()
{
int newPosition = m_mediaPlayer.position() + 1000;
int duration = m_mediaPlayer.duration();
m_mediaPlayer.setPosition(newPosition > duration ? duration : newPosition);
}
void Dialog::on_btnMute_clicked(bool checked)
{
m_mediaPlayer.setMuted(checked);
}
void Dialog::on_sliderVolume_valueChanged(int value)
{
m_mediaPlayer.setVolume(value);
}
void Dialog::on_sliderPosition_sliderReleased()
{
m_mediaPlayer.setPosition(ui->sliderPosition->value());
}
需要注意的是,由于sliderPosition同时受QMediaPlayer::positionChanged的影响,所有不能用valueChanged,否在会因为反复控制间的延时,造成杂音。
此处参考Qt 之 WAV文件解析_前行之路还需前行-程序员宅基地(https://blog.csdn.net/goforwardtostep/article/details/52776240)及Qt 之 解析wav文件的头信息(详细分析、对比不同wav文件的数据)_前行之路还需前行-程序员宅基地(https://blog.csdn.net/goforwardtostep/article/details/52789253)
QFile fileInfo(ui->editPath->text());
if (!fileInfo.open(QIODevice::ReadOnly))
{
return ;
}
fileInfo.read(m_wavFileHeader.RiffName, sizeof(m_wavFileHeader.RiffName));
fileInfo.read((char*)&m_wavFileHeader.nRiffLength, sizeof(m_wavFileHeader.nRiffLength));
fileInfo.read(m_wavFileHeader.WavName, sizeof(m_wavFileHeader.WavName));
fileInfo.read(m_wavFileHeader.FmtName, sizeof(m_wavFileHeader.FmtName));
fileInfo.read((char*)&m_wavFileHeader.nFmtLength, sizeof(m_wavFileHeader.nFmtLength));
fileInfo.read((char*)&m_wavFileHeader.nAudioFormat, sizeof(m_wavFileHeader.nAudioFormat));
fileInfo.read((char*)&m_wavFileHeader.nChannleNumber, sizeof(m_wavFileHeader.nChannleNumber));
fileInfo.read((char*)&m_wavFileHeader.nSampleRate, sizeof(m_wavFileHeader.nSampleRate));
fileInfo.read((char*)&m_wavFileHeader.nBytesPerSecond, sizeof(m_wavFileHeader.nBytesPerSecond));
fileInfo.read((char*)&m_wavFileHeader.nBytesPerSample, sizeof(m_wavFileHeader.nBytesPerSample));
fileInfo.read((char*)&m_wavFileHeader.nBitsPerSample, sizeof(m_wavFileHeader.nBitsPerSample));
QString strAppendMessageData;
if (m_wavFileHeader.nFmtLength >= 18)
{
fileInfo.read((char*)&m_wavFileHeader.nAppendMessage, sizeof(m_wavFileHeader.nAppendMessage));
int appendMessageLength = m_wavFileHeader.nFmtLength - 18;
m_wavFileHeader.AppendMessageData = new char[appendMessageLength];
fileInfo.read(m_wavFileHeader.AppendMessageData, appendMessageLength);
strAppendMessageData = QString(m_wavFileHeader.AppendMessageData);
}
char chunkName[5];
fileInfo.read(chunkName, sizeof(chunkName) - 1);
chunkName[4] = '\0';
QString strChunkName(chunkName);
if (strChunkName.compare("fact") == 0)
{
strcpy(m_wavFileHeader.FactName, chunkName);
fileInfo.read((char*)&m_wavFileHeader.nFactLength, sizeof(m_wavFileHeader.nFactLength));
fileInfo.read(m_wavFileHeader.FactData, sizeof(m_wavFileHeader.FactData));
fileInfo.read(m_wavFileHeader.DATANAME, sizeof(m_wavFileHeader.DATANAME));
}
else
{
strcpy(m_wavFileHeader.DATANAME, chunkName);
}
fileInfo.read((char*)&m_wavFileHeader.nDataLength, sizeof(m_wavFileHeader.nDataLength));
QByteArray pcmData;
pcmData = fileInfo.readAll();
m_wavFileHeader.fileDataSize = pcmData.size();
m_wavFileHeader.fileTotalSize = m_wavFileHeader.nRiffLength + 8;
m_wavFileHeader.fileHeaderSize = m_wavFileHeader.fileTotalSize - m_wavFileHeader.fileDataSize;
fileInfo.close();
由于篇幅关系删掉了注释,可以到原博客里看更具体的解释,以及WAVFILEHEADER的定义。
此处pcmData就是用于显示波形的数据。
QCustomPlot官网:https://www.qcustomplot.com/index.php/download
下载后解压,将qcustomplot.h/.cpp两个文件复制到项目,在UI中添加一个Widget窗体,并提升为QCustomPlot。(也可以examples-plots看到各种Demo)
QVector<double> waveData;
uint len = m_wavFileHeader.fileDataSize/m_wavFileHeader.nBytesPerSample;
qDebug()<<__FUNCTION__<<pcmData.size()<<len;
if(m_wavFileHeader.nBytesPerSample == 1)//8位
{
char *data = (char *)pcmData.data();
for (uint i = 0; i < len; i++)
{
waveData.append(data[i]);
}
}
else//16位
{
short *data = (short *)pcmData.data();
for (uint i = 0; i < len; i++)
{
waveData.append(data[i]);
}
}
ui->customPlot->addGraph();
ui->customPlot->graph(0)->setPen(QPen(Qt::blue));
ui->customPlot->addGraph();
ui->customPlot->graph(1)->setPen(QPen(Qt::red));
QVector<double> x(len);
for (uint i=0; i<len; ++i)
{
x[i] = i;
}
QVector<double> lineX(2),lineY(2);
lineX[0]=lineX[1]=0;
lineY[0]=-100000;
lineY[1]=100000;
ui->customPlot->graph(0)->setData(x, waveData);
ui->customPlot->graph(1)->setData(lineX,lineY);
ui->customPlot->graph(0)->rescaleAxes();
由于数据位数不同,8位的PCM数据使用1字节表示一个采样数据,而16位的PCM数据使用2字节表示一个采样数据,此处做了区分。由于实际项目上不会有双通道的音频文件,所以没有对通道数作出处理。
PCM格式可参考PCM数据格式介绍_SuperLi-程序员宅基地_pcm数据格式(https://blog.csdn.net/qq_25333681/article/details/90682989)
graph(0)用于显示波形,graph(1)用于显示进度线。因此在QMediaPlayer::positionChanged时需要实时刷新线的位置。
QVector<double> lineX(2),lineY(2);
lineX[0]=lineX[1]=(double)position*m_wavFileHeader.nSampleRate/1000;
lineY[0]=-100000;
lineY[1]=100000;
ui->customPlot->graph(1)->setData(lineX,lineY);
线的Y坐标±100000只是设置了比测试音频最大值更大的数值,使这线能贯穿整个波形。
画线的时候考虑过另外画一条无限延长的直线QCPItemStraightLine,但是测试x坐标逐一累加时发现效果不如graph(1)的这种,会有轻微的闪烁,不如graph(1)走得顺滑,所以放弃了。
该工具还在继续增加功能中,后期会加上音频截取,以及拖动图上得线控制播放进度,所以在做完之前暂时不会放代码。这些功能写完后,也会更新到这里。
这个工具目前为止就几百行代码非常简单,甚至想过是否值得特地写一篇来记录,最后写下来一个是因为音频方面前面弯路走得太多,一个是自己本身对音视频这方面非常欠缺就记录一下,还有就是这个工具是独立的功能,后期上传代码囤点积分,现在下代码积分要求极高吗,不多存点以后遇到难题实在下不起大神们的代码了。
文章浏览阅读461次。本次教程用宇宙模拟器space engine 0.980版本教大家如何创建自定义太阳系系统,包括创建恒星,行星,卫星,小行星,彗星等天体,最后教如何制作插件,打包压缩成pak文件。把链接复制到浏览器地址栏上,按回车键就可以看了关于宇宙模拟器Space Engine的天体(星球,星系,星云,星团等)脚本参数,如何创建天体(星球,星系,星云,星团等)的脚本,请看这些教程space engine打包创建..._space engine
文章浏览阅读10w+次,点赞5次,收藏17次。更新了Chrome,发现网页全部打不开了。都显示:“喔唷 崩溃啦”。点击任何按钮,例如,设置,选项,历史记录等,也“喔唷 崩溃啦” 卸载重装也无效解决方案找到 路径C:\Windows\System32\drivers\bd0001.sys删除(没有强制删除工具重命名也行,随便改个名字)重启电脑chrome就正常了原因可能是注册列表被一些卫士类优化工具或杀毒..._为什么googlechrome设置也打不开
文章浏览阅读1.5k次。安装thrift,我是mac机器,mac下使用homebrew安装thrift很方便,具体看另一篇博客。首先的定义接口文件: service HelloWorldService { string sayHello(1:string username)}因为thrift是支持跨平台的,所以这里接口的定义,thrift定义了自己的规范。 然后使用thrift的工具..._thrift调用例子 java
文章浏览阅读261次。我不确定术语“通配符”是否可以解释我的观点,但有时在一些现成的脚本中,我们可以调用一个非定义的函数,如find_by_age(23),其中age可以是映射到数据库表记录的任何其他内容.所以我可以调用find_by_name,find_by_email,find_by_id等等.那么我们怎么能以程序或面向对象的方式做这样的事情呢?解决方法:你正在寻找的术语是魔法.基本上是这样的:class Foo ..._php路径通配符
文章浏览阅读904次。ANR-WatchDog-ohos一个简单的监测程序,可检测到鸿蒙系统的 ANR(Application Not Response-应用程序无响应)错误并引发有意义的异常项目名称:ANR-WatchDog-ohos所属系列:鸿蒙的第三方组件适配移植功能:可检测到鸿蒙系统的ANR错误并引发有意义的异常项目移植状态:全部完成调用差异:无开发版本:sdk5,DevEco Studio2.1 beta3项..._com.github.anrwatchdog.anrerror: application not responding for at least 400
文章浏览阅读441次。赵辉《Visual+C++_MATLAB图像处理与识别实用案例精选》程序代码说明P0201:MATLAB赋值P0202:MATLAB中的for循环P0203:MATLAB中的for循环和if条件P0205:MATLAB图像处理的基本操作P0206:MATLAB高级图像处理操作P0207:根据RGB图像创建一幅灰度图像P0208:二值图像的取反操作P0209:用imshow函数显示图像P0210:在..._matlab命令行检测网络
文章浏览阅读3.1k次。经常会有机友提问,乐视MAX(乐视 X900+)手机支不支持一键刷机?由于奇兔刷机已经支持多达上千款安卓手机一键刷机,所以有时候小编也无法及时回答上来,最简单的办法就是把手机连上奇兔刷机,即可看到手机是否支持一键刷机。一键刷机并非每次都能一次成功,如果一次不成功可以多试几次,刷机失败不会对您的手机造成任何影响。奇兔市场小编给大家分享下乐视MAX(乐视 X900+)一键刷机教程,虽然使用方法很简单,..._乐视x900刷miui10
文章浏览阅读2.5k次,点赞7次,收藏49次。文章目录1.创建数据库与表2. 创建java项目并且导入jar包(buildPath)2.1 创建数据库连接文件 DBhelper.java2.2 创建vo (stu.java), 与数据库一致2.3 创建service (StuService.java)3. 编写功能类 FromDbToExcel.java注意事项:4. 编写功能类 FromExcelToDb.java注意事项5. 效果展示1.创建数据库与表2. 创建java项目并且导入jar包(buildPath)2.1 创建数据库连接文件_mysql导出多张表结构 excel java代码
文章浏览阅读352次。首先介绍阻塞与非阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。非阻塞忙轮询。接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”很明显一般人不会用第二..._epoll kqueue
文章浏览阅读509次。关于西门子PLC modbus通讯与运动控制的应用应用的设备昆仑通态1570gi(15寸)西门子plc smart-st20昆仑通态参数的设置与组态的重点组态软件选用型号1570gi的屏幕设置通讯地址 192.168.190报警参数的配置脚本的编写关于组态过程中遇到的问题由于这是我参加工作自己独立的完成的,这个期间真的是太坎坷了,我来说说我遇到的问题吧脚本应用if和if的嵌套IF THEN IF THEN ENDIFENDIF由于画面中关联着报_plc作为从站 modbus协议和运动控制命令冲突
文章浏览阅读972次。先用一台电脑连接IPSAN管理端口进去配置阵列配置完成后把IPSAN的ISCSI接口连接到服务器网口,man iscsiadm 查看可以查看是否安装了 iscsiadmyum -y install*iscsi* 命令安装iscsiadm然后在服务器上输入:iscsiadm -m discovery -t sendtargets -p 169.254.194.201:3260 (169.254.19..._centos实现ipsan卸载
文章浏览阅读698次。点击上方 "程序员小乐"关注,星标或置顶一起成长后台回复“大礼包”有惊喜礼包!关注订阅号「程序员小乐」,收看更多精彩内容每日英文Tough people aren..._cannot use range access on index 'env_index' due to type or collation conver