在iOS中绘制录音音频波形
带状波形图
线性波形图
配置音频会话
绘制波形图前需要配置AVAudioSession,需要建立数组保存体数据。
相关属性
RecorderSetting用于设置录音质量和其他相关数据。
定时器和更新频率用于定期更新波形图。
SoundMeter和soundMeterCount用于保存音量表数组。
RecordTime用于记录录制时间,可以用来判断录制时间是否符合要求等等。
///记录器
私人var记录仪:AVAudioRecorder!///录像机设置
私信录音机设置=[avsampleratekey:ns number(value:float(44100.0)),//声音采样率。
Avformatidkey: ns数(值:int 32(kaudioformatmpg 4 AAC)),//编码格式。
Avnumberofchannelskey: ns number(值:1),//收藏音轨。
Avencodeaudio质量键:ns数(值:int 32 (avaudio质量。medium.raw值))]//声音质量
///录音计时器
私有var定时器:定时器?///波形更新间隔
私人信函更新频率= 0.05
///声音数据数组
私有var soundMeters: [Float]!///声音数据数组容量
私人信件soundMeterCount = 10
///录制时间
私有var记录时间= 0.00
与音频会话相关的配置
配置AVAudioSession用于配置avaudiosession,其中AVAudioSessionCategoryRecord表示仅记录此会话。如果需要播放,可以设置为avaudiesessioncategoryplayandrerecord或者AvaudieSessionCategoryplayBlack。两者的区别在于一个可以录音播放,一个可以后台播放(也就是静音后仍然可以播放语音)。
ConfigRecord用于配置整个AVAudioRecoder,包括权限获取、代理源设置、是否记录卷表等。
DirectoryURL是配置文件保存地址。
private func configAVAudioSession(){ let session = avaudiosession . shared instance()do { try session . set category(avaudiosessioncategoryplayandrerecord,with:。defaultToSpeaker) } catch { print("会话配置失败")}
}
private func config record(){ avaudiosession . shared instance()。中的requestRecordPermission {(允许)
如果!允许{返回
}
} let session = avaudiosession . shared instance()do { try session . set category(AVAudioSessionCategoryPlayAndRecord,with:。defaultToSpeaker)} catch { print(" session config failed ")} do { self . recorder = try AVAudioRecorder(URL:self . directory yurl()!,settings:self . recorder setting)self . recorder . delegate = self
self . recorder . preparetorecord()self . recorder . ismeteringenabled = true
} catch { print(error .本地化描述)
} do { try avaudiosession . shared instance()。setActive(true) } catch { print("会话活动失败")}
}
private func directory URL()-& gt;网址?{ //做点什么...
返回声音文件URL
}
录制音频数据
记录之后,我们使用刚刚配置的计时器不断获取平均功率,并将其保存在数组中。
UpdateMeters由计时器调用,记录器中记录的音量数据不断保存到soundMeter数组中。
AddSoundMeter用于添加数据。
私有函数更新计数器(){
recorder.updateMeters()
记录时间+=更新频率
add soundmeter(item:recorder . average power(for channel:0))
}
private func add soundmeter(item:Float){ if soundmeters . count & lt;soundMeterCount {
soundMeters.append(项目)
} else { for (index,_)in soundmeters . enumerated(){ if index & lt;soundMeterCount - 1 {
测深仪[index] =测深仪[index + 1]
}
}//插入新数据
soundMeters[soundMeterCount-1]= item notification center . default . post(名称:NSNotification。Name.init("updateMeters "),object: soundMeters)
}
}
开始画波形图
现在我们已经获得了所有需要的数据,我们可以开始画波形图了。这时候我们就去MCVolumeView.swift文件。在上一步中,我们发送了一个名为updateMeters的通知,通知MCVolumeView更新波形图。
覆盖init(frame:CG rect){ super . init(frame:frame)
backgroundColor = UIColor.clear
内容模式=。重画?//内容模式是重绘,因为体积表需要重绘很多次。
notification center . default . add observer(self,selector:# selector(update view(notice:)),name: NSNotification。Name.init("updateMeters "),object: nil)
}
@ objc private func update view(notice:Notification){
soundMeters = notice.object as![浮动]
setNeedsDisplay()
}
调用setNeedsDisplay时,会调用drawRect方法,在这里我们可以画出波形图。
NoVoice和maxVolume用于保证声音的显示范围。
波形图由CGContext绘制,当然也可以由UIBezierPath绘制。
覆盖func draw(_ rect:CG rect){ if soundMeters!=零& amp& ampsoundMeters.count & gt0 { let context = UIGraphicsGetCurrentContext()
语境?。setLineCap(。圆形)
语境?。setLineJoin(。圆形)
语境?。setStrokeColor(ui color . white . CG color)
设noVoice = -46.0 //这个值意味着所有低于-46.0的声音都被认为是无声的。
设maxVolume = 55.0 //这个值代表最大声音为55.0。
//绘制体积...
语境?。strokePath()
}
}
柱状波形图的绘制
根据maxVolume和noVoice计算每列的高度,移动上下文所在的点进行绘制。
另外需要注意的是,CGContext中的坐标点是反的,所以计算时需要反坐标轴才能计算。
凯斯。酒吧:?
语境?。setLineWidth(3)?对于(索引,项目)在音表中。枚举(){ let bar height = max volume-(double(item)-no voice)//计算通过当前音表应该显示的音表高度。
语境?。移动(到:CGPoint(x: index * 6 + 3,y: 40))
语境?。addLine(to: CGPoint(x: index * 6 + 3,y: Int(barHeight)))
}
线性波形图的绘制
“高度”的计算方法与条带相同,但在绘制条带波形时,先画线再移动,而在绘制条带波形时,先移动线再绘制。
凯斯。线路:
语境?。在soundmeters中为(index,item)设置linewidth (1.5)。枚举(){ let position = max volume-(double(item)-no voice)//计算对应线段的高度。
语境?。addLine(to:CG point(x:Double(index * 6+3),y: position))
语境?。移动(to: CGPoint(x: Double(index * 6 + 3),y: position))
}
}
进一步完善我们的波形图。
很多情况下,录制不仅需要显示波形图,还需要我们显示当前的录制时间和进度,所以我们可以在波形图中添加一个录制进度条,于是我们转向MCProgressView.swift文件进行操作。
使用UIBezierPath用CAShapeLayer绘图。
MaskPath是整个进度路径的蒙版,因为我们的录音HUD不是一个规则的正方形,所以需要用蒙版进度路径来切割。
ProgressPath是进度路径,进度的绘制方法是从左到右依次绘制。
动画是进度路径的绘制动画。
private func config animate(){ let mask path = UIBezierPath(rounded rect:CG rect . init(x:0,y: 0,width: frame.width,height: frame.height),corner radius:HUDCornerRadius)let mask layer = CAShapeLayer()
mask layer . background color = ui color . clear . CG color
maskLayer.path = maskPath.cgPath
maskLayer.frame = bounds
//进度路径
/*
路径的中心是HUD的中心,宽度是HUD的高度,从左到右绘制。
*/
let progressPath = CGMutablePath()
progress path . move(to:CG point(x:0,y: frame.height / 2))
progress path . addline(to:CG point(x:frame . width,y: frame.height / 2))
progressLayer = CAShapeLayer()
progressLayer.frame = bounds
progress layer . fill color = uicolor.clear.cgcolor//Layer背景色。
progress layer . stroke color = ui color(红色:0.29,绿色:0.29,蓝色:0.29,alpha: 0.90)。cgColor?//图层绘制颜色
progress layer . line cap = kCALineCapButt
progress layer . line width = hud height
progress layer . path = progress path
progressLayer.mask = maskLayer
animation = cabasic animation(keyPath:" stroke end ")
Animation.duration = 60 //最大录制持续时间
动画。计时函数= camediatiming函数(名称:kcamediating泛函线性)//匀速行进。
animation . fill mode = kCAFillModeForwards
animation.fromValue = 0.0
animation.toValue = 1.0
animation.autoreverses = false
animation.repeatCount = 1
}
标签
以上是我在绘制录音波形时的一些体会和看法。在演示中,我还在录制HUD中添加了高斯模糊和阴影,以使其在显示中更有质感,因此我将跳过它。即便如此,我觉得这个录音HUD还是有一些缺陷的。第一,和VC的耦合度比较高。二是绘制线性波形图的效果不太合理。如果有更好的方法,希望大家可以和我交流。