音视频-SDL2播放PCM和WAV

SDL2

2.0.14

我的在win下,下载的是SDL2-devel-2.0.14-mingw.tar.gz (MinGW 32/64-bit)
如果是MAC就不用自己配置环境变量这么麻烦

Win环境下配:
image.png
项目配置:
macx {
    SDL_HOME = /usr/local/Cellar/sdl2/2.0.14_1
}

win32 {
    SDL_HOME = G:\SDL2-2.0.14\x86_64-w64-mingw32
}

INCLUDEPATH += $${SDL_HOME}/include
LIBS += -L $${SDL_HOME}/lib \
-lSDL2 \
-lSDL2_test \
-lSDL2main

地址路径随自己改

配置完毕就差不多可以整理代码了.

PS:可能遇到(我遇到)的问题,

一点击运行就提示, 程序运行奔溃。 这个时候需要检查环境变量是否已经配置好。 在项目中清除所有的已经构建的, 再次运行。



Demo



导入SDL2

extern "C" {
#include 
}

配置基本信息

#ifdef Q_OS_WIN
    #define FILEPATH "G:/Resource/"
    #define FILENAME "record_to_pcm.pcm"
    #define FORMAT AUDIO_S16SYS
#else
    #define FILEPATH "/Users/lumi/Desktop/"
    #define FILENAME "record_to_pcm.pcm"
    #define FORMAT AUDIO_F32
#endif

//采样率
#define SAMPLE_RATE 44100
//位深度
#define SAMPLE_BIT_DEPTH 16
//声道数
#define CHANNELS 2
//音频缓存区的样本大小
#define SAMPLES 4096
//每个样本占多少字节 (1byte = 8bit)
#define SAMPLE_PER_BYTES ((SAMPLE_BIT_DEPTH * CHANNELS) / 8)
//文件缓冲区的大小 (样本大小 *  每个样本占多少字节)
#define BUFFER_SIZE (SAMPLES * SAMPLE_PER_BYTES)

配置回调函数

int bufferLen = 0;
Uint8 *bufferData = nullptr;

// 等待音频设备回调(会回调多次)
void pull_audio_data (void *userdata,
                      // 需要往stream中填充PCM数据
                      Uint8 *stream,
                      // 希望填充的大小(samples * format * channels / 8)
                      int len) {
    qDebug() << "pull_audio_data" << len;
    if (stream == nullptr) {
        return;
    }
    SDL_memset(stream, 0, len);
    // 数据还没有准备好
    if(bufferLen <= 0) {
        return;
    }
    len = len > bufferLen ?  bufferLen : len;
    SDL_MixAudio(stream, bufferData, len, SDL_MIX_MAXVOLUME);
    bufferData += len;
    bufferLen -= len;
}

SDL播放PCM

void Playthread::run() {
    if(SDL_Init(SDL_INIT_EVERYTHING)) {
        qDebug() << "SDL_Init error : " << SDL_GetError();
        return;
    }
    SDL_AudioSpec audioSpec;
    audioSpec.freq = SAMPLE_RATE;
    audioSpec.format = FORMAT;
    audioSpec.channels = CHANNELS;
    audioSpec.samples = SAMPLES;
    audioSpec.callback = pull_audio_data;
    audioSpec.userdata = nullptr;
    if(SDL_OpenAudio(&audioSpec, nullptr)) {
        qDebug() << "SDL_OpenAudio err" << SDL_GetError();
        SDL_Quit();
        return;
    }
    // 打开文件
    QString filename = FILEPATH;
    filename += FILENAME;
    QFile file(filename);
    if (!file.open(QFile::ReadOnly)) {
        qDebug() << "file open fail : " << filename;
        SDL_CloseAudio();
        SDL_Quit();
        return;
    }
    // 开始播放(0是取消暂停)
    SDL_PauseAudio(0);
    // 存放从文件中读取的数据
    Uint8 data[BUFFER_SIZE];
    while(!isInterruptionRequested()) {
        if (bufferLen > 0) {
            continue;
        }
        bufferLen = file.read((char *)data, BUFFER_SIZE);
        //文件数据读取完毕
        if (bufferLen <= 0) {
            break;
        }
        bufferData = data;
    }
    // 关闭文件
    file.close();
    // 关闭设备
    SDL_CloseAudio();
    // 清除所有子系统
    SDL_Quit();
}


学习研究

SDL_AudioSpec

/**
 * 此结构中的计算值由 SDL_OpenAudio() 计算。
 *
 * 对于多声道音频,默认的 SDL 声道映射为:
 * 2:FL FR(立体声)
 * 3:FL FR LFE(2.1 环绕声)
 * 4:FL FR BL BR(四边形)
 * 5:FL FR FC BL BR(四边形 + 中心)
 * 6:FL FR FC LFE SL SR(5.1环绕声-最后两个也可以是BL BR)
 * 7:FL FR FC LFE BC SL SR(6.1 环绕声)
 * 8:FL FR FC LFE BL BR SL SR(7.1 环绕声)
 */
typedef struct SDL_AudioSpec
{
    int freq;                            /**< DSP 频率 -- 每秒采样数 */
    SDL_AudioFormat format;             /**< 音频数据格式 */
    Uint8 channels;                     /**< 声道数:1个单声道,2个立体声*/
    Uint8 silence;                      /**< 音频缓冲静音值(计算)*/
    Uint16 samples;                     /**< 样本帧中的音频缓冲区大小(总样本除以通道数)*/
    Uint16 padding;                     /**< 某些编译环境需要 */
    uint32 size;                        /**< 以字节为单位的音频缓冲区大小(计算)*/
    SDL_AudioCallback callback;         /**< 提供音频设备的回调(NULL 以使用 SDL_QueueAudio())。 */
    void *userdata;;                    /**< 传递给回调的用户数据(NULL 回调时忽略)。 */
} SDL_AudioSpec;


SDL_AudioCallback

/**
  * 当音频设备需要更多数据时调用此函数。
  *
  * param userdata 保存在 SDL_AudioSpec 结构中的应用程序特定参数
  * param stream 指向音频数据缓冲区的指针。
  * 参数 len 该缓冲区的长度(以字节为单位)。
  *
  * 一旦回调返回,缓冲区将不再有效。立体声样本存储在 LRLRLR 排序中。
  *
  * 如果您愿意,您可以选择避免回调并改用 SDL_QueueAudio()。 只需使用 NULL 回调打开您的音频设备。
  */
typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, 
                                            Uint8 * stream,
                                            int len);


SDL_QueueAudio()

 SDL offers two ways to feed audio to the device:
`you can either supply a callback that SDL triggers with some frequency to obtain more audio  (pull method)`,
or you can supply no callback, and then SDL will expect  you to supply data at regular intervals (push method) with this function.

SDL 提供两种向设备馈送音频的方法:
`您可以提供一个回调,SDL 以某种频率触发以获取更多音频(拉动方法)`,
或者您可以不提供回调,然后 SDL 将期望您提供数据具有此功能的定期间隔(推送方法)。
extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len);

SDL播放音频有2种模式:
Push(推):【程序】主动推送数据给【音频设备】
Pull(拉):【音频设备】主动向【程序】拉取数据

所以关于SDL_AudioCallback callback; 我们需要使用pull的方式, 也就是设置callback回调。 这个回调会以某种频率触发更多的音频。



SDL_OpenAudio

该函数打开所需参数的音频设备,成功返回0,将实际的硬件参数放入得到的参数指向的obtained中。
`如果obtained 为NULL,则传递给回调函数的音频数据将保证为请求的格式,并在必要时自动转换为硬件音频格式。`
如果无法打开音频设备或无法设置音频线程,则此函数返回 -1.
...
音频设备在打开时开始播放静音,当您准备好调用音频回调函数时,应通过调用 \c SDL_PauseAudio(0) 启用播放。
由于音频驱动程序可能会修改请求的音频缓冲区大小,因此您应该在打开音频设备后分配所有本地混合缓冲区
extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired,
                                          SDL_AudioSpec * obtained);

对应的有打开就有关闭

extern DECLSPEC void SDLCALL SDL_CloseAudio(void);

简单粗暴理解,
第一个SDL_AudioSpec * desired是我们需要打开的文件对应的相关的SDL_AudioSpec 格式配置,
第二个SDL_AudioSpec * obtained是我们对应的音频设备(硬件)的格式配置, 传null自动获取



SDL_PauseAudio

/**
  * \name 暂停音频功能
  *
  * 这些函数暂停和取消暂停音频回调处理。
  * 打开音频后,应使用参数 0 调用它们
  * 设备开始播放声音。 这样你就可以安全地初始化
  * 打开音频设备后回调函数的数据。
  * 在暂停期间静音将写入音频设备。
  */
extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on);

也就是打开了之后, 使用参数 0 调用它们, 开始播放



SDL_MixAudio

#define SDL_MIX_MAXVOLUME 128
/**
 *  This takes two audio buffers of the playing audio format and mixes
 *  them, performing addition, volume adjustment, and overflow clipping.
 *  The volume ranges from 0 - 128, and should be set to ::SDL_MIX_MAXVOLUME
 *  for full audio volume.  Note this does not change hardware volume.
 *  This is provided for convenience -- you can mix your own audio data.
 */
* 这需要播放音频格式的两个音频缓冲区并将它们混合,执行添加、音量调整和溢出剪辑。
* 音量范围为 0 - 128,应设置为 ::SDL_MIX_MAXVOLUME 以获得完整的音频音量。 请注意,这不会更改硬件音量。
* 这是为了方便而提供的——您可以混合自己的音频数据。
extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 * dst, 
                                          const Uint8 * src,
                                          Uint32 len, 
                                          int volume);

总结:

1.SDL_Init(SDL_INIT_AUDIO)
2.file.open
3.SDL_OpenAudio
4.SDL_PauseAudio(0)

pull_audio_data

SDL_memset
SDL_MixAudio

5.file.close
6.SDL_CloseAudio
7.SDL_Quit






播放WAV

播放wav和播放PCM差不多, 整体流程总结为

1.SDL_Init(SDL_INIT_AUDIO)
2.SDL_LoadWAV
3.SDL_OpenAudio
4.SDL_PauseAudio(0)

pull_audio_data

SDL_memset
SDL_MixAudio

5.SDL_FreeWAV
6.SDL_CloseAudio
7.SDL_Quit

就是打开pcm文件和打开wav不同的方式。

void Playthread::run() {
    if(SDL_Init(SDL_INIT_AUDIO)) {
        qDebug() << "SDL_Init error : " << SDL_GetError();
        return;
    }
    SDL_AudioSpec audioSpec;
    audioSpec.freq = SAMPLE_RATE;
    audioSpec.format = FORMAT;
    audioSpec.channels = CHANNELS;
    audioSpec.samples = SAMPLES;
    audioSpec.callback = pull_audio_data;
    audioSpec.userdata = (void *)3;
    if(SDL_OpenAudio(&audioSpec, nullptr)) {
        qDebug() << "SDL_OpenAudio err" << SDL_GetError();
        SDL_Quit();
        return;
    }
    // 打开文件
    QString filename = FILEPATH;
    filename += FILENAME;
    QFile file(filename);
    if (!file.open(QFile::ReadOnly)) {
        qDebug() << "file open fail : " << filename;
        SDL_CloseAudio();
        SDL_Quit();
        return;
    }
    // 开始播放(0是取消暂停)
    SDL_PauseAudio(0);
    // 存放从文件中读取的数据
    Uint8 data[BUFFER_SIZE];
    while(!isInterruptionRequested()) {
        if (bufferLen > 0) {
            continue;
        }
        bufferLen = file.read((char *)data, BUFFER_SIZE);
        //文件数据读取完毕
        if (bufferLen <= 0) {
            break;
        }
        bufferData = data;
    }
    // 关闭文件
    file.close();
    // 关闭设备
    SDL_CloseAudio();
    // 清除所有子系统
    SDL_Quit();
}

void pull_audio_data (void *userdata,
                      // 需要往stream中填充PCM数据
                      Uint8 *stream,
                      // 希望填充的大小(samples * format * channels / 8)
                      int len) {
    qDebug() << "pull_audio_data userdata : " << userdata;
    qDebug() << "pull_audio_data stream : " << stream;
    qDebug() << "pull_audio_data len : " << len;
    if (stream == nullptr) {
        return;
    }
    SDL_memset(stream, 0, len);
    // 数据还没有准备好
    if(bufferLen <= 0) {
        return;
    }
    len = len > bufferLen ?  bufferLen : len;
    SDL_MixAudio(stream, bufferData, len, SDL_MIX_MAXVOLUME);
    bufferData += len;
    bufferLen -= len;
}

你可能感兴趣的:(音视频-SDL2播放PCM和WAV)