AVFoundation框架解析看这里(3)- 音频AVAudi

2019-11-19  本文已影响0人  sweetpf

前言

AVFoundation框架是ios中很重要的框架,所有与视频音频相关的软硬件控制都在这个框架里面,接下来这几篇就主要对这个框架进行介绍和讲解。
便于读者查阅这个AVFoundation框架系列,在此提供目录直通车。
AVFoundation框架解析目录
AVFoundation框架解析目录
AVFoundation框架解析目录

本章导读

上一章节主要以媒体捕捉以起点,拍摄、保存视频,本章将以音频AVFAudio为重点,主要知识点有:

先看看官方的音频处理流程图:


音频处理流程图.png

可以发现,不同应用是共享音频硬件设备资源(麦克风,扬声器),单例AVAudioSession管理多个APP对音频硬件设备的资源使用。通过AudioSession API,可以控制App的audio相关的行为:

AVAudioSession

单例,使用流程为:获取单例AVAudioSession ——> 设置Category和Options ——> 激活回话。在设置Category、Mode和Options,要注意不同的场景。

        let session = AVAudioSession.sharedInstance()
        //设置session类型
        do {
            try session.setCategory(<#T##category: AVAudioSession.Category##AVAudioSession.Category#>, mode: <#T##AVAudioSession.Mode#>, options: <#T##AVAudioSession.CategoryOptions#>)
        } catch let err {
            print("设置类型失败:\(err.localizedDescription)")
        }
        //设置session动作
        do {
            try session.setActive(true)
        } catch let err {
            print("初始化动作失败:\(err.localizedDescription)")
        }

AVAudioSession Category

对于Category,目前有七种枚举类型,其中每种Category都对应是否支持下面几种能力:

可以用一张图表来直观感受每种category具体的能力集: AVAudioSession.Category.png

即:

需要注意一下,选择支持在静音键切到静音状态以及锁屏键切到锁屏状态下仍然可以播放音频 Category 时,必须在应用中开启支持后台音频功能UIBackgroundModes。

注意:并不是一个应用只能使用一个category,程序应该根据实际需要来切换设置不同的category,举个例子,录音的时候,需要设置为AVAudioSessionCategoryRecord,当录音结束时,应根据程序需要更改category为AVAudioSessionCategoryAmbient,AVAudioSessionCategorySoloAmbient或AVAudioSessionCategoryPlayback中的一种。

AVAudioSession Mode

Category定义了七种主场景,实际开发需求中有时候需要对Category进行微调整,即Mode和Options。

同样的,可以用一张图表来直观感受每种Mode具体的能力集:


AVAudioSession Mode.png

AVAudioSession Options

public struct CategoryOptions : OptionSet {

        public init(rawValue: UInt)

        public static var mixWithOthers: AVAudioSession.CategoryOptions { get }

        public static var duckOthers: AVAudioSession.CategoryOptions { get }

        public static var allowBluetooth: AVAudioSession.CategoryOptions { get }

        public static var defaultToSpeaker: AVAudioSession.CategoryOptions { get }

        @available(iOS 9.0, *)
        public static var interruptSpokenAudioAndMixWithOthers: AVAudioSession.CategoryOptions { get }

        @available(iOS 10.0, *)
        public static var allowBluetoothA2DP: AVAudioSession.CategoryOptions { get }

        @available(iOS 10.0, *)
        public static var allowAirPlay: AVAudioSession.CategoryOptions { get }
    }

我们还可以使用options去微调Category行为,如下表:


AVAudioSession Options.png

AVAudioRecorder

讲完AVAudioSession,我们再来看看AVAudioRecorder。AVAudioSession负责管理系统音频硬件,当我们准备录音时,配置AVAudioSession上下文,用AVAudioRecorder来实现音频录制。

    open var isRecording: Bool { get }                       /* 是否在录音 */

    open var url: URL { get }                                       /* 录音文件保存地址 */

    open var settings: [String : Any] { get }              /* 音频设置 */    

    open var format: AVAudioFormat { get }            /* 音频设置 */    

    weak open var delegate: AVAudioRecorderDelegate?

    open var currentTime: TimeInterval { get }   

    open var deviceCurrentTime: TimeInterval { get }

    open var isMeteringEnabled: Bool        
    public init(url: URL, settings: [String : Any]) throws

    public init(url: URL, format: AVAudioFormat) throws
public let AVFormatIDKey: String                               /* 音频格式 */
public let AVSampleRateKey: String                          /* 采样率 */
public let AVNumberOfChannelsKey: String             /* 通道数 */

/* linear PCM keys */
public let AVLinearPCMBitDepthKey: String             /* 采样位数, one of: 8, 16, 24, 32 */
public let AVLinearPCMIsBigEndianKey: String        /* 大端还是小端  内存的组织方式 */
public let AVLinearPCMIsFloatKey: String                 /* 采样信号是整数还是浮点数*/
public let AVLinearPCMIsNonInterleaved: String      /* 是否允许音频交叉他的值 */

/* audio file type key */
public let AVAudioFileTypeKey: String                        /* 音频文件类型 */

/* encoder property keys */
public let AVEncoderAudioQualityKey: String                              /* 音频编码质量 */
public let AVEncoderAudioQualityForVBRKey: String                /* 动态比特率编码时候的音质值 */
public let AVEncoderBitRateKey: String                                       /* 解码率*/
public let AVEncoderBitRatePerChannelKey: String                   /* 声道采样率 */
public let AVEncoderBitRateStrategyKey: String                       /* 编码时比特率策略 */
public let AVEncoderBitDepthHintKey: String                             /* 位深度 */

/* sample rate converter property keys */
public let AVSampleRateConverterAlgorithmKey: String           /* 采样率转换器的算法 */
public let AVSampleRateConverterAudioQualityKey: String      /* 采样率转换器的音质值 */

/* channel layout */
public let AVChannelLayoutKey: String                                        /* 通道布局值 */

/* property values */
/* values for AVEncoderBitRateStrategyKey */
public let AVAudioBitRateStrategy_Constant: String                              /* 常数 */         
public let AVAudioBitRateStrategy_LongTermAverage: String              /* 平均数 */
public let AVAudioBitRateStrategy_VariableConstrained: String           /* 有限制的 */
public let AVAudioBitRateStrategy_Variable: String                                 /* 可变的 */

/* values for AVSampleRateConverterAlgorithmKey */
public let AVSampleRateConverterAlgorithm_Normal: String                   /* 普通 */
public let AVSampleRateConverterAlgorithm_Mastering: String               /* 母带处理 */
public let AVSampleRateConverterAlgorithm_MinimumPhase: String     /*  */
   open func prepareToRecord() -> Bool 

    open func record() -> Bool

    open func record(atTime time: TimeInterval) -> Bool 

    open func record(forDuration duration: TimeInterval) -> Bool 

    open func record(atTime time: TimeInterval, forDuration duration: TimeInterval) -> Bool 

    open func pause()

    open func stop()

    open func deleteRecording() -> Bool

AVAudioPlayer

音频播放技术有多种,例如System Sound Services、AVAudioPlayer等,本文以AVAudioPlayer为音频播放技术展开说明。

    open var isPlaying: Bool { get } 

    open var numberOfChannels: Int { get }

    open var duration: TimeInterval { get } /* the duration of the sound. */

    weak open var delegate: AVAudioPlayerDelegate?

    open var url: URL? { get }

    open var data: Data? { get }

    open var pan: Float

    open var volume: Float

    open func setVolume(_ volume: Float, fadeDuration duration: TimeInterval)

    open var enableRate: Bool 

    open var rate: Float 

    open var currentTime: TimeInterval

    open var deviceCurrentTime: TimeInterval { get }

    open var numberOfLoops: Int

    open var settings: [String : Any] { get }
    
    open var format: AVAudioFormat { get }

    open var isMeteringEnabled: Bool 

    open var channelAssignments: [AVAudioSessionChannelDescription]? 
    public init(contentsOf url: URL, fileTypeHint utiString: String?) throws

    public init(data: Data, fileTypeHint utiString: String?) throws
    open func prepareToPlay() -> Bool

    open func play() -> Bool 

    open func play(atTime time: TimeInterval) -> Bool

    open func pause()

    open func stop()

音频中断处理

其他APP或者电话会中断我们的APP音频,所以相应的我们要做出处理。
我们可以通过监听AVAudioSessionInterruptionNotification这个key获取音频中断事件回调回来Userinfo键值。

AVAudioSessionInterruptionTypeKey:
取值AVAudioSessionInterruptionTypeBegan表示中断开始
取值AVAudioSessionInterruptionTypeEnded表示中断结束
中断开始:我们需要做的是保存好播放状态,上下文,更新用户界面等
中断结束:我们要做的是恢复好状态和上下文,更新用户界面,根据需求准备好之后选择是否激活我们session。

选择不同的音频播放技术,处理中断方式也有差别,具体如下:

AVSpeechSynthesizer

AVSpeechSynthesizer属于AVFAudio的一份子,整体上比较简单。它可以很方便的在iOS应用中实现”将文本转换成语音”的功能,设计到AVSpeechSynthesisVoice、AVSpeechUtterance以及AVSpeechSynthesizer等重要概念,这块暂时涉及的比较少,不做过多讲解,提供一段代码供参考:

        let synthesizer = AVSpeechSynthesizer()
        let voice = AVSpeechSynthesisVoice(language: "zh-CN")
        let utterance = AVSpeechUtterance(string: "小主,您可以开始录制音频了!")
        utterance.rate = 1.0
        utterance.voice = voice
        utterance.pitchMultiplier = 0.8
        utterance.postUtteranceDelay = 0.1
        synthesizer.speak(utterance)

你可以通过AVSpeechSynthesisVoice.speechVoices()获取设备支持的语言列表。




音频录制Demo

import UIKit
import AVFoundation


typealias AVFAudioRecordBlock = (_ audioUrl: URL) -> ()



/*
 不同应用共享音频硬件设备(麦克风,扬声器)
 单例AVAudioSession管理多个APP对音频硬件设备的资源使用。
 
 用单例AVFAudioRecorder来处理应用的音频处理
 */
class AVFAudioRecorder: NSObject {
    static let shared = AVFAudioRecorder()
    private override init() {}
    
    // 录音
    private var recorder: AVAudioRecorder? = nil
    private var recorderFilePath: String? = nil
    private var timer: Timer? = nil
    private var timeLength: Int = 0
    private var recordBlock: AVFAudioRecordBlock?
    

    deinit {
        timer?.invalidate()
        timer = nil
    }
    

    public func record(setting: [String: Any]? = nil, block: @escaping AVFAudioRecordBlock) {
        RRXCAuthorizeManager.authorization(.microphone, completion: { (substate) in
            if substate == .authorized {
                self.recordBlock = block
                
                let session = AVAudioSession.sharedInstance()
                do {
                    try session.setCategory(AVAudioSession.Category.playAndRecord)
                } catch let err{
                    print("设置类型失败:\(err.localizedDescription)")
                }
                do {
                    try session.setActive(true)
                } catch let err {
                    print("初始化动作失败:\(err.localizedDescription)")
                }
                
                self.timeLength = 0

                //默认设置
                let _recordSetting = [AVSampleRateKey: NSNumber(value: 16000),
                    AVFormatIDKey: NSNumber(value: kAudioFormatLinearPCM),
                    AVLinearPCMBitDepthKey: NSNumber(value: 16),
                    AVNumberOfChannelsKey: NSNumber(value: 1),
                    AVEncoderAudioQualityKey: NSNumber(value: AVAudioQuality.min.rawValue)
                ]
                let recordSetting: [String: Any] = setting ?? _recordSetting
                
                do {
                    self.recorderFilePath = self.getFilePath()
                    if let filePath = self.recorderFilePath {
                        let url = URL(fileURLWithPath: filePath)
                        self.recorder = try AVAudioRecorder(url: url, settings: recordSetting)
                        self.recorder?.isMeteringEnabled = true
                        self.recorder?.prepareToRecord()
                        self.recorder?.record()

                        self.timer = Timer.every(Double(1.0).seconds) { (timer: Timer) in
                            self.timeLength += 1
                        }
                    }
                } catch let err {
                    print("录音失败:\(err.localizedDescription)")
                }
            }
        })
    }

    public func stopRecord() {
        timer?.invalidate()
        timer = nil

        if let recorder = recorder {
            recorder.stop()
            self.recorder = nil
        }
        
        if let _recordBlock = recordBlock, let filePath = recorderFilePath {
            _recordBlock(URL(fileURLWithPath: filePath))
            
            recordBlock = nil
        }
    }

    public func cancelRecord() {
        stopRecord()
        
        //清空回调
        recordBlock = nil
        
        //删除录音
        if let filePath = recorderFilePath {
            try? FileManager.default.removeItem(at: URL(fileURLWithPath: filePath))
        }
    }
    
    fileprivate func getFilePath() -> String! {
        return NSHomeDirectory() + "/Library/Caches/\(Date().timeIntervalSince1970).wav"
    }
}

音频播放Demo

import UIKit
import AVFoundation

typealias PlayerDidFinishPlayingBlock = () -> ()

class AVFAudioPalyer: NSObject, AVAudioPlayerDelegate {
    
    private var player: AVAudioPlayer? = nil
    private var playBlock: PlayerDidFinishPlayingBlock?
    
    static let shared = AVFAudioPalyer()
    private override init() {}
    
    func play(_ url: URL?,_ block: PlayerDidFinishPlayingBlock?) {
        UIDevice.current.isProximityMonitoringEnabled = true

        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
        } catch let err {
            print("设置扬声器失败:\(err.localizedDescription)")
        }

        do {
            playBlock = block

            if let _url = url {
                player = try AVAudioPlayer(contentsOf: _url)
                player?.delegate = self
                player?.prepareToPlay()
                player?.play()
            }
        } catch let err {
            UIDevice.current.isProximityMonitoringEnabled = false
            print("播放失败:\(err.localizedDescription)")
        }
    }

    //结束播放
    func stopPlay() {
        UIDevice.current.isProximityMonitoringEnabled = false
        if let player = player {
            player.stop()
            playBlock = nil
            self.player?.delegate = nil
            self.player = nil
        }
    }
    

    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        UIDevice.current.isProximityMonitoringEnabled = false
        if let _playBlock = playBlock {
            _playBlock()
        }
    }

}

如果喜欢,请帮忙点赞。支持转载,转载请附原文链接。

上一篇 下一篇

猜你喜欢

热点阅读