Skip to content

Instantly share code, notes, and snippets.

@Josscii
Last active June 6, 2020 02:30
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Josscii/fa6663140b5348614e9f0b573d299faa to your computer and use it in GitHub Desktop.
Save Josscii/fa6663140b5348614e9f0b573d299faa to your computer and use it in GitHub Desktop.
iOS 转 mp3 总结

blog

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/ ⭐️⭐️⭐️

build lame

https://sourceforge.net/projects/lame/

https://github.com/kewlbear/lame-ios-build

https://github.com/wuqiong/mp3lame-for-iOS

code

https://github.com/JoanKing/JKRecorderKit ⭐️⭐️⭐️

https://gist.github.com/tad-iizuka/1ca07ca2045a8c6d11e22d15812f7e15 ⭐️⭐️⭐️

book

https://book.douban.com/subject/30124646/

documentation

https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/SupportedAudioFormatsMacOSX/SupportedAudioFormatsMacOSX.html#//apple_ref/doc/uid/TP40003577-CH7-SW1

安卓和 iOS 音频

都支持录制和播放 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
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment