在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的耦合度比较高。二是绘制线性波形图的效果不太合理。如果有更好的方法,希望大家可以和我交流。