美文网首页
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编码测试

    FDK-AAC wiki Fraunhofer_FDK_AAC git https://github.com/ms...

  • Spring AOP简化笔记--泛型中的应用(三)

    目录 文件目录 编码 测试 测试结果 说明 文件目录 编码 config.ConcertConfig entity...

  • 浅谈TDD

    JUnit促进“先测试再编码”,它强调建立测试数据的一段代码可以被测试,先测试再编码实现的想法。这种做法就像是“试...

  • 测试驱动开发的简单理解

    TDDTest Driven Development 测试驱动开发 大致思想是:在编码之前,先写测试代码,测试代码...

  • 软件生命周期有哪些

    计划——需求分析——设计——编码——测试——运行——评价

  • EAN条形码编码器

    提供EAN-13编码和EAN-8编码两种接口。EAN.h: EAN.c 测试程序test.c 测试程序运行效果: ...

  • 2-3 ES实现高亮

    编码 测试 页面处理 v-html 进行解析

  • 测试

    TDD:测试驱动编程,编程方法学,编程思想 先写测试用例,再编码。 单元测试、压力测试、疲劳强度测试(长期稳定运行...

  • JMeter 接口测试解决响应数据中文乱码方法

    产生原因: Jmeter的结果处理编码与被测试对象的编码不一致。Jmeter的sampler请求结果的默认编码方式...

  • 十九、测试环境

    测试环境 -从软件的编码、测试到用户实际使用,存在着:开发环境、测试环境和用户环境。 - “环境”,指的是被测试软...

网友评论

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

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