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;
}