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);
}
网友评论