NXAudioRecorder.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. //
  2. import AVFoundation
  3. import Foundation
  4. // NXAudioRecorder.swift
  5. // PQSpeed
  6. //
  7. // Created by ak on 2021/1/23.
  8. // Copyright © 2021 BytesFlow. All rights reserved.
  9. // 本类功能:录制声音,并转换成 MP3
  10. // alse see https://www.jianshu.com/p/971fff236881
  11. import UIKit
  12. // 录制时长
  13. typealias RecorderProgross = (_ time: Float64) -> Void
  14. class NXAudioRecorder {
  15. public let recorder: AVAudioRecorder
  16. public var finishClosure: ((_ isSuccess: Bool, _ url: String) -> Void)? {
  17. return delegateHandler.finishClosure
  18. }
  19. /// 由于AVAudioRecorderDelegate继承NSObjectProtocol 所以引入这个类处理代理避免污染主类
  20. private var delegateHandler = EditAudioRecorderDelegateHandler()
  21. var recorderProgross: RecorderProgross?
  22. var session: AVAudioSession!
  23. var recordFilePath: String!
  24. var displayLink: CADisplayLink?
  25. /// 初始化录音器
  26. /// - Parameter path: 保存的文件全路径,注意文件后缀一定要是 caf
  27. /// - Throws: description
  28. public init(path: String) throws {
  29. // 1,判断目录文件夹是否存在
  30. recordFilePath = path
  31. BFLog(message: "recorder file path is \(String(describing: recordFilePath))")
  32. // 2,参数
  33. let fileURL = URL(fileURLWithPath: recordFilePath)
  34. // 注意设置参数 设置不对就无法录制
  35. let settings: [String: Any] = [
  36. AVFormatIDKey: kAudioFormatLinearPCM,
  37. AVSampleRateKey: 16000.0,
  38. AVNumberOfChannelsKey: 1,
  39. AVEncoderBitDepthHintKey: 16,
  40. // AVLinearPCMIsFloatKey:true, // 不要打开ios 13有杂音
  41. AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue, // 录音质量
  42. ]
  43. recorder = try AVAudioRecorder(url: fileURL, settings: settings)
  44. recorder.isMeteringEnabled = true
  45. recorder.delegate = delegateHandler
  46. recorder.prepareToRecord()
  47. }
  48. /// 开始录制
  49. public func startRecord() {
  50. if recorder.isRecording {
  51. BFLog(message: "正在录制中。。")
  52. return
  53. }
  54. startTimer()
  55. if AVAudioSession.sharedInstance().category != .playAndRecord {
  56. do {
  57. try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: .defaultToSpeaker)
  58. try AVAudioSession.sharedInstance().setActive(true)
  59. } catch {
  60. BFLog(message: error)
  61. }
  62. }
  63. session = AVAudioSession.sharedInstance()
  64. session.requestRecordPermission { granted in
  65. if granted {
  66. DispatchQueue.global().async {
  67. DispatchQueue.main.async {}
  68. }
  69. } else {}
  70. }
  71. recorder.record()
  72. }
  73. // 暂停录制
  74. public func pauseRecord() {
  75. recorder.pause()
  76. }
  77. // 停止录制
  78. public func stopRecord(_ closure: @escaping (_ isSuccess: Bool, _ url: String) -> Void) {
  79. if !recorder.isRecording {
  80. BFLog(message: "不是录制状态")
  81. }
  82. stopTimer()
  83. delegateHandler.finishClosure = closure
  84. recorder.stop()
  85. }
  86. @objc func displayLinkClick(_: CADisplayLink) {
  87. recorder.updateMeters()
  88. BFLog(message: "当前录制时间长 \(String(describing: recorder.currentTime)) 波值:\(String(describing: recorder.averagePower(forChannel: 0)))")
  89. if recorderProgross != nil {
  90. recorderProgross!(recorder.currentTime)
  91. }
  92. }
  93. // 开始计时
  94. func startTimer() {
  95. if displayLink == nil {
  96. // 创建对象
  97. displayLink = CADisplayLink(target: self, selector: #selector(displayLinkClick(_:)))
  98. // 设置触发频率 这个周期可以通过frameInterval属性设置,CADisplayLink的selector每秒调用次数=60/frameInterval。比如当frameInterval设为2,每秒调用就变成30次
  99. // if #available(iOS 10.0, *) {
  100. // displayLink?.preferredFramesPerSecond = 1
  101. // } else {
  102. displayLink?.frameInterval = 1
  103. // }
  104. // 加入循环
  105. displayLink?.add(to: RunLoop.main, forMode: RunLoop.Mode.default)
  106. }
  107. }
  108. // 停止计时
  109. func stopTimer() {
  110. if displayLink != nil {
  111. displayLink?.isPaused = true
  112. // 将定时器移除主循环
  113. displayLink?.remove(from: RunLoop.main, forMode: RunLoop.Mode.default)
  114. // 停止定时器
  115. displayLink?.invalidate()
  116. displayLink = nil
  117. }
  118. }
  119. }
  120. private class EditAudioRecorderDelegateHandler: NSObject {
  121. var finishClosure: ((_ flag: Bool, _ url: String) -> Void)?
  122. }
  123. extension EditAudioRecorderDelegateHandler: AVAudioRecorderDelegate {
  124. func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
  125. BFLog(message: "完成录音结果 is \(flag) url is \(recorder.url)")
  126. if flag {
  127. finishClosure?(true, recorder.url.relativePath)
  128. }
  129. }
  130. func audioRecorderEncodeErrorDidOccur(_: AVAudioRecorder, error: Error?) {
  131. guard let error = error else { return }
  132. BFLog(message: error)
  133. }
  134. }