Qt SDL2播放Wav音频

这里介绍两种方法来实现Qt播放Wav音频数据。

方法一:使用QAudioOutput

pro文件中加入multimedia模块。

#include 
#include 
#include 
#include 

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();

运行效果图:

音频界面构造:

Qt SDL2播放Wav音频_第1张图片

音频播放界面:AudioPlayWidget类。

#ifndef AUDIOPLAYWIDGET_H
#define AUDIOPLAYWIDGET_H

#include 

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 
#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 
#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");
}

你可能感兴趣的:(SDL2,Qt进阶,qt,音视频,SDL,wav,QAudioOutput)