四、qt中通过sdl播放wav视频

前言

代码中播放wav视频和播放pcm的思路类似,但首先需要解析wav的头文件

链接:qt中通过sdl播放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;

注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用


码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方

猜你喜欢

转载自blog.csdn.net/weixin_44092851/article/details/128307607