【秒懂音視訊開發】09_音訊錄製02_程式設計

M了個J發表於2021-03-26

通過程式設計錄音

開發錄音功能的主要步驟是:

  • 註冊裝置
  • 獲取輸入格式物件
  • 開啟裝置
  • 採集資料
  • 釋放資源

主要步驟

需要用到的FFmpeg庫有3個。

extern "C" {
// 裝置相關API
#include <libavdevice/avdevice.h>
// 格式相關API
#include <libavformat/avformat.h>
// 工具相關API(比如錯誤處理)
#include <libavutil/avutil.h>
}

註冊裝置

在整個程式的執行過程中,只需要執行1次註冊裝置的程式碼。

// 初始化libavdevice並註冊所有輸入和輸出裝置
avdevice_register_all();

獲取輸入格式物件

巨集定義

Windows和Mac環境的格式名稱、裝置名稱都是不同的,所以使用條件編譯實現跨平臺。

// 格式名稱、裝置名稱目前暫時使用巨集定義固定死
#ifdef Q_OS_WIN
    // 格式名稱
    #define FMT_NAME "dshow"
    // 裝置名稱
    #define DEVICE_NAME "audio=麥克風陣列 (Realtek(R) Audio)"
#else
    #define FMT_NAME "avfoundation"
    #define DEVICE_NAME ":0"
#endif

核心程式碼

根據格式名稱獲取輸入格式物件,後面需要利用輸入格式物件開啟裝置。

AVInputFormat *fmt = av_find_input_format(FMT_NAME);
if (!fmt) {
    // 如果找不到輸入格式
    qDebug() << "找不到輸入格式" << FMT_NAME;
    return;
}

開啟裝置

// 格式上下文(後面通過格式上下文操作裝置)
AVFormatContext *ctx = nullptr;
// 開啟裝置
int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr);
// 如果開啟裝置失敗
if (ret < 0) {
    char errbuf[1024] = {0};
    // 根據函式返回的錯誤碼獲取錯誤資訊
    av_strerror(code, errbuf, sizeof (errbuf));
    qDebug() << "開啟裝置失敗" << errbuf;
    return;
}

採集資料

巨集定義

#ifdef Q_OS_WIN
    // PCM檔案的檔名
    #define FILENAME "F:/out.pcm"
#else
    #define FILENAME "/Users/mj/Desktop/out.pcm"
#endif

核心程式碼

#include <QFile>

// 檔案
QFile file(FILENAME);

// WriteOnly:只寫模式。如果檔案不存在,就建立檔案;如果檔案存在,就刪除檔案內容
if (!file.open(QFile::WriteOnly)) {
    qDebug() << "檔案開啟失敗" << FILENAME;
    // 關閉裝置
    avformat_close_input(&ctx);
    return;
}

// 暫時假定只採集50個資料包
int count = 50;

// 資料包
AVPacket pkt;
// 從裝置中採集資料
// av_read_frame返回值為0,代表採集資料成功
while (count-- > 0 && av_read_frame(ctx, &pkt) == 0) {
    // 寫入資料
    file.write((const char *) pkt.data, pkt.size);
}

釋放資源

// 關閉檔案
file.close();

// 關閉裝置
avformat_close_input(&ctx);

想要了解每一個函式的具體作用,可以查詢:官方API文件

多執行緒

錄音屬於耗時操作,為了避免阻塞主執行緒,最好在子執行緒中進行錄音操作。這裡建立了一個繼承自QThread的執行緒類,執行緒一旦啟動(start),就會自動呼叫run函式。

.h

#include <QThread>
 
class AudioThread : public QThread {
    Q_OBJECT
private:
    void run();
 
public:
    explicit AudioThread(QObject *parent = nullptr);
    ~AudioThread();
};

.cpp

AudioThread::AudioThread(QObject *parent,
                         AVInputFormat *fmt,
                         const char *deviceName)
    : QThread(parent), _fmt(fmt), _deviceName(deviceName) {
    // 線上程結束時自動回收執行緒的記憶體
    connect(this, &AudioThread::finished,
            this, &AudioThread::deleteLater);
}

AudioThread::~AudioThread() {
    // 執行緒物件的記憶體回收時,正常結束執行緒
    requestInterruption();
    quit();
    wait();
}

void AudioThread::run() {
    // 錄音操作
    // ...
}

開啟執行緒

AudioThread *audioThread = new AudioThread(this);
audioThread->start();

結束執行緒

// 外部呼叫執行緒的requestInterruption,請求結束執行緒
audioThread->requestInterruption();

// 執行緒內部的邏輯
void AudioThread::run() {
    // 可以通過isInterruptionRequested判斷是否要結束執行緒
    // 當呼叫過執行緒的requestInterruption時,isInterruptionRequested返回值就為true,否則為false
    while (!isInterruptionRequested()) {
    	// ...
    }
}

相關文章