https://blog.csdn.net/bjrxyz/article/details/73435407 ⭐️⭐️⭐️⭐️⭐️
https://github.com/CivelXu/iOS-Lame-Audio-transcoding ⭐️⭐️⭐️⭐️
https://juejin.im/entry/5c70b2936fb9a049e12ad3f5 ⭐️⭐️⭐️
https://keyj.emphy.de/lame-weirdness/ ⭐️⭐️⭐️
https://sourceforge.net/projects/lame/
https://github.com/kewlbear/lame-ios-build
https://github.com/wuqiong/mp3lame-for-iOS
https://github.com/JoanKing/JKRecorderKit ⭐️⭐️⭐️
https://gist.github.com/tad-iizuka/1ca07ca2045a8c6d11e22d15812f7e15 ⭐️⭐️⭐️
https://book.douban.com/subject/30124646/
都支持录制和播放 aac 编码的音频,简单的需求其实可以不用转 mp3。
import AVFoundation
func getLibraryURL() -> URL? {
return FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
}
func getSoundsURL() -> URL? {
return getLibraryURL()?.appendingPathComponent("Sounds", isDirectory: true)
}
class RecordManager: NSObject, AVAudioRecorderDelegate {
static let shared = RecordManager()
var audioRecorder: AVAudioRecorder?
private var isRecording = false
func startRecord(success: (Bool) -> Void) {
let permission = AVAudioSession.sharedInstance().recordPermission
if permission == .granted {
guard !isRecording else { return }
do {
guard let dir = getSoundsURL() else { return }
try AVAudioSession.sharedInstance().setCategory(.record, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let url = dir.appendingPathComponent("temp.wav")
let settings = [
AVFormatIDKey: Int(kAudioFormatLinearPCM),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 1,
AVLinearPCMBitDepthKey: 16,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
audioRecorder = try AVAudioRecorder(url: url, settings: settings)
audioRecorder?.delegate = self
let started = audioRecorder!.record()
if started {
isRecording = true
startConvertToMP3(dir: dir)
success(true)
} else {
success(false)
}
} catch {
success(false)
}
} else if permission == .undetermined {
AVAudioSession.sharedInstance().requestRecordPermission() { allowed in
DispatchQueue.main.async {
if !allowed {
HUD.showMessage("请到设置里面开启录音权限")
}
}
}
success(false)
} else {
HUD.showMessage("请到设置里面开启录音权限")
success(false)
}
}
func startConvertToMP3(dir: URL) {
var total = 0
var read = 0
var write: Int32 = 0
let path = dir.appendingPathComponent("temp.wav").path
let mp3path = dir.appendingPathComponent("temp.mp3").path
var pcm: UnsafeMutablePointer<FILE> = fopen(path, "rb")
fseek(pcm, 4*1024, SEEK_CUR)
let mp3: UnsafeMutablePointer<FILE> = fopen(mp3path, "wb+")
let PCM_SIZE: Int = 8192
let MP3_SIZE: Int32 = 8192
let pcmbuffer = UnsafeMutablePointer<Int16>.allocate(capacity: Int(PCM_SIZE*2))
let mp3buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MP3_SIZE))
let rate: Int32 = 128
let lame = lame_init()
lame_set_num_channels(lame, 1)
lame_set_mode(lame, MONO)
lame_set_in_samplerate(lame, 44100)
lame_set_brate(lame, rate)
lame_set_VBR(lame, vbr_default)
lame_init_params(lame)
DispatchQueue.global(qos: .default).async {
while true {
guard let file = fopen(path, "rb") else {
break
}
pcm = file
fseek(pcm, 4*1024 + total, SEEK_CUR)
read = fread(pcmbuffer, MemoryLayout<Int16>.size, PCM_SIZE, pcm)
if read != 0 {
write = lame_encode_buffer(lame, pcmbuffer, nil, Int32(read), mp3buffer, MP3_SIZE)
fwrite(mp3buffer, Int(write), 1, mp3)
total += read * MemoryLayout<Int16>.size
fclose(pcm)
} else if !self.isRecording {
_ = lame_encode_flush(lame, mp3buffer, MP3_SIZE)
_ = fwrite(mp3buffer, Int(write), 1, mp3)
lame_mp3_tags_fid(lame, mp3)
break
} else {
fclose(pcm)
usleep(50)
}
}
lame_close(lame)
fclose(mp3)
fclose(pcm)
}
}
enum RecordError: Error {
case notStarted
case fileUrlFailed
case tooShort
case recordFailed
}
func stopRecord(minSeconds: Double, completion: (Result<URL, RecordError>) -> Void) {
guard isRecording else { completion(.failure(RecordError.notStarted)); return }
guard let dir = getSoundsURL() else { completion(.failure(RecordError.fileUrlFailed)); return }
isRecording = false
audioRecorder?.stop()
do {
let fileName = UUID().uuidString
let newURL = dir.appendingPathComponent(fileName + ".mp3")
try FileManager.default.moveItem(at: dir.appendingPathComponent("temp.mp3"), to: newURL)
let duration = AVURLAsset(url: newURL).duration.seconds
if duration < minSeconds {
try FileManager.default.removeItem(at: newURL)
completion(.failure(RecordError.tooShort))
return
}
completion(.success(newURL))
} catch {
completion(.failure(RecordError.recordFailed))
}
clearup()
}
private func clearup() {
audioRecorder?.deleteRecording()
audioRecorder = nil
}
}