FDK-AAC
wiki
git
https://github.com/mstorsjo/fdk-aac
文档
安装vs2015
下载地址
链接:https://pan.baidu.com/s/1z9up1TJ9HIfihrLtc6FeMw 密码:q8l2
安装
勾选Visual C++相关部分,其余操作按提示即可。
image.png
设置fdk-aac路径
-
将fdk-aac目录拷贝到工程目录下
- 配置头文件路径
右键aac-encode工程,选择属性
配置头文件路径 - 配置库文件路径 配置库文件路径
- 指明引用的lib文件名 指明引用的lib文件名
-
增加预处理命令
增加预处理命令_CRT_SECURE_NO_WARNINGS,否则在使用fopen等函数时报错 增加预处理命令_CRT_SECURE_NO_WARNINGS
源码
aac_encoder.h
#ifndef _AAC_ENCODER_H_
#define _AAC_ENCODER_H_
#include <stdint.h>
#include <aacenc_lib.h>
#include <FDK_audio.h>
#ifdef __cplusplus
extern "C"
{
#endif
// 对应
#define PROFILE_AAC_LC 2 // AOT_AAC_LC
#define PROFILE_AAC_HE 5 // AOT_SBR
#define PROFILE_AAC_HE_v2 29 // AOT_PS PS, Parametric Stereo (includes SBR)
#define PROFILE_AAC_LD 23 // AOT_ER_AAC_LD Error Resilient(ER) AAC LowDelay object
#define PROFILE_AAC_ELD 39 // AOT_ER_AAC_ELD AAC Enhanced Low Delay
typedef struct aac_encoder
{
HANDLE_AACENCODER handle; // fdk-aac
AACENC_InfoStruct info; // fdk-aac
int pcm_frame_len; // 每次送pcm的字节数
}AACEncoder;
/*
支持的采样率sample_rate:8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000
*/
AACEncoder *aac_encoder_init(const int sample_rate, const int channels, const int bit_rate, const int profile_aac);
int aac_encoder_encode(AACEncoder *handle, const int8_t *input, const int input_len, int8_t *output, int *output_len);
void aac_encoder_deinit(AACEncoder **handle);
#ifdef __cplusplus
}
#endif
#endif // !__AAC_ENCODER_H__
aac_encoder.c
#include "aac_encoder.h"
#include <stdlib.h>
#include <stdio.h>
static const char *fdkaac_error(AACENC_ERROR erraac)
{
switch (erraac)
{
case AACENC_OK: return "No error";
case AACENC_INVALID_HANDLE: return "Invalid handle";
case AACENC_MEMORY_ERROR: return "Memory allocation error";
case AACENC_UNSUPPORTED_PARAMETER: return "Unsupported parameter";
case AACENC_INVALID_CONFIG: return "Invalid config";
case AACENC_INIT_ERROR: return "Initialization error";
case AACENC_INIT_AAC_ERROR: return "AAC library initialization error";
case AACENC_INIT_SBR_ERROR: return "SBR library initialization error";
case AACENC_INIT_TP_ERROR: return "Transport library initialization error";
case AACENC_INIT_META_ERROR: return "Metadata library initialization error";
case AACENC_ENCODE_ERROR: return "Encoding error";
case AACENC_ENCODE_EOF: return "End of file";
default: return "Unknown error";
}
}
AACEncoder* aac_encoder_init(const int sample_rate, const int channels, const int bit_rate, const int profile_aac)
{
AACENC_ERROR ret;
AACEncoder *aac_enc_handle_ = NULL;
aac_enc_handle_ = (AACEncoder *)malloc(sizeof(AACEncoder));
if (!aac_enc_handle_)
{
printf("Can't malloc memory\n");
return NULL;
}
memset(aac_enc_handle_, 0, sizeof(AACEncoder));
// 打开编码器,如果非常需要节省内存则可以调整encModules
if ((ret = aacEncOpen(&aac_enc_handle_->handle, 0x0, channels)) != AACENC_OK)
{
printf("Unable to open fdkaac encoder, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
free(aac_enc_handle_);
return NULL;
}
// 设置AAC标准格式
if ((ret = aacEncoder_SetParam(aac_enc_handle_->handle, AACENC_AOT, profile_aac)) != AACENC_OK) /* aac lc */
{
printf("Unable to set the AACENC_AOT, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
goto faild;
}
// 设置采样率
if ((ret = aacEncoder_SetParam(aac_enc_handle_->handle, AACENC_SAMPLERATE, sample_rate)) != AACENC_OK)
{
printf("Unable to set the SAMPLERATE, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
goto faild;
}
// 设置通道数
if ((ret = aacEncoder_SetParam(aac_enc_handle_->handle, AACENC_CHANNELMODE, channels)) != AACENC_OK)
{
printf("Unable to set the AACENC_CHANNELMODE, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
goto faild;
}
// 设置比特率
if ((ret = aacEncoder_SetParam(aac_enc_handle_->handle, AACENC_BITRATE, bit_rate)) != AACENC_OK)
{
printf("Unable to set the AACENC_BITRATE, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
goto faild;
}
// 设置编码出来的数据带aac adts头
if ((ret = aacEncoder_SetParam(aac_enc_handle_->handle, AACENC_TRANSMUX, TT_MP4_ADTS)) != AACENC_OK) // 0-raw 2-adts
{
printf("Unable to set the ADTS AACENC_TRANSMUX, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
goto faild;
}
// 初始化编码器
if ((ret = aacEncEncode(aac_enc_handle_->handle, NULL, NULL, NULL, NULL)) != AACENC_OK)
{
printf("Unable to initialize the aacEncEncode, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
goto faild;
}
// 获取编码器信息
if ((ret = aacEncInfo(aac_enc_handle_->handle, &aac_enc_handle_->info)) != AACENC_OK)
{
printf("Unable to get the aacEncInfo info, ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
goto faild;
}
// 计算pcm帧长
aac_enc_handle_->pcm_frame_len = aac_enc_handle_->info.inputChannels * aac_enc_handle_->info.frameLength * 2;
printf("AACEncoderInit channels = %d, pcm_frame_len = %d\n",
aac_enc_handle_->info.inputChannels, aac_enc_handle_->pcm_frame_len);
return aac_enc_handle_;
faild:
if (aac_enc_handle_ && aac_enc_handle_->handle)
{
if (aacEncClose(&aac_enc_handle_->handle) != AACENC_OK)
{
printf("aacEncClose failed\n");
}
}
if (aac_enc_handle_)
free(aac_enc_handle_);
return NULL;
}
int aac_encoder_encode(AACEncoder *handle, const int8_t *input, const int input_len, int8_t *output, int *output_len)
{
AACENC_ERROR ret;
if (!handle)
{
return AACENC_INVALID_HANDLE;
}
if (input_len != handle->pcm_frame_len)
{
printf("input_len = %d no equal to need length = %d\n", input_len, handle->pcm_frame_len);
return AACENC_UNSUPPORTED_PARAMETER; // 每次都按帧长的数据进行编码
}
AACENC_BufDesc out_buf = { 0 };
AACENC_InArgs in_args = { 0 };
// pcm数据输入配置
in_args.numInSamples = input_len/2; // 所有通道的加起来的采样点数,每个采样点是2个字节所以/2
// pcm数据输入配置
int in_identifier = IN_AUDIO_DATA;
int in_elem_size = 2;
void *in_ptr = input;
int in_buffer_size = input_len;
AACENC_BufDesc in_buf = { 0 };
in_buf.numBufs = 1;
in_buf.bufs = &in_ptr;
in_buf.bufferIdentifiers = &in_identifier;
in_buf.bufSizes = &in_buffer_size;
in_buf.bufElSizes = &in_elem_size;
// 编码数据输出配置
int out_identifier = OUT_BITSTREAM_DATA;
int out_elem_size = 1;
void *out_ptr = output;
int out_buffer_size = *output_len;
out_buf.numBufs = 1;
out_buf.bufs = &out_ptr;
out_buf.bufferIdentifiers = &out_identifier;
out_buf.bufSizes = &out_buffer_size; //一定要可以接收解码后的数据
out_buf.bufElSizes = &out_elem_size;
AACENC_OutArgs out_args = { 0 };
if ((ret = aacEncEncode(handle->handle, &in_buf, &out_buf, &in_args, &out_args)) != AACENC_OK)
{
printf("aacEncEncode ret = 0x%x, error is %s\n", ret, fdkaac_error(ret));
return ret;
}
*output_len = out_args.numOutBytes;
return AACENC_OK;
}
void aac_encoder_deinit(AACEncoder **handle)
{
// 先关闭编码器
if (aacEncClose(&(*handle)->handle) != AACENC_OK)
{
printf("aacEncClose failed\n");
}
// 释放内存
free(*handle);
// 将handle指向NULL
*handle = NULL;
}
/**
* 添加ADTS头部 这里只是为了以后rtmp拉流时存储aac文件做准备
*
* @param packet ADTS header 的 byte[],长度为7
* @param packetLen 该帧的长度,包括header的长度
* @param profile 0-Main profile, 1-AAC LC,2-SSR
* @param freqIdx 采样率
0: 96000 Hz
1: 88200 Hz
2: 64000 Hz
3: 48000 Hz
4: 44100 Hz
5: 32000 Hz
6: 24000 Hz
7: 22050 Hz
8: 16000 Hz
9: 12000 Hz
10: 11025 Hz
11: 8000 Hz
12: 7350 Hz
13: Reserved
14: Reserved
15: frequency is written explictly
* @param chanCfg 通道
2:L+R
3:C+L+R
*/
void add_adts_to_packet(int8_t *packet, int packetLen, int profile, int freqIdx, int chanCfg)
{
/*
int profile = 2; // AAC LC
int freqIdx = 3; // 48000Hz
int chanCfg = 2; // 2 Channel
*/
packet[0] = 0xFF;
packet[1] = 0xF9;
packet[2] = (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
packet[3] = (((chanCfg & 3) << 6) + (packetLen >> 11));
packet[4] = ((packetLen & 0x7FF) >> 3);
packet[5] = (((packetLen & 7) << 5) + 0x1F);
packet[6] = 0xFC;
}
aac-test.c
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include "aac_encoder.h"
#define PCM_FILE_NAME "buweishui_48000_2_s16le.pcm"
#define AAC_FILE_NAME "buweishui_48000_2_s16le_AAC_HE_128k.aac"
/**
* @brief get_millisecond
* @return 返回毫秒
*/
int64_t get_current_time_msec()
{
#ifdef _WIN32
return (int64_t)GetTickCount();
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return ((int64_t)tv.tv_sec * 1000 + (unsigned long long)tv.tv_usec / 1000);
#endif
}
int get_file_len(const char *file_nmae)
{
FILE * file;
long size;
file = fopen(file_nmae, "rb");
if (file == NULL)
perror("Error opening file");
else
{
fseek(file, 0, SEEK_END); //将文件指针移动文件结尾
size = ftell(file); ///求出当前文件指针距离文件开始的字节数
fclose(file);
}
return (int)size;
}
int main(void)
{
printf("hello aac\n");
AACEncoder *aac_enc_handle = NULL;
aac_enc_handle = aac_encoder_init(48000, MODE_2, 128000, PROFILE_AAC_HE);
if (!aac_enc_handle)
{
printf("aac_encoder_init failed");
system("pause");
exit(-1);
}
// pcm buffer缓冲区, 送s16 交错格式 LRLRLR
int pcm_len = aac_enc_handle->pcm_frame_len;
int8_t *pcm_buf = (int8_t *)malloc(pcm_len); // XXX个采样点,2字节一个采样点,2通道
int read_len = 0; // 每次读取回来的数据长度
// 编码后的数据
int frame_max_len = 1024 *8; // 2的13次方
int8_t *frame_buf = (int8_t *)malloc(frame_max_len);
int frame_len = frame_max_len;
// 统计编码进度(百分比)
int read_total_len = 0;
int read_count = 0;
float enc_percent = 0.0;
int file_total_len = get_file_len(PCM_FILE_NAME);
if (file_total_len <= 0)
{
printf("get_file_len failed\n");
system("pause");
return -1;
}
// 打开一个pcm文件,读取知道结束
FILE *file_pcm = fopen(PCM_FILE_NAME, "rb");
FILE *file_aac = fopen(AAC_FILE_NAME, "wb+");
// 计算编码时间
int64_t start_time = get_current_time_msec();
int64_t cur_time = 0;
while (feof(file_pcm) == 0)
{
// PCM格式要满足 S16 LRLR...LRLR 的格式
read_len = fread(pcm_buf, 1, pcm_len, file_pcm);
read_total_len += read_len;
if (read_len <= 0)
{
printf("fread failed\n");
break;
}
if (read_len < pcm_len)
{
printf("剩余数据不足一帧,用0补足一帧数据\n");
memset(pcm_buf + read_len, 0, pcm_len - read_len);
}
// 编码
int temp_len = 0;
frame_len = frame_max_len; // frame_len在输入的时候代表着frame_buf的最大存储空间
int ret = aac_encoder_encode(aac_enc_handle, pcm_buf, pcm_len, frame_buf, &frame_len);
if (ret != 0)
{
printf("aac_encoder_encode failed, ret = 0x%x\n", ret);
break;
}
// 写入文件
fwrite(frame_buf, 1, frame_len, file_aac);
if (frame_len > temp_len)
temp_len = frame_len;
if (++read_count % 100 == 0)
{
cur_time = get_current_time_msec();
enc_percent = (float)(1.0*read_total_len / file_total_len * 100);
printf("encode progress = %0.2f%%, elapsed time = %lld, temp_len = %d\n",
enc_percent, cur_time- start_time, temp_len);
}
/*
if (read_total_len > file_total_len / 2)
{
break;
}
*/
}
cur_time = get_current_time_msec();
printf("encode finish, elapsed time = %lld\n", cur_time - start_time);
if(file_aac)
fclose(file_aac);
if(file_pcm)
fclose(file_pcm);
aac_encoder_deinit(&aac_enc_handle);
free(frame_buf);
free(pcm_buf);
system("pause");
return 0;
}
测试结果
编码所用PCM文件时长5分12秒
AAC规格 | 编码码率 | 耗时(秒) | 文件大小(字节) |
---|---|---|---|
AAC-LC | 128Kbps | 33.812秒 | 4890KB |
AAC-HE | 128Kbps | 82.578秒 | 4890KB |
AAC-HE-v2 | 128Kbps | 41.469秒 | 2445KB |
这里就有个疑问了,为什么AAC-HE-V2编码耗时比AAC-HE还低?
我们先了解以下的内容(重点了解PS技术):
-
LC为低复杂度的,适合中等码流,也就是96kpbs-192kbps,据说,在此码流下,LC-AAC 可以完全打败同码率的用LAME最高质量慢速编码模式的MP3。
- HE主要是为了低码率,在小于36kbps的情况下,能达到比其他编码器都要优秀的音质。HE有两个版本,一个是V1,也就是aac+,包含LC+SBR;另外一个是V2,也就是he aac v2,或者叫he aac plus,或者叫和enhanced aac plus,包含LC+SBR+PS。 三者之间的关系
-
SBR其实代表的是Spectral Band Replication(频段复制)。
简要叙述一下,音乐的主要频谱集中在低频段,高频段幅度很小,但很重要,决定了音质。
如果对整个频段编码,若是为了 保护高频就会造成低频段编码过细以致文件巨大;
若是保存了低频的主要成分而失去高频成分就会丧失音质。SBR把频谱切割开来,低频单独编码保存主要成分, 高频单独放大编码保存音质,“统筹兼顾”了,在减少文件大小的情况下还保存了音质,完美的化解这一矛盾。 -
PS指“parametric stereo”(参数立体声)
原来的立体声文件文件大小是一个声道的两倍。但是两个声道的声音存在某种相似性,根据香农信息熵编码定理,相关性应该被去 掉才能减小文件大小。所以PS技术存储了一个声道的全部信息,然后,花很少的字节用参数描述另一个声道和它不同的地方。 (这也解析了为什么其编码后的文件相对AAC-LC和AAC-HE缩小了一半)
网友评论