这里主要介绍一下语音录制功能的实现,主要使用的录音器AVAudioRecorder
,这里涉及到麦克风权限,这个是从以前项目爬出来的,下面给出删减过后的文件内容
import UIKit
import AVFoundation
class kjSwiftExHelper: NSObject {
class func kjKeywindow() -> UIWindow? {
var window: UIWindow?
if #available(iOS 13.0, *) {
for winScene in ((UIApplication.shared.connectedScenes as? Set)!) {
if winScene.activationState == .foregroundActive {
window = winScene.windows.first
break
}
}
} else {
window = UIApplication.shared.keyWindow
}
return window
}
class func kjWindows() -> [UIWindow] {
var windows = Array()
if #available(iOS 13.0, *) {
for winScene in ((UIApplication.shared.connectedScenes as? Set)!) {
if winScene.activationState == .foregroundActive {
windows = winScene.windows
break
}
}
} else {
windows = UIApplication.shared.windows
}
return windows
}
/// 获取当前正在展示的控制器
class func kjDisplayViewController() -> UIViewController? {
func kj_topViewController(v: UIViewController?) -> UIViewController? {
if v is UINavigationController {
return kj_topViewController(v: (v as! UINavigationController).topViewController)
} else if v is UITabBarController {
return kj_topViewController(v: (v as! UITabBarController).selectedViewController)
}
return v
}
let window = kjKeywindow()
var ctrl: UIViewController? = kj_topViewController(v: window?.rootViewController)
while let presentVC = ctrl?.presentedViewController {
ctrl = kj_topViewController(v: presentVC)
}
return ctrl
}
/// 麦克风权限 true-麦克风可用,false-麦克风不可用
class func kjVerifyMicrophoneAuth() -> Bool {
let micStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.audio)
guard micStatus == AVAuthorizationStatus.authorized else {
//麦克风不可用
if micStatus == AVAuthorizationStatus.notDetermined {
//未提示授权
AVAudioSession.sharedInstance().requestRecordPermission { (allowed) in
//开始授权
}
} else {
//提示去设置开启麦克风权限
let alert = UIAlertController(title: "无法使用麦克风",
message: "需要开启麦克风权限才能进行语音答题和TA聊天,请在iPhone设置中开启",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "开启",
style: .default,
handler: { (action) in
// 去设置页开启麦克风权限
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}))
alert.addAction(UIAlertAction(title: "取消",
style: .cancel,
handler: { (action) in
}))
if let viewController = kjSwiftExHelper.kjDisplayViewController() {
alert.show(viewController, sender: nil)
}
}
return false
}
//麦克风可用
return true
}
}
下面直接给出录音器管理类的所有内容,注释都有,感觉没必要在这里写那么清楚
import UIKit
import AVFoundation
// MARK: - 协议
protocol KJAudioRecorderDelegate: AnyObject {
/// 语音录制成功
/// - Parameters:
/// - kjUrl: 语音文件的本地路径
/// - kjDur: 语音文件的时长
func kjAudioRecorderSucceed(kjUrl: URL, kjDur: TimeInterval) -> Void
/// 语音录制失败
/// - Parameter kjError: 失败的原因
func kjAudioRecorderFailure(kjError: NSError) -> Void
/// 录音时长变化
/// - Parameter kjDur: 时长
func kjAudioFileDurationChanged(kjDur: TimeInterval) -> Void
/// 音量变化
/// - Parameter kjVol: 音量
func kjAudioVolumeChanged(kjVol: Double) -> Void
}
// 可选实现
extension KJAudioRecorderDelegate {
/// 录音时长变化
/// - Parameter kjDur: 时长
func kjVoiceFileDurationChanged(kjDur: TimeInterval) -> Void {}
/// 音量变化
/// - Parameter kjVol: 音量
func kjVoiceVolumeChanged(kjVol: Double) -> Void {}
}
// MARK: - 枚举 错误码
enum KJVoiceRecorderErrorCode: Int {
case kjPathError = 0 // 路径错误
case kjRecorderError = 1 // 录音器初始化失败
case kjMicAuthError = 2 // 麦克风权限
case kjInterruption = 3 // 被中断
case kjEncodeError = 4
}
// MARK: - 录音器
class kjAudioRecorder: NSObject {
/// 录音器
fileprivate var kjRecorder: AVAudioRecorder?
/// 代理
fileprivate weak var kjRecorderDelegate: KJAudioRecorderDelegate?
/// 最大时长 默认为0-表示不限时长
fileprivate var kjMaxDur: TimeInterval = 0
/// 定时器
fileprivate var kjTimer: Timer?
/// 录音器配置
fileprivate lazy var kjConfigure: Dictionary = {
return [
//音频格式
AVFormatIDKey: kAudioFormatMPEG4AAC,
//采样率
AVSampleRateKey: 44100,
//声道
AVNumberOfChannelsKey: 2,
//采样位数
AVLinearPCMBitDepthKey: 32,
//比特采样率
AVEncoderBitRateKey: 128000
]
}()
deinit {
kjTimer?.invalidate()
removeNotification()
}
// MARK: - 外部调用
/// 外部初始化
/// - Parameters:
/// - maxDuration: 可录制最大时间
/// - delegate: 代理
init(maxDuration: TimeInterval = 0, delegate: KJAudioRecorderDelegate) {
super.init()
kjRecorderDelegate = delegate
kjMaxDur = maxDuration
kjTimer = Timer(timeInterval: 0.2, repeats: true, block: { [weak self] (timer) in
self?.getAudioDurationAndVolume()
})
if kjTimer != nil {
RunLoop.current.add(kjTimer!, forMode: RunLoop.Mode.common)
kjTimer?.fireDate = Date.distantFuture
}
}
/// 录音器状态
func isRecording() -> Bool {
return kjRecorder?.isRecording ?? false
}
/// 开始录音
func begin() {
// 麦克风权限
guard kjSwiftExHelper.kjVerifyMicrophoneAuth() == true else {
// 麦克风不可用
kjRecorderDelegate?.kjAudioRecorderFailure(kjError: kjHandleError(code: .kjMicAuthError))
return
}
// 文件路径
guard let fileUrl = kjAudioFilePath() else {
kjRecorderDelegate?.kjAudioRecorderFailure(kjError: kjHandleError(code: .kjPathError))
return
}
// 初始化录音器
do {
kjRecorder = try AVAudioRecorder(url: fileUrl, settings: kjConfigure)
} catch {
print(error)
}
guard kjRecorder != nil else {
kjRecorderDelegate?.kjAudioRecorderFailure(kjError: kjHandleError(code: .kjRecorderError))
return
}
// 设置会话
handleAudioSession(isOpen: true)
// 协议
kjRecorder?.delegate = self
// 开启音量检测
kjRecorder?.isMeteringEnabled = true
// 准备录音
guard kjRecorder?.prepareToRecord() == true else {
kjRecorderDelegate?.kjAudioRecorderFailure(kjError: kjHandleError(code: .kjRecorderError))
handleAudioSession(isOpen: false)
return
}
if start() == false {
// 录音器开启失败
handleAudioSession(isOpen: false)
}
}
/// 重启录音
func resume() -> Void {
if start() == false {
// 录音器开启失败
handleAudioSession(isOpen: false)
}
}
/// 暂停录音
func pause() {
if kjRecorder?.isRecording == true {
kjTimer?.fireDate = Date.distantFuture
kjRecorder?.pause()
}
}
/// 停止录音
func stop() {
if kjRecorder?.isRecording == true {
kjRecorder?.stop()
handleAudioSession(isOpen: false)
}
}
/// 取消录制 (会删除已录制的文件)
func cancel() {
pause()
if kjRecorder?.deleteRecording() == false {
// 取消录制失败
if let file = kjRecorder?.url {
//手动删除文件
if FileManager.default.fileExists(atPath: file.path) == true {
try? FileManager.default.removeItem(at: file)
}
stop()
}
}
handleAudioSession(isOpen: false)
}
// MARK: - 私有
fileprivate func start() -> Bool {
if kjRecorder != nil && kjRecorder?.isRecording == false {
if (kjMaxDur > 0 ? kjRecorder?.record(forDuration: kjMaxDur) : kjRecorder?.record()) == false {
// 录音器开启成功
kjTimer?.fireDate = Date.distantPast
return true
}
}
return false
}
/// 处理 AudioSession
///
/// - Parameter isOpen: 是否开启 true - 开启(让其他有声音的应用都停止), false - 关闭(让其他正在播放的应用能继续播放)
fileprivate func handleAudioSession(isOpen: Bool) {
let session = AVAudioSession.sharedInstance()
if isOpen {
// 开启定时器
kjTimer?.fireDate = Date.distantPast
try? session.setCategory(AVAudioSession.Category.record)
try? session.setActive(true, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
} else {
// 停止定时器
kjTimer?.fireDate = Date.distantFuture
try? session.setActive(false, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
}
}
/// 获取当前音量和已录制的时长
fileprivate func getAudioDurationAndVolume() {
if kjRecorder != nil && kjRecorder?.isRecording == true {
//音量
kjRecorder?.updateMeters()
let voi = pow(10.0, 0.05 * kjRecorder!.peakPower(forChannel: 0))
kjRecorderDelegate?.kjAudioVolumeChanged(kjVol: Double(voi))
//时长
kjRecorderDelegate?.kjAudioFileDurationChanged(kjDur: kjRecorder!.currentTime)
}
}
/// 组装错误信息
/// - Parameter code: 错误码
/// - Returns: NSError
fileprivate func kjHandleError(code: KJVoiceRecorderErrorCode) -> NSError {
var errorInfo = ""
switch code {
case .kjPathError:
errorInfo = "音频录音获取失败"
break
case .kjRecorderError:
errorInfo = "录音器初始化失败"
break
case .kjMicAuthError:
errorInfo = "没有麦克风权限"
break
case .kjInterruption:
errorInfo = "录音被(电话、闹铃等)中断"
break
case .kjEncodeError:
errorInfo = "录音编码失败"
break
}
return NSError(domain: "JOIMIMAudioRec",
code: code.rawValue,
userInfo: [NSDebugDescriptionErrorKey: errorInfo])
}
/// 音频录制保存的路径
///
/// - Returns: 路径
fileprivate func kjAudioFilePath() -> URL? {
if let dir = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last {
let file = dir + "/audio"
// 创建文件夹
if FileManager.default.fileExists(atPath: file) == false {
try? FileManager.default.createDirectory(atPath: file, withIntermediateDirectories: true, attributes: nil)
}
return URL(fileURLWithPath: file + "/" + UUID().uuidString + ".aac")
}
return nil
}
// MARK: - 通知
/// 添加被中断的通知
fileprivate func addInterruptionNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(interruption(ntf:)), name: AVAudioSession.interruptionNotification, object: nil)
}
/// 移除通知
fileprivate func removeNotification() {
NotificationCenter.default.removeObserver(self)
}
/// 处理被中断
@objc fileprivate func interruption(ntf: Notification) {
if ntf.userInfo != nil {
if let interuptType = ntf.userInfo![AVAudioSessionInterruptionTypeKey] as? UInt {
if interuptType == AVAudioSession.SilenceSecondaryAudioHintType.begin.rawValue {
kjRecorderDelegate?.kjAudioRecorderFailure(kjError: kjHandleError(code: .kjInterruption))
}
}
}
cancel()
}
}
// MARK: - AVAudioRecorderDelegate
extension kjAudioRecorder: AVAudioRecorderDelegate {
/// 录音完成的回调
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
handleAudioSession(isOpen: false)
if flag {
// 获取录音时长
let asset = AVURLAsset.init(url: recorder.url)
kjRecorderDelegate?.kjAudioRecorderSucceed(kjUrl: recorder.url, kjDur: CMTimeGetSeconds(asset.duration))
} else {
cancel()
}
}
/// 编码错误的回调
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
handleAudioSession(isOpen: false)
kjRecorderDelegate?.kjAudioRecorderFailure(kjError: kjHandleError(code: .kjEncodeError))
}
}
这里只提供了开始录制begin()
、重启录制resume ()
、暂停录制pause()
、停止录制stop()
、取消录制cancel()
,语音格式是.aac
,只是一个简单的语音录制功能,如果需要扩展,请自行复制下来后修改,这里已经放出所有的代码,希望能帮助到你,感谢你的阅读。