SDL读取PCM音频

news/2024/11/16 0:22:07/

文章目录

      • 音频相关的函数
      • 主线程循环更新
      • 回调函数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数据消耗
}
这里是等待将数据流消耗完之后才进行再次的数据更新
同时记录着总的数据读取量

pcm_33">回调函数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 initializeif (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_AudioSpecspec.freq = 44100; // 采样频率spec.format = AUDIO_S16SYS; // 采样点格式spec.channels = 2; // 2通道spec.silence = 0;spec.samples = 1024; // 23.2ms -> 46.4ms 每次读取的采样数量,多久产生一次回调和 samplesspec.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 audioSDL_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 momentwhile (s_audio_pos < s_audio_end) {SDL_Delay(10); // 等待PCM数据消耗}}printf("play PCM finish\n");// 关闭音频设备SDL_CloseAudio();_FAIL:// release some resourcesif (s_audio_buf)free(s_audio_buf);if (audio_fd)fclose(audio_fd);// quit SDLSDL_Quit();return 0;
}

http://www.ppmy.cn/news/1547309.html

相关文章

Go中数组和切片

数组和切片 【1】、数组 1、什么是数组 一组数 数组需要是相同类型的数据的集合 数组是需要定义大小的 数组一旦定义了大小是不可以改变的。 package mainimport "fmt"// 数组 // 数组和其他变量定义没什么区别&#xff0c;唯一的就是这个是一组数&#xff0c;需要…

layui的table组件中,对某一列的文字设置颜色为浅蓝怎么设置

问&#xff1a; layui的table组件中&#xff0c;对某一列的文字设置颜色为浅蓝怎么设置 回答&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><title>layui 表格示例</title><link r…

C++使用开源ConcurrentQueue库处理自定义业务数据类

ConcurrentQueue开源库介绍 ConcurrentQueue是一个高性能的、线程安全的并发队列库。它旨在提供高效、无锁的数据结构&#xff0c;适用于多线程环境中的数据交换。concurrentqueue 支持多个生产者和多个消费者&#xff0c;并且提供了多种配置选项来优化性能和内存使用。 Conc…

Flink使用SQL Gateway提交SQL Job到远程集群

从Flink 1.16.0开始集成了SQL Gateway功能&#xff0c;提供了多种客户端远程并发执行SQL的能力。不用再使用提交jar包的方式来创建任务了。 我是使用filnk 1.17.1版本。 官网关于SQL Gateway的讲解&#xff1a;https://nightlies.apache.org/flink/flink-docs-release-1.17/z…

单片机 外部中断实验 实验三

实验三 外部中断实验 实验目的 1、掌握MCS-51单片机外部中断的原理。 2、掌握MCS-51单片机外部中断程序的设计方法及其过程。 3、掌握MCS-51单片机外部中断的电路应用。 实验任务 利用外部中断方式&#xff0c;实现通过按键切换流水灯的流向。流水灯形式自定&#xff…

Rust实战项目与未来发展——跨平台应用开发项目实践

第十章&#xff1a;实战项目与未来发展 第一节&#xff1a;跨平台应用开发项目实践 随着移动设备、桌面设备和Web平台之间界限的模糊&#xff0c;跨平台应用开发已成为开发者日常工作中不可或缺的一部分。随着技术栈的不断演进&#xff0c;开发者有更多选择来构建高效、易维护…

[AI] 从游戏到现实:强化学习的应用与挑战

随着AI技术的快速发展,强化学习(Reinforcement Learning, RL)逐渐成为人工智能领域的一个重要分支。尤其是在游戏领域,RL展示了极大的潜力:它可以在没有预先标记的数据情况下,通过智能体的互动和反馈自主学习。然而,强化学习的影响力远远超越了游戏本身,它的理念和方法…

风险数据集市整体架构及技术实现

引言 在当今大数据时代&#xff0c;风险数据集市作为金融机构的核心基础设施之一&#xff0c;扮演着至关重要的角色。它不仅为银行、保险等金融机构提供了全面、准确的风险数据支持&#xff0c;还帮助这些机构实现了风险管理的精细化和智能化。本文将深入探讨一种基于大数据La…