新唐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庫重新進行編譯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章