一:音頻入門
(一)聲音三要素
1.音調(音頻)
2.音量(振幅)
3.音色(諧波)
粉色曲線是最接近自然界中的波形(基頻+多種不同頻率音頻合並:如黃色、藍色)
綠色曲線為基頻(主頻率),可以看到粉色曲線都是在主頻率上微調(走勢是基本一致的)
越接近正弦波,聲音一般越好聽,畸形或產生噪波
(二)模數轉換
模擬信號和數字信號之間可以相互轉換:
模擬信號一般通過PCM脈碼調制(Pulse Code Modulation)方法量化為數字信號,即讓模擬信號的不同幅度(采樣大小)分別對應不同的二進制值,例如采用8位編碼可將模擬信號量化為2^8=256個量級,實用中常采取24位或30位編碼;
數字信號一般通過對載波進行移相(Phase Shift)的方法轉換為模擬信號。
補充:通常的采樣率(每秒采樣次數)為48000、32000、16000等等;采樣率越高,數據信號還原為模擬信號的還原度越高!!!
計算機、計算機局域網與城域網中均使用二進制數字信號,目前在計算機廣域網中實際傳送的則既有二進制數字信號,也有由數字信號轉換而得的模擬信號。但是更具應用發展前景的是數字信號。
(三)音頻原始數據
音視頻常用格式有以下兩種:
PCM數據:純音頻數據; WAV(多媒體文件):既可以存儲PCM原始數據(多用),又可以存儲壓縮數據; 其實WAV就是在PCM原始數據之上,加上了一個頭,包含了基本信息(使得播放器使用正確的參數去播放PCM數據)
采樣率48000,位深度 16bit ,通道數2 知道這三個參數,那么基本我們就知道了 設備1秒內可以采集到多少音頻數據是: 48000 * 16 * 2 = 1536000 位 48000 * 16 * 2 / 8 = 192000 字節. 也就是我的設備在一秒內可以采集192000
這么大的碼流顯然無法在我們的網絡中傳輸!!!(不能帶寬全給音視頻傳輸吧),所以要進行音頻數據壓縮!!!
(四)音頻幀大小的計算(采樣率和時間間隔的區別)
采樣率是指在1秒中的采樣次數,而時間間隔是指每采取一幀音頻數據所要時間間隔!!!
假設音頻采樣率 = 8000,采樣通道 = 2,位深度 = 16,采樣間隔 = 20ms 首先我們計算一秒鍾總的數據量,采樣間隔采用20ms的話,說明每秒鍾需采集50次,這個計算大家應該都懂,那么總的數據量計算為 一秒鍾總的數據量 =8000 * 2*16/8 = 32000 所以每幀音頻數據大小 = 32000/50 = 640 每個通道樣本數 = 640/2 = 320
解析:#define MAX_AUDIO_FRAME_SIZE 192000
是指雙通道下,采用48k采樣率,位深為16位,采樣時間為1s
48000×2×16/8=192000字節
(五)PCM存儲格式
PCM存儲格式大體分為兩種Planner和Packed
我們以雙聲道為例,L表示左聲道,R表示右聲道,如下為兩種格式的存儲方式:
Planner
LLLLLLLL… RRRRRRRR…
Packed
LRLRLRLRLR…
FFMpeg中對音頻Format定義如下:
enum AVSampleFormat { AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, ///< unsigned 8 bits AV_SAMPLE_FMT_S16, ///< signed 16 bits AV_SAMPLE_FMT_S32, ///< signed 32 bits AV_SAMPLE_FMT_FLT, ///< float AV_SAMPLE_FMT_DBL, ///< double AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar AV_SAMPLE_FMT_FLTP, ///< float, planar AV_SAMPLE_FMT_DBLP, ///< double, planar AV_SAMPLE_FMT_S64, ///< signed 64 bits AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically };
我們這里以WASAPI為例,在windows中WASAPI捕獲的數據格式總是FLT,即浮點Packed格式,而新版的FFMpeg中僅僅支持FLTP格式壓縮(應該是需要自行編譯FFMpeg庫並附加其他壓縮庫,如有錯請指正),目前網絡中的大部分博客均是老版FFMpeg所以還都在使用其他格式。因此我們需要對PCM數據進行重新采樣。
(六)WAV header
在查看音頻數據壓縮方式前,先了解WAV header的數據結構:
WAV例子:
因為是4字節對齊,所以每個sample大小4字節;而之所以是4字節,取決於blockAlign字段=(幅度大小×通道數);
二:音頻處理流程
(一)直播客戶端的處理流程
我們講解的音視頻都是基於娛樂直播進行講解的,所以我們必須對整個流程非常清楚。
如下圖,是一個基本的直播客戶端的處理流程圖:
直播客戶端分為幾個模塊,我們需要對每個模塊做的事情非常清楚。總的來說我們直播客戶端分為兩個端:共享端和觀看端。
共享端包含:音視頻采集,音視頻編碼兩個模塊
觀看端包含:音視頻解碼,音視頻渲染兩個模塊。
1.音視頻采集:
音頻采集一般我們只需要簡單調用一下api就能實現,每個操作系統都提供了相對應的api, 只是對於不同的平台各不相同。
如Android端有audioRecord, mediaRecord等,我們什么時候使用audioRecord,什么時候使用MediaRecord,這個我們需要非常清楚它的應用場景。
同樣在IOS 端有蘋果提供的AVFoundation框架提供了很多音視頻采集的方法,其中有很常用的底層方法AudioUnit。
2.音視頻編碼:
我們采集到音視頻之后並不能直接傳輸,因為這個數據太大了,超出了我們網路的設備的負載。
如果我們直接傳輸這么大量的數據,很容易出現各種問題,因此我們必須對這些數據進行壓縮后再進行傳輸,這個壓縮的過程就是對音視頻的編碼處理。
編碼后的數據就是非常小的數據了。
對於編碼這塊的知識也非常多,我們后續也會詳細講解。
編碼分為有損編碼和無損編碼。我們什么時候用有損編碼,什么時候使用無損編碼? 有損編碼去掉的是哪些數據?這些原理我們都需要很清楚明白。
3.音視頻傳輸:
編碼時候,我們就需要把數據傳輸到對端,這個傳輸的過程也是非常復雜的,需要對網絡知識有個較好的理解。后續我也講到這些。
4.音視頻解碼:
對端收到傳輸的數據后,需要對編碼的數據進行解碼,把壓縮的數據還原后才能進行播放渲染。解碼這塊是跟編碼相對應的,用什么方式編碼,就需要用對應的方式解碼。
5.音視頻渲染:
解碼獲取得到原始數據之后,音頻交給揚聲器播放(實際是交給驅動,驅動交給驅動硬件模塊進行處理),視頻交給渲染器進行渲染。
(二)音頻數據的流轉(格式的轉換)
我們需要知道我們采集數據后,采集的是什么數據,是PCM數據(模擬數據轉數字信號,數字信號就是PCM)。然后經過編碼之后,編碼出的是什么格式的數據(aac/mp3)。這都是我們需要理解的。
對於錄制的視頻,我們最后將音視頻保存入容器中;對於直播來說,我們並不需要最后一步,不需要將他存入容器中!!!
如下圖是音頻數據流的流轉過程:
三:音頻采集
(一)各平台音頻采集方式
1.Android端音頻采集:https://blog.csdn.net/u010029439/article/details/85056767
android平台上的音頻采集一般就兩種方式:
(1)使用MediaRecorder進行音頻采集。
MediaRecorder 是基於 AudioRecorder 的 API(最終還是會創建AudioRecord用來與AudioFlinger進行交互) ,它可以直接將采集到的音頻數據轉化為執行的編碼格式,並保存。
這種方案相較於調用系統內置的用用程序,便於開發者在UI界面上布局,而且系統封裝的很好,便於使用,唯一的缺點是使用它錄下來的音頻是經過編碼的,沒有辦法的得到原始的音頻。
同時MediaRecorder即可用於音頻的捕獲也可以用於視頻的捕獲相當的強大。實際開發中沒有特殊需求的話,用的是比較多的!
(2)使用AudioRecord進行音頻采集。
AudioRecord 是一個比較偏底層的API,它可以獲取到一幀幀PCM數據,之后可以對這些數據進行處理。
AudioRecord這種方式采集最為靈活,使開發者最大限度的處理采集的音頻,同時它捕獲到的音頻是原始音頻PCM格式的!
像做變聲處理的需要就必須要用它收集音頻。
在實際開發中,它也是最常用來采集音頻的手段。如直播技術采用的就是AudioRecorder采集音頻數據。
2.Ios端音頻采集:https://blog.51cto.com/u_13505171/2057075
3.Windows音頻采集:https://blog.csdn.net/machh/article/details/83546258
(二)FFMpeg采集音頻方式(集成上面所有平台)
(1)通過命令方式(不同平台可能不同):https://www.cnblogs.com/ssyfj/p/14576359.html
先查看可用設備:
其中card中:PCH與NVidia是聲卡類型,可以通過查看cat /proc/asound/cards文件查看所有在主機中注冊的聲卡列表:
其中device設備中:
HDMI:是高清多媒體接口(High Definition Multimedia Interface,HDMI)是一種全數字化視頻和聲音發送接口,可以發送未壓縮的音頻及視頻信號。 alc662是聲卡:聲卡 (Sound Card)也叫音頻卡(港台稱之為聲效卡),是計算機多媒體系統中最基本的組成部分,是實現聲波/數字信號相互轉換的一種硬件。聲卡的基本功能是把來自話筒、磁帶、光盤的原始聲音信號加以轉換,輸出到耳機、揚聲器、擴音機、錄音機等聲響設備,或通過音樂設備數字接口(MIDI)發出合成樂器的聲音
所以:想要錄取聲音,我們必須選取card:0,其他的device與subdevice可以任意
選取對應設備獲取輸入:
ffmpeg -f alsa -i hw:0 alsaout.wav
(2)通過API方式:如下(三)
(三)FFmpeg編程采集音頻
1.打開輸入設備

#include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> int main(int argc,char* argv) { char* devicename = "hw:0"; char errors[1024]; int ret; av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注冊所有的設備,包括我們需要的音頻設備 avdevice_register_all(); //獲取輸入(采集)格式 AVInputFormat *iformat = av_find_input_format("alsa"); //打開輸入設備 AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); return 0; }
gcc -o od 01OpenDevice.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice
2.從音頻設備中讀取音頻數據

#include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> int main(int argc,char* argv) { char* devicename = "hw:0"; char errors[1024]; int ret,count=0; AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包結構 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注冊所有的設備,包括我們需要的音頻設備 avdevice_register_all(); //獲取輸入(采集)格式 iformat = av_find_input_format("alsa"); //打開輸入設備 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //開始從設備中讀取數據 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); //釋放空間 av_packet_unref(&packet); } //關閉設備、釋放上下文空間 avformat_close_input(&fmt_ctx); return 0; }
gcc -o gap 02GetAudioPacket.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice
包的大小同碼率/8=字節數
3.錄制成為音頻文件
創建文件--->將音頻數據寫入到文件中--->關閉文件

#include <stdio.h> #include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> int main(int argc,char* argv) { char* devicename = "hw:0"; char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包結構 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注冊所有的設備,包括我們需要的音頻設備 avdevice_register_all(); //獲取輸入(采集)格式 iformat = av_find_input_format("alsa"); //打開輸入設備 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //打開文件 fp = fopen("./audio.pcm","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file,[%d]%s\n",ret,errors); goto fail; } //開始從設備中讀取數據 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); len = fwrite(packet.data,packet.size,1,fp); fflush(fp); if(len!=packet.size){ av_log(NULL,AV_LOG_WARNING,"Warning,Packet size:%d not equal writen size:%d\n",len,packet.size); }else{ av_log(NULL,AV_LOG_INFO,"Success write Packet to file"); } //釋放空間 av_packet_unref(&packet); } fail: if(fp) fclose(fp); //關閉設備、釋放上下文空間 avformat_close_input(&fmt_ctx); return 0; }
gcc -o wad 03WriteAudioData.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice
注意:播放時需要指定格式,通過查看配置文件獲取采樣率等信息
sudo gedit /etc/pulse/daemon.conf
ffplay -ar 44100 -ac 2 -f s16le audio.pcm
四:音頻壓縮
(一)音頻有損壓縮技術(消除冗余信息)
注意:頻域遮蔽效應和時域遮蔽效應的縱軸都是聲音強度;
頻域遮蔽的橫軸是頻率,是指在頻率相近的聲音,在一定的范圍內(遮蔽門檻下的聲音),聲音強度大的會遮蔽聲音強度小的聲音。
時域遮蔽的橫軸是時間,是指在時間相近的聲音,聲音強度大的會遮蔽聲音強度小的聲音,而且在發聲前的部分較弱的聲音(前遮蔽)和發聲后的部分較弱的聲音都會被屏蔽掉(后遮蔽)。
(二)音頻無損壓縮技術
哈夫曼編碼
算術編碼
香農編碼
(三)音頻編碼過程
原始數據--->傳入給兩個模塊進行處理-------------------------------->將兩種數據(真正需要編碼的數據)匯總,進行量化,編碼-------->形成比特流
1.時域轉頻域變換(將一段長時間的數據,轉換為多種頻段的數據,從而獲取我們需要的頻段數據)
2.心理聲學模型(前面的有損壓縮過程)
(四)常見的音頻編碼器
其中最常見的是OPUS和AAC:延遲小,壓縮率高
實時互動系統可以用opus(在線教育、會議),其中WebRTC默認使用OPUS
泛娛樂化直播一般使用AAC(最廣泛),opus一般不支持,推廣上有些困難
兩個系統融合,需要將opus與AAC互轉
speex:回音消除,降噪模塊等可實現。 G.711:有些會與固話相聯系,固話用的就是G.711,或者G.722 (聲音損失嚴重,但是可以在窄寬帶下傳輸)
音頻編碼器性能質量對比:
橫軸:比特率越大(質量越好),但是傳輸速度可能較低;所以對於實時性要求高的,如OPus,在窄帶時,會降低質量,從而提高傳輸速度
縱軸:帶寬質量(窄帶、寬帶、全帶),硬件設施有關
音頻編碼碼率對比:(結合上面性能圖)
縱軸是延遲(0~200ms),橫軸是碼率。延遲越低,碼率越小:
Opus延遲較低,所以在20ms內,碼率從0~20kb/s 而在碼率較小時(0~20kb/s),AAC延遲則較大,在200ms附近,所以AAC不適用於實時性直播,適合於有一定延遲的直播。如果使用AAC於實時通信,那么可以選擇AAC-LD低延遲類型
五:AAC編碼
(一)AAC編碼器介紹
MP3相對來說存儲的壓縮比較低,壓縮后的文件還是比較大。而AAC壓縮率高,壓縮后文件較小,並且保真性能好,還原數據后,與原始數據相似性高。
AAC常用規格中:AAC HE V1使用較少,因為被V2取代。通過下面AAC規格可以了解:
AAC LC 最基礎
AAC HE V1 = AAC LC + SBR
AAC HE V2 = AAC HE V1 + PS
AAC LC:碼流越大(AAC LC,128k),存儲信息量越大,音質越好,保真性高。碼流越小,壓縮比越高,去除的冗余信息多,會對重要的數據造成一定的損失。
AAC HE:按頻譜分別保存;低頻(基頻)保存主要成分,高頻(諧頻)和音色有很大關系,將高頻單獨放大去保證音質。實現進一步壓縮。
AAC HE V2:參數化,保存差異,進一步減少碼流大小。
對於AAC格式:ADTS相對於ADIF而言,雖然每一幀前面都有header信息,但是卻可以實現隨時拖動播放,不必每次從頭播放
補充:音頻一幀數據計算---假設音頻采樣率 = 8000,采樣通道 = 2,位深度 = 8,采樣間隔 = 20ms
首先我們計算一秒鍾總的數據量,采樣間隔采用20ms的話,說明每秒鍾需采集50次(1s=1000ms),那么總的數據量計算為
一秒鍾總的數據量 =8000 * 2*8/8 = 16000(Byte) 所以每幀音頻數據大小 = 16000/50 =320(Byte) 每個通道樣本數 = 320/2 = 160(Byte)
(二)ADTS格式
ADTS頭,適用性廣,更加適用於流的傳輸,尤其對於直播系統
每一幀的ADTS的頭文件都包含了音頻的采樣率,聲道,幀長度等信息,這樣解碼器才能解析讀取。
一般情況下ADTS的頭信息都是7個字節,分為2部分:
adts_fixed_header();
adts_variable_header();
1.adts_fixed_header();
syncword :總是0xFFF, 代表一個ADTS幀的開始, 用於同步。解碼器可通過0xFFF確定每個ADTS的開始位置。因為它的存在,解碼可以在這個流中任何位置開始, 即可以在任意幀解碼。 ID:MPEG Version: 0 for MPEG-4,1 for MPEG-2 Layer:always: '00' protection_absent:Warning, set to 1 if there is no CRC and 0 if there is CRC #0表示需要CRC校驗,1不需要 profile:表示使用哪個級別的AAC,如01 Low Complexity(LC) -- AAC LC; profile的值等於 Audio Object Type的值減1。 profile=(audio_object_type - 1)
sampling_frequency_index:采樣率的下標
channel_configuration:聲道數,比如2表示立體聲雙聲道
2.adts_variable_header();
aac_frame_length:一個ADTS幀的長度包括ADTS頭和AAC原始流。frame length, this value must include 7 or 9 bytes of header length: aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame) protection_absent=0時, header length=9bytes protection_absent=1時, header length=7bytes adts_buffer_fullness:0x7FF 說明是碼率可變的碼流。 number_of_raw_data_blocks_in_frame:表示ADTS幀中有number_of_raw_data_blocks_in_frame + 1個AAC原始幀。 所以說number_of_raw_data_blocks_in_frame == 0 表示說ADTS幀中有一個AAC數據塊。 (一個AAC原始幀包含一段時間內1024個采樣及相關數據)
3.代碼實現頭部的添加:https://www.cnblogs.com/ssyfj/p/14579909.html
void adts_header(char *szAdtsHeader, int dataLen){ int audio_object_type = 2; //通過av_dump_format顯示音頻信息或者ffplay獲取多媒體文件的音頻流編碼acc(LC),對應表格中Object Type ID -- 2 int sampling_frequency_index = 4; //音頻信息中采樣率為44100 Hz 對應采樣率索引0x4 int channel_config = 2; //音頻信息中音頻通道為雙通道2 int adtsLen = dataLen + 7; //采用頭長度為7字節,所以protection_absent=1 =0時為9字節,表示含有CRC校驗碼 szAdtsHeader[0] = 0xff; //syncword :總是0xFFF, 代表一個ADTS幀的開始, 用於同步. 高8bits szAdtsHeader[1] = 0xf0; //syncword:0xfff 低4bits szAdtsHeader[1] |= (0 << 3); //MPEG Version:0 : MPEG-4(mp4a),1 : MPEG-2 1bit szAdtsHeader[1] |= (0 << 1); //Layer:0 2bits szAdtsHeader[1] |= 1; //protection absent:1 沒有CRC校驗 1bit szAdtsHeader[2] = (audio_object_type - 1)<<6; //profile=(audio_object_type - 1) 表示使用哪個級別的AAC 2bits szAdtsHeader[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index 4bits szAdtsHeader[2] |= (0 << 1); //private bit:0 1bit szAdtsHeader[2] |= (channel_config & 0x04)>>2; //channel configuration:channel_config 高1bit szAdtsHeader[3] = (channel_config & 0x03)<<6; //channel configuration:channel_config 低2bits szAdtsHeader[3] |= (0 << 5); //original:0 1bit szAdtsHeader[3] |= (0 << 4); //home:0 1bit ----------------固定頭完結,開始可變頭 szAdtsHeader[3] |= (0 << 3); //copyright id bit:0 1bit szAdtsHeader[3] |= (0 << 2); //copyright id start:0 1bit szAdtsHeader[3] |= ((adtsLen & 0x1800) >> 11); //frame length:value 高2bits 000|1 1000|0000 0000 szAdtsHeader[4] = (uint8_t)((adtsLen & 0x7f8) >> 3); //frame length:value 中間8bits 0000 0111 1111 1000 szAdtsHeader[5] = (uint8_t)((adtsLen & 0x7) << 5); //frame length:value 低 3bits 0000 0000 0000 0111 //number_of_raw_data_blocks_in_frame:表示ADTS幀中有number_of_raw_data_blocks_in_frame + 1個AAC原始幀。所以說number_of_raw_data_blocks_in_frame == 0 表示說ADTS幀中有一個AAC數據塊。(一個AAC原始幀包含一段時間內1024個采樣及相關數據) szAdtsHeader[5] |= 0x1f; //buffer fullness:0x7ff 高5bits 0x7FF 說明是碼率可變的碼流 ---> 111 1111 1111 00----> 1 1111 1111 1100--->0x1f與0xfc szAdtsHeader[6] = 0xfc; }
4.AAC首部分析:https://www.p23.nl/projects/aac-header/
5.命令行提取AAC文件
部分參數可以通過ffplay播放來獲取:
ffmpeg -i gfxm.mp4 -vn -c:a aac -ar 44100 -channels 2 -profile:a 2 1.aac
ffmpeg -i gfxm.mp4 -vn -c:a libfdk_aac -ar 44100 -channel 2 -profile:a aac_he_v2 1.aac
補充:通過profile參數,修改編碼級別,2表示修改為AAC SSR級別
ffplay 1.aac
編程實現編碼器:如七所示
六:音頻重采樣
(一)什么是音頻重采樣
1.為什么需要重采樣?
2.如何知道是否需要進行重采樣?
3.重采樣的步驟
4.重采樣相關API
(二)編程實現音頻重采樣
1.補充:
音頻通道布局
FFMpeg筆記(三) 音頻處理基本概念及音頻重采樣
2.代碼實現

#include <stdio.h> #include <libavutil/log.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> void rec_audio(){ char* devicename = "hw:0"; char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包結構 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注冊所有的設備,包括我們需要的音頻設備 avdevice_register_all(); //獲取輸入(采集)格式 iformat = av_find_input_format("alsa"); //打開輸入設備 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //打開文件 fp = fopen("./audio_2.pcm","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n"); goto fail; } //----------創建重采樣的上下文 SwrContext* swr_cxt = NULL; swr_cxt = swr_alloc_set_opts(NULL, //設置已經創建好的上下文,如果沒有,則為NULL AV_CH_LAYOUT_STEREO, //設置輸出目標的通道布局(雙聲道,立體聲,...,方位增寬) AV_SAMPLE_FMT_FLT, //設置輸出目標的采樣格式,設置為32位浮點型 44100, //設置輸出目標的采樣率 AV_CH_LAYOUT_STEREO, //輸入數據的通道布局,是雙聲道 AV_SAMPLE_FMT_S16, //輸入數據的采樣格式為s16le 44100, //輸入的采樣率 0, //日志級別 NULL); //日志上下文 if(!swr_cxt){ av_log(NULL,AV_LOG_ERROR,"Failed to set swr context\n"); goto fail; } //----------初始化上下文 if(swr_init(swr_cxt)<0){ av_log(NULL,AV_LOG_ERROR,"Failed to initial swr context\n"); goto fail; } //----------構造輸入、輸出空間 uint8_t** src_data = NULL; uint8_t** dst_data = NULL; int src_linesize = 0,dst_linesize=0; av_samples_alloc_array_and_samples(&src_data, //前面兩個傳入地址,用作輸出;其他作為輸入數據 &src_linesize, 2, //雙通道 512, //單通道采樣個數 每個packet大小2048,2048/2(和下面采樣格式有關)=1024個采樣個數,1024/2(這里是雙通道)=512(是單通道) AV_SAMPLE_FMT_S16, //采樣格式 0); //對齊 av_samples_alloc_array_and_samples(&dst_data, //前面兩個傳入地址,用作輸出;其他作為輸入數據 &dst_linesize, 2, //雙通道 512, //輸出的單通道采樣個數,和上面一致即可,只是對每個采樣進行了數據轉換,對內部數據重采樣,沒有修改采樣個數 AV_SAMPLE_FMT_FLT, //采樣格式 0); //對齊 //開始從設備中讀取數據 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); //----------先對數據進行重采樣,然后寫入文件中 memcpy((void*)src_data[0],(void*)packet.data,packet.size); swr_convert(swr_cxt, //上下文 dst_data, //輸出數組(雙指針) 512, //每個通道的采樣數 (const uint8_t**)src_data, //輸入數據,來自與packet.data,要改造格式 512); //輸入的通道采樣數 len = fwrite(dst_data[0],1,dst_linesize,fp); fflush(fp); if(len!=dst_linesize){ av_log(NULL,AV_LOG_WARNING,"Warning,Packet size:%d not equal writen size:%d\n",len,packet.size); }else{ av_log(NULL,AV_LOG_INFO,"Success write Packet to file\n"); } //釋放空間 av_packet_unref(&packet); } fail: if(fp) fclose(fp); //----------釋放空間 if(src_data){ av_freep(&src_data[0]); } av_freep(&src_data); if(dst_data){ av_freep(&dst_data[0]); } av_freep(&dst_data); swr_free(&swr_cxt); //關閉設備、釋放上下文空間 avformat_close_input(&fmt_ctx); return ; } int main(int argc,char* argv) { rec_audio(); return 0; }
gcc -o ra 04ResampleAudio.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample
七:創建AAC編碼器
見六:Audio:pcm_s16le,說明數據為原始pcm數據,沒有進行編碼處理!!!
(一)FFmpeg編碼過程
1.創建並打開編碼器
2.獲取輸入輸出數據
avcodec_send_frame:將幀(frame)發送給編碼器
avcodedc_receive_packet:通過receive_packet獲取編碼后的數據(packet)
一般來說AVPacket中存放的是編碼后的數據,而AVFrame中存放的未編碼的數據!!
疑問:為什么前面實現代碼,是從設備中獲取packet,而不是上面所說的frame??
與前面avformat_open_input有關,是將打開的設備當作多媒體文件進行處理。而多媒體文件肯定是編碼后的數據。所以會認為從設備中讀取的數據應該是編碼后的數據,使用pakcet獲取。而實際上數據類型應該是frame。
所以從設備中讀取的數據並沒有走編碼過程,實際數據還是pcm數據(見六圖中Audio:pcm_s16le)
(二)FFmpeg代碼實現(重采樣+編碼為AAC)

#include <stdio.h> #include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> SwrContext* initSwrCxt(uint8_t*** src_data,int* src_linesize,uint8_t*** dst_data,int* dst_linesize){ //創建重采樣的上下文 SwrContext* swr_cxt = NULL; swr_cxt = swr_alloc_set_opts(NULL, //設置已經創建好的上下文,如果沒有,則為NULL AV_CH_LAYOUT_STEREO, //設置輸出目標的通道布局(雙聲道,立體聲,...,方位增寬) AV_SAMPLE_FMT_S16, //設置輸出目標的采樣格式,設置為32位浮點型 44100, //設置輸出目標的采樣率 AV_CH_LAYOUT_STEREO, //輸入數據的通道布局,是雙聲道 AV_SAMPLE_FMT_S16, //輸入數據的采樣格式為s16le 44100, //輸入的采樣率 0, //日志級別 NULL); //日志上下文 if(!swr_cxt){ av_log(NULL,AV_LOG_ERROR,"Failed to set swr context\n"); return NULL; } //初始化上下文 if(swr_init(swr_cxt)<0){ av_log(NULL,AV_LOG_ERROR,"Failed to initial swr context\n"); return NULL; } //構造輸入、輸出空間 av_samples_alloc_array_and_samples(src_data, //前面兩個傳入地址,用作輸出;其他作為輸入數據 src_linesize, 2, //雙通道 512, //單通道采樣個數 每個packet大小2048,2048/2(和下面采樣格式有關)=1024個采樣個數,1024/2(這里是雙通道)=512(是單通道) AV_SAMPLE_FMT_S16, //采樣格式 0); //對齊 av_samples_alloc_array_and_samples(dst_data, //前面兩個傳入地址,用作輸出;其他作為輸入數據 dst_linesize, 2, //AV_CH_LAYOUT_SURROUND 三通道 512, //輸出的單通道采樣個數,和上面一致即可,只是對每個采樣進行了數據轉換,對內部數據重采樣,沒有修改采樣個數 AV_SAMPLE_FMT_S16, //采樣格式 0); //對齊 return swr_cxt; } AVCodecContext* openCodec(){ //---------1.打開編碼器 AVCodec* codec = avcodec_find_encoder_by_name("libfdk_aac"); //內部要求的采樣大小就是是s16le------重點 //---------2.創建上下文 AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); //------------------設置上下文參數 codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16; //設置采樣格式(該字段是被固定了),上面說到,libfdk_aac處理16位,所以設置為16位。所以我們一般是將其他格式進行重采樣為16位 codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; //設置通道布局 codec_ctx->channels = 2; //設置通道數(其實和上面一樣) codec_ctx->sample_rate = 44100; //設置采樣率 codec_ctx->profile = FF_PROFILE_AAC_HE; //設置AAC編碼格式,如果設置了這個字段,就不需要設置下面的比特率了 //codec_ctx->bit_rate = 64000; //設置比特率,64k;對於每個編碼方式,都有最低編碼碼率。AAC_LC:128k AAC HE:64k AAC HE V2:32k //---------3.打開編碼器 if(avcodec_open2(codec_ctx,codec,NULL)<0){ av_log(NULL,AV_LOG_ERROR,"Failed to open libfdk_aac context\n"); return NULL; } return codec_ctx; } void encode(AVCodecContext* codec_ctx,AVFrame* frame,AVPacket* newpkt,FILE* fp){ //--------開始進行編碼操作---------重點 int ret = avcodec_send_frame(codec_ctx,frame); //將frame交給編碼器進行編碼;內部會將一幀數據掛到編碼器的緩沖區 while(ret>=0){ //只有當frame被放入緩沖區之后(數據設置成功),並且下面ret表示獲取緩沖區數據完成后才退出循環。因為可能一個frame對應1或者多個packet,或者多個frame對應1個packet ret = avcodec_receive_packet(codec_ctx,newpkt); //從編碼器中獲取編碼后的packet數據,處理多種情況 if(ret<0){ if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF){ //讀完數據 return; }else{ //編碼器出錯 av_log(NULL,AV_LOG_ERROR,"avcodec_receive_packet error! [%d] %s\n",ret,av_err2str(ret)); return; } } int len = fwrite(newpkt->data,1,newpkt->size,fp); fflush(fp); if(len!=newpkt->size){ av_log(NULL,AV_LOG_WARNING,"Warning,newpkt size:%d not equal writen size:%d\n",len,newpkt->size); }else{ av_log(NULL,AV_LOG_INFO,"Success write newpkt to file\n"); } } } AVFrame* initFrame(){ AVFrame* frame = av_frame_alloc(); //分配frame空間,但是數據真正被存放在buffer中 if(!frame){ av_log(NULL,AV_LOG_ERROR,"Failed to create frame\n"); return NULL; } frame->nb_samples = 512; //單通道采樣數,和前面重采樣配置一樣 frame->format = AV_SAMPLE_FMT_S16; //采樣大小 frame->channel_layout = AV_CH_LAYOUT_STEREO; av_frame_get_buffer(frame,0); //第二個參數是對齊 512*2*2 = 2048 if(!frame->data[0]){ av_log(NULL,AV_LOG_ERROR,"Failed to create frame buffer\n"); return NULL; } return frame; } void rec_audio(){ char* devicename = "hw:0"; char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; //格式上下文獲取-----av_read_frame獲取packet AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包結構 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注冊所有的設備,包括我們需要的音頻設備 avdevice_register_all(); //獲取輸入(采集)格式 iformat = av_find_input_format("alsa"); //打開輸入設備 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); //----打開輸入設備,初始化格式上下文和選項 if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //打開文件 fp = fopen("./audio_2.aac","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n"); goto fail; } //創建重采樣的上下文 SwrContext* swr_cxt = NULL; //------根據initSwrCxt中的swr_alloc_set_opts創建重采樣上下文 uint8_t** src_data = NULL; uint8_t** dst_data = NULL; int src_linesize = 0,dst_linesize=0; swr_cxt = initSwrCxt(&src_data,&src_linesize,&dst_data,&dst_linesize); if(!swr_cxt){ av_log(NULL,AV_LOG_ERROR,"Failed to set swr context or initial swr context\n"); goto fail; } //-------獲取編碼器上下文 AVCodecContext* codec_ctx = openCodec(); //---------根據openCodec中的avcodec_alloc_context3分配編碼器的上下文 if(!codec_ctx) goto fail; //-------設置輸入數據frame AVFrame* frame = initFrame(); //分配frame空間,但是數據真正被存放在buffer中 if(!frame){ av_log(NULL,AV_LOG_ERROR,"Failed to create frame\n"); goto fail; } //-------設置輸入數據packet AVPacket* newpkt = av_packet_alloc(); if(!newpkt){ av_log(NULL,AV_LOG_ERROR,"Failed to create newpkt\n"); goto fail; } //開始從設備中讀取數據 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); //先對數據進行重采樣,然后寫入文件中 memcpy((void*)src_data[0],(void*)packet.data,packet.size); swr_convert(swr_cxt, //上下文 dst_data, //輸出數組(雙指針) 512, //每個通道的采樣數 (const uint8_t**)src_data, //輸入數據,來自與packet.data,要改造格式 512); //輸入的通道采樣數 //--------將重采樣后的數據轉存入frame中去 memcpy((void*)frame->data[0],dst_data[0],dst_linesize); //將編碼數據寫入文件 encode(codec_ctx,frame,newpkt,fp); //釋放空間 av_packet_unref(&packet); } //---------強制將編碼器緩沖區中的音頻數據進行編碼輸出 encode(codec_ctx,NULL,newpkt,fp); fail: if(fp) fclose(fp); //釋放空間 if(src_data){ av_freep(&src_data[0]); } av_freep(&src_data); if(dst_data){ av_freep(&dst_data[0]); } av_freep(&dst_data); //釋放重采樣上下文 swr_free(&swr_cxt); //釋放packet和frame if(frame){ av_frame_free(&frame); } if(newpkt){ av_packet_free(&newpkt); } //釋放編碼器上下文 if(codec_ctx){ avcodec_free_context(&codec_ctx); } //關閉設備、釋放上下文空間 avformat_close_input(&fmt_ctx); return ; } int main(int argc,char* argv) { rec_audio(); return 0; }
gcc -o ce 05CreateEncoder.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample