在N32926開發板上實現對原始PCM音頻數據編碼成AAC音頻數據,以及將AAC音頻數據進行解碼的功能。
一、PCM編碼
快捷提供的開發工具中含有PCM音頻編碼的demo,文件位置application/aacenc,使用libnaacenc庫進行編碼。原代碼是對文件進行處理,現需要對代碼進行修改,實現對音頻字符串進行處理的功能。所需文件:
文件名 | 說明 |
---|---|
libnaacenc.a | 使用官方提供的demo中的庫文件 |
Pcm2aac.h | PCM音頻編碼頭文件 |
Pcm2aac.c | PCM音頻編碼功能函數文件 |
encode.c | 音頻編碼測試文件 |
Makefile | makefile文件 |
1、pcm2aac.h文件
文件內容:編碼設置、結構體定義和函數聲明。
#ifndef __AAC_H_
#define __AAC_H_
#include <stdbool.h>
#define ENCFRAME_BUFSIZE 1024
#define AACRECORDER_BIT_RATE 57000 // 64K bps
#define AACRECORDER_CHANNEL_NUM 1 // Mono
#define AACRECORDER_QUALITY 45 // 1 ~ 999
#define AACRECORDER_SAMPLE_RATE 8000 // 8K Hz
/* 編碼器 */
typedef struct{
bool m_bUseAdts;
bool m_bUseMidSide;
bool m_bUseTns;
unsigned int m_u32Quality; // 1 ~ 999
unsigned int m_u32BitRate; // bps
unsigned int m_u32ChannelNum;
unsigned int m_u32SampleRate; // Hz
} S_AACENC;
/* 錯誤碼 */
typedef enum {
eAACENC_ERROR_NONE = 0x0000, // No error
// Un-recoverable errors
eAACENC_ERROR_BUFLEN = 0x0001, // Input buffer too small (or EOF)
eAACENC_ERROR_BUFPTR = 0x0002, // Invalid (null) buffer pointer
eAACENC_ERROR_NOMEM = 0x0031, // Not enough memory
// Recoverable errors
eAACENC_ERROR_BADBITRATE = 0x0103, // Forbidden bitrate value
eAACENC_ERROR_BADSAMPLERATE = 0x0104, // Reserved sample frequency value
} E_AACENC_ERROR;
/* 初始化編碼器 */
E_AACENC_ERROR AACEnc_Initialize(S_AACENC *psEncoder);
/* 進行音頻編碼 */
E_AACENC_ERROR AACEnc_EncodeFrame(
short *pi16PCMBuf, // [in] PCM buffer
char *pi8FrameBuf, // [in] AAC frame buffer
int i32FrameBufSize, // [in] Bytes of AAC frame buffer size
int *pi32FrameSize // [out]Bytes of encoded AAC frame
);
/* 結束編碼 */
void AACEnc_Finalize(void);
#endif
編碼設置需要修改參數:
參數 | 說明 |
---|---|
AACRECORDER_CHANNEL_NUM | 音頻數據通道數 |
AACRECORDER_SAMPLE_RATE | 採樣頻率 |
其他參數不需要進行修改,修改後對實際壓縮效果無影響。
音頻編碼函數AACEnc_EncodeFrame說明。
參數 | 說明 |
---|---|
pi16PCMBuf | 要編碼的PCM數據字符串 |
pi8FrameBuf | 編碼生成的AAC數據字符串存放位置 |
i32FrameBufSize | pi8FrameBuf字符串的長度 |
pi32FrameSize | 編碼生成的AAC數據長度 |
2、pcm2aac.c
文件內容:PCM音頻壓縮接口函數
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pcm2aac.h"
int flag = 0;
/*
* 函數名: encode
* 描 述: 將獲取的音頻數據由pcm格式壓縮爲aac格式
* 輸 入:
* src: 獲取的源音頻數據
* dst: 經過壓縮後的音頻數據
* 返回值: 返回0表示壓縮成功,返回-1表示壓縮失敗
*/
int encode(const char *src, char *dst)
{
// 創建音頻壓縮器
S_AACENC enc;
int framesize;
E_AACENC_ERROR eAACEnc_Error;
// 初始化壓縮器,只執行一次
if (flag == 0) {
enc.m_u32SampleRate = AACRECORDER_SAMPLE_RATE;
enc.m_u32ChannelNum = AACRECORDER_CHANNEL_NUM;
enc.m_u32BitRate = AACRECORDER_BIT_RATE * enc.m_u32ChannelNum;
enc.m_u32Quality = AACRECORDER_QUALITY;
enc.m_bUseAdts = true;
enc.m_bUseMidSide = false;
enc.m_bUseTns = false;
eAACEnc_Error = AACEnc_Initialize(&enc);
if (eAACEnc_Error != eAACENC_ERROR_NONE) {
printf("AAC Recorder: Fail to initialize AAC Encoder: Error code 0x%08x\n", eAACEnc_Error);
return -1;
}
flag = 1;
}
// 壓縮音頻數據
eAACEnc_Error = AACEnc_EncodeFrame((short*)src, dst, ENCFRAME_BUFSIZE, &framesize);
if (eAACEnc_Error != eAACENC_ERROR_NONE) {
printf("AAC Recorder: Fail to encode file: Error code 0x%08x\n", eAACEnc_Error);
framesize = 0;
}
// AACEnc_Finalize();
return framesize;
}
3、encode.c文件
文件內容:PCM音頻編碼測試程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/soundcard.h>
#include <linux/input.h>
#include "pcm2aac.h"
extern int encode(const char *src, char *dst);
/* 功能:進行音頻壓縮測試
*/
int main(int argc, char *argv[])
{
int ret; //函數執行返回值
int fd_dsp1, fd_mixer1, fd_dsp2, fd_mixer2; //硬件相關文件描述符
char *buf; //傳輸數據
char *aac;
int fd, fd1;
int frag;
char out[4096];
int len;
buf = (char *)malloc(20480);
aac = (char *)malloc(1024);
printf("1.0.0ver\n");
fd = open("out.aac", O_RDWR);
fd1 = open("in.pcm", O_RDONLY);
while (1)
{
ret = read(fd1, buf, 2048);
if (ret == 0)
break;
ret = encode(buf, aac);
write(fd, aac, ret);
}
return 0;
}
程序功能爲將in.pcm文件的數據編碼後寫入out.aac文件中。In.pcm文件爲從開發板mic錄製的音頻文件,參數設置爲8000採樣率,16bit採樣位數,單通道。
每次從PCM文件中讀取的數據大小爲2048,表示將2048個字節的PCM數據壓縮爲一個AAC幀。使用其他大小會使編碼出錯。
4、Makefile文件
AR = arm-none-linux-gnueabi-ar
CC = arm-none-linux-gnueabi-gcc
LD = arm-none-linux-gnueabi-ld
STRIP = arm-none-linux-gnueabi-strip
SHELL = sh
OUTPUT_PATH = .
OUTPUT_NAME = $(OUTPUT_PATH)/encode
ROOT = /usr/local/arm_linux_4.2
SYS_INCLUDE = -I$(ROOT)/arm-linux/include -I$(ROOT)/arm-linux/sys-include -I.
SYS_LIB = -L$(ROOT)/lib/gcc/arm-linux/4.2.1 -L$(ROOT)/arm-linux/lib
AS_FLAG = -O2
C_FLAG = -O2 -ffunction-sections -fdata-sections -Wall -Wno-strict-aliasing
LD_FLAG = -static -pthread -Wl,--gc-sections
TARGET_FLAG = -mcpu=arm926ej-s
SOURCES = encode.c pcm2aac.c audio.c mic.c
LSOURCES = "encode.c" "pcm2aac.c" "audio.c" "mic.c"
S_OBJECTS = $(OUTPUT_PATH)/encode.o $(OUTPUT_PATH)/pcm2aac.o $(OUTPUT_PATH)/audio.o $(OUTPUT_PATH)/mic.o
O_OBJECTS = libnaacenc.a
OBJECTS = $(S_OBJECTS) $(O_OBJECTS)
LS_OBJECTS = "$(OUTPUT_PATH)/encode.o" "$(OUTPUT_PATH)/pcm2aac.o" "$(OUTPUT_PATH)/audio.o" "$(OUTPUT_PATH)/mic.o"
LO_OBJECTS = "libnaacenc.a"
LOBJECTS = $(LS_OBJECTS) $(LO_OBJECTS)
all: prebuild $(OUTPUT_NAME) postbuild
$(OUTPUT_NAME): $(OBJECTS)
@echo "Linking... "
@echo "Creating file $@..."
@$(CC) -o $@ $(LOBJECTS) $(TARGET_FLAG) $(LD_FLAG) $(SYS_LIB)
$(STRIP) $(OUTPUT_NAME)
$(OUTPUT_PATH)/encode.o: encode.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
$(OUTPUT_PATH)/pcm2aac.o: pcm2aac.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
$(OUTPUT_PATH)/audio.o: audio.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
$(OUTPUT_PATH)/mic.o: mic.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
clean:
$(RM) -f $(LS_OBJECTS)
$(RM) -f $(OUTPUT_NAME)
postbuild:
rm -f $(S_OBJECTS)
prebuild:
if [ ! -d "$(OUTPUT_PATH)" ]; then mkdir -p $(OUTPUT_PATH); fi
5、PCM編碼功能測試
(a)在電腦代碼目錄下執行make命令,將生成的encode可執行文件拷貝入開發板,並在該目錄下添加in.pcm和out.aac文件。
(b)在開發板中執行./encode命令,進行編碼。
(c)測試編碼是否成功。使用快捷提供的AAC文件播放程序,程序位置application/aacdec/bin/AACPlayer,執行命令./AACPlayer out.aac播放AAC文件,檢查是否壓縮成功。
二、AAC解碼
快捷提供的開發工具中含有AAC音頻解碼的demo,文件位置application/aacdec,但是該代碼只能對AAC文件進行操作,無法對源代碼修改後實現對AAC數據字符串的處理。所以使用libfaad庫重新編寫AAC解碼函數,所需文件:
文件名 | 說明 |
---|---|
faad2-2.7.tar.gz | libfaad源代碼 |
libfaad.a | libfaad解碼庫 |
neaacdec.h | 解碼庫頭文件 |
aac2pcm.h | AAC音頻解碼頭文件 |
aac2pcm.c | AAC音頻解碼功能函數文件 |
decode.c | 音頻解碼測試文件 |
Makefile | makefile文件 |
1、編譯libfaad庫
(a)下載faad2-2.7.tar.gz,並解壓。
(b)在faad2-2.7目錄下進行安裝配置:
./configure –prefix=/home/horo/arm/software/faad_arm –host=arm-linux –enable-shared=no
(c)在faad2-2.7目錄下執行make命令,然後執行make install命令。
(d)在指定安裝目錄下找到libfaad.a文件和neaacdec.h文件,拷貝到解碼代碼目錄下。
2、aac2pcm.h文件
文件內容:AAC解碼函數聲明。
代碼:
#ifndef __AAC2PCM_H_
#define __AAC2PCM_H_
#include "neaacdec.h"
void destroyaacdecoder();
int InitAACDecoder(int nSamplesPerSec, int nChannels);
int Decoder(unsigned char *pszAAC, unsigned int nLen, char *pszOut, int *pnOutLen);
#endif
相關函數說明見aac2pcm.c文件。
3、aac2pcm.c文件
文件內容:AAC解碼函數實現。
代碼:
#include <stdio.h>
#include <string.h>
#include "aac2pcm.h"
/* 定義全局變量 */
unsigned long m_nSampleRate; //波特率
unsigned char m_nChannels; //通道
NeAACDecHandle m_hAACDecoder; //解碼器
int m_bInit; //初始化標識
NeAACDecFrameInfo hInfo; //解碼數據
/*
* 函數名: DestroyAACDecoder
* 描 述: 解碼結束或解碼出錯時關閉解碼器
* 輸 入: 無
* 返回值: 無
*/
void DestroyAACDecoder()
{
if (m_hAACDecoder != NULL)
{
NeAACDecClose(m_hAACDecoder);
m_hAACDecoder = NULL;
}
}
/*
* 函數名: InitAACDecoder
* 描 述: 初始化AAC解碼器,設置採樣率和通道數
* 輸 入:
* nSamplesPerSec: 採樣率
* nChannels: 通道數(note: 設置值無效,解碼時NeAACDecInit自動設置爲2)
* 返回值: 成功返回0, 失敗返回-1
*/
int InitAACDecoder(int nSamplesPerSec, int nChannels)
{
m_nSampleRate = nSamplesPerSec; //採樣率
m_nChannels = nChannels; //通道數
m_bInit = 0; //初始化標誌
// 打開解碼器
m_hAACDecoder = NeAACDecOpen();
if (!m_hAACDecoder)
{
printf("NeAACDecOpen() failed");
DestroyAACDecoder();
return -1;
}
// 設置解碼參數
NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(m_hAACDecoder);
if (!conf)
{
printf("NeAACDecGetCurrentConfiguration() failed");
DestroyAACDecoder();
return -1;
}
conf->defSampleRate = nSamplesPerSec;
conf->defObjectType = LC;
conf->outputFormat = 1;
conf->dontUpSampleImplicitSBR = 1;
NeAACDecSetConfiguration(m_hAACDecoder, conf);
return 0;
}
/*
* 函數名: Decoder
* 描 述: 將aac數據轉換成pcm數據
* 輸 入:
* pszAAC: aac數據指針
* nLen: aac數據大小
* pszOut: pcm數據指針
* pnOutLen: pcm數據大小指針
* 返回值: 失敗返回-1,成功返回未進行解碼的aac數據大小
*/
int Decoder(unsigned char *pszAAC, unsigned int nLen, char *pszOut, int *pnOutLen)
{
// 解碼前,需要根據第一個AAC包來初始化解碼器
if (m_bInit == 0)
{
if (NeAACDecInit(m_hAACDecoder, pszAAC, nLen, &m_nSampleRate, &m_nChannels) < 0)
{
printf("NeAACDecInit failed!\n");
return -1;
};
m_bInit = 1;
return nLen;
}
// 解碼AAC數據(注意,首次解碼數據錯誤,應該是解碼器內部初始化)
unsigned char *pInputPtr = pszAAC;
char *pOutputPtr = (char*)pszOut;
int nRemainLen = *pnOutLen;
int nDecodeLen = 0;
void *out;
*pnOutLen = 0;
// 進行解碼, 第一次解碼出錯,爲正常現象
out = NeAACDecDecode(m_hAACDecoder, &hInfo, pInputPtr, nLen);
if (hInfo.error != 0 || hInfo.samples == 0)
{
printf("NeAACDecDecode failed!\n");
return nLen;
}
// bytesconsumed 是指消費掉了多少AAC數據,如果你提供的AAC數據較多,那麼可能會分幾次解出PCM數據
pInputPtr += hInfo.bytesconsumed;
nLen -= hInfo.bytesconsumed;
nDecodeLen = hInfo.channels * hInfo.samples; // 實際解出的PCM數據需要將樣本數和通道數相乘
if (nDecodeLen > nRemainLen)
{
printf("The remaining buffer is insufficient, can not complete the encoding\n");
return -1;
}
// 輸出解碼數據
*pnOutLen += nDecodeLen;
memcpy(pOutputPtr, out, nDecodeLen);
return nLen;
}
第一幀AAC數據解碼時用來初始化AAC解碼器,第一次解碼AAC數據時解碼失敗爲正常現象,還是用於解碼器的設置。
4、decode.c文件
文件內容:AAC數據解碼測試程序。
代碼:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "aac2pcm.h"
/*
* 函數名: main
* 描 述: 將out.aac(AAC音頻文件)轉換成pcm(PCM音頻文件)
* 輸 入: 無
* 輸 出: 無
*/
int main(int argc, char *argv[])
{
int fd, fd1; //文件描述符
int ret; //函數返回值
unsigned char *buf; //aac音頻數據數組
char *out; //pcm音頻數據數組
int len = 10240; //pcm音頻數據數組大小(大於4096)
unsigned int d = 1024; //每次輸入的aac音頻數據數組大小
int inlen; //每次從aac文件中實際讀取的數據大小
printf("Ver1.0.0\n");
// 爲音頻數據數組開闢空間
buf = malloc(d);
out = malloc(len);
// 打開音頻文件
fd = open("out.aac", O_RDONLY);
fd1 = open("pcm", O_RDWR);
// 初始化AAC解碼器,並設置採樣率爲8000
// 第二個參數爲通道數,實際設置不起作用,解碼器自動設置爲2
InitAACDecoder(8000, 1);
// 循環從AAC文件中讀取數據並解碼
gettimeofday(&tpstart, NULL);
while (1) {
// 重新設置pcm數組大小,進行解碼後被設置爲實際解碼出的pcm數據大小
len = 10240;
// 從AAC文件中讀取數據
inlen = read(fd, buf, d);
if (inlen == 0)
break;
// 進行解碼,ret值爲buf數組中未進行解碼的aac數據大小
ret = Decoder(buf, inlen, out, &len);
if (ret == 600)
write(fdtmp, buf, inlen);
printf("len = %d\n", ret);
// 重新定位文件位置,從未進行解碼的數據處開始讀取數據
lseek(fd, 0 - ret, SEEK_CUR);
// 將解碼出的數據寫入pcm音頻文件
if (len > 0 && len != 10240) {
write(fd1, out, len);
}
memset(out, 0, 10240);
}
return 0;
}
程序功能,將out.aac的音頻數據進行解碼並保存到pcm文件中。
編寫代碼時需要注意,Decode函數的參數len表示存放解碼出的PCM數據的字符串的大小,執行完函數後值變爲實際解碼出的PCM數據大小,所以每次循環需要重新設置len的值,保證存放數據的字符串足夠大。否則會提示空間不足的錯誤。
每幀AAC數據的大小不同,程序中每次從文件中讀取1024字節的數據進行解碼,Decode函數的返回值爲未進行解碼的AAC數據大小,注意要將這部分數據重新加入進行解碼。
5、Makefile文件
代碼:
AR = arm-none-linux-gnueabi-ar
CC = arm-none-linux-gnueabi-gcc
LD = arm-none-linux-gnueabi-ld
STRIP = arm-none-linux-gnueabi-strip
SHELL = sh
OUTPUT_PATH = .
OUTPUT_NAME = $(OUTPUT_PATH)/decode
ROOT = /usr/local/arm_linux_4.2
SYS_INCLUDE = -I$(ROOT)/arm-linux/include -I$(ROOT)/arm-linux/sys-include -I.
SYS_LIB = -L$(ROOT)/lib/gcc/arm-linux/4.2.1 -L$(ROOT)/arm-linux/lib
AS_FLAG = -O2
C_FLAG = -O2 -ffunction-sections -fdata-sections -Wall -Wno-strict-aliasing
LD_FLAG = -static -pthread -Wl,--gc-sections
DLIBS = -lm
TARGET_FLAG = -mcpu=arm926ej-s
SOURCES = decode.c aac2pcm.c audio.c mic.c
LSOURCES = "decode.c" "aac2pcm.c" "audio.c" "mic.c"
S_OBJECTS = $(OUTPUT_PATH)/decode.o $(OUTPUT_PATH)/aac2pcm.o $(OUTPUT_PATH)/audio.o $(OUTPUT_PATH)/mic.o
O_OBJECTS = libfaad.a
OBJECTS = $(S_OBJECTS) $(O_OBJECTS)
LS_OBJECTS = "$(OUTPUT_PATH)/decode.o" "$(OUTPUT_PATH)/aac2pcm.o" "$(OUTPUT_PATH)/audio.o" "$(OUTPUT_PATH)/mic.o"
LO_OBJECTS = "libfaad.a"
LOBJECTS = $(LS_OBJECTS) $(LO_OBJECTS)
all: prebuild $(OUTPUT_NAME) postbuild
$(OUTPUT_NAME): $(OBJECTS)
@echo "Linking... "
@echo "Creating file $@..."
@$(CC) -o $@ $(LOBJECTS) $(TARGET_FLAG) $(LD_FLAG) $(SYS_LIB) -lm
$(STRIP) $(OUTPUT_NAME)
$(OUTPUT_PATH)/decode.o: decode.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
$(OUTPUT_PATH)/aac2pcm.o: aac2pcm.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
$(OUTPUT_PATH)/audio.o: audio.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
$(OUTPUT_PATH)/mic.o: mic.c Makefile
@echo "Compiling $<"
@$(CC) -c -o "$@" "$<" $(TARGET_FLAG) $(C_FLAG) $(SYS_INCLUDE)
clean:
$(RM) -f $(LS_OBJECTS)
$(RM) -f $(OUTPUT_NAME)
postbuild:
rm -f $(S_OBJECTS)
prebuild:
if [ ! -d "$(OUTPUT_PATH)" ]; then mkdir -p $(OUTPUT_PATH); fi
6、AAC解碼功能測試
(a)在電腦代碼目錄下執行make命令,將生成的decode可執行文件拷貝入開發板,並在該目錄下添加pcm和out.aac文件。
(b)在開發板中執行./encode命令,進行解碼。
(c)測試解碼是否成功,播放pcm音頻文件。
7、測試問題
(a)編解碼數據問題
問題:編碼出一個AAC幀需要PCM數據大小爲2048字節,但是解碼一個AAC幀出來的PCM數據大小爲4096字節。
原因:解碼時將解碼器通道數設爲1,但是解碼庫代碼自動將1設置爲2。所以數據量變爲原來的兩倍。
解決:修改libfaad庫源代碼,文件位置faad2-2.7/libfaad/decoder.c:
註釋掉將通道數由1設爲2的代碼
329 // if (*channels == 1)
330 // {
331 /* upMatrix to 2 channels for implicit signalling of PS */
332 // *channels = 2;
333 // }
1033 // output_channels = 2;
將解碼數據長度設置爲2048
1046 frame_len = 2048;
將libfaad庫重新進行編譯。