用 Qt 的 QAudioOutput 类播放 WAV 音频文件
用 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)。类似这样的小问题估计代码中还有一些。等我以后测试出问题时再修改吧。
写到这里,这篇文章就差不多了。希望对大家有用。
用 Qt 的 QAudioOutput 类播放 WAV 音频文件的更多相关文章
- 使用WaveOut API播放WAV音频文件(解决卡顿)
虽然waveout已经过时,但是其api简单,有些时候也还是需要用到. 其实还是自己上msdn查阅相应api最靠谱,waveout也有提供暂停.设置音量等接口的,这里给个链接,需要的可以自己查找: h ...
- S3C2416裸机开发系列19_Fatfs播放录像wav音频文件
S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩 1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...
- Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件
原文 Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件 第21部分:永久保存Wav音频文件 原文地址:http://channel9.msdn.com/Series/Win ...
- Windows Phone 8初学者开发—第20部分:录制Wav音频文件
原文 Windows Phone 8初学者开发—第20部分:录制Wav音频文件 原文地址:http://channel9.msdn.com/Series/Windows-Phone-8-Develop ...
- C语言解析WAV音频文件
C语言解析WAV音频文件 代码地址: Github : https://github.com/CasterWx/c-wave-master 目录 前言 了解WAV音频文件 什么是二进制文件 WAV的二 ...
- iOS从零开始学习直播之音频1.播放本地音频文件
现在直播越来越火,俨然已经成为了下一个红海.作为一个资深码农(我只喜欢这样称呼自己,不喜欢别人这样称呼我),我必须赶上时代的潮流,开始研究视频直播.发现视屏直播类的文章上来就讲拉流.推流.采集.美 ...
- 解析WAV音频文件----》生成WAV音频文件头
前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i WAV音频文件介绍: WAV文件是在PC机平台上很常见的.最经典的多媒体音频文件,最早于1991年8月出现在Windows3.1操作系统 ...
- AVAudioPlayer播放在线音频文件
AVAudioPlayer播放在线音频文件 一:原里: AVAudioPlayer是不支持播放在线音频的,但是AVAudioPlayer有一个 initWithData的方法:我们可以把在线音频转换为 ...
- Qt ------ WAV 音频文件播放
1.用 QFile 打开 WAV 文件,读出文件头信息,看看是否符合音频播放设备的要求 QAudioDeviceInfo m_audioOutputDevice;//可以获取音频输出设备的信息,比如哪 ...
随机推荐
- Mongoose 利用实现HTTP服务
嘛.... 注意:这里是使用mongoose实现HTTP服务,非数据库使用. 最近由于需要使用HTTP服务端,原先是使用的Qt框架实现的HTTP服务端,然后发现有些缺陷导致我不得不放弃这个框架,也不是 ...
- 关于Mysql查询varchar类型错误问题
因为后台所有表ID都是按照雪花算法生成的18位数字,需要对接到Android,Ios和H5,此时H5会出现字符超长溢出,所以直接把ID改为varchar类型. 如我的一张表ID为varchar(18) ...
- Linux学习总结(十五)文件查找 which whereis locate find
which命令 用于查找并显示给定命令的绝对路径,环境变量PATH中保存了查找命令时需要遍历的目录.which指令会在环境变量$PATH设置的目录里查找符合条件的文件.也就是说,使用which命令,就 ...
- 3、RabbitMQ-work queues 工作队列
work queues 工作队列 1.模型图: 为什么会出现 work queues? 前提:使用 simple 队列的时候 我们应用程序在是使用消息系统的时候,一般生产者 P 生产消息是毫不费力的( ...
- spring boot 2.x拦截器导致静态资源404终极解决办法
首先添加application文件static路径,我的是yml文件 spring: mvc: static-path-pattern: /static/**然后注册拦截器类如下方法; @Overri ...
- 如何利用java程序实现加密所需的公钥、密钥、数字证书
本篇的主要目的在于实现pdf的数字签名问题,只是作为我学习知识的总结. 1.数字签名算法的概述 本部分主要参考于:https://blog.csdn.net/lovelichao12/article/ ...
- Android-应用性能测试
参考了文章:http://www.cnblogs.com/mliangchen/p/5125114.html 问题一:使用真机在DDMS下,查看不到应用进程,logcat也不全 最后,只能使用Geny ...
- 前端css小米导航栏设置及盒子定位居中问题
1.小米最上部导航栏设置 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...
- oracle数据库——常用的数据类型
2018-12-19 23:08:03 oracle数据库中常用的数据类型有23种,我们把数据类型分为字符型.数字型.日期型和其他数据类型. 一.字符型: 数据类型 取值范围 (字节) 说明 v ...
- ORACLE逐行累计求和方法(OVER函数)
1.RANK ( ) OVER ( [QUERY_PARTITION_CLAUSE] ORDER_BY_CLAUSE ) DENSE_RANK ( ) OVER ( [QUERY_PARTITION_ ...