PCM⾳频波形的绘制以及注意事项
最近在项⽬中有个需求,公司有⼀个设备,能够获取⾳频,⾳频传输过来,解码后就播放了,需求就是播放时,根据声⾳进⾏添加波形动画,⽹上有⼀些资源,⼤多都是⽤AVAudioRecorder的averagePowerForChannel⽅法,获取麦上获取到的⾳量,然后动画显⽰,如果是⼀个PCM码流的话,需要⾃⼰获取对应的⾳量信息,⽽后显⽰在动画上了,后⾯我找了⼀些资料,把PCM的数据解析出⾳量信息,然后再显⽰,效果还可以,发出来参考⼀下。
先来点基础的知识,⾸先是采样频率,指每秒钟取得声⾳样本的次数,采样频率越⾼,包含的声⾳信息⾃然就越多,声⾳也就越好,频率越⾼,保存需要的空间也会⾼,所以不⼀定越⾼越好,看实际需求。
8和2
采样位宽,即采样值,⼀般分为8位和16位,可以表⽰的范围分别是216的区间,区间越⼤,分辨率也就越⼤,发出声⾳⼤能⼒也就越强,同样的,位宽越⼤,需要的空间也就越⼤。
我的恶魔弟弟声道数,分为单声道和双声道,双声道即⽴体声。
另外⼀些信息,需要⼀些理解能⼒,链接中有详细的过程
临产前的征兆有哪些
dB = 20×log(data^2),data是从PCM中获取到的对应位宽的数据,例如,如果是8位就是⼀个字节,如果是16位就是2个字节
Y = A×sin(2×M_PI×X+Pha),X是横坐标,pha是相位
Y = (cos(M_PI+2×M_PI×X)+1)/2,X是0~1之间的⼀个值
再来分析⼀下我需求中的⼀些信息,我的解码后获取到的PCM码流是位宽为16位,采样频率为16KHz的单声道数据,每秒钟的码流解码后的PCM数据会被分为5个包,通过计算,每个包的⼤⼩是(16×16000×1/8)/5 = 6400(字节),在程序中,我们使⽤的是CADisplayLink的定时刷新功能,以和屏幕⼀样的刷新频率刷新,也就是60Hz,也就是说,我们应该保持让屏幕每隔1/60秒就更新到⼀个新数据,所以,解码后,每秒的数据应该被分割成60个⾳量值,也就是说,五个包,每个包有6400个字节,⼀个包可以获取到12个⾳量值,⼤概每1600/3个字节就得取到⼀个⾳量平均值,这样就可以简单的实现在屏幕上显⽰⼀段⾳频波形动画了,不过,要注意的是,虽然每隔1/60秒刷新⼀个新的数据可以让你的波形得到接近表现真实的⾳频,但是会导致动画的效果会发⽣类似抖动的效果,因为,相邻的每个波形直接,⾳量差异可能⽐较⼤,从⼀个波形到另⼀个波形的跨度⼤的话,在切换过去的时候就会出现跳过去的感觉,也就是抖动,解决这种抖动现象需要⽤到插值,先把从PCM数据取⾳量的次数降下来,原来每个⼩包取6400个字节取12个⾳量值,你改为取4次,也就是,每6400/4=1600字节
就取⼀个⾳量值,然后两个⾳量值之间再通过插值的⽅法,取2个值,我这边直接简单的⽤⼀次线性插值取值,这样可以使抖动不那么明显,甚⾄看不出来,如果还有明显的抖动,可以以此类推,再减少取值数量,增加插值数量。
static NSRunLoop* _voiceWaveRunLoop;
#pragma mark - MCVolumeQueue
@interface MCVolumeQueue()
@property (nonatomic, strong) NSMutableArray* volumeArray;
@end
@implementation MCVolumeQueue
-(instancetype)init
{
lf = [super init];
if (lf) {
lf.volumeArray = [NSMutableArray array];
}
return lf;
}
-(void)pushVolume:(CGFloat)volume
{
if (volume >= minVolume) {
[_volumeArray addObject:[NSNumber numberWithFloat:volume]];
}
}
怎么练武功
-(void)pushVolumeWithArray:(NSArray *)array
{
if (unt > 0) {
for (NSInteger i = 0; i < unt; i++) {
CGFloat volume = [array[i] floatValue];
[lf pushVolume:volume];
}
}
}
-(CGFloat)popVolume
{
CGFloat volume = -10;
if (_unt > 0) {
volume = [[_volumeArray firstObject] floatValue];
[_volumeArray removeObjectAtIndex:0];
}
return volume;
}
-(void)cleanQueue
{
if (_volumeArray) {
[_volumeArray removeAllObjects];
}
}
@end
#pragma mark - MCVoiceWaveView
@interface MCVoiceWaveView(){
CGFloat _idleAmplitude;//最⼩振幅
CGFloat _amplitude;//振幅系数,表⽰⾳量在屏幕上⾼度的⽐例
CGFloat _density;//X轴粒度,粒度越⼩,线条越顺
CGFloat _waveHeight;//波形图所在view的⾼
CGFloat _waveWidth;//波形图所在view的宽
CGFloat _waveMid;//波形图所在view的中点
CGFloat _maxAmplitude;//最⼤振幅
//可以多画⼏根线,使声波波形看起来更复杂真实
CGFloat _pha;//初始相位位移
CGFloat _phaShift;//_pha累进的相位位移量,造成向前推移的感觉
CGFloat _frequencyFirst;//firstLine在view上的频率
CGFloat _frequencySecond;//condLine在view上的频率
//
CGFloat _currentVolume;//⾳量相关
CGFloat _lastVolume;
CGFloat _middleVolume;
//
CGFloat _maxWidth;//波纹显⽰最⼤宽度
CGFloat _beginX;//波纹开始坐标
CGFloat _stopAnimationRatio;//衰减系数,停⽌后避免⾳量过⼤,波纹振幅⼤,乘以衰减系数 BOOL _isStopAnimating;//正在进⾏消失动画
//
UIBezierPath* _firstLayerPath;
UIBezierPath* _firstLayerPath;
UIBezierPath* _condLayerPath;
职场说话技巧}
@property (nonatomic, strong) CADisplayLink* displayLink;
@property (nonatomic, strong) CAShapeLayer* firstShapeLayer;
@property (nonatomic, strong) CAShapeLayer* condShapeLayer;
@property (nonatomic, strong) CAShapeLayer* fillShapeLayer;
//
@property (nonatomic, strong) UIImageView* firstLine;
@property (nonatomic, strong) UIImageView* condLine;
@property (nonatomic, strong) UIImageView* fillLayerImage;
//
月儿圆圆@property (nonatomic, strong) MCVolumeQueue* volumeQueue;
@end
@implementation MCVoiceWaveView
-(void)tup
{
_frequencyFirst = 2.0f;//2个周期
_frequencySecond = 1.8f;//1.6个周期,更平缓,有点周期差,使图像看起来更有错落感 _amplitude = 1.0f;
_idleAmplitude = 0.01f;
_pha = 0.0f;
_phaShift = -0.22f;
_density = 1.f;
_waveHeight = CGRectGetHeight(lf.bounds);
_waveWidth = CGRectGetWidth(lf.bounds);
_waveMid = _waveWidth / 2.0f;
泵站是做什么的_maxAmplitude = _waveHeight * 0.5;
_maxWidth = _waveWidth + _density;
_beginX = 0.0;
_lastVolume = 0.0;
_currentVolume = 0.0;
_middleVolume = 0.01;
_stopAnimationRatio = 1.0;
[_volumeQueue cleanQueue];
}
-(instancetype)init
{
lf = [super init];
if (lf) {
[lf startVoiceWaveThread];
}
return lf;
}
-(void)dealloc
自制辣椒面
{
[_displayLink invalidate];
}
-(void)voiceWaveThreadEntryPoint:(id)__unud object
{
@autoreleapool {
[[NSThread currentThread] tName:@"com.anxin-net.VoiceWave"];
_voiceWaveRunLoop = [NSRunLoop currentRunLoop];
监督管理
[_voiceWaveRunLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [_voiceWaveRunLoop run];
}
}
-(NSThread*)startVoiceWaveThread
{
static NSThread* _voiceWaveThread = nil;
static dispatch_once_t oncePredicate;