本文共 3846 字,大约阅读时间需要 12 分钟。
可以使用ffplay播放《音频录制02_编程》中录制好的PCM文件,测试一下是否录制成功。
播放PCM需要指定相关参数:
ffplay -ar 44100 -ac 2 -f s16le out.pcm
接下来演示一下,如何通过编程的方式播放PCM数据。
ffplay是基于FFmpeg、两个库实现的。通过编程的方式播放音视频,也是需要用到这2个库。FFmpeg大家都已经清楚了,比较陌生的是SDL。
SDL(Simple DirectMedia Layer),是一个跨平台的C语言多媒体开发库。
SDL官网下载地址:。
由于我们使用的是MinGW编译器,所以选择下载。
解压后的目录结构如下图所示,跟FFmpeg的目录结构类似,因此就不再赘述每个文件夹的作用。
从可以看得出来:之前执行brew install ffmpeg时,已经顺带安装了SDL,安装目录是:/usr/local/Cellar/sdl2。
如果没有这个目录,就执行brew install sdl2进行安装即可。
来个简单的SDL HelloWorld吧,打印一下SDL的版本号。
win32 { FFMPEG_HOME = F:/Dev/ffmpeg-4.3.2 SDL_HOME = F:/Dev/SDL2-2.0.14/x86_64-w64-mingw32}macx { FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.3.2 SDL_HOME = /usr/local/Cellar/sdl2/2.0.14_1}INCLUDEPATH += $${FFMPEG_HOME}/includeLIBS += -L$${FFMPEG_HOME}/lib \ -lavdevice \ -lavcodec \ -lavformat \ -lavutilINCLUDEPATH += $${SDL_HOME}/includeLIBS += -L$${SDL_HOME}/lib \ -lSDL2
在Windows环境中,还需要处理一下dll文件,参考:。
#includeSDL_version v;SDL_VERSION(&v);// 2 0 14qDebug() << v.major << v.minor << v.patch;
SDL分成好多个子系统(subsystem):
目前只用到了音频功能,所以只需要通过函数初始化Audio子系统即可。
// 初始化Audio子系统if (SDL_Init(SDL_INIT_AUDIO)) { // 返回值不是0,就代表失败 qDebug() << "SDL_Init Error" << SDL_GetError(); return;}
/* 一些宏定义 */// 采样率#define SAMPLE_RATE 44100// 采样格式#define SAMPLE_FORMAT AUDIO_S16LSB// 采样大小#define SAMPLE_SIZE SDL_AUDIO_BITSIZE(SAMPLE_FORMAT)// 声道数#define CHANNELS 2// 音频缓冲区的样本数量#define SAMPLES 1024// 用于存储读取的音频数据和长度typedef struct { int len = 0; int pullLen = 0; Uint8 *data = nullptr;} AudioBuffer;// 音频参数SDL_AudioSpec spec;// 采样率spec.freq = SAMPLE_RATE;// 采样格式(s16le)spec.format = SAMPLE_FORMAT;// 声道数spec.channels = CHANNELS;// 音频缓冲区的样本数量(这个值必须是2的幂)spec.samples = SAMPLES;// 回调spec.callback = pull_audio_data;// 传递给回调的参数AudioBuffer buffer;spec.userdata = &buffer;// 打开音频设备if (SDL_OpenAudio(&spec, nullptr)) { qDebug() << "SDL_OpenAudio Error" << SDL_GetError(); // 清除所有初始化的子系统 SDL_Quit(); return;}
#define FILENAME "F:/in.pcm"// 打开文件QFile file(FILENAME);if (!file.open(QFile::ReadOnly)) { qDebug() << "文件打开失败" << FILENAME; // 关闭音频设备 SDL_CloseAudio(); // 清除所有初始化的子系统 SDL_Quit(); return;}
// 每个样本占用多少个字节#define BYTES_PER_SAMPLE ((SAMPLE_SIZE * CHANNELS) / 8)// 文件缓冲区的大小#define BUFFER_SIZE (SAMPLES * BYTES_PER_SAMPLE)// 开始播放SDL_PauseAudio(0);// 存放文件数据Uint8 data[BUFFER_LEN];while (!isInterruptionRequested()) { // 只要从文件中读取的音频数据,还没有填充完毕,就跳过 if (buffer.len > 0) continue; buffer.len = file.read((char *) data, BUFFER_SIZE); // 文件数据已经读取完毕 if (buffer.len <= 0) { // 剩余的样本数量 int samples = buffer.pullLen / BYTES_PER_SAMPLE; int ms = samples * 1000 / SAMPLE_RATE; SDL_Delay(ms); break; } // 读取到了文件数据 buffer.data = data;}
// userdata:SDL_AudioSpec.userdata// stream:音频缓冲区(需要将音频数据填充到这个缓冲区)// len:音频缓冲区的大小(SDL_AudioSpec.samples * 每个样本的大小)void pull_audio_data(void *userdata, Uint8 *stream, int len) { // 清空stream SDL_memset(stream, 0, len); // 取出缓冲信息 AudioBuffer *buffer = (AudioBuffer *) userdata; if (buffer->len == 0) return; // 取len、bufferLen的最小值(为了保证数据安全,防止指针越界) buffer->pullLen = (len > buffer->len) ? buffer->len : len; // 填充数据 SDL_MixAudio(stream, buffer->data, buffer->pullLen, SDL_MIX_MAXVOLUME); buffer->data += buffer->pullLen; buffer->len -= buffer->pullLen;}
// 关闭文件file.close();// 关闭音频设备SDL_CloseAudio();// 清理所有初始化的子系统SDL_Quit();
转载地址:http://vxhyz.baihongyu.com/