音視訊之程式碼錄製音訊(六)
阿新 • • 發佈:2021-10-30
通過程式碼錄音
許可權申請
在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檔名 #defineFILEPATH "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); }