美文网首页
1.AVAudioSession、AudioQueue

1.AVAudioSession、AudioQueue

作者: LucXion | 来源:发表于2023-07-02 23:09 被阅读0次
iOS常用音视频框架

Media Player

关键类以MP前缀,主要提供本地音视频播放功能

AVFoundation

除了提供本地音视频播放功能,还可以支持网络流媒体协议。

  • AVAudioPlayer:播放音频文件
  • AVPlayer:可以播放音视频
  • AVCaptureSession:捕获音视频
  • AVAsset、AVMetadataItem:获取媒体信息
  • AVAssetExportSession:格式转换,由AVAssetReader、AVAssetWriter支持

OpenAL

处理音频效果,包括处理声源、音效、环境等。iOS 9以后,Apple推荐使用 AVAudioEngine 代替。

Audio Unit

Apple处理音频框架的底层框架,用于实现低延迟、高效率的音频处理和合成。

VideoToolbox

Apple处理视频框架的底层框架。

AVAudioSession
func setUpSession() {
        /*
         AVAudioSession.Category:
         ambient(环境音):用于播放背景音乐或其他环境声音,可以和其他音频同时播放
         soloAmbient(环境音):播放时会停止其他音频的播放
         playback(音频):音乐播放器或视频播放器
         record(音频录制):音频录制,例如录音机或语音识别。
         playAndRecord(音频录制、播放):支持音频播放和录制,例如VoIP应用或视频通话。例如 K 歌、RTC 场景。注意:用户必须打开音频录制权限(iPhone 麦克风权限)。
         multiRoute(多路输出):多路音频输入输出,例如连接多个音频设备进行混音或分离。
         
         音频:有明确来源和目的的声音,例如音乐、语音、效果声等。在音频播放过程中,我们通常需要考虑音频的音量、平衡、音质等方面,以确保音频的质量和效果。
         环境音:周围环境中存在的声音,例如自然声音、城市噪音、人声等。在环境音播放过程中,我们通常需要考虑环境的噪声水平、声音方向、声音类型等方面,以确保环境音的真实性和逼真度。
         
         AVAudioSession.CategoryOptions:
         mixWithOthers
         duckOthers
         allowBluetooth:只在category为.record或.playAndRecord中使用,代表音频录入和输出全部走蓝牙设备,此时播放和录制的声音音质均为通话音质(16kHz),适用于 RTC 的通话场景,但不适用于 K 歌等需要高音质采集与播放的场景。
         defaultToSpeaker:一般只在category为.playback或.playAndRecord中使用,指定在没有接入其他音频输出设备(耳机等)时,音频的输出使用的是扬声器,而不是听筒
         interruptSpokenAudioAndMixWithOthers(available iOS 9.0)
         allowBluetoothA2DP(available iOS 10.0):代表音频可以输出到高音质(立体声、仅支持音频输出不支持音频录入)的蓝牙设备中,如果使用 Playback 类别,系统将自动使用这个 A2DP 选项,如果使用 PlayAndRecord 类别,需要开发者自己手动设置这个选项,音频采集将使用机身内置麦克风(在需要高音质输出和输入的场景下可以设置成这种)。
         allowAirPlay(available iOS 10.0)
         overrideMutedMicrophoneInterruption(available iOS 14.5)
         */
        try? avSession.setCategory(.playback, mode: .default, options: .mixWithOthers)
        
        /*
        设置 I/O 的 Buffer,Buffer 越小说明延迟越低
         AVAudioSession.Category.ambient:默认缓冲区持续时间为0.092秒(92毫秒)。
         AVAudioSession.Category.soloAmbient:默认缓冲区持续时间为0.092秒(92毫秒)。
        AVAudioSession.Category.playback:默认缓冲区持续时间为0.023秒(23毫秒)。
        AVAudioSession.Category.record:默认缓冲区持续时间为0.092秒(92毫秒)。
        AVAudioSession.Category.playAndRecord:默认缓冲区持续时间为0.023秒(23毫秒)。
        AVAudioSession.Category.multiRoute:默认缓冲区持续时间为0.023秒(23毫秒)。
         */
        guard let bufferDuration = TimeInterval(exactly: 0.002) else { return }
        try? avSession.setPreferredIOBufferDuration(bufferDuration)
        
        // 设置采样率
        let hwSampleRate:Double = 44100.0;
        try? avSession.setPreferredSampleRate(hwSampleRate)
        
        try? avSession.setActive(true)
    }
    
    func notifiSessionState () {
        // 监听音频焦点抢占
        NotificationCenter.default.addObserver(self, selector: #selector(audioSessionInterruptionNoti(notification:)), name: AVAudioSession.interruptionNotification, object: nil)
        
        // 监听声音硬件路由变化
        NotificationCenter.default.addObserver(self, selector: #selector(audioRouteChangeListenerCallback(notification:)), name: AVAudioSession.routeChangeNotification, object: nil)
    }
    
    @objc func audioRouteChangeListenerCallback(notification:NSNotification) {
        guard let userInfo = notification.userInfo, let reasonRawValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonRawValue) else {
                    return
                }
                switch reason {
                case .newDeviceAvailable:
                    // 新的输出设备可用
                    break
                case .oldDeviceUnavailable:
                    // 旧的输出设备不可用
                    break
                case .routeConfigurationChange:
                    // 路由配置发生变化
                    break
                default:
                    break
                }
     }

     @objc func audioSessionInterruptionNoti(notification:NSNotification) {
         guard let userInfo = notification.userInfo, let interruptionTypeRawValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, let interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionTypeRawValue) else {
                    return
                }
         
         switch interruptionType {
                 case .began:
                     // 处理音频焦点抢占
                     break
                 case .ended:
                     guard let optionsRawValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else {
                         return
                     }
                     let options = AVAudioSession.InterruptionOptions(rawValue: optionsRawValue)
                     if options.contains(.shouldResume) {
                         // 处理音频焦点恢复
                     }
                     break
                 @unknown default:
                     break
                 }
     }
ASBD
    UInt32 bytesPerSample = sizeof(Float32);
    AudioStreamBasicDescription asbd;
    bzero(&asbd, sizeof(asbd));
    asbd.mFormatID = kAudioFormatLinearPCM; // 指定音频编码格式
    asbd.mSampleRate = 16000; // 采样率
    asbd.mFramesPerPacket = 1;//每个packet多少帧数据,PCM,frame等同与packet
    
    asbd.mChannelsPerFrame = 1;// 单声道
    /**
     mFormatFlags:格式参数
     kAudioFormatFlagsNativeFloatPacked:每个sample都以浮点类型存储
     kAudioFormatFlagIsNonInterleaved:多声道的音频流不会被交错存储,而是单独存储在不同的缓冲区
     kAudioFormatFlagsNativeFloatPacked:多声道交错存储
     非交错存储:左声道就会在 mBuffers[0]里面,右声道就会在 mBuffers[1]里面,
     交错存储:左右声道就会交错排列在 mBuffers[0]
     
     设置交错存储:
     asbd.mChannelsPerFrame = 2;
     asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
     */
    asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
    
    // 每个通道的样本数据位数,受mFormatFlags的kAudioFormatFlagsNativeFloatPacked影响,如果通道样本位数与样本位数不匹配,会影响音频数据质量
    asbd.mBitsPerChannel = 8 * bytesPerSample;
    
    // 每个音频帧所占字节数,当前一个通道样本数据位数为32位,4字节
    asbd.mBytesPerFrame = bytesPerSample * asbd.mChannelsPerFrame;
    // 每个数据包,所占字节数, mFramesPerPacket * asbd.mBytesPerFrame
    asbd.mBytesPerPacket = asbd.mFramesPerPacket * asbd.mBytesPerFrame;
AudioUnit构建
- (void)AUGraph创建AudioUnit {
    // 构造 AudioUnit 描述
    AudioComponentDescription ioUnitDescription;
    ioUnitDescription.componentType = kAudioUnitType_Output;
    ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
    ioUnitDescription.componentManufacturer=kAudioUnitManufacturer_Apple;
    ioUnitDescription.componentFlags = 0;
    ioUnitDescription.componentFlagsMask = 0;
    
//    实例化一个 AUGraph
    AUGraph processingGraph;
    NewAUGraph(&processingGraph);
    
//    在 AUGraph 中按照描述增加一个 AUNode
    AUNode ioNode;
    AUGraphAddNode(processingGraph, &ioUnitDescription, &ioNode);
    
    /**
     打开 AUGraph
     间接实例化 AUGraph 中所有的 AUNode
     必须在获取 AudioUnit 之前打开整个 Graph
    */
    AUGraphOpen(processingGraph);
    
//    在 AUGraph 中的某个 Node 里面获得 AudioUnit 的引用
    AudioUnit remoteIOUnit;
    AUGraphNodeInfo(processingGraph, ioNode, NULL, &remoteIOUnit);
    
    /*
     RemoteIO,有两个Element,
     Element0在上面,是输出;
     Element1在下面,是输入端。
     
     每个Element有两个Scope
     InputScope:Element1的input跟麦克风连接
     outputScope:Element0的output跟扬声器
     */
    OSStatus status = noErr;
    UInt32 oneFlag = 1;
    UInt32 busZero = 0;//表示将属性应用于音频单元的第一个输出总线
    status = AudioUnitSetProperty(remoteIOUnit,        kAudioOutputUnitProperty_EnableIO,        kAudioUnitScope_Output,        busZero,        &oneFlag,        sizeof(oneFlag));
    CheckStatus(status, @"Could not Connect To Speaker", YES);
    
    
//    AudioUnitSetProperty( remoteIOUnit,kAudioUnitProperty_StreamFormat,
//    kAudioUnitScope_Output, 1, &asbd, sizeof(asbd));
}

//使用自定义的 CheckError 函数来判断错误并且打印 Could not Connect To Speaker 提示
static void CheckStatus(OSStatus status, NSString *message, BOOL fatal)
{
    if(status != noErr)
    {
        char fourCC[16];
        *(UInt32 *)fourCC = CFSwapInt32HostToBig(status);
        fourCC[4] = '\0';
        if(isprint(fourCC[0]) && isprint(fourCC[1]) && isprint(fourCC[2]) &&
           isprint(fourCC[3]))
            NSLog(@"%@: %s", message, fourCC);
        else
            NSLog(@"%@: %d", message, (int)status);
        if(fatal)
            exit(-1);
    }
}


- (void)裸创建AudioUnit {
    // 构造 AudioUnit 描述
    AudioComponentDescription ioUnitDescription;
    ioUnitDescription.componentType = kAudioUnitType_Output;
    ioUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
    ioUnitDescription.componentManufacturer=kAudioUnitManufacturer_Apple;
    ioUnitDescription.componentFlags = 0;
    ioUnitDescription.componentFlagsMask = 0;
    
    // 实际的 AudioUnit 类型
    AudioComponent ioUnitRef = AudioComponentFindNext(NULL, &ioUnitDescription);
    
    // 创建 AudioUnit 引用
    AudioUnit ioUnitInstance;
    
    // 创建 AudioUnit
    AudioComponentInstanceNew(ioUnitRef, &ioUnitInstance);
}

相关文章

网友评论

      本文标题:1.AVAudioSession、AudioQueue

      本文链接:https://www.haomeiwen.com/subject/tthqudtx.html