音视频 Day 08 PCM 播放

1、录音相关的补充点

1. av_read_frame() 在读取录音设备的录音时,会经常返回错误码 -35,这个错误码是什么意思?

#define EAGAIN 35 // Resource temporarily unavailable
复制代码
  • 这个错误码表明资源暂时不可读
  • 能够经过下面的代码来屏蔽
else if (reslutCode == AVERROR(EAGAIN)){
qDebug() << "录音设备还未准备好" << reslutCode;
continue;
}
复制代码

2. avformat_open_input() 在 mac 上调用的时候,没有权限致使崩溃怎么办?

在项目底下添加 Info.plist 文件,文件内容以下:安全

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>NSMicrophoneUsageDescription</key>
 <string>申请用户麦克风</string>
 <key>NSCameraUsageDescription</key>
 <string>申请用户摄像头</string>
</dict>
</plist>
复制代码

而后在 .pro 中引入 Info.plist 文件markdown

QMAKE_INFO_PLIST = Info.plist
复制代码
  • 最后以 debug 模式运行便可

3. 一个小技巧:如何直观的获取 mac 上录音设备的采样率和声道?(了解便可)

  • 点击: mac 左上角苹果图标关于本机系统报告音频

4. 如何以日期来做为录音输出文件的名字?(了解便可)

// 文件名
QString filename = FILEPATH;
filename += QDateTime::currentDateTime().toString("MM_dd____HH:mm:ss");
filename += ".pcm";
QFile file(filename);
复制代码

5. 若是子线程异常退出,此刻 UI 也要从录音状态结束,如何监听子线程异常结束了呢?

if (!_audioThread) { // 点击了“开始录音”
 _audioThread = new AudioThread (this);
 _audioThread->start();
 connect(_audioThread, &AudioThread::finished, [this]() {
     _audioThread = nullptr;
     ui->startRecordAudioButton->setText("开始录音");
 });
 ui->startRecordAudioButton->setText("结束录音");
}
复制代码

2、PCM

1. 使用 ffplay 播放 PCM 的命名格式如何?-ar、-ac、-f 分别是什么?如何查看 PCM 的采样格式写法?

  • 格式以下:
ffplay -ar 44100 -ac 2 -f s16le in.pcm
复制代码
  • -ar :采样率
  • -ac:声道数
  • -f:采样格式,s16le: signed 16-bit little-endian
  • 查看采样格式写法 :ffmpeg -formats | grep PCM

2. 什么是 SDL?

  • SDL(Simple DirectMedia Layer),是一个 跨平台 的 C 语言多媒体开发库。
  • 提供对音频、键盘、鼠标、游戏操做杆、图形硬件的底层访问
  • 目前不少视频播放软件、模拟器、受欢迎的游戏都在使用它

3. 引入 SDL2 的头文件和库文件

INCLUDEPATH += /usr/local/Cellar/sdl2/2.0.14_1/include

LIBS += -L /usr/local/Cellar/sdl2/2.0.14_1/lib -lSDL2
复制代码

4. 初始化 SDL

if (SDL_Init(SDL_INIT_AUDIO) != 0) {
qDebug() << "初始化 SDL 报错" << SDL_GetError();
return 1;
}
qDebug() << "初始化 SDL 成功";
SDL_Quit();
复制代码

5. SDL 播放音频有两种主流模式,是哪两种?

  • push(推):【程序】主动推送数据给【音频设备】
  • pull(拉):【音频设备】主动向【程序】拉取数据

6. 使用 SDL 完成播放 in.pcm 的核心步骤(思想)

  • 子线程中作的主要操做

SDL_Init 初始化 SDL_INIT_AUDIO 子系统app

SDL_OpenAudio 打开音频子系统函数

③ 建立 SDL_AudioSpec 对象,配置音频参数设备拉流的回调函数ui

file.open 打开文件this

SDL_PauseAudio(0) 开始播放音频spa

⑥ 读取 PCM 数据,提供给 音频播放设备播放线程

⑦ 所有播放完毕,关闭文件、关闭设备、清除音频子系统debug

  • 音频播放线程(会不断调用pull_audio_data进行拉流)的主要操做
void pull_audio_data(void *userdata,
                  // 须要往 stream 中填充 PCM 数据
                  Uint8 * stream,
                  // 但愿填充的大小(samples * format * channels / 8)
                  int len)
复制代码

① 清空 stream(静音处理)SDL_memset(stream, 0, len);指针

② 调用 SDL_MixAudio(stream, buffer->data, buffer->pullLen, SDL_MIX_MAXVOLUME); 为音频子系统提供数据

③ 调整 buffer 中的 data 和 len

7. 使用 SDL 完成播放 in.pcm 的核心代码

#include "playthread.h"

#include <SDL2/SDL.h>
#include <QDebug>
#include <QFile>

#define FILENAME "F:/in.pcm"
// 采样率
#define SAMPLE_RATE 44100
// 采样格式
#define SAMPLE_FORMAT AUDIO_S16LSB
// 采样大小
#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 << "析构了";
}

// 等待音频设备回调(会回调屡次)
void pull_audio_data(void *userdata, // 须要往stream中填充PCM数据 Uint8 *stream, // 但愿填充的大小(samples * format * channels / 8) 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;
}

void PlayThread::run() {
 // 初始化Audio子系统
 if (SDL_Init(SDL_INIT_AUDIO)) {
     qDebug() << "SDL_Init error" << SDL_GetError();
     return;
 }

 // 音频参数
 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;
 }

 // 打开文件
 QFile file(FILENAME);
 if (!file.open(QFile::ReadOnly)) {
     qDebug() << "file open error" << FILENAME;
     // 关闭设备
     SDL_CloseAudio();
     // 清除全部的子系统
     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();
}
复制代码
相关文章
相关标签/搜索