1. 程式人生 > >【秒懂音視訊開發】09_音訊錄製02_程式設計

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

## 通過程式設計錄音 開發錄音功能的主要步驟是: - 註冊裝置 - 獲取輸入格式物件 - 開啟裝置 - 採集資料 - 釋放資源 ![主要步驟](https://img2020.cnblogs.com/blog/497279/202103/497279-20210319195750551-1109534799.png) 需要用到的FFmpeg庫有3個。 ```cpp extern "C" { // 裝置相關API #include // 格式相關API #include // 工具相關API(比如錯誤處理) #include } ``` ### 註冊裝置 在整個程式的執行過程中,只需要執行1次註冊裝置的程式碼。 ```cpp // 初始化libavdevice並註冊所有輸入和輸出裝置 avdevice_register_all(); ``` ### 獲取輸入格式物件 #### 巨集定義 Windows和Mac環境的格式名稱、裝置名稱都是不同的,所以使用條件編譯實現跨平臺。 ```cpp // 格式名稱、裝置名稱目前暫時使用巨集定義固定死 #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 ``` #### 核心程式碼 根據格式名稱獲取輸入格式物件,後面需要利用輸入格式物件開啟裝置。 ```cpp AVInputFormat *fmt = av_find_input_format(FMT_NAME); if (!fmt) { // 如果找不到輸入格式 qDebug() << "找不到輸入格式" << FMT_NAME; return; } ``` ### 開啟裝置 ```cpp // 格式上下文(後面通過格式上下文操作裝置) 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; } ``` ### 採集資料 #### 巨集定義 ```cpp #ifdef Q_OS_WIN // PCM檔案的檔名 #define FILENAME "F:/out.pcm" #else #define FILENAME "/Users/mj/Desktop/out.pcm" #endif ``` #### 核心程式碼 ```cpp #include // 檔案 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); } ``` ### 釋放資源 ```cpp // 關閉檔案 file.close(); // 關閉裝置 avformat_close_input(&ctx); ``` 想要了解每一個函式的具體作用,可以查詢:[官方API文件](https://ffmpeg.org/doxygen/trunk/index.html)。 ## 多執行緒 錄音屬於耗時操作,為了避免阻塞主執行緒,最好在子執行緒中進行錄音操作。這裡建立了一個繼承自QThread的執行緒類,執行緒一旦啟動(start),就會自動呼叫*run*函式。 ### .h ```cpp #include class AudioThread : public QThread { Q_OBJECT private: void run(); public: explicit AudioThread(QObject *parent = nullptr); ~AudioThread(); }; ``` ### .cpp ```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() { // 錄音操作 // ... } ``` ### 開啟執行緒 ```cpp AudioThread *audioThread = new AudioThread(this); audioThread->start(); ``` ### 結束執行緒 ```cpp // 外部呼叫執行緒的requestInterruption,請求結束執行緒 audioThread->requestInterruption(); // 執行緒內部的邏輯 void AudioThread::run() { // 可以通過isInterruptionRequested判斷是否要結束執行緒 // 當呼叫過執行緒的requestInterruption時,isInterruptionRequested返回值就為true,否則為false while (!isInterruptionRequested()) { // ...