Qt SDL2播放Wav音频

news2024/11/24 3:57:06

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RK3566 linux修改CMA size

1、进入内核配置界面 执行命令&#xff1a; cd kernel make ARCHarm64 menuconfig 2、搜索CONFIG_CMA_SIZE_MBYTES 在配置界面输入/搜索CONFIG_CMA_SIZE_MBYTES&#xff0c;结果如下&#xff1a; 根据提示进入以下界面&#xff1a; 修改Size in Mega Bytes的值。

【时间复杂度】时间复杂度优化法则简讲

一、引言 时间复杂度是衡量算法运行效率的一项重要指标&#xff0c;它描述了随着输入规模的增加&#xff0c;算法的执行时间如何增长。在算法设计与分析中&#xff0c;我们经常面临着优化时间复杂度的任务&#xff0c;以便提高程序的性能。本博客将深入探讨时间复杂度的优化法…

UJA1169A恢复出厂设置(Restoring factory preset values)

UJA1169A是一款CAN芯片&#xff0c;同时又带有硬件看门狗的功能。出厂默认是Forced Normal模式&#xff0c;此时看门狗是禁用的。CAN通信的正常功能是在Forced Normal/Normal模式下才好用的&#xff0c;Normal模式下需要配置。包含位FNMC&#xff08;地址74h&#xff09;的寄存…

Uniapp多选Popup(弹出层)

uniapp中多选组件很少&#xff0c;故个人简单开发了一个&#xff0c;可简单使用&#xff0c;也可根据个人需求稍微改进 支持的功能 单选多选&#xff08;默认&#xff09;限制选择数量默认选中禁用选项 属性说明 属性默认值说明singlefalsetrue为开启单选&#xff0c;否则为…

【网络安全】2024年一个漏洞4w+,网安副业挖SRC漏洞,躺着把钱挣了!

一个漏洞奖励2w&#xff0c;这是真实的嘛&#xff01; 作为资深白帽&#xff0c;入行网安这些年也一直在接私活&#xff0c;副业赚的钱几乎是我工资的三倍&#xff01;看到最近副业挖漏洞的内容非常火爆&#xff0c;我便决定将自己的经验分享出来&#xff0c;带我的粉丝们一起…

css3 纯代码案例

css3 纯代码案例 前言渐变之美1.1 纯CSS3实现的渐变背景1.2 使用多重颜色和方向打造丰富渐变效果1.3 渐变色停留动画的巧妙运用 纯CSS图形绘制2.1 使用border属性制作三角形、梯形等形状伪类箭头图标2.2 利用transform创建旋转、缩放的图形 浮动的阴影敲代码css准备reset 样式复…

JS封装本地缓存的设置,读取,移除,清空方法及使用示例

我封装了一个JS通用的缓存管理对象&#xff0c;可以提供缓存的设置&#xff0c;读取&#xff0c;移除&#xff0c;清空操作&#xff0c;使用也很方便&#xff0c;封装方法的代码在最下方。 Q: 为什么不直接用原生的缓存方法&#xff0c;要封装&#xff1f; A1:原生的缓存管理…

MySQL篇—性能压测工具mysqlslap介绍

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

SpringBoot参数校验@Validated、@Valid

SpringBoot参数校验Validated、Valid&#xff08;javax.validation&#xff09; 一、应用场景 在实际开发中&#xff0c;前端校验并不安全&#xff0c;任何人都可以通过接口来调用我们的服务&#xff0c;就算加了一层token的校验&#xff0c;有心人总会转空子&#xff0c;来传…

如何禁用WordPress站点的管理员电子邮件验证或修改检查频率?

今天boke112百科登录某个WordPress站点时&#xff0c;又出现“管理员邮件确认”的提示&#xff0c;要求确认此站点的管理员电子邮箱地址是否仍然正确。具体如下图所示&#xff1a; 如果点击“稍后提醒我”&#xff0c;那么管理员邮件验证页面就会在3天后重新显示。 说实话&…

【JVM】JVM概述

JVM概述 基本介绍 JVM&#xff1a;全称 Java Virtual Machine&#xff0c;即 Java 虚拟机&#xff0c;一种规范&#xff0c;本身是一个虚拟计算机&#xff0c;直接和操作系统进行交互&#xff0c;与硬件不直接交互&#xff0c;而操作系统可以帮我们完成和硬件进行交互的工作特…

大数据开发之Hadoop(MapReduce)

第 1 章&#xff1a;MapReduce概述 1.1 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并…

我的隐私计算学习——联邦学习(4)

本篇笔记部分内容来源于这位老师的知识分享【公众号&#xff1a;秃顶的码农】&#xff0c;我从他的资料里学到了很多&#xff0c;期间还私信询问了一些困惑&#xff0c;都得到了老师详细的答复&#xff0c;相当nice&#xff01; &#xff08;六&#xff09;横向联邦学习 — 梯度…

学习VUE-安装环境

下载安装Node.js 官网下载最新版本&#xff1a;https://nodejs.org/en/download/ 解压zip包 由于node.js默认安装了npm所以不用额外配置全局命令就可以使用npm命令 在cmd中输入node -v 和 npm -v就可以得到版本信息 配置一下目录&#xff1a; node.js环境配置 配置镜像 安装…

命令行参数环境变量和进程空间地址

文章目录 命令行参数环境变量进程地址空间 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站。 命令行参数 什么是命令行参数&#xff1f; 我…

✅稳定检索,高校嘉宾出席,2024年机械应用与机器视觉研究国际会议(ICMAMVR 2024)

2024年机械应用与机器视觉研究国际会议(ICMAMVR 2024) 数据库&#xff1a;EI,CPCI,CNKI,Google Scholar 等 2024 International Conference on Mechanical Applications and Machine Vision Research(ICMAMVR 2024) 一、【会议简介】 &#x1f389;&#x1f389; 2024年机械应用…

运维平台介绍:视频智能运维平台的视频质量诊断分析和监控中心

目 录 一、概述 二、框架图 1、图像过亮检测&#xff1a; 2、图像模糊检测&#xff1a; 3、画面冻结检测&#xff1a; 4、信号缺失检测&#xff1a; 5、图像偏色检测&#xff1a; 6、噪声干扰检测&#xff1a; 7、条纹干扰检测&#xff1a; 三、监控中心模…

electron+vite+vue3 快速入门教程

文章目录 前言一、electron是什么&#xff1f;二、electron 进程模型1.主进程2.渲染进程3.预加载脚本4.进程通信4.1 sendon&#xff08;单向&#xff09;4.2 invokehandle (双向)4.3 主进程向渲染进程发送事件 三、窗口创建与应用事件四、技术栈和构建工具五、electron-vite安装…

母线温度预测业务需求设计

1、需求背景 需求对象&#xff1a;设备使用方、设备维修人员 使用场景&#xff1a;使用方需要对母线温度进行实时监测和预警&#xff0c;及时排除安全隐患&#xff0c;保证长期正常运行。 使用目的&#xff1a;准确预测母线的未来温度&#xff0c;对于可能存在的隐患提前预警…

【C++】STL 算法 - 累加填充算法 ( 元素累加算法 - accumulate 函数 | 元素填充算法 - fill 函数 )

文章目录 一、元素累加算法 - accumulate 函数1、函数原型分析2、代码示例 二、元素填充算法 - fill 函数1、函数原型分析2、代码示例 一、元素累加算法 - accumulate 函数 1、函数原型分析 在 C 语言 的 标准模板库 ( STL , STL Standard Template Library ) 中 , 提供了 accu…