本文主要讲的是使用libfaac和libfaad这个两个库来实现PCM音频的编解码。
首先,你应该去官网下载这两个库的源码,然后编译成lib或者dll,这个过程网上有很详细的教程,我这里就不在啰嗦了。下面我们重点来看看如何用代码来实现编解码:
我们先来看编码部分,原始音频数据是PCM,至于怎么获取PCM数据,这不是本文的重点,就不说了。
编码AAC需要两个重要的参数,采样率和通道数,这两个参数你可以从PCM中获取,或者人为设定;另外,要编码一帧AAC,需要4096个字节的PCM数据(一帧PCM数据),单次采样捕获是无法满足这个要求的,所以需要设置一个PCM缓冲区,用来暂存临时的PCM数据。
#include "faac.h"
#include "Mutex.h"
//
// AAC编码处理类
// 提供AAC编码相关接口
//
class AACEnc
{
public:
AACEnc();
~AACEnc();
public:
BOOL InitAACEncoder(ULONG nSampleRate, INT nChannels);
VOID DestroyAACEncoder();
// 录制mp4时,需要使用此接口来获取音频信息
BOOL GetSpecificInfo(UCHAR **ppAsc, ULONG *pnAscLen);
// 编码PCM成AAC
// 只有返回TRUE时,pszOut和pnOutLen才为有效值
BOOL Encoder(BYTE *pszData, UINT nLen, BYTE *pszOut, UINT *pnOutLen);
private:
faacEncHandle m_hEncoder;
ULONG m_nSampleInput;
INT m_nChannels, m_nSampleRate;
Mutex m_lock;
BYTE m_szBuf[8192];
UINT m_nWriteIndex;
};
#pragma comment(lib,"libfaad2.lib")
//===============================================================================================================
// AAC编码
//===============================================================================================================
AACEnc::AACEnc()
{
m_hEncoder = NULL;
m_nSampleInput = 0;
m_nChannels = 2;
m_nSampleRate = 48000;
m_nWriteIndex = 0;
}
AACEnc::~AACEnc()
{
DestroyAACEncoder();
}
BOOL AACEnc::InitAACEncoder(ULONG nSampleRate, INT nChannels)
{
m_nChannels = nChannels;
m_nSampleRate = nSampleRate;
ULONG maxBytesOutput;
m_hEncoder = faacEncOpen(m_nSampleRate, m_nChannels, &m_nSampleInput, &maxBytesOutput);
if (m_hEncoder == NULL)
{
TRACE("faacEncOpen failed!\n");
return FALSE;
}
// 设置编码参数
faacEncConfigurationPtr pFormat = faacEncGetCurrentConfiguration(m_hEncoder);
pFormat->aacObjectType = LOW; //编码类型
pFormat->mpegVersion = MPEG4; //设置版本,录制MP4文件时要用MPEG4
pFormat->useTns = 0; //瞬时噪声定形(temporal noise shaping,TNS)滤波器
pFormat->allowMidside = 1; //M/S编码
pFormat->shortctl = SHORTCTL_NORMAL;
pFormat->inputFormat = FAAC_INPUT_16BIT; //输入数据类型
pFormat->outputFormat = 0; //录制MP4文件时,要用raw流; 1是ADTS
if (!faacEncSetConfiguration(m_hEncoder, pFormat))
{
TRACE("faacEncSetConfiguration failed!\n");
return FALSE;
}
return TRUE;
}
VOID AACEnc::DestroyAACEncoder()
{
if (m_hEncoder != NULL)
{
faacEncClose(m_hEncoder);
m_hEncoder = NULL;
}
}
BOOL AACEnc::GetSpecificInfo(UCHAR **ppAsc, ULONG *pnAscLen)
{
return !faacEncGetDecoderSpecificInfo(m_hEncoder, ppAsc, pnAscLen);
}
BOOL AACEnc::Encoder(BYTE *pszData, UINT nLen, BYTE *pszOut, UINT *pnOutLen)
{
BOOL bRet = FALSE;
UINT nNeedEncLen = m_nSampleInput * m_nChannels;
// 由于编码一帧AAC,需要4096个字节的PCM,所以这里需要一个临时缓冲区用来暂存编码前的数据
m_lock.Lock();
memcpy(m_szBuf + m_nWriteIndex, pszData, nLen);
m_nWriteIndex += nLen;
if (m_nWriteIndex >= nNeedEncLen)
{
bRet = TRUE;
nLen = faacEncEncode(m_hEncoder, (int*)m_szBuf, m_nSampleInput, pszOut, *pnOutLen);
*pnOutLen = nLen;
m_nWriteIndex -= nNeedEncLen;
memmove(m_szBuf, m_szBuf + nNeedEncLen, m_nWriteIndex); // 调整缓冲区中剩下数据的位置
}
m_lock.Unlock();
return bRet;
}
以上代码就是整个AAC编码的实现,看起来相当简单。实测的效果是4096个字节的PCM,经过压缩后,大小大概为350个字节,压缩效果还是很客观的。
解码器的实现:
#include "neaacdec.h"
//
// AAC解码处理类
// 提供AAC解码相关接口
//
class AACDec
{
public:
AACDec();
~AACDec();
public:
BOOL InitAACDecoder(int nSamplesPerSec, int nChannels);
VOID DestroyAACDecoder();
// 解码AAC成PCM
// 只有返回TRUE时,pszOut和pnOutLen才为有效值
BOOL Decoder(BYTE *pszAAC, UINT nLen, BYTE *pszOut, UINT *pnOutLen);
private:
BOOL m_bInit;
NeAACDecHandle m_hAACDecoder;
ULONG m_nSampleRate;
UCHAR m_nChannels;
};
#pragma comment(lib,"libfaad2.lib")
AACDec::AACDec()
{
m_bInit = FALSE;
m_hAACDecoder = NULL;
m_nSampleRate = 48000;
m_nChannels = 2;
}
AACDec::~AACDec()
{
DestroyAACDecoder();
}
BOOL AACDec::InitAACDecoder(int nSamplesPerSec, int nChannels)
{
m_nSampleRate = nSamplesPerSec;
m_nChannels = nChannels;
//ULONG cap = NeAACDecGetCapabilities();
m_hAACDecoder = NeAACDecOpen();
if (!m_hAACDecoder)
{
TRACE("NeAACDecOpen() failed");
DestroyAACDecoder();
return FALSE;
}
// 设置解码参数
NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(m_hAACDecoder);
if (!conf)
{
TRACE("NeAACDecGetCurrentConfiguration() failed");
DestroyAACDecoder();
return FALSE;
}
conf->defSampleRate = nSamplesPerSec;
conf->defObjectType = LC;
conf->outputFormat = 1;
conf->dontUpSampleImplicitSBR = 1;
NeAACDecSetConfiguration(m_hAACDecoder, conf);
return TRUE;
}
VOID AACDec::DestroyAACDecoder()
{
if (m_hAACDecoder != NULL)
{
NeAACDecClose(m_hAACDecoder);
m_hAACDecoder = NULL;
}
}
BOOL AACDec::Decoder(BYTE *pszAAC, UINT nLen, BYTE *pszOut, UINT *pnOutLen)
{
if (m_bInit == FALSE) // 解码前,需要根据第一个AAC包来初始化解码器
{
if (NeAACDecInit(m_hAACDecoder, pszAAC, nLen, &m_nSampleRate, &m_nChannels) < 0)
{
TRACE("NeAACDecInit failed!\n");
return FALSE;
}
m_bInit = TRUE;
return FALSE;
}
//
// 解码AAC数据(注意,首次解码不会解出任何数据,也没有错误,应该是解码器内部初始化)
//
UCHAR *pInputPtr = (UCHAR*)pszAAC;
UCHAR *pOutputPtr = (UCHAR*)pszOut;
UINT nRemainLen = *pnOutLen;
INT nDecodeLen = 0;
*pnOutLen = 0;
while (nLen > 0)
{
NeAACDecFrameInfo hInfo;
void *out = NeAACDecDecode(m_hAACDecoder, &hInfo, pInputPtr, nLen);
if (hInfo.error != 0 || hInfo.samples == 0)
{
TRACE("NeAACDecDecode failed!\n");
return FALSE;
}
// bytesconsumed 是指消费掉了多少AAC数据,如果你提供的AAC数据较多,那么可能会分几次解出PCM数据
pInputPtr += hInfo.bytesconsumed;
nLen -= hInfo.bytesconsumed;
nDecodeLen = hInfo.channels * hInfo.samples; // 实际解出的PCM数据需要将样本数和通道数相乘
if (nDecodeLen > nRemainLen)
{
TRACE("The remaining buffer is insufficient, can not complete the encoding\n");
return FALSE;
}
*pnOutLen += nDecodeLen;
memcpy(pOutputPtr, out, nDecodeLen);
pOutputPtr += nDecodeLen;
nRemainLen -= nDecodeLen;
}
return TRUE;
}