英特尔 IPP:实施宽带编解码器 VoIP 解决方案

 作者 Karthik Krishnan

了解英特尔® 集成性能基元(英特尔® IPP)如何提供构建模块来开发具备高级功能的 VoIP 应用程序。了解构建模块,以构建完整的 Softphone 应用程序。

IP 电话 (VoIP) 通过将语音和数据合并到一个 IP 网络,正在从根本上改变电信行业。英特尔可以提供各种产品、服务和构建模块,以在各个域中实现 VoIP 解决方案。英特尔® 集成性能基元(英特尔® IPP)是一种软件库,可以提供各种高度优化的功能,例如多媒体和语音编解码器。本文提供有关将英特尔® IPP 用于语音编解码器以及 VoIP Softphone 完整实现的参考要点。已使用用于网络通信的 Windows Sockets*、用于音频捕获和播放的 DirectSound* 以及使用英特尔 IPP 的宽带编解码器(GSM-AMR 自适应多速率)构建了示例应用程序。

英特尔® 集成性能基元
英特尔 IPP 是一种高度优化的跨平台库,包括与多媒体和通信软件有关的各种功能。G.168、G.167、G.711、G.722、G.722.1、G.722.2、AMRWB、G.723.1、G.726、G.728、G.729、GSM-AMR 和 GSM-FR 是由 国际电信联盟 (ITU)*、 欧洲电信标准协会 (ETSI)*、 3GPP* 和其他组织推出的国际标准。以下列表是使用英特尔集成性能基元作为完全符合标准的构建模块构建的语音编码示例。

语音编码示例Windows*Linux*
G.722.1
GSM/WMR WB / G.722.2
G.723.1
G.726 
G.728
G.729
GSM-AMR
GSM-FR 
请注意,实现这些标准或符合标准的平台,可能需要获得各个实体的许可,其中包括英特尔公司。本文使用 ITU GSM-AMR(自适应多速率)作为 VoIP 呼叫期间要使用的标准编解码器。

链接模型
英特尔 IPP 提供将应用程序代码与库相链接的各种机制,例如静态链接、动态链接和自动调度。有关详细信息,请参阅 链接模型 (PDF 231 KB)。所含的 Softphone 应用程序(请参阅“其他资源”部分中的链接)使用动态链接和自动调度。

GSM-AMR
GSM-AMR 具有每采样 16 位、16 KHz 的采样速率,并支持各种输出比特率(6.6kbps、8.85kbps 等)。下表列出了英特尔 IPP 提供的所有受支持比特率和相应的每帧输出大小(即采用 600 字节的 20ms 音频输入)。

帧类型GSM AMR-WB(以 kbps 为单位的比特率)每帧输出比特数
06.6132
18.85177
212.65253
314.25285
415.85317
518.25365
619.85397
723.05461
统一语音编解码器 API
语音编解码器示例使用英特尔 IPP 作为构建模块,并含有与标准完全符合的所有受支持编解码器的完整实现。英特尔 IPP 5.0 中的示例代码还包含可促进所有编解码器集成的统一方法。以下部分介绍有关使用统一语音编解码器 (USC) 方法集成 GSM-AMR 编解码器编码和解码功能的一些启示。
USC 初始化 API
#ifdef __cplusplus
extern "C" {
#endif
extern USC_Fxns USC_AMRWB_Fxns;
#ifdef __cplusplus
}
#endif
//USC_xxx_Fxns 为所有编解码器的模板
static USC_Fxns *USC_Codec_Fxn = 
static int nbanksEnc = 0,nbanksDec = 0;
static USC_MemBank* pBanksEnc = NULL;
static USC_MemBank* pBanksDec = NULL;
static USC_Handle hUSCEncoder;
static USC_Handle hUSCDecoder;
static USC_CodecInfo pInfo;


//这将分配内存并初始化 AMR-WB 编码器/解码器
//句柄。

int InitializeCodec(int bitrate)
{
int i;
FreeCodecMemory();

ippStaticInitBest(); //选择最优化代码 


/* 获得 Gxxx 编解码器信息 */

if (USC_NoError != USC_Codec_Fxn->std.GetInfo(
(USC_Handle)NULL,    &pInfo))
return -1;

/*
编码器实例创建
*/
pInfo.params.direction = 0;             /* 方向:编码 */
pInfo.params.modes.vad = 0;  /* 禁止静音压缩 */
pInfo.params.law = 0;                    /* 线性 PCM 输入 */
pInfo.params.modes.bitrate = bitrate;

/* 了解需要将多少内存块用于编码器 */
if(USC_NoError != USC_Codec_Fxn->std.NumAlloc(
&pInfo.params, &nbanksEnc))
return -1;

/* 为内存库表分配内存 */
pBanksEnc = (USC_MemBank*)malloc(sizeof(USC_MemBank)*nbanksEnc);


/* 查询每个内存块必需的大小 */
if(USC_NoError != USC_Codec_Fxn->std.MemAlloc(
&pInfo.params, pBanksEnc))
return -1;


/* 分配每个内存块的内存 */
for(i=0; i<nbanksEnc;i++)
{
pBanksEnc[i].pMem = (char*)malloc(pBanksEnc[i].nbytes);
}

/* 创建编码器实例 */
if(USC_NoError != USC_Codec_Fxn->std.Init(
&pInfo.params, pBanksEnc, &hUSCEncoder))
return -1;



/*
解码器实例创建
*/
pInfo.params.direction = 1;             /* 方向:解码 */


/* 了解需要将多少内存块用于解码器 */
if(USC_NoError != USC_Codec_Fxn->std.NumAlloc(
  &pInfo.params, &nbanksDec))
return -1;


/* 为内存库表分配内存 */
pBanksDec = (USC_MemBank*)malloc(sizeof(USC_MemBank)*nbanksDec);


/* 查询每个内存块必需的大小 */
if(USC_NoError != USC_Codec_Fxn->std.MemAlloc(&pInfo.params, pBanksDec))
return -1;


/* 分配每个内存块的内存 */
for(i=0; i<nbanksDec;i++)
{
pBanksDec[i].pMem = ( char*)malloc(pBanksDec[i].nbytes);
}
/* 创建解码器实例 */
if(USC_NoError != USC_Codec_Fxn->std.Init(
  &pInfo.params, pBanksDec, &hUSCDecoder))
return -1;
return 1;
}  
USC 编码 API
/* 假定初始化已完成。一旦启动了 VoIP 呼叫,示例 Softphone 就不可更改比特率。进行修改以支持每帧可变比特率是直接了当的做法。 */

int EncodeOneFrame(char *src,char *dst) //速率已确定
{
USC_PCMStream in;
USC_Bitstream out;


in.pBuffer = src;
out.pBuffer = dst;

in.bitrate = pInfo.params.modes.bitrate;
in.nbytes = pInfo.framesize;
in.pcmType.bitPerSample = pInfo.pcmType.bitPerSample;
in.pcmType.sample_frequency = pInfo.pcmType.sample_frequency;


/* 对帧进行编码  */
if(USC_NoError != USC_Codec_Fxn->std.Encode (hUSCEncoder,
&in, &out))
{
DebugBreak(); //不应该发生
return -1;
}
return out.frametype;
}     
USC 解码 API
int DecodeOneFrame(char *src,char *dst,int frametype)
{
USC_Bitstream in;
USC_PCMStream out;

in.pBuffer = src;
in.frametype = frametype;//RX_SPEECH_GOOD ;
in.bitrate = pInfo.params.modes.bitrate;
/* EvaluateEncodedByteSize 应返回受支持比特率的输出
   字节大小。请注意,应将此值舍入为
   最接近的下限值
*/
in.nbytes = EvaluateEncodedByteSize(in.bitrate);

out.pBuffer = dst;
out.pcmType.bitPerSample = pInfo.pcmType.bitPerSample;
out.pcmType.sample_frequency = pInfo.pcmType.sample_frequency;
out.bitrate = pInfo.params.modes.bitrate;


if(USC_NoError != USC_Codec_Fxn->std.Decode (
hUSCDecoder, &in, &out))
return -1;
return out.nbytes;


音频捕获/播放
编码器采用 16 位线性 PCM 数据输入,该输入为模拟信号(例如语音)值数字化后的非压缩的纯二进制代码表示。解码器采用编码器的压缩数据作为输入并输出原始 PCM 文件。本部分说明使用 DirectSound* 以所需的采样频率(16KHz 的 GSM-AMR)捕获和播放原始 PCM 文件。

Microsoft DirectSound 提供各种 API 来捕获和播放音频内容。所附加的 Softphone 应用程序使用 Microsoft 平台 SDK 提供的示例代码捕获和播放音频。本部分说明更详细的实现详细信息。

音频捕获的工作方式是创建循环缓冲区,以便以原始的 PCM 格式存储捕获的音频数据。在初始化阶段设置并分配采样率、每采样比特大小(16 KHz、16 位)和捕获缓冲区的总大小。DirectSound 还提供在每次缓冲区中捕获一定数量的音频数据时就触发事件对象的方法。通常音频捕获是在单独的线程中处理的,音频提取线程会定期访问这些事件对象,以提取所捕获的音频数据。以下部分介绍使用 DirectSound 捕获音频的控制流。

DirectSound* 音频捕获线程控制流



音频数据提取线程控制流
所捕获的音频数据需要定期提取(通常为每帧提取一次),以将其传递给编码器。可使用 timeSetEvent() API 定期(例如每 20ms)执行提取功能。以下部分概述了提取功能。



请注意,由于缓冲区是循环式的,所以提取原始 PCM 数据分两阶段进行,且所捕获的数据可能已在分配内存的端部换行。捕获功能和提取功能在单独的线程中运行。也可能未访问其中的某些通知事件,这些事件可能&ldquo;丢失&rdquo;。在这种情况下,将在下一个信号提取相应的 PCM 数据。


音频播放控制流
播放代码的工作方式类似于音频捕获的工作方式。播放缓冲区需要使用其他扬声器中的原始 PCM 音频内容填满。一收到编码的数据包,就将其传递给解码器,以提取源 PCM 数据。锁定播放缓冲区,将 PCM 数据复制到缓冲区然后播放。应考虑抖动影响,方法为播放 PCM 数据前保持一个阈值。




网络层
示例应用程序使用带有 TCP/IP 的 Windows Sockets 作为网络传输在节点间传递语音包。Softphone 允许多用户会议并支持各种比特率的 GSM-AMR 编解码器。发起程序(或主机)的作用类似服务器,在指定的端口侦听所有传入呼叫。在指定的端口将其他 VoIP 扬声器连接到该主机。主机等待传入的连接,直至超时。主机在超时阶段后广播所有连接节点的 IP 地址。之后建立将所有 VoIP 参与者彼此连接的星形网络。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值