音视频之播放PCM(七)
使用命令行播放-ffplay
可以使用ffplay播放我们在上面博客中录制好的PCm文件,测试一下是否录制成功。
播放PCM需要指定相关参数:
- ar: 采样率
- ac: 声道数
- f: 采样格式
- s16le: PCM signed 16-bit little-endian
- 更多PCM的采样格式可以使用命令查看
- Windows: ffmpeg -formats | findstr OCM
- Mac: ffmpeg -formats | grep PCM
播放命令如下:
ffplay -ar 44100 -ac 2 -f s16le /Users/muzi/Desktop/out.pcm
借助SDL使用代码播放
简介
SDL全称 Simple DirectMedia Layer,是一个跨平台的C语言多媒体开发库。
- 支持Windows、Mac OSX、Linux、iOS、Android
- 提供对音频、键盘、鼠标、游戏操纵杆、图形硬件的底层访问
- 很多的视频播放软件、模拟器、受欢迎的游戏都在使用它
- 目前最新的稳定版本是: 2.0.16
- API文档: wiki
安装环境
从brew官网可以看的出来:之前执行 brew install ffmpeg时,已经顺带安装了SDL,安装目录是: /usr/local/Cellar/sdl2。如果没有这个目录,就执行brew install sdl2进行安装即可。
配置
.pro文件
win32 { SDL_HOME = /muzi } mac { SDL_HOME = /usr/local/Cellar/sdl2/2.0.16 } INCLUDEPATH += $${SDL_HOME}/include LIBS += -L $${SDL_HOME}/lib \ -lSDL2
播放PCM
初始化子系统
SDL分成好多个子系统:
- Video: 显示和窗口管理
- Audio: 音频设备管理
- Joystick: 游戏摇杆控制
- Timers: 定时器
- ...
目前只用到了音频功能,所以只需要通过SDL_init函数初始化Audio子系统即可。
打开音频设备
// 音频参数 SDL_AudioSpec spec; // 采样率 spec.freq = SAMPLE_RATE; // 采样格式 (s16le) spec.format = AUDIO_S16LSB; // 声道数 spec.channels = CHANNELS; // 音频缓冲区的样本数量(这个值必须是2的幂) spec.samples = 1024; // 回调 spec.callback = pull_audio_data; AudioBuffer buffer; spec.userdata = &buffer; // 打开设备 if (SDL_OpenAudio(&spec, nullptr)) { qDebug() << "SDL_OpenAudio error" << SDL_GetError(); // 清除所有的子系统 SDL_Quit(); return; }
打开文件
// 打开文件 QFile file(FILENAME); if (!file.open(QFile::ReadOnly)) { qDebug() << "file open error" << FILENAME; // 关闭设备 SDL_CloseAudio(); // 清除所有的子系统 SDL_Quit(); return; }
开始播放
// 开始播放 (0是取消暂停) SDL_PauseAudio(0); qDebug() << BUFFER_SIZE << "BUFFER_SIZE"; // 存放从文件中读取的数据 Uint8 data[BUFFER_SIZE]; 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; }
回调函数
// 等待音频设备回调(会回调多次) void pull_audio_data(void *userdata, Uint8 *stream, // 需要往stream中填充PCM数据 int len) { // 希望填充的大小(samples * format * channels / 8) qDebug() << "pull_audio_data" << len; // 清空stream (静音处理) SDL_memset(stream, 0 , len); // 取出AudioBuffer 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();