opencore-amr移植至嵌入式设备
这段时间在做一个智能学生证项目。其中一个需求是做一个类似微信发语音的功能。由于录音保存的是PCM编码的wav格式音频,文件体积巨大。为了获得极致的压缩率,打算将PCM编码的音频文件转为AMR格式,需要移植opencore-amr库。
opencore-amr是用C和C++编写的AMR音频编解码库,可以对AMR-NB和AMR-WB格式的音频进行编解码。要移植opencore-amr我们只需用对应的工具链编译出静态链接库即可。
opencore-amr源码下载地址:
https://sourceforge.net/projects/opencore-amr/files/opencore-amr/
x86 Linux下编译
1.下载并解压opencore-amr源码
tar -xvf opencore-amr-0.1.3.tar.gz
2. opencore-amr使用configure脚本配置不同的参数。x86平台下,只需要指定‘--prefix’参数即可,这个参数指定了完成编译后的头文件和库的保存位置,必须是绝对路径。如需了解其他参数,可以使用 ./configure --help 命令查看。
./configure --prefix='/home/joshua/opencore-amr/x86'
3.配置好后,使用make进行编译和安装
make && make install
之后就可以到”--prefix“设定的目录中查看编译好的库文件了。
嵌入式交叉编译
1.相比于x86,嵌入式平台需要指定交叉编译器进行配置。请确保环境变量中有交叉编译器的路径。
./configure --host=arm-none-eabi --prefix='/home/joshua/opencore-amr/arm'
如果有“configure: error: C++ compiler cannot create executables”的错误提示,并且config.log中的报错信息为 ‘exit.c:(.text.exit+0x2c): undefined reference to `_exit'’,该错误的原因可能是g++编译器的版本不匹配,解决办法为在configure前增加编译参数'--specs=nosys.specs'。
2.还需注意的是,编译器参数最好和要使用该库的代码保持一致,否则可能出现浮点计算方式不同而不能正常链接的错误。
export OTHER_LINK_OPTIONS="--specs=nosys.specs"
export GCC_FLAGS="-std=gnu11 -mcpu=cortex-a5 -mtune=generic-armv7-a -mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mno-unaligned-access -Os"
LDFLAGS=$OTHER_LINK_OPTIONS CFLAGS=$GCC_FLAGS CXXFLAGS=$GCC_FLAGS ./configure --host=arm-none-eabi --prefix='/f/sc02/opencore-amr-0.1.3/openCPU'
PCM转AMR程序
#include <stdio.h>
#include <stdlib.h>
#include "interf_enc.h"
/* PCM参数 */
#define PCM_SAMPLERATE (8000) /* 只能编码 8 khz */
#define PCM_SAMPLEBITS (16) /* 只支持16位 */
#define PCM_CHANNELS (1) /* 不管PCM输入是单声道还是双声道,这里输出的amr都是单声道的 */
/* amr一帧数据是20ms,一秒50帧。8000,16,1 ==> 320 Bytes */
#define PCM_ONE_FRAME_SIZE (PCM_SAMPLERATE/50 * PCM_SAMPLEBITS/8 * PCM_CHANNELS)
/* AMR参数 */
#define AMR_ENCODE_MODE MR122
#define AMR_ONE_FRAME_SIZE (32) /* MR122格式是32字节一帧 */
/* 是否使能背景噪声编码模式 */
#define DTX_DECODE_ENABLE 1
#define DTX_DECODE_DISABLE 0
int main(int argc, char *argv[])
{
int dtx = DTX_DECODE_ENABLE;
void *vpAmr = NULL;
FILE *fpAmr = NULL;
FILE *fpPcm = NULL;
/* 检查参数 */
if(argc != 3)
{
printf("Usage: \n"
"\t %s ./audio/test_8000_16_1.pcm out.amr\n", argv[0]);
return -1;
}
printf("It will encode a PCM file as [sample rate: %d] - [sample bits: %d] - [channels: %d] !\n",
PCM_SAMPLERATE, PCM_SAMPLEBITS, PCM_CHANNELS);
/* 初始化编码器 */
vpAmr = Encoder_Interface_init(dtx);
if(vpAmr == NULL)
{
printf("Encoder_Interface_init error!\n");
return -1;
}
/* 打开pcm文件 */
fpPcm = fopen(argv[1], "rb");
if(fpPcm == NULL)
{
perror("argv[1]");
return -1;
}
/* 打开amr文件 */
fpAmr = fopen(argv[2], "wb");
if(fpAmr == NULL)
{
perror("argv[2]");
return -1;
}
/* 先写入amr头部 */
fwrite("#!AMR\n", 1, 6, fpAmr);
/* 循环编码 */
while(1)
{
unsigned char acPcmBuf[PCM_ONE_FRAME_SIZE] = {0}; /* 保存在文件中一帧(20ms)PCM数据,8bit为单位,这里是unsigned */
short asEncInBuf[PCM_ONE_FRAME_SIZE/2] = {0}; /* 编码需要的一帧(20ms)PCM数据,16bit为单位 */
char acEncOutBuf[AMR_ONE_FRAME_SIZE] = {0}; /* 编码出来的一帧(20ms)AMR数据 */
int iReadPcmBytes = 0; /* 从PCM文件中读取出的数据大小,单位:字节 */
int iEncAmrBytes = 0; /* 编码出的AMR数据大小,单位:字节 */
/* 读出一帧PCM数据 */
iReadPcmBytes = fread(acPcmBuf, 1, PCM_ONE_FRAME_SIZE, fpPcm);
if(iReadPcmBytes <= 0)
{
break;
}
//printf("iReadPcmBytes = %d\n", iReadPcmBytes);
#if 0
/* 编码方式 1:像官方测试程序一样转换为short类型再进行编码 */
for(int i = 0; i < PCM_ONE_FRAME_SIZE/2; i++)
{
unsigned char *p = &acPcmBuf[2*PCM_CHANNELS*i];
asEncInBuf[i] = (p[1] << 8) | p[0];
}
/* 编码 */
iEncAmrBytes = Encoder_Interface_Encode(vpAmr, AMR_ENCODE_MODE, asEncInBuf, acEncOutBuf, 0/* 参数未使用 */);
#else
/* 编码方式 2:传参时直接类型强制转换即可 */
/* 编码 */
iEncAmrBytes = Encoder_Interface_Encode(vpAmr, AMR_ENCODE_MODE, (short *)acPcmBuf, acEncOutBuf, 0/* 参数未使用 */);
#endif
//printf("iEncAmrBytes = %d\n", iEncAmrBytes);
/* 写入到AMR文件中 */
fwrite(acEncOutBuf, 1, iEncAmrBytes, fpAmr);
}
/* 关闭文件 */
fclose(fpAmr);
fclose(fpPcm);
/* 关闭编码器 */
Encoder_Interface_exit(vpAmr);
printf("%s -> %s: Success!\n", argv[1], argv[2]);
return 0;
}