iOS 錄音功能

音頻基本知識

  • 採樣率(sampleRate)
    所謂採樣就是在時間軸上對信號進行數字化。根據奈奎斯特定理(也稱爲採樣定理),按比聲音最高頻率高2倍以上的頻率對聲音進行採樣(也稱爲AD轉換),對於高質量的音頻信號,其頻率範圍(人耳能夠聽到的頻率範圍)是20Hz~20kHz,所以採樣頻率一般爲44.1kHz,這樣就可以保證採樣聲音達到20kHz也能被數字化,從而使得經過數字化處理之後,人耳聽到的聲音質量不會被降低。而所謂的44.1kHz就是代表1秒會採樣44100次。

  • 量化格式(sampleFormat)
    量化是指在幅度軸上對信號進行數字化,比如用16比特的二進制信號來表示聲音的一個採樣,而16比特(一個short)所表示的範圍是[-32768,32767],共有65536個可能取值,因此最終模擬的音頻信號在幅度上也分爲了65536層

  • 聲道數(channel)
    聲道(Sound Channel) 是指聲音在錄製或播放時在不同空間位置採集或回放的相互獨立的音頻信號,所以聲道數也就是聲音錄製時的音源數量或回放時相應的揚聲器數量。

  • PCM
    通常所說的音頻的裸數據格式就是脈衝編碼調製(Pulse Code Modulation,PCM)數據。描述一段PCM數據一般需要以下幾個概念:量化格式(sampleFormat)、採樣率(sampleRate)、聲道數(channel)。以CD的音質爲例:量化格式(有的地方描述爲位深度)爲16比特(2字節),採樣率爲44100,聲道數爲2,這些信息就描述了CD的音質。而對於聲音格式,還有一個概念用來描述它的大小,稱爲數據比特率,即1秒時間內的比特數目,它用於衡量音頻數據單位時間內的容量大小。而對於CD音質的數據,比特率爲多少呢?計算如下:
    44100 * 16 * 2 = 1378.125kbps

介紹幾種常用的壓縮編碼格式。

WAV編碼

PCM(通常所說的音頻的裸數據格式就是脈衝編碼調製(Pulse Code Modulation,PCM)數據),WAV編碼的一種實現(有多種實現方式,但是都不會進行壓縮操作)就是在PCM數據格式的前面加上44字節,分別用來描述PCM的採樣率、聲道數、數據格式等信息。

  • 特點:音質非常好,大量軟件都支持。
  • 適用場合:多媒體開發的中間文件、保存音樂和音效素材。

MP3編碼

MP3具有不錯的壓縮比,使用LAME編碼(MP3編碼格式的一種實現)的中高碼率的MP3文件,聽感上非常接近源WAV文件,當然在不同的應用場景下,應該調整合適的參數以達到最好的效果。

  • 特點:音質在128Kbit/s以上表現還不錯,壓縮比比較高,大量軟件和硬件都支持,兼容性好。
  • 適用場合:高比特率下對兼容性有要求的音樂欣賞。

AAC編碼

AAC是新一代的音頻有損壓縮技術,它通過一些附加的編碼技術(比如PS、SBR等),衍生出了LC-AAC、HE-AAC、HE-AAC v2三種主要的編碼格式。LC-AAC是比較傳統的AAC,相對而言,其主要應用於中高碼率場景的編碼(≥80Kbit/s);HE-AAC(相當於AAC+SBR)主要應用於中低碼率場景的編碼(≤80Kbit/s);而新近推出的HE-AACv2(相當於AAC+SBR+PS)主要應用於低碼率場景的編碼(≤48Kbit/s)。事實上大部分編碼器都設置爲≤48Kbit/s自動啓用PS技術,而>48Kbit/s則不加PS,相當於普通的HE-AAC。

  • 特點:在小於128Kbit/s的碼率下表現優異,並且多用於視頻中的音頻編碼。
  • 適用場合:128Kbit/s以下的音頻編碼,多用於視頻中音頻軌的編碼。

Ogg編碼

Ogg是一種非常有潛力的編碼,在各種碼率下都有比較優秀的表現,尤其是在中低碼率場景下。Ogg除了音質好之外,還是完全免費的,這爲Ogg獲得更多的支持打好了基礎。Ogg有着非常出色的算法,可以用更小的碼率達到更好的音質,128Kbit/s的Ogg比192Kbit/s甚至更高碼率的MP3還要出色。但目前因爲還沒有媒體服務軟件的支持,因此基於Ogg的數字廣播還無法實現。Ogg目前受支持的情況還不夠好,無論是軟件上的還是硬件上的支持,都無法和MP3相提並論。

  • 特點:可以用比MP3更小的碼率實現比MP3更好的音質,高中低碼率下均有良好的表現,兼容性不夠好,流媒體特性不支持。
  • 適用場合:語音聊天的音頻消息場景。

在info.plist 添加

<key>NSMicrophoneUsageDescription</key>
<string>獲取麥克風權限</string>

向系統申請麥克風權限

 AVCaptureDevice.requestAccess(for: AVMediaType.audio) {(granted: Bool) in
 
 }

封裝的簡單工具類

import AVFoundation

/// 採樣率
enum AudioSampleRate:Int {
    case AudioSampleRate8KHZ = 8000
    case AudioSampleRate12KHZ = 12000
    case AudioSampleRate16KHZ = 16000
    case AudioSampleRate24KHZ = 24000
    case AudioSampleRate32KHZ = 32000
}

///聲道數
enum AudioNumberOfChannels:Int {
    case AudioNumberOfChannelsOne = 1
    case AudioNumberOfChannelsTwo = 2
}

///量化格式
enum AudioLinearPCMBitDepth:Int {
    case AudioLinearPCMBitDepthKey8 = 8
    case AudioLinearPCMBitDepthKey16 = 16
    case AudioLinearPCMBitDepthKey24 = 24
    case AudioLinearPCMBitDepthKey32 = 32
}



enum AudioAuthorizationStatus: Int, CustomStringConvertible {
    case notDetermined = 0
    case notAuthorized
    case authorized
    public var description: String {
        get {
            switch self {
            case .notDetermined:
                return "用戶沒有做選擇"
            case .notAuthorized:
                return "沒有獲得權限"
            case .authorized:
                return "獲得權限"
            }
        }
    }
}

class AudioManager:NSObject{
    static let shared = AudioManager()
    //採樣間隔
    var audioSetting:[String:Any] = [:]
    /// 設置編碼格式
    var formatKey:AudioFormatID = kAudioFormatLinearPCM
    /// 抽樣率
    var sampleRate:AudioSampleRate = .AudioSampleRate8KHZ
    /// 聲道數
    var numberOfChannels:AudioNumberOfChannels = .AudioNumberOfChannelsOne
    /// 位寬(量化格式)
    var linearPCMBitDepth:AudioLinearPCMBitDepth = .AudioLinearPCMBitDepthKey8
    /// 音頻權限
    var audioAuthorizationStatus:AudioAuthorizationStatus = .notDetermined
    var audioRecorder:AVAudioRecorder?
    var audioPlayer:AVAudioPlayer?
    
    private override init() {
        super.init()
        self.checkAudioAuthorization()
    }
}


extension AudioManager{
    func checkAudioAuthorization()  {
        let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.audio)
        switch status {
        case .notDetermined:
            self.audioAuthorizationStatus = .notDetermined
        case .restricted:
            self.audioAuthorizationStatus = .notAuthorized
        case .denied:
            self.audioAuthorizationStatus = .notAuthorized
        case .authorized:
            self.audioAuthorizationStatus = .authorized
        @unknown default:
            self.audioAuthorizationStatus = .notDetermined
        }
        
    }
    
    func requestAudioAuthorization(completionHandler:@escaping (AudioAuthorizationStatus)->()){
        AVCaptureDevice.requestAccess(for: AVMediaType.audio) {[weak self] (granted: Bool) in
            self?.audioAuthorizationStatus = granted ? .authorized : .notAuthorized
            DispatchQueue.main.async {
                completionHandler((granted ? .authorized : .notAuthorized))
            }
        }
    }
}


extension AudioManager{
    func startRecorder(voiceUrl:URL) {
        guard self.audioAuthorizationStatus == .authorized else {
            self.requestAudioAuthorization { (status) -> () in}
            return
        }
        do {//[AVSampleRateKey:sampleRate,AVFormatIDKey:formatKey,AVNumberOfChannelsKey:numberOfChannels,AVLinearPCMIsFloatKey:linearPCMBitDepth]
            try audioRecorder = AVAudioRecorder.init(url: voiceUrl, settings:[AVFormatIDKey:formatKey] )
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.record)
            audioRecorder?.delegate = self
            audioRecorder?.isMeteringEnabled = true
            guard let success = audioRecorder?.record()else{
                return
            }
            if success == true{
                print("開始錄音")
            }else{
                print("錄音失敗")
            }
            
        } catch _ {
            print("錄音異常")
        }
    }
    
    func pauseRecorderAudio()  {
        audioRecorder?.pause()
        print("暫停錄音")
    }
    
    func stopRecorderAudio(){
        audioRecorder?.stop()
        print("停止錄音")
    }
}

extension AudioManager{
    func playAudio(voiceUrl:URL){
        do {
            try audioPlayer = AVAudioPlayer.init(contentsOf:voiceUrl)
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
            audioPlayer?.delegate = self
            guard let success = audioPlayer?.play() else {
                return
            }
            if success == true {
                print("開始播放")
            }else{
                print("開始播放失敗")
            }
        } catch _{
            print("播放異常")
        }
    }
    
    func audioPause() {
        audioPlayer?.pause()
        print("暫停播放")
    }
    func audioStop()  {
        audioPlayer?.stop()
        print("停止播放")
    }
}

extension AudioManager:AVAudioRecorderDelegate{
    func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
        
    }
    func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
        
    }
}


extension AudioManager:AVAudioPlayerDelegate{
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        
    }
    func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
        
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章