美文网首页
VS2015+FDK-AAC编码测试

VS2015+FDK-AAC编码测试

作者: C_GO流媒体后台开发 | 来源:发表于2018-10-23 23:10 被阅读68次

    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缩小了一半)

    相关文章

      网友评论

          本文标题:VS2015+FDK-AAC编码测试

          本文链接:https://www.haomeiwen.com/subject/lbuzzftx.html