VS2015+FDK-AAC編碼測(cè)試

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++相關(guān)部分,其余操作按提示即可。


image.png

設(shè)置fdk-aac路徑

  1. 將fdk-aac目錄拷貝到工程目錄下


  2. 配置頭文件路徑
    右鍵aac-encode工程,選擇屬性
    配置頭文件路徑
  3. 配置庫(kù)文件路徑
    配置庫(kù)文件路徑
  4. 指明引用的lib文件名
    指明引用的lib文件名
  5. 增加預(yù)處理命令

    增加預(yù)處理命令_CRT_SECURE_NO_WARNINGS,否則在使用fopen等函數(shù)時(shí)報(bào)錯(cuò)
    增加預(yù)處理命令_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
// 對(duì)應(yīng)
#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的字節(jié)數(shù)
}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));

    // 打開編碼器,如果非常需要節(jié)省內(nèi)存則可以調(diào)整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;
    }

    // 設(shè)置AAC標(biāo)準(zhǔn)格式
    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;
    }
    // 設(shè)置采樣率
    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;
    }
    // 設(shè)置通道數(shù)
    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;
    }
    // 設(shè)置比特率
    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;
    }
    // 設(shè)置編碼出來(lái)的數(shù)據(jù)帶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;
    }

    // 計(jì)算pcm幀長(zhǎng)
    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;            // 每次都按幀長(zhǎng)的數(shù)據(jù)進(jìn)行編碼
    }
    
    AACENC_BufDesc  out_buf = { 0 };
    AACENC_InArgs   in_args = { 0 };
    
    // pcm數(shù)據(jù)輸入配置
    in_args.numInSamples = input_len/2; // 所有通道的加起來(lái)的采樣點(diǎn)數(shù),每個(gè)采樣點(diǎn)是2個(gè)字節(jié)所以/2

    // pcm數(shù)據(jù)輸入配置
    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;

    // 編碼數(shù)據(jù)輸出配置
    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;        //一定要可以接收解碼后的數(shù)據(jù)
    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)
{
    // 先關(guān)閉編碼器
    if (aacEncClose(&(*handle)->handle) != AACENC_OK)
    {
        printf("aacEncClose failed\n");
    }
    // 釋放內(nèi)存
    free(*handle);  
    // 將handle指向NULL
    *handle = NULL; 
}

/**
* 添加ADTS頭部 這里只是為了以后rtmp拉流時(shí)存儲(chǔ)aac文件做準(zhǔn)備
*
* @param packet    ADTS header 的 byte[],長(zhǎng)度為7
* @param packetLen 該幀的長(zhǎng)度,包括header的長(zhǎng)度
* @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);    //將文件指針移動(dòng)文件結(jié)尾
        size = ftell(file);             ///求出當(dāng)前文件指針距離文件開始的字節(jié)數(shù)
        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緩沖區(qū), 送s16 交錯(cuò)格式 LRLRLR
    int     pcm_len = aac_enc_handle->pcm_frame_len;
    int8_t *pcm_buf = (int8_t *)malloc(pcm_len);    // XXX個(gè)采樣點(diǎn),2字節(jié)一個(gè)采樣點(diǎn),2通道
    int     read_len = 0;   // 每次讀取回來(lái)的數(shù)據(jù)長(zhǎng)度

    // 編碼后的數(shù)據(jù)
    int     frame_max_len = 1024 *8;        // 2的13次方
    int8_t *frame_buf = (int8_t *)malloc(frame_max_len);
    int     frame_len = frame_max_len;

    // 統(tǒng)計(jì)編碼進(jìn)度(百分比)
    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;
    }

    // 打開一個(gè)pcm文件,讀取知道結(jié)束
    FILE *file_pcm = fopen(PCM_FILE_NAME, "rb");
    FILE *file_aac = fopen(AAC_FILE_NAME, "wb+");

    // 計(jì)算編碼時(shí)間
    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("剩余數(shù)據(jù)不足一幀,用0補(bǔ)足一幀數(shù)據(jù)\n");
            memset(pcm_buf + read_len, 0, pcm_len - read_len);
        }
        // 編碼
        int temp_len = 0;
        frame_len = frame_max_len;          // frame_len在輸入的時(shí)候代表著frame_buf的最大存儲(chǔ)空間
        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;
}

測(cè)試結(jié)果

編碼所用PCM文件時(shí)長(zhǎng)5分12秒

AAC規(guī)格 編碼碼率 耗時(shí)(秒) 文件大小(字節(jié))
AAC-LC 128Kbps 33.812秒 4890KB
AAC-HE 128Kbps 82.578秒 4890KB
AAC-HE-v2 128Kbps 41.469秒 2445KB

這里就有個(gè)疑問了,為什么AAC-HE-V2編碼耗時(shí)比AAC-HE還低?

我們先了解以下的內(nèi)容(重點(diǎn)了解PS技術(shù)):

  1. LC為低復(fù)雜度的,適合中等碼流,也就是96kpbs-192kbps,據(jù)說(shuō),在此碼流下,LC-AAC 可以完全打敗同碼率的用LAME最高質(zhì)量慢速編碼模式的MP3。

  2. HE主要是為了低碼率,在小于36kbps的情況下,能達(dá)到比其他編碼器都要優(yōu)秀的音質(zhì)。HE有兩個(gè)版本,一個(gè)是V1,也就是aac+,包含LC+SBR;另外一個(gè)是V2,也就是he aac v2,或者叫he aac plus,或者叫和enhanced aac plus,包含LC+SBR+PS。
    三者之間的關(guān)系
  3. SBR其實(shí)代表的是Spectral Band Replication(頻段復(fù)制)。
    簡(jiǎn)要敘述一下,音樂的主要頻譜集中在低頻段,高頻段幅度很小,但很重要,決定了音質(zhì)。
    如果對(duì)整個(gè)頻段編碼,若是為了 保護(hù)高頻就會(huì)造成低頻段編碼過細(xì)以致文件巨大;
    若是保存了低頻的主要成分而失去高頻成分就會(huì)喪失音質(zhì)。SBR把頻譜切割開來(lái),低頻單獨(dú)編碼保存主要成分, 高頻單獨(dú)放大編碼保存音質(zhì),“統(tǒng)籌兼顧”了,在減少文件大小的情況下還保存了音質(zhì),完美的化解這一矛盾。

  4. PS指“parametric stereo”(參數(shù)立體聲)
    原來(lái)的立體聲文件文件大小是一個(gè)聲道的兩倍。但是兩個(gè)聲道的聲音存在某種相似性,根據(jù)香農(nóng)信息熵編碼定理,相關(guān)性應(yīng)該被去 掉才能減小文件大小。所以PS技術(shù)存儲(chǔ)了一個(gè)聲道的全部信息,然后,花很少的字節(jié)用參數(shù)描述另一個(gè)聲道和它不同的地方。 (這也解析了為什么其編碼后的文件相對(duì)AAC-LC和AAC-HE縮小了一半)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • AAC介紹 介紹AAC,全稱Advanced Audio Coding,是一種專為聲音數(shù)據(jù)設(shè)計(jì)的文件壓縮格式。他的...
    請(qǐng)叫我果爸閱讀 10,656評(píng)論 3 9
  • 介紹 AAC(Advanced Audio Coding),中文稱為“高級(jí)音頻編碼”,出現(xiàn)于1997年,基于 MP...
    sxyxsp123閱讀 7,556評(píng)論 0 7
  • 又逢佳節(jié)菊花黃, 雁陣聲聲送酒香。 每憶兒時(shí)慈父面, 最為心痛醉重陽(yáng)。
    賀蘭月兒閱讀 267評(píng)論 0 6
  • 如果說(shuō)還有一次機(jī)會(huì)我選擇的是離開,現(xiàn)在的生活帶給我只有無(wú)盡的痛苦,身在廣州的你卻讀不懂一個(gè)男人的心,如果哪一天我真...
    vamuuu閱讀 212評(píng)論 0 1