这两种方法都需要在.pro文件中加入multimedia模块。方法一是通过QAudioOutput实现的,先用QFile打开PCM文件,然后进行音频参数设置,最后调用QAudioOutput的start函数进行播放;方法二也用到了QAudioOutput,先将PCM文件读取到内存,然后进行音频参数设置,最后从内存中定时读取部分数据,调用QIODevice的write函数将这部分数据写入到扬声器。与方法一相比,方法二虽然麻烦,但是这种方法是进行语音编解码和语音网络传输的基础。
方法一:
#include <QApplication>
#include <QFile>
#include <QAudioFormat>
#include <QAudioOutput>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QFile inputFile;
inputFile.setFileName("test.pcm");
inputFile.open(QIODevice::ReadOnly);
//设置采样格式
QAudioFormat audioFormat;
//设置采样率
audioFormat.setSampleRate(44100);
//设置通道数
audioFormat.setChannelCount(2);
//设置采样大小,一般为8位或16位
audioFormat.setSampleSize(16);
//设置编码方式
audioFormat.setCodec("audio/pcm");
//设置字节序
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
//设置样本数据类型
audioFormat.setSampleType(QAudioFormat::UnSignedInt);
QAudioOutput *audio = new QAudioOutput( audioFormat, 0);
audio->start(&inputFile);
return a.exec();
}方法二:
#ifndef TESTAUDIO_H
#define TESTAUDIO_H
#include <QWidget>
#include <QAudioOutput>
#include <QIODevice>
#include <QTimer>
#include <QByteArray>
class TestAudio : public QWidget
{
Q_OBJECT
public:
explicit TestAudio(QWidget *parent = nullptr);
~TestAudio();
private:
int fileLength;
char *fileBuffer;
QAudioOutput *audioOutput;
QIODevice *streamOut;
QTimer *timer;
QByteArray tempBuffer;
private slots:
void slotTimeout();
};
#endif // TESTAUDIO_H#include "TestAudio.h"
#include <QDebug>
#include <QAudioFormat>
#include <QAudioDeviceInfo>
#include <QApplication>
TestAudio::TestAudio(QWidget *parent) :
QWidget(parent)
{
//获取文件的指针.
FILE *file=fopen(QString(qApp->applicationDirPath()+"/NorwayForest-500.pcm").toStdString().c_str(),"rb");
if(!file)
{
qDebug()<<"Open Failed";
return;
}
//把指针移动到文件的结尾 ,获取文件长度.
fseek(file,0,SEEK_END);
//获取文件长度.
fileLength=ftell(file);
//定义数组长度.
fileBuffer=new char[fileLength+1];
//把指针移动到文件开头 因为我们一开始把指针移动到结尾,如果不移动回来 会出错.
rewind(file);
//读文件.
fread(fileBuffer,1,fileLength,file);
//把读到的文件最后一位 写为0 要不然系统会一直寻找到0后才结束.
fileBuffer[fileLength]=0;
//关闭文件.
fclose(file);
qDebug()<<fileLength;
//设置采样格式.
QAudioFormat audioFormat;
//设置采样率.
audioFormat.setSampleRate(44100);
//设置通道数.
audioFormat.setChannelCount(2);
//设置采样大小,一般为8位或16位.
audioFormat.setSampleSize(16);
//设置编码方式.
audioFormat.setCodec("audio/pcm");
//设置字节序.
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
//设置样本数据类型.
audioFormat.setSampleType(QAudioFormat::UnSignedInt);
//音频设备信息.
QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();
if (!info.isFormatSupported(audioFormat)) {
qDebug()<<"default format not supported try to use nearest";
audioFormat = info.nearestFormat(audioFormat);
}
audioOutput = new QAudioOutput(audioFormat, this);
streamOut = audioOutput->start();
//定时器.
timer=new QTimer(this);
connect(timer, SIGNAL(timeout()), SLOT(slotTimeout()));
timer->start(10);
}
TestAudio::~TestAudio()
{
}
void TestAudio::slotTimeout()
{
static int i=0;
if(i<fileLength/1764)
{
tempBuffer.append(fileBuffer+i*1764,1764);
if(audioOutput&&audioOutput->state()!=QAudio::StoppedState&&
audioOutput->state()!=QAudio::SuspendedState)
{
int chunks = audioOutput->bytesFree()/audioOutput->periodSize();
while (chunks)
{
if (tempBuffer.length() >= audioOutput->periodSize())
{
//写入到扬声器.
streamOut->write(tempBuffer.data(),audioOutput->periodSize());
tempBuffer = tempBuffer.mid(audioOutput->periodSize());
}
else
{
//写入到扬声器.
streamOut->write(tempBuffer);
tempBuffer.clear();
break;
}
--chunks;
}
}
}
i++;
}
PCM裸流1秒的数据量是44100×16×2/8=176400字节,那么10毫秒的数据量就是1764字节,所以定时器定时10毫秒,每隔10毫秒从数组中顺序读取1746个字节。
不幸的是,方法二会有杂音,感谢评论区winwingy的提醒。方法三如下:
方法三
#ifndef MYDEVICE_H
#define MYDEVICE_H
#include <QIODevice>
class MyDevice : public QIODevice
{
private:
QByteArray data_pcm; //存放pcm数据
int len_written; //记录已写入多少字节
public:
MyDevice(QByteArray pcm); //创建对象传递pcm数据
~MyDevice();
qint64 readData(char *data, qint64 maxlen); //重新实现的虚函数
qint64 writeData(const char *data, qint64 len); //它是个纯虚函数, 不得不实现
};
#endif // MYDEVICE_H#include "MyDevice.h"
#include <QDebug>
MyDevice::MyDevice(QByteArray pcm) : data_pcm(pcm)
{
this->open(QIODevice::ReadOnly); // 为了解决QIODevice::read (QIODevice): device not open.
len_written = 0;
}
MyDevice::~MyDevice()
{
this->close();
}
// data为声卡的数据缓冲区地址, maxlen为声卡缓冲区最大能存放的字节数.
qint64 MyDevice::readData(char *data, qint64 maxlen)
{
if (len_written >= data_pcm.size())
return 0;
int len;
//计算未播放的数据的长度.
len = (len_written+maxlen) > data_pcm.size() ? (data_pcm.size() - len_written) : maxlen;
memcpy(data, data_pcm.data()+len_written, len); //把要播放的pcm数据存入声卡缓冲区里.
len_written += len; //更新已播放的数据长度.
return len;
}
qint64 MyDevice::writeData(const char *data, qint64 len)
{
return len;
}
#ifndef TESTAUDIO_H
#define TESTAUDIO_H
#include <QWidget>
class TestAudio : public QWidget
{
Q_OBJECT
public:
explicit TestAudio(QWidget *parent = nullptr);
~TestAudio();
};
#endif // TESTAUDIO_H
#include "TestAudio.h"
#include "MyDevice.h"
#include <QDebug>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QApplication>
#include <QFile>
TestAudio::TestAudio(QWidget *parent) :
QWidget(parent)
{
QAudioFormat fmt;
fmt.setSampleRate(44100);
fmt.setChannelCount(2);
fmt.setSampleSize(16);
fmt.setCodec("audio/pcm");
fmt.setCodec("audio/pcm");
fmt.setByteOrder(QAudioFormat::LittleEndian);
fmt.setSampleType(QAudioFormat::UnSignedInt);
QAudioOutput *out = new QAudioOutput(fmt, this);
QByteArray ba;
QFile f(qApp->applicationDirPath()+"/NorwayForest-500.pcm");
if (!f.open(QIODevice::ReadOnly))
exit(0);
ba = f.readAll();
f.close();
MyDevice *dev = new MyDevice(ba);
out->start(dev);
}
TestAudio::~TestAudio()
{
}方法三虽然是播放的本地文件,如果要播放网络流的话,实时给QByteArray填充数据就行。
更详细的可参考Qt自带例子Audio Output:

PCM音频文件的制作参考:http://blog.csdn.net/caoshangpa/article/details/51218994
音频参数解析参考:http://blog.csdn.net/caoshangpa/article/details/51218597
测试用PCM音频文件下载链接:http://download.csdn.net/detail/caoshangpa/9497751
原创不易,转载请标明出处:https://blog.csdn.net/caoshangpa/article/details/51224678
版权声明:本文为caoshangpa原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。