iOS Audio : 录音、播放,Swift5 基于 AVFoundation

您所在的位置:网站首页 播放音乐怎么录音 iOS Audio : 录音、播放,Swift5 基于 AVFoundation

iOS Audio : 录音、播放,Swift5 基于 AVFoundation

2024-04-21 13:54| 来源: 网络整理| 查看: 265

录音,就要用到麦克风了

iOS 设备中,每一个应用 app,都有一个音频会话 Audio Session.

app 调用音频相关,自然会用到 iOS 的硬件功能。

音频会话 Audio Session ,就是来管理音频操作的。

iOS 使用音频,管理粒度很细

你觉得: 后台播放的音乐,要不要与你 app 的音频,混杂在一起?

Audio Session 处理音频,通过他的分类 Audio Session Category 设置

默认的分类,

1, 允许播放,不允许录音。

2, 静音按钮开启后,你的应用就哑巴了,播放音频没声音。

3, 锁屏后,你的应用也哑巴了,播放音频没声音。

4, 如果后台有别的 app 播放音频,你 app 要开始播放音频的时候,别的 app 就哑巴了。

更多分类,如图:

0

首先要对音频操作,做一些配置。

一般操作音频,会用到 AVFoundation 框架,先引入 import AVFoundation

设置 Audio Session 的分类,AVAudioSession.CategoryOptions.defaultToSpeaker , 允许我们的 app , 调用内置的麦克风来录音,又可以播放音频。

这里要做录音功能,就把分类的选项也改了。

分类的默认选项是,音频播放的是收听者,即上面的喇叭口,场景一般是你把手机拿到耳朵边,打电话。

现在把音频播放路径, 指向说话的人,即麦克风,下面的喇叭口。

// 这是一个全局变量,记录麦克风权限的 var appHasMicAccess = true // ... // 先获取一个 AVAudioSession 的实例 let session = AVAudioSession.sharedInstance() do { // 在这里,设置分类 try session.setCategory(AVAudioSession.Category.playAndRecord, options: AVAudioSession.CategoryOptions.defaultToSpeaker) try session.setActive(true) // 检查 app 有没有权限,使用该设备麦克风 session.requestRecordPermission({ (isGranted: Bool) in if isGranted { // 你的 app 想要录制音频,用户必须授予麦克风权限 appHasMicAccess = true } else{ appHasMicAccess = false } }) } catch let error as NSError { print("AVAudioSession configuration error: (error.localizedDescription)") } 复制代码 进入录音, // 这是一个枚举变量,用来手动追踪录音的状态 var audioStatus: AudioStatus = AudioStatus.Stopped var audioRecorder: AVAudioRecorder! func setupRecorder() { // getURLforMemo, 这个方法,拿到一个可以保存录音文件的,临时路径 // getURLforMemo , 具体见下面的 GitHub 链接 let fileURL = getURLforMemo() // 设置录音采样的描述信息 /* 线性脉冲编码调制,非压缩的数据格式 采样频率, 44.1 千赫兹的,CD 级别的效果 单声道,就录制一个单音 */ let recordSettings = [ AVFormatIDKey: Int(kAudioFormatLinearPCM), AVSampleRateKey: 44100.0, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] as [String : Any] do { // 实例化 audioRecorder audioRecorder = try AVAudioRecorder(url: fileURL, settings: recordSettings) audioRecorder.delegate = self audioRecorder.prepareToRecord() } catch { print("Error creating audio Recorder.") } } // 开始录音 func record() { startUpdateLoop() // 追踪,记录下当前 app 的录音状态 audioStatus = .recording // 这一行,就是开始录音了 audioRecorder.record() } // 停止录音 func stopRecording() { recordButton.setBackgroundImage(UIImage(named: "button-record"), for: UIControl.State.normal ) audioStatus = .stopped audioRecorder.stop() stopUpdateLoop() } 复制代码 录音结束,通过代理 AVAudioRecorderDelegate ,更新状态 func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { audioStatus = .stopped // 因为这个场景,录制完了, 必须手动点击, // 所以不需要在这里更新 UI } 复制代码 录音好了,做播放

播放录音

var audioPlayer: AVAudioPlayer! // 开始播放 func play() { // getURLforMemo, 这个方法,拿到一个可以保存录音文件的,临时路径 // getURLforMemo , 具体见下面的 GitHub 链接 let fileURL = getURLforMemo() do { // 实例化 audioPlayer audioPlayer = try AVAudioPlayer(contentsOf: fileURL) audioPlayer.delegate = self // 检查音频文件不为空,才播放音频文件 if audioPlayer.duration > 0.0 { setPlayButtonOn(flag: true) audioPlayer.play() audioStatus = .Playing startUpdateLoop() } } catch { print("Error loading audio Player") } } // 停止播放 func stopPlayback() { setPlayButtonOn(flag: false) audioStatus = .stopped audioPlayer.stop() stopUpdateLoop() } 复制代码 播放结束,通过代理 AVAudioPlayerDelegate ,更新 UI func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { // 因为只有在这里,我们才知道,播放完了的时机 setPlayButtonOn(flag: false) audioStatus = .stopped stopUpdateLoop() } 复制代码 显示录音/ 播放进展的 UI

要显示显示录音/ 播放的进展,就要用到计时器了,

因为录音/ 播放,每时每刻,都在变化。

计时器三步走:

开启计时器, var soundTimer: CFTimeInterval = 0.0 var updateTimer: CADisplayLink! func startUpdateLoop(){ if updateTimer != nil{ updateTimer.invalidate() } // 计时器是非常轻量级的对象,使用前,先销毁 updateTimer = CADisplayLink(target: self, selector: #selector(ViewController.updateLoop)) updateTimer.preferredFramesPerSecond = 1 updateTimer.add(to: RunLoop.current, forMode: RunLoop.Mode.common) } 复制代码 定时,做事情 @objc func updateLoop(){ if audioStatus == .recording{ // 录音状态,定时刷新 if CFAbsoluteTimeGetCurrent() - soundTimer > 0.5 { timeLabel.text = formattedCurrentTime(UInt(audioRecorder.currentTime)) soundTimer = CFAbsoluteTimeGetCurrent() } } else if audioStatus == .playing{ // 播放状态,定时刷新 if CFAbsoluteTimeGetCurrent() - soundTimer > 0.5 { timeLabel.text = formattedCurrentTime(UInt(audioPlayer.currentTime)) soundTimer = CFAbsoluteTimeGetCurrent() } } } 复制代码 销毁计时器

需要停止的时候,就调用这个方法,例如: 播放完成的代理方法中,再一次点击播放按钮...

func stopUpdateLoop(){ updateTimer.invalidate() updateTimer = nil // formattedCurrentTime,这个方法,时间转文字,具体见文尾的 GitHub 链接 timeLabel.text = formattedCurrentTime(UInt(0)) } 复制代码 采样音量大小计量

AVAudioPlayer 有音频的计量功能,播放音频的时候,音频计量可以检测到,波形的平均能级等信息

AVAudioPlayer 的方法 averagePower(forChannel:),会返回当前的分贝值,取值范围是 -160 ~ 0 db, 0 是很吵, -160 是很安静

波形,长这样

1

做一个张口嘴巴的动画,就是一个简单的音量大小可视化,音量越大,张开嘴的幅度也越大,具体见文尾的 GitHub repo

d

// 自己创建一个结构体,计量表 MeterTable // 音频计量返回的浮点数的范围 -160 ~ 0,先做分贝转振幅,转换为 0 ~ 1 之间 // 张口嘴巴的动画的图片有 5 张,分为 5 个级别,上面的取值范围,就要划分为对应的五个层级, // MeterTable 就要把采集的声音,映射到对应的图片 let meterTable = MeterTable(tableSize: 100) // ... // 播放前,先要激活音量分贝值检测功能 audioPlayer.isMeteringEnabled = true // ... // 将采集到的音量大小,映射为图片编号 // 更新状态的方法,一定要用到计时器。 // 该方法,要在计时器方法中使用到,具体见文尾的 github repo func meterLevelsToFrame() -> Int{ guard let player = audioPlayer else { return 1 } player.updateMeters() // 之前设置了,播放器是单声道 let avgPower = player.averagePower(forChannel: 0) let linearLevel = meterTable.valueForPower(power: avgPower) // 继续处理数据,转换出一个能级,具体见文尾的 GitHub repo let powerPercentage = Int(round(linearLevel * 100)) // 目前总共有 5 张图片 let totalFrames = 5 // 根据音量大小,决定呈现哪一张 // 图片命名是 01~05,所以要 + 1 let frame = ( powerPercentage / totalFrames ) + 1 return min(frame, totalFrames) } 复制代码 音频播放控制: 包含音量大小控制、左右声道切换、播放循环、播放速率控制等等 控制播放音量大小

音量的取值范围是 0 ~ 1, 0 是静音,1 是最大

func toSetVolumn(value: Float){ guard let player = audioPlayer else { return } // 苹果都封装好了,设置 audioPlayer 的 volume player.volume = value } 复制代码 设置左右声道

取值范围是 -1 到 1,

-1 是全左,1 是全右,0是均衡声道

func toSetPan(value: Float) { guard let player = audioPlayer else { return } // 苹果都封装好了,设置 audioPlayer 的 pan player.pan = value } 复制代码 设置播放循环

循环的取值范围是 -1 到 Int.max,

numberOfLoops 取值 0 到 Int.max,则会多播放那个取值的次数

func toSetLoopPlayback(loop: Bool) { guard let player = audioPlayer else { return } // 苹果都封装好了,设置 audioPlayer 的 numberOfLoops if loop == true{ // numberOfLoops 为 -1,无限循环,直到 audioPlayer 停止 player.numberOfLoops = -1 } else{ // numberOfLoops 为 0,仅播放一次,不循环 player.numberOfLoops = 0 } } 复制代码 设置播放速率

audioPlayer 的播放速率范围是,0.5 ~ 2.0

0.5 是半速播放,1.0 是正常播放,2.0 是倍速播放

// 播放前,要点亮 audioPlayer 的播放速率控制,为可用 audioPlayer.enableRate = true // ... func toSetRate(value: Float) { guard let player = audioPlayer else { return } // 苹果都封装好了,设置 audioPlayer 的 rate player.rate = value } 复制代码

github.com/BoxDengJZ/A…

转载自:juejin.cn/post/684490…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3