在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库重新进行编译。