当前位置: 首页 > article >正文

SDL 播放PCM

SDL2播放PCM使用SDL2播放PCM音频采样数据。SDL实际上是对底层绘图API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层API。
测试的PCM数据采用采样率44.1k, 采用精度S16SYS, 通道数2

函数调用步骤如下:

  • [初始化]
  • SDL_Init(): 初始化SDL。
  • SDL_OpenAudio(): 根据参数(存储于SDL_AudioSpec)打开音频设备。
  • SDL_PauseAudio(): 播放音频数据。
  • [循环播放数据]
  • SDL_Delay(): 延时等待播放完成。
打开音频设备
int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, 
SDL_AudioSpec * obtained); 
// desired:期望的参数。
// obtained:实际音频设备的参数,一般情况下设置为NULL即可。
SDL_AudioSpec
typedef struct SDL_AudioSpec {
int freq; // 音频采样率
SDL_AudioFormat format; // 音频数据格式
Uint8 channels; // 声道数: 1 单声道, 2 立体声
Uint8 silence; // 设置静音的值,因为声音采样是有符号的,所以0当然就是这个值
Uint16 samples; // 音频缓冲区中的采样个数,要求必须是2的n次
Uint16 padding; // 考虑到兼容性的一个参数
Uint32 size; // 音频缓冲区的大小,以字节为单位
SDL_AudioCallback callback; // 填充音频缓冲区的回调函数
void *userdata; // 用户自定义的数据
} SDL_AudioSpec;

SDL_AudioCallback
// userdata:SDL_AudioSpec结构中的用户自定义数据,一般情况下可以不用。
// stream:该指针指向需要填充的音频缓冲区。
// len:音频缓冲区的大小(以字节为单位)1024*2*2。
void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 *stream, int len)

播放音频数据
// 当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会播放静音的值。
void SDLCALL SDL_PauseAudio(int pause_on)


#include <stdio.h>
#include <SDL.h>

// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
#define PCM_BUFFER_SIZE (1024*2*2*2)

// 音频PCM数据缓存
static Uint8 *s_audio_buf = NULL;
// 目前读取的位置
static Uint8 *s_audio_pos = NULL;
// 缓存结束位置
static Uint8 *s_audio_end = NULL;


//音频设备回调函数
void fill_audio_pcm(void *udata, Uint8 *stream, int len)
{
    SDL_memset(stream, 0, len);

    if(s_audio_pos >= s_audio_end) // 数据读取完毕
    {
        return;
    }

    // 数据够了就读预设长度,数据不够就只读部分(不够的时候剩多少就读取多少)
    int remain_buffer_len = s_audio_end - s_audio_pos;
    len = (len < remain_buffer_len) ? len : remain_buffer_len;
    // 拷贝数据到stream并调整音量
    SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME/8);
    printf("len = %d\n", len);
    s_audio_pos += len;  // 移动缓存指针
}

// 提取PCM文件
// ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
// 测试PCM文件
// ffplay -ar 44100 -ac 2 -f s16le 44100_16bit_2ch.pcm
#undef main
int main(int argc, char *argv[])
{
    int ret = -1;
    FILE *audio_fd = NULL;
    SDL_AudioSpec spec;
    const char *path = "44100_16bit_2ch.pcm";
    // 每次缓存的长度
    size_t read_buffer_len = 0;

    //SDL initialize
    if(SDL_Init(SDL_INIT_AUDIO))    // 支持AUDIO
    {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return ret;
    }

    //打开PCM文件
    audio_fd = fopen(path, "rb");
    if(!audio_fd)
    {
        fprintf(stderr, "Failed to open pcm file!\n");
        goto _FAIL;
    }

    s_audio_buf = (uint8_t *)malloc(PCM_BUFFER_SIZE);

    // 音频参数设置SDL_AudioSpec
    spec.freq = 44100;          // 采样频率
    spec.format = AUDIO_S16SYS; // 采样点格式
    spec.channels = 2;          // 2通道
    spec.silence = 0;
    spec.samples = 1024;       // 23.2ms -> 46.4ms 每次读取的采样数量,多久产生一次回调和 samples
    spec.callback = fill_audio_pcm; // 回调函数
    spec.userdata = NULL;

    //打开音频设备
    if(SDL_OpenAudio(&spec, NULL))
    {
        fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
        goto _FAIL;
    }

    //play audio
    SDL_PauseAudio(0);

    int data_count = 0;
    while(1)
    {
        // 从文件读取PCM数据
        read_buffer_len = fread(s_audio_buf, 1, PCM_BUFFER_SIZE, audio_fd);
        if(read_buffer_len == 0)
        {
            break;
        }
        data_count += read_buffer_len; // 统计读取的数据总字节数
        printf("now playing %10d bytes data.\n",data_count);
        s_audio_end = s_audio_buf + read_buffer_len;    // 更新buffer的结束位置
        s_audio_pos = s_audio_buf;  // 更新buffer的起始位置
        //the main thread wait for a moment
        while(s_audio_pos < s_audio_end)
        {
            SDL_Delay(10);  // 等待PCM数据消耗
        }
    }
    printf("play PCM finish\n");
    // 关闭音频设备
    SDL_CloseAudio();

_FAIL:
    //release some resources
    if(s_audio_buf)
        free(s_audio_buf);

    if(audio_fd)
        fclose(audio_fd);

    //quit SDL
    SDL_Quit();

    return 0;
}

获取更多Linux C/C++资料


http://www.kler.cn/a/386406.html

相关文章:

  • DAY112代码审计PHP开发框架POP链利用Yii反序列化POP利用链
  • JAVA:探索 EasyExcel 的技术指南
  • SQL集合运算
  • HBase 安装与基本操作指南
  • 数字后端教程之Innovus report_property和get_property使用方法及应用案例
  • 【go从零单排】JSON序列化和反序列化
  • 国内读新加坡公立大学在职博士是一种怎样的体验?还中文授课
  • Python学习从0到1 day27 第三阶段 Spark ③ 数据计算 Ⅱ
  • Nuxt3之使用lighthouse性能测试及性能优化实操
  • MySQL 中的 `IN`、`EXISTS` 区别与性能分析
  • Kubernetes-编排工具篇-01-Kustomize与Helm对比
  • 安装和运行开发微信小程序
  • 贪心算法day2(最长递增子序列)
  • 常见插入排序算法的实现(直接插入排序与希尔排序)
  • 虚拟化负载均衡至少需要几台服务器?
  • Linux服务器网络故障排查命令
  • 【前端】Svelte:事件处理
  • Node.js——fs模块-文件重命名和移动
  • 【Django】配置文件 settings.py
  • shodan4(泷羽sec)
  • STM32——毕设基于单片机的多功能节能窗控制系统
  • JavaWeb合集23-文件上传
  • kafka 安装和使用
  • vue3+vite 前端打包不缓存配置
  • Spring中的过滤器和拦截器
  • ORU——ORAN 无线电单元参考架构