欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

GSM音频编码的优化和写入wav文件

程序员文章站 2024-03-24 23:40:04
...

GSM是voip中较为常见的一种编码,压缩率比很高,写到wav文件每秒只占用1.6k字节(接近于g729),是普通g711格式的五分之一,对录音来说可节省大量磁盘空间。生成的wav文件,可能是不牵涉到专利的原因,在各种操作系统下都能够播放。

1、使用IPP的codec

GSM的编解码通常使用开源C代码,入口文件是gsm_encode.c和gsm_decode.c,能用,但不够优化。

我使用Intel公司著名的IPP库,这个库经过高度优化,cpu占用很小。

实际使用中发现用IPP进行encode后rtp发给对方,对方软电话无声,解不出来。经过跟踪,IPP编码出来的33个字节,第1个字节的前半个字节为0,应该为gsm“魔术字”:0xd,加上这么几句即可:

IppEncode(... ..., outRawBuff);

unsigned char ch = outRawBuff[0];

ch = ch | 0xd0;

outRawBuff[0] = ch;

2、写wav文件头

关于gsm格式的wav文件格式,网上的文章很少,大多语焉不详。

其实掌握要这2个要点就好:

首先,voip一般都是单声道,8000采样率,这个是基本。

然后,GSM encode后的一帧数据是32.5个字节,wav文件当然不能存储半个字节,所以2帧组成一个数据块,即65字节,剩下的都是枝节了:

	... ...
	WAVEFORMATEX m_wfExt;
	int fmtLen = 20;  // fmt块长20字节
	memcpy(FileHeader.dwRiff, "RIFF", 4);
	memcpy(FileHeader.dwWave, "WAVE", 4);
	memcpy(FileHeader.dwFormat, "fmt ", 4);
	FileHeader.dwFormatLength = fmtLen;  // 20
	FileHeader.dwFileSize = contentLen + 20 + fmtLen;
	wavFp.write((char*)&FileHeader, 20);

	m_wfExt.nChannels = 1;  // 单声道
	m_wfExt.nSamplesPerSec = 8000;  // 采样率
	m_wfExt.wFormatTag = WAVE_FORMAT_GSM610;
	m_wfExt.wBitsPerSample = 0;  // 每个采样的位数,gsm编码显然无法凑成整数,所以填0
	m_wfExt.nBlockAlign = 65;  // 块对齐,即一个块的字节数:1个块就是2数据帧65字节
	m_wfExt.nAvgBytesPerSec = 1625; // 评价每秒的字节数,1秒有50个采样周期,2个采样周期为65字节,所以65*25=1625字节
	m_wfExt.cbSize = 2;  // 扩展字段长度,即占2个字节
	WORD wSamplesPerBlock = 320;  // 扩展字段:每个块的采样数,对于GSM来说两帧数据为1块,所以此处填320,即160x2

	char fmtBuff[300];
	memset(fmtBuff, 0, 300);
	memcpy(fmtBuff, &m_wfExt, 18);
	memcpy(fmtBuff + 18, &wSamplesPerBlock, 2);

	wavFp.write(fmtBuff, fmtLen);  // 写入fmt块

3、数据如何写入wav文件?

上面说了,用gsm codec编码或rtp收取到的一帧gsm数据是33字节,有效数据为32.5字节,第一个字节的前4位是没什么用的GSM“魔术字”0xd,但如何将两帧数据合并成65字节的块呢?

最容易想到的办法是将第一帧数据全部前移4bit,剩下半个字节,填充到下一帧的第一个字节,经试验证明不行,播放出来是噪音,后来读gsm源代码,才发现wav文件存储的字节高低位和普通我们认知的高低位是相反的。1个字节8位,按顺序0-7位,我们平常认为最左边是0bit,最右边是7bit,但wav存储的相反,最左边是最高位7bit,最右边是最低位0bit。

要把颠倒了的顺序重新颠倒过来,甚为复杂,此处就不贴代码了,详细可参考gsm的开源代码。

 

 

相关标签: 实现