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

SDL读取PCM音频

文章目录

      • 音频相关的函数
      • 主线程循环更新
      • 回调函数fill_audio_pcm的调用频率是
      • // PCM_BUFFER_SIZE = 1024 (采样点) * 2 (通道) * 2 (字节/采样点) * 2 (帧) = 8192 字节
      • 设置的音频流大小

音频相关的函数

  • 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);

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

主线程循环更新

while (s_audio_pos < s_audio_end) {
SDL_Delay(10); // 等待PCM数据消耗
}
这里是等待将数据流消耗完之后才进行再次的数据更新
同时记录着总的数据读取量

回调函数fill_audio_pcm的调用频率是

采样数量除以采样频率 主循环里面这个循环等待的 SDL_Delay(10) 参数不能高于这个值 否则就会出现断音的情况

// PCM_BUFFER_SIZE = 1024 (采样点) * 2 (通道) * 2 (字节/采样点) * 2 (帧) = 8192 字节

#define PCM_BUFFER_SIZE (1024 * 2 * 2 * 2)
采样的字节大小计算方式

设置的音频流大小

// 数据够了就读预设长度,数据不够就只读部分(不够的时候剩多少就读取多少) 可读取的数据长度
int remain_buffer_len = s_audio_end - s_audio_pos;
len = (len < remain_buffer_len) ? len : remain_buffer_len;
// 拷贝数据到stream并调整音量 将设置流设置给SDL并进行播放 将s_audio_pos指针的位置向前挪动
不能越界读取 音频也不一定是预设音频的倍数 这里要设置

/**
 * 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(): 延时等待播放完成。
 *
 */

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

// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
// PCM_BUFFER_SIZE = 1024 (采样点) * 2 (通道) * 2 (字节/采样点) * 2 (帧) = 8192 字节
#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) {
    // stream是用于播放的流
    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并进行播放 将s_audio_pos指针的位置向前挪动
    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;
    // 主线程更新数据部分s_audio_buf 并更新s_audio_end s_audio_pos的大小
    while (1) {
        // 从文件读取PCM数据 读取数据到buf中 第二个参数表示每个元素字节的大小比如读取char类型的就是 sizeof(char)
        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;
}


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

相关文章:

  • 界面控件Kendo UI for Angular中文教程:如何构建带图表的仪表板?(一)
  • 软件工程概论项目(二),node.js的配置,npm的使用与vue的安装
  • Pycharm PyQt5 环境搭建创建第一个Hello程序
  • 在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别
  • 94个属于一区且接受医工交叉领域投稿的期刊汇总|个人观点·24-11-13
  • Spring Cloud Contract快速入门Demo
  • Docker在微服务架构中的最佳实践
  • 云速搭助力用友 BIP 平台快速接入阿里云产品
  • 计算机网络(8)数据链路层之子层
  • 沈阳乐晟睿浩科技有限公司引领新潮流
  • linux提权-RSYNC未授权访问覆盖
  • SQLite Where 子句
  • 【HAProxy08】企业级反向代理HAProxy高级功能之自定义日志格式与IP透传
  • 华为机试笔记
  • LeetCode【0038】外观数列
  • Go 语言已立足主流,编程语言排行榜24 年 11 月
  • 【基于轻量型架构的WEB开发】课程 作业3 Spring框架
  • 前端基础的讲解-JS(10)
  • Scala学习记录,case class,迭代器
  • 如何制作代购系统:从概念到实现
  • 微服务day06
  • 刷算法题(C++)
  • LeetCode【0025】K个一组翻转链表
  • 【工具插件类教学】在 Unity 中使用 iTextSharp 实现 PDF 文件生成与导出
  • Netty实现WebSocket Client三种典型方式
  • 【Springboot】黑马大事件笔记 day1