美文网首页工作生活
SDL实现PCM播放器

SDL实现PCM播放器

作者: 一叶知秋0830 | 来源:发表于2019-08-10 14:45 被阅读0次

sdl实现pcm播放主要包括以下几个步骤:
打开音频设备-->设置音频参数(采样率、采样大小、通道数等)-->向声卡传pcm数据-->播放音频-->关闭音频设备

有一点要注意的是,和sdl播放YUV不一样的是不是我们主动把要播放的数据推给声卡,而是声卡向我们要数据。声卡缓存区大小都不一样,所以一下推很多数据过去声卡可能承受不了这么多数据。而且声卡是可以根据音频参数计算出单位时间内可以播放多大量的数据,当数据播放完是会发一个通知再给它传数据。

/*PCM播放器*/
#include <stdio.h>
#include <SDL.h>

// 定义一个数值较大的宏(4兆,用于分配读取pcm数据块的存储空间大小)
#define BLOCK_SIZE 4096000

// 定义一个全局指针其他函数也可以访问,指向从pcm文件中读取的数据块
static Uint8 *audio_buf = NULL;

// 实际读取的数据大小(我想读4兆但文件中剩余的可能没有4兆了)
static size_t buffer_len = 0;

// 当前播放的位置
static Uint8 *audio_pos = NULL;

/*
声卡回调函数,每次从audio_buf中拷贝len长度的数据到stream(sdl的缓存区,也可以理解为声卡的缓存区)
udata是设置回调函数时传的参数userdata
stream是需要播放的音频buf的地址
len是声卡可播放的音频数据的长度(这个是由声卡根据音频的参数计算出来的)
*/
void read_audio_data(void *udata,Uint8 *stream, int len){
    if (buffer_len == 0) // 没有数据了
    {
        return;
    }

    SDL_memset(stream,0,len); // 清空sdl缓存中遗留的数据

    // 声卡计算出来每次播放数据大小是len,所以每次拷贝的数据长度不能超过len
    len = (len<buffer_len) ? len : buffer_len;
    // 将数据拷贝到stream指向的地址,拷贝的数据从audio_pos开始取len长度,SDL_MIX_MAXVOLUME是将音量设置为最大
    SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);

    // 拷贝完后audio_pos后移len长度,bufer_len减少len
    audio_pos += len;
    buffer_len -= len;
}

int main(int argc, char* argv[])
{
    int ret = -1;
    char *filePath = NULL; // pcm文件路径
    FILE *file = NULL; // 打开的pcm文件
    SDL_AudioSpec spec; // 音频参数信息

    if (argc < 2)
    {
        printf("入口函数参数个数不对!\n");
        return ret;
    }

    filePath = argv[1];
    printf("-->%s\n", filePath);

    // 初始化(返回0表示成功)
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
    {
        printf("sdl初始化失败!\n");
        return ret;
    }

    // 打开pcm文件
    file = fopen(filePath,"r");
    if (!file)
    {
        printf("pcm文件打开失败!\n");
        goto __EXIT;
    }

    // 分配存储读取pcm文件数据库的空间
    audio_buf = (Uint8 *)malloc(BLOCK_SIZE);
    if (!audio_buf)
    {
        printf("分配audio_buf空间失败!\n");
        goto __EXIT;
    }

    // 打开音频设备并设置音频参数信息
    spec.freq = 44100; // 采样率
    spec.channels = 2; // 双声道
    spec.format = AUDIO_S16SYS; // 采样大小
    spec.silence = 0;
    spec.samples = 2048;
    spec.callback = read_audio_data; // 设置声卡的回调函数
    spec.userdata = NULL; // 设置回调参数
    if(SDL_OpenAudio(&spec,NULL)){
        printf("打开音频设备失败!\n");
        goto __EXIT;
    }

    // 启动播放(0表示播放状态,1表示暂停状态)
    SDL_PauseAudio(0);

    // 从pcm文件读取数据存放在audio_buf中
    do
    {
        // 从文件读取BLOCK_SIZE大小的数据到audio_buf中
        buffer_len = fread(audio_buf,1,BLOCK_SIZE,file);
        printf("读取文件长度%zu\n",buffer_len );
        audio_pos = audio_buf; // 当前播放的地方
        /*audio_pos在回调函数里面会改变。
        每隔一段时间(1ms)判断当前播放的地方是否已经到了audio_buf的尾部。
        如果没到尾部继续循环等待,如果到了尾部就表示audio_buf已经播放完了。
        然后再次读取BLOCK_SIZE大小的数据到audio_buf中,如此循环知道播放完为止*/
        while(audio_pos < (audio_buf+buffer_len)){
            SDL_Delay(1);
        }

    } while (buffer_len); // buffer_len==0就表示读到文件尾了

    // 关闭音频设备
    SDL_CloseAudio();

__EXIT:
    if (file) // 关闭文件
    {
        fclose(file);
    }
    if (audio_buf) // 是否audio_buf存储空间
    {
        free(audio_buf);
    }


    SDL_Quit();
    
    return 0;
}

相关文章

网友评论

    本文标题:SDL实现PCM播放器

    本文链接:https://www.haomeiwen.com/subject/axzrhctx.html