超级版主
- 积分
- 4746
- 金钱
- 4746
- 注册时间
- 2019-5-8
- 在线时间
- 1237 小时
|
本帖最后由 正点原子运营 于 2022-8-11 09:40 编辑
1)实验平台:正点原子阿尔法Linux开发板
2) 章节摘自【正点原子】《I.MX6U 嵌入式Qt开发指南》
3)购买链接:https://detail.tmall.com/item.htm?id=609033604451
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子阿尔法Linux交流群:1027879335
第十二章 多媒体 第十二章12.5 录音Qt提供了QAudioRecorder类录制音频,继承于QmediaRecorder类,音频输入可以使用QAudioRecorder或者QAudioInput类实现。QAudioRecorder是高级的类,输入的音频数据直接保存为一个音频文件。而QAudioInput则是低层次的实现,从类的名称可以知道它是与输入输出流有关的,它可以将音频录制的数据写入一个流设备。本小节将介绍使用QAudioRecorder录制音频并保存成一个mp3文件。并使用QAudioProbe类探测音频数据缓冲区里的实时音量大小设计一个实用的录音应用程序。
第十三章12.5.1 应用实例本例设计一个实用的录音界面,界面是编者原创界面。本例适用于正点原子ALPHA开发板,已经测试。Windows与Ubuntu下请读者使用Qt官方的audiorecorder例子自行测试,Windows系统上的声卡设置比较复杂,不详解,编者只确保正点原子I.MX6U ALPHA开发板正常运行此应用程序。Mini板没有声卡,请使用USB声卡插到正点原子I.MX6U开发板进行自行测试!!! 本例目的:录音程序的设计与使用。 例16_audiorecorder,录音程序(难度:难)。项目路径为Qt/2/16_audiorecorder。注意本例有用到qss样式文件,关于如何添加资源文件与qss文件请参考7.1.3小节。本例设计一个录音程序,录音功能部分直接参考Qt官方的audiorecorder例程,界面设计由编者设计。在Qt官方的audiorecorder例程里(自行在Qt官方的audiorecorder例程里打开查看),我们可以看到官方的例程录音设置都是通过QComboBox来选择的,当然这个只是一个Qt官方的例子,有很大的参考性。如果运用到实际项目里我们需要做一定的修改。如果是面向用户,我们就不需要暴露那么多信息给用户,同时也可以避免用户操作失败等等。所以编者参考Qt官方的例程重新设计了一个录音例程。源码如下,界面效果如12.5.2小节。 项目文件16_audiorecorder文件第一行添加的代码部分如下。
- 16_audiorecorder.pro编程后的代码
- 1 QT += core gui multimedia
- 2
- 3 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- 4
- 5 CONFIG += c++11
- 6
- 7 # The following define makes your compiler emit warnings if you use
- 8 # any Qt feature that has been marked deprecated (the exact warnings
- 9 # depend on your compiler). Please consult the documentation of the
- 10 # deprecated API in order to know how to port your code away from it.
- 11 DEFINES += QT_DEPRECATED_WARNINGS
- 12
- 13 # You can also make your code fail to compile if it uses deprecated APIs.
- 14 # In order to do so, uncomment the following line.
- 15 # You can also select to disable deprecated APIs only up to a certain version of Qt.
- 16 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
- 17
- 18 SOURCES += \
- 19 main.cpp \
- 20 audiorecorder.cpp
- 21
- 22 HEADERS += \
- 23 audiorecorder.h
- 24
- 25 # Default rules for deployment.
- 26 qnx: target.path = /tmp/${TARGET}/bin
- 27 else: unix:!android: target.path = /opt/${TARGET}/bin
- 28 !isEmpty(target.path): INSTALLS += target
- 29
- 30 RESOURCES += \
- 31 res.qrc
复制代码在头文件“audiorecorder.h”具体代码如下。 头文件里主要是声明界面所使用的元素及一些槽函数。重点是QAudioRecorder与QAudioProbe对象的声明。 在源文件“audiorecorder.cpp”具体代码如下。 - audiorecorder.cpp编程后的代码
- /******************************************************************
- Copyright (C) 2017 The Qt Company Ltd.
- Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
- * @projectName 16_audiorecorder
- * @brief audiorecorder.cpp
- * @author Deng Zhimao
- * @email 1252699831@qq.com
- * @net www.openedv.com
- * @date 2021-05-10
- *******************************************************************/
- 1 #include "audiorecorder.h"
- 2 #include <QDebug>
- 3 #include <QUrl>
- 4 #include <QDateTime>
- 5 #include <QDir>
- 6 #include <QCoreApplication>
- 7
- 8 static qreal getPeakValue(const QAudioFormat &format);
- 9 static QVector<qreal> getBufferLevels(const QAudioBuffer &buffer);
- 10
- 11 template <class T>
- 12 static QVector<qreal> getBufferLevels(const T *buffer, int frames, int channels);
- 13
- 14 AudioRecorder::AudioRecorder(QWidget *parent)
- 15 : QMainWindow(parent)
- 16 {
- 17 /* 初始化布局 */
- 18 layoutInit();
- 19
- 20 /* 录制音频的类 */
- 21 m_audioRecorder = new QAudioRecorder(this);
- 22
- 23 /* 用于探测缓冲区的数据 */
- 24 m_probe = new QAudioProbe(this);
- 25
- 26 /* 信号槽连接,更新录音level显示 */
- 27 connect(m_probe, &QAudioProbe::audioBufferProbed,
- 28 this, &AudioRecorder::processBuffer);
- 29
- 30 /* 设置探测的对象 */
- 31 m_probe->setSource(m_audioRecorder);
- 32
- 33 /* 播放器 */
- 34 recorderPlayer = new QMediaPlayer(this);
- 35
- 36 /* 播放列表 */
- 37 mediaPlaylist = new QMediaPlaylist(this);
- 38
- 39 recorderPlayer->setPlaylist(mediaPlaylist);
- 40
- 41 /* 设置播放模式,默认是列表播放 */
- 42 mediaPlaylist->setPlaybackMode(QMediaPlaylist::CurrentItemOnce);
- 43
- 44 /* 扫描本地声卡设备 */
- 45 devicesVar.append(QVariant(QString()));
- 46 for (auto &device: m_audioRecorder->audioInputs()) {
- 47 devicesVar.append(QVariant(device));
- 48 //qDebug()<<"本地声卡设备:"<<device<<endl;
- 49 }
- 50
- 51 /* 音频编码 */
- 52 codecsVar.append(QVariant(QString()));
- 53 for (auto &codecName: m_audioRecorder->supportedAudioCodecs()) {
- 54 codecsVar.append(QVariant(codecName));
- 55 //qDebug()<<"音频编码:"<<codecName<<endl;
- 56 }
- 57
- 58 /* 容器/支持的格式 */
- 59 containersVar.append(QVariant(QString()));
- 60 for (auto &containerName: m_audioRecorder->supportedContainers()) {
- 61 containersVar.append(QVariant(containerName));
- 62 //qDebug()<<"支持的格式:"<<containerName<<endl;
- 63 }
- 64
- 65 /* 采样率 */
- 66 sampleRateVar.append(QVariant(0));
- 67 for (int sampleRate: m_audioRecorder->supportedAudioSampleRates()) {
- 68 sampleRateVar.append(QVariant(sampleRate));
- 69 //qDebug()<<"采样率:"<<sampleRate<<endl;
- 70 }
- 71
- 72 /* 通道 */
- 73 channelsVar.append(QVariant(-1));
- 74 channelsVar.append(QVariant(1));
- 75 channelsVar.append(QVariant(2));
- 76 channelsVar.append(QVariant(4));
- 77
- 78 /* 质量 */
- 79 qualityVar.append(QVariant(int(QMultimedia::LowQuality)));
- 80 qualityVar.append(QVariant(int(QMultimedia::NormalQuality)));
- 81 qualityVar.append(QVariant(int(QMultimedia::HighQuality)));
- 82
- 83 /* 比特率 */
- 84 bitratesVar.append(QVariant(0));
- 85 bitratesVar.append(QVariant(32000));
- 86 bitratesVar.append(QVariant(64000));
- 87 bitratesVar.append(QVariant(96000));
- 88 bitratesVar.append(QVariant(128000));
- 89
- 90 /* 初始化时扫描已经录制的录音mp3文件 */
- 91 scanRecordFiles();
- 92
- 93 /* 录音类信号槽连接 */
- 94 connect(m_audioRecorder, &QAudioRecorder::durationChanged,
- 95 this, &AudioRecorder::updateProgress);
- 96
- 97 /* 列表信号槽连接 */
- 98 connect(listWidget, SIGNAL(itemClicked(QListWidgetItem*)),
- 99 this, SLOT(listWidgetCliked(QListWidgetItem*)));
- 100 connect(listWidget, SIGNAL(currentItemChanged(QListWidgetItem*,
- 101 QListWidgetItem*)),
- 102 this, SLOT(listWidgetCurrentItemChange(QListWidgetItem*,
- 103 QListWidgetItem*)));
- 104
- 105 /* 媒体连接信号槽 */
- 106 connect(recorderPlayer,
- 107 SIGNAL(stateChanged(QMediaPlayer::State)),
- 108 this,
- 109 SLOT(mediaPlayerStateChanged(QMediaPlayer::State)));
- 110 connect(mediaPlaylist,
- 111 SIGNAL(currentIndexChanged(int)),
- 112 this,
- 113 SLOT(mediaPlaylistCurrentIndexChanged(int)));
- 114 connect(recorderPlayer, SIGNAL(positionChanged(qint64)),
- 115 this,
- 116 SLOT(recorderPlayerPositionChanged(qint64)));
- 117
- 118 /* 按钮 */
- 119 connect(recorderBt, SIGNAL(clicked()), this, SLOT(recorderBtClicked()));
- 120 connect(nextBt, SIGNAL(clicked()), this, SLOT(nextBtClicked()));
- 121 connect(previousBt, SIGNAL(clicked()), this, SLOT(previousBtClicked()));
- 122 connect(removeBt, SIGNAL(clicked()), this, SLOT(removeBtClicked()));
- 123 }
- 124
- 125 AudioRecorder::~AudioRecorder()
- 126 {
- 127 }
- 128
- 129 void AudioRecorder::layoutInit()
- 130 {
- 131 this->setGeometry(0, 0, 800, 480);
- 132
- 133 mainWidget = new QWidget();
- 134 setCentralWidget(mainWidget);
- 135
- 136 vBoxLayout = new QVBoxLayout();
- 137 bottomWidget = new QWidget();
- 138 listWidget = new QListWidget();
- 139 listWidget->setFocusPolicy(Qt::NoFocus);
- 140 listWidget->setVerticalScrollBarPolicy(
- 141 Qt::ScrollBarAlwaysOff);
- 142 listWidget->setHorizontalScrollBarPolicy(
- 143 Qt::ScrollBarAlwaysOff);
- 144
- 145 /* 垂直布局 */
- 146 vBoxLayout->addWidget(listWidget);
- 147 vBoxLayout->addWidget(bottomWidget);
- 148 vBoxLayout->setContentsMargins(0, 0, 0, 0);
- 149 mainWidget->setLayout(vBoxLayout);
- 150
- 151 bottomWidget->setMinimumHeight(80);
- 152 bottomWidget->setMaximumHeight(80);
- 153 bottomWidget->setStyleSheet("background:#cccccc");
- 154
- 155 /* 水平布局 */
- 156 hBoxLayout = new QHBoxLayout();
- 157
- 158 /* 按钮,录音、上一首、下一首、删除项按钮 */
- 159 recorderBt = new QPushButton();
- 160 previousBt = new QPushButton();
- 161 nextBt = new QPushButton();
- 162 removeBt = new QPushButton();
- 163
- 164 recorderBt->setCheckable(true);
- 165 recorderBt->setObjectName("recorderBt");
- 166 recorderBt->setFocusPolicy(Qt::NoFocus);
- 167 recorderBt->setMaximumSize(60, 60);
- 168 recorderBt->setMinimumSize(60, 60);
- 169
- 170 hBoxLayout->setContentsMargins(0, 0, 0, 0);
- 171
- 172 bottomWidget->setLayout(hBoxLayout);
- 173 hBoxLayout->addWidget(recorderBt);
- 174 hBoxLayout->addWidget(previousBt);
- 175 hBoxLayout->addWidget(nextBt);
- 176 hBoxLayout->addWidget(removeBt);
- 177
- 178 nextBt->setMaximumSize(50, 50);
- 179 removeBt->setMaximumSize(50, 50);
- 180 previousBt->setMaximumSize(50, 50);
- 181
- 182 previousBt->setObjectName("previousBt");
- 183 removeBt->setObjectName("removeBt");
- 184 nextBt->setObjectName("nextBt");
- 185
- 186 previousBt->setFocusPolicy(Qt::NoFocus);
- 187 removeBt->setFocusPolicy(Qt::NoFocus);
- 188 nextBt->setFocusPolicy(Qt::NoFocus);
- 189
- 190 /* 显示录音时长与录音Level */
- 191 centerWidget = new QWidget(this);
- 192 centerWidget->setGeometry(width()/ 2 - 150,
- 193 height() /2 - 100,
- 194 300,
- 195 200);
- 196 centerWidget->setStyleSheet("QWidget { background:#8823242a;"
- 197 "border-radius:10px}");
- 198 countLabel = new QLabel(centerWidget);
- 199 countLabel->setGeometry(0,
- 200 0,
- 201 300,
- 202 50);
- 203 countLabel->setStyleSheet("QLabel {font-size: 30px;color:#eeeeee;"
- 204 "font: blod;background:transparent}");
- 205 countLabel->setAlignment(Qt::AlignCenter);
- 206 levelHBoxLayout = new QHBoxLayout();
- 207
- 208 for (int i = 0; i < 4; i++) {
- 209 progressBar[i] = new QProgressBar();
- 210 progressBar[i]->setOrientation(Qt::Vertical);
- 211 progressBar[i]->setRange(0, 100);
- 212 progressBar[i]->setVisible(false);
- 213 progressBar[i]->setMaximumWidth(centralWidget()->width());
- 214 levelHBoxLayout->addWidget(progressBar[i]);
- 215 levelHBoxLayout->setContentsMargins(5, 50, 5, 5);
- 216 progressBar[i]->setStyleSheet("QWidget { background:#22eeeeee;"
- 217 "border-radius:0px}");
- 218 }
- 219 centerWidget->setLayout(levelHBoxLayout);
- 220 centerWidget->hide();
- 221 countLabel->raise();
- 222
- 223
- 224 }
- 225
- 226 void AudioRecorder::recorderBtClicked()
- 227 {
- 228 /* 录音前停止正在播放的媒体 */
- 229 if (recorderPlayer->state() != QMediaPlayer::StoppedState)
- 230 recorderPlayer->stop();
- 231 /* 如果录音已经停止,则开始录音 */
- 232 if (m_audioRecorder->state() == QMediaRecorder::StoppedState) {
- 233 /* 设置默认的录音设备 */
- 234 m_audioRecorder->setAudioInput(devicesVar.at(0).toString());
- 235
- 236 /* 下面的是录音设置,都是选择默认,可根据录音可用项,自行修改 */
- 237 QAudioEncoderSettings settings;
- 238 settings.setCodec(codecsVar.at(0).toString());
- 239 settings.setSampleRate(sampleRateVar[0].toInt());
- 240 settings.setBitRate(bitratesVar[0].toInt());
- 241 settings.setChannelCount(channelsVar[0].toInt());
- 242 settings.setQuality(QMultimedia::EncodingQuality(
- 243 qualityVar[0].toInt()));
- 244 /* 以恒定的质量录制,可选恒定的比特率 */
- 245 settings.setEncodingMode(QMultimedia::ConstantQualityEncoding);
- 246 QString container = containersVar.at(0).toString();
- 247 m_audioRecorder->setEncodingSettings(settings,
- 248 QVideoEncoderSettings(),
- 249 container);
- 250 m_audioRecorder->setOutputLocation(
- 251 QUrl::fromLocalFile(tr("./Sounds/%1.mp3")
- 252 .arg(QDateTime::currentDateTime()
- 253 .toString())));
- 254 /* 开始录音 */
- 255 m_audioRecorder->record();
- 256 /* 显示录制时长标签 */
- 257 countLabel->clear();
- 258 centerWidget->show();
- 259 } else {
- 260 /* 停止录音 */
- 261 m_audioRecorder->stop();
- 262 /* 重设录音level */
- 263 clearAudioLevels();
- 264 /* 隐藏录制时长标签 */
- 265 centerWidget->hide();
- 266 /* 重新扫描录音文件 */
- 267 scanRecordFiles();
- 268 }
- 269 }
- 270
- 271 void AudioRecorder::scanRecordFiles()
- 272 {
- 273 mediaPlaylist->clear();
- 274 listWidget->clear();
- 275 mediaObjectInfo.clear();
- 276 /* 录音文件保存在当前Sounds文件夹下 */
- 277 QDir dir(QCoreApplication::applicationDirPath()
- 278 + "/Sounds");
- 279 QDir dirbsolutePath(dir.absolutePath());
- 280
- 281 /* 如果文件夹不存在,则创建一个 */
- 282 if(!dirbsolutePath.exists())
- 283 dirbsolutePath.mkdir(dirbsolutePath.absolutePath());
- 284
- 285 /* 定义过滤器 */
- 286 QStringList filter;
- 287 /* 包含所有xx后缀的文件 */
- 288 filter<<"*.mp3";
- 289 /* 获取该目录下的所有文件 */
- 290 QFileInfoList files =
- 291 dirbsolutePath.entryInfoList(filter, QDir::Files);
- 292 /* 遍历 */
- 293 for (int i = 0; i < files.count(); i++) {
- 294 MediaObjectInfo info;
- 295 /* 使用utf-8编码 */
- 296 info.fileName = QString::fromUtf8(files.at(i)
- 297 .fileName()
- 298 .toUtf8()
- 299 .data());
- 300 info.filePath = QString::fromUtf8(files.at(i)
- 301 .filePath()
- 302 .toUtf8()
- 303 .data());
- 304 /* 媒体列表添加音频 */
- 305 if (mediaPlaylist->addMedia(
- 306 QUrl::fromLocalFile(info.filePath))) {
- 307 /* 添加到容器数组里储存 */
- 308 mediaObjectInfo.append(info);
- 309 /* 添加音频名字至列表 */
- 310 listWidget->addItem(
- 311 new QListWidgetItem(QIcon(":/icons/play.png"),
- 312 info.fileName));
- 313 } else {
- 314 qDebug()<<
- 315 mediaPlaylist->errorString()
- 316 .toUtf8().data()
- 317 << endl;
- 318 qDebug()<< " Error number:"
- 319 << mediaPlaylist->error()
- 320 << endl;
- 321 }
- 322 }
- 323 }
- 324
- 325 void AudioRecorder::listWidgetCliked(QListWidgetItem *item)
- 326 {
- 327 /* item->setIcon 为设置列表里的图标状态 */
- 328 for (int i = 0; i < listWidget->count(); i++) {
- 329 listWidget->item(i)->setIcon(QIcon(":/icons/play.png"));
- 330 }
- 331
- 332 if (recorderPlayer->state() != QMediaPlayer::PlayingState) {
- 333 recorderPlayer->play();
- 334 item->setIcon(QIcon(":/icons/pause.png"));
- 335 } else {
- 336 recorderPlayer->pause();
- 337 item->setIcon(QIcon(":/icons/play.png"));
- 338 }
- 339 }
- 340
- 341 void AudioRecorder::listWidgetCurrentItemChange(
- 342 QListWidgetItem *currentItem,
- 343 QListWidgetItem *previousItem)
- 344 {
- 345 if (mediaPlaylist->mediaCount() == 0)
- 346 return;
- 347
- 348 if (listWidget->row(previousItem) != -1)
- 349 previousItem->setText(mediaObjectInfo
- 350 .at(listWidget->row(previousItem))
- 351 .fileName);
- 352
- 353 /* 先暂停播放媒体 */
- 354 if (recorderPlayer->state() == QMediaPlayer::PlayingState)
- 355 recorderPlayer->pause();
- 356
- 357 /* 设置当前媒体 */
- 358 mediaPlaylist->
- 359 setCurrentIndex(listWidget->row(currentItem));
- 360 }
- 361
- 362 void AudioRecorder::mediaPlayerStateChanged(
- 363 QMediaPlayer::State
- 364 state)
- 365 {
- 366 for (int i = 0; i < listWidget->count(); i++) {
- 367 listWidget->item(i)
- 368 ->setIcon(QIcon(":/icons/play.png"));
- 369 }
- 370
- 371 /* 获取当前项,根据当前媒体的状态,然后设置不同的图标 */
- 372 if (mediaPlaylist->currentIndex() == -1)
- 373 return;
- 374 QListWidgetItem *item = listWidget->item(
- 375 mediaPlaylist->currentIndex());
- 376
- 377 switch (state) {
- 378 case QMediaPlayer::PausedState:
- 379 case QMediaPlayer::PlayingState:
- 380 item->setIcon(QIcon(":/icons/pause.png"));
- 381 break;
- 382 case QMediaPlayer::StoppedState:
- 383 item->setIcon(QIcon(":/icons/play.png"));
- 384 break;
- 385 }
- 386 }
- 387
- 388 void AudioRecorder::mediaPlaylistCurrentIndexChanged(
- 389 int index)
- 390 {
- 391 if (-1 == index)
- 392 return;
- 393 }
- 394
- 395 void AudioRecorder::previousBtClicked()
- 396 {
- 397 /* 上一首操作 */
- 398 recorderPlayer->stop();
- 399 int count = listWidget->count();
- 400 if (0 == count)
- 401 return;
- 402 if (listWidget->currentRow() == -1)
- 403 listWidget->setCurrentRow(0);
- 404 else {
- 405 if (listWidget->currentRow() - 1 != -1)
- 406 listWidget->setCurrentRow(
- 407 listWidget->currentRow() - 1);
- 408 else
- 409 listWidget->setCurrentRow(listWidget->count() - 1);
- 410 }
- 411 mediaPlaylist->setCurrentIndex(listWidget->currentRow());
- 412 recorderPlayer->play();
- 413 }
- 414
- 415 void AudioRecorder::nextBtClicked()
- 416 {
- 417 /* 下一首操作 */
- 418 recorderPlayer->stop();
- 419
- 420 /* 获取列表的总数目 */
- 421 int count = listWidget->count();
- 422
- 423 /* 如果列表的总数目为0则返回 */
- 424 if (0 == count)
- 425 return;
- 426
- 427 if (listWidget->currentRow() == -1)
- 428 listWidget->setCurrentRow(0);
- 429 else {
- 430 if (listWidget->currentRow() + 1 < listWidget->count())
- 431 listWidget->setCurrentRow(
- 432 listWidget->currentRow() + 1);
- 433 else
- 434 listWidget->setCurrentRow(0);
- 435 }
- 436 mediaPlaylist->setCurrentIndex(listWidget->currentRow());
- 437 recorderPlayer->play();
- 438 }
- 439
- 440 void AudioRecorder::removeBtClicked()
- 441 {
- 442 int index = listWidget->currentRow();
- 443 if (index == -1)
- 444 return;
- 445
- 446 /* 移除媒体的项 */
- 447 mediaPlaylist->removeMedia(index);
- 448
- 449 /* 指向要删除的文件 */
- 450 QFile file(mediaObjectInfo.at(index).filePath);
- 451
- 452 /* 移除录音文件 */
- 453 file.remove();
- 454
- 455 /* 删除列表选中的项 */
- 456 listWidget->takeItem(index);
- 457
- 458 /* 删除后设置当前项为删除项的前一个 */
- 459 if (index - 1 != -1)
- 460 listWidget->setCurrentRow(index - 1);
- 461 }
- 462
- 463 void AudioRecorder::updateProgress(qint64 duration)
- 464 {
- 465 if (m_audioRecorder->error()
- 466 != QMediaRecorder::NoError)
- 467 return;
- 468
- 469 /* 显示录制时长 */
- 470 countLabel->setText(tr("已录制 %1 s")
- 471 .arg(duration / 1000));
- 472 }
- 473
- 474 void AudioRecorder::recorderPlayerPositionChanged(
- 475 qint64 position)
- 476 {
- 477 /* 格式化时间 */
- 478 int p_second = position / 1000;
- 479 int p_minute = p_second / 60;
- 480 p_second %= 60;
- 481
- 482 QString mediaPosition;
- 483 mediaPosition.clear();
- 484
- 485 if (p_minute >= 10)
- 486 mediaPosition = QString::number(p_minute, 10);
- 487 else
- 488 mediaPosition = "0" + QString::number(p_minute, 10);
- 489
- 490 if (p_second >= 10)
- 491 mediaPosition = mediaPosition
- 492 + ":" + QString::number(p_second, 10);
- 493 else
- 494 mediaPosition = mediaPosition
- 495 + ":0" + QString::number(p_second, 10);
- 496
- 497
- 498 int d_second = recorderPlayer->duration() / 1000;
- 499 int d_minute = d_second / 60;
- 500 d_second %= 60;
- 501
- 502 QString mediaDuration;
- 503 mediaDuration.clear();
- 504
- 505 if (d_minute >= 10)
- 506 mediaDuration = QString::number(d_minute, 10);
- 507 else
- 508 mediaDuration = "0" + QString::number(d_minute, 10);
- 509
- 510 if (d_second >= 10)
- 511 mediaDuration = mediaDuration
- 512 + ":" + QString::number(d_second, 10);
- 513 else
- 514 mediaDuration = mediaDuration
- 515 + ":0" + QString::number(d_second, 10);
- 516
- 517 QString fileNmae = mediaObjectInfo
- 518 .at(listWidget->currentRow()).fileName + "\t";
- 519 /* 显示媒体总长度时间与播放的当前位置 */
- 520 listWidget->currentItem()->setText(fileNmae
- 521 + mediaPosition
- 522 +"/" + mediaDuration);
- 523 }
- 524
- 525 void AudioRecorder::clearAudioLevels()
- 526 {
- 527 for (int i = 0; i < 4; i++)
- 528 progressBar[i]->setValue(0);
- 529 }
- 530
- 531 // This function returns the maximum possible sample value for a given audio format
- 532 qreal getPeakValue(const QAudioFormat& format)
- 533 {
- 534 // Note: Only the most common sample formats are supported
- 535 if (!format.isValid())
- 536 return qreal(0);
- 537
- 538 if (format.codec() != "audio/pcm")
- 539 return qreal(0);
- 540
- 541 switch (format.sampleType()) {
- 542 case QAudioFormat::Unknown:
- 543 break;
- 544 case QAudioFormat::Float:
- 545 if (format.sampleSize() != 32) // other sample formats are not supported
- 546 return qreal(0);
- 547 return qreal(1.00003);
- 548 case QAudioFormat::SignedInt:
- 549 if (format.sampleSize() == 32)
- 550 return qreal(INT_MAX);
- 551 if (format.sampleSize() == 16)
- 552 return qreal(SHRT_MAX);
- 553 if (format.sampleSize() == 8)
- 554 return qreal(CHAR_MAX);
- 555 break;
- 556 case QAudioFormat::UnSignedInt:
- 557 if (format.sampleSize() == 32)
- 558 return qreal(UINT_MAX);
- 559 if (format.sampleSize() == 16)
- 560 return qreal(USHRT_MAX);
- 561 if (format.sampleSize() == 8)
- 562 return qreal(UCHAR_MAX);
- 563 break;
- 564 }
- 565
- 566 return qreal(0);
- 567 }
- 568
- 569 // returns the audio level for each channel
- 570 QVector<qreal> getBufferLevels(const QAudioBuffer& buffer)
- 571 {
- 572 QVector<qreal> values;
- 573
- 574 if (!buffer.format().isValid() || buffer.format().byteOrder() != QAudioFormat::LittleEndian)
- 575 return values;
- 576
- 577 if (buffer.format().codec() != "audio/pcm")
- 578 return values;
- 579
- 580 int channelCount = buffer.format().channelCount();
- 581 values.fill(0, channelCount);
- 582 qreal peak_value = getPeakValue(buffer.format());
- 583 if (qFuzzyCompare(peak_value, qreal(0)))
- 584 return values;
- 585
- 586 switch (buffer.format().sampleType()) {
- 587 case QAudioFormat::Unknown:
- 588 case QAudioFormat::UnSignedInt:
- 589 if (buffer.format().sampleSize() == 32)
- 590 values = getBufferLevels(buffer.constData<quint32>(), buffer.frameCount(), channelCount);
- 591 if (buffer.format().sampleSize() == 16)
- 592 values = getBufferLevels(buffer.constData<quint16>(), buffer.frameCount(), channelCount);
- 593 if (buffer.format().sampleSize() == 8)
- 594 values = getBufferLevels(buffer.constData<quint8>(), buffer.frameCount(), channelCount);
- 595 for (int i = 0; i < values.size(); ++i)
- 596 values[i] = qAbs(values.at(i) - peak_value / 2) / (peak_value / 2);
- 597 break;
- 598 case QAudioFormat::Float:
- 599 if (buffer.format().sampleSize() == 32) {
- 600 values = getBufferLevels(buffer.constData<float>(), buffer.frameCount(), channelCount);
- 601 for (int i = 0; i < values.size(); ++i)
- 602 values[i] /= peak_value;
- 603 }
- 604 break;
- 605 case QAudioFormat::SignedInt:
- 606 if (buffer.format().sampleSize() == 32)
- 607 values = getBufferLevels(buffer.constData<qint32>(), buffer.frameCount(), channelCount);
- 608 if (buffer.format().sampleSize() == 16)
- 609 values = getBufferLevels(buffer.constData<qint16>(), buffer.frameCount(), channelCount);
- 610 if (buffer.format().sampleSize() == 8)
- 611 values = getBufferLevels(buffer.constData<qint8>(), buffer.frameCount(), channelCount);
- 612 for (int i = 0; i < values.size(); ++i)
- 613 values[i] /= peak_value;
- 614 break;
- 615 }
- 616
- 617 return values;
- 618 }
- 619
- 620 template <class T>
- 621 QVector<qreal> getBufferLevels(const T *buffer, int frames, int channels)
- 622 {
- 623 QVector<qreal> max_values;
- 624 max_values.fill(0, channels);
- 625
- 626 for (int i = 0; i < frames; ++i) {
- 627 for (int j = 0; j < channels; ++j) {
- 628 qreal value = qAbs(qreal(buffer[i * channels + j]));
- 629 if (value > max_values.at(j))
- 630 max_values.replace(j, value);
- 631 }
- 632 }
- 633
- 634 return max_values;
- 635 }
- 636
- 637 void AudioRecorder::processBuffer(const QAudioBuffer& buffer)
- 638 {
- 639 /* 根据通道数目需要显示count个level */
- 640 int count = buffer.format().channelCount();
- 641 for (int i = 0; i < 4; i++) {
- 642 if (i < count)
- 643 progressBar[i]->setVisible(true);
- 644 else
- 645 progressBar[i]->setVisible(false);
- 646 }
- 647
- 648 /* 设置level的值 */
- 649 QVector<qreal> levels = getBufferLevels(buffer);
- 650 for (int i = 0; i < levels.count(); ++i)
- 651 progressBar[i]->setValue(levels.at(i) * 100);
- 652 }
复制代码 布局与播放录音部分的代码编者不再解释,与音乐播放器和视频播放器的原理一样。只是换了个样式而已。 第21行,初始化录音类对象,m_audioRecorder。录音的功能全靠这个类了,要完成录音的工作,我们只需要关注这个类。剩下的都是其他功能的实现。 第44~88行,本例将录音设置的参数,参数可以从Qt提供的API里如supportedAudioCodecs()表示可用支持的编码方式,详细请参考代码,全部使用QVariant容器来储存。这样可以不必要暴露太多接口给用户修改,以免出错。 第222~269,是录音功能的重要代码,一般是通过QAudioEncoderSettings来设置输入音频设置,主要是编码格式、采样率、通道数、音频质量等设置(音频格式Qt提供了如setCodes()等方式可以直接设置,音频相关知识不探讨,我们只需要知道这个流程即可。)。这些参考Qt官方的audiorecorder例程,使用默认的设置,也就是Default项,Qt自动选择系统音频输入设备输入,同时自动确定底层的采样参数等。在Linux里,Qt扫描声卡的设备项很多,让用户选择可能会导致出错。所以编者测试使用默认的设置,即可录音,无需用户自行选择和修改。同时设置了录音保存的文件名为xx.mp3。根据系统时间命名录音文件,如果不指定录音文件名,在Linux下一般保存为clip_0001.mov,clip_0002.mov等录音文件(mov格式为苹果操作系统常用音视频格式)。Windows一般保存为clip_0001.wav格式文件。 设置完成录音项后,使用QAudioRecorder的record()、pause()和stop()函数即可完成录音。本例没有使用pause()也就是录音暂停,根据情景无需录音暂停。record()和stop()是开始录音和录音停止。 第23~28行,QAudioProbe类型的对象用于探测缓冲区的数据。setSource()是指定探测的对象。 第525~652行,这部分代码是直接拷贝Qt官方的代码进行修改,代码比较复杂(闲时自行理解),我们只需要知道它是从缓冲区里获取通道数与音频的实时音量。 整个代码主要完成录音的功能是QAudioRecorder、QAudioEncoderSettings和QAudioProbe类。其他代码可以没有也可以完成本例录音的功能。QAudioRecorder负责录音,QAudioProbe类负责获取通道数,与输入的实时音量。只要掌握了这两个类,设计一个好看的录音应用界面不在话下。 main .cpp内容如下,主要是加载qss样式文件。 - 1 #include "audiorecorder.h"
- 2
- 3 #include <QApplication>
- 4 #include <QFile>
- 5
- 6 int main(int argc, char *argv[])
- 7 {
- 8 QApplication a(argc, argv);
- 9 /* 指定文件 */
- 10 QFile file(":/style.qss");
- 11
- 12 /* 判断文件是否存在 */
- 13 if (file.exists() ) {
- 14 /* 以只读的方式打开 */
- 15 file.open(QFile::ReadOnly);
- 16 /* 以字符串的方式保存读出的结果 */
- 17 QString styleSheet = QLatin1String(file.readAll());
- 18 /* 设置全局样式 */
- 19 qApp->setStyleSheet(styleSheet);
- 20 /* 关闭文件 */
- 21 file.close();
- 22 }
- 23
- 24 AudioRecorder w;
- 25 w.show();
- 26 return a.exec();
- 27 }
复制代码style.qss样式文件如下。素材已经在源码处提供。注意下面的style.qss不能有注释! - 1 QTabBar:tab {
- 2 height:0; width:0;
- 3 }
- 4
- 5 QWidget {
- 6 background:#e6e6e6;
- 7 }
- 8
- 9 QListWidget {
- 10 border:none;
- 11 }
- 12
- 13 QPushButton#recorderBt {
- 14 border-image:url(:/icons/recorder_stop1.png);
- 15 background:transparent;
- 16 }
- 17
- 18 QPushButton#recorderBt:hover {
- 19 border-image:url(:/icons/recorder_stop2.png);
- 20 }
- 21
- 22 QPushButton#recorderBt:checked {
- 23 border-image:url(:/icons/recorder_start1.png);
- 24 }
- 25
- 26 QPushButton#recorderBt:checked:hover {
- 27 border-image:url(:/icons/recorder_start2.png);
- 28 }
- 29
- 30 QListWidget {
- 31 color:black;
- 32 font-size: 20px;
- 33 border:none;
- 34 icon-size:40px;
- 35 }
- 36
- 37 QListWidget:item:active {
- 38 background: transparent;
- 39 }
- 40
- 41 QListWidget:item {
- 42 background: transparent;
- 43 height:60;
- 44 }
- 45
- 46 QListWidget:item:selected {
- 47 color:red;
- 48 background: transparent;
- 49 }
- 50
- 51 QListWidget:item:hover {
- 52 background: transparent;
- 53 color:red;
- 54 border:none;
- 55 }
- 56
- 57 QPushButton#nextBt {
- 58 border-image:url(:/icons/btn_next1.png);
- 59 }
- 60
- 61 QPushButton#nextBt:hover {
- 62 border-image:url(:/icons/btn_next2.png);
- 63 }
- 64
- 65 QPushButton#previousBt {
- 66 border-image:url(:/icons/btn_previous1.png);
- 67 }
- 68
- 69 QPushButton#previousBt:hover {
- 70 border-image:url(:/icons/btn_previous2.png);
- 71 }
- 72
- 73 QPushButton#removeBt {
- 74 border-image:url(:/icons/remove1.png);
- 75 }
- 76
- 77 QPushButton#removeBt:hover {
- 78 border-image:url(:/icons/remove2.png);
- 79 }
- 80
- 81 QProgressBar::chunk {
- 82 background-color: #f6f6f6;
- 83 height: 8px;
- 84 margin: 0.5px;
- 85 border-radius:0px;
- 86 }
- 87
- 88 QProgressBar {
- 89 color:transparent;
- 90 }
复制代码 第十四章12.5.2 程序运行效果本例适用于正点原子I.MX6U ALPHA开发板!请使用正点原子I.MX6U的出厂系统进行测试! 请使用正点原子的I.MX6U的出厂时的系统测试! 请使用正点原子的I.MX6U的出厂时的系统测试! 请使用正点原子的I.MX6U的出厂时的系统测试! 重要的事情是说三遍! 在正点原子I.MX6U开发板上运行此录音程序,需要先配置是麦克风(板子上的麦头)或者是Line_in输入方式。 如果是麦头录音,则在板子上运行开启麦头录音的脚本。 - /home/root/shell/audio/mic_in_config.sh
复制代码如果是Line_in的方式录音,请使用一条3.5mm的两头公头的音频线,一头对板子上Line_in接口。另一头连接手机或者电脑的音频输出设备。手机或者电脑开始播放音乐,音乐尽量调高!执行下面的脚本,开启板子以line_in的方式录音。 - /home/root/shell/audio/line_in_config.sh
复制代码点击左下角的录音按钮开始录音(Ubuntu上模拟效果图)。可以看到两个通道的实时音量柱状图,用于显示实时音量输入的大小。 开如播放录音。(Ubuntu上模拟效果图)
|
|