SDL播放PCM文件
C语言文件打开方式
// 提取PCM文件
// ffmpeg -i input.mp4 -t 20 -codec:a pcm_s16le -ar 44100 -ac 2 -f s16le suiyue_44100_2_s16le.pcm
// 测试PCM文件
// ffplay -ar 44100 -ac 2 -f s16le suiyue_44100_2_s16le.pcm
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
extern "C"
{
#include <SDL.h>
}
// 每次读取2帧数据, 以1024个采样点一帧 2通道 16bit采样点为例
#define PCM_BUFFER_SIZE (2 * 1024 * 2 * 2)
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)
{
// udata: 用户自定义数据
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;
// 设置混音
SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME / 8); // 音量的值 SDL_MIX_MAXVOLUME 128
printf("use %d bytes\n", len); // 4096 bytes samples * channels * format
s_audio_pos += len; // 移动缓存指针(当前pos)
}
#undef main
int main()
{
int ret = -1;
FILE *audio_fd = NULL;
SDL_AudioSpec spec; // SDL音频设备
const char *path = "./suiyue_44100_2_s16le.pcm";
size_t read_buffer_len = 0; // 存储每次读取文件数据的长度
// 1. SDL初始化AUDIO
if (SDL_Init(SDL_INIT_AUDIO))
{
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
return ret;
}
// 2. 打开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; // 采样频率 44.1k
spec.format = AUDIO_S16SYS; // 采样点格式 (交错模式16bit)
spec.channels = 2; // 2通道
spec.silence = 0; // 设置静音的值
// samples * channels * format = 4096 bytes (回调每次读取的数据)
// 每次读取的采样数量,多久调用一次回调和samples有关系(回调间隔 1024 / 44100)23.2ms
spec.samples = 1024;
spec.callback = fill_audio_pcm; // 设置回调函数
spec.userdata = NULL;
// 3. 打开音频设备
if (SDL_OpenAudio(&spec, NULL))
{
fprintf(stderr, "Failed to open audio device, %s\n", SDL_GetError());
goto _FAIL;
}
// 4. 设置为0时开始播放,为1时播放静音数据
SDL_PauseAudio(0);
int data_count = 0; // 存储读取的总字节数
while (1)
{
// 从文件读取PCM数据到s_audio_buf中(每次读取两帧数据)
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("read %10d bytes data\n", data_count); // 每次加2帧数据 8192 bytes
s_audio_end = s_audio_buf + read_buffer_len; // 更新buffer的结束位置(buf位置 + 本次读取的长度)
s_audio_pos = s_audio_buf; // 更新buffer的起始位置
while (s_audio_pos < s_audio_end) // 等待回调函数将数据读取完毕
{
// 不能大于上面计算的23.2ms 否则会产生断音
SDL_Delay(10); // 等待PCM数据消耗
}
}
printf("play PCM finish\n");
// 关闭音频设备
SDL_CloseAudio();
_FAIL:
// 释放资源退出
if (s_audio_buf)
free(s_audio_buf);
if (audio_fd)
fclose(audio_fd);
SDL_Quit();
return 0;
}
C++文件打开方式
-
- 初始化SDL SDL_Init
-
- 创建音频播放回调函数,打开音频设备 SDL_OpenAudio
-
- 开始播放 SDL_PauseAudio
-
- 打开文件循环读取数据到缓冲
-
- 关闭音频设备 SDL_CloseAudio
-
- 释放资源,退出 SDL_Quit
#include <iostream>
#include <fstream>
#include <cmath>
// SDL 播放PCM数据
extern "C"
{
#include <SDL.h>
}
#pragma comment(lib, "SDL2.lib")
Uint8* g_audio_chunk; // 下面读取两帧数据的缓存
Uint32 g_audio_len; // 读取的长度
Uint8* g_audio_pos; // 当前声卡消耗到的位置
// 接收音频数据的回调函数
void read_audio_data_cb(void* udata, Uint8* stream, int len)
{
SDL_memset(stream, 0x00, len);
if (g_audio_len == 0)
return;
len = SDL_min(len, static_cast<int>(g_audio_len));
// 将音频数据 g_audio_pos 传给 stream
std::cout << "use " << len << " bytes" << std::endl;
SDL_MixAudio(stream, g_audio_pos, len, SDL_MIX_MAXVOLUME); // (设置混音(也可直接拷贝))将声音喂给声卡(最后一个参数音量0-128)
g_audio_pos += len;
g_audio_len -= len;
}
#undef main
int main()
{
// 1. 初始化SDL为audio
int nRet = SDL_Init(SDL_INIT_AUDIO);
if (nRet < 0)
{
std::cout << "SDL Error: " << SDL_GetError() << std::endl;
return -1;
}
// 定义结构
SDL_AudioSpec spec;
spec.freq = 44100;
spec.channels = 2;
spec.format = AUDIO_S16SYS; // s16le
spec.samples = 1024; // 字节数(与回调间隔有关) 1024 / 44100 约 23.2ms (设置延时超过这个数会出现断音现象)
spec.callback = read_audio_data_cb; // 回调函数(回调函数缓冲len的长度为 samples * format * channels)
spec.userdata = NULL;
// 2. 根据参数打开音频设备
if (SDL_OpenAudio(&spec, NULL) < 0)
{
return -1;
}
// 3. 打开文件(需要使用二进制方式打开)
std::ifstream pcmFile("./suiyue_44100_2_s16le.pcm", std::ios::in | std::ios::binary);
if (!pcmFile.is_open())
{
return -1;
}
// 4. 开始播放
SDL_PauseAudio(0); // 0 开始播放 1 播放静音数据
char* buffer = (char*)malloc(2 * 1024 * 2 * 2); // 每次读两帧数据,以1024个采样点 双声道 位深16 为一帧
while (!pcmFile.eof())
{
// 将两帧音频数据读取到buffer中(8192 bytes)
pcmFile.read(buffer, 1024 * 2 * 2 * 2);
if (pcmFile.bad())
{
return -1;
}
g_audio_chunk = reinterpret_cast<Uint8*>(buffer); // 真实读取的音频数据
g_audio_len = pcmFile.gcount(); // 真实读取的字节数
g_audio_pos = g_audio_chunk; // 将音频位置设置到刚读取到的buffer位置
std::cout << "read " << g_audio_len << " bytes" << std::endl;
// 延时等待声卡消耗音频数据
while (g_audio_len > 0)
{
SDL_Delay(10);
}
}
// 5. 关闭音频设备
SDL_CloseAudio();
// 6. 释放资源、退出
free(buffer);
buffer = NULL;
SDL_Quit();
return 0;
}