1. 程式人生 > 其它 >音視訊之程式碼錄製音訊(六)

音視訊之程式碼錄製音訊(六)

通過程式碼錄音

許可權申請

在Mac平臺,有兩個注意點: - 需要在Info.plist中新增麥克風的使用說明,申請麥克風的使用許可權 - 使用Debug模式執行程式

上面兩點非常重要,兩個都會導致閃退
檔案目錄配置如下:

pro檔案配置如下:

macx {
    FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.4_2
    QMAKE_INFO_PLIST = mac/Info.plist
}

相關庫

使用程式碼錄音,需要用到FFmpeg中庫有4個,如下:

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

錄音功能主要步驟

  • 註冊裝置
  • // 註冊裝置
    avdevice_register_all();
  • 獲取輸入格式物件
  • // 獲取輸入格式物件
    AVInputFormat *fmt = av_find_input_format(FMT_NAME);
  • 開啟裝置
  • // 開啟裝置
    int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr);
  • 採集資料
  • // 不斷採集資料
    ret = av_read_frame(ctx, pkt);
  • 釋放資源
  •  // 釋放資源
     av_packet_free(&pkt);
    
     // 關閉裝置
     avformat_close_input(&ctx);

核心程式碼

巨集定義

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

#ifdef Q_OS_WIN
    // 格式名稱
    #define FMT_NAME "dshow"
    // 裝置名稱
    #define DEVICE_NAME ""
    // PCM檔名
    #define
FILEPATH "F:/" #else #define FMT_NAME "avfoundation" #define DEVICE_NAME ":0" #define FILEPATH "/Users/muzi/Desktop/" #endif

配置pro檔案中FFmpeg執行環境

pro中檔案配置如下所示:

win32 {
    FFMPEG_HOME = F:/test
}

macx {
    FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.4_2
    QMAKE_INFO_PLIST = mac/Info.plist
}

INCLUDEPATH += $${FFMPEG_HOME}/include

LIBS += -L $${FFMPEG_HOME}/lib \
        -lavdevice \
        -lavformat \
        -lavcodec \
        -lavutil

多執行緒

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

.h中程式碼如下所示
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H

#include <QThread>

class AudioThread : public QThread {
    Q_OBJECT
private:
    void run();
    bool _stop = false;

public:
    explicit AudioThread(QObject *parent = nullptr);
    ~AudioThread();
    void setStop(bool stop);
signals:

};

#endif // AUDIOTHREAD_H
cpp中程式碼如下所示
AudioThread::AudioThread(QObject *parent) : QThread(parent) {
    // 當監聽到執行緒結束時(finished),就呼叫deleteLater回收記憶體
    connect(this, &AudioThread::finished,
            this, &AudioThread::deleteLater);
}

/// 在解構函式中斷開連線,安全退出執行緒
AudioThread::~AudioThread() {
   
}


// 當執行緒啟動的時候(start),就會自動呼叫run函式
// run函式中的程式碼是在子執行緒中執行的
// 耗時操作應該放在run函式中
void AudioThread::run() {
   
}

void AudioThread::setStop(bool stop) {
    _stop = stop;
}

具體程式碼實現

寫入資料、關閉裝置

// 當執行緒啟動的時候(start),就會自動呼叫run函式
// run函式中的程式碼是在子執行緒中執行的
// 耗時操作應該放在run函式中
void AudioThread::run() {
    qDebug() << this << "開始執行----------";

    // 獲取輸入格式物件
    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];
        av_strerror(ret, errbuf, sizeof (errbuf));
        qDebug() << "開啟裝置失敗" << errbuf;
        return;
    }

    // 列印一下錄音裝置的引數資訊
    showSpec(ctx);

    // 檔名
    QString filename = FILEPATH;

    filename += QDateTime::currentDateTime().toString("MM_dd_HH_mm_ss");
    filename += ".pcm";
    QFile file(filename);

    // 開啟檔案
    // WriteOnly:只寫模式。如果檔案不存在,就建立檔案;如果檔案存在,就會清空檔案內容
    if (!file.open(QFile::WriteOnly)) {
        qDebug() << "檔案開啟失敗" << filename;

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

    // 資料包
    AVPacket *pkt = av_packet_alloc();
    while (!isInterruptionRequested()) {
        // 不斷採集資料
        ret = av_read_frame(ctx, pkt);

        if (ret == 0) { // 讀取成功
            // 將資料寫入檔案

            file.write((const char *) pkt->data, pkt->size);
        } else if (ret == AVERROR(EAGAIN)) { // 資源臨時不可用
            continue;
        } else { // 其他錯誤
            char errbuf[1024];
            av_strerror(ret, errbuf, sizeof (errbuf));
            qDebug() << "av_read_frame error" << errbuf << ret;
            break;
        }

        // 必須要加,釋放pkt內部的資源
        av_packet_unref(pkt);
    }

    // 釋放資源
    // 關閉檔案
    file.close();

    // 釋放資源
    av_packet_free(&pkt);

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

    qDebug() << this << "正常結束----------";
}

獲取錄音裝置的相關引數

void showSpec(AVFormatContext *ctx) {
    // 獲取輸入流
    AVStream *stream = ctx->streams[0];
    // 獲取音訊引數
    AVCodecParameters *params = stream->codecpar;
    // 聲道數
    qDebug() << params->channels;
    // 取樣率
    qDebug() << params->sample_rate;
    // 取樣格式
    qDebug() << params->format;
    // 每一個樣本的一個聲道佔用多少個位元組
    qDebug() << av_get_bytes_per_sample((AVSampleFormat) params->format);
}