1.命令行操控
ffplay -ar 44100 -ac 2 -f f32le /Users/songlin/audio/qt_record/05_05_23_05_35.pcm
如果你的mac的参数样式和我的是一样的话,那么执行上面的命令,应该就能正常播放了之前录制的pcm,如果不是的话,需要你对照自己的mac参数来进行相关处理
- ar: 采样率
- ac: 声道数
- f: 采样格式
- s16le:PCM signed 16-bit little-endian
更多PCM的采样格式可以使用命令查看
Windows:ffmpeg -formats | findstr PCM
Mac:ffmpeg -formats | grep PCM
- s16le:PCM signed 16-bit little-endian
2. 代码操控
2.1 SDL介绍
SDL (Simple DirectMedia Layer),是一个跨平台的C语言多媒体开发库,支持Windows,Mac OS X、Linux、iOS、Android,提供对音频、鼠标、键盘、图形硬件的底层访问、很多的视频播放软件、模拟器等都使用这个库
2.2 Mac平台的安装
brew install ffmpeg
在之前执行ffmpeg的时候,已经安装了SDL,安装目录位于/usr/local/Cellar/sdl2
安装ffmpeg
如果没有安装这个目录,可以执行brew install sdl2
2.2 示例代码
- playthread.h
#ifndef PLAYTHREAD_H
#define PLAYTHREAD_H
#include <QThread>
class PlayThread : public QThread
{
Q_OBJECT
private:
void run();
public:
explicit PlayThread(QObject *parent = nullptr);
~PlayThread();
signals:
};
#endif // PLAYTHREAD_H
- playthread.cpp
#include "playthread.h"
#include <SDL2/SDL.h>
#include <QDebug>
#include <QFile>
#define FILENAME "/Users/songlin/audio/qt_record/05_05_23_05_35.pcm"
//采样率
#define SAMPLE_RATE 44100
//采样格式
#define SAMPLE_FORMAT AUDIO_F32LSB
//采样大小
#define SAMPLE_SIZE SDL_AUDIO_BITSIZE(SAMPLE_FORMAT)
//声道数
#define CHANNELS 2
// 音频缓冲区的样本数量
#define SAMPLES 1024
//每个样本占用多少个字节
#define BYTES_PER_SAMPLE ((SAMPLE_SIZE * CHANNELS) >> 3)
//文件缓冲区的大小
#define BUFFER_SIZE (SAMPLES * BYTES_PER_SAMPLE)
typedef struct {
int len = 0;
int pullLen = 0;
Uint8 *data = nullptr;
} AudioBuffer;
PlayThread::PlayThread(QObject *parent) : QThread(parent) {
connect(this, &PlayThread::finished,
this, &PlayThread::deleteLater);
}
PlayThread::~PlayThread() {
disconnect();
requestInterruption();
quit();
wait();
qDebug() << this << "析构了";
}
//等待音频设备回调(会回掉多次)
//stream:需要往stream中填充PCM数据
//len:希望填充的大小(samples * format * channels / 8)
void pull_audio_data(void *userdata,Uint8 *stream,int len){
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;
}
/*
* SDL 播放音频有2种模式:
* Push(推):【程序】主动推胸数据给音频设备
* Pull (拉);【音频设备】主动向【程序】拉去数据
*/
void PlayThread::run() {
//初始化Audio子系统
if (SDL_Init(SDL_INIT_AUDIO)){
qDebug() << "SDL_Init error" << SDL_GetError();
return;
}
//音频参数
SDL_AudioSpec spec;
//采样率
spec.freq = SAMPLE_RATE;
//采样格式(s161e)
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_OepnAudio error" << SDL_GetError();
//清除所有的子系统
SDL_Quit();
return;
}
//打开文件
QFile file(FILENAME);
if(!file.open(QFile::ReadOnly)){
qDebug() << "file open error" << FILENAME;
//清除所有的子系统
SDL_Quit();
return;
}
//开始播放(0是取消暂停)
SDL_PauseAudio(0);
//存放从文件中读取的数据
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;
}
// 关闭文件
file.close();
// 关闭设备
SDL_CloseAudio();
// 清除所有的子系统
SDL_Quit();
}
- mainwindow.cpp
···
include "mainwindow.h"
include "ui_mainwindow.h"
include <SDL2/SDL.h>
include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void showVersion() {
SDL_version version;
SDL_VERSION(&version);
qDebug() << version.major << version.minor << version.patch;
}
void MainWindow::on_playButton_clicked()
{
if (_playThread) { // 停止播放
_playThread->requestInterruption();
_playThread = nullptr;
ui->playButton->setText("开始播放");
} else { // 开始播放
_playThread = new PlayThread(this);
_playThread->start();
// 监听线程的结束
connect(_playThread, &PlayThread::finished,
this {
_playThread = nullptr;
ui->playButton->setText("开始播放");
});
ui->playButton->setText("停止播放");
}
}
···
然后Run程序,就可以发现成功播放了pcm文件
网友评论