前言
代码中播放wav视频和播放pcm的思路类似,但首先需要解析wav的头文件
测试环境:
- ffmpeg的shared版本
- windows环境
- qt5.12
- sdl2.0.22(mingw编译器)
播放wav音频,sdl提供了封装好的方法
#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \
SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len)
这里表示SDL_LoadWAV被定义为了SDL_LoadWAV_RW(即SDL_LoadWAV_RW和SDL_LoadWAV是一样的)
关于SDL_LoadWAV_RW的方法介绍如下
extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src,
int freesrc,
SDL_AudioSpec * spec,
Uint8 ** audio_buf,
Uint32 * audio_len);
完整代码:(我这里是将功能封装在线程里,其中还考虑了线程的关闭问题)
wavplaythread.h
#ifndef WAVPLAYTHREAD_H
#define WAVPLAYTHREAD_H
#include <QThread>
class PlayThread : public QThread {
Q_OBJECT
private:
void run();
public:
explicit PlayThread(QObject *parent = nullptr);
~PlayThread();
signals:
};
#endif // WAVPLAYTHREAD_H
wavplaythread.cpp
#include "wavplaythread.h"
#include <SDL2/SDL.h>
#include <QDebug>
#include <QFile>
#define FILENAME "E:/media/out.wav"
/*
// 每个样本占用多少个字节
#define BYTES_PER_SAMPLE ((SAMPLE_SIZE * CHANNELS) >> 3)
// 文件缓冲区的大小
#define BUFFER_SIZE (SAMPLES * BYTES_PER_SAMPLE)
*/
typedef struct {
int len = 0;
int pullLen = 0;
Uint8 *data = nullptr;
} AudioBuffer;
PlayThread::PlayThread(QObject *parent) : QThread(parent) {
connect(this, &PlayThread::finished,
this, &PlayThread::deleteLater);
}
PlayThread::~PlayThread() {
disconnect();
requestInterruption();
quit();
wait();
qDebug() << this << "析构了";
}
// 等待音频设备回调(会回调多次)
void pull_audio_data_wav(void *userdata,
// 需要往stream中填充PCM数据
Uint8 *stream,
// 希望填充的大小(samples * format * channels / 8)
int len) {
qDebug() << "pull_audio_data_wav" << len;
// 清空stream(静音处理)
SDL_memset(stream, 0, len);
// 取出AudioBuffer
AudioBuffer *buffer = (AudioBuffer *) userdata;
// 文件数据还没准备好
if (buffer->len <= 0) return;
// 取len、bufferLen的最小值(为了保证数据安全,防止指针越界)
buffer->pullLen = (len > buffer->len) ? buffer->len : len;
// 填充数据
SDL_MixAudio(stream,
buffer->data,
buffer->pullLen,
SDL_MIX_MAXVOLUME);
buffer->data += buffer->pullLen;
buffer->len -= buffer->pullLen;
}
/*
SDL播放音频有2种模式:
Push(推):【程序】主动推送数据给【音频设备】
Pull(拉):【音频设备】主动向【程序】拉取数据
*/
void PlayThread::run() {
// 初始化Audio子系统
if (SDL_Init(SDL_INIT_AUDIO)) {
qDebug() << "SDL_Init error" << SDL_GetError();
return;
}
// 加载wav文件
SDL_AudioSpec spec;
// 指向PCM数据
Uint8 *data = nullptr;
// PCM数据的长度
Uint32 len = 0;
if (!SDL_LoadWAV(FILENAME, &spec, &data, &len)) {
qDebug() << "SDL_LoadWAV error" << SDL_GetError();
// 清除所有的子系统
SDL_Quit();
return;
}
// 音频缓冲区的样本数量
spec.samples = 1024;
// 设置回调
spec.callback = pull_audio_data_wav;
// 设置userdata
AudioBuffer buffer;
buffer.data = data;
buffer.len = len;
spec.userdata = &buffer;
// 打开设备
if (SDL_OpenAudio(&spec, nullptr)) {
qDebug() << "SDL_OpenAudio error" << SDL_GetError();
// 清除所有的子系统
SDL_Quit();
return;
}
// 开始播放(0是取消暂停)
SDL_PauseAudio(0);
// 计算一些参数
int sampleSize = SDL_AUDIO_BITSIZE(spec.format);
int bytesPerSample = (sampleSize * spec.channels) >> 3;
// 存放从文件中读取的数据
while (!isInterruptionRequested()) {
// 只要从文件中读取的音频数据,还没有填充完毕,就跳过
if (buffer.len > 0) continue;
// 文件数据已经读取完毕
if (buffer.len <= 0) {
// 剩余的样本数量
int samples = buffer.pullLen / bytesPerSample;
int ms = samples * 1000 / spec.freq;
SDL_Delay(ms);
break;
}
}
// 释放WAV文件数据
SDL_FreeWAV(data);
// 关闭设备
SDL_CloseAudio();
// 清除所有的子系统
SDL_Quit();
}
注意:该代码对可能出现的问题都进行了一定的优化,读者使用时只需关注关键代码
扫描二维码关注公众号,回复:
14667296 查看本文章
线程调用
void MainWindow::on_pushButton_play_wav_clicked()
{
if (_playThread) {
// 停止播放
_playThread->requestInterruption();
_playThread = nullptr;
ui->pushButton_play_wav->setText("播放wav视频");
} else {
// 开始播放
_playThread = new PlayThread(this);
_playThread->start();
// 监听线程的结束
connect(_playThread, &PlayThread::finished,
[this]() {
_playThread = nullptr;
ui->pushButton_play_wav->setText("播放wav视频");
});
ui->pushButton_play_wav->setText("停止播放wav视频");
}
}
注意:mainwindow.h文件中提前声明了以下全局变量
PlayThread *_playThread = nullptr;
注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用
码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方