音視頻 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();
}
複製代碼
相關文章
相關標籤/搜索