目录
该程序是一个基于Qt框架的音频采集与播放工具,支持实时波形显示、数据保存及回放。以下是实现思路与方法:
核心功能
-
音频采集
-
通过
QAudioSource
从默认音频输入设备(如麦克风)实时获取音频数据。 -
支持自定义采样率、通道数、采样格式等参数。
-
数据可保存为
.raw
文件,便于后续分析或回放。
-
-
实时波形显示
-
使用
QChart
动态绘制音频波形。 -
仅支持
UInt8
格式单通道数据的实时显示(通过QLineSeries
实现)。
-
-
音频回放
-
通过
QAudioSink
播放已保存的音频文件,支持与采集时相同的音频格式参数。 -
实现代码:
//播放文件 void MainWindow::on_actPlayFile_triggered() { QString filename=ui->editFileName->text().trimmed(); if(filename.isEmpty()){ QMessageBox::critical(this,"警告","未设置播放文件"); return; } if(!QFile::exists(filename)){ QMessageBox::critical(this,"警告","文件不可用"); return; } sinkfileDevice.setFileName(filename); if(!sinkfileDevice.open(QIODeviceBase::ReadOnly)){ QMessageBox::critical(this,"警告","无法打开指定文件!"); return; } QAudioFormat format; format.setChannelCount(ui->spinChanCount->value()); format.setSampleRate(ui->spinSampRate->value()); int index=ui->comboSampFormat->currentIndex(); format.setSampleFormat(QAudioFormat::SampleFormat(index)); audiosink=new QAudioSink(format,this); audiosink->start(&sinkfileDevice); m_isworking=true; ui->actPlayFile->setEnabled(false); ui->actStart->setEnabled(false); connect(audiosink,&QAudioSink::stateChanged,this,&MainWindow::do_stateChanged); }
-
-
参数配置与验证
-
提供界面控件调整音频格式参数(如采样率、通道数)。
-
支持测试当前参数是否被设备支持。
-
关键技术实现
-
音频数据处理
-
自定义
TMyDevice
类(继承自QIODevice
):-
在
writeData()
中处理输入数据:-
文件保存:直接将二进制数据写入文件。
-
波形更新:将音频数据转换为
QPointF
点集,动态更新图表。
-
-
数据截断逻辑:当数据量超过图表容量(
m_range
)时:-
截取旧数据的后
m_range - len
个点。 -
调整旧数据的X坐标(从
0
开始),避免新数据堆积在右侧。 -
追加新数据并更新序列,确保图表始终显示最新内容。
-
实现代码如下:
if(save_to_file){ m_file.write(data,len); } if(draw_chart){ //更新序列用到qlist容器 QList<QPointF>points; //预分配空间 points.reserve(m_range); //原来的序列中点的数量 int oldcount =series->points().size(); //如果序列中数据满了,截断从len到m_range中的数据放到list中 if(oldcount>=m_range){ const int keepcount = m_range - len; // 需要保留的旧数据数量 points = series->points().mid(oldcount - keepcount); // 正确截取旧数据 for (int i = 0; i < points.size(); ++i) { points[i].setX(i); } } else { points=series->points(); } //如果当前的序列没满,就把新的数据补上,如果满了循环不会执行 int curcount=points.size(); for (int k = 0; k < len; k++) { qreal x = (curcount + k) % m_range; // X 坐标循环在 0~m_range-1 之间 quint8 value = static_cast<quint8>(data[k]); // 确保 Y 值正确转换 points.append(QPointF(x, value)); } //更新序列 series->replace(points); emit updateBlockSize(len); return len;
这是我写的写入函数,其中更新图表数据的功能是首先如果有新的数据要进序列,我们就把原来序列前面去掉一部分,后面的数据往前走,给新数据留出相应的位置,这就到了数据截断这有一个坑,按照deepseek分析是调用point.mid()函数直接截取比一个一个点填入更为高效,但问题就是出现在这里,这里的QList容器装的数据类型是qpointf,他是数据点,是一个二维的,之前我光想着截取下来替换series中的数据点,但没想到mid()是截取了数据点的完整数据,即(x,y)全截取下来了,这就造成了一个问题,我们截取下来的数据点要往左移,右边append新的数据点,如果未更新x值,就会造成图表显示异常,他的曲线全部拥挤在图标右边。 解决方法就是把list容器中的值遍历一遍,给他们的x值重新赋值。这里长教训了,要注意截取的数据是什么类型。
-
-
-
-
动态图表
-
初始化:
inichart()
创建QLineSeries
并绑定坐标轴。 -
性能优化:使用
series->replace(points)
批量更新数据,而非逐点添加。 -
坐标轴范围:X轴固定为
0 ~ m_maxsize
(2000),Y轴固定为0 ~ 256
(适配UInt8
数据)。 -
实现代码:
//mainwindow.cpp void MainWindow::inichart() { QChart *chart=new QChart; chart->setTitle("音频输入原始信号"); ui->chartView->setChart(chart); series =new QLineSeries(); chart->addSeries(series); //series->setUseOpenGL(true);//opengl加速 //坐标轴设置 QValueAxis *axisX=new QValueAxis; axisX->setRange(0,m_maxsize); axisX->setLabelFormat("%g"); axisX->setTitleText("samples"); QValueAxis *axisY=new QValueAxis; axisY->setRange(0,256);//chart只能使用UTF_8 axisY->setTitleText("Audio Level"); chart->addAxis(axisX,Qt::AlignBottom); chart->addAxis(axisY,Qt::AlignLeft); series->attachAxis(axisX); series->attachAxis(axisY); }
m_maxsize是预设值,后面可以被覆盖,y轴只能是0~256,为了适配UTF—8。
-
-
线程与事件处理
-
主线程处理UI交互与数据更新,未单独分离数据处理线程(可能导致界面卡顿,但代码未体现)。
-
通过信号
updateBlockSize
更新录制时间与数据块大小。
-
-
错误处理
-
设备检测:若无可用音频设备,禁用功能并提示。
-
文件操作:检查文件路径有效性,避免无效写入。
-
潜在改进方向
-
多线程优化:将数据写入与图表更新移至子线程,避免主线程阻塞。
-
扩展格式支持:当前仅支持
UInt8
单通道波形显示,可适配更多格式(如Int16
)。 -
错误恢复机制:增加文件写入失败的回滚逻辑,确保数据完整性。
-
性能监控:添加CPU/内存占用显示,帮助用户优化参数配置。
总结
该程序通过Qt多媒体模块实现了音频采集、实时波形显示与回放功能,核心难点在于动态数据的截断与图表更新。通过自定义TMyDevice
类高效处理数据流,结合QChart
的批量更新机制,确保了实时性的同时解决了旧版本的数据显示异常问题。