语音输入声波动画。AVAudioRecorder+三角函数,实现根据声音分贝控制振幅声波动画

前言:很久没写东西了,最近项目需要用到语音识别,需要做一个输入语音,根据声音的分贝大小,控制波纹振幅的动画,看了别人的轮子,感觉效果不错。最近比较咸鱼就自己摸索着大概模仿实现了一下。代码并不复杂请耐心观看。

效果图:


效果.GIF

实现思路:

  • 1、首先获取输入声音的分贝大小变化值。
  • 2、绘制一条波纹曲线(曲线需具备两个性质,①能通过一个值控制曲线绘制的弧度,②实时平移实现流动效果)。
  • 3、波纹曲线根据声音分贝大小控制振幅。

现在按照思路来一步一步实现。

1、声音输入并获取分贝大小。

简单音频处理首先想到的就是使用AVFoundation,使用起来比较简单,我们需要的效果能实现,像AudioToolbox 的AudioQueue虽然强大但是学习成本太高。

使用AVFoundationAVAudioRecorder来实现录音及获取分贝大小。

1)、设置录音参数。
- (NSDictionary *)audioParamSetting {
    NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc]init];
    //设置录音格式
    [recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM ] forKey:AVFormatIDKey];
    //设置录音采样率(Hz) 如:AVSampleRateKey==8000/44100/96000(影响音频的质量)
    [recordSetting setValue:[NSNumber numberWithFloat:8000] forKey:AVSampleRateKey];
    //录音通道数  1 或 2
    [recordSetting setValue:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey];
    //线性采样位数  8、16、24、32
    [recordSetting setValue:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
    //录音的质量
    [recordSetting setValue:[NSNumber numberWithInt:AVAudioQualityHigh] forKey:AVEncoderAudioQualityKey];
    
    return recordSetting;
}
2)、设置录音保存路径,并初始化AVAudioRecorder,进入录音准备状态。
- (void)_initVoice {
    // 初始化录音参数
    NSDictionary *recordSetting = [self audioParamSetting];
    
    NSString *strUrl = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/Video.pcm", strUrl]];
    
    NSError *error;
    //初始化
    audioRecorder = [[AVAudioRecorder alloc]initWithURL:url settings:recordSetting error:&error];
    
    //开启音量检测
    audioRecorder.meteringEnabled = YES;

    audioRecorder.delegate = self;
    
    // 设备开启录音模式
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    [audioSession setActive:YES error:nil];
    
    //创建录音文件,准备录音
    BOOL prepareSuccess = [audioRecorder prepareToRecord];
    NSLog(@"准备录音: %d", prepareSuccess);
}

现在只需要在点击开始时调用[audioRecorder record];方法就行了,相对停止调用[audioRecorder stop];

3)、获取声音分贝大小。
- (void)detectionVoice {
    [audioRecorder updateMeters];//刷新音量数据
    
    //获取音量的平均值  [recorder averagePowerForChannel:0];
    //音量的最大值  [recorder peakPowerForChannel:0];
    double lowPassResults = pow(10, (0.1 * [audioRecorder peakPowerForChannel:0]));
    NSLog(@"%f", lowPassResults);
}

通过AVAudioRecorderpeakPowerForChannel方法可以获取声音分贝峰值大小,但是获取值的范围是 -160~0 。声音峰值越大,越接近0。需要直接使用需要像上面做一下处理。

到此声音输入并获取声音分贝大小实现了。


接下来是实现 绘制一条波纹曲线(曲线需具备两个性质,①能通过一个值控制曲线绘制的弧度,②实时平移实现流动效果)。

首选熟悉的三角函数,y = Asin(ωx+φ)+ C
简单解释一下:
三角函数公式:y = Asin(ωx+φ)+ C
A:振幅,波纹在Y轴的高度,成正比,越大Y轴峰值越大。
ω:和周期有关,越大周期越短。
φ:横向偏移量,控制波纹的移动。
C:整个波纹的Y轴偏移量。

通过以上可以基本了解我们需要定义和控制的参数了。首先是波纹绘制显示的一个CAShapeLayer,再是一个控制振幅的参数CGFloat kRippleYMax,再是一个控制控制平移的偏移量CGFloat rippleOffset,最后是一个实时刷新的定时器CADisplayLink(控制波纹实时刷新,动起来)。


CADisplayLink简单说明

可能有些人对CADisplayLink比较陌生,下面简单说明一下CADisplayLink,还有和常用定时器NSTimer的区别。了解的可忽略以下说明。
CADisplayLink也是定时器的一种。是一个能让屏幕刷新率相同的频率,将内容画到屏幕上的定时器,设备屏幕刷新周期是1秒60次。
基本使用:

// 创建
CADisplayLink * displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeRipple)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
// 开始
self.displayLink.paused = NO;
// 停止
self.displayLink.paused = YES;
// 销毁
[self.displayLink invalidate];

和NSTimer区别:

  • 1、原理不同。
    每当屏幕显示内容刷新结束的时候,runloop就会向 CADisplayLink指定的target发送一次指定的selector消息,selector方法就会被调用一次。
    NSTimer当设定的周期时间到达后,runloop会向指定的target发送一次指定的selector消息。
  • 2、周期设置不同。
    CADisplayLink在不设置frameInterval时默认是1秒调用60次。
    NSTimer通过TimeInterval直接指定周期。
  • 3、精确度不同。
    CADisplayLink的精度更高,因为是通过屏幕刷新频率控制周期的,所有精度高。
    NSTimer精度低,当前runloop阻塞时,会推迟到下一个runloop周期执行。所以NSTimer有tolerance设置定时器的延迟范围。
  • 4、应用场景不同。
    CADisplayLink一般适合配合UI刷新重绘使用。
    NSTimer使用灵活,适合自定义循环任务。

理解CADisplayLink定时器后,下面开始绘制波纹曲线了。
首先设置懒加载波纹和定时器

#pragma mark 波纹
- (CAShapeLayer *)rippleLayer{
    if (_rippleLayer == nil) {
        CAShapeLayer * shapeLayer = [CAShapeLayer layer];
        shapeLayer.fillColor = [UIColor colorWithRed:145/255.0f green:186/255.0f blue:248/255.0f alpha:1].CGColor;
        shapeLayer.lineWidth = 2.0f;
        shapeLayer.strokeColor = [UIColor whiteColor].CGColor;
        [self.layer addSublayer:shapeLayer];
        _rippleLayer = shapeLayer;
    }
    return _rippleLayer;
}
#pragma mark 定时器
- (CADisplayLink *)displayLink{
    if (_displayLink == nil) {
        CADisplayLink * displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeRipple)];
        [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        _displayLink = displayLink;
    }
    return _displayLink;
}

然后再自定义的波纹view的drawRect方法中开始绘制波纹曲线。曲线使用三角函数sin绘制路径。其中kRippleYMax可控制曲线的振幅,rippleOffset可控制曲线的偏移量。

- (void)drawRippleLayer{
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 0, rippleYOffset);
    for (int i = 0; i <= KRippleWidth ; i++) {
        CGFloat y = _kRippleYMax * sin(rippleCycle*i+self.rippleOffset) + rippleYOffset;
        CGPathAddLineToPoint(path, NULL, i, y);
    }
    self.rippleLayer.path = path;
    CGPathRelease(path);
}

然后再在定时器触发的方法中实时控制曲线平移。

#pragma mark 定时器刷新页面
- (void)changeRipple{
    self.rippleOffset += rippleSpeed;
    [self setNeedsDisplay];
}
现在获取声音的分贝峰值和波纹曲线都已经具备了,只需要定时获取声音分贝峰值大小,再根据峰值控制曲线就ok了。

以上只贴出主要功能实现代码,具体实现请参考Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容