VS2015+FDK-AAC编码测试

FDK-AAC

wiki

Fraunhofer_FDK_AAC

git

https://github.com/mstorsjo/fdk-aac

文档

aacEncoder.pdf
aacDecoder.pdf

安装vs2015

下载地址

链接:https://pan.baidu.com/s/1z9up1TJ9HIfihrLtc6FeMw 密码:q8l2

安装

勾选Visual C++相关部分,其余操作按提示即可。


image.png

设置fdk-aac路径

  1. 将fdk-aac目录拷贝到工程目录下


  2. 配置头文件路径
    右键aac-encode工程,选择属性
    配置头文件路径
  3. 配置库文件路径
    配置库文件路径
  4. 指明引用的lib文件名
    指明引用的lib文件名
  5. 增加预处理命令

    增加预处理命令_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技术):

  1. LC为低复杂度的,适合中等码流,也就是96kpbs-192kbps,据说,在此码流下,LC-AAC 可以完全打败同码率的用LAME最高质量慢速编码模式的MP3。

  2. HE主要是为了低码率,在小于36kbps的情况下,能达到比其他编码器都要优秀的音质。HE有两个版本,一个是V1,也就是aac+,包含LC+SBR;另外一个是V2,也就是he aac v2,或者叫he aac plus,或者叫和enhanced aac plus,包含LC+SBR+PS。
    三者之间的关系
  3. SBR其实代表的是Spectral Band Replication(频段复制)。
    简要叙述一下,音乐的主要频谱集中在低频段,高频段幅度很小,但很重要,决定了音质。
    如果对整个频段编码,若是为了 保护高频就会造成低频段编码过细以致文件巨大;
    若是保存了低频的主要成分而失去高频成分就会丧失音质。SBR把频谱切割开来,低频单独编码保存主要成分, 高频单独放大编码保存音质,“统筹兼顾”了,在减少文件大小的情况下还保存了音质,完美的化解这一矛盾。

  4. PS指“parametric stereo”(参数立体声)
    原来的立体声文件文件大小是一个声道的两倍。但是两个声道的声音存在某种相似性,根据香农信息熵编码定理,相关性应该被去 掉才能减小文件大小。所以PS技术存储了一个声道的全部信息,然后,花很少的字节用参数描述另一个声道和它不同的地方。 (这也解析了为什么其编码后的文件相对AAC-LC和AAC-HE缩小了一半)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,809评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,189评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,290评论 0 359
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,399评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,425评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,116评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,710评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,629评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,155评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,261评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,399评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,068评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,758评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,252评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,381评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,747评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,402评论 2 358

推荐阅读更多精彩内容

  • AAC介绍 介绍AAC,全称Advanced Audio Coding,是一种专为声音数据设计的文件压缩格式。他的...
    请叫我果爸阅读 10,478评论 3 9
  • 介绍 AAC(Advanced Audio Coding),中文称为“高级音频编码”,出现于1997年,基于 MP...
    sxyxsp123阅读 7,298评论 0 7
  • 又逢佳节菊花黄, 雁阵声声送酒香。 每忆儿时慈父面, 最为心痛醉重阳。
    贺兰月儿阅读 261评论 0 6
  • 如果说还有一次机会我选择的是离开,现在的生活带给我只有无尽的痛苦,身在广州的你却读不懂一个男人的心,如果哪一天我真...
    vamuuu阅读 195评论 0 1