Qt音频采集与录制wav音频图wav格式介绍C++实现声音文件的播放(OpenAL、Fmod、BASS、SFML)6个声音频谱软件

1、PCM声音格式

(1)频率范围 声音频率在20Hz~20kHz之间的声波。(2)采样率 在数字音频领域,常用的采样率有:

  • 8000 Hz - 电话所用采样率, 对于人的说话已经足够 11025 Hz - 电话所用采样率
  • 22050 Hz - 无线电广播所用采样率(常用) 32000 Hz - miniDV 数码视频 camcorder、DAT (LP mode)所用采样率
  • 44100 Hz - 音频 CD, 也常用于 MPEG-1 音频(VCD,SVCD,MP3)所用采样率
  • 47250 Hz - 商用 PCM 录音机所用采样率
  • 48000 Hz - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
  • 50000 Hz - 商用数字录音机所用采样率 96000 Hz或者 192000 Hz - DVD-Audio、一些 LPCM DVD 音轨、BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨所用所用采样率

(3)内存布局:

android 录音机下载 采样率 录音采样率一般用多少_qt

(4)比特率bps(bit per second)

比特率也叫码率,指音乐每秒播放的数据量,单位用bit表示。

bps = 采样率×采样位宽×声道数

44.1K×16×2 =1411.2Kbps

波形数据传输速率(每秒平均字节数) = 采样频率 × 音频通道数 × 采样位宽 / 8

一、QAudioOutput使用

Qt播放PCM音频(裸流)文件的三种方法 (最开始看的)QT QAudioOutput+QIODevice 音频流实时播放

//CAudioDevice.h---------------------
#ifndef CAUDIODEVICE_H
#define CAUDIODEVICE_H

#include <QObject>
#include <QIODevice>

class CAudioDevice  :public QIODevice
{
public:
    CAudioDevice(const QByteArray  &pcm, QObject *parent = nullptr);
    ~CAudioDevice();
    qint64 readData(char *data, qint64 maxlen); //重新实现的虚函数
    qint64 writeData(const char *data, qint64 len); //它是个纯虚函数, 不得不实现
private:
    QByteArray m_baPcm;
    int        m_lenWritten;
};

#endif // CAUDIODEVICE_H

//CAudioDevice.cpp-------------------
#include "CAudioDevice.h"
#include <QDebug>
CAudioDevice::CAudioDevice(const QByteArray  &pcm, QObject *parent):QIODevice (parent),m_baPcm(pcm),m_lenWritten(0)
{
    this->open(QIODevice::ReadOnly);
}
CAudioDevice::~CAudioDevice()
{
    this->close();
}
qint64 CAudioDevice::readData(char *data, qint64 maxlen)
{
    if (m_lenWritten >= m_baPcm.size())
    {
        qDebug()<<"m_lenWritten >= m_baPcm.size(), return";
         return 0;
    }
    //计算未播放的数据的长度.
    int len = (m_lenWritten+maxlen) > m_baPcm.size() ? (m_baPcm.size() - m_lenWritten) : maxlen;

    memcpy(data, m_baPcm.data()+m_lenWritten, len); //把要播放的pcm数据存入声卡缓冲区里.
    m_lenWritten += len; //更新已播放的数据长度.
    return len;
}
qint64 CAudioDevice::writeData(const char *data, qint64 len)
{
    return  len;
}
//Widget.h-------------------
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "CAudioDevice.h"
#include <QAudioFormat>
#include <QAudioOutput>
#include <QTimer>
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_btnCAudioDevice_clicked();
    void on_btnQAudioOutput_clicked();
    void on_btnOpenAL_clicked();
    void slotTimeout();

private:
    Ui::Widget *ui;
    // for QAudioOutput 
    QByteArray    m_ba;
    QAudioOutput *m_pAudioOutput;
    QIODevice    *m_pDevice;
    QTimer       *m_pTimer;
    bool         m_bFinish;
};
#endif // WIDGET_H

//Widget.cpp---------
#include "widget.h"
#include "ui_widget.h"
#include <QFile>
#include <QDir>
#include <QDebug>


Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget),m_pAudioOutput(nullptr),m_pDevice(nullptr),m_pTimer(nullptr),m_bFinish(true)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_btnCAudioDevice_clicked()
{
    //读入文件
    QByteArray ba;
    QFile file(QDir::currentPath()+"/1.wav");
    if (!file.open(QIODevice::ReadOnly))
    {
        qDebug()<<"open file error";
        return;
    }
    ba = file.readAll();
    file.close();
    //format
    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);

    //播放
    CAudioDevice *dev = new CAudioDevice(ba);
    QAudioOutput *out = new QAudioOutput(fmt, this);
    out->start(dev);
}
void Widget::on_btnQAudioOutput_clicked()
{

    //读入文件
    //QByteArray ba;
    QFile file(QDir::currentPath()+"/1.wav");
    if (!file.open(QIODevice::ReadOnly))
    {
        qDebug()<<"open file error";
        return;
    }
    m_ba = file.readAll();
    file.close();
    //format
    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);

    //音频设备信息.
    QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();
    if (!info.isFormatSupported(fmt)) {
        qDebug()<<"default format not supported try to use nearest";
        fmt = info.nearestFormat(fmt);
    }
    if(m_pAudioOutput == nullptr)
        m_pAudioOutput = new QAudioOutput(fmt, this);
    if(m_pDevice == nullptr)
        m_pDevice = m_pAudioOutput->start();
    if(m_pTimer == nullptr)
    {
        m_pTimer=new QTimer(this);
        //connect(m_pTimer, SIGNAL(timeout()), SLOT(slotTimeout()));
        connect(m_pTimer,&QTimer::timeout,this,&Widget::slotTimeout);
    }
    m_pTimer->start(10);
    m_bFinish = false;
//    while (!m_bFinish) {
//        slotTimeout();
//    }



}

void Widget::slotTimeout()
{
    static int i=0;
    if(i<m_ba.size()/1764)
    {
        QByteArray tempBuffer;
        tempBuffer.append(m_ba.data()+i*1764,1764);

        if(m_pAudioOutput&&m_pAudioOutput->state()!=QAudio::StoppedState&&
                m_pAudioOutput->state()!=QAudio::SuspendedState)
        {
            int chunks = m_pAudioOutput->bytesFree()/m_pAudioOutput->periodSize();
            while (chunks)
            {
                if (tempBuffer.length() >= m_pAudioOutput->periodSize())
                {
                    //写入到扬声器.
                    m_pDevice->write(tempBuffer.data(),m_pAudioOutput->periodSize());
                    tempBuffer = tempBuffer.mid(m_pAudioOutput->periodSize());
                }
                else
                {
                    //写入到扬声器.
                    m_pDevice->write(tempBuffer);
                    tempBuffer.clear();
                    break;
                }
                --chunks;
            }
        }
    }
    else {
        m_bFinish=true;
        m_pTimer->stop();
        i=-1;

    }
    i++;
}

void Widget::on_btnOpenAL_clicked()
{

}

今天又碰到了QAudioOutput的天花板

//这是立即播放新数据,停止正在播放的-----
bool PCMPlayer::initData(int nChannels,int SampleRate,int nSampleSize)
{
    if(nullptr != m_audioOut){
        return true;
    }
    QAudioFormat audioFmt;
    audioFmt.setChannelCount(nChannels);
    audioFmt.setCodec("audio/pcm");
    audioFmt.setSampleRate(SampleRate);
    audioFmt.setSampleSize(nSampleSize);
    audioFmt.setByteOrder(QAudioFormat::LittleEndian);
    audioFmt.setSampleType(QAudioFormat::SignedInt);
    m_audioOut = new QAudioOutput(audioFmt);
    /*m_audioOut->setBufferSize(10000000);*/
    connect(m_audioOut,&QAudioOutput::stateChanged,this,[&](QAudio::State state){
        if (QAudio::ActiveState == state)
        {
            m_bFinished = false;//新的一段开始播放,设置未播放完的标识位
        }
        else if(QAudio::IdleState == state)
        {
            if (!m_bFinished)//与QAudio::ActiveState中标识位的false对应
            {
                emit sig_PlayFinished();
            }
        }
    });
     /*m_device = m_audioOut->start();
    if(nullptr == m_device){
        return false;
    }*/ 
    return true;
}
void PCMPlayer::playData(QByteArray data)
{
    m_bFinished = true;
    if(nullptr != m_audioOut)
    {
        m_audioOut->reset();//清空缓存中原来的数据--------
        if(!start())
        {
        	return ;
        }
    }
    if(nullptr != m_device){
        m_device->write(data.toStdString().c_str(), data.length());
    }
}
bool PCMPlayer::start()
{
	if(nullptr == m_audioOut)
	{
		return false;
	}
    m_audioOut->setBufferSize(10000000); //重新设置大小----
    m_device = m_audioOut->start();      //重新启动-------

    if(nullptr == m_device){
        return false;
    }
    return true;
}

二、openAL使用

官方简单例子OpenAl编程入门:播放一段音频

openal全教程

基于OpenAL的播放封装类(用的)

openal播放裸数据(有遍历设备)

Linux下使用Openal播放声音类

Openal播放音频(win+android)

1、查看应用环境所用的声卡驱动

(OSS、ALSA、PulseAudio、PipeWire、Solaris、SndIO)

Windows-only(MMSystem、DSound、WASAPI)

PortAudio、PulseAudio、JACK、CoreAudio、

OpenSL (Android)、 Oboe (Android)、SDL2、Wave Writer、

Linux查看声卡及驱动命令

//查看设备

cat /proc/asound/cards

cat /proc/asound/devices

ll /dev/snd

//如果是alsa

cat /proc/asound/version //alsa版本

$ alsactl -v

$ aplay ./1.wav

android 录音机下载 采样率 录音采样率一般用多少_#include_02

2、编译

编译时确保:

For most systems, you will likely want to make sure ALSA, OSS, and PulseAudio were detected (if your target system uses them). For Windows, make sure DirectSound was detected.

使用alsa,要先编译alsa,并在cmakelist中设置目录变量

LINUX编译alsa

//修改cmakelist
set(ALSA_INCLUDE_DIR "/home/mysoft/alsa-lib-1.2.6/install/include")
set(ALSA_LIBRARY  "/home/mysoft/alsa-lib-1.2.6/install/lib")

//编译openal----
BUILD_LIBS=${HOME}/build_libs 
export PATH=${BUILD_LIBS}/bin:${PATH}
export PKG_CONFIG_PATH=${BUILD_LIBS}/lib/pkgconfig:${PKG_CONFIG_PATH} 
rm -rf build
mkdir build
cd build 
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=${BUILD_LIBS} .. 
make
make install

cmake时的输出:(提示需要设置alsa的头文件目录和库目录)

-- The C compiler identification is GNU 9.2.0
-- The CXX compiler identification is GNU 9.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29.1") 
-- Looking for posix_memalign
-- Looking for posix_memalign - found
-- Performing Test HAVE___RESTRICT
-- Performing Test HAVE___RESTRICT - Success
-- Performing Test HAVE_LIBATOMIC
-- Performing Test HAVE_LIBATOMIC - Success
-- Performing Test HAVE_FNO_MATH_ERRNO
-- Performing Test HAVE_FNO_MATH_ERRNO - Success
-- Performing Test HAVE_GCC_PROTECTED_VISIBILITY
-- Performing Test HAVE_GCC_PROTECTED_VISIBILITY - Success
-- Performing Test HAVE_VISIBILITY_HIDDEN_SWITCH
-- Performing Test HAVE_VISIBILITY_HIDDEN_SWITCH - Success
-- Performing Test HAVE_MSSE2_SWITCH
-- Performing Test HAVE_MSSE2_SWITCH - Success
-- Looking for xmmintrin.h
-- Looking for xmmintrin.h - found
-- Looking for emmintrin.h
-- Looking for emmintrin.h - found
-- Looking for pmmintrin.h
-- Looking for pmmintrin.h - found
-- Looking for smmintrin.h
-- Looking for smmintrin.h - found
-- Looking for arm_neon.h
-- Looking for arm_neon.h - not found
-- Performing Test HAVE_SSE_INTRINSICS
-- Performing Test HAVE_SSE_INTRINSICS - Success
-- Looking for malloc.h
-- Looking for malloc.h - found
-- Looking for cpuid.h
-- Looking for cpuid.h - found
-- Looking for intrin.h
-- Looking for intrin.h - not found
-- Looking for guiddef.h
-- Looking for guiddef.h - not found
-- Looking for initguid.h
-- Looking for initguid.h - not found
-- Looking for pow in m
-- Looking for pow in m - found
-- Looking for clock_gettime in rt
-- Looking for clock_gettime in rt - found
-- Looking for dlfcn.h
-- Looking for dlfcn.h - found
-- Looking for dlopen in dl
-- Looking for dlopen in dl - found
-- Performing Test HAVE_GCC_GET_CPUID
-- Performing Test HAVE_GCC_GET_CPUID - Success
-- Looking for posix_memalign
-- Looking for posix_memalign - found
-- Looking for _aligned_malloc
-- Looking for _aligned_malloc - not found        //--------
-- Looking for proc_pidpath
-- Looking for proc_pidpath - not found           //--------
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test HAVE_PTHREAD
-- Performing Test HAVE_PTHREAD - Success
-- Looking for pthread_setschedparam
-- Looking for pthread_setschedparam - found
-- Looking for include files pthread.h, pthread_np.h
-- Looking for include files pthread.h, pthread_np.h - not found    //--------
-- Looking for pthread_setname_np
-- Looking for pthread_setname_np - not found         //--------
-- Looking for pthread_set_name_np
-- Looking for pthread_set_name_np - not found        //--------
-- Looking for getopt
-- Looking for getopt - found
-- Could NOT find DBus1 (missing: DBus1_INCLUDE_DIRS DBus1_LIBRARIES)     //--------
-- Could NOT find ALSA (missing: ALSA_LIBRARY ALSA_INCLUDE_DIR)        //--------
-- Found OSS: /usr/include/x86_64-linux-gnu                    //--------
-- Checking for module 'libpipewire-0.3'
--   No package 'libpipewire-0.3' found                        //--------
-- Could NOT find AudioIO (missing: AUDIOIO_INCLUDE_DIR)       //--------
-- Could NOT find SoundIO (missing: SOUNDIO_LIBRARY SOUNDIO_INCLUDE_DIR)           //--------
-- Could NOT find PortAudio (missing: PORTAUDIO_LIBRARY PORTAUDIO_INCLUDE_DIR)     //--------
-- Could NOT find PulseAudio (missing: PULSEAUDIO_LIBRARY PULSEAUDIO_INCLUDE_DIR)  //--------
-- Could NOT find JACK (missing: JACK_LIBRARY JACK_INCLUDE_DIR)                    //--------
-- Could NOT find OpenSL (missing: OPENSL_LIBRARY OPENSL_INCLUDE_DIR OPENSL_ANDROID_INCLUDE_DIR)  //-------- 
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE  
-- Could NOT find SDL2 (missing: SDL2_LIBRARY SDL2_INCLUDE_DIR)      //--------
-- Could NOT find Git (missing: GIT_EXECUTABLE)                      //--------
-- Could NOT find ZLIB (missing: ZLIB_LIBRARY ZLIB_INCLUDE_DIR)      //--------
-- Could NOT find MySOFA (missing: MYSOFA_LIBRARY MYSOFA_INCLUDE_DIR ZLIB_FOUND)   //--------
-- Could NOT find SndFile (missing: SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR)           //--------
-- Could NOT find SDL2 (missing: SDL2_LIBRARY SDL2_INCLUDE_DIR)     //--------
-- 
-- Building OpenAL with support for the following backends:
--     OSS, WaveFile, Null                                         //--提示支持的驱动------
-- 
-- Building with support for CPU extensions:
--     Default, SSE, SSE2, SSE3, SSE4.1
-- 
-- Embedding HRTF datasets
-- 
-- Installing library and headers
-- Installing sample configuration
-- Installing HRTF data files
-- Installing AmbDec presets
-- 
-- Building utility programs
-- Building configuration program
-- 
-- Building example programs
-- 
-- Configuring done
-- Generating done
-- Build files have been written to: /home/mysoft/openal-soft-master/build

3、OpenAL常用 函数

//buffer
openal_buffer_create()      // Generate OpenAL buffer
openal_buffer_data()        // Load a buffer with data
openal_buffer_destroy()     // Destroys an OpenAL buffer
openal_buffer_get()       	// Retrieve an OpenAL buffer property
openal_buffer_loadwav() 	// Load a .wav file into a buffer
//context
openal_context_create() 	// Create an audio processing context
openal_context_current()  	// Make the specified context current
openal_context_destroy() 	// Destroys a context
openal_context_process() 	// Process the specified context
openal_context_suspend()  ——— Suspend the specified context
//device
openal_device_close()  ———— Close an OpenAL device
openal_device_open()  ———— Initialize the OpenAL audio layer
//listener
openal_listener_get()  ———— Retrieve a listener property
openal_listener_set()  ———— Set a listener property
//source
openal_source_create()  ——— Generate a source resource
openal_source_destroy()  ——— Destroy a source resource
openal_source_get()  ————— Retrieve an OpenAL source property
openal_source_pause()  ——— Pause the source
openal_source_play()  ———— Start playing the source
openal_source_rewind()  ——— Rewind the source
openal_source_set()  ———— Set source property
openal_source_stop()  ————Stop playing the source
openal_stream()  —————— Begin streaming on a source

4、调用过程:

//查看支持的设备
int checkEnumerationSupport()
{
    ALboolean enumeration;
    enumeration = alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
    if (enumeration == AL_FALSE) {
        // enumeration not supported
        std::cout << "enumerating devices NOT supported\n";
    } else {
        // enumeration supported
        std::cout << "enumerating devices supported\n";
    };
    return 0;
}
alutInit(NULL, NULL);
ALuint source1;              //存储source的ID
alGenSources(1, &source1);  //创建一个ALSource,并把其ID存入source1

ALuint buffer1 = alutCreateBufferFromFile("音频文件名.wav");
alSourcei(source1, AL_BUFFER, buffer1);
alSourcePlay(source1);

ALint state;
do{
    alGetSourcei(source1, AL_SOURCE_STATE, &state);
} while (state == AL_PLAYING);

alDeleteSources(1, &source1);
alDeleteBuffers(1, &buffer1);
alutExit();
//打开设备,创建设备
ALCdevice *dev = alcOpenDevice(NULL);
ALCcontextCurrent *cc = alcCreateContext(dev, NULL);
alcMakeContextCurrent(cc);
//创建音源和缓冲器
ALuint bid, sid;
alGenSources(1, &sid);
alGenBuffers(1, &bid);
//取得pcm数据,用缓冲区来关联它
ALvoid *data;
ALsizei size, bits, freq;
ALenum format;
ALboolean loop;
alutLoadWAVFile("boom.wav", &format, &data, &size, &freq, &loop); //alut中的函数,也可自己读进data
alBufferData(bid, format, data, size, freq);     //将数据存入buffer
//用音源关联缓冲器
alSourcei(sid, AL_BUFFER, bid);
//播放音源然后等待直到完成
alSourcePlay(sid);
ALint state;
do{
alGetSourcei(sid, AL_SOURCE_STATE, &state);    //查询状态,直到播完
}while(state == AL_PLAYING);     
//然后释放它
alDeleteSources(1, &sid);
alDeleteBuffers(1, &bid);
alcMakeContextCurrent(NULL);
alcDestroyContext(cc);
alcCloseDevice(dev);

三、音频图

Qt显示wav波形图

android 录音机下载 采样率 录音采样率一般用多少_采样率_03

QPainter音频图音频图

音频控件

Qt显示WAV音频文件的波形图/频谱图

H5录音音频可视化-实时波形频谱绘制、频率直方图

android 录音机下载 采样率 录音采样率一般用多少_数据_04

波形图的横坐标一般为时间,纵坐标一般为dB (即分贝)来表示;有的时候只关心振幅的趋势,那就对振幅进行归一化为 [-1,1]范围内。

android 录音机下载 采样率 录音采样率一般用多少_#include_05

四、语谱图(Spectrogram)

音频相关知识集合语谱图 Spectrogram 的分类语谱图(一) Spectrogram 的定义与机理语谱图(二) Spectrogram 的产生语谱图(三) Spectrogram 的实例语谱图(四) Mel spectrogram 梅尔语谱图语谱图(五) Mel_语谱图之MFCC系数信号处理(二)音频信号的分帧, 加窗梅尔频率倒谱系数(MFCC)的提取过程与C++代码实现