简述

在上一篇 Qt 之 WAV文件解析 中详细地分析了wav格式文件的文件头信息。通过QAudioInput实现录音功能,但是录音生成的文件并不能用播放器打开,就算更改后缀名也无法识别(有时候下载的一些音频文件通过修改文件名可以播放)。在Qt助手中将录音生成的文件保存为.raw格式,那么这个raw到底是什么格式呢?

其实看raw字面的意思是原始的、未处理的、未加工的,从此看来QAudioInput 生成的音频文件未经过处理,即文件保存的数据全部为音频数据,没有文件头,播放器识别不了。好了,既然知道这个原因导致播放器播不了,那么我们就手动给.raw文件添加上头信息,转为wav格式,这样不仅可以通过QAudioOutput来播放,同时播放器也能够播放该音频文件。

代码之路

代码思路

这里主要是通过QAudioInput来生成音频文件,录音结束后,将.raw音频文件转为.wav格式文件,在 Qt 之 WAV文件解析 中介绍了wav文件头的数据结构,这里我们只要将这个结构的数据加在.raw文件的头部即可,代码中通过addWavHeader方法将.raw文件转成.wav文件。播放音频文件利用QAudioOutput类即可,既可播放.raw文件也可以播放.wav文件。我们这里就直接播放重新生成的.wav格式的音频文件。

仔细看代码有点多,其实实现很简单,这里我添加的一些代码是用来实现一个简单完整的小录音机功能。

MyAudioInput.cpp

#include "myaudioinput.h"
#include <QAudioDeviceInfo>
#include <QDebug>
#include <QMessageBox>#define RAW_RECORD_FILENAME "F:/audio/test.raw"         // 录音文件名;
#define WAV_RECORD_FILENAME "F:/audio/test.wav"         // 录音文件转wav格式文件名;const qint64 TIME_TRANSFORM = 1000 * 1000;              // 微妙转秒;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;
};MyAudioInput::MyAudioInput(QWidget *parent): QWidget(parent), m_isRecord(false), m_isPlay(false), m_RecordTimerId(0), m_RecordTime(0)
{ui.setupUi(this);// 录音,播放 等按钮 信号槽;connect(ui.pButtonRecord, SIGNAL(clicked()), this, SLOT(onStartRecord()));connect(ui.pButtonStopRecord, SIGNAL(clicked()), this, SLOT(onStopRecording()));connect(ui.pButtonPlay, SIGNAL(clicked()), this, SLOT(onPlay()));connect(ui.pButtonStopPlay, SIGNAL(clicked()), this, SLOT(onStopPlay()));// 输出当前设备支持的音频编码格式;QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());qDebug()<< "AudioDevice supportedCodecs : " << info.supportedCodecs();
}MyAudioInput::~MyAudioInput()
{}void MyAudioInput::onStartRecord()
{// 如果正在播放则停止播放;if (m_isPlay){onStopPlay();}// 如果当前没有开始录音则允许录音;if (!m_isRecord){// 判断本地设备是否支持该格式QAudioDeviceInfo audioDeviceInfo = QAudioDeviceInfo::defaultInputDevice();// 判断本地是否有录音设备;if (!audioDeviceInfo.isNull()){m_isRecord = true;destinationFile.setFileName(RAW_RECORD_FILENAME);destinationFile.open(QIODevice::WriteOnly | QIODevice::Truncate);// 设置音频文件格式;QAudioFormat format;// 设置采样频率;format.setSampleRate(8000);// 设置通道数;format.setChannelCount(1);// 设置每次采样得到的样本数据位值;format.setSampleSize(16);// 设置编码方法;format.setCodec("audio/pcm");// 设置采样字节存储顺序;format.setByteOrder(QAudioFormat::LittleEndian);// 设置采样类型;format.setSampleType(QAudioFormat::UnSignedInt);// 判断当前设备设置是否支持该音频格式;if (!audioDeviceInfo.isFormatSupported(format)){qDebug() << "Default format not supported, trying to use the nearest.";format = audioDeviceInfo.nearestFormat(format);}// 开始录音;m_audioInput = new QAudioInput(format, this);m_audioInput->start(&destinationFile);// 开启时钟,用于更新当前录音时间;if (m_RecordTimerId == 0){m_RecordTimerId = startTimer(100);}}else{// 没有录音设备;QMessageBox::information(NULL, tr("Record"), tr("Current No Record Device"));}}else{// 当前正在录音;QMessageBox::information(NULL, tr("Record"), tr("Current is Recording"));}
}void MyAudioInput::onStopRecording()
{// 当前正在录音时,停止录音;if (m_isRecord){// 关闭时钟,并初始化数据;killTimer(m_RecordTimerId);m_RecordTime = 0;m_RecordTimerId = 0;m_isRecord = false;ui.labelTime->setText(QString("Idle : %1/S").arg(m_RecordTime));if (m_audioInput != NULL){m_audioInput->stop();destinationFile.close();delete m_audioInput;m_audioInput = NULL;}// 将生成的.raw文件转成.wav格式文件;if (addWavHeader(RAW_RECORD_FILENAME, WAV_RECORD_FILENAME) > 0)QMessageBox::information(NULL, tr("Save"), tr("RecordFile Save Success"));}
}void MyAudioInput::onPlay()
{// 当前没有录音才播放;if (!m_isRecord){// 如果正在播放则关闭播放,准备重新播放;if (m_isPlay){onStopPlay();}m_isPlay = true;sourceFile.setFileName(WAV_RECORD_FILENAME);sourceFile.open(QIODevice::ReadOnly);// 设置播放音频格式;QAudioFormat format;format.setSampleRate(8000);format.setChannelCount(1);format.setSampleSize(16);format.setCodec("audio/pcm");format.setByteOrder(QAudioFormat::LittleEndian);format.setSampleType(QAudioFormat::UnSignedInt);QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());qDebug() << info.supportedCodecs();if (!info.isFormatSupported(format)){qWarning() << "Raw audio format not supported by backend, cannot play audio.";return;}m_audioOutput = new QAudioOutput(format, this);connect(m_audioOutput, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));m_audioOutput->start(&sourceFile);// 开启时钟,用于更新当前播放时间;if (m_RecordTimerId == 0){m_RecordTimerId = startTimer(100);}}   else{// 当前正在录音;QMessageBox::information(NULL, tr("Record"), tr("Current is Recording"));}
}void MyAudioInput::onStopPlay()
{// 如果当前在播放则停止播放;if (m_isPlay){// 关闭时钟,并初始化数据;killTimer(m_RecordTimerId);m_RecordTime = 0;m_RecordTimerId = 0;m_isPlay = false;ui.labelTime->setText(QString("Idle : %1/S").arg(m_RecordTime));if (m_audioOutput != NULL){m_audioOutput->stop();sourceFile.close();delete m_audioOutput;m_audioOutput = NULL;}}
}// 播放状态更新;
void MyAudioInput::handleStateChanged(QAudio::State state)
{switch (state) {case QAudio::IdleState:// Finished playing (no more data)onStopPlay();break;case QAudio::StoppedState:// Stopped for other reasonsif (m_audioOutput->error() != QAudio::NoError) {// Error handling}break;default:// ... other cases as appropriatebreak;}
}// 时钟事件;
void MyAudioInput::timerEvent(QTimerEvent *event)
{if (event->timerId() == m_RecordTimerId){QString strState;if (m_isRecord){strState = "Recording";m_RecordTime = m_audioInput->elapsedUSecs() / TIME_TRANSFORM;}else if (m_isPlay){strState = "Playing";m_RecordTime = m_audioOutput->elapsedUSecs() / TIME_TRANSFORM;}ui.labelTime->setText(QString("%1 : %2/S").arg(strState).arg(m_RecordTime));}
}// 将生成的.raw文件转成.wav格式文件;
qint64 MyAudioInput::addWavHeader(QString catheFileName , QString wavFileName)
{// 开始设置WAV的文件头  // 这里具体的数据代表什么含义请看上一篇文章(Qt之WAV文件解析)中对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 = 8000;// nBytesPerSample 和 nBytesPerSecond这两个值通过设置的参数计算得到;// 数据块对齐单位(每个采样需要的字节数 = 通道数 × 每次采样得到的样本数据位数 / 8 )WavFileHeader.nBytesPerSample = 2;// 波形数据传输速率// (每秒平均字节数 = 采样频率 × 通道数 × 每次采样得到的样本数据位数 / 8  = 采样频率 × 每个采样需要的字节数 )WavFileHeader.nBytesPerSecond = 16000;// 每次采样得到的样本数据位数;WavFileHeader.nBitsPerSample = 16;QFile cacheFile(catheFileName);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 = nFileLen - 8 + nSize;WavFileHeader.nDataLength = nFileLen;// 先将wav文件头信息写入,再将音频数据写入;wavFile.write((char *)&WavFileHeader, nSize);wavFile.write(cacheFile.readAll());cacheFile.close();wavFile.close();return nFileLen;
}

程序截图

点击录音开始录音,再次点击会弹出提示,当前正在录音,这样防止多次点击录音,导致重复录音。点击停止录音,结束录音,并将生成的.raw文件转成.wav文件,点击播放,则播放.wav音频文件。多次点击播放会重新播放,点击停止播放,则停止当前播放。
这里做了一些简单的逻辑判断,避免多次点击按钮导致生成的文件错误,同时在时钟事件中不断更新当前状态。

生成的文件截图

仔细对比两个文件,二者大小相差44个字节,这44个字节即为手动添加的wav文件头信息,而前者是不能用播放器打开,后者可以直接用播放器播放。


从上图中可以通过文件头来判断是否是一个wav文件,该文件的前4位为 “52 49 46 46” ,即为 “RIFF”。后面的数据也是按照wav文件头数据结构依次存储。有兴趣可以对照上一篇文章对wav文件头的介绍,将生成的wav文件头信息解析出来。

以下是对wav文件解析后的数据:

具体如何解析可以参考 Qt 之 解析wav文件的头信息(详细分析、对比不同wav文件的数据)。

特别注意

在利用QAudioInput生成音频时需要设置音频的格式(通过QAudioFormat来设置),这里设置的格式要与 转为wav文件时设置的一系列参数 以及 在用 QAudioOutput 进行播放时设置的格式要完全一致,否则会导致声音文件识别不了,或者播放声音不清楚或者就只能听见嗡嗡的声音,所以一定要保持格式的一致性。至于在格式中参数的取值,到底对生成的音频文件有什么影响,将在下篇中进行解答。


下一篇将继续介绍用Qt直接生成wav格式的文件,不需要手动来添加wav文件头,同时也会用代码来解析一个wav文件的头信息,以及在生成时设置的一些格式参数对音频文件的影响等,下次见 。


代码下载

Qt之实现录音播放及raw(pcm)转wav格式


其他文章:

Qt 之 WAV文件解析
Qt 之 解析wav文件的头信息(详细分析、对比不同wav文件的数据)。

Qt之实现录音播放及raw(pcm)转wav格式相关推荐

  1. 【音视频数据数据处理 10】【PCM篇】将PCM转为WAV格式音频

    [音视频数据数据处理 10][PCM篇]将PCM转为WAV格式音频 一.WAV头信息 1.1 RIFF区块 1.2 FORMAT区块 1.3 DATA区块 二.PCM 转 WAV 代码实现 PCM转为 ...

  2. 录音播放系统(实现pcm转码mp3,wav)

    本文介绍了基于waveX低级音频API采集音频,然后播放的技术,也支持实时的播放.对于将录音和播放分开的做法原因是为了保存pcm文件(未经压缩过的音频文件), 然后转码wav和mp3格式. PCM(P ...

  3. android 字节转wav,Android音频开发(4):PCM转WAV格式音频

    前面几篇已经介绍了PCM音频文件的录制,这一篇主要介绍下pcm转wav. 一.wav 和 pcm 一般通过麦克风采集的录音数据都是PCM格式的,即不包含头部信息,播放器无法知道音频采样率.位宽等参数, ...

  4. PCM、WAV格式介绍及用C语言实现PCM转WAV

    1.PCM格式介绍: PCM(Pulse Code Modulation)也被称为 脉码编码调制.PCM中的声音数据没有被压缩,如果是单声道的文件,采样数据按时间的先后顺序依次存入.(它的基本组织单位 ...

  5. 如何将PCM转换为WAV格式?

    许多大多数媒体播放器和便携式设备不支持PCM文件,它们兼容的音频格式为MP3,WAV等.那么如何才能将PCM格式转换为WAV格式呢?这里为大家介绍一款名为AnyMP4 Video Converter ...

  6. 音频PCM转WAV格式

    一. WAV与PCM WAV:一种无损的音频文件格式,符合 PIFF(Resource Interchange File Format)规范.所有的WAV都有一个文件头(文件头占44字节). PCM: ...

  7. pcm转wav,解决浏览器无法直接播放pcm格式的音频数据

    1. 创建PCM2WAV类 const stream = require('stream') const waveheader = require('waveheader')class PCM2WAV ...

  8. java实现录音并保存为wav格式的音频文件

    前言:本意是想像个录屏的软件,这篇先从录音功能开始. 整体思路:采用java官方API--TargetDataLine,从声卡中采集音频数据达到录音效果,采集的数据为PCM裸流,再将PCM转为wav格 ...

  9. PCM和WAV音频格式的区别,以及python自动转换

    目录 WAV和PCM的简单介绍 PCM WAV 关于音频的基础知识 声道数channels 采样位数bits 采样频率sample_rate 进阶内容 互相转换代码 WAV和PCM的简单介绍 PCM ...

最新文章

  1. 《Redis in action》读书笔记
  2. 【转】Unity3D研究院之使用Xamarin Studio调试Unity程序
  3. Maven 使用bat批量清除本地仓库的lastUpdated文件
  4. Silverlight学习笔记(九)-----RenderTransform特效【五种基本变换】及【矩阵变换MatrixTransform】...
  5. python xlrd使用_python处理Excel xlrd的简单使用
  6. 诗与远方:无题(七)
  7. 帧传送、关联与身份验证状态
  8. [渝粤教育] 西南科技大学 运输组织学 在线考试复习资料
  9. Linux系统基础开发应用及Linux-C用户手册
  10. 在自动驾驶技术上,一向自信满满的马斯克也承认了特斯拉的不足
  11. 施耐德PLC Unity Pro xl 软件使用四
  12. STM32基于Proteus虚拟仿真电源设置
  13. SVM -支持向量机原理详解与实践之四
  14. vue 3 的devtools beta 版离线下载
  15. 最新Java面试题整理!java字符大写转小写
  16. java中怎么输入中文_MultiMC下载-MultiMC中文实用版 v1.0
  17. 操作系统13章(个人笔记)
  18. 程序员专不专业,这些词汇拼写一看便知!
  19. Learn Computer Vision-计算机视觉学习
  20. 了解文件系统调用吗?如何实现的?

热门文章

  1. 周末又没有了......
  2. 如何快速获取并分析自己所在城市的房价行情?
  3. WPF 动态切换黑|白皮肤
  4. nowcoder20 A 柯朵莉
  5. np.c_[xx.ravel(),yy.ravel()]
  6. 1、Java中“并发编程”详解【voliate、synchronized、JMM内存模型、原子类操作Atomic..、CAS原理】
  7. rsync—远程同步
  8. Ubuntu之which查看命令所在位置
  9. Givens旋转变换
  10. C语言学习笔记—P1(<C语言初阶>+<数据类型>+图解+题例)