WAV的播放

1.命令行播放

 songlin@feng-sl  ~/audio/pcm_to_wav   master ±  ffplay 44100-2-f32le.wav
ffplay version 4.3.2 Copyright (c) 2003-2021 the FFmpeg developers
  built with Apple clang version 12.0.0 (clang-1200.0.32.29)
  configuration: --prefix=/usr/local/Cellar/ffmpeg/4.3.2_4 --enable-shared --enable-pthreads --enable-version3 --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox
  libavutil      56. 51.100 / 56. 51.100
  libavcodec     58. 91.100 / 58. 91.100
  libavformat    58. 45.100 / 58. 45.100
  libavdevice    58. 10.100 / 58. 10.100
  libavfilter     7. 85.100 /  7. 85.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  7.100 /  5.  7.100
  libswresample   3.  7.100 /  3.  7.100
  libpostproc    55.  7.100 / 55.  7.100
Input #0, wav, from '44100-2-f32le.wav':
  Duration: 00:00:17.36, bitrate: 2822 kb/s
    Stream #0:0: Audio: pcm_f32le ([3][0][0][0] / 0x0003), 44100 Hz, 2 channels, flt, 2822 kb/s
   5.56 M-A:  0.000 fd=   0 aq=  356KB vq=    0KB sq=    0B f=0/0

从终端输出可以看到已经成功播放了。从前面得知WAV文件是对PCM文件的深一层封装,里面已经包含了声道数,采样率,采样格式等参数,所以可以直接使用ffplay命令播放,不需要想PCM 这样的裸数据增加额外的参数

2.程序代码播放

  • mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include "playthread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    PlayThread *_playThread = nullptr;
};
#endif // MAINWINDOW_H

  • mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include 
#include 

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow) {
    ui->setupUi(this);
}

MainWindow::~MainWindow() {
    delete ui;
}

void showVersion() {
    SDL_version version;
    SDL_VERSION(&version);
    qDebug() << version.major << version.minor << version.patch;
}

void MainWindow::on_playButton_clicked() {
    if (_playThread) { // 停止播放
        _playThread->requestInterruption();
        _playThread = nullptr;
        ui->playButton->setText("开始播放");
    } else { // 开始播放
        _playThread = new PlayThread(this);
        _playThread->start();
        // 监听线程的结束
        connect(_playThread, &PlayThread::finished,
        [this]() {
            _playThread = nullptr;
            ui->playButton->setText("开始播放");
        });
        ui->playButton->setText("停止播放");
    }
}

  • playthread.h
#ifndef PLAYTHREAD_H
#define PLAYTHREAD_H

#include 

class PlayThread : public QThread
{
public:
    explicit PlayThread(QObject *parent = nullptr);
    ~PlayThread();
private:
     void run();
};

#endif // PLAYTHREAD_H

  • playthread.cpp
#include "playthread.h"

#include 
#include 
#include 

#define FILENAME "/Users/songlin/audio/pcm_to_wav/44100-2-f32le.wav"

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 << "析构了";
}

/*音频回调方法,会多次回调
 * *
 * userdata:需要往stream中填充PCM数据
 * stream 流数据
 * len 希望填充的大小(samples * format *channels / 8)
 **/

void pull_audio_data(void *userdata,Uint8 *stream,int len){
    qDebug() << "pull_audio_data" << 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 播放音频有两种模式
 * 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;
    //设置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();

}

2.1 方法解说

初始化子系统

// 初始化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;
}

播放音乐

// 开始播放(0是取消暂停)
SDL_PauseAudio(0);
 
while (!isInterruptionRequested()) {
    if (buffer.len > 0) continue;
    // 每一个样本的大小
    int size = spec.channels * SDL_AUDIO_BITSIZE(spec.format) / 8;
    // 最后一次播放的样本数量
    int samples = buffer.pullLen / size;
    // 最后一次播放的时长
    int ms = samples * 1000 / spec.freq;
    SDL_Delay(ms);
    break;
}

播放回调函数

// 等待音频设备回调(会回调多次)
void pull_audio_data(void *userdata,
                     // 需要往stream中填充PCM数据
                     Uint8 *stream,
                     // 希望填充的大小(samples * format * channels / 8)
                     int len
                    ) {
    // 清空stream
    SDL_memset(stream, 0, len);
 
    AudioBuffer *buffer = (AudioBuffer *) userdata;
 
    // 文件数据还没准备好
    if (buffer->len <= 0) return;
 
    // 取len、bufferLen的最小值
    buffer->pullLen = (len > (int) buffer->len) ? buffer->len : len;
 
    // 填充数据
    SDL_MixAudio(stream,
                 buffer->data,
                 buffer->pullLen,
                 SDL_MIX_MAXVOLUME);
    buffer->data += buffer->pullLen;
    buffer->len -= buffer->pullLen;
}

释放内存资源

// 释放WAV文件数据
SDL_FreeWAV(data);
 
// 关闭设备
SDL_CloseAudio();
 
// 清除所有的子系统
SDL_Quit();

你可能感兴趣的:(WAV的播放)