天天看點

使用FAAC實作PCM轉AAC

一、前言

AAC全稱為Advanced Audio Coding,目前比較主流的AAC開源編碼器主要有Nero和Faac。接下來我們将使用Faac實作音頻PCM至AAC的音頻格式轉換,并使用Emscripten編譯成WebAssembly子產品。

二、實作步驟

使用Faac實作音頻編碼,主要有以下步驟:

使用FAAC實作PCM轉AAC

2.1 主要函數

  • faacEncOpen

faacEncHandle FAACAPI faacEncOpen(unsigned long sampleRate,
    unsigned int numChannels,
    unsigned long *inputSamples,
    unsigned long *maxOutputBytes
);
           
變量名 變量含義
sampleRate 輸入PCM的采樣率。
numChannels 輸入PCM的通道數。
inputSamples 編碼一幀AAC所需要的位元組數,打開編碼器後擷取,故聲明時不需指派。
maxOutputBytes 編碼後的資料輸出的最大長度。
  • faacEncEncode

int FAACAPI faacEncEncode(faacEncHandle hEncoder,
    int32_t * inputBuffer,
    unsigned int samplesInput,
    unsigned char *outputBuffer,
    unsigned int bufferSize
);
           
變量名 變量含義
hEncoder faacEncOpen傳回的編碼器句柄
inputBuffer PCM緩沖區
samplesInput faacEncOpen編碼後的資料長度inputSamples,即PCM緩沖區長度
outputBuffer 編碼後輸出資料
bufferSize 輸出資料的長度,對應faacEncOpen的maxOutputBytes

2.2 編碼器參數

與Faac編碼器相關的配置在faaccfg.h中聲明。主要參數的含義如下:

// 生成的mpeg版本,如果需要錄制MP4則設定為MPEG4,如果希望得到未封裝的AAC裸流,則設定為MPEG2
// 0-MPEG4 1-MPEG2
unsigned int mpegVersion;

// AAC編碼類型
// 1-MAIN 2-LOW 3-SSR 4-LTP
unsigned int aacObjectType;

// 是否允許一個通道為低頻通道
// 0-NO 1-YES
unsigned int useLfe;

// 是否使用瞬時噪聲定形(temporal noise shaping,TNS)濾波器
// 0-NO 1-YES
unsigned int useTns;

// AAC碼率,可參考常見AAC碼率,機關bps
unsigned long bitRate;

// AAC頻寬
unsigned int bandWidth;

// AAC編碼品質
// lower<100 default=100 higher>100
unsigned long quantqual;

// 輸出的資料類型,RAW不帶adts頭部
// 0-RAW 1-ADTS
unsigned int outputFormat;

// 輸入PCM資料類型
// 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
unsigned int inputFormat;
           

2.3 編碼器初始化

unsigned long inputSample = 0;
unsigned long maxOutputBytes = 0;
faacEncHandle encoder;

EM_PORT_API(void) turn_on_encoder() {
	unsigned int numChannels = 1;
	unsigned long sampleRate = 8000;
	
	faacEncConfigurationPtr config;

	encoder = faacEncOpen(sampleRate, numChannels, &inputSample, &maxOutputBytes);

	// EM_ASM_({
	// 	console.log('inputSample', $0);
	// 	console.log('maxOutputBytes', $1)
	// }, (unsigned int)inputSample, (unsigned int)maxOutputBytes);

	config = faacEncGetCurrentConfiguration(encoder);

	config->aacObjectType = LOW;
	config->useTns = 1;
	config->allowMidside = 1;
	config->bitRate = 8000;
	config->outputFormat = 1;
	config->inputFormat = FAAC_INPUT_16BIT;

	faacEncSetConfiguration(encoder, config);
}
           

在之前的Emscripten的介紹中,已經給出宏EM_PORT_API的定義。值得注意的是,因為inputSample、maxOutputBytes的資料類型是unsigned long,使用64位存儲,為了避免C與JS進行資料互動時,發生記憶體不對齊的情況,此處将資料類型轉為32位的unsigned int類型。

2.4 編碼

EM_PORT_API(unsigned char*) pcm_2_aac(unsigned char* inputBuffer) {

	byteLength = 0;
	
	unsigned char* outputBuffer = (unsigned char*)malloc(maxOutputBytes);
	
	do {
		byteLength = faacEncEncode(encoder, (int32_t*)inputBuffer, inputSample, outputBuffer, maxOutputBytes);
		if (byteLength > 0) break;
	} while (byteLength <= 0);

	return outputBuffer;
}
           

在這個函數中,使用了malloc為編碼後的資料緩沖區outputBuffer動态配置設定記憶體空間,為了避免記憶體洩漏,在不需要outputBuffer時,需要手動将記憶體釋放。因而增加以下函數,可在JS中調用函數進行釋放。

EM_PORT_API(void) free_buf(void* buf) {
	free(buf);
}
           

三、如何在JS中使用

使用Emscripten編譯生成WebAssembly子產品和膠水代碼,假設為faac.wasm與faac.js,加入到JS項目中。

下面是我自己寫的一個由PCM轉AAC的例子:

/**
 * PCM轉AAC
 * @param {ArrayBuffer} buffer PCM資料,有符号16位
 */
pcm_2_aac(buffer) {
    var pcmBuf = new Uint8Array(buffer);
    
    // 建立PCM資料在HEAP中的指針變量
    var pcmPtr = Faac._malloc(pcmBuf.byteLength);
    Faac.HEAPU8.set(Array.from(pcmBuf), pcmPtr);

    /**
     * Faac._pcm_2_aac(inputBuffer)
     * @param {Number} inputBuffer PCM數組在HEAP中的首位址
     */
    var aacPtr = Faac._pcm_2_aac(pcmPtr);
    var byteLen = Faac._getByteLen();
    var arrBuf = Uint8Array.from(Faac.HEAPU8.subarray(aacPtr, aacPtr + byteLen));

    // 清除緩存
    Faac._free(pcmPtr);
    Faac._free_buf(aacPtr);

    return arrBuf;
}
           

注意到我們是從HEAPU8中取出編碼後的AAC資料的,此處的HEAP事實上是指C環境的整個記憶體空間。在調用該函數進行編碼時,需要将大塊的資料送入C環境下,此時我們可以在JS中配置設定記憶體并裝入資料,然後将資料指針傳入,調用C函數進行處理。這種做法借助了C的導出函數_malloc/_free實作的。

另外,HEAPU8實際上對應的資料類型是Uint8Array。

以上是關于在Web項目中,如何使用Faac将PCM轉成AAC的分享。