1.什么是AAC待榔?
AAC(Advanced Audio Coding)是一種音頻編碼標(biāo)準(zhǔn)蜻展,最早定義在MPEG-2標(biāo)準(zhǔn)(ISO/IEC 13818-7)中涮帘,后來在MPEG-4(ISO/IEC 14496-3)標(biāo)準(zhǔn)中又加入了SBR技術(shù)和PS技術(shù)(<font color=gray size=2>MPEG的介紹可以看這里:MPEG標(biāo)準(zhǔn)介紹</font>)。AAC標(biāo)準(zhǔn)是作為MP3的繼承者而設(shè)計(jì)出來的赶促,相同的比特率之下愕秫,AAC比MP3有更好的音質(zhì)慨菱。
</br></br></br>
為了適應(yīng)不同的應(yīng)用場(chǎng)景,AAC定義9種Profile
- MPEG-2 AAC LC 低復(fù)雜度規(guī)格(Low Complexity)--比較簡(jiǎn)單戴甩,沒有增益控制符喝,但提高了編碼效率,在中等碼率的編碼效率以及音質(zhì)方面甜孤,都能找到平衡點(diǎn)
- MPEG-2 AAC Main 主規(guī)格
- MPEG-2 AAC SSR 可變采樣率規(guī)格(Scaleable Sample Rate)
- MPEG-4 AAC LC低復(fù)雜度規(guī)格(Low Complexity)------現(xiàn)在的手機(jī)比較常見的MP4文件中的音頻部份就包括了該規(guī)格音頻文件
- MPEG-4 AAC Main 主規(guī)格 ------包含了除增益控制之外的全部功能协饲,其音質(zhì)最好
- MPEG-4 AAC SSR 可變采樣率規(guī)格(Scaleable Sample Rate)
- MPEG-4 AAC LTP 長(zhǎng)時(shí)期預(yù)測(cè)規(guī)格(Long Term Predicition)
- MPEG-4 AAC LD 低延遲規(guī)格(Low Delay)
- MPEG-4 AAC HE 高效率規(guī)格(High Efficiency)-----這種規(guī)格適合用于低碼率編碼畏腕,有Nero ACC 編碼器支持
目前使用最多的是LC和HE。其中LC-AAC用于中高碼率(>=80Kbps)茉稠,HE-AAC(LC + SBR技術(shù))主要用于中低碼(<=80Kbps)描馅,而新近推出的HE-AACv2(LC+SBR+PS)主要用于低碼率(<=48Kbps),事實(shí)上大部分編碼器設(shè)成<=48Kbps自動(dòng)啟用PS技術(shù),而>48Kbps就不加PS而线。流行的Nero AAC編碼程序只支持LC铭污,HE,HEv2這三種規(guī)格膀篮,編碼后的AAC音頻嘹狞,規(guī)格顯示都是LC。
<center><font color=gray size=2>圖中AAC即為AAC-LC誓竿,aacPlus v1磅网,v2分別代表Hev1和HEv2</font></center>
HE:“High Efficiency”(高效性)。HE-AAC v1(又稱AACPlusV1烤黍,SBR)知市,用容器的方法實(shí)現(xiàn)了AAC(LC)+SBR技術(shù)。SBR其實(shí)代表的是Spectral Band Replication(頻段復(fù)制)速蕊。簡(jiǎn)要敘述一下嫂丙,音樂的主要頻譜集中在低頻段,高頻段幅度很小规哲,但很重要跟啤,決定了音質(zhì)。如果對(duì)整個(gè)頻段編碼唉锌,若是為了保護(hù)高頻就會(huì)造成低頻段編碼過細(xì)以致文件巨大隅肥;若是保存了低頻的主要成分而失去高頻成分就會(huì)喪失音質(zhì)。SBR把頻譜切割開來袄简,低頻單獨(dú)編碼保存主要成分腥放,高頻單獨(dú)放大編碼保存音質(zhì),“統(tǒng)籌兼顧”了绿语,在減少文件大小的情況下還保存了音質(zhì)秃症,完美的化解這一矛盾。
HEv2:用容器的方法包含了HE-AAC v1和PS技術(shù)吕粹。PS指“parametric stereo”(參數(shù)立體聲)种柑。原來的立體聲文件文件大小是一個(gè)聲道的兩倍。但是兩個(gè)聲道的聲音存在某種相似性匹耕,根據(jù)香農(nóng)信息熵編碼定理聚请,相關(guān)性應(yīng)該被去掉才能減小文件大小。所以PS技術(shù)存儲(chǔ)了一個(gè)聲道的全部信息稳其,然后驶赏,花很少的字節(jié)用參數(shù)描述另一個(gè)聲道和它不同的地方炸卑。
2.AAC文件格式
AAC的音頻文件格式有ADIF和ADTS:
ADIF:Audio Data Interchange Format 音頻數(shù)據(jù)交換格式。這種格式的特征是可以確定的找到這個(gè)音頻數(shù)據(jù)的開始煤傍,不需進(jìn)行在音頻數(shù)據(jù)流中間開始的解碼矾兜,即它的解碼必須在明確定義的開始處進(jìn)行。故這種格式常用在磁盤文件中患久。
ADTS:Audio Data Transport Stream 音頻數(shù)據(jù)傳輸流椅寺。這種格式的特征是它是一個(gè)有同步字的比特流,解碼可以在這個(gè)流中任何位置開始蒋失。它的特征類似于mp3數(shù)據(jù)流格式返帕。
簡(jiǎn)單來說,ADIF只有一個(gè)統(tǒng)一的頭篙挽,所以必須得到所有的數(shù)據(jù)后解碼荆萤,ADTS每一幀都有頭信息,可以從任意幀開始解碼铣卡,因此網(wǎng)絡(luò)上的aac基本都是ADTS格式链韭。
關(guān)于ADTS格式的定義見ISO/IEC 13818文檔的Part 7,6.2節(jié):
2.1 adts_sequence
可以看出adts的aac流是一個(gè)個(gè)adts_frame組成序列
2.2 adts_frame
每個(gè)adts幀包含以下數(shù)據(jù)結(jié)構(gòu)adts_fixed_header煮落,adts_variable_header,raw_data_block序列
2.3 adts_fixed_header
字段定義在ISO/IEC 13818的Part 7敞峭,8.1.1.1節(jié)中,其中部分字段同MP3定義一樣蝉仇,見ISO/IEC 11172的Part 3旋讹,2.4.2.3節(jié):
syncword:12bit,所有位都位1轿衔,即'1111 1111 1111'
ID:1bit沉迹,始終為1。1 - MPEG audio害驹,0 - 保留
Layer:2bit鞭呕,始終為00。決定用那種layer協(xié)議.
- "11" Layer I
- "10" Layer II
- "01" Layer III
- "00" reserved
protection_bit:1bit,表示是否有crc校驗(yàn)宛官。1 - 無 0 - 有
profile:2bit,決定用哪種profile.
sampling_frequency_index:4bit葫松,采樣頻率index。
private_bit:1bit,bit for private use. This bit will not be used in the future by ISO
channel_configuration:3bit,聲道配置. 如果等于0摘刑,則聲道配置在第一個(gè)raw_data_block中通過調(diào)用program_config_element設(shè)置进宝;如果大于0刻坊,則參照下圖
original_copy:1bit, 0 - 無版權(quán)保護(hù) 1 - 有版權(quán)保護(hù)
home:1bit,表明當(dāng)前數(shù)據(jù)是拷貝流還是原始流枷恕。0 - 拷貝流 1 - 原始流
2.4 adts_variable_header
copyright_identification_bit:1bit,版權(quán)信息,暫不深究
copyright_identification_start:1bit谭胚,版權(quán)相關(guān)徐块,暫不深究
frame_length:13bit未玻,一個(gè)ADTS幀的字節(jié)數(shù),包含headers和error_check的長(zhǎng)度
adts_buffer_fullness:11bit, 如果值為7FF則表明當(dāng)前碼流的碼率是可變的
number_of_raw_data_blocks_in_frame:2bit,表示ADTS幀中有number_of_raw_data_blocks_in_frame + 1個(gè)AAC原始幀
2.5 raw_data_block
id_syn_ele:3bit胡控,元素類型id扳剿,定義如下圖:
SCE: Single Channel Element,單通道元素昼激。單通道元素基本上只由一個(gè)ICS組成庇绽。一個(gè)原始數(shù)據(jù)塊最可能由16個(gè)SCE組成。
CPE: Channel Pair Element橙困,雙通道元素瞧掺,由兩個(gè)可能共享邊信息的ICS和一些聯(lián)合立體聲編碼信息組成。
CCE: Coupling Channel Element凡傅,藕合通道元素辟狈。代表一個(gè)塊的多通道聯(lián)合立體聲信息或者多語種程序的對(duì)話信息。
LFE: Low Frequency Element夏跷,低頻元素哼转。包含了一個(gè)加強(qiáng)低采樣頻率的通道。
DSE: Data Stream Element槽华,數(shù)據(jù)流元素壹蔓,包含了一些并不屬于音頻的附加信息。
PCE: Program Config Element猫态,程序配置元素庶溶。包含了聲道的配置信息。它可能出現(xiàn)在ADIF 頭部信息中懂鸵。
FIL: Fill Element偏螺,填充元素。包含了一些擴(kuò)展信息匆光。如SBR套像,動(dòng)態(tài)范圍控制信息等。
后面就是元素內(nèi)容的具體分析了终息,由于涉及到音頻編碼的知識(shí)夺巩,比較復(fù)雜,這里不做討論周崭。
2.6 aac格式解析示例
用二進(jìn)制查看工具打開一個(gè)aac文件柳譬,如下圖:
按照上一節(jié)的aac格式來解析
字段名 | 占用bit數(shù) | 值 | 含義 |
---|---|---|---|
adts_fixed_header | - | 1111 1111 1111 1001 0101 0000 1000(0xFFF9508) | |
syncword | 12 | 0xFFF | 頭標(biāo)識(shí) |
ID | 1 | 1 | MPEG audio |
Layer | 2 | 00 | 保留字段 |
protection_bit | 1 | 1 | 無crc校驗(yàn) |
profile | 2 | 01 | 采用LC profile |
sampling_frequency_index | 4 | 0100 | 采樣率44100Hz |
private_bit | 1 | 0 | ISO保留字段 |
channel_configuration | 3 | 010 | 雙聲道 |
original_copy | 1 | 0 | 無版權(quán) |
home | 1 | 0 | 原始數(shù)據(jù) |
adts_variable_header | - | 0000 0010 1110 0111 1111 1111 1100(0x02E7FFC) | |
copyright_identification_bit | 1 | 0 | 版權(quán)信息 |
copyright_identification_start | 1 | 0 | 版權(quán)信息 |
frame_length | 13 | 00 0010 1110 011 | 幀長(zhǎng)度為371字節(jié) |
adts_buffer_fullness | 11 | 1 1111 1111 11 | 碼率可變 |
number_of_raw_data_blocks_in_frame | 2 | 00 | 有一個(gè)原始幀數(shù)據(jù) |
raw_data_block | - | 001 | |
id_syn_ele | 3 | 001 | 雙通道元素 |
channel_pair_element | ... | ... | ... |
... | ... | ... | ... |
第一幀長(zhǎng)度為371字節(jié),然后又開始下一幀(頭三個(gè)字節(jié)0xFFF)续镇,剛好對(duì)應(yīng)圖中劃紅線的地方美澳。
3.AAC編碼
AAC編碼流程在ISO/IEC 13818-7中制定:
其編碼流程概述如下: 當(dāng)音頻信號(hào)送至編碼端時(shí),會(huì)分別送至聽覺心里模型(Psychoacoustic Model)以求得編碼所需之相關(guān)參數(shù)及增益控制(gain control)模塊中,將信號(hào)做某個(gè)程度的衰減制跟,以降低其峰值大小舅桩,如此可減少Pre-echo 的發(fā)生。之后雨膨,再以MDCT 將時(shí)域信號(hào)轉(zhuǎn)換至頻率域擂涛,而送入至TNS(Temporal Noise Shaping Module)模塊中,來判斷是否需要啟動(dòng)TNS聊记,此模塊系利用開回路預(yù)測(cè)(open-loop prediction) 來修飾其量化噪聲撒妈,如此可將其量化噪聲的分布,修飾到原始信號(hào)能量所能含蓋的范圍之下排监,進(jìn)一步的減少Pre-echo 的發(fā)生踩身,若TNS 被啟動(dòng),則傳出其預(yù)測(cè)差值社露;反之挟阻,則傳出原始頻譜值。AAC 為了提升其壓縮效率峭弟,則使用了Joint Stereo Coding與預(yù)測(cè)(Prediction)模塊來進(jìn)一步消除信號(hào)間的冗余成份附鸽。在Joint Stereo Coding中又可分為Intensity Stereo Coding 與M/S Stereo Coding。在Intensity Stereo Coding模塊中瞒瘸,是利用信號(hào)在高頻時(shí)坷备,人耳只對(duì)能量較敏感,對(duì)于其相位不敏感之特性情臭,將其左右聲道之頻譜系數(shù)合并省撑,以節(jié)省使用之位;在M/S Stereo Coding 模塊中俯在,利用左右聲道之和與差竟秫,做進(jìn)一步地壓縮,若其差值能量很小跷乐,如此便可以用較少之位編碼此一聲道肥败,將剩余之位應(yīng)用于另一聲道上的編碼,如此來提升其壓縮率愕提。而預(yù)測(cè)模塊的主要架構(gòu)是使用Backward Adaptive Predictors馒稍,利用前兩個(gè)音頻幀來預(yù)測(cè)現(xiàn)在的音頻幀,若決定啟動(dòng)此模塊浅侨,則傳出其預(yù)測(cè)差值纽谒,如此一來可以減少其數(shù)據(jù)量,達(dá)數(shù)據(jù)壓縮之目的如输。經(jīng)過上述處理頻譜信號(hào)上的壓縮tools程序后鼓黔,則將其數(shù)據(jù)予以量化與編碼央勒,為了達(dá)到量化編碼的最佳化,AAC 使用了雙巢狀式循環(huán)(two nested loop)的量化編碼結(jié)構(gòu)请祖,以得最佳的壓縮質(zhì)量,最后則將其位串送至解碼端脖祈,而完成整個(gè)編碼程序肆捕。
AAC編碼的原理比較復(fù)雜,涉及信息編碼以及人耳的生理知識(shí)盖高,按照功能大致可以劃分為熵編碼慎陵,量化編碼,變換編碼喻奥,預(yù)測(cè)編碼席纽,音頻建模5大類,這里就不展開了撞蚕。
4.利用ffmpeg和fdk-aac將pcm編碼成aac格式
ffmpeg作為音視頻開發(fā)必不可少的工具润梯,這里就不做介紹了。至于如何在編譯環(huán)境搭建可以參考:
fdk-aac是一款開源的aac編解碼實(shí)現(xiàn)庫(kù)甥厦,源碼地址:https://github.com/mstorsjo/fdk-aac
下面我們實(shí)現(xiàn)一個(gè)在Android上將pcm文件轉(zhuǎn)成aac文件的功能纺铭。新建一個(gè)Android工程,導(dǎo)入ffmpeg和fdk-aac的so庫(kù)刀疙,工程配置這里就不講了舶赔,文章末尾有源碼地址。
JNI java接口
package me.huaisu.audio.encode;
public class AacEncoder {
static {
System.loadLibrary("fdk-aac");
System.loadLibrary("avcodec");
System.loadLibrary("avdevice");
System.loadLibrary("avfilter");
System.loadLibrary("avformat");
System.loadLibrary("avutil");
System.loadLibrary("swresample");
System.loadLibrary("swscale");
System.loadLibrary("aac_encoder");
}
public native int encodePcmFile(String pcmFile, String aacFile);
}
JNI c++實(shí)現(xiàn)
#include <jni.h>
#include <string>
#include "AACEncoder.h"
extern "C" JNIEXPORT jint JNICALL
Java_me_huaisu_audio_encode_AacEncoder_encodePcmFile(
JNIEnv* env,
jobject thiz,
jstring pcmFile,
jstring aacFile) {
AACEncoder* encoder = new AACEncoder();
const char* pcm_file = env->GetStringUTFChars(pcmFile, NULL);
if (pcm_file == NULL) {
return NULL;
}
const char* aac_file = env->GetStringUTFChars(aacFile, NULL);
if (aac_file == NULL) {
return NULL;
}
env->ReleaseStringUTFChars(pcmFile, pcm_file);
env->ReleaseStringUTFChars(aacFile, aac_file);
return encoder->encode(pcm_file, aac_file);
}
下面是具體的aac編碼實(shí)現(xiàn)
//
// Created by Administrator on 2020/2/23.
//
#ifndef ANDROID_LIVE_AACENCODER_H
#define ANDROID_LIVE_AACENCODER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
}
#endif
#include "AndroidLog.h"
class AACEncoder {
private:
uint8_t** src_data = NULL;//一幀的數(shù)據(jù)谦秧,是個(gè)二位數(shù)組
int src_linesize;
int src_bufsize;//一幀數(shù)據(jù)的長(zhǎng)度
AVFormatContext* pFormatContext;
AVStream* audioStream;
AVCodecParameters* param;
AVCodecContext* pCodecContext;
AVCodec* pCodec;
AVFrame* pFrame;
int frame_cnt = 0;
AVPacket *pkt;
int ret;
int initCodec();
int initAudioStream(const char* aac_file);
int initAudioFrame();
public:
AACEncoder();
~AACEncoder();
int encode(const char *pcm_file, const char *aac_file);
};
#endif //ANDROID_LIVE_AACENCODER_H
#include "AACEncoder.h"
AACEncoder::AACEncoder() {
}
AACEncoder::~AACEncoder() {
}
static void android_log_callback(void *ptr, int level, const char *fmt, va_list vl)
{
switch (level) {
case AV_LOG_VERBOSE:
LOGV(fmt, vl);
break;
case AV_LOG_DEBUG:
LOGD(fmt, vl);
break;
case AV_LOG_INFO:
LOGI(fmt, vl);
break;
case AV_LOG_WARNING:
LOGW(fmt, vl);
break;
case AV_LOG_ERROR:
LOGE(fmt, vl);
break;
}
}
int AACEncoder::initCodec() {
pCodec = avcodec_find_encoder_by_name("libfdk_aac");
if (!pCodec) {
LOGE("Codec not found\n");
return -1;
}
pCodecContext = avcodec_alloc_context3(pCodec);
if (!pCodecContext) {
LOGE("Codec context alloc fail\n");
return -1;
}
pCodecContext->codec_id = AV_CODEC_ID_AAC;
pCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
pCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecContext->sample_rate = 44100;
pCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
pCodecContext->channels = av_get_channel_layout_nb_channels(pCodecContext->channel_layout);
pCodecContext->bit_rate = 96000;
if (avcodec_open2(pCodecContext, pCodec, NULL) < 0) {
LOGE("Can't open codec\n");
return -1;
}
return 0;
}
int AACEncoder::initAudioStream(const char* aac_file) {
avformat_alloc_output_context2(&pFormatContext, NULL, NULL, aac_file);
if (avio_open(&pFormatContext->pb, aac_file, AVIO_FLAG_READ_WRITE) < 0) {
LOGE("Could't open output file\n");
return -1;
}
audioStream = avformat_new_stream(pFormatContext, pCodec);
if (audioStream == NULL) {
LOGE("Could't create stream\n");
return -1;
}
param = avcodec_parameters_alloc();
ret = avcodec_parameters_from_context(param, pCodecContext);
if (ret < 0) {
LOGE("create parameters fail\n");
return -1;
}
audioStream->codecpar = param;
return 0;
}
/**
* ffmpeg一幀有1024個(gè)采樣點(diǎn)竟纳,即pCodecContext->frame_size=1024
*
* 雙聲道,AV_SAMPLE_FMT_S16采樣格式的數(shù)據(jù)方式存儲(chǔ)如下:
* LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRR……
* 所有數(shù)據(jù)存在data[0]疚鲤,大小為1024 * 2(每個(gè)采樣點(diǎn)占2字節(jié)) * 2(雙聲道) =4096
*
* 雙聲道锥累,AV_SAMPLE_FMT_FLTP采樣格式的數(shù)據(jù)方式存儲(chǔ)如下:
* LLLLLLLLLLLLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRRRRRRRRRRRRRR……
* 左聲道數(shù)據(jù)存在data[0],大小為1024 * 4(每個(gè)采樣點(diǎn)占4字節(jié))=4096
* 右聲道數(shù)據(jù)存在data[1]集歇,大小為1024 * 4(每個(gè)采樣點(diǎn)占4字節(jié))=4096
*
* pFrame->linesize[0]揩悄,表示data[0]數(shù)組的長(zhǎng)度
* av_samples_get_buffer_size返回一幀的數(shù)據(jù)長(zhǎng)度:
* 雙聲道、AV_SAMPLE_FMT_S16長(zhǎng)度為4096
* 雙聲道鬼悠,AV_SAMPLE_FMT_FLTP長(zhǎng)度為8192
*/
int AACEncoder::initAudioFrame() {
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, pCodecContext->channels,
pCodecContext->frame_size, pCodecContext->sample_fmt, 0);
if (ret < 0) {
LOGE("Could not allocate source samples\n");
return -1;
}
src_bufsize = av_samples_get_buffer_size(&src_linesize, pCodecContext->channels,
pCodecContext->frame_size, pCodecContext->sample_fmt, 1);
pFrame = av_frame_alloc();
pFrame->nb_samples = pCodecContext->frame_size;
pFrame->format = pCodecContext->sample_fmt;
pFrame->channels = pCodecContext->channels;
pFrame->channel_layout = pCodecContext->channel_layout;
pFrame->linesize[0] = src_linesize;
pFrame->sample_rate = pCodecContext->sample_rate;
return 0;
}
int AACEncoder::encode(const char* pcm_file, const char* aac_file)
{
//打印ffmpeg系統(tǒng)日志删性,方便排查問題
av_log_set_level(AV_LOG_VERBOSE);
av_log_set_callback(android_log_callback);
// 初始化編碼器
if (initCodec() < 0) {
return -1;
}
// 創(chuàng)建AVStream
if (initAudioStream(aac_file) < 0) {
return -1;
}
// 寫入aac文件頭
avformat_write_header(pFormatContext, NULL);
// 初始化AVFrame,存放原始音頻數(shù)據(jù)
if (initAudioFrame() < 0) {
return -1;
}
// 初始化AVPacket焕窝,存放編碼后的aac數(shù)據(jù)
pkt = av_packet_alloc();
if (!pkt) {
LOGE("could not allocate the packet\n");
return -1;
}
FILE* fp_in = fopen(pcm_file, "rb");
if (!fp_in) {
LOGE("Can't open pcm input file\n");
return -1;
}
int pts = 0;
for (;;)
{
// 每次從pcm文件讀取一幀數(shù)據(jù)
if ((ret = fread(src_data[0], 1, src_bufsize, fp_in)) <= 0) {
LOGE("Fail to read buf from input file\n");
return -1;
}
else if (feof(fp_in)) {
LOGE("End of input file\n");
break;
}
// 設(shè)置當(dāng)前幀的顯示位置
pFrame->pts = pts;
pts++;
// 將讀到的幀數(shù)據(jù)賦值給AVFrame
pFrame->data[0] = src_data[0];
// 將AVFrame發(fā)送到編碼器進(jìn)行編碼
ret = avcodec_send_frame(pCodecContext, pFrame);
if (ret < 0) {
LOGE("Error sending the frame to the encoder\n");
return -1;
}
while (ret >= 0) {
// 得到編碼后的AVPacket
ret = avcodec_receive_packet(pCodecContext, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// LOGD("Error encoding audio frame %d\n", ret);
continue;
} else if (ret < 0) {
LOGE("Error encoding audio frame %d\n", ret);
return -1;
}
// LOGD("Success encode frame[%d] size:%d\n", frame_cnt, pkt->size);
frame_cnt++;
pkt->stream_index = audioStream->index;
// 將編碼后的AVPacket數(shù)據(jù)寫入aac文件
ret = av_interleaved_write_frame(pFormatContext, pkt);
av_packet_unref(pkt);
if (ret < 0) {
LOGE("Error write frame to output file,err code=%d", ret);
return -1;
}
}
}
//寫入文件尾
av_write_trailer(pFormatContext);
fclose(fp_in);
avcodec_close(pCodecContext);
av_free(pCodecContext);
av_free(&pFrame->data[0]);
av_frame_free(&pFrame);
return 0;
}
源碼地址:
Gitee:https://gitee.com/huaisu2020/Android-Live
Github:https://github.com/xh2009cn/Android-Live