SDL读取PCM音频

时间:2024-11-09 11:20:56
/** * 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; }