这里介绍两种方法来实现Qt播放Wav音频数据。
方法一:使用QAudioOutput
pro文件中加入multimedia模块。
#include <QApplication>
#include <QFile>
#include <QAudioFormat>
#include <QAudioOutput>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QFile inputFile;
inputFile.setFileName("test.wav");
inputFile.open(QIODevice::ReadOnly);
//设置采样格式
QAudioFormat audioFormat;
//设置采样率
audioFormat.setSampleRate(44100);
//设置通道数
audioFormat.setChannelCount(2);
//设置采样大小,一般为8位或16位
audioFormat.setSampleSize(16);
//设置编码方式
audioFormat.setCodec("audio/pcm");
//设置字节序
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
//设置样本数据类型
audioFormat.setSampleType(QAudioFormat::UnSignedInt);
QAudioOutput *audio = new QAudioOutput( audioFormat, 0);
audio->start(&inputFile);
return a.exec();
}
注意这里采样率、通道数和采样大小的设置,本例只能用来播放无损的WAV。
方法二:使用SDL2来播放
接下来演示一下如何使用SDL播放WAV文件。
初始化子系统:
// 初始化Audio子系统
if (SDL_Init(SDL_INIT_AUDIO)) {
qDebug() << "SDL_Init error:" << SDL_GetError();
return;
}
加载WAV文件:
// 存放WAV的PCM数据和数据长度
typedef struct {
Uint32 len = 0;
int pullLen = 0;
Uint8 *data = nullptr;
} AudioBuffer;
// WAV中的PCM数据
Uint8 *data;
// WAV中的PCM数据大小(字节)
Uint32 len;
// 音频参数
SDL_AudioSpec spec;
// 加载wav文件
if (!SDL_LoadWAV(FILENAME, &spec, &data, &len)) {
qDebug() << "SDL_LoadWAV error:" << SDL_GetError();
// 清除所有的子系统
SDL_Quit();
return;
}
// 回调
spec.callback = pull_audio_data;
// 传递给回调函数的userdata
AudioBuffer buffer;
buffer.len = len;
buffer.data = data;
spec.userdata = &buffer;
打开音频设备:
// 打开设备
if (SDL_OpenAudio(&spec, nullptr)) {
qDebug() << "SDL_OpenAudio error:" << SDL_GetError();
// 释放文件数据
SDL_FreeWAV(data);
// 清除所有的子系统
SDL_Quit();
return;
}
开始播放:
// 每一个样本大小
int size = SDL_AUDIO_BITSIZE(m_spec.format) * m_spec.channels / 8;
// 最后一次播放的样本数量
int leftSample = m_buffer.pullLen / size;
// 最后一次播放的时长ms
int ms = leftSample * 1000 / m_spec.freq;
SDL_Delay(ms);
回调:
static void fill_audio(void *userdata, Uint8 *stream, int len)
{
AudioPlayThread::AudioBuffer *buffer = (AudioPlayThread::AudioBuffer *)userdata;
SDL_memset(stream, 0, len);
if (buffer->len <= 0)
return;
if (buffer->thread->m_isPause)
return;
buffer->pullLen = buffer->len > len ? len : buffer->len;
SDL_MixAudio(stream, buffer->data, buffer->pullLen, SDL_MIX_MAXVOLUME);
buffer->data += buffer->pullLen;
buffer->len -= buffer->pullLen;
//未播放的时间
int unPlayTime = (buffer->thread->m_totalTime) - (buffer->len / buffer->thread->m_perByte);
buffer->thread->m_timeFunc(unPlayTime,buffer->thread->m_totalTime);
}
释放资源:
// 释放WAV文件数据
SDL_FreeWAV(data);
// 关闭设备
SDL_CloseAudio();
// 清除所有的子系统
SDL_Quit();
运行效果图:
音频界面构造:
音频播放界面:AudioPlayWidget类。
#ifndef AUDIOPLAYWIDGET_H
#define AUDIOPLAYWIDGET_H
#include <QWidget>
namespace Ui {
class AudioPlayWidget;
}
class AudioPlayThread;
class AudioPlayWidget : public QWidget
{
Q_OBJECT
public:
explicit AudioPlayWidget(const QString &name, const QString &url, QWidget *parent = 0);
~AudioPlayWidget();
private slots:
void on_btnPlay_clicked();
void slotFinished();
void slotShowTime(int playTime, int totalTime);
private:
Ui::AudioPlayWidget *ui;
private:
bool m_play = false;
AudioPlayThread *m_thread = nullptr;
bool m_isExistPlay = false;
QString m_url;
};
#endif // AUDIOPLAYWIDGET_H
#include "AudioPlayWidget.h"
#include "ui_AudioPlayWidget.h"
#include "BaseHelper.h"
#include "AudioPlayThread.h"
#include <QTime>
#include "mymessagebox.h"
const QString playStyle = "QPushButton#btnPlay\
{\
border-image: url(\":/image/audioPlay.png\");\
}";
const QString pauseStyle = "QPushButton#btnPlay\
{\
border-image: url(\":/image/audioPause.png\");\
}";
AudioPlayWidget::AudioPlayWidget(const QString &name, const QString &url,QWidget *parent) :
QWidget(parent),
ui(new Ui::AudioPlayWidget),
m_url(url)
{
ui->setupUi(this);
m_thread = new AudioPlayThread(this);
connect(m_thread,&AudioPlayThread::finished,
this,&AudioPlayWidget::slotFinished);
BaseHelper::load(":/qss/AudioPlayWidget.qss",this);
}
AudioPlayWidget::~AudioPlayWidget()
{
delete ui;
if(m_thread)
{
m_thread->stop();
m_thread->wait();
}
}
void AudioPlayWidget::on_btnPlay_clicked()
{
if (!BaseHelper::fileExist(m_url))
{
MyMessageBox::showMyMessageBox(this, QString("文件丢失"),
QString("打开的音频文件丢失?"), MESSAGE_WARNNING, BUTTON_OK, true);
return;
}
m_play = !m_play;
if(m_play)
{
ui->btnPlay->setStyleSheet(pauseStyle);
if (!m_isExistPlay)
{
TimeFunc totalTimeFunc = std::bind(&AudioPlayWidget::slotShowTime, this,
std::placeholders::_1, std::placeholders::_2);
m_thread->open(m_url, totalTimeFunc);
m_thread->start();
m_isExistPlay = true;
}
else
{
m_thread->resume();
}
}
else
{
ui->btnPlay->setStyleSheet(playStyle);
m_thread->pause();
}
}
void AudioPlayWidget::slotFinished()
{
if (m_isExistPlay)
m_isExistPlay = false;
m_play = !m_play;
ui->lbPlay->setText("00:00:00");
ui->lbTotal->setText("00:00:00");
ui->horizontalSlider->setValue(0);
ui->btnPlay->setStyleSheet(playStyle);
}
void AudioPlayWidget::slotShowTime(int playTime, int totalTime)
{
QString strPlayTime = QTime::fromMSecsSinceStartOfDay(playTime* 1000).toString("hh:mm:ss");
QString strTotalTime = QTime::fromMSecsSinceStartOfDay(totalTime*1000).toString("hh:mm:ss");
ui->lbPlay->setText(strPlayTime);
ui->lbTotal->setText(strTotalTime);
ui->horizontalSlider->setMaximum(totalTime);
ui->horizontalSlider->setMinimum(0);
ui->horizontalSlider->setValue(playTime);
}
音频播放线程:
#ifndef AUDIOPLAYTHREAD_H
#define AUDIOPLAYTHREAD_H
#include <QThread>
#include "global.h"
class AudioPlayThread : public QThread
{
public:
AudioPlayThread(QObject *parent = nullptr);
typedef struct AudioBuffer {
int len = 0;
int pullLen = 0;
uint8_t *data = nullptr;
AudioPlayThread *thread = nullptr;
} AudioBuffer;
public:
int open(const QString &fileName, TimeFunc timeFunc);
void stop();
void pause();
void resume();
protected:
void run();
public:
TimeFunc m_timeFunc;
int m_totalTime = 0; //单位s
int m_perByte = 0;//1s字节数
bool m_isPause = false;
private:
bool m_isExit = false;
Uint8 *m_data = nullptr;
Uint32 m_len = 0;
AudioBuffer m_buffer;
SDL_AudioSpec m_spec;
};
#endif // AUDIOPLAYTHREAD_H
#include "AudioPlayThread.h"
static void fill_audio(void *userdata, Uint8 *stream, int len)
{
AudioPlayThread::AudioBuffer *buffer = (AudioPlayThread::AudioBuffer *)userdata;
SDL_memset(stream, 0, len);
if (buffer->len <= 0)
return;
if (buffer->thread->m_isPause)
return;
buffer->pullLen = buffer->len > len ? len : buffer->len;
SDL_MixAudio(stream, buffer->data, buffer->pullLen, SDL_MIX_MAXVOLUME);
buffer->data += buffer->pullLen;
buffer->len -= buffer->pullLen;
//未播放的时间
int unPlayTime = (buffer->thread->m_totalTime) - (buffer->len / buffer->thread->m_perByte);
buffer->thread->m_timeFunc(unPlayTime,buffer->thread->m_totalTime);
}
AudioPlayThread::AudioPlayThread(QObject *parent)
: QThread(parent)
{
}
int AudioPlayThread::open(const QString &fileName, TimeFunc timeFunc)
{
m_timeFunc = timeFunc;
// 加载 WAV 文件
if (!SDL_LoadWAV(fileName.toStdString().c_str(), &m_spec, &m_data, &m_len))
{
printf("load wav error \n");
return -1;
}
//计算1s钟字节大小
m_perByte = (SDL_AUDIO_BITSIZE(m_spec.format) * m_spec.channels / 8 * m_spec.freq);
m_totalTime = m_len / m_perByte;
m_timeFunc(0, m_totalTime);
printf("===============Wav params=============\n");
printf("======size = %d\n", m_len);
printf("======channels = %d\n", m_spec.channels);
printf("======samples = %d\n", m_spec.samples);
printf("======freq = %d\n", m_spec.freq);
printf("======time = %d\n", m_totalTime);
printf("===============Wav params=============\n");
m_buffer.data = m_data;
m_buffer.len = m_len;
m_buffer.thread = this;
m_spec.userdata = &m_buffer;
m_spec.callback = fill_audio;
// 打开音频设备
if (SDL_OpenAudio(&m_spec, nullptr))
{
printf("SDL_OpenAudio error \n");
SDL_FreeWAV(m_data);
return -1;
}
return 0;
}
void AudioPlayThread::stop()
{
m_isExit = true;
}
void AudioPlayThread::pause()
{
m_isPause = true;
}
void AudioPlayThread::resume()
{
m_isPause = false;
}
void AudioPlayThread::run()
{
SDL_PauseAudio(0);
while(!m_isExit)
{
if (m_buffer.len > 0)
continue;
// 每一个样本大小
int size = SDL_AUDIO_BITSIZE(m_spec.format) * m_spec.channels / 8;
// 最后一次播放的样本数量
int leftSample = m_buffer.pullLen / size;
// 最后一次播放的时长ms
int ms = leftSample * 1000 / m_spec.freq;
SDL_Delay(ms);
break;
}
// 释放 WAV 数据
SDL_FreeWAV(m_data);
SDL_CloseAudio();
printf("=======Play Wav thread exit===\n");
}