新唐N32926开发板PCM音频编解码

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

猜你喜欢

转载自blog.csdn.net/horotororensu/article/details/78354642
今日推荐