ios 音频开发
最近接触到新项目里的音频业务,根据这几天的整理,总结一点内容,方便记录。后续不断更新。。。
在iOS程序中,音频播放随处可见,有的声音只有1秒,有的声音好几分钟 。iOS支持的音频格式AAC、ALAC、IMA4、linear、MP3。
AVAudioPlayer
AVAudioPlayer类用于回放音频数据。是一个易于使用的类,它提供了大量的功能。使用该类可以实现音频的载入,播放,暂停,停止。需要加入AVFoundation.framework框架,在使用的类中引入<AVFoundation/AVFoundation.h>。
- AVAudioPlayer只能播放本地音乐文件
可以通过音频的NSData或者本地音频文件的url,来创建一个AVAudioPlayer实例,如加载本地的music.mp3的音频文件:
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"music" withExtension:@"mp3"];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];
if (self.player) {
[self.player prepareToPlay];
}
加载音频文件后,可以调用prepareToPlay方法,这样可以提前获取需要的硬件支持,并加载音频到缓冲区。在调用play方法时,减少开始播放的延迟。
当调用play方法后,开始播放音乐:
[self.player play];
可以调用pause或stop来暂停播放,这里的stop方法的效果也只是暂停播放,不同之处是stop会撤销prepareToPlay方法所做的准备。
[self.player stop];
另外,我们可以进行更多的操作:
单独设置音乐的音量(默认1.0,可设置范围为0.0至1.0,两个极端为静音、系统音量):
self.player.volume = 0.5;
修改左右声道的平衡(默认0.0,可设置范围为-1.0至1.0,两个极端分别为只有左声道、只有右声道):
self.player.pan = -1;
设置播放速度(默认1.0,可设置范围为0.5至2.0,两个极端分别为一半速度、两倍速度):
self.player.rate = 0.5;
设置循环播放(默认1,若设置值大于0,则为相应的循环次数,设置为-1可以实现无限循环):
self.player.numberOfLoops = -1;
AVPlayer
支持播放本地、分步下载、或在线流媒体音视频,不仅可以播放音频,配合AVPlayerLayer类可实现视频播放。另外支持播放进度监听。
- AVPLayer:可以用来播放在线及本地音视频
- AVAudioSession:音频会话,主要用来管理音频设置与硬件交互
使用时需要导入
1.AVPlayer需要通过AVPlayerItem来关联需要播放的媒体。
#import <AVFoundation/AVFoundation.h>
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:urlStr]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];
- 2.在准备播放前,通过KVO添加播放状态改变监听
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
处理KVO回调事件:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"status"]) {
switch (self.player.status) {
case AVPlayerStatusUnknown:
{
NSLog(@"未知转态");
}
break;
case AVPlayerStatusReadyToPlay:
{
NSLog(@"准备播放");
}
break;
case AVPlayerStatusFailed:
{
NSLog(@"加载失败");
}
break;
default:
break;
}
}
}
- 3.KVO监听音乐缓冲状态:
[self.player.currentItem addObserver:self
forKeyPath:@"loadedTimeRanges"
options:NSKeyValueObservingOptionNew
context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//...
if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;
//本次缓冲的时间范围
CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
//缓冲总长度
NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
//音乐的总时间
NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);
//计算缓冲百分比例
NSTimeInterval scale = totalLoadTime/duration;
//更新缓冲进度条
// self.loadTimeProgress.progress = scale;
}
}
- 4.开始播放后,通过KVO添加播放结束事件监听
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playFinished:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:_player.currentItem];
- 5.开始播放时,通过AVPlayer的方法监听播放进度,并更新进度条(定期监听的方法):
__weak typeof(self) weakSelf = self;
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
//当前播放的时间
float current = CMTimeGetSeconds(time);
//总时间
float total = CMTimeGetSeconds(item.duration);
if (current) {
float progress = current / total;
//更新播放进度条
weakSelf.playSlider.value = progress;
}
}];
- 6.用户拖动进度条,修改播放进度
- (void)playSliderValueChange:(UISlider *)sender
{
//根据值计算时间
float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);
//跳转到当前指定时间
[self.player seekToTime:CMTimeMake(time, 1)];
}
后台播放
AVAudioSession中配置选项:
- AVAudioSessionCategory
1.AVAudioSessionCategoryAmbient
当前App的播放声音可以和其他app播放的声音共存,当锁屏或按静音时停止。
2.AVAudioSessionCategorySoloAmbient
只能播放当前App的声音,其他app的声音会停止,当锁屏或按静音时停止。
3.AVAudioSessionCategoryPlayback
只能播放当前App的声音,其他app的声音会停止,当锁屏或按静音时不会停止。
4.AVAudioSessionCategoryRecord
只能用于录音,其他app的声音会停止,当锁屏或按静音时不会停止
5.AVAudioSessionCategoryPlayAndRecord
在录音的同时播放其他声音,当锁屏或按静音时不会停止
可用于听筒播放,比如微信语音消息听筒播放
6.AVAudioSessionCategoryAudioProcessing
使用硬件解码器处理音频,该音频会话使用期间,不能播放或录音
7.AVAudioSessionCategoryMultiRoute
多种音频输入输出,例如可以耳机、USB设备同时播放等
- AVAudioSessionCategoryOptions
1.AVAudioSessionModeDefault
默认的模式,适用于所有的场景,可用于场景还原
2.AVAudioSessionModeVoiceChat
适用类别 :
AVAudioSessionCategoryPlayAndRecord
应用场景VoIP
3.AVAudioSessionModeGameChat
适用类别:
AVAudioSessionCategoryPlayAndRecord
应用场景游戏录制,由GKVoiceChat自动设置,无需手动调用
4.AVAudioSessionModeVideoRecording
适用类别:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryRecord
应用场景视频录制
5.AVAudioSessionModeMoviePlayback
适用类别:
AVAudioSessionCategoryPlayBack
应用场景视频播放
6.AVAudioSessionModeVideoChat
适用类别:
AVAudioSessionCategoryPlayAndRecord
应用场景视频通话
7.AVAudioSessionModeMeasurement
适用类别:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryRecord
AVAudioSessionCategoryPlayback
AVAudioSessionModeSpokenAudio
- AVAudioSessionMode
1.AVAudioSessionCategoryOptionMixWithOthers
适用于:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute
用于可以和其他app进行混音
2.AVAudioSessionCategoryOptionDuckOthers
适用于:
AVAudioSessionCategoryAmbient
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute
用于压低其他声音播放的音量,使期音量变小
3.AVAudioSessionCategoryOptionAllowBluetooth
适用于:
AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord
用于是否支持蓝牙设备耳机等
4.AVAudioSessionCategoryOptionDefaultToSpeaker
适用于:
AVAudioSessionCategoryPlayAndRecord
用于将声音从Speaker播放,外放,即免提
5.AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
适用于:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute
6.AVAudioSessionCategoryOptionAllowBluetoothA2DP
适用于:
AVAudioSessionCategoryPlayAndRecord
蓝牙和a2dp
7.AVAudioSessionCategoryOptionAllowAirPlay
适用于:
AVAudioSessionCategoryPlayAndRecord
airplay
系统中断响应
上面说的这些Category啊、Option啊以及Mode都是对自己作为播放主体时的表现,但是假设,现在正在播放着,突然来电话了、闹钟响了或者你在后台放歌但是用户启动其他App用上面的方法影响的时候,我们的App该如何表现呢?最常用的场景当然是先暂停,待恢复的时候再继续。那我们的App要如何感知到这个终端以及何时恢复呢?
AVAudioSession提供了多种Notifications来进行此类状况的通知。其中将来电话、闹铃响等都归结为一般性的中断,用
AVAudioSessionInterruptionNotification来通知。其回调回来的userInfo主要包含两个键:
AVAudioSessionInterruptionTypeKey: 取值为AVAudioSessionInterruptionTypeBegan表示中断开始,我们应该暂停播放和采集
取值为AVAudioSessionInterruptionTypeEnded表示中断结束,我们可以继续播放和采集。
AVAudioSessionInterruptionOptionKey: 当前只有一种值AVAudioSessionInterruptionOptionShouldResume表示此时也应该恢复继续播放和采集。
而将其他App占据AudioSession的时候用AVAudioSessionSilenceSecondaryAudioHintNotification来进行通知。其回调回来的userInfo键为:
AVAudioSessionSilenceSecondaryAudioHintTypeKey
可能包含的值:
AVAudioSessionSilenceSecondaryAudioHintTypeBegin: 表示其他App开始占据Session
AVAudioSessionSilenceSecondaryAudioHintTypeEnd: 表示其他App开始释放Session
首先在AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:方法中添加代码:
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
然后在Info.plist文件中添加:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
锁屏信息
- 在每次准备播放下一首时,更新锁屏信息:
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"封面图片"]];
infoCenter.nowPlayingInfo = @{
MPMediaItemPropertyTitle :@"歌曲名",
MPMediaItemPropertyArtist :@"歌手名",
MPMediaItemPropertyPlaybackDuration :歌曲时间长度,
MPNowPlayingInfoPropertyElapsedPlaybackTime : @(已播放时间长度),
MPMediaItemPropertyArtwork : artwork
};
通过耳机、锁屏界面控制
- 在需要处理远程控制事件的具体控制器或其它类中调用下面这个方法
#import <MediaPlayer/MPRemoteCommandCenter.h>
#import <MediaPlayer/MPRemoteCommand.h>
- (void)remoteControlEventHandler
{
// 直接使用sharedCommandCenter来获取MPRemoteCommandCenter的shared实例
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
// 启用播放命令 (锁屏界面和上拉快捷功能菜单处的播放按钮触发的命令)
commandCenter.playCommand.enabled = YES;
// 为播放命令添加响应事件, 在点击后触发
[commandCenter.playCommand addTarget:self action:@selector(playAction:)];
// 播放, 暂停, 上下曲的命令默认都是启用状态, 即enabled默认为YES
// 为暂停, 上一曲, 下一曲分别添加对应的响应事件
[commandCenter.pauseCommand addTarget:self action:@selector(pauseAction:)];
[commandCenter.previousTrackCommand addTarget:self action:@selector(previousTrackAction:)];
[commandCenter.nextTrackCommand addTarget:self action:@selector(nextTrackAction:)];
// 启用耳机的播放/暂停命令 (耳机上的播放按钮触发的命令)
commandCenter.togglePlayPauseCommand.enabled = YES;
// 为耳机的按钮操作添加相关的响应事件
[commandCenter.togglePlayPauseCommand addTarget:self action:@selector(playOrPauseAction:)];
}
-(void)playAction:(id)obj{
[[HYPlayerTool sharePlayerTool] play];
}
-(void)pauseAction:(id)obj{
[[HYPlayerTool sharePlayerTool] pause];
}
-(void)nextTrackAction:(id)obj{
[[HYPlayerTool sharePlayerTool] playNext];
}
-(void)previousTrackAction:(id)obj{
[[HYPlayerTool sharePlayerTool] playPre];
}
-(void)playOrPauseAction:(id)obj{
if ([[HYPlayerTool sharePlayerTool] isPlaying]) {
[[HYPlayerTool sharePlayerTool] pause];
}else{
[[HYPlayerTool sharePlayerTool] play];
}
}
AVQueuePlayer
AVPlayer只支持单个媒体资源的播放,我们可以使用AVPlayer的子类AVQueuePlayer实现列表播放。在AVPlayer的基础上,增加以下方法:
//通过给定的AVPlayerItem数组创建一个AVQueuePlayer实例
+ (instancetype)queuePlayerWithItems:(NSArray<AVPlayerItem *> *)items;
//通过给定的AVPlayerItem数组初始化AVQueuePlayer实例
- (AVQueuePlayer *)initWithItems:(NSArray<AVPlayerItem *> *)items;
//获得当前的播放队列数组
- (NSArray<AVPlayerItem *> *)items;
//停止播放当前音乐,并播放队列中的下一首
- (void)advanceToNextItem;
//往播放队列中插入新的AVPlayerItem
- (void)insertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem;
//从播放队列中移除指定AVPlayerItem
- (void)removeItem:(AVPlayerItem *)item;
//清空播放队列
- (void)removeAllItems;
- 官方API中没找到播放上一首的方法,所以其实直接用AVPlayer做列表播放也是可以的,自己维护一个播放列表数组,监听用户点击上一首和下一首按钮,并监听播放结束事件,调用 AVPlayer 实例的replaceCurrentItemWithPlayerItem:方法传入播放列表中的上一首或下一首就行。