iOS开发@IT·互联网iOS Developer

在iOS中绘制录音音频波形图

2018-03-04  本文已影响364人  Dywane

在开发中我遇到有根据录音来绘制波形图的需求,所以这篇文章就教大家利用swift和AVAudioSession来绘制波形图。

本文Demo

效果图

条状波形图
线状波形图

配置AvAudioSession

绘制波形图前首先需要配置好AVAudioSession,同时需要建立一个数组去保存音量数据。

相关属性

     /// 录音器
    private var recorder: AVAudioRecorder!
    /// 录音器设置
    private let recorderSetting = [AVSampleRateKey : NSNumber(value: Float(44100.0)),//声音采样率
                                     AVFormatIDKey : NSNumber(value: Int32(kAudioFormatMPEG4AAC)),//编码格式
                             AVNumberOfChannelsKey : NSNumber(value: 1),//采集音轨
                          AVEncoderAudioQualityKey : NSNumber(value: Int32(AVAudioQuality.medium.rawValue))]//声音质量
    /// 录音计时器
    private var timer: Timer?
    /// 波形更新间隔
    private let updateFequency = 0.05
    /// 声音数据数组
    private var soundMeters: [Float]!
    /// 声音数据数组容量
    private let soundMeterCount = 10
    /// 录音时间
    private var recordTime = 0.00

AvAudioSession相关配置

    private func configAVAudioSession() {
        let session = AVAudioSession.sharedInstance()
        do { try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker) }
        catch { print("session config failed") }
    }
    
    
    private func configRecord() {
        AVAudioSession.sharedInstance().requestRecordPermission { (allowed) in
            if !allowed {
                return
            }
        }
        let session = AVAudioSession.sharedInstance()
        do { try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker) }
        catch { print("session config failed") }
        do {
            self.recorder = try AVAudioRecorder(url: self.directoryURL()!, settings: self.recorderSetting)
            self.recorder.delegate = self
            self.recorder.prepareToRecord()
            self.recorder.isMeteringEnabled = true
        } catch {
            print(error.localizedDescription)
        }
        do { try AVAudioSession.sharedInstance().setActive(true) }
        catch { print("session active failed") }
    }
    
    
    private func directoryURL() -> URL? {
        // do something ...
        return soundFileURL
    }

记录音频数据

在开始录音后,利用我们刚刚配置的定时器不断获取averagePower,并保存到数组之中。

    private func updateMeters() {
        recorder.updateMeters()
        recordTime += updateFequency
        addSoundMeter(item: recorder.averagePower(forChannel: 0))
    }
    
    
    private func addSoundMeter(item: Float) {
        if soundMeters.count < soundMeterCount {
            soundMeters.append(item)
        } else {
            for (index, _) in soundMeters.enumerated() {
                if index < soundMeterCount - 1 {
                    soundMeters[index] = soundMeters[index + 1]
                }
            }
            // 插入新数据
            soundMeters[soundMeterCount - 1] = item
            NotificationCenter.default.post(name: NSNotification.Name.init("updateMeters"), object: soundMeters)
        }
    }

开始绘制波形图

现在我们已经获取了我们需要的所有数据,可以开始绘制波形图了。这时候让我们转到MCVolumeView.swift文件中,在上一个步骤中,我们发送了一条叫做updateMeters的通知,目的就是为了通知MCVolumeView进行波形图的更新。

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.clear
        contentMode = .redraw   //内容模式为重绘,因为需要多次重复绘制音量表
        NotificationCenter.default.addObserver(self, selector: #selector(updateView(notice:)), name: NSNotification.Name.init("updateMeters"), object: nil)
    }
    
    @objc private func updateView(notice: Notification) {
        soundMeters = notice.object as! [Float]
        setNeedsDisplay()
    }

setNeedsDisplay被调用之后,就会调用drawRect方法,在这里我们可以进行绘制波形图的操作。

    override func draw(_ rect: CGRect) {
        if soundMeters != nil && soundMeters.count > 0 {
            let context = UIGraphicsGetCurrentContext()
            context?.setLineCap(.round)
            context?.setLineJoin(.round)
            context?.setStrokeColor(UIColor.white.cgColor)
            
            let noVoice = -46.0     // 该值代表低于-46.0的声音都认为无声音
            let maxVolume = 55.0    // 该值代表最高声音为55.0
            
              // draw the volume...            
              
            context?.strokePath()
        }
    }

柱状波形图的绘制

    case .bar:          
        context?.setLineWidth(3)
       for (index,item) in soundMeters.enumerated() {
        let barHeight = maxVolume - (Double(item) - noVoice)    //通过当前声音表计算应该显示的声音表高度
            context?.move(to: CGPoint(x: index * 6 + 3, y: 40))
            context?.addLine(to: CGPoint(x: index * 6 + 3, y: Int(barHeight)))
       }

线状波形图的绘制

    case .line:
        context?.setLineWidth(1.5)
        for (index, item) in soundMeters.enumerated() {
            let position = maxVolume - (Double(item) - noVoice)     //计算对应线段高度
            context?.addLine(to: CGPoint(x: Double(index * 6 + 3), y: position))
            context?.move(to: CGPoint(x: Double(index * 6 + 3), y: position))
        }
    }

进一步完善我们的波形图

在很多时候,录音不单止是需要显示波形图,还需要我们展示目前录音的时间和进度,所以我们可以在波形图上添加录音的进度条,所以我们转向MCProgressView.swift文件进行操作。

    private func configAnimate() {
        let maskPath = UIBezierPath(roundedRect: CGRect.init(x: 0, y: 0, width: frame.width, height: frame.height), cornerRadius: HUDCornerRadius)
        let maskLayer = CAShapeLayer()
        maskLayer.backgroundColor = UIColor.clear.cgColor
        maskLayer.path = maskPath.cgPath
        maskLayer.frame = bounds
        
        // 进度路径
        /*
         路径的中心为HUD的中心,宽度为HUD的高度,从左往右绘制
         */
        let progressPath = CGMutablePath()
        progressPath.move(to: CGPoint(x: 0, y: frame.height / 2))
        progressPath.addLine(to: CGPoint(x: frame.width, y: frame.height / 2))
        
        progressLayer = CAShapeLayer()
        progressLayer.frame = bounds
        progressLayer.fillColor = UIColor.clear.cgColor //图层背景颜色
        progressLayer.strokeColor = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: 0.90).cgColor   //图层绘制颜色
        progressLayer.lineCap = kCALineCapButt
        progressLayer.lineWidth = HUDHeight
        progressLayer.path = progressPath
        progressLayer.mask = maskLayer 
        
        animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.duration = 60 //最大录音时长
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)    //匀速前进
        animation.fillMode = kCAFillModeForwards
        animation.fromValue = 0.0
        animation.toValue = 1.0
        animation.autoreverses = false
        animation.repeatCount = 1
    }

结语

以上就是我在绘制录音波形图的一些心得和看法,在demo中我还为录音HUD加入了高斯模糊和阴影,让HUD在展示上更具质感,这些就略过不提了。虽然如此,但是这个录音HUD我觉得还是有一些缺陷的,一来是和VC的耦合比较高,二是绘制线状波形图的效果并不是太理性,希望各位如果有更好的方法可以与我交流。


想了解更多内容可以查看我的博客

上一篇下一篇

猜你喜欢

热点阅读