1.参考资料:
https://www.docin.com/p-1263172990.html
https://wenku.baidu.com/view/738ea046fd4ffe4733687e21af45b307e971f96f.html
2.涉及QT内容:
2.1 使用QMediaPlayer来播放音频。
//在.pro文件中加入 QT += multimedia
QMediaPlayer player;
QMediaPlaylist playlist;
playlist.addMedia(QUrl("xx.wav"));
player.setPlaylist(&playlist);
player.play();
player.pause();
player.stop();
2.2 使用QPainter,QPixmap绘制波形图。
QPixmap pixmap(100, 20);
pixmap.fill(Qt::black);
QPainter painter(&pixamp);
painter.drawLine(QLine());
3.波形图绘制原理。
注意:本文章只实现声道数为1,2,采样位数为8,16的wav非压缩(PCM)音频;
①根据绘制区域得到宽高,再得出绘制波形图的每条竖线需要N个采样点,然后N个连续采样点中取出最大值和最小值,然后比例计算竖线;
②获取采样位数为8时,数据大小范围为0~255,获取采样位数为16时,数据大小范围为-32768~32767.
关键源码:
if (fileWav.endsWith(".wav", Qt::CaseInsensitive) && QFile(fileWav).exists())
{
if (m_qUnplay.width() == 0)
{
return;
}
m_qUnplay.fill(Qt::black);
QPainter painter1(&m_qUnplay);
painter1.setPen(QColor(75, 243, 167));
m_qPlaying.fill(QColor(98, 108, 123));
QPainter painter2(&m_qPlaying);
painter2.setPen(QColor(75, 243, 167));
FILE* fp;
if ((fp = fopen(fileWav.toLocal8Bit().toStdString().c_str(), "rb")) != nullptr)
{
struct WavHead //wave文件头格式
{
char chunkID[4]; //文档标识 固定值"RIFF"
qint32 chunkSize; //文件数据长度
char format[4]; //文件格式类型 固定值为"WAVE"
qint32 subChunk1ID; //格式块标识 固定值为"fmt"
qint32 subChunk1Size; //格式块长度 取决于编码格式
qint16 audioFormat; //编码格式代码 (PCM/非压缩格式)
qint16 numChannels; //声道个数
qint32 sampleRate; //采样频率
qint32 byteRate; //传输速率
qint16 blockRate; //数据块对齐单位
qint16 bitsPerSample; //采样位数
char subChunk2ID[4];
qint32 subChunk2Size;
}stuWavHead;
fread(&stuWavHead, sizeof(stuWavHead), 1, fp);
//只支持声道数为1,2的非压缩格式(PCM)
if ((stuWavHead.numChannels != 1 && stuWavHead.numChannels != 2)
|| stuWavHead.audioFormat != 1)
{
return ;
}
int dataSize = stuWavHead.chunkSize - sizeof(stuWavHead) - 8;
//样本总数
int sampleCount = dataSize / NO_ZERO(stuWavHead.bitsPerSample / 8);
//只去左声道样本
sampleCount = sampleCount / stuWavHead.numChannels;
//每条竖线的样本数
int linePerSample = sampleCount / m_qPlaying.width();
int minY = 0;
int maxY = 0;
if (stuWavHead.bitsPerSample == 8)
{
quint8 data;
quint8 min = 255;
quint8 max = 0;
int x = 0;
for (int i = 1; i <= sampleCount; i++)
{
fread(&data, 1, 1, fp);
if (stuWavHead.numChannels == 2)
{
fseek(fp, 1, SEEK_CUR);
}
if (min > data)
{
min = data;
}
if (max < data)
{
max = data;
}
if (i % linePerSample == 0)
{
maxY = m_qPlaying.height() - max * m_qPlaying.height() / 255;
minY = m_qPlaying.height() - min * m_qPlaying.height() / 255;
painter1.drawLine(QPoint(x, minY), QPoint(x, maxY));
painter2.drawLine(QPoint(x, minY), QPoint(x, maxY));
qDebug() << "min:" << min;
qDebug() << "max:" << max;
min = 255;
max = 0;
x++;
}
}
}
else if (stuWavHead.bitsPerSample == 16)
{
qint16 data;
qint16 min = 32767;
qint16 max = min + 1;
int x = 0;
for (int i = 1; i <= sampleCount; i++)
{
fread(&data, 2, 1, fp);
if (stuWavHead.numChannels == 2)
{
fseek(fp, 2, SEEK_CUR);
}
if (min > data)
{
min = data;
}
if (max < data)
{
max = data;
}
if (i % linePerSample == 0)
{
maxY = m_qPlaying.height() / 2 - max * m_qPlaying.height() / 65535;
minY = m_qPlaying.height() / 2 - min * m_qPlaying.height() / 65535;
painter1.drawLine(QPoint(x, minY), QPoint(x, maxY));
painter2.drawLine(QPoint(x, minY), QPoint(x, maxY));
min = 32767;
max = min + 1;
x++;
}
}
}
}
}
效果: