SDL播放音频

543 阅读2分钟

播放音频基本流程

打开音频设备 ---> 设置音频参数 ---> 声卡回调读取音频数据 ---> 播放音频 ---> 关闭音频设备

播放音频基本原则

声卡通过回调函数主动获取音频数据,而非主动推给声卡 音频数据大小由音频参数(采样率、声道数等)决定

SDL音频API

  • SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained)
// 打开音频设备
int SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained)
{
    SDL_AudioDeviceID id = 0;
    /* Start up the audio driver, if necessary. This is legacy behaviour! */
    // 判断是否初始化SDL_INIT_AUDIO
    if (!SDL_WasInit(SDL_INIT_AUDIO)) {
        if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
            return -1;
        }
    }
    
    /* SDL_OpenAudio() is legacy and can only act on Device ID #1. */
    if (open_devices[0] != NULL) {
        return SDL_SetError("Audio device is already opened");
    }

    if (obtained) {
        id = open_audio_device(NULL, 0, desired, obtained,
                               SDL_AUDIO_ALLOW_ANY_CHANGE, 1);
    } else {
        SDL_AudioSpec _obtained;
        SDL_zero(_obtained);
        id = open_audio_device(NULL, 0, desired, &_obtained, 0, 1);
        /* On successful open, copy calculated values into 'desired'. */
        if (id > 0) {
            desired->size = _obtained.size;
            desired->silence = _obtained.silence;
        }
    }
    SDL_assert((id == 0) || (id == 1));
    return (id == 0) ? -1 : 0;
}
  • SDL_CloseAudio(void)
//关闭音频设备
void SDL_CloseAudio(void)
{
    SDL_CloseAudioDevice(1);
}

void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
{
    close_audio_device(get_audio_device(devid));
}
  • SDL_PauseAudio(int pause_on)
// 设置音频播放状态
void SDL_PauseAudio(int pause_on)
{
    SDL_PauseAudioDevice(1, pause_on);
}

void SDL_PauseAudioDevice(SDL_AudioDeviceID devid, int pause_on)
{
    SDL_AudioDevice *device = get_audio_device(devid);
    if (device) {
        current_audio.impl.LockDevice(device);
        SDL_AtomicSet(&device->paused, pause_on ? 1 : 0);
        current_audio.impl.UnlockDevice(device);
    }
}
  • SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume)
// 混合音频数据到声卡
void
SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume)
{
    /* Mix the user-level audio format */
    SDL_AudioDevice *device = get_audio_device(1);
    if (device != NULL) {
        SDL_MixAudioFormat(dst, src, device->callbackspec.format, len, volume);
    }
}

SDL播放音频DEMO

#include <SDL2/SDL.h>

#define BLOCK_SIZE 4096000
static size_t buf_len = 0;
static Uint8 *audio_buf = NULL;
static Uint8 *audio_pos = NULL;

void read_audio_data(void *udata, Uint8 *stream, int len){
    if(buf_len == 0){
        return;
    }
    //清空声卡缓存区
    SDL_memset(stream, 0, len);
    //设置copy数据大小
    len = (len < buf_len) ? len : buf_len;
    //copy音频数据
    SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
    audio_pos += len;
    buf_len -= len;
}

int main(int argc, char* argv[]){
    int ret = -1;
    char *path = "./audio_player.pcm";
    FILE *audio_fd = NULL;

    if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)){
        SDL_Log("Failed to Audio init, ERROR:'%s' \n",SDL_GetError());
        return ret;
    }

    audio_fd = fopen(path, "r");
    if (!audio_fd){
        SDL_Log("Failed to open pcm file:'%s', ERROR:'%s' \n",path, SDL_GetError());
        goto _FAIL;
    }
    audio_buf = (Uint8*)malloc(BLOCK_SIZE);
    if(!audio_buf){
        SDL_Log("Failed to alloc memory, ERROR:'%s'! \n", SDL_GetError());
        goto _FAIL;
    }
    SDL_AudioSpec spec;
    spec.freq = 44100;
    spec.channels = 2;
    spec.format = AUDIO_S16SYS;
    spec.silence = 0;
    //声卡获取音频数据回调函数名称
    spec.callback = read_audio_data;
    //声卡获取音频数据回调函数参数
    spec.userdata = NULL;
    //打开音频设备
    if(SDL_OpenAudio(&spec, NULL)) {
        SDL_Log("Failed to open audio device, ERROR:'%s' \n", SDL_GetError());
        goto _FAIL;
    }
    //设置音频开始暂停:0:开始,1:暂停
    SDL_PauseAudio(0);
    do{
        buf_len = fread(audio_buf, 1, BLOCK_SIZE, audio_fd);
        audio_pos = audio_buf;
        while (audio_pos < (audio_buf + buf_len)){
            SDL_Delay(1);
        }
        
    } while (buf_len != 0);
    //关闭音频设备
    SDL_CloseAudio();
    return 0;

    
_FAIL:
    if(audio_buf){
        free(audio_buf);
    }
    if(audio_fd){
        fclose(audio_fd);
    }
    SDL_Quit();
    return ret;
}

备注:FFMpeg提取PCM格式音频数据:

ffmpeg -i yhz.1080p.mkv -ar 48000 -ac 2 -f s16le audio_player.pcm

yhz.1080p.mkv: 替换成您的音频文件名称