------------------------------------全系列文章目录------------------------------------
大致流程
- 获取输入文件的采样率和采样数,来作为音频输出设备的采样率和采样数。
- 根据音频输出设备的实际情况,设置参数,并使用该参数打开音频设备。
- 开始音频的播放,并在音频回调函数中填充音频设备需要播放的音频数据。
音频文件主要有以下几个参数
- 声道数:声道(Sound Channel) 是指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,所以声道数也就是声音录制时的音源数量或回放时相应的扬声器数量。
- 声道布局:根据声道数,摆放录制设备或扬声器设备。
- 采样数:一帧音频帧的样本数量。
- 采样率:每秒音频的样本数量。
- 采样位数/采样格式:每个采样样本的量化位数。
SDL_AudioSpec结构体
-
同样道理,要想音频设备驱动程序能正确使用音频设备播放一个音频文件,就要设定好以上几个参数(需要音频设备自身支持的参数)。SDL中采用SDL_AudioSpec结构体描述一个音频设备,该结构体如下:
typedef struct SDL_AudioSpec { int freq; //采样率 SDL_AudioFormat format; //采样样本格式 Uint8 channels; //通道数 Uint8 silence; //音频数据静音时的数据,无需设置(自动计算出来,根据采样格式) Uint16 samples; //采样数,必须为2的幂次方 Uint16 padding; //用于结构体对齐操作 Uint32 size; //音频缓冲区字节大小,无需设置(自动计算出来,根据采样率,采样格式,声道数) SDL_AudioCallback callback; //音频设备回调函数 void *userdata; //回调函数参数 } SDL_AudioSpec;
- 回调函数SDL_AudioCallback定义为void (* SDL_AudioCallback) (void *userdata, Uint8 * stream, int len); 当音频播放设备需要数据进行播放时,就会调用该函数,用户通过该函数将数据通过 stream 传送给音频播放设备。
- userdata:回调函数的用户参数
- stream:用户传递数据给音频设备的地方,使用前将其初始化清零处理
- len:音频播放设备需要数据的长度
- 回调函数SDL_AudioCallback定义为void (* SDL_AudioCallback) (void *userdata, Uint8 * stream, int len); 当音频播放设备需要数据进行播放时,就会调用该函数,用户通过该函数将数据通过 stream 传送给音频播放设备。
打开音频设备
int SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained);
SDL_AudioDeviceID SDL_OpenAudioDevice(const char *device, int iscapture, const SDL_AudioSpec *esired,
SDL_AudioSpec *obtained, int allowed_changes);
- device:设备名字,为NULL时表示请求最合理的设备
- iscapture:非0表示打开设备录制,0表示打开设备播放
- desired:想要的音频参数
- obtained:实际的音频参数
- allowed_changes:是否允许参数变化,0或者其他标志如SDL_AUDIO_ALLOW_ANY_CHANGE
- 两者区别:
- 第一个函数只打开设备ID为1的音频设备,返回值为成功或失败
- 第二个函数可以打开任意音频设备,返回值为SDL_AudioDeviceID设备ID,>=2才为成功
补充几个SDL函数
-
返回当前音频设备的数量,iscapture为0返回播放设备,非0返回录制设备。
int SDL_GetNumAudioDevices(int iscapture);
-
返回请求索引index的音频设备名字;iscapture为0查询播放设备,非0查询录制设备。
const char* SDL_GetAudioDeviceName(int index, int iscapture);
-
SDL_MixAudioFormat用于以指定格式混合音频数据。
void SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, int volume);
- dst:混合音频的目的地址
- src:被混合的音频源地址
- SDL_AudioFormat:混合音频所需的音频格式
- len:音频源缓冲区长度
- volume:音量,分为0—128(SDL_MIX_MAXVOLUME)
举例
-
定义如下结构体
/*音频参数*/ class audio_param { public: audio_param() {}; ~audio_param() {}; void set_channel_layout(uint64_t layout) { channel_layout = layout; } void set_channel_nb(int nb) { channel_nb = nb; } void set_nb_sample(int num) { nb_sample = num; } void set_sample_fmt(enum AVSampleFormat sf) { sample_fmt = sf; } void set_sample_rate(int rate) { sample_rate = rate; } void set_sample_fmt_Byte(uint8_t Byte) { sample_fmt_Byte = Byte; } int get_channel_nb() const { return channel_nb; } uint64_t get_channel_layout() const { return channel_layout; } enum AVSampleFormat get_sample_fmt() const { return sample_fmt; } int get_sample_rate() const { return sample_rate; } int get_nb_sample() const { return nb_sample; } uint8_t get_sample_fmt_Byte() const { return sample_fmt_Byte; } int output_audio_param_config(AVCodecContext *ctx); /*设置音频输出设备的参数*/ private: uint64_t channel_layout; //输出声道布局 int channel_nb; //输出声道数 int nb_sample; //一帧采样数 enum AVSampleFormat sample_fmt; //采样样本格式 int sample_rate; //采样率 uint8_t sample_fmt_Byte; //样本采样格式大小(字节) }; /*音频数据处理*/ class AudioDeal : public audio_param { public: ...... private: ....... }; /*音频控制*/ class AudioCtrl { public: class AudioDeal deal; int open_audio(void *userdata, SDL_AudioCallback callback); /*打开音频设备*/ SDL_AudioDeviceID get_audio_dev_id() { return audio_dev_id; } /* 获取音频设备ID */ ...... private: SDL_AudioSpec want_audio_device_spec; /*想要的音频设备参数*/ SDL_AudioSpec actual_audio_device_spec; /*实际的音频设备参数*/ SDL_AudioDeviceID audio_dev_id; /*音频设备ID*/ const char *audio_dev_name; /*音频设备名字*/ int get_power2_samples(int _sample); /*获取幂次方的samples参数*/ }; class MediaCtrl { public: class AudioCtrl ac; ...... private: ...... }
-
打开音频设备流程
int audio_param::output_audio_param_config(AVCodecContext *ctx) { set_channel_layout(AV_CH_LAYOUT_STEREO); //设置输出的声道布局 set_channel_nb(av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO)); //设置输出的声道数 set_sample_rate(ctx->sample_rate); //设置输出的采样率 set_sample_fmt(AV_SAMPLE_FMT_S16); //设置输出的采样格式 set_nb_sample(ctx->frame_size); //设置输出的采样数 set_sample_fmt_Byte(2); //设置输出的采样格式的字节数 return 0; } int AudioCtrl::open_audio(void *userdata, SDL_AudioCallback callback) { /*设置音频设备参数*/ SDL_zero(want_audio_device_spec); SDL_zero(want_audio_device_spec); want_audio_device_spec.channels = deal.get_channel_nb(); //通道数 want_audio_device_spec.freq = deal.get_sample_rate(); //采样率 want_audio_device_spec.format = AUDIO_S16SYS; //音频样本数据格式 want_audio_device_spec.samples = deal.get_nb_sample(); //一帧采样数 want_audio_device_spec.userdata = userdata; //音频设备回调函数参数 want_audio_device_spec.callback = callback; //音频设备回调函数 /*根据设定的音频参数,打开音频设备*/ audio_dev_id = SDL_OpenAudioDevice(NULL, 0, &want_audio_device_spec, &actual_audio_device_spec, SDL_AUDIO_ALLOW_ANY_CHANGE); if (audio_dev_id < 2) { printf("can not open the audio device - %s !\n", SDL_GetError()); return -1; } audio_dev_name = SDL_GetAudioDeviceName(audio_dev_id, 0); //音频设备名字 return 0; }
-
开始播放音频及回调函数
void fill_audio_device_callback(void *userdata, Uint8 * stream, int len) { uint8_t *p_src; ...... SDL_memset(stream, 0, len); /*清空回调数据区*/ ...... DL_MixAudioFormat(stream, p_src, AUDIO_S16SYS, len, SDL_MIX_MAXVOLUME); } int main(int argc, char* argv[]) { ...... class MediaCtrl *media = new class MediaCtrl(argv[1]); media->ac.open_audio(media, fill_audio_device_callback); SDL_PauseAudioDevice(media->ac.get_audio_dev_id(), 0); ...... }