Bootstrap

音频demo:使用opencore-amr、vo-amrwbenc将PCM数据与AMR-WB数据进行编解码

1、README

前言

  • AMR-NB(AMR-NarrowBind):语音带宽范围:300 - 3700Hz,8KHz采样频率;
  • AMR-WB(AMR-WideBand):语音带宽范围50 - 7000Hz,16KHz采样频率。

不管NB还是WB,一帧都是20ms,一秒50帧。

a. 开源库下载地址

opencore-amr-0.1.3.tar.gz:支持amr-nb的编码与解码、amr-wb的解码,但不支持amr-wb的编码;

vo-amrwbenc-0.1.3.tar.gz:仅支持amr-wb的编码。

opencore-amr/vo-amrwbenc的下载地址:https://sourceforge.net/projects/opencore-amr/files/

b. 编译开源库
opencore-amr
#!/bin/bash

tar xzf opencore-amr-0.1.3.tar.gz
cd opencore-amr-0.1.3/
./configure --prefix=$PWD/_install # --host=arm-linux-gnueabihf CC=arm-linux-gnueabihf-gcc
make -j96
make install
vo-amrwbenc
#!/bin/bash

tar xzf vo-amrwbenc-0.1.3.tar.gz
cd vo-amrwbenc-0.1.3
./configure --prefix=$PWD/_install # --host=arm-linux-gnueabihf CC=arm-linux-gnueabihf-gcc
make -j96
make install
c. demo使用
  • 编译
make
# 或者打开编译调试信息
make clean && make DEBUG=1
  • 使用
# 编码
$ ./pcm2amrwb
Usage:
         ./pcm2amrwb ./audio/test_16000_16_1.pcm out1.amr
         ./pcm2amrwb ./audio/test_16000_16_2.pcm out2.amr (need to modify PCM_CHANNELS=2)

# 解码
$ ./amrwb2pcm
Usage:
         ./amrwb2pcm ./audio/test.amr out_16000_16_1.pcm
d. 参考资料
e. demo目录架构
$ tree
.
├── audio
│   ├── test_16000_16_1.pcm
│   ├── test_16000_16_2.pcm
│   └── test.amr
├── include
│   ├── dec_if.h
│   └── enc_if.h
├── lib
│   ├── libopencore-amrwb.a
│   └── libvo-amrwbenc.a
├── main_amrwb2pcm.c
├── main_pcm2amrwb.c
├── Makefile
├── README.md
└── reference
    ├── amrwb-dec.c
    ├── amrwb-enc.c
    └── AMR编码文件解析_hanzhen7541的博客-CSDN博客.mhtml

2、主要代码片段

main_pcm2amrwb.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "enc_if.h"


// 编译时Makefile里控制
#ifdef ENABLE_DEBUG
    #define DEBUG(fmt, args...)     printf(fmt, ##args)
#else
    #define DEBUG(fmt, args...)
#endif


/* PCM参数 */
#define PCM_SAMPLERATE 	(16000) /* 只能编码 8 khz */
#define PCM_SAMPLEBITS 	(16) 	/* 只支持16位 */
#define PCM_CHANNELS 	(1) 	/* 不管PCM输入是单声道还是双声道,这里输出的amr都是单声道的 */

/* amr一帧数据是20ms,一秒50帧。16000,16,1 ==> 640 Bytes */
#define PCM_ONE_FRAME_SIZE  (PCM_SAMPLERATE/50 * PCM_SAMPLEBITS/8 * PCM_CHANNELS)

/*******************
      AMR参数
   mode    rate
    0      6600
    1      8850
    2     12650
    3     14250
    4     15850
    5     18250
    6     19850
    7     23050
    8     23850
 *******************/
#define AMR_ENCODE_MODE 	(8)


/* 是否使能背景噪声编码模式 */
#define DTX_DECODE_ENABLE 	1
#define DTX_DECODE_DISABLE 	0
#define DTX_DECODE_MODE 	DTX_DECODE_ENABLE



int main(int argc, char *argv[])
{
	int mode = AMR_ENCODE_MODE;
	int dtx = DTX_DECODE_MODE;
	void *vpAmr = NULL;
	FILE *fpAmr = NULL;
	FILE *fpPcm = NULL;
	uint8_t u8PcmBuf[PCM_ONE_FRAME_SIZE] = {0}; 		/* 保存在文件中一帧(20ms)PCM数据,8bit为单位,这里是unsigned */
	int16_t u16EncInBuf[PCM_ONE_FRAME_SIZE/2] = {0}; 	/* 编码需要的一帧(20ms)PCM数据,16bit为单位 */
	uint8_t u8EncOutBuf[PCM_ONE_FRAME_SIZE] = {0};		/* 编码出来的一帧(20ms)AMR数据(一般装不满) */
	int iReadPcmBytes = 0; 								/* 从PCM文件中读取出的数据大小,单位:字节 */
	int iEncAmrBytes = 0; 								/* 编码出的AMR数据大小,单位:字节 */


	/* 检查参数 */
	if(argc != 3)
	{
		printf("Usage: \n"
			   "\t %s ./audio/test_16000_16_1.pcm out1.amr\n"
			   "\t %s ./audio/test_16000_16_2.pcm out2.amr (need to modify PCM_CHANNELS=2)\n", argv[0], 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 = E_IF_init();
	if(vpAmr == NULL)
	{
		printf("E_IF_init() error!\n");
		goto exit;
	}

	/* 打开文件 */
	fpPcm = fopen(argv[1], "rb");
	if(fpPcm == NULL)
	{
		perror("argv[1]");
		goto exit;
	}

	fpAmr = fopen(argv[2], "wb");
	if(fpAmr == NULL)
	{
		perror("argv[2]");
		goto exit;
	}

	/* 先写入amr-wb的头部 */
	fwrite("#!AMR-WB\n", 1, 9, fpAmr);

	/* 循环编码 */
	while(1)
	{
		/* 读出一帧PCM数据 */
		iReadPcmBytes = fread(u8PcmBuf, 1, PCM_ONE_FRAME_SIZE, fpPcm);
		if(iReadPcmBytes < PCM_ONE_FRAME_SIZE)
		{
			break;
		}
		DEBUG("[in pcm bytes: %d]\t", iReadPcmBytes);

		/* 转换类型 */
		for(int i = 0; i < PCM_ONE_FRAME_SIZE/2; i++)
		{
			uint8_t *p = &u8PcmBuf[2*PCM_CHANNELS*i];
			u16EncInBuf[i] = (p[1] << 8) | p[0];
		}

		/* 编码 */
		iEncAmrBytes = E_IF_encode(vpAmr, mode, u16EncInBuf, u8EncOutBuf, dtx);

		DEBUG("[out amr-wb bytes: %d]\n", iEncAmrBytes);

		/* 写入到AMR文件中 */
		fwrite(u8EncOutBuf, 1, iEncAmrBytes, fpAmr);
	}

	printf("%s -> %s: Success!\n", argv[1], argv[2]);

exit:

	/* 关闭文件 */
	if(fpAmr)
		fclose(fpAmr);

	if(fpPcm)
		fclose(fpPcm);

	/* 关闭编码器 */
	if(vpAmr)
		E_IF_exit(vpAmr);

	return 0;
}
main_amrwb2pcm.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include "dec_if.h"


// 编译时Makefile里控制
#ifdef ENABLE_DEBUG
    #define DEBUG(fmt, args...)     printf(fmt, ##args)
#else
    #define DEBUG(fmt, args...)
#endif


/* amrwb解码出来的PCM就是这个参数 */
#define PCM_SAMPLERATE  (16000)
#define PCM_SAMPLEBITS  (16)
#define PCM_CHANNELS    (1)

/* amr一帧数据是20ms,一秒50帧。16000,16,1 ==> 640 Bytes */
#define PCM_ONE_FRAME_SIZE  (PCM_SAMPLERATE/50 * PCM_SAMPLEBITS/8 * PCM_CHANNELS)



int main(int argc, char *argv[])
{
	void *vpAmr = NULL;
	FILE *fpAmr = NULL;
	FILE *fpPcm = NULL;
	uint8_t u8AmrHeader[9] = {0};
	int iReadBytes = 0;
	int iFrameSize = 0; 		// 根据AMR文件每帧的头部获取该帧数据大小
	const int iFrameSizes[] = { 17, 23, 32, 36, 40, 46, 50, 58, 60, 5, -1, -1, -1, -1, -1, 0 };
	uint8_t u8AmrBuf[PCM_ONE_FRAME_SIZE] = {0}; 	// 给予足够的空间,一般装不满
	uint8_t u8PcmBuf[PCM_ONE_FRAME_SIZE] = {0}; 	// 解码出来的是以8bit为单位
	int16_t u16DecBuf[PCM_ONE_FRAME_SIZE/2] = {0}; 	// 解码出来的是以16bit为单位


	/* 检查参数 */
	if(argc != 3)
	{
		printf("Usage: \n"
			   "\t %s ./audio/test.amr out_16000_16_1.pcm\n", argv[0]);
		return -1;
	}

	/* 初始化解码器 */
	vpAmr = D_IF_init();
	if(vpAmr == NULL)
	{
		printf("Decoder_Interface_init() error!\n");
		goto exit;
	}

	/* 打开文件 */
	fpAmr = fopen(argv[1], "rb");
	if(fpAmr == NULL)
	{
		perror("argv[1]");
		goto exit;
	}
	fpPcm = fopen(argv[2], "wb");
	if(fpPcm == NULL)
	{
		perror("argv[2]");
		goto exit;
	}

	/* 判断是否为AMR文件 */
	iReadBytes = fread(u8AmrHeader, 1, 9, fpAmr);
	if (iReadBytes != 9 || memcmp(u8AmrHeader, "#!AMR-WB\n", 9)) {
		printf("%s is not a amr(wb) file!\n", argv[1]);
		goto exit;
	}

	/* 循环解码 */
	while(1)
	{
		/* 获取AMR规格 */
		iReadBytes = fread(u8AmrBuf, 1, 1, fpAmr);
		if(iReadBytes <= 0)
			break;

		/* 获取一帧的大小 */
		iFrameSize = iFrameSizes[(u8AmrBuf[0] >> 3) & 0x0F];

		/* 读取一帧AMR数据,需要注意的是记得偏移一个地址 */
		iReadBytes = fread(&u8AmrBuf[1], 1, iFrameSize, fpAmr);
		if(iFrameSize != iReadBytes)
			break;

		DEBUG("[in amr bytes: %d]\t", 1 + iReadBytes);
#if 0
		/* 解码方式 1:像官方测试程序一样解码出来存到short类型的缓存里 */
		/* 将AMR数据解码 */
		D_IF_decode(vpAmr, u8AmrBuf, u16DecBuf, 0/* 参数未使用 */);

		uint8_t *p = u8PcmBuf;
		for(int i = 0; i < PCM_ONE_FRAME_SIZE/2; i++)
		{
			*p++ = (u16DecBuf[i] >> 0) & 0xff;
			*p++ = (u16DecBuf[i] >> 8) & 0xff;
		}
#else
		/* 解码方式2:传参时直接强制类型转换即可 */
		/* 将AMR数据解码 */
		D_IF_decode(vpAmr, u8AmrBuf, (short *)u8PcmBuf, 0/* 参数未使用 */);
#endif

		DEBUG("[out pcm bytes: %d]\n", PCM_ONE_FRAME_SIZE);

		fwrite(u8PcmBuf, 1, PCM_ONE_FRAME_SIZE, fpPcm);
	}

	printf("%s -> %s : Success!\n", argv[1], argv[2]);

exit:

	/* 关闭文件 */
	if(fpAmr)
		fclose(fpAmr);
	if(fpPcm)
		fclose(fpPcm);

	/* 关闭解码器 */
	if(vpAmr)
		D_IF_exit(vpAmr);

	return 0;
}

3、demo下载地址(任选一个)

;