Qt SDL2播放Wav音频

这里介绍两种方法来实现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");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/331205.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

游卡:OceanBase在游戏核心业务的规模化降本实践

从 2023 年 9 月测试 OceanBase&#xff0c;到如今 3 个核心业务应用 OceanBase&#xff0c;国内最早卡牌游戏研发者之一的游卡仅用了两个月。是什么原因让游卡放弃游戏行业通用的 MySQL方案&#xff0c;选择升级至 OceanBase&#xff1f;杭州游卡网络技术有限公司&#xff08;…

精品IDEA插件推荐:Apipost-Helper

Apipost-Helper是由Apipost推出的IDEA插件&#xff0c;写完接口可以进行快速调试&#xff0c;且支持搜索接口、根据method跳转接口&#xff0c;还支持生成标准的API文档&#xff0c;注意&#xff1a;这些操作都可以在代码编辑器内独立完成&#xff0c;非常好用&#xff01;这里…

Linux的权限(2)

目录 Linux的&#xff08;事物属性&#xff09;文件权限 文件权限值得表示方法 字符表示方法 8进制表示方法 文件访问权限得相关设置方法 chmod修改权限法1 chmod修改权限法2 文件的角色&#xff08;拥有者/所属者&#xff09;修改 chown拥有者 chgrp所属者 &…

【网站项目】基于jsp的拍卖网站设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

[C++] 如何在Windows下使用vs 2022的vc++项目访问mysql 8?

关于mysql connector/C++ mysql connector/C++是官方提供的C++驱动程序,如果我们想通过C++代码来访问Mysql8,就必须借助它。 MySQL :: MySQL Connector/C++ Developer Guide GitHub - mysql/mysql-connector-cpp: MySQL Connector/C++ is a MySQL database connector for C…

浅聊雷池社区版(WAF)的tengine

雷池社区版是一个开源的免费Web应用防火墙&#xff08;WAF&#xff09;&#xff0c;专为保护Web应用免受各种网络攻击而设计。基于强大的Tengine&#xff0c;雷池社区版提供了一系列先进的安全功能&#xff0c;适用于中小企业和个人用户。 Tengine的故事始于2011年&#xff0c;…

深入了解性能优化(web应用)

影响一个系统性能的方方面面 一个 web应用不是一个孤立的个体,它是一个系统的部分,系统中的每一部分都会影响整个系统的性能 一.常用的性能评价/测试指标 1.响应时间 提交请求和返回该请求的响应之间使用的时间,一般比较关注平均响应时间。 常用操作的响应时间列表: 操作 响应…

跑通 yolov5-7.0 项目之训练自己的数据集

yolov5 一、yolov5 源码下载二、配置环境&#xff0c;跑通项目三、训练自己的数据集1、获取验证码数据2、标注图片&#xff0c;准备数据集3、开始训练自己的数据集1、train.py 训练数据集2、val.py 验证测试你的模型3、detect.py 正式用你的模型 四、遇到的报错、踩坑1、import…

AD导出BOM表 导出PDF

1.Simple BOM: 这种模式下&#xff0c;最好在pcb界面&#xff0c;这样的导出的文件名字是工程名字&#xff0c;要是在原理图界面导出&#xff0c;会以原理图的名字命名表格。 直接在菜单栏 报告->Simple BOM 即可导出物料清单&#xff0c;默认导出 comment pattern qu…

springboot 原理分析之自动配置

一、Condition Condition 是在 Spring 4.0 增加的条件判断功能&#xff0c;通过这个可以功能可以实现选择性的创建 Bean 操作。比如说&#xff0c;只有满足某一个条件才能创建这个 Bean&#xff0c;否则就不创建。 SpringBoot 是如何知道要创建哪个 Bean 的&#xff1f;比如 Sp…

[C++] opencv - copyTo函数介绍和使用案例

copyTo函数介绍 copyTo函数是OpenCV库中的一个成员函数&#xff0c;用于将一个Mat对象的内容复制到另一个Mat对象中。 函数原型&#xff1a; void cv::Mat::copyTo(OutputArray m) const;void cv::Mat::copyTo(OutputArray m, InputArray mask) const; 参数说明&#xff1a;…

动手学深度学习6 自动求导

自动求导 1. 自动求导2. 自动求导实现1. 示例 y 2 X T X y2X^TX y2XTX 关于列向量x求导。2. 非标量变量的反向传播3. 分离计算4. Python控制流的梯度计算 QA 视频&#xff1a; https://www.bilibili.com/video/BV1KA411N7Px/?spm_id_fromautoNext&vd_sourceeb04c9a33e87…

STL中的stack、queue以及deque

目录 一、关于deque容器&#xff08;双端队列&#xff09; 1、deque的底层实现 2、deque的缺点 3、关于stack与squeue默认使用deque容器 二、stack简介 1、stack的成员函数&#xff08;接口&#xff09; 2、stack的模拟实现 三、queue简介 1、queue的成员函数&#xff08…

js:锚点滚动到页面对应区域

锚点跳转到对应页面的区域使用 scrollIntoView // anchor即你要跳转到的元素 anchor.scrollIntoView({behavior: "smooth", block: "center", inline: "nearest" });1、behavior&#xff1a;定义滚动行为。它可以设置为 “auto” 或 “smoo…

老师布置作业的技巧有哪些

布置作业可不只是简单地给学生分配任务&#xff0c;而是需要运用一些技巧&#xff0c;以达到更好的教学效果。那么&#xff0c;老师应该如何布置作业呢&#xff1f; 一、作业要有针对性 布置作业时&#xff0c;老师应该根据学生的实际情况和课程要求&#xff0c;有针对性地设…

小程序商城在易货模式中的可行性

一、引言 随着科技的快速发展和互联网的普及&#xff0c;电子商务已经深入人们的生活。小程序商城作为电子商务的一种形式&#xff0c;凭借其便捷性、高效性和广泛覆盖的优势&#xff0c;成为商业领域的新宠。本文将探讨使用小程序商城实现易货模式的可行性。 二、小程序商城的…

Grind75第9天 | 733.图像渲染、542.01矩阵、1235.规划兼职工作

733.图像渲染 题目链接&#xff1a;https://leetcode.com/problems/flood-fill 解法&#xff1a; 可以用深度优先搜索和广度优先搜索。 深度优先搜索。每次搜索到一个方格时&#xff0c;如果其与初始位置的方格颜色相同&#xff0c;就将该方格的染色&#xff0c;然后继续对…

鸿蒙NEXT来了,操作系统的寒武纪时代

鸿蒙来了&#xff0c;加上Android、iOS&#xff0c;企业又要投入人力物力财力&#xff0c;多支持一个技术阵营的一种技术平台。从IT角度看&#xff0c;是更多的责任&#xff1a;新技能培训、新员工招聘、新小组成立&#xff0c;也是新增的代码、新的bug、新的测试任务&#xff…

智能车培训——硬件篇:电源转换的硬件设计

培训课件及资料 链接&#xff1a;https://pan.baidu.com/s/1IikVfZ04Wl9UmEuizfP12A?pwd89gs 提取码&#xff1a;89gs 一.BUCK降压电路的设计 1.什么是BUCK降压&#xff1f;&#xff08;原理&#xff09; &#xff08;1&#xff09;导通回路与续流回路 电流的环路是电源通…

【H3C】配置AAA认证和Telnet远程登陆,S5130 Series交换机

AAA配置步骤为&#xff1a; 1.开启telent远程登陆服务 2.创建用户&#xff0c;设置用户名、密码、用户的服务类型 3.配置终端登录的数量 4.配置vlan-if的ip地址&#xff0c;用来远程登陆 5.允许对应的vlan通过 1.开启telent远程登陆服务 sys …