1. 程式人生 > 其它 >Qt音視訊開發04-儲存音訊檔案(pcm/wav/aac)

Qt音視訊開發04-儲存音訊檔案(pcm/wav/aac)

一、前言

音訊的儲存相對來說比視訊的要簡單,具有通用性,不需要經過ffmpeg的編碼,ffmpeg解碼出來後一般會轉換成pcm原始的資料用來播放,所以對資料直接寫檔案即可,但是這種格式是無法用播放器直接播放的,因為取樣率、通道等引數未知,除非手動指定,所以就多出來一個wav格式,這個格式就是在pcm格式前面加上44位元組的檔案頭,所以wav格式的音訊檔案永遠比pcm的檔案大44位元組,簡單來說pcm加上wav標頭檔案就變成了wav。所以這些就好辦了,在開始儲存的時候先寫入這個檔案頭就行,後面一直插入pcm音訊資料即可。

由於wav檔案體積很大,所以需要一個壓縮的播放器可以直接播放的,這樣就需要用aac格式儲存。ffmpeg收到後還沒有解碼出來在沒有轉換前預設的資料一般以aac格式居多,也有部分的是mp3之類的,所以如果要儲存為aac格式,直接儲存最原始的packet的資料即可,這個資料是aac格式的壓縮過的音訊資料,每個資料包前面插入ADTS頭即可。可能你會有疑問每個包都插入了頭部位元組,那不是檔案體積更大,其實不是的,畢竟aac格式是一種聲音資料的檔案壓縮格式,有著極高的壓縮比,這點頭部位元組資料簡直是毛毛雨。

終上所述,三種格式的音訊資料可以滿足所有的情況,如果檢測到音訊解碼器用的aac則儲存aac格式,非aac則全部儲存成wav格式,如果不需要播放器支援就儲存成pcm格式即可。

二、效果圖


三、體驗地址

  1. 國內站點:https://gitee.com/feiyangqingyun
  2. 國際站點:https://github.com/feiyangqingyun
  3. 個人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 體驗地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 檔名:bin_video_demo/bin_linux_video。

四、相關程式碼

//儲存視訊檔案型別
enum SaveVideoType {
    SaveVideoType_None = 0,     //不儲存
    SaveVideoType_Yuv = 1,      //原始資料
    SaveVideoType_H264 = 2,     //解碼後的裸流
    SaveVideoType_Mp4 = 3       //標準視訊檔案
};

//儲存音訊檔案型別
enum SaveAudioType {
    SaveAudioType_None = 0,     //不儲存
    SaveAudioType_Pcm = 1,      //原始資料
    SaveAudioType_Wav = 2,      //wav檔案
    SaveAudioType_Aac = 3,      //aac檔案
};

#include "savehelper.h"
void SaveHelper::pcmToWav(const QString &pcmFile, const QString &wavFile, int sampleRate, int channelCount, bool deleteFile)
{
    //wav音訊檔案固定頭部位元組(資料有順序要求)
    struct WaveFileHeader {
        //RIFF頭
        char riffName[4];
        quint32 riffLen;

        //資料型別識別符號
        char wavName[4];

        //格式塊中的塊頭
        char fmtName[4];
        quint32 fmtLen;

        //音訊編碼格式
        quint16 audioFormat;
        //通道數量
        quint16 numChannels;
        //取樣率
        quint32 sampleRate;
        //波形資料傳輸速率
        quint32 bytesPerSecond;
        //資料塊對齊單位
        quint16 bytesPerSample;
        //每次取樣得到的樣本資料位數
        quint16 bitsPerSample;

        //資料塊中的塊頭
        char dataName[4];
        quint32 dataLen;
    };

    WaveFileHeader header;
    qstrcpy(header.riffName, "RIFF");
    qstrcpy(header.wavName, "WAVE");
    qstrcpy(header.fmtName, "fmt ");
    qstrcpy(header.dataName, "data");

    header.fmtLen = 16;
    header.audioFormat = 1;
    header.numChannels = channelCount;
    header.sampleRate = sampleRate;
    header.bytesPerSecond = channelCount * sampleRate;
    header.bytesPerSample = 2;
    header.bitsPerSample = 16;

    QFile filePcm(pcmFile);
    QFile fileWav(wavFile);
    if (!filePcm.open(QIODevice::ReadOnly) || !fileWav.open(QIODevice::WriteOnly)) {
        return;
    }

    //計算對應的長度大小
    int sizeHeader = sizeof(header);
    quint32 sizeData = filePcm.bytesAvailable();
    header.riffLen = (sizeData - 8 + sizeHeader);
    header.dataLen = sizeData;

    //先寫入頭部資訊
    fileWav.write((const char *)&header, sizeHeader);
    //再寫入音訊資料
    fileWav.write(filePcm.readAll());

    //關閉檔案
    filePcm.close();
    fileWav.close();

    //刪除檔案
    if (deleteFile) {
        QFile(pcmFile).remove();
        qDebug() << TIMEMS << QString("刪除檔案 -> 檔案: %1").arg(pcmFile);
    }
}

int SaveHelper::getSamplingFrequencyIndex(int sampleRate)
{
    int freqIdx = 3;
    if (sampleRate == 96000) {
        freqIdx = 0;
    } else if (sampleRate == 88200) {
        freqIdx = 1;
    } else if (sampleRate == 64000) {
        freqIdx = 2;
    } else if (sampleRate == 48000) {
        freqIdx = 3;
    } else if (sampleRate == 44100) {
        freqIdx = 4;
    } else if (sampleRate == 32000) {
        freqIdx = 5;
    } else if (sampleRate == 24000) {
        freqIdx = 6;
    } else if (sampleRate == 22050) {
        freqIdx = 7;
    } else if (sampleRate == 16000) {
        freqIdx = 8;
    } else if (sampleRate == 12000) {
        freqIdx = 9;
    } else if (sampleRate == 11025) {
        freqIdx = 10;
    } else if (sampleRate == 8000) {
        freqIdx = 11;
    }

    return freqIdx;
}

void SaveHelper::adtsHeader(char *header, int len, int sampleRate, int channelCount, int profile)
{
    //抽取音訊命令 ffmpeg -i d:/1.mp4 -vn -y -acodec copy d:/1.aac
    //音訊adts頭部資料 https://blog.csdn.net/u013113678/article/details/123134860
    int chanCfg = channelCount;
    int freqIdx = getSamplingFrequencyIndex(sampleRate);
    int adtsLen = len + 7;

    //絕大部分音訊都是1或者-99未設定(有部分是4表示高壓縮率)
    //網上的演算法缺少下面這個計算導致部分檔案儲存的音訊檔案不正常
    if (profile > 1) {
        freqIdx += (profile - 1);
    }
    profile = 1;

#if 1
    header[0] = (char)0xff;
    header[1] = (char)0xf1;
    header[2] = (char)(((profile) << 6) + (freqIdx << 2) + (chanCfg >> 2));
    header[6] = (char)0xfc;

    header[3] = (char)(((2 & 3) << 6) + (adtsLen >> 11));
    header[4] = (char)((adtsLen & 0x7f8) >> 3);
    header[5] = (char)(((adtsLen & 0x7) << 5) + 0x1f);
#else
    header[0] = 0xff;
    header[1] = 0xf0;
    header[1] |= (0 << 3);
    header[1] |= (0 << 1);
    header[1] |= 1;

    header[2] = (profile) << 6;
    header[2] |= (freqIdx & 0x0f) << 2;
    header[2] |= (0 << 1);
    header[2] |= (chanCfg & 0x04) >> 2;

    header[3] = (chanCfg & 0x03) << 6;
    header[3] |= (0 << 5);
    header[3] |= (0 << 4);
    header[3] |= (0 << 3);
    header[3] |= (0 << 2);
    header[3] |= ((adtsLen & 0x1800) >> 11);

    header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);
    header[5] = (uint8_t)((adtsLen & 0x7) << 5);
    header[5] |= 0x1f;
    header[6] = 0xfc;
#endif
}

五、功能特點

5.1 基礎功能

  1. 支援各種音訊視訊檔案格式,比如mp3、wav、mp4、asf、rm、rmvb、mkv等。
  2. 支援本地攝像頭裝置,可指定解析度、幀率。
  3. 支援各種視訊流格式,比如rtp、rtsp、rtmp、http等。
  4. 本地音視訊檔案和網路音視訊檔案,自動識別檔案長度、播放進度、音量大小、靜音狀態等。
  5. 檔案可以指定播放位置、調節音量大小、設定靜音狀態等。
  6. 支援倍速播放檔案,可選0.5倍、1.0倍、2.5倍、5.0倍等速度,相當於慢放和快放。
  7. 支援開始播放、停止播放、暫停播放、繼續播放。
  8. 支援抓拍截圖,可指定檔案路徑,可選抓拍完成是否自動顯示預覽。
  9. 支援錄影儲存,手動開始錄影、停止錄影,部分核心支援暫停錄影後繼續錄影,跳過不需要錄影的部分。
  10. 支援無感知切換迴圈播放、自動重連等機制。
  11. 提供播放成功、播放完成、收到解碼圖片、收到抓拍圖片、視訊尺寸變化、錄影狀態變化等訊號。
  12. 多執行緒處理,一個解碼一個執行緒,不卡主介面。

5.2 特色功能

  1. 同時支援多種解碼核心,包括qmedia核心(Qt4/Qt5/Qt6)、ffmpeg核心(ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5)、vlc核心(vlc2/vlc3)、mpv核心(mpv1/mp2)、海康sdk、easyplayer核心等。
  2. 非常完善的多重基類設計,新增一種解碼核心只需要實現極少的程式碼量,就可以應用整套機制。
  3. 同時支援多種畫面顯示策略,自動調整(原始解析度小於顯示控制元件尺寸則按照原始解析度大小顯示,否則等比例縮放)、等比例縮放(永遠等比例縮放)、拉伸填充(永遠拉伸填充)。所有核心和所有視訊顯示模式下都支援三種畫面顯示策略。
  4. 同時支援多種視訊顯示模式,控制代碼模式(傳入控制元件控制代碼交給對方繪製控制)、繪製模式(回撥拿到資料後轉成QImage用QPainter繪製)、GPU模式(回撥拿到資料後轉成yuv用QOpenglWidget繪製)。
  5. 支援多種硬體加速型別,ffmpeg可選dxva2、d3d11va等,mpv可選auto、dxva2、d3d11va,vlc可選any、dxva2、d3d11va。不同的系統環境有不同的型別選擇,比如linux系統有vaapi、vdpau,macos系統有videotoolbox。
  6. 解碼執行緒和顯示窗體分離,可指定任意解碼核心掛載到任意顯示窗體,動態切換。
  7. 支援共享解碼執行緒,預設開啟並且自動處理,當識別到相同的視訊地址,共享一個解碼執行緒,在網路視訊環境中可以大大節約網路流量以及對方裝置的推流壓力。國內頂尖視訊廠商均採用此策略。這樣只要拉一路視訊流就可以共享到幾十個幾百個通道展示。
  8. 自動識別視訊旋轉角度並繪製,比如手機上拍攝的視訊一般是旋轉了90度的,播放的時候要自動旋轉處理,不然預設是倒著的。
  9. 自動識別視訊流播放過程中解析度的變化,在視訊控制元件上自動調整尺寸。比如攝像機可以在使用過程中動態配置解析度,當解析度改動後對應視訊控制元件也要做出同步反應。
  10. 音視訊檔案無感知自動切換迴圈播放,不會出現切換期間黑屏等肉眼可見的切換痕跡。
  11. 視訊控制元件同時支援任意解碼核心、任意畫面顯示策略、任意視訊顯示模式。
  12. 視訊控制元件懸浮條同時支援控制代碼、繪製、GPU三種模式,非絕對座標移來移去。
  13. 本地攝像頭裝置支援指定裝置名稱、解析度、幀率進行播放。
  14. 錄影檔案同時支援開啟的視訊檔案、本地攝像頭、網路視訊流等。
  15. 瞬間響應開啟和關閉,無論是開啟不存在的視訊或者網路流,探測裝置是否存在,讀取中的超時等待,收到關閉指令立即中斷之前的操作並響應。
  16. 支援開啟各種圖片檔案,支援本地音視訊檔案拖曳播放。
  17. 視訊控制元件懸浮條自帶開始和停止錄影切換、聲音靜音切換、抓拍截圖、關閉視訊等功能。
  18. 音訊元件支援聲音波形值資料解析,可以根據該值繪製波形曲線和柱狀聲音條,預設提供了聲音振幅訊號。
  19. 各元件中極其詳細的列印資訊提示,尤其是報錯資訊提示,封裝的統一列印格式。針對現場複雜的裝置環境測試極其方便有用,相當於精確定位到具體哪個通道哪個步驟出錯。
  20. 程式碼框架和結構優化到最優,效能強悍,持續迭代更新升級。
  21. 原始碼支援Qt4、Qt5、Qt6,相容所有版本。

5.3 視訊控制元件

  1. 可動態新增任意多個osd標籤資訊,標籤資訊包括名字、是否可見、字號大小、文字文字、文字顏色、標籤圖片、標籤座標、標籤格式(文字、日期、時間、日期時間、圖片)、標籤位置(左上角、左下角、右上角、右下角、居中、自定義座標)。
  2. 可動態新增任意多個圖形資訊,這個非常有用,比如人工智慧演算法解析後的圖形區域資訊直接發給視訊控制元件即可。圖形資訊支援任意形狀,直接繪製在原始圖片上,採用絕對座標。
  3. 圖形資訊包括名字、邊框大小、邊框顏色、背景顏色、矩形區域、路徑集合、點座標集合等。
  4. 每個圖形資訊都可指定三種區域中的一種或者多種,指定了的都會繪製。
  5. 內建懸浮條控制元件,懸浮條位置支援頂部、底部、左側、右側。
  6. 懸浮條控制元件引數包括邊距、間距、背景透明度、背景顏色、文字顏色、按下顏色、位置、按鈕圖示程式碼集合、按鈕名稱標識集合、按鈕提示資訊集合。
  7. 懸浮條控制元件一排工具按鈕可自定義,通過結構體引數設定,圖示可選圖形字型還是自定義圖片。
  8. 懸浮條按鈕內部實現了錄影切換、抓拍截圖、靜音切換、關閉視訊等功能,也可以自行在原始碼中增加自己對應的功能。
  9. 懸浮條按鈕對應實現了功能的按鈕,有對應圖示切換處理,比如錄影按鈕按下後會切換到正在錄影中的圖示,聲音按鈕切換後變成靜音圖示,再次切換還原。
  10. 懸浮條按鈕單擊後都用名稱唯一標識作為訊號發出,可以自行關聯響應處理。
  11. 懸浮條空白區域可以顯示提示資訊,預設顯示當前視訊解析度大小,可以增加幀率、碼流大小等資訊。
  12. 視訊控制元件引數包括邊框大小、邊框顏色、焦點顏色、背景顏色(預設透明)、文字顏色(預設全域性文字顏色)、填充顏色(視訊外的空白處填充黑色)、背景文字、背景圖片(如果設定了圖片優先取圖片)、是否拷貝圖片、縮放顯示模式(自動調整、等比例縮放、拉伸填充)、視訊顯示模式(控制代碼、繪製、GPU)、啟用懸浮條、懸浮條尺寸(橫向為高度、縱向為寬度)、懸浮條位置(頂部、底部、左側、右側)。

5.4 核心ffmpeg

  1. 支援各種音視訊檔案、本地攝像頭裝置,各種視訊流網路流。
  2. 支援開始播放、暫停播放、繼續播放、停止播放、設定播放進度、倍速播放。
  3. 可設定音量、靜音切換、抓拍圖片、錄影儲存。
  4. 自動提取專輯資訊比如標題、藝術家、專輯、專輯封面,自動顯示專輯封面。
  5. 完美支援音視訊同步和倍速播放。
  6. 解碼策略支援速度優先、質量優先、均衡處理、最快速度。
  7. 支援手機視訊旋轉角度顯示,比如一般手機拍攝的視訊是旋轉了90度的,解碼顯示的時候需要重新旋轉90度才是正的。
  8. 自動轉換yuv420格式,比如本地攝像頭是yuyv422格式,有些視訊檔案是xx格式,統一將非yuv420格式轉換,然後再進行處理。
  9. 支援硬解碼dxva2、d3d11va等,效能極高尤其是大解析度比如4K視訊。
  10. 視訊響應極低延遲0.2s左右,極速響應開啟視訊流0.5s左右,專門做了優化處理。
  11. 硬解碼和GPU繪製組合,極低CPU佔用,比海康大華等客戶端更優。
  12. 支援視訊流中的各種音訊格式,AAC、PCM、G.726、G.711A、G.711Mu、G.711ulaw、G.711alaw、MP2L2等都支援,推薦選擇AAC相容性跨平臺性最好。
  13. 視訊儲存支援yuv、h264、mp4多種格式,音訊儲存支援pcm、wav、aac多種格式。預設視訊mp4格式、音訊aac格式。
  14. 支援分開儲存音訊視訊檔案,也支援合併到一個mp4檔案,預設策略是無論何種音視訊檔案格式儲存,最終都轉成mp4及aac格式,然後合併成音視訊一起的mp4檔案。
  15. 支援本地攝像頭實時視訊顯示帶音訊輸入輸出,音視訊錄製合併到一個mp4檔案。
  16. 支援H264/H265編碼(現在越來越多的監控攝像頭是H265視訊流格式)生成視訊檔案,內部自動識別切換編碼格式。
  17. 自動識別視訊流動態解析度改動,重新開啟視訊流。
  18. 支援使用者資訊中包含特殊字元(比如使用者資訊中包含+#@等字元)的視訊流播放,內建解析轉義處理。
  19. 純qt+ffmpeg解碼,非sdl等第三方繪製播放依賴,gpu繪製採用qopenglwidget,音訊播放採用qaudiooutput。
  20. 同時支援ffmpeg2、ffmpeg3、ffmpeg4、ffmpeg5版本,全部做了相容處理。如果需要支援xp需要選用ffmpeg3及以下。