ffplay播放器源码(四)SDL库播放音频设备
ffplay播放器用SDL库来播放声音的,我们来看看SDL库打开音频设备函数接口
1. 初始化SDL库
int SDL_Init(Uint32 flags);
flags: SDL_INIT_TIMER支持定时器子系统,SDL_INIT_AUDIO支持音频子系统,SDL_INIT_VIDEO支持视频子系统
SDL_AudioSpec是音频输出格式的结构体:
typedef struct SDL_AudioSpec
{
int freq; //采样率(每秒采样数量)
SDL_AudioFormat format; //音频数据格式 ,AUDIO_U16SYS,AUDIO_S16SYS,AUDIO_S32SYS,AUDIO_F32SYS
Uint8 channels; //通道数量:1单声道,2立体声
Uint8 silence; //默认填0
Uint16 samples; ///以采样帧为单位的音频缓冲区大小(总采样数除以通道数)
Uint16 padding; ///默认填0
Uint32 size; ///音频缓冲区大小,默认填0,用SDL_OpenAudioDevice创建成功后才知道具体大小
SDL_AudioCallback callback; //当音频设备需要更多数据时调用的回调函数;
void *userdata; //SDL_AudioCallback回调函数运行的参数;
} SDL_AudioSpec;
2. 打开一个特定的音频设备
SDL_AudioDeviceID SDL_OpenAudioDevice(const char *device,int iscapture
,const SDL_AudioSpec *desired
,SDL_AudioSpec *obtained,
int allowed_changes);
device[in] 默认填null
iscapture[in] 默认填0
desired[in] 输入期望参数
obtained[out] 实际输出参数
allowed_changes 0或组合值,
如SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002可以动态修改音频数据格式
返回值:一个有效的设备ID,大于0时成功 否则失败;
3.关闭音频设备:
使用关闭音频处理和关闭音频设备。
void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev);
4.播放与暂停音频设备:
使用此函数暂停和播放的音频播放设备
void SDL_PauseAudioDevice(
SDL_AudioDeviceID dev //有效音频设备 可用SDL_OpenAudioDevice()函数取得
,int pause_on //非0时为暂停,0时为播放
);
用SDL库来播放音频的流程比较简单:
Uint32 flags = SDL_INIT_AUDIO
SDL_Init(flags); //1.初始化
SDL_AudioSpec inParam;
SDL_AudioSpec outParam;
//2. 设置音频输出格式
inParam.freq = 48000;
inParam.format = AUDIO_S16SYS;
inParam.channels = 2;
inParam.callback = [](void *opaque, Uint8 *stream, int len)-> void
{
//3. 设置SDL回调函数,让 SDL 来主动执行回调函数来取数据
}
//4. 打开一个的音频设备。outParam音频设备信息
SDL_AudioDeviceID devid= SDL_OpenAudioDevice(null,0,&inParam, &outParam,0);
//5.播放音频,SDL回调函数生效
SDL_PauseAudioDevice(devid,0);
.....
SDL_CloseAudioDevice()//6.关闭
接下来我们看看ffpaly是如何使用SDL库来处理音频部份:
1. 设置播放器音量大小(代码有注释):
static VideoState *stream_open(const char *filename, const AVInputFormat *iformat)
{
//SDL库音量范围[0~SDL_MIX_MAXVOLUME(128)],播放器音量范围[0~%100], SDL音量大小 = SDL_MIX_MAXVOLUME*百分比
// SDL音量大小 = SDL_MIX_MAXVOLUME*(startup_volume%100)
startup_volume = av_clip(startup_volume, 0, 100);
startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
is->audio_volume = startup_volume;
is->muted = 0;// 是否是静音
}
实现播放器音量控制,播放器音量100%则用memcpy(),否则用SDL_MixAudioFormat()函数:
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
//1. 如果 is->muted == 1, stream内容为空
if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
{
// 播放器音量为100% 直接 memcpy
memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
}
else
{
memset(stream, 0, len1);
if (!is->muted && is->audio_buf)
{
// 播放器音量小于 100% ,用 SDL_MixAudioFormat()函数
SDL_MixAudioFormat(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, AUDIO_S16SYS, len1, is->audio_volume);
}
}
}
2. 设置音频格式
//设置音频格式
static int audio_open(void *opaque, AVChannelLayout *wanted_channel_layout, int wanted_sample_rate, struct AudioParams *audio_hw_params)
{
//wanted_spec 想要的音频格式, spec实际打开的音频格式
SDL_AudioSpec wanted_spec, spec;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = wanted_nb_channels;
wanted_spec.freq = wanted_sample_rate;
wanted_spec.silence = 0;
wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));
wanted_spec.callback = sdl_audio_callback; // 回调函数
wanted_spec.userdata = opaque;//回调函数参数;
}
注意1:ffpaly固定了只用AUDIO_S16SYS 格式。
注意2:samples 设置比较难以理解。比如采样率为48000音频设备每秒播放 48000 个样本,如果回调函数一次只能取4800个样本,那么一秒内就需要调 10 次回调函数。因为音频采样率是由音频文件决定的,是动态的,所以ffpaly固定了回调函数次数,也就是一秒内回调30次,计算出每次取样本数量。 2 << av_log2() (等价于pow(2,n))目的样本数据值最终为2ⁿ。
3. 打开音频设备:
static int audio_open(void *opaque, AVChannelLayout *wanted_channel_layout, int wanted_sample_rate, struct AudioParams *audio_hw_params)
{
static const int next_nb_channels[] = { 0, 0, 1, 6, 2, 6, 4, 6 }; // 通道数量组数
static const int next_sample_rates[] = { 0, 44100, 48000, 96000, 192000 }; //采样率组数
int next_sample_rate_idx = FF_ARRAY_ELEMS(next_sample_rates) - 1;
while (next_sample_rate_idx && next_sample_rates[next_sample_rate_idx] >= wanted_spec.freq)
{
/// 找到音频采样率在next_sample_rates组数的下标,为打开音频设备失败时降低采样率
next_sample_rate_idx--;
}
//尝试打开音频设备,失败时先调整通道数量,再去尝试,直到通道数量为0,还是失败时,再降低采样率
while (!(audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)))
{
wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)];
if (!wanted_spec.channels)
{
wanted_spec.freq = next_sample_rates[next_sample_rate_idx--];
wanted_spec.channels = wanted_nb_channels;
if (!wanted_spec.freq)
{
return -1;
}
}
}
return spec.size;
}
注意:next_nb_channels通道数量组数,当打开音频设备失败,调整通道数量。
next_nb_channels[7] = 6,从7声道切换到6声道打开音频设备
next_nb_channels[6] = 4,从6声道切换到4声道打开音频设备
next_nb_channels[5] = 6,从5声道切换到6声道打开音频设备
next_nb_channels[4] = 2,从4声道切换到2声道打开音频设备
next_nb_channels[3] = 6,从3声道切换到6声道打开音频设备
next_nb_channels[2] = 1,从双声道切换到单声道打开音频设备
next_nb_channels[1] = 0,无法再切换,需要降低采样率打开音频设备
next_nb_channels[0] = 0,无法再切换,需要降低采样率打开音频设备
next_sample_rates[0] = 0,降低采样率到0了,不用打开音频设备了。
打开音频设备成功时spec附带音频设备更多信息。
4.填充音频数据 ,让 SDL 来主动执行回调函数来取数据
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
/// SDL_PauseAudioDevice(devid,0);
//播放音频,SDL回调函数生效
//填充数据
.......
}
到这里就结束啦,感谢大家的支持,如要问题欢迎提问指正。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)