在開發(fā)中我遇到有根據(jù)錄音來繪制波形圖的需求,所以這篇文章就教大家利用swift和
AVAudioSession
來繪制波形圖。
本文Demo
效果圖
條狀波形圖
線狀波形圖
配置AvAudioSession
繪制波形圖前首先需要配置好AVAudioSession
逊躁,同時需要建立一個數(shù)組去保存音量數(shù)據(jù)找爱。
相關(guān)屬性
- recorderSetting用于設(shè)定錄音音質(zhì)等相關(guān)數(shù)據(jù)枯芬。
- timer以及updateFequency用于定時更新波形圖。
- soundMeter和soundMeterCount用于保存音量表數(shù)組徘意。
- recordTime用于記錄錄音時間,可以用于判斷錄音時間是否達(dá)到要求等進(jìn)一波需求轩褐。
/// 錄音器
private var recorder: AVAudioRecorder!
/// 錄音器設(shè)置
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))]//聲音質(zhì)量
/// 錄音計時器
private var timer: Timer?
/// 波形更新間隔
private let updateFequency = 0.05
/// 聲音數(shù)據(jù)數(shù)組
private var soundMeters: [Float]!
/// 聲音數(shù)據(jù)數(shù)組容量
private let soundMeterCount = 10
/// 錄音時間
private var recordTime = 0.00
AvAudioSession相關(guān)配置
-
configAVAudioSession用于配置
AVAudioSession
椎咧,其中AVAudioSessionCategoryRecord
是代表僅僅利用這個session進(jìn)行錄音操作,而需要播放操作的話是可以設(shè)置成AVAudioSessionCategoryPlayAndRecord
或AVAudioSessionCategoryPlayBlack
,兩者區(qū)別一個是可以錄音和播放把介,另一個是可以在后臺播放(即靜音后仍然可以播放語音)勤讽。 -
configRecord是用于配置整個
AVAudioRecoder
,包括權(quán)限獲取拗踢、代理源設(shè)置脚牍、是否記錄音量表等。 - directoryURL是用于配置文件保存地址巢墅。
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
}
記錄音頻數(shù)據(jù)
在開始錄音后诸狭,利用我們剛剛配置的定時器不斷獲取averagePower
,并保存到數(shù)組之中砂缩。
- updateMeters被定時器調(diào)用作谚,不斷將recorder中記錄的音量數(shù)據(jù)保存到soundMeter數(shù)組中。
- addSoundMeter用于完成添加數(shù)據(jù)的工作庵芭。
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]
}
}
// 插入新數(shù)據(jù)
soundMeters[soundMeterCount - 1] = item
NotificationCenter.default.post(name: NSNotification.Name.init("updateMeters"), object: soundMeters)
}
}
開始繪制波形圖
現(xiàn)在我們已經(jīng)獲取了我們需要的所有數(shù)據(jù)妹懒,可以開始繪制波形圖了。這時候讓我們轉(zhuǎn)到MCVolumeView.swift
文件中双吆,在上一個步驟中眨唬,我們發(fā)送了一條叫做updateMeters
的通知,目的就是為了通知MCVolumeView
進(jìn)行波形圖的更新好乐。
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
contentMode = .redraw //內(nèi)容模式為重繪匾竿,因為需要多次重復(fù)繪制音量表
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()
}
當(dāng)setNeedsDisplay
被調(diào)用之后,就會調(diào)用drawRect
方法蔚万,在這里我們可以進(jìn)行繪制波形圖的操作岭妖。
- noVoice和maxVolume是用于確保聲音的顯示范圍
- 波形圖的繪制使用CGContext進(jìn)行繪制,當(dāng)然也可以使用UIBezierPath進(jìn)行繪制。
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的聲音都認(rèn)為無聲音
let maxVolume = 55.0 // 該值代表最高聲音為55.0
// draw the volume...
context?.strokePath()
}
}
柱狀波形圖的繪制
- 根據(jù)
maxVolume
和noVoice
計算出每一條柱狀的高度昵慌,并移動context所在的點(diǎn)進(jìn)行繪制 - 另外需要注意的是
CGContext
中坐標(biāo)點(diǎn)時反轉(zhuǎn)的假夺,所以在進(jìn)行計算時需要將坐標(biāo)軸進(jìn)行反轉(zhuǎn)來計算。
case .bar:
context?.setLineWidth(3)
for (index,item) in soundMeters.enumerated() {
let barHeight = maxVolume - (Double(item) - noVoice) //通過當(dāng)前聲音表計算應(yīng)該顯示的聲音表高度
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) //計算對應(yīng)線段高度
context?.addLine(to: CGPoint(x: Double(index * 6 + 3), y: position))
context?.move(to: CGPoint(x: Double(index * 6 + 3), y: position))
}
}
進(jìn)一步完善我們的波形圖
在很多時候,錄音不單止是需要顯示波形圖鹉梨,還需要我們展示目前錄音的時間和進(jìn)度讳癌,所以我們可以在波形圖上添加錄音的進(jìn)度條,所以我們轉(zhuǎn)向MCProgressView.swift
文件進(jìn)行操作俯画。
- 使用
UIBezierPath
配合CAShapeLayer
進(jìn)行繪制析桥。 - maskPath是作為整個進(jìn)度路徑的蒙版,因為我們的錄音HUD不是規(guī)則的方形艰垂,所以需要使用蒙版進(jìn)度路徑進(jìn)行裁剪泡仗。
- progressPath為進(jìn)度路徑,進(jìn)度的繪制方法為從左到右依次繪制猜憎。
- animation是進(jìn)度路徑的繪制動畫娩怎。
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
// 進(jìn)度路徑
/*
路徑的中心為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) //勻速前進(jìn)
animation.fillMode = kCAFillModeForwards
animation.fromValue = 0.0
animation.toValue = 1.0
animation.autoreverses = false
animation.repeatCount = 1
}
結(jié)語
以上就是我在繪制錄音波形圖的一些心得和看法截亦,在demo中我還為錄音HUD加入了高斯模糊和陰影,讓HUD在展示上更具質(zhì)感柬讨,這些就略過不提了崩瓤。雖然如此,但是這個錄音HUD我覺得還是有一些缺陷的踩官,一來是和VC的耦合比較高却桶,二是繪制線狀波形圖的效果并不是太理性,希望各位如果有更好的方法可以與我交流蔗牡。
想了解更多內(nèi)容可以查看我的博客