简介
QMediaPlayer 可以用于播放经过压缩的音频文件,如 MP3 文件和 WMA 文件。QSoundEffect 可以 用于播放低延迟音效文件,例如无压缩的 WAV 文件。这两个类都可以用于播放本地文件和网络文件。
QMediaPlayer 与播放音频相关的接口函数如下:
void setAudioOutput(QAudioOutput *output) //设置一个音频输出设备
QAudioOutput *audioOutput() //返回播放器关联的音频输出设备信息
void setSource(const QUrl &source) //设置播放媒介来源,本地文件或网络文件
QUrl source() //当前播放的媒介来源
void setActiveAudioTrack(int index) //设置当前的音频轨道
void setPlaybackRate(qreal rate) //设置播放速度,1.0 表示正常速度
void setLoops(int loops) //设置播放的循环次数
QMediaPlayer::PlaybackState playbackState() //返回当前播放器状态
QMediaMetaData metaData() //返回当前媒介的元数据
QMediaPlayer::MediaStatus mediaStatus() //媒介状态(正在缓冲、已下载等),对于网络媒介比较有用
bool hasAudio() //当前媒介是否有音频
bool hasVideo() //当前媒介是否有视频
qint64 duration() //媒介的持续时间,单位为 ms
void setPosition(qint64 position) //设置当前的播放位置,单位为 ms
qint64 position() //返回当前的播放位置,单位为 ms
void play() //开始播放
void pause() //暂停播放
void stop() //停止播放
一般使用步骤
1、创建QMediaPlayer 对象,使用函数setAudioOutput()设置一个音频输出设备,
2、使用函数setSource()设置播放媒介来源(可以是本地文件或网络文件)
3、使用函数数 play() 开始播放了。使用 pause()和 stop()函数可以暂停和停止播放。
QMediaPlayer常用的一些信号如下:
void durationChanged(qint64 duration) //媒介的持续时间发生变化
void mediaStatusChanged(QMediaPlayer::MediaStatus status) //媒介状态发生变化
void metaDataChanged() //媒介的元数据发生变化
void playbackStateChanged(QMediaPlayer::PlaybackState newState) //播放器状态发生变化
void positionChanged(qint64 position) //播放位置发生变化
void sourceChanged(const QUrl &media) //媒介来源发生变化
QMediaPlayer 在开始、暂停或停止播放时,播放器状态发生变化,会发playbackStateChanged()
信号,函数 playbackState()会返回当前播放器状态。
媒介有元数据,函数 metaData()可以返回当前媒介的元数据,重新设置媒介时会发射 metaDataChanged()信号。媒介的元数据是 QMediaMetaData 类型数据,元数据用“key-value”形式 的键值对表示,QMediaMetaData 主要有以下几个函数:
QList<QMediaMetaData::Key> keys() //返回键名称列表
QString stringValue(QMediaMetaData::Key key) //以字符串形式返回一个键的数据
QVariant value(QMediaMetaData::Key key) //以 QVariant 类型返回一个键的数据
媒介元数据的键用枚举类型 QMediaMetaData::Key 的常量表示,常见枚举常量表示的元数据的类型和意义如下:
示例程序
该播放器可以打开多个文件后连续播放,可以显示播 放进度、歌曲对应的图片,还可以设置静音、调节音 量。文件列表里的项可以被拖动,从而改变其在列表 里的位置。
主窗口头文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QtMultimedia>
#include <QListWidgetItem>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTprivate:QMediaPlayer *player; //播放器bool loopPlay=true; //是否循环播放QString durationTime; //文件总长度,mm:ss字符串QString positionTime; //当前播放到位置,mm:ss字符串QUrl getUrlFromItem(QListWidgetItem *item); //获取item的用户数据bool eventFilter(QObject *watched, QEvent *event); //事件过滤处理
public:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots://自定义槽函数void do_stateChanged(QMediaPlayer::PlaybackState state); //播放器状态发生变化void do_sourceChanged(const QUrl &media); //文件发生变化void do_durationChanged(qint64 duration); //文件长度发生变化void do_positionChanged(qint64 position); //播放位置发生变化void do_metaDataChanged(); //元数据发生变化void on_btnAdd_clicked(); //添加按钮void on_btnPlay_clicked(); //播放按钮void on_btnPause_clicked(); //暂停按钮void on_btnStop_clicked(); //停止按钮void on_listWidget_doubleClicked(const QModelIndex &index); //双击事件void on_btnClear_clicked(); //清空列表 void on_sliderVolumn_valueChanged(int value); //音量条数值改变void on_btnSound_clicked(); //点击音量按钮void on_sliderPosition_valueChanged(int value); //音频条数值改变void on_btnPrevious_clicked(); //上一曲void on_btnNext_clicked(); //下一曲void on_btnLoop_clicked(bool checked); //循环播放void on_doubleSpinBox_valueChanged(double arg1); //播放倍数void on_btnRemove_clicked(); //移除private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H
这里为主窗口定义了一个事件过滤器eventFilter(),后续listWidget的事件委托给窗口来监视并处理。被监视对象(这里是listWidget)使用函数 installEventFilter()将自己注册给监视对象(Mainwindow),监视对象就是事件过滤器。监视对象重新实现函数 eventFilter(),对监视到的事件进行处理。
在主窗口构造函数中,listWidget将自己的事件通过installEventFilter()委托给主窗口的eventFilter,接下来创建了QMediaPlayer对象用于音频播放。创建的QAudioOutput指向默认音频输出设备,然后用 setAudioOutput() 函数设置播放器player的音频输出设备。最后为QMediaPlayer对象发射的一些信号设置了相关的槽函数用于界面响应和控制。
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);ui->listWidget->installEventFilter(this); //安装事件过滤器,将窗口对象设置为事件过滤器ui->listWidget->setDragEnabled(true); //允许拖放操作ui->listWidget->setDragDropMode(QAbstractItemView::InternalMove); //列表项可移动player = new QMediaPlayer(this); //创建音视频对象QAudioOutput *audioOutput = new QAudioOutput(this); //音频输出,指向默认的音频输出设备player->setAudioOutput(audioOutput); //设置音频输出connect(player,&QMediaPlayer::positionChanged, //播放位置发生变化this, &MainWindow::do_positionChanged);connect(player,&QMediaPlayer::durationChanged, //播放源长度发生变化this, &MainWindow::do_durationChanged);connect(player, &QMediaPlayer::sourceChanged, //播放源发生变化this, &MainWindow::do_sourceChanged);connect(player, &QMediaPlayer::playbackStateChanged, //播放器状态发生变化this, &MainWindow::do_stateChanged);connect(player, &QMediaPlayer::metaDataChanged, //元数据发生变化this, &MainWindow::do_metaDataChanged);
}//为listWidget安装事件过滤器,用于delete按键移除曲目
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{if (event->type() != QEvent::KeyPress) //不是KeyPress事件,退出return QWidget::eventFilter(watched,event);QKeyEvent *keyEvent=static_cast<QKeyEvent *>(event);if (keyEvent->key() != Qt::Key_Delete) //按下的不是Delete键,退出return QWidget::eventFilter(watched,event);if (watched==ui->listWidget) //判断被监视的对象是否是listWidget{QListWidgetItem *item= ui->listWidget->takeItem(ui->listWidget->currentRow());delete item;}return true; //表示事件已经被处理
}
QAudioOutput 是指向音频输出设备的类,它有如下几个函数:
void setDevice(const QAudioDevice &device) //设置一个 QAudioDevice 设备
QAudioDevice device() //返回当前的 QAudioDevice 设备
void setMuted(bool muted) //设置是否静音
bool isMuted() //是否静音了
void setVolume(float volume) //设置音量
float volume() //返回当前音量
一般创建QAudioOutput时就已经将其指向了默认的音频输出设备,如果要设置其他输出设备使用setDevice()函数,device()函数返回当前的输出设备,类型是QMediaDevices。
QMediaDevices 类提供系统内 的多媒体设备信息,它有以下几个静态函数,用于返回系统中的默认多媒体设备:
QAudioDevice QMediaDevices::defaultAudioInput() //返回默认的音频输入设备(如麦克风)信息
QAudioDevice QMediaDevices::defaultAudioOutput() //返回默认的音频输出设备(如音箱)信息
QCameraDevice QMediaDevices::defaultVideoInput() //返回默认的视频输入设备(如摄像头)信息
添加文件和移除文件
添加文件按钮的槽函数为on_btnAdd_clicked(),可以一次打开多个文件,文件被添加到界面上的列表组件 listWidget 里。
void MainWindow::on_btnAdd_clicked()
{//"添加"按钮,添加文件QString curPath=QDir::homePath(); //获取系统当前目录QString dlgTitle="选择音频文件";QString filter="音频文件(*.mp3 *.wav *.wma);;所有文件(*.*)"; //文件过滤器QStringList fileList=QFileDialog::getOpenFileNames(this,dlgTitle,curPath,filter); //可以选择多个文件if (fileList.count()<1)return;for (int i=0; i<fileList.size();i++) //依次将选择的文件添加到listWidget,并将文件路径保存到用户数据中{QString aFile=fileList.at(i);QFileInfo fileInfo(aFile);QListWidgetItem *aItem =new QListWidgetItem(fileInfo.fileName());aItem->setIcon(QIcon(":/images/images/musicFile.png"));aItem->setData(Qt::UserRole, QUrl::fromLocalFile(aFile)); //设置用户数据,QUrl对象ui->listWidget->addItem(aItem);}if (player->playbackState() != QMediaPlayer::PlayingState) //添加后开始播放第一个文件{ //当前没有在播放,就播放第1个文件ui->listWidget->setCurrentRow(0);QUrl source= getUrlFromItem(ui->listWidget->currentItem());player->setSource(source); //设置播放文件的路径}player->play(); //播放文件
}QUrl MainWindow::getUrlFromItem(QListWidgetItem *item)
{QVariant itemData= item->data(Qt::UserRole); //获取用户数据QUrl source =itemData.value<QUrl>(); //QVariant转换为QUrl类型return source;
}
根据选择的音频文件将其文件名添加到listWidget中,并设置其用户数据为QUrl类型的本地文件路径,后续播放listWidget中的曲目时,通过自定义函数getUrlFromItem从listWidget条目中获取到保存的本地文件路径,就可以使用setSource()函数设置播放媒介。
移除按钮对应的槽函数如下:
void MainWindow::on_btnRemove_clicked()
{//"移除"按钮,移除列表中的当前项int index =ui->listWidget->currentRow();if (index>=0){QListWidgetItem *item= ui->listWidget->takeItem(index); //new出来的对象移除时要使用deletedelete item;}
}
在这个函数中,选择当前的listWidget进行删除,前面创建条目时使用的是new,那就对应使用delete删除
清空按钮对应的槽函数如下:
void MainWindow::on_btnClear_clicked()
{//"清空"按钮,清空播放列表loopPlay=false; //防止do_stateChanged()里切换曲目ui->listWidget->clear();player->stop();
}
清空可以直接调用listWidget的clear()函数删除全部条目。
上一曲下一曲
这实际上还是对于listWidget中条目的处理,获取到当前的前一个/下一个条目后,取出条目中保存的用户数据(QUrl类型的本地文件路径)获得对应文件的路径,然后设置播放媒介即可。
void MainWindow::on_btnPrevious_clicked()
{//前一曲int curRow=ui->listWidget->currentRow();curRow--;curRow= curRow<0? 0:curRow;ui->listWidget->setCurrentRow(curRow); //设置当前行loopPlay=false; //暂时设置为false,防止do_stateChanged()里切换曲目player->setSource(getUrlFromItem(ui->listWidget->currentItem()));player->play();loopPlay=ui->btnLoop->isChecked();// if(ui->btnLoop->isChecked())
// {
// loopPlay=false; //暂时设置为false,防止do_stateChanged()里切换曲目
// player->setSource(getUrlFromItem(ui->listWidget->currentItem()));
// player->play();
// loopPlay=true;
// }
}void MainWindow::on_btnNext_clicked()
{//下一曲int count=ui->listWidget->count();int curRow=ui->listWidget->currentRow();curRow++;curRow= curRow>=count? count-1:curRow;ui->listWidget->setCurrentRow(curRow);loopPlay=false; //暂时设置为false,防止do_stateChanged()里切换曲目player->setSource(getUrlFromItem(ui->listWidget->currentItem()));player->play();loopPlay=ui->btnLoop->isChecked();// if(ui->btnLoop->isChecked())
// {
// loopPlay=false; //暂时设置为false,防止do_stateChanged()里切换曲目
// player->setSource(getUrlFromItem(ui->listWidget->currentItem()));
// player->play();
// loopPlay=true;
// }
}
这里切换曲目时要注意循环播放的问题。如果界面上的“循环”按钮是被选中的,那么 loopPlay 值为 true。如果直接重新设置播放源,播放器的状态会变为停止状态,那么 do_stateChanged()函数就会自动切换曲目,导致混乱。因此,程序里先把变量 loopPlay 设置为 false,避免do_stateChanged()函数切换曲目,重新设置曲目并开始播放后,再重新设置变量 loopPlay 的值。
QMediaPlayer 各信号的处理
切换播放文件时 player 会发射 sourceChanged()和 metaDataChanged()信号。
sourceChanged()信号对应的槽函数如下,主要用于更新界面label的显示
void MainWindow::do_sourceChanged(const QUrl &media)
{//播放的文件发生变化时的响应ui->labCurMedia->setText(media.fileName());
}
metaDataChanged()信号对应的槽函数如下:
void MainWindow::do_metaDataChanged()
{//元数据变化时执行,显示歌曲图片QMediaMetaData metaData=player->metaData(); //元数据对象QVariant metaImg= metaData.value(QMediaMetaData::ThumbnailImage); //获取ThumbnailImage元数据,无效
// QVariant metaImg= metaData.value(QMediaMetaData::CoverArtImage); //获取 CoverArtImage 元数据,无效if (metaImg.isValid()){QImage img= metaImg.value<QImage>(); //QVariant转换为QImageQPixmap musicPixmp= QPixmap::fromImage(img);if (ui->scrollArea->width() <musicPixmp.width())ui->labPic->setPixmap(musicPixmp.scaledToWidth(ui->scrollArea->width()-30));elseui->labPic->setPixmap(musicPixmp);}elseui->labPic->clear();
}
当播放媒介的元数据发生变化时,就读取媒介元数据中的 QMediaMetaData::ThumbnailImage
键的数据,这是歌曲的内嵌图片,是 QImage 类型的,如果判断有效就使用QPixmap显示到Label中,这里需要判断一下显示区域是否足够,并进行相应的调整。
播放源时长和播放位置发生变化时,player 会发射 durationChanged()和 positionChanged()信号。
durationChanged()信号对应的槽函数如下:
void MainWindow::do_durationChanged(qint64 duration)
{//播放源时长变化时执行,更新进度显示,一般切换曲目时会触发ui->sliderPosition->setMaximum(duration);int secs=duration/1000; //秒int mins=secs/60; //分钟secs=secs % 60; //余数秒durationTime=QString::asprintf("%d:%d",mins,secs);ui->labRatio->setText(positionTime+"/"+durationTime);
}
一般曲目发生更改时,播放源的时长会发生变化,在这个槽函数中重新设置了进度条
positionChanged()信号对应的槽函数如下:
void MainWindow::do_positionChanged(qint64 position)
{//播放位置变化时执行,更新进度显示if (ui->sliderPosition->isSliderDown()) //滑条正被鼠标拖动return;ui->sliderPosition->setSliderPosition(position);int secs=position/1000; //秒int mins=secs/60; //分钟secs=secs % 60; //余数秒positionTime=QString::asprintf("%d:%d",mins,secs);ui->labRatio->setText(positionTime+"/"+durationTime);
}
这个槽函数主要是用于更新界面进度条显示的,以实时显示播放的进度。
播放器开始播放、暂停播放和停止播放时会发射 playbackStateChanged()信号,该信号对应的槽函数如下所示:
void MainWindow::do_stateChanged(QMediaPlayer::PlaybackState state)
{//播放器状态变化时执行,更新按钮状态,或播放下一曲ui->btnPlay->setEnabled(state!=QMediaPlayer::PlayingState);ui->btnPause->setEnabled(state==QMediaPlayer::PlayingState);ui->btnStop->setEnabled(state==QMediaPlayer::PlayingState);//播放完一曲后停止了,如果loopPlay为true,自动播放下一曲if (loopPlay && (state ==QMediaPlayer::StoppedState)){int count=ui->listWidget->count();int curRow=ui->listWidget->currentRow();curRow++;curRow= curRow>=count? 0:curRow; //最后一曲播放完后切换到第一曲ui->listWidget->setCurrentRow(curRow);player->setSource(getUrlFromItem(ui->listWidget->currentItem()));player->play();}
}
MainWindow 类的私有变量 loopPlay 表示是否要循环播放,使用界面上的“循环”按钮可以 对应设置这个变量的值。QMediaPlayer 播放完当前曲目后就进入停止状态,不会自动播放下一曲。 为了实现循环播放,我们将界面组件 listWidget 的当前行下移或重设为 0,然后重新设置播放器的播放媒介并开始播放。
其他播放控制
界面下方的一些按钮和组件用于进行播放控制和设置,包括控制播放器开始播放、暂停播放和停止播放,设置播放倍速,设置是否循环播放,设置静音和音量,拖动播放进度条的滑块直接改变播放位置。对应的槽函数如下所示:
void MainWindow::on_btnPlay_clicked()
{//开始播放if (ui->listWidget->currentRow()<0) //没有选择文件,就播放第1个ui->listWidget->setCurrentRow(0);player->setSource(getUrlFromItem(ui->listWidget->currentItem()));player->play();loopPlay=ui->btnLoop->isChecked(); //是否循环播放
}
void MainWindow::on_btnPause_clicked()
{//暂停播放player->pause();
}void MainWindow::on_btnStop_clicked()
{//停止播放loopPlay=false;player->stop();
}void MainWindow::on_sliderVolumn_valueChanged(int value)
{//调整音量player->audioOutput()->setVolume(value/100.0); //0~ 1之间
}void MainWindow::on_btnSound_clicked()
{//静音控制bool mute=player->audioOutput()->isMuted();player->audioOutput()->setMuted(!mute);if (mute)ui->btnSound->setIcon(QIcon(":/images/images/volumn.bmp"));elseui->btnSound->setIcon(QIcon(":/images/images/mute.bmp"));
}void MainWindow::on_sliderPosition_valueChanged(int value)
{//播放进度调控player->setPosition(value);
}void MainWindow::on_doubleSpinBox_valueChanged(double arg1)
{//"倍速" DoubleSpinboxplayer->setPlaybackRate(arg1);
}
QSoundEffect 播放音效文件
QSoundEffect 用于播放低延迟音效文件,例如无压缩的 WAV 文件,从而实现一些音效,例如按键音、提示音,游戏中的爆炸音、开枪音等。QSoundEffect 不仅可以播放本地文件,还可以播放网络文件。通常使用该类播放一些时长较短的音频。
示例代码如下:
#include "widget.h"
#include "ui_widget.h"#include <QPainter>
#include <QPaintEvent>void Widget::defense(QString weapon)
{QUrl url=QUrl::fromLocalFile(appPath+"/sound/"+weapon);player1->setSource(url);player1->play();
}void Widget::attack(QString weapon)
{QUrl url=QUrl::fromLocalFile(appPath+"/sound/"+weapon);player2->setSource(url);player2->play();
}void Widget::paintEvent(QPaintEvent *event)
{QPainter painter(this);painter.drawPixmap(0,0,this->width(), this->height(),pixBackground);event->accept();
}Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);appPath=QCoreApplication::applicationDirPath(); // 无“/”pixBackground.load(appPath+"/sound/background.jpg");player1=new QSoundEffect(this);player1->setLoopCount(3);player2=new QSoundEffect(this);player2->setLoopCount(3);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{defense("Ak7.wav");
}void Widget::on_pushButton_2_clicked()
{defense("machinegun.wav");
}void Widget::on_pushButton_5_clicked()
{attack("Ak7.wav");
}void Widget::on_pushButton_6_clicked()
{attack("machinegun.wav");
}void Widget::on_pushButton_8_clicked()
{attack("blast.wav");
}void Widget::on_pushButton_12_clicked()
{attack("tank.wav");
}void Widget::on_pushButton_11_clicked()
{attack("mine.wav");
}void Widget::on_pushButton_9_clicked()
{defense("shell.wav");
}void Widget::on_pushButton_4_clicked()
{defense("blast.wav");
}void Widget::on_pushButton_10_clicked()
{defense("blast2.wav");
}void Widget::on_pushButton_3_clicked()
{defense("fire.wav");
}void Widget::on_pushButton_7_clicked()
{attack("fire2.wav");
}
参考
QT6 C++开发指南