QT多媒体开发(二):播放音频

简介

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_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    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出来的对象移除时要使用delete
        delete 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转换为QImage
        QPixmap musicPixmp= QPixmap::fromImage(img);
        if (ui->scrollArea->width() <musicPixmp.width())
            ui->labPic->setPixmap(musicPixmp.scaledToWidth(ui->scrollArea->width()-30));
        else
            ui->labPic->setPixmap(musicPixmp);
    }
    else
        ui->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"));
    else
        ui->btnSound->setIcon(QIcon(":/images/images/mute.bmp"));
}

void MainWindow::on_sliderPosition_valueChanged(int value)
{//播放进度调控
    player->setPosition(value);
}

void MainWindow::on_doubleSpinBox_valueChanged(double arg1)
{//"倍速" DoubleSpinbox
    player->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++开发指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值