播放PCM文件

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

2. 代码操控

2.1 SDL介绍
SDL (Simple DirectMedia Layer),是一个跨平台的C语言多媒体开发库,支持Windows,Mac OS X、Linux、iOS、Android,提供对音频、鼠标、键盘、图形硬件的底层访问、很多的视频播放软件、模拟器等都使用这个库
  • API 文档
    官方链接
  • SDL官网下载地址
    SDL下载地址
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 

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 
#include 
#include 

#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

include

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文件

你可能感兴趣的:(播放PCM文件)