当前位置: 首页 > 工具软件 > faac > 使用案例 >

使用faac库编码无损音频pcm为aac

万俟高峻
2023-12-01

编码无损音频pcm为aac(使用faac库)

1.使用faac库

使用faac库编码无损音频pcm为aac时,主要使用以下几个函数:

faacEncHandle faacEncOpen(unsigned long sampleRate,
				  unsigned int numChannels,
				  unsigned long *inputSamples,
                  unsigned long *maxOutputBytes);
 
faacEncConfigurationPtr faacEncGetCurrentConfiguration(faacEncHandle hEncoder);
 
int faacEncSetConfiguration(faacEncHandle hEncoder,
				    faacEncConfigurationPtr config);
 
int faacEncEncode(faacEncHandle hEncoder, 
             int32_t * inputBuffer, 
             unsigned int samplesInput,
			 unsigned char *outputBuffer,
			 unsigned int bufferSize);
 
int faacEncClose(faacEncHandle hEncoder);

我们从上往下进行介绍:

1.1 打开和关闭handle

typedef void *faacEncHandle;
 
/*
 *  @brief  打开一个用于aac编码的handle
 *  @param  sampleRate[in] 音频采样率
 *          numChannels[in] 音频通道数量(一般是1或2,表示单通道和立体声)
 *          inputSamples[out] aac编码器一次需要输入的音频采样
 *          maxOutputBytes[out] 编码后aac数据的最大长度
 *  @return 失败返回NULL
 */
faacEncHandle faacEncOpen(unsigned long sampleRate,
				  unsigned int numChannels,
				  unsigned long *inputSamples,
                  unsigned long *maxOutputBytes);
 
 
//关闭一个用于aac编码的handle
int faacEncClose(faacEncHandle hEncoder);

sampleRate、numChannels是输入参数,需要在打开handle时告诉编码器,等会儿需要编码的pcm音频的采样率和声道(音频通道)是多少。

inputSamples、maxOutputBytes是输出参数,需要传int类型的地址(不能为nullptr),当调用faacEncOpen成功后,返回用于编码的句柄,并且填充inputSamples、maxOutputBytes两个参数,告诉用户等会儿编码时,每次需要输入的音频采样数量,以及编码成aac数据的最大数据长度。用户需要提前分配好至少maxOutputBytes大小的空间用于存储编码后aac音频数据。

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

1.2 获取、设置编码参数

/* MPEG ID's */
#define MPEG2 1
#define MPEG4 0
 
/* AAC object types */
#define MAIN 1
#define LOW  2
#define SSR  3
#define LTP  4
 
/* Input Formats */
#define FAAC_INPUT_NULL    0
#define FAAC_INPUT_16BIT   1
#define FAAC_INPUT_24BIT   2
#define FAAC_INPUT_32BIT   3
#define FAAC_INPUT_FLOAT   4
 
#pragma pack(push, 1)
typedef struct faacEncConfiguration
{
    /* config version */
    int version;
 
    /* library version */
    char *name;
 
    /* copyright string */
    char *copyright;
 
    /* MPEG version, 2 or 4 */
    /* mpeg的版本号,MPEG2 or MPEG2 */
    unsigned int mpegVersion;
 
    /* AAC object type */
    /* AAC规格 MAIN、LOW、SSR、LTP */
    unsigned int aacObjectType;
 
    union {
        /* Joint coding mode */
        unsigned int jointmode;
        /* compatibility alias */
        unsigned int allowMidside;
    };
 
    /* Use one of the channels as LFE channel */
    /*
     * 低频音效加强通道LFE (low frequency effects)
     * 低频效果通道是一个有限带宽的音轨,用于再现3–120 Hz频率范围内深沉而强烈的低频声音。
     * 此轨道通常会发送到超低音扬声器,即用于重现极低频率的扬声器。
     */
    unsigned int useLfe;
 
    /* Use Temporal Noise Shaping */
    /*
     * 瞬时噪声定形TNS
     * 
     * TNS是用来消除由激发讯号所造成pre-echo现象的一个模组,
     * 它可以控制量化误差并且将误差塑形在遮避能力较大的激发讯号里,进而改进音乐品质。
     * 
     * TNS 是感官式音訊編碼(Perceptual Audio Coding) 中的一個新的觀念,
     * 目的在於使單一區塊在經過時間 -頻率轉換、量化及編碼之後,
     * 音訊在區塊內時間上的噪音遮蔽效果仍然能夠維持。
     */
    unsigned int useTns;
 
    /* bitrate / channel of AAC file */
    /* 每个通道的码率(比特率) */
    unsigned long bitRate;
 
    /* AAC file frequency bandwidth */
    unsigned int bandWidth;
 
    /* Quantizer quality */
    /* 默认100, 值越大音质越好 */
    unsigned long quantqual;
 
    /* Bitstream output format (0 = Raw; 1 = ADTS) */
    /* 是纯AAC音频数据,还是带了ADTS头的AAC(一般使用ADTS)*/
    unsigned int outputFormat;
 
    /* psychoacoustic model list */
    psymodellist_t *psymodellist;
 
    /* selected index in psymodellist */
    unsigned int psymodelidx;
 
    /*
		PCM Sample Input Format
		0	FAAC_INPUT_NULL			invalid, signifies a misconfigured config
		1	FAAC_INPUT_16BIT		native endian 16bit
		2	FAAC_INPUT_24BIT		native endian 24bit in 24 bits		(not implemented)
		3	FAAC_INPUT_32BIT		native endian 24bit in 32 bits		(DEFAULT)
		4	FAAC_INPUT_FLOAT		32bit floating point
    */
    /* 
     * 输入音频的位深度,也叫位宽,量化精度。
     * 位数越多,表示得就越精细,声音质量自然就越好,当然,数据量也会成倍增大。
     * 16bit被认为是专业音频领域里面最低的位深度标准。
     */
    unsigned int inputFormat;
 
    /* block type enforcing (SHORTCTL_NORMAL/SHORTCTL_NOSHORT/SHORTCTL_NOLONG) */
    int shortctl;
	
	/*
		Channel Remapping
		Default			0, 1, 2, 3 ... 63  (64 is MAX_CHANNELS in coder.h)
		WAVE 4.0		2, 0, 1, 3
		WAVE 5.0		2, 0, 1, 3, 4
		WAVE 5.1		2, 0, 1, 4, 5, 3
		AIFF 5.1		2, 0, 3, 1, 4, 5 
	*/
    int channel_map[64];
    int pnslevel;
} faacEncConfiguration, *faacEncConfigurationPtr;
 
/* 
 * 获取编码配置的结构体指针 
 * 在调用设置编码参数之前,需要先调用该函数获取结构体指针,再进行参数填充
 */
faacEncConfigurationPtr faacEncGetCurrentConfiguration(faacEncHandle hEncoder);
 
/* 
 * 设置编码参数
 * 在调用编码函数之前,需要调用该函数设置编码参数
 */
int faacEncSetConfiguration(faacEncHandle hEncoder,
				    faacEncConfigurationPtr config);

        其中faacEncConfigurationPtr 是编码参数的结构体指针。经常使用的参数已经在上面的代码中用中文进行了注释,inputFormat、outputFormat、aacObjectType、mpegVersion、(bitRate一般编码器会自动设置成64kpbs,即每个通道的码率为64kpbs)、(useTns、useLfe一般设置为0,即不使用)

1.3编码无损音频pcm为aac

/*
 *  @brief  编码pcm音频为aac音频
 *  @param  hEncoder[in] 调用faacEncOpen函数成功后获取的句柄
 *          inputBuffer[in] pcm音频数据
 *          samplesInput[in] 通过faacEncOpen得到的“每次需要输入的音频采样数量”
 *          outputBuffer[out] 用于存储编码后数据空间的首地址(需要提前分配好空间)
 *          bufferSize[out] 编码后aac数据的最大长度
 *  @return 返回编码后aac实际数据大小
 */
int faacEncEncode(faacEncHandle hEncoder, 
             int32_t * inputBuffer, 
             unsigned int samplesInput,
			 unsigned char *outputBuffer,
			 unsigned int bufferSize);

samplesInput是指音频采样数量,而非pcm音频数据的大小!是通过调用faacEncOpen后得到的。输入的pcm音频数据的大小 = samplesInput * 音频位深 / 8;

outputBuffer是提前分配好的空间,存储编码后的aac数据,至少需要分配的大小 = 通过调用faacEncOpen后得到的maxOutputBytes。

2.总结

1. 调用faacEncOpen打开一个用于编码pcm为aac的句柄,需要传入采样率、声道数。成功返回编码句柄,并且得到每次需要输入的采样数量inputSamples,和编码后aac数据的最大数据大小maxOutputBytes。

2. 通过得到的maxOutputBytes分配空间,用于之后存储aac数据。

3. 调用faacEncGetCurrentConfiguration获取编码参数的结构体指针,并填充编码参数,主要填充inputFormat、outputFormat、aacObjectType、mpegVersion等参数;useTns、useLfe通常不使用,设置为0; bitRate是每个声道的码率,而非总码率,一般会自动填充好,也可以手动赋值。

4. 调用faacEncSetConfiguration设置编码参数。

5. 调用faacEncEncode进行音频编码,并把编码后的数据保存下来。(正常情况下前面几帧会返回0,即没有aac数据,属于正常现象)。

6. 编码结束后,调用faacEncClose关闭句柄。

3.代码展示

PcmToAac.cpp

#include <cstdio>
#include <string>
#include <cstring>
#include "PcmToAac.h"
 
PcmToAac::PcmToAac(uint32_t maxPcmSize, uint32_t maxAacQueueSize)
        : hEncoder(nullptr)
        , pConfiguration(nullptr)
        , m_AacBuffer(nullptr)
        , m_nInputSamples(0)
        , m_nMaxOutputBytes(0)
        , m_nMaxInputBytes(0)
        , m_nPCMBitSize(0)
        , m_PcmMaxSize(maxPcmSize)
        , m_AacMaxQueueSize(maxAacQueueSize)
{
 
}
 
 
PcmToAac::~PcmToAac()
{
    //关闭句柄
    if (nullptr != hEncoder) {
        faacEncClose(hEncoder);
    }
 
    //释放内存
    delete []m_AacBuffer;
}
 
int PcmToAac::Init(uint8_t Channel, uint32_t SampleRate, uint32_t BitSize)
{
    m_nPCMBitSize = BitSize;
    //打开句柄
    hEncoder = faacEncOpen(SampleRate, Channel, &m_nInputSamples, &m_nMaxOutputBytes);
    if (hEncoder == nullptr) {
        fprintf(stderr, "faac enc open failed\n");
        return -1;
    }
    //计算每次输入pcm数据大小
    m_nMaxInputBytes = m_nInputSamples * m_nPCMBitSize / 8;
 
    //分配空间用于存储aac数据
    m_AacBuffer = new unsigned char[m_nMaxOutputBytes];
 
    //获取结构体指针用于填充编码参数
    pConfiguration = faacEncGetCurrentConfiguration(hEncoder);
    pConfiguration->inputFormat = FAAC_INPUT_16BIT; //位深16
    pConfiguration->outputFormat = 1;   //ADTS
    pConfiguration->useTns = 0;
    pConfiguration->useLfe = 0;
    pConfiguration->aacObjectType = LOW;    //AAC-LC
    pConfiguration->mpegVersion = MPEG2;
 
    //设置编码参数
    faacEncSetConfiguration(hEncoder, pConfiguration);
 
    return 0;
}
 
int PcmToAac::InputPcmData(const uint8_t* pcmData, uint32_t dataSize) {
    if (dataSize + m_PcmStr.size() > m_PcmMaxSize) {
        fprintf(stderr, "Input pcm data is too large\n");
        return -1;
    }
    m_PcmStr.append((const char*)pcmData, dataSize);
 
    if (m_AacQueue.size() > m_AacMaxQueueSize) {
        fprintf(stderr, "Aac queue is full\n");
        return -1;  //aac队列已满
    }
 
    int ret = 0;
    while(m_PcmStr.size() >= m_nMaxInputBytes) {
        memset(m_AacBuffer, 0, m_nMaxOutputBytes);
        ret = faacEncEncode(hEncoder, (int32_t*)m_PcmStr.data(), m_nInputSamples, m_AacBuffer, m_nMaxOutputBytes);
        if (ret > 0) {
            m_AacQueue.emplace((const char*)m_AacBuffer, ret);
        }
        m_PcmStr.erase(0, m_nMaxInputBytes);
    }
 
    return 0;
}
 
 
std::string PcmToAac::GetAacFrame() {
    if (m_AacQueue.empty()) {
        return "";
    }
    std::string aac = m_AacQueue.front();
    m_AacQueue.pop();
    return aac;
}

PcmToAac.h

#ifndef _PCM_TO_AAC_H
#define _PCM_TO_AAC_H
 
#include <queue>
extern "C"
{
#include <faac.h>
}
 
class PcmToAac
{
public:
    explicit PcmToAac(uint32_t maxPcmSize = 1024 * 1024, uint32_t maxAacQueueSize = 1024);
    ~PcmToAac();
 
    int InputPcmData(const uint8_t* pcmData, uint32_t dataSize);
    std::string GetAacFrame();
    int Init(uint8_t Channel, uint32_t SampleRate, uint32_t BitSize);
 
private:
    faacEncHandle hEncoder;
    faacEncConfigurationPtr pConfiguration;
    uint32_t m_nPCMBitSize /*= 16*/;
    unsigned long m_nInputSamples /*= 0*/;
    unsigned long m_nMaxOutputBytes /*= 0*/;
    unsigned long m_nMaxInputBytes /*= 0*/;
 
    const uint32_t m_PcmMaxSize;         //内存中能存储的最大pcm
    const uint32_t m_AacMaxQueueSize;    //aac队列最大数量
    std::string m_PcmStr;
    std::queue<std::string> m_AacQueue;
    unsigned char* m_AacBuffer;
};
 
#endif

 main.cpp

#include <memory>
#include <cstring>
#include "PcmToAac.h"
 
int main() {
    //打开2个文件,我们目标是读取pcm文件并编码,最后保存为aac文件
    FILE *fpIn = fopen("D:\\music.pcm", "rb");
    if (!fpIn) {
        return -1;
    }
    FILE *fpOut = fopen("D:\\music.aac", "wb");
    if (!fpOut) {
        return -1;
    }
 
    //构造类对象PcmToAac并初始化, 最大编码大小 和 缓存中的aac队列大小 可以自己定义
    std::shared_ptr<PcmToAac> pcmToAacPtr = std::make_shared<PcmToAac>();
    //输入pcm音频:采样率44100, 立体声, 位深16
    if(pcmToAacPtr->Init(2, 44100, 16)) {
        return -1;
    }
 
    char buf[1024] = {0};
    for(; ;) {
        memset(buf, 0, sizeof(buf));
        int ret = fread(buf, 1, sizeof(buf), fpIn);
        if (ret <= 0) {
            break;
        }
 
        pcmToAacPtr->InputPcmData((const uint8_t *)buf, ret);
 
        //循环从缓存中输出aac数据并保存为文件
        std::string aacFrame;
        for( ; ; ) {
            aacFrame = pcmToAacPtr->GetAacFrame();
            if (aacFrame.empty()) {
                break;
            }
 
            fwrite(aacFrame.data(), 1, aacFrame.size(), fpOut);
        }
    }
    return 0;
}

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

 类似资料: