文章目录
Cable Messenger 聊天中进行短语音发送时,可以通过对语音文本数据进行实时分析,生成相关的语音波纹起伏曲线。
此篇文章主要为了倡导大家,在项目开发中,要多思考多实践,不要动手就离不开第三方库,没有第三方库就迈不开腿,甚至去找产品经理去改需求。很多东西其实可以自己写自己实现,而且要多了解和学习技术,多了解事物的本身,做项目不是简单的堆第三方库。
波纹数据的生成与分析
PCM(Pulse Code Modulation,脉冲编码调制)音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字音频数据。
如果是单声道的音频文件,采样数据按时间的先后顺序依次存,如果是双声道的话就按照LRLRLR的方式存储,存储的时候与字节序有关。以位深度为16bit为例,对于双声道的音频文件而言,在每个采样时间间隔内,会同时生成 16 * 2 bit的数字音频数据,以顺序的形式进行存储。
PCM数据,作为设备生成的最原始数据,在进行各种压缩算法和封装格式进行封装后,生成了我们大家所熟知的MP3, AMR 等格式。而当我们接收到各种各样的音频格式,要进行播放前,要反向地对各种格式进行解封装,对相应该压缩后的数据进行还原,还原成原始的PCM数据后才能进行播放。因为本编不是对音频格式理论的详细描述,所以只一笔带过。
Cable Messenger 对于短语音文件格式上,采用了比较适合传输短语音文件的AMR-WB 格式进行传输。与AMR-NB相比,AMR-WB支持的频率区间更大,也是比较常用的AMR格式。
在安卓平台上,原生的播放控件已经完美支持AMR下两种格式文件的播放。而在IOS平台上,声称在过去的版本曾经支持过AMR文件格式的播放。但就现时,原生的播放器还是缺少了对于AMR文本进行直接拆封解压生成PCM数据进行播放的能力。于是IOS端在接收到AMR格式文件时,在播放前就对AMR数据进行了自动的转换,生成PCM格式数据,以WAV封装规范对PCM数据进行封装。在播放的时候交由原生音频播放器进行播放。
PCM作为最原始的音频数据,是生成波纹数据的基础。所以第一步我们要做的就是如何分离PCM数据。
在介绍前,先要了解什么是RIFF资源互换文件格式。RIFF文件由一个或多个“块”组成。每个“块”由“块标识”(4Byte)“长度”(4Byte)“数据”(由前面的长度决定)。
就WAV文件而言,它由一个“块标识”值RIFF
的“块”进行封装。而在这个“块”内部的“数据”中,由一个标准的块组成,“块标识”值为WAVE
。
在WAVE
子块中,又可能存在以fmt
(格式信息) data
(PCM数据)fact
(附加数据)为“块标识”值的三种子块。其中波纹数据就放在 data
“块标识”值的的“数据”中。
好了,说到我自己都绕进去了。因为不是一篇理论型的文章,就不长篇大论的说理论了,有兴趣的话,这些资料到处都可以查来。
以下以Objective C 代码为例给出PCM数据的取值方法,因为只关注于PCM数据的取值,其它的块信息就不给出分析代码了。
以下给出的代码中,以位深度为16bit,单声道,采用均衡采样的方式进行采样生成的PCM数据为例。
#pragma mark - 分析wav的声纹曲线, 返回声纹数据
+ (nullable NSData*) decodePCM:(nonnull NSString*)path{
NSData *wavData = [NSData dataWithContentsOfFile:path];
if wavData == nil{
return null;
}
int index = 0;
int dataSize = 0;
BOOL enable = NO;
NSData *riifData = nil;
//1. 先判断文件是否是标准RIFF格式
NSData *dType = [wavData subdataWithRange:NSMakeRange(0, 4)];
NSString *sType = [[NSString alloc] initWithData:dType encoding:NSUTF8StringEncoding];
if([@"RIFF" isEqualToString:fileType]){
enable = YES;
//2.取得RIFF数据长度
int riifSize;
[[wavData subdataWithRange:NSMakeRange(4, 4)] getBytes:&riifSize length:sizeof(riifSize)];
//判断 是否是 WAVE 格, WAVE格式后面会有 format 和 data chunk
NSData *dWave = [wavData subdataWithRange:NSMakeRange(8, 4)];
NSString *sWave = [[NSString alloc] initWithData:dWave encoding:NSUTF8StringEncoding];
if ([@"WAVE" isEqualToString:sWave] == NO){
enable = NO;
}
//3.截取 WAVE chunk 数据
if (riifSize > 0){
riifSize = riifSize - 4;
riifData = [wavData subdataWithRange:NSMakeRan