AVAudioSesion和AVAudioPlayer的基本使用

 

iOS基础篇-AVPLayerAVAudioSession

 

2018.02.27 16:17 字数 215 阅读 1516评论 0喜欢 4

  • 作用
  • AVPLayer:可以用来播放在线及本地音视频
  • AVAudioSession:音频会话,主要用来管理音频设置与硬件交互
  • 使用时需要导入

#import <AVFoundation/AVFoundation.h>

  • AVAudioSession中配置选项:

AVAudioSessionCategory

注意:除了 AVAudioSessionCategoryMultiRoute 外,其他的 Category 都遵循 last in wins 原则,即最后接入的音频设备作为输入或输出的主设备。

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

使用AVAudioSession对上下文app进行音频控制

/**

 *  SetAVAudioSessionCategory

 */

- (void)setAVAudioSessionCategory:(NSString *)category

                          options:(AVAudioSessionCategoryOptions)options {

    NSError *error = nil;

    BOOL success = [[AVAudioSession sharedInstance] setCategory:category withOptions:options error:&error];

    

    if (!success) {

        NSLog(@"SetCategory error:%@ ",error.description);

    }

    

    AVAudioSession *audioSession = [AVAudioSession sharedInstance];

    BOOL ret = [audioSession setActive:YES error:&error];

    

    if (!ret) {

        NSLog(@"%s - activate audio session failed with error %@", __func__,[error description]);

    }

}

  • 结合AVPlayer使用

@property (strong, nonatomic) AVPlayer *avPlayer;

 

AVPlayerItem *playerItem  = [self creatAVPlayerItemWithUrlStr:AudioUrlStr];

self.playState = VoicePlayStateLoding;

    

if (self.avPlayer != nil && self.avPlayer.status == AVPlayerStatusReadyToPlay) {

   [self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];

} else {

   self.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];

}

    

[self setSessionLabType:@"SoloAmbient"];

  • 监听音频受其他事件(电话,闹铃,其他app占用通道)影响

- (void)customAddNotification {

    [[NSNotificationCenter defaultCenter] addObserver:self

                                             selector:@selector(audioRouteChangeListenerCallback:)

                                                 name:AVAudioSessionRouteChangeNotification object:nil];

    

    [[NSNotificationCenter defaultCenter] addObserver:self

                                             selector:@selector(otherAppAudioSessionCallBack:)

                                                 name:AVAudioSessionSilenceSecondaryAudioHintNotification object:nil];

    

    [[NSNotificationCenter defaultCenter] addObserver:self

                                             selector:@selector(systermAudioSessionCallBack:)

                                                 name:AVAudioSessionInterruptionNotification object:nil];

}

 

// 监听外部设备改变

- (void)audioRouteChangeListenerCallback:(NSNotification*)notification {

    NSDictionary *interuptionDict = notification.userInfo;

    NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];

    switch (routeChangeReason) {

        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:{

            NSLog(@"headset input");

            break;

        }

        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:{

            NSLog(@"pause play when headset output");

            [self.avPlayer pause];

            break;

        }

        case AVAudioSessionRouteChangeReasonCategoryChange:

            NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");

            break;

    }

}

 

// 其他app占用音频通道

- (void)otherAppAudioSessionCallBack:(NSNotification *)notification {

    NSDictionary *interuptionDict = notification.userInfo;

    NSInteger interuptType = [[interuptionDict valueForKey:AVAudioSessionSilenceSecondaryAudioHintTypeKey] integerValue];

    switch (interuptType) {

        case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:{

            [self.avPlayer pause];

            NSLog(@"pause play when other app occupied session");

            break;

        }

        case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:{

            NSLog(@"occupied session");

            break;

        }

        default:

            break;

    }

}

 

// 电话、闹铃等一般性中断通知

- (void)systermAudioSessionCallBack:(NSNotification *)notification {

    NSDictionary *interuptionDict = notification.userInfo;

    NSInteger interuptType = [[interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];

 

    switch (interuptType) { 

        case AVAudioSessionInterruptionTypeBegan:{

            [self.avPlayer pause];

            NSLog(@"pause play when phone call or alarm ");

            break;

        }

        case AVAudioSessionInterruptionTypeEnded:{

            break;

        }

        default:

            break;

    }

}

  • 总结
  • 使用AVPAVAudioSession和AVPlayer结合可灵活实现音视频播放,可控制听筒、前后台、音频混播等多种播放控制。在使用中我们可以结合场景来切换不同的session.
  • Demo地址:https://github.com/MrWilsonXu/AudioSession.git

AVAudioSession

 

在上一遍学习中提到了AudioSession和AVAudioSession两个类,在苹果文档中可以看到,在iOS 7以后苹果建议使用的是AVAudioSession,所以AudioSession就等以后有时间再进行学习。

AVAudioSession是AVFoundation框架中的类,用来设置app的音频上下文,主要的功能有:

激活或者取消app的audio session

设置系统使用音频的方式

配置如采样率,I/O缓存时长和信道数等音频设置

处理Route Change(如拔出耳机后音频停止播放)

应对其他事件

系统只允许一个session来控制audio routin。如果当前有多个sessions在运行的话,系统会根据当前的重要性来选择一个最重要的session并且判断该session是否允许混合。

 

获取AVAudioSession单例

 

[AVAudioSession shareInstance];

 

录音时请求用户允许

 

- (void)requestRecordPermission:(PermissionBlock)response // 参数response表示用户允许或拒绝时的回调函数

录音需要获取用户的允许,系统会自动跳出提示的。

 

typedef void (^PermissionBlock)(BOOL granted)

PermissionBlock,返回YES允许录音,NO则不允许

 

- (AVAudioSessionRecordPermission)recordPermission

返回当前录音的许可状态

有三个返回参数:AVAudioSessionPermissionGranted/AVAudioSessionPermissionDenied/AVAudioSessionPermissionUndetermined

 

 

管理Audio Session

 

@property(readonly) NSString *category

当前Audio session的类别,默认是AVAudioSessionCategorySoloAmbinet

 

NSString *const  AVAudioSessionCategoryAmbient ; // 该播放不是主要的。使用此类时,音频会与其他app的进行混合。锁屏或者静音模式下无法播放声音

NSString *const  AVAudioSessionCategorySoloAmbient ; // 锁屏或者静音模式下无法播放声音。音频不会与其他app混合

NSString *const  AVAudioSessionCategoryPlayback ; // 

NSString *const  AVAudioSessionCategoryRecord ;

NSString *const  AVAudioSessionCategoryPlayAndRecord ;

NSString *const  AVAudioSessionCategoryAudioProcessing ;

NSString *const  AVAudioSessionCategoryMultiRoute;

Audio Session的Category

 

@property(readonly) AVAudioSessionCategoryOptions categoryOptions

当前Category相关的选项

 

enum {

   AVAudioSessionCategoryOptionMixWithOthers  = 1, // 混合其他已激活的Session的音频。使用这个选项激活Session的话,会打断其他app正在播放的声音。如果不使用的话,则会打断其他不允许混合的session

   AVAudioSessionCategoryOptionDuckOthers  = 2, // 在本Session播放时避开其他session的音频

   AVAudioSessionCategoryOptionAllowBluetooth  = 4, // 允许蓝牙作为输入

   AVAudioSessionCategoryOptionDefaultToSpeaker  = 8 // 接入到系统默认的扬声器

};

typedef NSUInteger  AVAudioSessionCategoryOptions;

前两个个Options都需要在AVAudioSessionCategoryPlayback或者AVAudioSessionPlayAndRecord下才能使用,第三个在AVAudioSessionPlayAndRecord或者AVAudioSessionRecord,最后一个需在AVAudioSessionPlayAndRecord下使用

 

- (BOOL)setCategory:(NSString *)theCategory

              error:(NSError **)outError

设置Audio Session的Category

一般会在激活之前设置好Category和mode。但是也可以在已激活的audio session中设置,不过会在发生route change之后才会发生改变

 

- (BOOL)setCategory:(NSString *)category

        withOptions:(AVAudioSessionCategoryOptions)options

              error:(NSError **)outError

跟上面的函数相似

 

@property(readonly) NSString *mode

Audio Session使用的mode

 

NSString *const  AVAudioSessionModeDefault ;

NSString *const  AVAudioSessionModeVoiceChat ;

NSString *const  AVAudioSessionModeGameChat 

NSString *const AVAudioSessionModeVideoRecording;

NSString *const  AVAudioSessionModeMeasurement ;

NSString *const  AVAudioSessionModeMoviePlayback ;

NSString *const  AVAudioSessionModeVideoChat;

Audio Session Mode

 

- (BOOL)setMode:(NSString *)theMode

          error:(NSError **)outError

设置Mode

 

- (BOOL)setActive:(BOOL)beActive

            error:(NSError **)outError

激活或者取消Audio Session

如果另外一个活动中的Audio Session的优先级高于你所定义的(比如说有电话打进来)或者有一个允许混音的Audio Session,那么激活可能会失败。如果当前的音频相关对象正在运行的话,取消Audio Session有可能会失败

 

- (BOOL)setActive:(BOOL)active

      withOptions:(AVAudioSessionSetActiveOptions)options

            error:(NSError **)outError

跟上面的函数相似

第二个参数:

enum {

   AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation  = 1

};

typedef NSUInteger  AVAudioSessionSetActiveOptions;

表明当你的Audio Session被取消时,其他被你的Audio Session所打断的Audio Session可以恢复至其激活状态

 

 

音频设备设定

 

@property(readonly) float outputVolume

outputVolume的范围是0.0-1.0.系统的音量可以直接设置,想要控制音量大小的话,可以使用MPVolumeView类。

可以通过KVO来监测该值的变化

 

@property(readonly) float inputGain

输入增益inputGain的范围是0.0 - 1.0.

可通过KVO来监测变化

 

@property(readonly, getter=isInputGainSettable) BOOL inputGainSettable

因为不是所有设备都允许添加输入增益,所以在设置之前要检查是否允许设置输入增益inputGain。

 

- (BOOL)setInputGain:(float)gain

               error:(NSError **)outError

设置inputGain为特定值。gain的范围是0.0 - 1.0。调用此方法之前要检查是否允许设置inputGain

 

@property(readonly) NSTimeInterval inputLatency

音频输入的等待时间,以秒为单位。

如果Audio Session的Category设置不允许音频输入,该函数会返回AVAudioSessionErrorCodeIncompatibleCategory

 

@property(readonly) NSTimeInterval outputLatency

音频输出的等待时间,以秒为单位

 

@property(readonly) double sampleRate

当前音频的采样率,以赫兹为单位

 

@property(readonly) double preferredSampleRate

首选的采样率

 

- (BOOL)setPreferredSampleRate:(double)sampleRate

                         error:(NSError **)outError

设置输入输出的首选采样率

在激活和取消Audio Session之前或之后都可以进行设置

 

@property(readonly) NSTimeInterval IOBufferDuration

I/O的缓冲时间

 

@property(readonly) NSTimeInterval preferredIOBufferDuration

首选的I/O缓冲时间

 

- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration

                               error:(NSError **)outError

设置首选I/O缓冲时间

I/O缓冲时间是指单音频输入/输出周期的时长。比如说I/O缓冲时间为0.005s,那么在每一个音频I/O周期里,在获取输入时,会收到0.005s的音频,在输出时,必须提供0.005s的音频

通常来说I/O缓冲时间的范围是0.005s至0.93s

 

@property(readonly) BOOL secondaryAudioShouldBeSilencedHint

表明另一个app是否正在播放音频

返回YES表明另一个有着不允许混音的Audio Session的app正在播放音频

 

 

音频信道

 

@property(readonly) NSInteger inputNumberOfChannels

当前route的音频输入信道数

使用KVO来监测此值。如果Audio Session不支持录音的话,会产生AVAudioSessionErrorCodeIncompatibleCategory

 

@property(readonly) NSInteger maximumInputNumberOfChannels

可接受的最大的音频输入信道数

 

@property(readonly) NSInteger preferredInputNumberOfChannels

首选的音频输入信道数

 

- (BOOL)setPreferredInputNumberOfChannels:(NSInteger)count

                                    error:(NSError **)outError

设置首选的音频输入信道数

只能在设置Audio Session的Category和mode,并在激活session后调用

 

输出的话将input换成output即可

 

音频输入输出的Route

 

@property(readonly) AVAudioSessionRouteDescription *currentRoute

描述当前音频输入输出的route

 

@property(readonly, getter=isInputAvailable) BOOL inputAvailable

表明当前设备是否支持音频输入

 

@property(readonly, getter=isOtherAudioPlaying) BOOL otherAudioPlaying

其他app是否正在播放音频

在iOS 8.0以后要使用secondaryAudioShouldBeSilencedHint来代替这个属性

 

- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride

                          error:(NSError **)outError

暂时更改当前的音频路线

第一个参数:

enum {

   AVAudioSessionPortOverrideNone     = 0, // 不覆写

   AVAudioSessionPortOverrideSpeaker  = 'spkr' // 将输入输出变换到内置扬声器和话筒

};

typedef NSUInteger  AVAudioSessionPortOverride;

使用AVAudioSessionPortOverrideSpeaker和AVAudioSessionCategoryPlayAndRecord的话会无视其他设置进行话筒录音和扬声器播放,不过会在重新调用这个方法并且参数为AVAudioSessionPortOverrideNone时失效

 

@property(readonly) NSArray *availableInputs

Route的输入端口数组

AVAudioSessionPortDescription对象的数组。代表了当前与系统相关的以及与当前Audio Session相关Category和mode的音频输入设备

 

@property(readonly) AVAudioSessionPortDescription *preferredInput

音频线路首要的输入端口

 

- (BOOL)setPreferredInput:(AVAudioSessionPortDescription *)inPort

                    error:(NSError **)outError

设置首要输入端口

inPort参数必须是availableInputs中的一个AVAudioSessionPortDescription对象

只能在设置了Audio Session的category和mode并且激活了session后才能进行调用设置

 

@property(readonly) NSArray *inputDataSources

Audio session的输入端口的可接受的数据源数组

 

@property(readonly) AVAudioSessionDataSourceDescription *inputDataSource

当前选择的输入数据源

 

- (BOOL)setInputDataSource:(AVAudioSessionDataSourceDescription *)dataSource

                     error:(NSError **)outError

设置选择的数据源

 

输出的话将input换成output即可

 

Notification

 

AVAudioSessionInterruptionNotification

当音频中断出现时传递给主线程

notification的userInfo字典包含了AVAudioSessionInterruption TypeKey,如果中断的类型是AVAudioSessionInterruptionTypeBegan,则app的audio session会被中断。如果是AVAudioSessionInterruptionTypeEnded,则字典会同时包含AVAudioSessionInterruptionOptionKey

 

AVAudioSessionRouteChangeNotification

当系统的音频线路发生改变时通知主线程

notification的userInfo字典会包含AVAudioSessionRouteChangeReasonKey和AVAudioSessionRouteChangePreviousRouteKey

 

AVAudioSessionMediaServicesWereLostNotification

当媒体服务终止时通知主线程

将该通知作为提示来在重新启动服务之前做相关处理

此notification不包含userInfo

 

AVAudioSessionMediaServicesWereResetNotification

媒体服务重新启动时通知主线程

不包含userInfo

 

AVAudioSessionSilenceSecondaryAudioHintNotification

当其他app的首要音频开始播放或者停止时通知主线程

userInfo中AVAudioSessionSilenceSecondaryAudioHintTypeKey的值为AVAudioSessionSilenceSecondaryAudioHintType类型

--------------------- 

 

原文:https://blog.csdn.net/kingshuo7/article/details/42588191 

 

iOS 音频-AVAudioSession

 

2018.02.03 11:22* 字数 2157 阅读 6624评论 9喜欢 31

1. AVAudioSession 概述

最近一年一直在做IPC Camera的iOS客户端开发。和音频打交道,必须要弄清楚

AVAudioSession。

先看下苹果的官方图:

 

 

pastedGraphic_1.png

Audio Session

可以看到AVAudioSession就是用来管理多个APP对音频硬件设备(麦克风,扬声器)的资源使用。

举例一下AVAudioSession可以做这些事情

  • 设置自己的APP是否和其他APP音频同时存在,还是中断其他APP声音
  • 在手机调到静音模式下,自己的APP音频是否可以播放出声音
  • 电话或者其他APP中断自己APP的音频的事件处理
  • 指定音频输入和输出的设备(比如是听筒输出声音,还是扬声器输出声音)
  • 是否支持录音,录音同时是否支持音频播放

2. AVAudioSession Category

AVAudioSession的接口比较简单。APP启动的时候会自动帮激活AVAudioSession,当然我们可以手动激活代码如下。

    //导入头文件

    #import <AVFoundation/AVFoundation.h>

 

    //AVAudioSession是一个单例类

    AVAudioSession *session = [AVAudioSession sharedInstance];

    //AVAudioSessionCategorySoloAmbient是系统默认的category

    [session setCategory:AVAudioSessionCategorySoloAmbient error:nil];

    //激活AVAudioSession

    [session setActive:YES error:nil];

可以看到设置session这里有两个参数,category和options

Category iOS下目前有七种,每种Category都对应是否支持下面四种能力

  • Interrupts non-mixable apps audio:是否打断不支持混音播放的APP
  • Silenced by the Silent switch:是否会响应手机静音键开关
  • Supports audio input:是否支持音频录制
  • Supports audio output:是否支持音频播放

下面用图表来直观的看下每种category具体的能力集

Category

是否允许音频播放/录音

是否打断其他不支持混音APP

是否会被静音键或锁屏键静音

AVAudioSessionCategoryAmbient

只支持播放

AVAudioSessionCategoryAudioProcessing

不支持播放,不支持录制

AVAudioSessionCategoryMultiRoute

支持播放,支持录制

AVAudioSessionCategoryPlayAndRecord

支持播放,支持录制

默认YES,可以重写为NO

AVAudioSessionCategoryPlayback

只支持播放

默认YES,可以重写为NO

AVAudioSessionCategoryRecord

只支持录制

否(锁屏下仍可录制)

AVAudioSessionCategorySoloAmbient

只支持播放

  • AVAudioSessionCategoryAmbient,只支持音频播放。这个 Category,音频会被静音键和锁屏键静音。并且不会打断其他应用的音频播放。
  • AVAudioSessionCategorySoloAmbient,这个是系统默认使用的 Category,只支持音频播放。音频会被静音键和锁屏键静音。和AVAudioSessionCategoryAmbient不同的是,这个会打断其他应用的音频播放
  • AVAudioSessionCategoryPlayback,只支持音频播放。你的音频不会被静音键和锁屏键静音。适用于音频是主要功能的APP,像网易云这些音乐app,锁屏后依然可以播放。

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

  • AVAudioSessionCategoryRecord,只支持音频录制。不支持播放。
  • AVAudioSessionCategoryPlayAndRecord,支持音频播放和录制。音频的输入和输出不需要同步进行,也可以同步进行。需要音频通话类应用,可以使用这个 Category。
  • AVAudioSessionCategoryAudioProcessing,只支持本地音频编解码处理。不支持播放和录制。
  • AVAudioSessionCategoryMultiRoute,支持音频播放和录制。允许多条音频流的同步输入和输出。(比如USB连接外部扬声器输出音频,蓝牙耳机同时播放另一路音频这种特殊需求)

我们也可以通过AVAudioSession的属性来读取当前设备支持的Category

@property(readonly) NSArray<NSString *> *availableCategories;

这样可以保证设备兼容性。

设置Category的代码示例如下

NSError *setCategoryError = nil;

BOOL isSuccess = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&setCategoryError];

if (!success) { 

    //这里可以读取setCategoryError.localizedDescription查看错误原因

}

3. AVAudioSession Mode&&Options

刚刚介绍的Category定义了七种主场景,实际开发需求中有时候需要对Category进行微调整,我们发现这个接口还有两个参数Mode和Options。

/* set session category and mode with options */

- (BOOL)setCategory:(NSString *)category mode:(NSString *)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0));

AVAudioSession Mode

我们通过读取下面这条属性获取当前设备支持的Mode

@property(readonly) NSArray<NSString *> *availableModes;

iOS下有七种mode来定制我们的Category行为

模式

兼容的 Category

场景

AVAudioSessionModeDefault

All

默认模式

AVAudioSessionModeVoiceChat

AVAudioSessionCategoryPlayAndRecord

VoIP

AVAudioSessionModeGameChat

AVAudioSessionCategoryPlayAndRecord

游戏录制,GKVoiceChat自动设置

AVAudioSessionModeVideoRecording

AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord

录制视频

AVAudioSessionModeMoviePlayback

AVAudioSessionCategoryPlayback

视频播放

AVAudioSessionModeMeasurement

AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback

最小系统

AVAudioSessionModeVideoChat

AVAudioSessionCategoryPlayAndRecord

视频通话

下面逐一介绍下每个Mode

  • AVAudioSessionModeDefault,默认模式,与所有的 Category 兼容
  • AVAudioSessionModeVoiceChat,适用于VoIP 类型的应用。只能是 AVAudioSessionCategoryPlayAndRecord Category下。在这个模式系统会自动配置AVAudioSessionCategoryOptionAllowBluetooth 这个选项。系统会自动选择最佳的内置麦克风组合支持语音聊天。
  • AVAudioSessionModeVideoChat,用于视频聊天类型应用,只能是 AVAudioSessionCategoryPlayAndRecord Category下。适在这个模式系统会自动配置 AVAudioSessionCategoryOptionAllowBluetooth 和 AVAudioSessionCategoryOptionDefaultToSpeaker 选项。系统会自动选择最佳的内置麦克风组合支持视频聊天。
  • AVAudioSessionModeGameChat,适用于游戏类应用。使用 GKVoiceChat 对象的应用会自动设置这个模式和 AVAudioSessionCategoryPlayAndRecord Category。实际参数和AVAudioSessionModeVideoChat一致
  • AVAudioSessionModeVideoRecording,适用于使用摄像头采集视频的应用。只能是 AVAudioSessionCategoryPlayAndRecord 和 AVAudioSessionCategoryRecord 这两个 Category下。这个模式搭配 AVCaptureSession API 结合来用可以更好地控制音视频的输入输出路径。(例如,设置 automaticallyConfiguresApplicationAudioSession 属性,系统会自动选择最佳输出路径。
  • AVAudioSessionModeMeasurement,最小化系统。只用于 AVAudioSessionCategoryPlayAndRecord、AVAudioSessionCategoryRecord、AVAudioSessionCategoryPlayback 这几种 Category。
  • AVAudioSessionModeMoviePlayback,适用于播放视频的应用。只用于 AVAudioSessionCategoryPlayback 这个Category。

AVAudioSession Options

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

Option

Option功能说明

兼容的 Category

AVAudioSessionCategoryOptionMixWithOthers

支持和其他APP音频 mix

AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute

AVAudioSessionCategoryOptionDuckOthers

系统智能调低其他APP音频音量

AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute

AVAudioSessionCategoryOptionAllowBluetooth

支持蓝牙音频输入

AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayAndRecord

AVAudioSessionCategoryOptionDefaultToSpeaker

设置默认输出音频到扬声器

AVAudioSessionCategoryPlayAndRecord

调优我们的Category

通过Category和合适的Mode和Options的搭配我们可以调优出我们的效果,下面举两个应用场景:

用过高德地图的都知道,在后台播放QQ音乐的时候,如果导航语音出来,QQ音乐不会停止,而是被智能压低和混音,等导航语音播报完后,QQ音乐正常播放,这里我们需要后台播放音乐,所以Category使用AVAudioSessionCategoryPlayback,需要混音和智能压低其他APP音量,所以Options选用 AVAudioSessionCategoryOptionMixWithOthers和AVAudioSessionCategoryOptionDuckOthers

代码示例如下

 BOOL isSuccess = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDuckOthers error:&setCategoryError];

又或者我希望AVAudioSessionCategoryPlayAndRecord这个Category默认的音频由扬声器播放,那么可以调用这个接口去调整Category

- (BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError

通过选择合适和Category,mode和options,就可以调优音频的输入输出,来满足日常开发需求(需要注意的是Category,mode,option是搭配使用的,而不是简单组合,也就是说某种Category支持某些mode和option,从上面的表中也可以看出这一点)

4. 音频中断处理

其他APP或者电话会中断我们的APP音频,所以相应的我们要做出处理。

我们可以通过监听AVAudioSessionInterruptionNotification这个key获取音频中断事件

回调回来Userinfo有键值

  • AVAudioSessionInterruptionTypeKey:
  • 取值AVAudioSessionInterruptionTypeBegan表示中断开始
  • 取值AVAudioSessionInterruptionTypeEnded表示中断结束

中断开始:我们需要做的是保存好播放状态,上下文,更新用户界面等

中断结束:我们要做的是恢复好状态和上下文,更新用户界面,根据需求准备好之后选择是否激活我们session。

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

  • System Sound Services:使用 System Sound Services 播发音频,系统会自动处理,不受APP控制,当中断发生时,音频播放会静音,当中断结束后,音频播放会恢复。
  • AV Foundation framework:AVAudioPlayer 类和 AVAudioRecorder 类提供了中断开始和结束的 Delegate 回调方法来处理中断。中断发生,系统会自动停止播放,需要做的是记录播放时间等状态,更新用户界面,等中断结束后,再次调用播放方法,系统会自动激活session。
  • Audio Queue Services, I/O audio unit:使用aduio unit这些技术需要处理中断,需要做的是记录播放或者录制的位置,中断结束后自己恢复audio session。
  • OpenAL:使用 OpenAL 播放时,同样需要自己监听中断。管理 OpenAL上下文,用户中断结束后恢复audio session。

需要注意的是:1. 有中断开始事件,不一定对应有中断结束事件,所以需要在用户进入前台,点击UI操作的时候,需要保存好播放状态和对Audio Session管理,以便不影响APP的音频功能。2.音频资源竞争上,一定是电话优先。3. AVAudioSession同样可以监听外设音频状态,比如耳机拔入拔出。这里不做累述

5. AVAudioSession总结

AVAudioSession的作用就是管理音频这一唯一硬件资源的分配,通过调优合适的AVAudioSession来适配我们的APP对于音频的功能需求。切换音频场景时候,需要相应的切换AVAudioSession。

参考文献:Audio Session Programming Guide

 

ios播放声音中断后台音乐的问题

 

 今天遇到一个ios播放声音中断后台音乐的问题,在我的app中如果调用AVAudioSession 播放完声音,后台的qq音乐偶尔不能恢复,而网易云音乐一次都不能恢复播放,研究了一下AVAudioSession ,我之前调用[audioSession setActive:NO error:&err];还有一个方法

/* Set the session active or inactive. Note that activating an audio session is a synchronous (blocking) operation.

 Therefore, we recommend that applications not activate their session from a thread where a long blocking operation will be problematic.

 Note that this method will throw an exception in apps linked on or after iOS 8 if the session is set inactive while it has running or 

 paused I/O (e.g. audio queues, players, recorders, converters, remote I/Os, etc.).

*/

- (BOOL)setActive:(BOOL)active error:(NSError **)outError;

- (BOOL)setActive:(BOOL)active withOptions:(AVAudioSessionSetActiveOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0);

而AVAudioSessionSetActiveOptions这个枚举

/*  options for use when calling setActive:withOptions:error: 

AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation -- 

Notify an interrupted app that the interruption has ended and it may resume playback. Only valid on 

session deactivation. */

typedef NS_OPTIONS(NSUInteger, AVAudioSessionSetActiveOptions)

{

AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation = 1

} NS_AVAILABLE_IOS(6_0);

注释的意思是:“通知中断程序中断已经结束,可以恢复播放。。。。“,

看来这里应该是造成播放声音导致后台的qq音乐偶尔不能恢复,而网易云音乐一次都不能恢复播放的地方

 

 之后改成调用   [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];果然网易云音乐也可以恢复了,看了苹果官方的文章,上面有张图

AVAudioPlayer在AVFoundation框架下,所以我们要导入AVFoundation.framework。

AVAudioPlayer类封装了播放单个声音的能力。播放器可以用NSURL或者NSData来初始化,要注意的是NSURL并不可以是网络url而必须是本地文件URL,

因为 AVAudioPlayer不具备播放网络音频的能力,如果要播放网络URL,需要先转化为NSData.但是此法并不可取,

因为AVAudioPlayer只能播放一个完整的文件,并不支持流式播放,所以必须是缓冲完才能播放,所以如果网络文件过大抑或是网速不够岂不是要等很久?

所以播放网络音频我们一般用音频队列。

注意:需要添加AVFoundation.framework

    AVAudioPlayer不支持边下边播,所以只能下载到本地再播放

 

    1.实例化方法

    - (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError;

    

    2.预加载资源

    - (BOOL)prepareToPlay;

 

    3.遵守协议

    AVAudioPlayerDelegate

 

    4.播放完成之后回调以下方法

    - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;

先搭建UI吧,直接上图:

pastedGraphic_2.png

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

#import "AVAudioPlayerViewController.h"

#import <AVFoundation/AVFoundation.h>

 

@interface AVAudioPlayerViewController ()

{

    //声明一个播放器

    AVAudioPlayer *_musicPlay;//音乐播放器

}

 

- (IBAction)playAction:(id)sender;//播放

- (IBAction)pauseAction:(id)sender;//暂停

- (IBAction)voiceAction:(UISlider *)sender;//音量

- (IBAction)progressAction:(UISlider *)sender;//进度

 

@property (weak, nonatomic) IBOutlet UISlider *progressSlider;//进度条

@property (nonatomic,strong)NSTimer *myTimer;//声明一个定时器类型的成员变量

 

 

@end

 

@implementation AVAudioPlayerViewController

 

- (void)viewDidLoad {

    [super viewDidLoad];

 

    //获取资源路径  需要事先导入本地音频内容

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"mp3"];

    NSLog(@"%@",filePath);

     

    NSURL *URL =[NSURL fileURLWithPath:filePath];

    _musicPlay = [[AVAudioPlayer alloc] initWithContentsOfURL:URL error:nil];

    //实例化音乐播放器

//    _musicPlay = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:nil];

     

    //设置音量大小处于中等强度

    _musicPlay.volume = 0.5;<br> //循环次数,默认是1<br>    _musicPlay.numberOfLoops = 1;<br>   //    声道数<br>    NSUInteger channels = _musicPlay.numberOfChannels;//只读属性<br><br><br>

     

    //预加载资源

    if ([_musicPlay prepareToPlay]) {

         

        NSLog(@"准备完毕");

         

    }

     

    //设置总进度大小

    self.progressSlider.maximumValue = _musicPlay.duration;

     

}

#pragma mark- 各类触发事件

-(void)change:(NSTimer *)sender{

     

    //每一秒钟设置一下进度值  播放位置

    self.progressSlider.value = _musicPlay.currentTime;

     

}

 

#pragma mark- AVAudioPlayer相关的方法

- (IBAction)playAction:(id)sender {

    //播放

    [_musicPlay play];

     

    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(change:) userInfo:nil repeats:YES];  

}

 

- (IBAction)pauseAction:(id)sender {

    //暂停

    [_musicPlay pause];

    //停止定时器

    [self.myTimer invalidate];

    NSLog(@"%f",_musicPlay.currentTime);

}

 

- (IBAction)voiceAction:(UISlider *)sender {

     

    //改变音量大小

    _musicPlay.volume = sender.value;

     

}

 

- (IBAction)progressAction:(UISlider *)sender {

     

    //设置进度

    _musicPlay.currentTime = sender.value;

     

}

代理方法

          加入播放出现异常,或者被更高级别的系统任务打断,我们的程序还没来得及收场就挂了,怎么办?不急,我们可以通过几个委托方法很好地处理所有的情形。

         首先给player设置委托是必须的:

  1. player.delegate = self; 
  2. - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag{  
  3.     //播放结束时执行的动作  
  4. }  
  5. - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer*)player error:(NSError *)error{  
  6.     //解码错误执行的动作  
  7. }  
  8. - (void)audioPlayerBeginInteruption:(AVAudioPlayer*)player{  
  9.     //处理中断的代码  
  10. }  
  11. - (void)audioPlayerEndInteruption:(AVAudioPlayer*)player{  
  12.     //处理中断结束的代码  

iOSAVAudioPlayer

pastedGraphic_3.png 请输入账号名 关注

2016.06.04 01:38* 字数 1316 阅读 3596评论 1喜欢 6

苹果系统带有一个音频播放器,这就是AVAudioPlayer,要使用这个播放器进行播放音频,首先需要创建这个播放器对象,而这个播放器的创建所需的头文件并不是在foundation的头文件下而是在播放器自己专属的头文件下,如下:

#import <AVFoundation/AVFoundation.h>

而AV开头的文件里包含了许多的和音视频相关的头文件。

一、创建播放器对象

我们先声明一个播放器的全局变量,而且只能声明称全局变量。

AVAudioPlayer *_audioPlayer;

此时我们创建播放器对象:

_audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];

这里有两个参数:

参数1:需要播放的音频的地址,这里可以是本地或者是网页的文件

参数2:错误信息

这里就暂且使用本地资源,需要创建本地资源的路径,然后转为url路径

NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:nil];

这里有两个参数:

参数1:文件的名字

参数2:文件的类型

注意的是:在参数1中如果包含了名字和类型,在参数2中可以不用谢类型,直接写nil就可以了。

然后将string类型的路径用url来接收

NSURL *url = [NSURL fileURLWithPath:path];

这里的参数就是之前创建的NSString类型的路径。

完整的代码如下:

NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:nil];

NSURL *url = [NSURL fileURLWithPath:path];

_audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];

 

二、播放

音频播放之前需要准备播放,这里需要调用一个方法;

[_audioPlayer prepareToPlay];

就绪后直接播放

[_audioPlayer play];

以上就是和音频最主要的功能的介绍。

三、音频播放器的相关功能

下面是对音频播放器的扩展,音频播放器一般会有的功能如:开始播放、暂停播放、调整音频的音量的大小、调整音频的进度、显示音频文件的内部附有的图片信息及协议代理等。

1.开始和暂停播放音频

首先,为了方便,我们在storyBoard去拖一个按钮控件,主要功能是让音频暂停和播放。

这里我们可以用到自动布局的相关知识,在storyBoard里运用约束条件,使得可以进行适配手机。这里对按钮的左右下和高度进行约束。

让按钮的初始文字显示为播放,点击播放后,显示为STOP,暂停后显示为play;随便设个背景颜色用于区分

然后直接在storyBoard里面拖个方法到ViewController.m文件的@implementation里,然后就在这里这个按钮的点击事件里执行音频的播放和暂停

代码如下:

#pragma mark - 开始暂停按钮

- (IBAction)button:(UIButton *)sender {

    // 用一个静态变量对按钮的状态进行控制

    static int i = 0;

    if (i == 0) {

        // 开始播放

        [_audioPlayer play];

        // 开始后按钮文字变为stop,按钮的状态为正常

        [sender setTitle:@"stop" forState:UIControlStateNormal];

        i = 1;

    }

    else {

        // 4.暂停播放

        [_audioPlayer pause];

        //[_audioPlayer stop];

        [sender setTitle:@"play" forState:UIControlStateNormal];

        i = 0;

    }

}

2.调整音量大小

我们还是可以在storyBoard中拖一个控件出来,这里需要一个步进器,然后把相关的方法拖到文件中去实现。

#pragma mark - 改变音量

- (IBAction)volumeChange:(UIStepper *)sender {

    // 设置音量(0 ~ 1);我们设置的步进的音量值为0.1

    _audioPlayer.volume = sender.value;

}

3.调整音频播放进度

3.1更新进度

调整进度就需要用到滑块控件,然后向之前的在storyBoard里去拖个滑块的控件,还有相关的属性和方法。

这里我们可能会运用到定时器,所以我们需要创建定时器,而这个定时器的创建应该放到准备播放音频的时候就创建出来。

// 添加定时器

[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateProgress) userInfo:nil repeats:YES];

这里的参数为:

参数1:定时器的监测时间,单位是S

参数2:执行的对象

参数3:选择器(需要执行的方法)

参数4:用户的信息,一般为nil

参数5:是否需要重复执行,这里是需要的,为YES

实现选择器中的方法;

#pragma mark - 更新进度updateProgress

- (void)updateProgress {

    // 当前时间

    double currentTime = _audioPlayer.currentTime;

    // 总时间

    double totalTime = _audioPlayer.duration;

    // 计算进度

    float progress = currentTime / totalTime;

    // 展示到进度条上

    self.sliderProgress.value = progress;

}

3.2更改滑块的进度

这里就需要实现滑块拖出来的方法了

#pragma mark - 改变进度条

- (IBAction)sliderProgress:(UISlider *)sender {

    // 当前时间 = 总时间 * 爆发进度

    double currentTime = _audioPlayer.duration * _sliderProgress.value;

    // 是否让进度条实时更新。

    _sliderProgress.continuous = YES;

    // 重新设置播放器的当前时间

    _audioPlayer.currentTime = currentTime;

}

4.获取专辑的图片

同样的需要在storyBoard上拖一个imageView的控件,展示出其属性。

#pragma mark - 获取专辑图片

- (void)showPlayerImage {

    AVURLAsset *asset = [[AVURLAsset alloc]initWithURL:_audioPlayer.url options:nil];

    NSString *format = [asset availableMetadataFormats].firstObject;

    NSArray *array = [asset metadataForFormat:format];

    for (AVMutableMetadataItem *item in array) {

        // 这里的artwork是需要在音频文件里去得到的。可以吧array打印出来,得到里面的值

        if ([item.commonKey isEqualToString:@"artwork"]) {

            NSData *data = (NSData *)item.value;

            UIImage *image = [UIImage imageWithData:data];

            _imageView.image = image;

        }

    }

}

5.协议代理

5.1当前音频播放器播放的音频结束后会自动调用这个方法

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {

    NSLog(@"播放结束");

    // 切换下一首

    [self preparePlayerWithName:@"春天里.mp3"];

    [_audioPlayer play];

}

5.2音频播放被中断的时候会调用这个方法

一般有电话的接入和有短信接收的时候,一般会有中断,这需要调用这个方法。我们一般执行的都是暂停音频的播放。

- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player {

    NSLog(@"一般这个方法中会暂停音频");

    [player stop];

}

5.3中断结束后

一般就在来电结束后会调用这个方法。一般在这个方法中执行的是会继续音频的播放。

// 中断结束的时候会调用这个方法

- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags {

    // 一般在这个方法中继续播放音频

    [player play];

    NSLog(@"继续播放");

}

5.4解码出错

// 解码错误的时候会调用这个方法

- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {

    NSLog(@"文件出错");

}

 

总结:

1.这里需要导入的头文件一定是:AVFoundation/AVFoundation.h;

2.音频播放器AVAudioPlayer作为一个对象,要使用必须要先创建对象,创建对象前需要给一个音频文件的路径(一般都是url);

3.声明对象一定是要全局变量,不然运行可能失败,因为会调用到偏硬件的;

4.就是在storyBoard里拖控件的时候需要注意自动布局的约束条件,为的是适配;

5.在有代理的设置的时候,一定要遵循代理:AVAudioPlayerDelegate

posted @ 2019-02-16 10:46  sundaysios  阅读(4223)  评论(0编辑  收藏  举报