使用libaac(d)库实现PCM音频的编解码

本文主要讲的是使用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;
}

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值