iOS开发-IM语音录制

这里主要介绍一下语音录制功能的实现,主要使用的录音器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,只是一个简单的语音录制功能,如果需要扩展,请自行复制下来后修改,这里已经放出所有的代码,希望能帮助到你,感谢你的阅读。

你可能感兴趣的:(iOS开发-IM语音录制)