当前位置: 首页 > news >正文

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;// 最后一次播放的时长msint 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_OBJECTpublic: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; //单位sint 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;// 最后一次播放的时长msint 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.lryc.cn/news/281125.html

相关文章:

  • [ACM学习] 动态规划基础之一二三维dp
  • Qt点击按钮在其附近弹出一个窗口
  • Springboot注解@Configuration和@Bean注解作用,生命周期
  • 30天精通Nodejs--第十五天:Websocket
  • C++深入学习之STL:2、适配器、迭代器与算法部分
  • Tiktok/抖音旋转验证码识别
  • 【Java 设计模式】设计原则
  • Druid连接池工具公式化SQL附踩坑记录
  • Linux内核--网络协议栈(二)UDP数据包发送
  • 基于深度学习的时间序列算法总结
  • nginx中多个server块共用upstream会相互影响吗
  • 基于信号完整性的一些PCB设计建议
  • 《BackTrader量化交易图解》第8章:plot 绘制金融图
  • 什么是欧拉筛??
  • 2023年全国职业院校技能大赛软件测试赛题—单元测试卷⑩
  • 使用WAF防御网络上的隐蔽威胁之SSRF攻击
  • Redis基础系列-哨兵模式
  • 【angular教程240112】09(完) Angular中的数据请求 与 路由
  • go中拷贝文件操作
  • 未来气膜体育馆的发展趋势是什么?
  • 通信扫盲(五)
  • nbcio-boot项目的文件上传与回显处理方法
  • 《动手学深度学习》学习笔记 第9章 现代循环神经网络
  • 「HDLBits题解」Vector100r
  • 如何制作专业商业画册,提升品牌形象
  • vim升级和配置
  • java通过okhttp方式实现https请求的工具类(绕过证书验证)
  • mysql定时备份shell脚本和还原
  • DevOps搭建(十六)-Jenkins+K8s部署详细步骤
  • WaitForSingleObject 函数的诸多用途与使用场景总结