QT播放Wav音频并显示波形

1、前言

因为项目需要,做了个小工具来做前期准备。

这个需求实现两步:播放和显示波形。

播放方面,一开始选择FMod,小工具快做好的时候偶然发现FMod需要商业授权,所以只能放弃。试了试ffmpeg+SDL又觉得大材小用过于复杂(主要是对编解码这一块有点畏惧)。最后才发现QT自带播放类其实已经满足需求,最后播放就交给QMediaPlayer。

由于显示波形需要放大平移等操作,自己实现起来很费时间,所以选择现有的图表工具。目前可选的有QWT和QCustomPlot。由于QWT配置起来比较麻烦,所以显示方面选择QCustomPlot。

显示波形的数据源在使用FMod的时候,是从FMod函数中获取的,后来发现Wav文件就是文件头+PCM数据,直接解出来就可以了。自己对音视频这一块真的一窍不通。

2、播放Wav文件

使用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的博客-CSDN博客 (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,否在会因为反复控制间的延时,造成杂音。

3、获取波形数据

此处参考Qt 之 WAV文件解析_前行之路还需前行-CSDN博客https://blog.csdn.net/goforwardtostep/article/details/52776240)及Qt 之 解析wav文件的头信息(详细分析、对比不同wav文件的数据)_前行之路还需前行-CSDN博客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就是用于显示波形的数据。

4、显示波形-QCustomPlot

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-CSDN博客_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)走得顺滑,所以放弃了。

该工具还在继续增加功能中,后期会加上音频截取,以及拖动图上得线控制播放进度,所以在做完之前暂时不会放代码。这些功能写完后,也会更新到这里。

这个工具目前为止就几百行代码非常简单,甚至想过是否值得特地写一篇来记录,最后写下来一个是因为音频方面前面弯路走得太多,一个是自己本身对音视频这方面非常欠缺就记录一下,还有就是这个工具是独立的功能,后期上传代码囤点积分,现在下代码积分要求极高吗,不多存点以后遇到难题实在下不起大神们的代码了。

  • 8
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: QWave是一款基于Qt框架开发的音频波形显示控件,可以用来在Qt应用程序中实现音频波形显示和控制。该控件支持WAV、MP3、OGG等常用音频格式,支持多种显示模式和操作,可以实现实时显示、滚动播放和缩放等功能。通过使用QWave控件,可以轻松地开发出具有音频功能的多媒体应用程序,如音频播放器、录音器等。 同时,QWave控件还支持跨平台运行,可以在多种操作系统和Qt版本下运行。其开源免费的特点,也为广大开发者提供了便捷和经济的选择。如果您需要在您的Qt应用程序中添加音频波形显示功能,不妨考虑使用QWave控件。您可以到QWave项目官网或github等开源代码库中下载最新的QWave源代码和使用文档。谢谢。 ### 回答2: Qt是一种跨平台的C++框架,它提供了许多常用的控件和功能,方便开发人员进行桌面应用程序的开发。其中,qwave是一款Qt音频波形显示控件,它可以实现对音频波形进行可视化展示。 该控件可以直接从Qt官网上的软件中心进行下载和安装,也可以在GitHub等代码托管平台上找到相关的开源库进行下载和使用。使用qwave控件可以方便地实现对音频文件的读取和展示,同时还可以实现波形颜色、粗细、放大缩小等操作。 在使用qwave控件之前,需要先进行安装和集成。安装和集成的过程可以参考官方文档和教程,具体步骤包括下载安装包、解压、编译等。安装完成后,在Qt的项目文件中添加qwave的头文件和库文件,即可在程序中调用该控件。 除了qwave,还有其他一些类似控件,如Qt音频可视化插件QwtAudio。这些控件都可以方便地进行音频波形展示、分析和处理,加快开发人员的开发效率。 ### 回答3: qwave是一个基于Qt框架的音频波形显示控件,适合于QT开发者用于自己的音频软件项目中,它可以实现音频波形显示、放大、缩小、移动等操作。通过将qwave控件添加到项目中,开发者可以快速方便地实现音频波形的可视化。 如果需要下载qwave控件,可以通过GitHub源码库访问qwave控件的下载链接。在GitHub官网上搜索“qwave”,即可找到开源代码和下载链接。此外,还可以直接在Qt Creator中安装qwave控件,方法是在项目的.pro文件中添加一行代码:“QT += qwave”,Qt Creator将自动引用库文件。 在使用qwave控件时,需要注意一些问题。首先,需要熟悉Qt框架的基本概念和部件组件,以便能够正确地安装、运行和使用qwave控件。其次,需要针对具体的音频软件项目,进行一些修饰和调整,满足项目需求。最后,需要对使用qwave控件的代码进行测试,确保其稳定性和可靠性,减少项目出错的风险。 总之,qwave是一个强大的音频波形显示控件,使用方便,可以在很多项目中帮助开发者实现音频波形的可视化。下载和使用qwave控件需要一定的技术基础和对Qt框架的了解,开发者可以通过学习和实践,逐渐掌握其使用技巧和技能。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值