SDL播放音频工具类封装

一、概述

  SDL2既可以播放视频也可以播放音频,前面介绍了视频的播放,本节对播放音频做一下总结。

  步骤:

    1.初始化

SDL_Init(SDL_INIT_AUDIO);

    2.根据传入的参数打开音频设备

SDL_AudioSpec sdl_spec;
sdl_spec.freq = audioSpec.freq;
sdl_spec.format = audioSpec.format;
sdl_spec.channels = audioSpec.channels;
sdl_spec.samples = audioSpec.samples;
sdl_spec.silence = 0;
sdl_spec.userdata = this;
sdl_spec.callback = AudioCallback;
//打开SDL音频播放
if (SDL_OpenAudio(&sdl_spec, nullptr) < 0)
{
    qDebug() << "SDL_OpenAudio->" << SDL_GetError() << endl;
    return false;
}

 

    3.开始/暂停播放

SDL_PauseAudio(0);//0代表开始播放,1代表停止播放

 

    4.退出音频播放

SDL_QuitSubSystem(SDL_INIT_AUDIO);

    5.设置音频播放的回调函数,并循环向音频缓冲区填充数据。声卡接收到数据后会自动播放

static void AudioCallback(void* userdata, unsigned char* stream, int len)
{
    auto player = (YAudioPlayer*)userdata;
    player->Callback(stream, len);
}

 

二、代码示例

  工具类

  1.YAudioPlayer.h

struct YAudioSpec
{
    int freq = 44100;//音频采样率
    unsigned short format = AUDIO_S16SYS;//采样格式(位深)
    unsigned char channels = 2;//声道数
    unsigned short samples = 1024;//样本数
};

/// <summary>
/// 音频数据结构体
/// </summary>
struct YAudioData
{
    std::vector<unsigned char> data;
    int offset = 0; //偏移位置
};

class YAudioPlayer
{

public:
    static YAudioPlayer* Create();

    //打开音频,开始播放,并调用回调函数
    virtual bool Open(YAudioSpec& audioSpec) = 0;//纯虚函数

    virtual void Close() = 0;

    /// <summary>
    /// 需要向音频缓冲区塞数据,数据塞进去后声卡会自动播放,只要有数据就会播放
    /// </summary>
    /// <param name="data"></param>
    /// <param name="size"></param>
    void Push(const unsigned char* data, int size)
    {
        std::unique_lock<std::mutex> lock(mux_);
        audio_datas_.push_back(YAudioData());//尾部插入数据
        audio_datas_.back().data.assign(data, data + size);//获取队列尾部对象的引用,并分配音频数据
    }

    //播放速度
    virtual void SetSpeed(float s)
    {
        auto spec = audio_spec_;
        auto old_freq = spec.freq;
        spec.freq *= s;
        Open(spec);
        audio_spec_.freq = old_freq;
    }

    //音量
    void set_volume(int v)
    {
        volume_ = v;
    }

protected:
    YAudioPlayer();
    ~YAudioPlayer();

    /// <summary>
    /// SDL音频播放回调函数
    /// </summary>
    /// <param name="stream">音频流缓冲区</param>
    /// <param name="len">数据长度</param>
    virtual void Callback(unsigned char* stream, int len) = 0;
    static void AudioCallback(void* userdata, unsigned char* stream, int len)
    {
        auto player = (YAudioPlayer*)userdata;
        player->Callback(stream, len);
    }
    std::list<YAudioData> audio_datas_;//音频缓冲列表
    std::mutex mux_;//互斥锁
    YAudioSpec audio_spec_;
    unsigned char volume_ = 128;// 0~128 音量

};

 

  2.YAudioPlayer.cpp

class CYAudioPlayer : public YAudioPlayer
{
    bool Open(YAudioSpec& audioSpec)
    {
        //退出上一次音频
        SDL_QuitSubSystem(SDL_INIT_AUDIO);

        SDL_AudioSpec sdl_spec;
        sdl_spec.freq = audioSpec.freq;
        sdl_spec.format = audioSpec.format;
        sdl_spec.channels = audioSpec.channels;
        sdl_spec.samples = audioSpec.samples;
        sdl_spec.silence = 0;
        sdl_spec.userdata = this;
        sdl_spec.callback = AudioCallback;
        //打开SDL音频播放
        if (SDL_OpenAudio(&sdl_spec, nullptr) < 0)
        {
            qDebug() << "SDL_OpenAudio->" << SDL_GetError() << endl;
            return false;
        }
        //开始播放
        SDL_PauseAudio(0);//0代表开始播放,1代表停止播放
        return true;
    }

    /// <summary>
    /// 关闭SDL音频播放器
    /// </summary>
    void Close()
    {
        SDL_QuitSubSystem(SDL_INIT_AUDIO);
        std::unique_lock<std::mutex> lock(mux_);
        audio_datas_.clear();
    }

    /// <summary>
    /// SDL音频播放回调函数
    /// </summary>
    /// <param name="stream">指向 SDL 音频输出缓冲区的指针,需要填充音频数据</param>
    /// <param name="len">输出缓冲区的大小(字节数)</param>
    void Callback(unsigned char* stream, int len)
    {
        //初始化输出缓冲区,将其全部置零(静音状态),同时也会把残余旧数据清零,确保数据都是最新的
        SDL_memset(stream, 0, len);
        //互斥锁,确保只有一个线程能够访问
        std::unique_lock<std::mutex> lock(mux_);
        if (audio_datas_.empty())return;
        auto buf = audio_datas_.front();
        // 1 buf 大于stream缓冲  offset记录位置
        // 2 buf 小于stream 缓冲  拼接
        int mixed_size = 0;     //已经处理的字节数
        int need_size = len;    //还需要处理的字节数

        while (mixed_size < len)
        {
            if (audio_datas_.empty())break;
            buf = audio_datas_.front();
            //计算当前缓冲区剩余未处理的数据大小
            int size = buf.data.size() - buf.offset;

            //若剩余数据超过需要的量,截取需要的部分
            if (size > need_size)
            {
                size = need_size;
            }
            //使用SDL库将当前缓冲区数据混合到输出流中
            SDL_MixAudio(stream + mixed_size,
                buf.data.data() + buf.offset,
                size, SDL_MIX_MAXVOLUME);
            //更新计数:减少需要的数据量,增加已处理的数据量
            need_size -= size;
            mixed_size += size;
            //标记已处理数据的位置
            buf.offset += size;
            //若当前缓冲区(list<YAudioData>)已全部处理完,从队列中移除
            if (buf.offset >= buf.data.size())
            {
                audio_datas_.pop_front();
            }
        }
    }

};

YAudioPlayer* YAudioPlayer::Create()
{
    static CYAudioPlayer cyp;
    return &cyp;
}

YAudioPlayer::YAudioPlayer()
{
}

YAudioPlayer::~YAudioPlayer()
{
    //初始化SDL音频
    SDL_Init(SDL_INIT_AUDIO);
}

 

posted on 2025-06-20 11:24  飘杨......  阅读(38)  评论(0)    收藏  举报