打开音频设备
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:音频缓冲区的大小(以字节为单位)。
void (SDLCALL* SDL AudioCallback) (void *userdata, Uint8 *stream, int len);
播放音频数据
// 当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会播放静音的值。
void SDLCALL SDL_PauseAudio(int pause_on)
/************************************ ffmpeg 提取 PCM 音频数据 ****************************************/
提取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
/************************************ ffmpeg 提取 PCM 音频数据 ****************************************/
公共部分
// 音频PCM数据缓存
static Uint8 *s_audio_buf = NULL;
// 目前读取的位置
static Uint8 *s_audio_pos = NULL;
// 缓存结束位置
static Uint8 *s_audio_end = NULL;
回调函数
// void(SDLCALL *SDL AudioCallback)(void *userdata, Uint8 *stream, int len);
void fill_audio_pcm(void *udata, Uint8 *stream, int len)
{
SDL_memset(stream, 0, len); // stream - 音频缓冲区的数据指针
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); // 采样率 1024 | 声道数 2 | 音频数据格式是 16 bit
s_audio_pos += len; // 移动缓存指针
}
主线程
int PCM_audio_display()
{
int ret = -1;
FILE *audio_fd = NULL;
SDL_AudioSpec spec;
const char *path = "/home/king/ffmpeg_src/lessonCode/ZeroVoiceEducation/01_lesson/44100_16bit_2ch.pcm";
// 每次缓存的长度
size_t read_buffer_len = 0;
// SDL initialize
SDL_Init(SDL_INIT_AUDIO); // 支持AUDIO
// 打开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; // 设置静音的值,因为声音采样是有符号的,所以0当然就是这个值
spec.samples = 1024; // 每次读取的采样数量,多久产生一次回调和 samples 有关 <23.2ms>
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;
}
// 当pause_on设置为0的时候即可开始播放音频数据。设置为1的时候,将会播放静音的值。
SDL_PauseAudio(0);
int data_count = 0; // 记录读取总的字节数
while (TRUE)
{
// 从文件中读取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("读取的数据总字节数 %10d .\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(2); // 等待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;
}