首页 > 代码库 > 用 Qt 的 QAudioOutput 类播放 WAV 音频文件(使用了libsndfile外部库)
用 Qt 的 QAudioOutput 类播放 WAV 音频文件(使用了libsndfile外部库)
用 Qt 的 QAudioOutput 类播放 WAV 音频文件
最近有一个项目,需要同时控制 4 个声卡播放不同的声音,声音文件很简单就是没有任何压缩的 wav 文件。 如果只是播放 wav 文件,那么 Qt 里简单的 QSound 类是最适合的。但是 QSound 有一个很大的缺陷就是无法选择用哪个声卡。一番研究之后,决定用 QAudioOutput 来播放 WAV 音频文件。
网上也能找到几篇相关的文章,比如:
http://blog.csdn.net/qyee16/article/details/17062413
http://blog.csdn.net/GoForwardToStep/article/details/52805459
http://blog.csdn.net/caoshangpa/article/details/51224587
但是这几篇文章的实现方法其实都有问题。把这几篇文章中的代码精简一下,可以简化成:
QFile inputFile; inputFile.setFileName("test.wav"); inputFile.open(QIODevice::ReadOnly); QAudioOutput *audio = new QAudioOutput( format, 0); audio->start(&inputFile);
这里都没有注意到 wav 文件是有文件头的,播放时我们需要跳过文件头。他们的代码中音频格式也都是硬编码的,这样换一个不同的音频格式的 wav 文件播放出来的声音肯定就不对了。
所以严谨的方法是解析 wav 文件的文件头,获取音频格式,然后跳到音频数据区。播放音频数据区的数据。
解析文件头的方法并不太难,网上这样的文章也很多。百度搜索:c++ 读取 wav 文件 可以获得大量的文章,这里就不多说了。
解析文件头的方法不难但是很繁琐,我偷懒直接用了个第三方的库: libsndfile。
这个库我以前也有介绍: http://blog.csdn.net/liyuanbhu/article/details/10143157
这里主要说说如何和 QAudioOutput 一起使用。 介绍两种方式,一种采用面向过程的编程模式,另一种面向对象的方式。
面向过程的编程模式就是按照 C 语言的使用习惯那样,现将 wav 文件中的数据读取到一个数组中。然后再想办法把这个数组播放出来。 Qt 中对数组的封装是 QByteArray。所以代码可以这样写:
QString fileName = "C:/Program Files (x86)/Tencent/QQGAME/ClientCommon/1010/Sound/chat/1001.wav"; SNDFILE * sndfile; SF_INFO sfinfo = {0, 0, 0, 0, 0, 0}; sndfile = sf_open(fileName.toLocal8Bit(), SFM_READ, &sfinfo); int size = sf_seek(sndfile, 0, SEEK_END); sf_seek(sndfile, 0, SEEK_SET); QByteArray array(size * 2, 0); sf_read_short(sndfile, (short *)array.data(), size);
第二步就是如何将 QByteArray 播放出来了。Qt 中有一个 QBuffer 类,可以将 QByteArray 包装成一个 QIODevice。 所以下面的代码可以这样写:
QBuffer buffer(&array); buffer.open(QIODevice::ReadWrite); QAudioFormat audioFormat; audioFormat.setCodec("audio/pcm"); audioFormat.setByteOrder(QAudioFormat::LittleEndian); audioFormat.setSampleRate(sfinfo.samplerate); audioFormat.setChannelCount(sfinfo.channels); audioFormat.setSampleSize(16); audioFormat.setSampleType(QAudioFormat::SignedInt); QAudioOutput *audio = new QAudioOutput(QAudioDeviceInfo::defaultOutputDevice(), audioFormat ); audio->start(&buffer);
至此,第一种方法就完成了。但是这种方法不够面向对象,所以就有了后面第二种方法。
既然 QAudioOutput 只能与 QIODevice 联用。我们就构造一个派生自 QIODevice 的类。这个类实现对 wav 文件的读写。
派生 QIODevice 还是比较简单的,我们只需要实现两个纯虚函数:
qint64 readData(char * data, qint64 maxSize) ; qint64 writeData(const char * data, qint64 maxSize);
为了使用方便,我多实现了些函数。下面是头文件。
#ifndef WAVFILE_H #define WAVFILE_H #include "sndfile.h" #include <QIODevice> #include <QAudioFormat> class WAVFile : public QIODevice { public: WAVFile(); ~WAVFile(); bool isSequential() const {return false;} bool open(QString fileName, OpenMode mode, QAudioFormat format = QAudioFormat()); void close(); bool canReadLine() const {return false;} qint64 pos() const; qint64 size() const; bool seek(qint64 pos); bool atEnd() const; bool reset(); QAudioFormat format(); protected: qint64 readData(char * data, qint64 maxSize) ; qint64 writeData(const char * data, qint64 maxSize); private: SNDFILE * m_sndfile; SF_INFO m_info; QString m_fileName; }; #endif // WAVFILE_H
之后是 C++ 代码:
#include "wavfile.h" WAVFile::WAVFile() { } qint64 WAVFile::pos() const { sf_count_t p = sf_seek(m_sndfile, 0, SEEK_CUR); return p; } qint64 WAVFile::size() const { sf_count_t p = sf_seek(m_sndfile, 0, SEEK_END); return p; } bool WAVFile::seek(qint64 pos) { sf_seek(m_sndfile, pos, SEEK_SET); return true; } bool WAVFile::atEnd() const { if(pos() == size()) { return true; } return false; } bool WAVFile::reset() { sf_count_t p = sf_seek(m_sndfile, 0, SEEK_SET); if(p == -1) { return false; } return true; } bool WAVFile::open(QString fileName, OpenMode mode, QAudioFormat format) { int snd_mode; switch(mode) { case QIODevice::ReadOnly: snd_mode = SFM_READ; m_info.channels = 0; m_info.format = 0; m_info.frames = 0; m_info.samplerate = 0; break; case QIODevice::WriteOnly: snd_mode = SFM_WRITE; m_info.channels = format.channelCount(); m_info.format = SF_FORMAT_WAV; m_info.frames = 0; m_info.samplerate = format.sampleRate(); break; case QIODevice::ReadWrite: snd_mode = SFM_RDWR; break; default: return false; } m_sndfile = sf_open(fileName.toLocal8Bit(), snd_mode, &m_info); QIODevice::open(mode); return m_sndfile; } qint64 WAVFile::readData(char * data, qint64 maxSize) { return sf_read_raw(m_sndfile, data, maxSize); } qint64 WAVFile::writeData(const char * data, qint64 maxSize) { return sf_write_raw(m_sndfile, data, maxSize); } QAudioFormat WAVFile::format() { QAudioFormat audioFormat; audioFormat.setCodec("audio/pcm"); audioFormat.setByteOrder(QAudioFormat::LittleEndian); audioFormat.setSampleRate(m_info.samplerate); audioFormat.setChannelCount(m_info.channels); switch(m_info.format & 0x0f) { case SF_FORMAT_PCM_U8: audioFormat.setSampleSize(8); audioFormat.setSampleType(QAudioFormat::UnSignedInt); break; case SF_FORMAT_PCM_S8: audioFormat.setSampleSize(8); audioFormat.setSampleType(QAudioFormat::SignedInt); break; case SF_FORMAT_PCM_16: audioFormat.setSampleSize(16); audioFormat.setSampleType(QAudioFormat::SignedInt); break; case SF_FORMAT_PCM_24: audioFormat.setSampleSize(24); audioFormat.setSampleType(QAudioFormat::SignedInt); break; case SF_FORMAT_PCM_32: audioFormat.setSampleSize(32); audioFormat.setSampleType(QAudioFormat::SignedInt); break; default: audioFormat.setSampleSize(8); audioFormat.setSampleType(QAudioFormat::UnSignedInt); break; } return audioFormat; } void WAVFile::close() { sf_close(m_sndfile); } WAVFile::~WAVFile() { }
有了这个 WAVFile 类之后,剩下的代码就很简单了。
WAVFile inputFile = new WAVFile; inputFile->open(f1, QIODevice::ReadOnly); QAudioOutput audio = new QAudioOutput(QAudioDeviceInfo::defaultOutputDevice(), inputFile->format() ); connect(audio, SIGNAL(stateChanged(QAudio::State)), this, SLOT(audio0(QAudio::State))); if(inputFile->isReadable()) { audio->start(inputFile); }
这里多说两句,代码刚写好时一直播放不出声音。检查了好久发现是 inputFile->isReadable() 一直都是 false 状态。原因是刚开始我们没在 open 函数中调用 QIODevice::open(mode)。类似这样的小问题估计代码中还有一些。等我以后测试出问题时再修改吧。
写到这里,这篇文章就差不多了。希望对大家有用。
http://blog.csdn.net/liyuanbhu/article/details/53208269
用 Qt 的 QAudioOutput 类播放 WAV 音频文件(使用了libsndfile外部库)