一、功能介绍
运行环境:Windows10 64位
QT通过QAudioInput类读取声卡PCM数据,在封装WAV头,转为WAV格式的文件保存到本地。
可以选择指定的声卡录制、播放音频。
代码里固定录制10S的声音,有进度条显示录制和播放的进度。
在Android、ubuntu、windows测试运行OK。
注意:在其他系统运行,要注意代码里定义WAV头的结构体字节对齐方式--必须1字节对齐才行。
完整项目代码下载(下载可编译运行,不懂可以私信): https://download.csdn.net/download/xiaolong1126626497/19354758
二、核心代码
mainwindow.h文件代码:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QtNetwork/QUdpSocket> #include <QAudio> //这五个是QT处理音频的库 #include <QAudioFormat> #include <QAudioInput> #include <QAudioOutput> #include <QIODevice> #include <QFile> #include <QTimer> #include <QDir> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: void SetStyle(const QString &qssFile); QFile sourceFile; // class member. QFile destinationFile; // Class member QAudioFormat auido_input_format; QTimer timer_progressBar; int progressBar_val; MainWindow(QWidget *parent = nullptr); ~MainWindow(); QAudioInput *audio_in; QAudioOutput *audio_out; void Log_Display(QString text); qint64 CreateWavFile(QString catheFileName , QString wavFileName); private slots: void update_progressBar(); void on_pushButton_clicked(); void stopRecording(); void handleStateChanged_input(QAudio::State newState); void handleStateChanged_out(QAudio::State newState); void on_pushButton_2_clicked(); void on_pushButton_3_clicked(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp文件代码:
#include "mainwindow.h" #include "ui_mainwindow.h" //设置录音的时间--ms #define AUDIO_INPUT_TIME 10000 //#define ANDROID_DEVICE #ifdef ANDROID_DEVICE //设置保存文件的路径 #define SAVE_FILE_PATH "/sdcard/DS_XIAOLONG/test.raw" #else //设置保存文件的路径 #define SAVE_FILE_PATH "test.pcm" #define SAVE_WAV_FILE_PATH "test.wav" #endif /* * 设置QT界面的样式 */ void MainWindow::SetStyle(const QString &qssFile) { QFile file(qssFile); if (file.open(QFile::ReadOnly)) { QString qss = QLatin1String(file.readAll()); qApp->setStyleSheet(qss); QString PaletteColor = qss.mid(20,7); qApp->setPalette(QPalette(QColor(PaletteColor))); file.close(); } else { qApp->setStyleSheet(""); } } //日志信息显示 void MainWindow::Log_Display(QString text) { ui->plainTextEdit->insertPlainText(text); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); this->SetStyle(":/images/blue.css" ); //设置样式表 this->setWindowIcon(QIcon(":/images/log.ico")); //设置图标 this->setWindowTitle("录音机"); //创建工作目录 #ifdef ANDROID_DEVICE QDir dir; if(!dir.exists("/sdcard/DS_XIAOLONG")) { if(dir.mkdir("/sdcard/DS_XIAOLONG")) { Log_Display("/sdcard/DS_XIAOLONG目录创建成功.\n"); } else { Log_Display("/sdcard/DS_XIAOLONG目录创建失败.\n"); } } #endif //进度条更新 progressBar_val=0; ui->progressBar->setRange(0,AUDIO_INPUT_TIME); ui->progressBar->setValue(0); connect(&timer_progressBar, SIGNAL(timeout()), this, SLOT(update_progressBar())); } void MainWindow::stopRecording() { Log_Display("停止录音.\n"); audio_in->stop(); destinationFile.close(); } MainWindow::~MainWindow() { delete ui; } //录音状态 void MainWindow::handleStateChanged_input(QAudio::State newState) { switch (newState) { case QAudio::StoppedState: if (audio_in->error() != QAudio::NoError) { // Error handling Log_Display("录音出现错误.\n"); } else { // Finished recording Log_Display("完成录音\n"); //将PCM文件转为WAV文件 CreateWavFile(SAVE_FILE_PATH,SAVE_WAV_FILE_PATH); } break; case QAudio::ActiveState: // Started recording - read from IO device Log_Display("开始从IO设备读取PCM声音数据.\n"); break; default: // ... other cases as appropriate break; } } //开始采集音频数据 void MainWindow::on_pushButton_clicked() { static bool flag1=1; if(flag1) //只需要运行一次 { flag1=0; //设置录音的格式 auido_input_format.setSampleRate(16000); //设置采样率以对赫兹采样。 auido_input_format.setChannelCount(1); //将通道数设置为通道。 auido_input_format.setSampleSize(16); /*将样本大小设置为指定的sampleSize(以位为单位)通常为8或16,但是某些系统可能支持更大的样本量。*/ auido_input_format.setCodec("audio/pcm"); //设置编码格式 auido_input_format.setByteOrder(QAudioFormat::LittleEndian); //样本是小端字节顺序 auido_input_format.setSampleType(QAudioFormat::UnSignedInt); //样本是无符号整数 //选择默认设备作为输入源 QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice(); Log_Display(tr("当前的录音设备的名字:%1\n").arg(info.deviceName())); //判断输入的格式是否支持,如果不支持就使用系统支持的默认格式 if(!info.isFormatSupported(auido_input_format)) { auido_input_format=info.nearestFormat(auido_input_format); /* * 返回与系统支持的提供的设置最接近的QAudioFormat。 这些设置由所使用的平台/音频插件提供。 它们还取决于所使用的QAudio :: Mode。 */ } //当前设备支持的编码 Log_Display("当前设备支持的编码格式:\n"); QStringList list=info.supportedCodecs(); for(int i=0;i<list.size();i++) { Log_Display(list.at(i)+"\n"); } Log_Display(tr("当前录音的采样率=%1\n").arg(auido_input_format.sampleRate())); Log_Display(tr("当前录音的通道数=%1\n").arg(auido_input_format.channelCount())); Log_Display(tr("当前录音的样本大小=%1\n").arg(auido_input_format.sampleSize())); Log_Display(tr("当前录音的编码格式=%1\n").arg(auido_input_format.codec())); audio_in = new QAudioInput(auido_input_format, this); connect(audio_in,SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged_input(QAudio::State))); } if(audio_in->state()==QAudio::StoppedState) { // qDebug()<<"没有处理任何数据.\n"; //设置采集的时间 QTimer::singleShot(AUDIO_INPUT_TIME,this,SLOT(stopRecording())); destinationFile.setFileName(SAVE_FILE_PATH); destinationFile.open( QIODevice::WriteOnly | QIODevice::Truncate); audio_in->start(&destinationFile); progressBar_val=0; ui->progressBar->setFormat("录音进度%p"); timer_progressBar.start(1000); //开始定时器--显示进度条 } } //更新进度条 void MainWindow::update_progressBar() { progressBar_val+=1000; //1000ms if(progressBar_val>=AUDIO_INPUT_TIME)timer_progressBar.stop(); ui->progressBar->setValue(progressBar_val); } //开始播放音频 void MainWindow::on_pushButton_2_clicked() { static bool flag=1; if(flag) { flag=0; QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); if(!info.isFormatSupported(auido_input_format)) { Log_Display("后端不支持原始音频格式,无法播放音频.\n"); return; } //当前设备支持的编码 Log_Display("当前设备支持的编码格式:\n"); QStringList list=info.supportedCodecs(); for(int i=0;i<list.size();i++) { Log_Display(list.at(i)+"\n"); } audio_out = new QAudioOutput(auido_input_format,this); connect(audio_out,SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged_out(QAudio::State))); } sourceFile.setFileName(SAVE_FILE_PATH); sourceFile.open(QIODevice::ReadOnly); audio_out->start(&sourceFile); progressBar_val=0; ui->progressBar->setFormat("播放进度%p"); timer_progressBar.start(1000); //开始定时器--显示进度条 } //播放音频的反馈信息 void MainWindow::handleStateChanged_out(QAudio::State newState) { switch (newState){ case QAudio::IdleState: // Finished playing (no more data) audio_out->stop(); sourceFile.close(); Log_Display("音频播放完成.\n"); break; case QAudio::StoppedState: // Stopped for other reasons if (audio_out->error() != QAudio::NoError) { Log_Display("播放音频出现错误.\n"); } break; default: // ... other cases as appropriate break; } } //查询可用的音频设备列表 void MainWindow::on_pushButton_3_clicked() { foreach(const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) Log_Display(tr("Device name:%1\n").arg(deviceInfo.deviceName())); } struct WAVFILEHEADER { // RIFF 头 char RiffName[4]; unsigned long nRiffLength; // 数据类型标识符 char WavName[4]; // 格式块中的块头 char FmtName[4]; unsigned long nFmtLength; // 格式块中的块数据 unsigned short nAudioFormat; unsigned short nChannleNumber; unsigned long nSampleRate; unsigned long nBytesPerSecond; unsigned short nBytesPerSample; unsigned short nBitsPerSample; // 数据块中的块头 char DATANAME[4]; unsigned long nDataLength; }; // 将生成的.raw文件转成.wav格式文件; qint64 MainWindow::CreateWavFile(QString PcmFileName,QString wavFileName) { // 开始设置WAV的文件头 WAVFILEHEADER WavFileHeader; qstrcpy(WavFileHeader.RiffName,"RIFF"); qstrcpy(WavFileHeader.WavName, "WAVE"); qstrcpy(WavFileHeader.FmtName, "fmt "); qstrcpy(WavFileHeader.DATANAME,"data"); // 表示 FMT块 的长度 WavFileHeader.nFmtLength = 16; // 表示 按照PCM 编码; WavFileHeader.nAudioFormat = 1; // 声道数目; WavFileHeader.nChannleNumber = 1; // 采样频率; WavFileHeader.nSampleRate = 16000; // nBytesPerSample 和 nBytesPerSecond这两个值通过设置的参数计算得到; // 数据块对齐单位(每个采样需要的字节数 = 通道数 × 每次采样得到的样本数据位数 / 8 ) WavFileHeader.nBytesPerSample = 2; // 波形数据传输速率 // (每秒平均字节数 = 采样频率 × 通道数 × 每次采样得到的样本数据位数 / 8 = 采样频率 × 每个采样需要的字节数 ) WavFileHeader.nBytesPerSecond = 32000; // 每次采样得到的样本数据位数; WavFileHeader.nBitsPerSample = 16; QFile cacheFile(PcmFileName); QFile wavFile(wavFileName); if (!cacheFile.open(QIODevice::ReadWrite)) { return -1; } if (!wavFile.open(QIODevice::WriteOnly)) { return -2; } int nSize = sizeof(WavFileHeader); qint64 nFileLen = cacheFile.bytesAvailable(); WavFileHeader.nRiffLength = static_cast<unsigned long>(nFileLen - 8 + nSize); //static_cast<类型>(变量); //新式的强制转换 WavFileHeader.nDataLength = static_cast<unsigned long>(nFileLen); // 先将wav文件头信息写入,再将音频数据写入; wavFile.write((const char *)&WavFileHeader,nSize); wavFile.write(cacheFile.readAll()); cacheFile.close(); wavFile.close(); return nFileLen; }