原理:
- 消除冗余信息,壓縮量最大,也叫有損壓縮
- 剔除人耳聽覺范圍外的音頻信號(hào)20Hz以下和20000Hz以上;
- 去除被掩蔽的音頻信號(hào),信號(hào)的遮蔽可以分為頻域遮蔽和時(shí)域遮蔽;
- 頻域遮蔽效應(yīng)
屏蔽70分貝以下,20HZ以下,20000HZ以上
屏蔽分貝小,頻率小的聲音
兩個(gè)頻率相近發(fā)出的聲音,去除低強(qiáng)度的,也就是分貝高的會(huì)蓋住分貝低的
- 時(shí)域遮蔽效應(yīng):
根根時(shí)間推移,相近頻率且同時(shí)出現(xiàn)的聲音,聲音強(qiáng)度高的遮蔽強(qiáng)度低的聲音,并且去除同一時(shí)間段前后雜音,前遮蔽50毫秒,后遮蔽200毫秒,在這段時(shí)間內(nèi)的聲音,強(qiáng)度越接近就越會(huì)被屏蔽。
- 去除冗余信息后,再進(jìn)行無損壓縮;
- 無損壓縮就是壓縮后的數(shù)據(jù)能夠解壓縮進(jìn)行還原,有損則不能;
- 熵編碼中有
哈夫曼編碼:用一個(gè)很小的二進(jìn)制數(shù)代替一個(gè)長(zhǎng)的字符串,頻率越高,編碼越小,頻率越低,編碼越長(zhǎng)
算術(shù)編碼:利用小數(shù)進(jìn)行編碼,在香農(nóng)編碼的基礎(chǔ)改進(jìn)而來的
香農(nóng)編碼
音頻編碼過程
數(shù)據(jù)先同時(shí)通過 時(shí)域轉(zhuǎn)頻域變換器和心理學(xué)模型處理數(shù)據(jù),前者將數(shù)據(jù)轉(zhuǎn)換成多種頻段的數(shù)據(jù),然后剔除不需要的頻段數(shù)據(jù),后者會(huì)去除非人耳聽到的范圍聲音和一些復(fù)合聲音,最后將兩者合并經(jīng)過量化編碼,無損編碼之類的,形成比特流數(shù)據(jù),在此之前還會(huì)有一些輔助數(shù)據(jù),此后數(shù)據(jù)就會(huì)變得非常??;
常見的音頻編碼器
opus、aac、Ogg、Speex、iLBC、AMR、G.711, 最常用的編碼器是opus aac。
opus常用于直播,尤其是無延遲的直播,webrtc默認(rèn)使用opus;
AAC是應(yīng)用最廣泛的編解碼;
Ogg收費(fèi);
Speex支持回音消除;
G.711一般用于固定電話,聲音損耗嚴(yán)重,通話會(huì)失真;
本文福利, 免費(fèi)領(lǐng)取C++音視頻學(xué)習(xí)資料包+學(xué)習(xí)路線大綱、技術(shù)視頻/代碼,內(nèi)容包括(音視頻開發(fā),面試題,F(xiàn)Fmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,編解碼,推拉流,srs),有需要的可以進(jìn)企鵝裙927239107領(lǐng)取哦~
?
AAC比較適合有一定延遲的直播,AAC-LD屬于低延遲編碼器
- AAC編碼器:目前應(yīng)用最廣泛,如iOS、安卓和其他嵌入式設(shè)備都包含了AAC硬件編解碼器,主要學(xué)習(xí)這個(gè)編碼器;
用來取代mp3,比mp3更高的壓縮比和保真性更強(qiáng);
常用的規(guī)格有AAC LC、AAC HE V1 、AAC HE V2三種;
AAC HE V1 = AAC + SBR;
AAV HE V2 = AAC + SBR + PS;
目前AAC HE V1 已經(jīng)被取代 V2 取代了;
V2的碼流跟V1的差別不是很大,根據(jù)聲音的數(shù)據(jù)變化,如果兩個(gè)聲道的差別很大,碼流差別就會(huì)越??;
AAC 中header有兩種格式:
就相當(dāng)于在aac數(shù)據(jù)前面加了個(gè)Header,header里面就會(huì)包含aac數(shù)據(jù)的一些信息,方便進(jìn)行編解碼
- ADIF(Audio data interchange format): 特點(diǎn)是只能從頭開始解碼,可以確定的找到音頻數(shù)據(jù)的開始部分,不能從音頻數(shù)據(jù)中間開始,這種格式常用于磁盤文件中;
- ADTS(Audio Data Transport Format):在每一幀的數(shù)據(jù)里面都會(huì)有一個(gè)同步字,也就是每幀都有一個(gè)header,所以他可以在任意的位置開始進(jìn)行解碼,就像流式數(shù)據(jù);
- ADTS結(jié)構(gòu): 由7-9個(gè)字節(jié)組成,通常情況下是7個(gè)字節(jié),如果有CRC 就是9個(gè)字節(jié),字節(jié)中的每一位都有獨(dú)特的含義;
- 1~12bit:全部是1也就是0xFFF,表示是同步字;
- 13:編碼規(guī)范 0 = MPEG-4 1 = MPEG-2;
- 14~15:總是0;
- 16:是否有保護(hù) 1 代表 沒有 CRC 0 代表有CRC;
- 17~18:表示的是MPEG-4的音頻類型:AAC LC、 AAC HE V1 、AAC HE V2
- 19~22:表示的是采樣率
- 24~26:通道數(shù)
- 31~33:數(shù)據(jù)長(zhǎng)度,也包括了header的長(zhǎng)度
- 剩余的之后補(bǔ)上
其中每一十進(jìn)制數(shù)對(duì)應(yīng)的含義:
Audio Object Type: 在代碼中實(shí)際獲取類型的時(shí)候需要進(jìn)行+1,才是下面的類型
1 == AAC main
2 == AAC LC
5 == SBR == HE V1
29 == ps == HE V2
其中的采樣率是通過十進(jìn)制數(shù)表示的一個(gè)采樣率,有一個(gè)表,比如:0 == 96000Hz 1 == 88200HZ 等
音頻采集實(shí)戰(zhàn)
每個(gè)端音頻采集的底層和應(yīng)用層的庫是不一樣的,所以使用ffmpeg中間層能夠?qū)崿F(xiàn)跨平臺(tái)開發(fā);
- Android端的底層庫是AudioRecorder,應(yīng)用層是MediaRecorder;
- iOS端的底層庫是AudioUnit,應(yīng)用層是AVFoundation;
- Windows端的常用的是Directshow OpenAL 還有Windows7之上的AudioCore;
使用ffmpeg有兩種采集方式:
- 使用命令方式,命令詳情查看ffmpeg相關(guān)指令的那篇
- 使用代碼調(diào)用api的方式
- 在mac下的動(dòng)態(tài)庫需要對(duì)動(dòng)態(tài)庫進(jìn)行簽名
獲取本地簽名證書列表:/usr/bin/security find-identity -v -p codesigning
查看動(dòng)態(tài)庫是否簽名: codesign -d -vv 動(dòng)態(tài)庫文件
簽名命令:codesign -fs "iPhone Distribution: 你的簽名證書." 動(dòng)態(tài)庫文件
xcode環(huán)境:13.2.1
簽名了如果還是報(bào)錯(cuò),關(guān)掉沙盒并且設(shè)置 Enable Hardened Runtime 為NO
在項(xiàng)目中設(shè)置user header search path的時(shí)候,要使用全路徑方式,我使用$(PROJECT_NAME)方式,有的頭文件在鏈接的時(shí)候會(huì)報(bào)錯(cuò);
采集音頻的步驟:
- 打開輸入輸出設(shè)備,涉及的包是avdevice avformat 注冊(cè)設(shè)備 設(shè)置采集方式,根據(jù)平臺(tái)選擇,即設(shè)置輸入 打開音頻設(shè)備
- 獲取數(shù)據(jù)包 包:avcodec 主要使用av_read_frame方法獲取數(shù)據(jù) 將數(shù)據(jù)放入packet中 在讀取的時(shí)候注意緩沖區(qū)無未準(zhǔn)備好的情況
- 將數(shù)據(jù)輸出到文件 創(chuàng)建文件--- fopen 將數(shù)據(jù)寫入文件-- fwrite 關(guān)閉文件 -- fclose
- 打開設(shè)備 ·
void startRecorder(void) {
// 上下文
AVFormatContext *av_context = NULL;
AVDictionary *options = NULL;
// 1. 注冊(cè)設(shè)備
avdevice_register_all();
// 2. 設(shè)置采集方式
//設(shè)置采集方式 mac os 下是AVfoundation Windows下是dshow linux 下是alsa
AVInputFormat *format = av_find_input_format("avfoundation");
// 3. 打開設(shè)備
//里面的識(shí)別格式為[[video device]:[audio device]] 這里寫0 是獲取第1個(gè)音頻設(shè)備
char *name = ":0";
// url 是路徑 可以是網(wǎng)絡(luò)路徑也可以是本地路徑 本地路徑mac下的格式是 video : audio 這里表示獲取第一個(gè)音頻設(shè)備
int result = avformat_open_input(&av_context, name, format, &options);
if (result != 0) {
char errors[1024];
// 根據(jù)返回值生成錯(cuò)誤信息
av_make_error_string(errors, 1024, result);
printf("打開設(shè)備失?。?s\n", errors);
return;
}
printf("打開設(shè)備成功!\n");
get_audio_packet(av_context,&packet_callback);
// 關(guān)閉輸入 上下文
avformat_close_input(&av_context);
}
- 讀取數(shù)據(jù)和存儲(chǔ)到文件
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {
// w == 寫 b == 二進(jìn)制 + == 沒有就創(chuàng)建文件
FILE *f = fopen("/Users/cunw/Desktop/learning/音視頻學(xué)習(xí)/音視頻文件/code_recorder.pcm", "wb+");
AVPacket *packet = av_packet_alloc();
int result = -1;
// 循環(huán)讀取設(shè)備信息
// result == -35 是Resource temporarily unavailable 因?yàn)楂@取太頻繁 設(shè)備未準(zhǔn)備好,還正在處理數(shù)據(jù)
// 因?yàn)檩斎朐O(shè)備準(zhǔn)備好需要時(shí)間 睡一秒后再讀取
sleep(1.0);
while ((result = av_read_frame(·context, packet)) == 0 || result == -35) {
if (packet->size > 0) {
packet_callback(*packet);
fwrite(packet->data, packet->size, 1, f);
// 每讀取一次 就清空數(shù)據(jù)包 不然數(shù)據(jù)包會(huì)一直增大
av_packet_unref(packet);
}
}
if (result != 0) {
char errors[1024];
av_make_error_string(errors, 1024, result);
printf("get packet occured error is \"%s\" \n", errors);
}
// 將緩沖區(qū)剩余的數(shù)據(jù) 強(qiáng)制寫入文件
fflush(f);
fclose(f);
// 釋放packet空間
av_packet_free(&packet);
}
// 回調(diào)函數(shù)
void packet_callback(AVPacket packet) {
printf("packet size is %d\n",packet.size);
}
- 播放
- ffplay 播放pcm數(shù)據(jù): ffplay -ar(采樣率) 44100 -ac(通道數(shù)) 2 -f(采樣大?。ゝ32le 文件名
音頻編解碼實(shí)戰(zhàn)
音頻重采樣
就是將音頻三元組(采樣率 采樣大小 通道數(shù))的值轉(zhuǎn)成另外一組值
1. 應(yīng)用場(chǎng)景:
1、從設(shè)備采集的音頻數(shù)據(jù)與編碼器要求的不一致;
2、揚(yáng)聲器要求的音頻數(shù)據(jù)與要播放的音頻數(shù)據(jù)不一致;
3、方便運(yùn)算:例如回音消除 將多聲道變?yōu)閱温暤?
2. 如何判斷是否需要重采樣
- 了解音頻設(shè)備的參數(shù)
- 查看ffmpeg源碼
3. 重采樣的步驟
api:需要使用libswresample庫
1. 創(chuàng)建重采樣上下文
- swr_alloc_set_opts 通過設(shè)置采樣參數(shù)獲取上下文
2. 設(shè)置參數(shù)
- 參數(shù)大體分為輸出的采樣率、采樣大小、聲道和輸入的采樣率、采樣大小、聲道;
- out_ch_layout:表示聲道也可以是布局(揚(yáng)聲器的布局)AV_CH_LAYOUT_STEREO 立體聲;
- out_sample_fmt:輸出的采樣格式 16 = AV_SAMPLE_FMT_S16 或者 32 = AV_SAMPLE_FMT_FLT;
- av_sample_fmt_s16 in_ch_layout:輸入的聲道布局 ;
- in_sample_fmt 輸入的采樣格式 ;
- in_sample_rate: 輸入的采樣率;
- 后兩位是log相關(guān) 0,null
3. 初始化重采樣
- swr_init 初始化上下文
4. 進(jìn)行重采樣
- swr_convert 開始轉(zhuǎn)換 ,目的就是將輸入緩沖區(qū)的數(shù)據(jù)寫入輸出緩沖區(qū)
out:輸出結(jié)果緩沖區(qū) out_count:每個(gè)通道的采樣數(shù)
in:輸入的緩沖區(qū) in_count:輸入的單個(gè)通道的采樣數(shù)
- 因?yàn)橹夭蓸拥臄?shù)據(jù)需要重新構(gòu)造所以需要?jiǎng)?chuàng)建輸入緩沖區(qū)和輸出緩沖區(qū)
使用av_sample_array_and_samples audio_data創(chuàng)建
其中的單通道采樣數(shù)(單位是字節(jié))`nb_samples = pkt.size / (32位 / 8) / 2(通道數(shù))`
linessize:緩沖區(qū)大小 align:對(duì)齊 0
- 在轉(zhuǎn)換前需要將pkt的data按字節(jié)拷貝到輸入緩沖區(qū),調(diào)用memcpy需要引用string.h
- 將輸出數(shù)據(jù)寫入文件
將輸出緩沖區(qū)已經(jīng)轉(zhuǎn)換的數(shù)據(jù)寫入文件
5. 釋放資源
- 還有輸入輸出緩沖區(qū)的釋放av_freep
- swr_free釋放上下文
重采樣上下文初始化代碼
SwrContext * init_swr_context(void) {
SwrContext *context = NULL;
// 假設(shè)已經(jīng)提前知道輸入音頻數(shù)據(jù)的三要素的值 AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLT, 44100
context = swr_alloc_set_opts(NULL,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,
44100,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_FLT,
44100,
0, NULL);
int result = swr_init(context);
if (result != 0) {
char error[1024];
av_make_error_string(error, 1024, result);
printf("初始化重采樣上下文失敗:%s", error);
}
return context;
}
將采集的數(shù)據(jù)重采樣后 寫入文件代碼
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {
// w == 寫 b == 二進(jìn)制 + == 沒有就創(chuàng)建文件
FILE *f = fopen("/Users/cunw/Desktop/learning/音視頻學(xué)習(xí)/音視頻文件/resample.pcm", "wb+");
// 初始化重采樣上下文
SwrContext *swr_context = init_swr_context();
// 初始化轉(zhuǎn)換的輸入輸出緩沖區(qū)
uint8_t **out_buffer = NULL;
int linesize_out = 0;
av_samples_alloc_array_and_samples(&out_buffer, &linesize_out, 2, 512, AV_SAMPLE_FMT_S16, 0);
uint8_t **in_buffer = NULL;
int linesize_in = 0;
// nb_samples 單通道采樣數(shù) 4096 / (32 / 8) / 2 = 1024
av_samples_alloc_array_and_samples(&in_buffer, &linesize_in, 2, 512, AV_SAMPLE_FMT_FLT, 0);
AVPacket *packet = av_packet_alloc();
int result = -1;
// 循環(huán)讀取設(shè)備信息
// result == -35 是Resource temporarily unavailable 因?yàn)楂@取太頻繁 設(shè)備未準(zhǔn)備好,還正在處理數(shù)據(jù)
sleep(1);
while (((result = av_read_frame(context, packet)) == 0 || result == -35) && isRecording == 1) {
if (packet->size > 0) {
// 開始轉(zhuǎn)換數(shù)據(jù)
// 先將音頻數(shù)據(jù)拷貝到輸入緩沖區(qū) 只是重采樣音頻的話 只需要處理數(shù)組的第一個(gè)
memcpy(in_buffer[0], packet->data, packet->size);
// 再進(jìn)行轉(zhuǎn)換
swr_convert(swr_context, out_buffer, 512, (const uint8_t **)in_buffer, 512);
fwrite(out_buffer[0],linesize_out, 1, f);
// 每讀取一次 就清空數(shù)據(jù)包 不然數(shù)據(jù)包會(huì)一直增大
av_packet_unref(packet);
}
}
if (result != 0) {
char errors[1024];
av_make_error_string(errors, 1024, result);
printf("get packet occured error is \"%s\" \n", errors);
}
// 釋放重采樣資源
if (in_buffer) {
av_freep(&in_buffer[0]);
}
if (out_buffer) {
av_freep(&out_buffer[0]);
}
av_freep(&in_buffer);
av_freep(&out_buffer);
swr_free(&swr_context);
// 將緩沖區(qū)剩余的數(shù)據(jù) 強(qiáng)制寫入文件
fflush(f);
fclose(f);
// 釋放packet空間
av_packet_free(&packet);
}
ffmpeg 音頻數(shù)據(jù)編碼
本文福利, 免費(fèi)領(lǐng)取C++音視頻學(xué)習(xí)資料包+學(xué)習(xí)路線大綱、技術(shù)視頻/代碼,內(nèi)容包括(音視頻開發(fā),面試題,F(xiàn)Fmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,編解碼,推拉流,srs),有需要的可以進(jìn)企鵝裙927239107領(lǐng)取哦~
在使用fdk_aac編碼器的時(shí)候,由于默認(rèn)的ffmpeg有自帶的aac,所以通過avcodec_find_encoder_by_name("libfdk_aac")就獲取不到。在編譯的時(shí)候加上--enable-libfdk-aac。注意:重新編譯安裝ffmpeg之前最好先刪掉之前的ffmpeg,然后更新項(xiàng)目中的動(dòng)態(tài)庫;
如果還不行,試試單獨(dú)下載安裝[fdk_aac](https://www.linuxfromscratch.org/blfs/view/svn/multimedia/fdk-aac.html),再重新編譯ffmpeg文章來源:http://www.zghlxwxcb.cn/news/detail-745411.html
- 創(chuàng)建編碼器 avcodec
- avcodec_find_encoder 一種通過名字查找 一種是通過id查找,id的查找方式只會(huì)找默認(rèn)的編碼器,比如aac,如果是fdkaac就需要通過名字查找;
- AV_CODEC_ID_AAC | opus 其他編碼器
- "libfdk_aac", aac默認(rèn)的規(guī)格是AAC LC
- 創(chuàng)建上下文 avcodexcontext
設(shè)置音頻三要素 - avcodec_alloc_context3
3表示第三個(gè)版本 - sample_fmt = av_sample_FMT_S16 aac編碼器不支持flt 32位
- chnnel_layout = AV_CH_LAYOUT_STEREO( 或者chanels = 2)
- sample_rate = 44100
- bit_rate = 64000; (KB 碼率)可選設(shè)置
- profile = FF_PROFILE_AAC_HE_V2; (只有bit_rate=0 才有用) 可選設(shè)置,設(shè)置編碼器規(guī)格
- 打開編碼器
- avcodex_opne2
2表示第二個(gè)版本
送數(shù)據(jù)給編碼器時(shí),編碼器內(nèi)部有一個(gè)緩沖區(qū),緩沖一部分?jǐn)?shù)據(jù)后才進(jìn)行編碼
- 編碼
- 用AVFrame包裝未編碼的數(shù)據(jù),相當(dāng)于是個(gè)輸入,用AVPacket包裝已編碼的數(shù)據(jù),相當(dāng)于是個(gè)輸出;
- 調(diào)用avcodec_send_frame 將avframe緩沖區(qū)的數(shù)據(jù)發(fā)送給編碼器,如果返回值大于0,就表示數(shù)據(jù)成功發(fā)送到了編碼器,接著就可以通過循環(huán)使用 avcodec_receive_packet讀取編碼好的數(shù)據(jù)到AVPacket,并寫入文件中,如果讀取的結(jié)果是AVERROR(EAGIN)或者是AVERROR_EOF,就停止讀取,如果是其他的負(fù)數(shù),就停止編碼;
- av_frame_alloc 堆區(qū)初始化frame
- 設(shè)置frame的nb_samples 單通道一個(gè)數(shù)據(jù)幀采樣數(shù) 512
- format 每個(gè)采樣的大小 av_sample_fmt_s16
- channel_layout 聲道 av_ch_layout_stereo
- av_frame_get_buffer 分配frame里面buffer的大小
- 還要判斷frame的buffer是否分配成功
- 將重采樣后的數(shù)據(jù)memcpy到frame->data中
- 再將frame中的數(shù)據(jù)塞到編碼器上下文中 avcodec_send_frame,該函數(shù)會(huì)返回一個(gè)int , 當(dāng)結(jié)果>=0的時(shí)候表明有數(shù)據(jù)已經(jīng)在編碼緩沖區(qū)了;
- avcodec_receive_packet 讀取編碼好的數(shù)據(jù) avpacket
- av_packet_alloc 分配編碼后的數(shù)據(jù)空間
- 因?yàn)榫幋a器上下文中有一個(gè)緩沖區(qū),其中會(huì)緩存多個(gè)frame,因此并不是每塞一個(gè)frame就會(huì)有一個(gè)packet出來,所以需要通過一個(gè)while循環(huán)判斷編碼器的數(shù)據(jù)是否>=0,再通過avcodec_receive_packet獲取packet,該函數(shù)也會(huì)返回一個(gè)int,如果返回值>=0表明獲取成功,如果失敗直接退出編碼,這個(gè)值返回值還有其他含義,需要判斷eagain 表明編碼器沒有數(shù)據(jù)了或者是有數(shù)據(jù)但是不夠編碼 這個(gè)eagain需要用AVERROR包裝成一個(gè)負(fù)數(shù),表明數(shù)據(jù)還沒準(zhǔn)備好 averror_eof 表明一點(diǎn)數(shù)據(jù)都沒有了;
- 最后將數(shù)據(jù)編碼后的數(shù)據(jù)寫入到文件pkt->data,數(shù)據(jù)格式就是aac了;
- 在停止錄制的時(shí)候,由于編碼的緩存區(qū)可能還有數(shù)據(jù),在最后關(guān)閉之前,再去取一遍編碼數(shù)據(jù)放入文件;
- 釋放資源
在結(jié)束的時(shí)候釋放frame(av_frame_free) 和packet(av_packet_frame);文章來源地址http://www.zghlxwxcb.cn/news/detail-745411.html
編碼實(shí)戰(zhàn)代碼:
1. 創(chuàng)建fdk_aac編碼器及上下文
AVCodecContext* init_codec_context(void) {
// 創(chuàng)建aac編碼器
AVCodec *codec = avcodec_find_encoder_by_name("libfdk_aac");
// 初始化上下文
AVCodecContext *context = NULL;
context = avcodec_alloc_context3(codec);
context->sample_fmt = AV_SAMPLE_FMT_S16;
context->sample_rate = 44100;
context->channel_layout = AV_CH_LAYOUT_STEREO;
context->bit_rate = 0;
// bitrate == 0 才會(huì)生效
context->profile = FF_PROFILE_AAC_HE_V2;
int result = avcodec_open2(context, codec, NULL);
if (result < 0) {
char error[1024];
av_make_error_string(error, 1024, result);
av_log(NULL, AV_LOG_DEBUG, "創(chuàng)建AAC編碼器失?。?s",error);
}
return context;
}
2. 創(chuàng)建輸入緩沖區(qū)
AVFrame* create_audio_input_frame(void) {
AVFrame *codec_frame = NULL;
codec_frame = av_frame_alloc();
codec_frame->nb_samples = 512;
codec_frame->channel_layout = AV_CH_LAYOUT_STEREO;
codec_frame->format = AV_SAMPLE_FMT_S16;
int buffer_result = av_frame_get_buffer(codec_frame, 0);
if (buffer_result < 0) {
char error[1024];
av_make_error_string(error, 1024, buffer_result);
printf("frame 緩沖區(qū)分配失敗:%s", error);
}
return codec_frame;
}
3. 開始編碼并寫入文件
void audio_encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *packet, FILE *fl) {
// 將數(shù)據(jù)送入編碼器
int codec_result = avcodec_send_frame(ctx, frame);
while (codec_result >= 0) {
// 從packet中循環(huán)讀取編碼好的數(shù)據(jù)
codec_result = avcodec_receive_packet(ctx, packet);
if (codec_result == AVERROR(EAGAIN) || codec_result == AVERROR_EOF) {
break;
} else if (codec_result < 0) {
char error[1024];
av_make_error_string(error, 1024, codec_result);
printf("編碼器出錯(cuò):%s 停止編碼", error);
} else {
fwrite(packet->data, 1,packet->size, fl);
}
}
if (codec_result < 0) {
char error[1024];
av_make_error_string(error, 1024, codec_result);
printf("將數(shù)據(jù)送入編碼器錯(cuò)誤: %s\n",error);
}
}
4. 調(diào)用
- 先將重采樣的數(shù)據(jù)放入avframe的緩沖區(qū)中
memcpy(codec_frame->data[0], out_buffer[0], linesize_out);
- 再開始編碼
audio_encode(codec_context, codec_frame, codec_packet, f);
- 總覽
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {
// w == 寫 b == 二進(jìn)制 + == 沒有就創(chuàng)建文件
FILE *f = fopen("/Users/cunw/Desktop/learning/音視頻學(xué)習(xí)/音視頻文件/encoder.aac", "wb+");
// 創(chuàng)建編碼器上下文
AVCodecContext *codec_context = init_codec_context();
// 初始化輸入緩沖區(qū) AVframe
AVFrame *codec_frame = create_audio_input_frame();
// 初始化編碼輸出緩沖區(qū)
AVPacket *codec_packet = av_packet_alloc();
// 初始化重采樣上下文
SwrContext *swr_context = init_swr_context();
// 初始化重采樣的緩沖區(qū)
uint8_t **out_buffer = NULL;
int linesize_out = 0;
uint8_t **in_buffer = NULL;
int linesize_in = 0;
init_resammple_buffer(&in_buffer, &linesize_in, &out_buffer, &linesize_out);
AVPacket *packet = av_packet_alloc();
int result = -1;
// 循環(huán)讀取設(shè)備信息
while (isRecording == 1) {
result = av_read_frame(context, packet);
if (packet->size > 0 && result == 0) {
packet_callback(*packet);
// 開始轉(zhuǎn)換數(shù)據(jù)
// 先將音頻數(shù)據(jù)拷貝到輸入緩沖區(qū) 只是重采樣音頻的話 只需要處理數(shù)組的第一個(gè)
memcpy(in_buffer[0], packet->data, packet->size);
// 再進(jìn)行轉(zhuǎn)換
swr_convert(swr_context, out_buffer, 512, (const uint8_t **)in_buffer, 512);
// 將重采樣好的數(shù)據(jù)按字節(jié)拷貝到frame緩沖區(qū)
memcpy(codec_frame->data[0], out_buffer[0], linesize_out);
audio_encode(codec_context, codec_frame, codec_packet, f);
// 每讀取一次 就清空數(shù)據(jù)包 不然數(shù)據(jù)包會(huì)一直增大
av_packet_unref(packet);
} else if (result == -EAGAIN) {
// result == -35 是Resource temporarily unavailable 因?yàn)樵O(shè)備未準(zhǔn)備好,還正在處理數(shù)據(jù)
av_usleep(1);
}
}
// 把緩沖區(qū)剩余的數(shù)據(jù)拿出來編碼
audio_encode(codec_context, NULL, codec_packet, f);
if (result != 0) {
char errors[1024];
av_make_error_string(errors, 1024, result);
printf("get packet occured error is \"%s\" \n", errors);
}
// 釋放重采樣資源
if (in_buffer) {
av_freep(&in_buffer[0]);
}
if (out_buffer) {
av_freep(&out_buffer[0]);
}
av_freep(&in_buffer);
av_freep(&out_buffer);
swr_free(&swr_context);
av_frame_free(&codec_frame);
av_packet_free(&codec_packet);
// 將緩沖區(qū)剩余的數(shù)據(jù) 強(qiáng)制寫入文件
fflush(f);
fclose(f);
// 釋放packet空間
av_packet_free(&packet);
}
到了這里,關(guān)于音視頻開發(fā):音頻編碼原理+采集+編碼實(shí)戰(zhàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!