iOS_Swift

ios 音频开发

2018-05-18  本文已影响479人  Mr大喵喵

最近接触到新项目里的音频业务,根据这几天的整理,总结一点内容,方便记录。后续不断更新。。。

在iOS程序中,音频播放随处可见,有的声音只有1秒,有的声音好几分钟 。iOS支持的音频格式AAC、ALAC、IMA4、linear、MP3。

AVAudioPlayer

AVAudioPlayer类用于回放音频数据。是一个易于使用的类,它提供了大量的功能。使用该类可以实现音频的载入,播放,暂停,停止。需要加入AVFoundation.framework框架,在使用的类中引入<AVFoundation/AVFoundation.h>。

可以通过音频的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类可实现视频播放。另外支持播放进度监听。

1.AVPlayer需要通过AVPlayerItem来关联需要播放的媒体。

  #import <AVFoundation/AVFoundation.h>
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:urlStr]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];

[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;
        }

    }
}
[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;
    }
}
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(playFinished:)
                                             name:AVPlayerItemDidPlayToEndTimeNotification
                                           object:_player.currentItem];
__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;
    }
}];
- (void)playSliderValueChange:(UISlider *)sender
{
    //根据值计算时间
    float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);
    //跳转到当前指定时间
    [self.player seekToTime:CMTimeMake(time, 1)];
}

后台播放

AVAudioSession中配置选项:

1.AVAudioSessionCategoryAmbient
当前App的播放声音可以和其他app播放的声音共存,当锁屏或按静音时停止。

2.AVAudioSessionCategorySoloAmbient
只能播放当前App的声音,其他app的声音会停止,当锁屏或按静音时停止。

3.AVAudioSessionCategoryPlayback
只能播放当前App的声音,其他app的声音会停止,当锁屏或按静音时不会停止。

4.AVAudioSessionCategoryRecord
只能用于录音,其他app的声音会停止,当锁屏或按静音时不会停止

5.AVAudioSessionCategoryPlayAndRecord
在录音的同时播放其他声音,当锁屏或按静音时不会停止
可用于听筒播放,比如微信语音消息听筒播放

6.AVAudioSessionCategoryAudioProcessing
使用硬件解码器处理音频,该音频会话使用期间,不能播放或录音

7.AVAudioSessionCategoryMultiRoute
多种音频输入输出,例如可以耳机、USB设备同时播放等
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
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;
上一篇下一篇

猜你喜欢

热点阅读