用 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 音频文件的更多相关文章

  1. 使用WaveOut API播放WAV音频文件(解决卡顿)

    虽然waveout已经过时,但是其api简单,有些时候也还是需要用到. 其实还是自己上msdn查阅相应api最靠谱,waveout也有提供暂停.设置音量等接口的,这里给个链接,需要的可以自己查找: h ...

  2. S3C2416裸机开发系列19_Fatfs播放录像wav音频文件

    S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩    1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...

  3. Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件

    原文 Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件 第21部分:永久保存Wav音频文件 原文地址:http://channel9.msdn.com/Series/Win ...

  4. Windows Phone 8初学者开发—第20部分:录制Wav音频文件

    原文 Windows Phone 8初学者开发—第20部分:录制Wav音频文件 原文地址:http://channel9.msdn.com/Series/Windows-Phone-8-Develop ...

  5. C语言解析WAV音频文件

    C语言解析WAV音频文件 代码地址: Github : https://github.com/CasterWx/c-wave-master 目录 前言 了解WAV音频文件 什么是二进制文件 WAV的二 ...

  6. iOS从零开始学习直播之音频1.播放本地音频文件

      现在直播越来越火,俨然已经成为了下一个红海.作为一个资深码农(我只喜欢这样称呼自己,不喜欢别人这样称呼我),我必须赶上时代的潮流,开始研究视频直播.发现视屏直播类的文章上来就讲拉流.推流.采集.美 ...

  7. 解析WAV音频文件----》生成WAV音频文件头

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i WAV音频文件介绍: WAV文件是在PC机平台上很常见的.最经典的多媒体音频文件,最早于1991年8月出现在Windows3.1操作系统 ...

  8. AVAudioPlayer播放在线音频文件

    AVAudioPlayer播放在线音频文件 一:原里: AVAudioPlayer是不支持播放在线音频的,但是AVAudioPlayer有一个 initWithData的方法:我们可以把在线音频转换为 ...

  9. Qt ------ WAV 音频文件播放

    1.用 QFile 打开 WAV 文件,读出文件头信息,看看是否符合音频播放设备的要求 QAudioDeviceInfo m_audioOutputDevice;//可以获取音频输出设备的信息,比如哪 ...

随机推荐

  1. 学习python第一天总纲

    1).python基础语法:4周课程(结束阶段考试) 2).前端知识点:html.css.javascript(js).jQuery 3).Linux(系统).数据库(关系型&非关系型) 4) ...

  2. Python version 3.6 required, which was not found in the registry错误解决

    问题: 安装pywin32出现Python version 3.6 required, which was not found in the registry错误解决 解决: 建立一个文件 regis ...

  3. OS开发小记:iOS富文本框架DTCoreText在UITableView上的使用

    要在页面中显示自己的布局,比如文字的字体和颜色.图文并排的样式,我们要用iOS SDK的原生UI在app本地搭建,如果一个页面需要在服务器端获取数据的话,我们也要在本地搭建好固定的布局,解析服务器传回 ...

  4. 指纹协查统计sql

     select dic.name, NVL(zc.zc_djzs,0),NVL(zc.zc_shzs,0),NVL(zc.zc_bzzs,0), NVL(zt.zt_djzs,0),NVL(zt.zt ...

  5. LWIP network interface 网卡 初始化 以 STM32 为例子 后面会有 用 2G 或者4G 模块 用 PPP拨号的 形式 虚拟出网卡 所以先以 这个为 前提

    LWIP   network interface   网卡 初始化    以  STM32  为例子  后面会有 用  2G 或者4G 模块 用 PPP拨号的 形式  虚拟出网卡  所以先以 这个为 ...

  6. [Zedboard Linux系统移植]-从MACHINE_START開始

    改动自:http://www.cnblogs.com/lknlfy/archive/2012/05/06/2486479.html 内核的启动过程? 3)内核的启动过程? arch/arm/kerne ...

  7. 彻底弄懂JS原型与继承

    本文由浅到深,循序渐进的将原型与继承的抽象概念形象化,且每个知识点都搭配相应的例子,尽可能的将其通俗化,而且本文最大的优点就是:长(为了更详细嘛). 一.原型 首先,我们先说说原型,但说到原型就得从函 ...

  8. Bulk Rename Utility 3.0 + x64 中文汉化版

    Bulk Rename Utility 3.0 + x64 中文汉化版由大眼仔旭(www.dayanzai.me)汉化发布.当发现做一件事情,原本用工具或软件进行批量处理也能达到相同效果,可却花了数倍 ...

  9. HTML5 -- 浏览器数据缓存 -- indexedDB

    IndexedDB是一种可以让你在用户的浏览器内持久化存储数据的方法,为web应用提供了丰富的查询功能,使我们的应用在在线和离线都能正常工作. 由于 IndexedDB 本身的规范还在持续演进中,当前 ...

  10. STM32F4XX中断方式通过IO模拟I2C总线Master模式

    STM32的I2C硬核为了规避NXP的知识产权,使得I2C用起来经常出问题,因此ST公司推出了CPAL库,CPAL库在中断方式工作下仅支持无子地址 的器件,无法做到中断方式完成读写大部分I2C器件.同 ...