美文网首页
二、播放和录制音频

二、播放和录制音频

作者: smallLabel | 来源:发表于2018-10-30 18:26 被阅读16次

理解音频会话

    音频会话在应用程序和操作系统之间扮演着中间人的角色。意思就是开发者可以利用音频会话与iOS音频环境交互,而不需要关心底层如何实现。

    所有的iOS程序都具有音频会话,无论是否使用。默认音频会话来自以下一些配置:

  • 激活了音频播放,但是音频录制未激活;
  • 用户切换响铃/静音开关到静音模式时,所有音频消失;
  • 设备显示解锁屏幕时,所有音频消失;
  • 应用程序播放音频时,所有后台播放的音频处于静音状态;

可以通过音频会话分类定制这些需求。

AudioSessionCategory.png

配置音频会话

  在应用程序声明周期中一般只在程序启动时配置一遍,即在application:didFinishLaunchingWithOptions:方法中。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError *error = nil;
    //设置后台播放
    if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {
        NSLog(@"%@", error.localizedDescription);
    }
    //
    if (![session setActive:YES error:&error]) {
        NSLog(@"%@", error.localizedDescription);
    }
    return YES;
}

使用AVAudioplayer播放音频

播放音频功能主要包括播放过程控制及播放时参数获取。如播放、暂停、停止、修改音量、修改播放速率、循环播放、立体声播放以及音频计量。

创建AVAudioPlayer
  获取待播放文件url或data
  可通过url或data创建播放器实例对象AVAudioPlayer

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

准备播放工作

/**
   准备播放 建议显式调用  
   该方法会取得需要的音频硬件并预加载Audio Queue缓冲区,
   降低调用play方法和听到声音输出之间的延时。  
   当直接调用play方法时也会隐式调用prepareToPlay方法
 */
[_audioPlayer prepareToPlay];

播放音频

  //允许控制播放速率 注意调用此方法要在prepareToPlay之前
    //官方说明:set this property to YES after you initialize the player and before you call the prepareToPlay instance method for the player.
    _audioPlayer.enableRate = YES;
    //准备播放 建议显式调用
    [_audioPlayer prepareToPlay];
    //播放
    [_audioPlayer play];
    //循环次数  -1无限循环
    _audioPlayer.numberOfLoops = -1;

控制播放参数

//设置播放速率 0.5-2.0之间倍速播放
_audioPlayer.rate = slider.value;

//立体声播放 -1.0-1.0之间
_audioPlayer.pan = slider.value;

//控制播放音量 在0.0-1.0之间
_audioPlayer.volume = slider.value;

通过音频会话分类处理中断事件

上面我们在AppDelegate中将session的分类设置为后台播放,仅仅设置这个还是不能满足后台播放条件的。还需要在Info.plist文件中添加一个新的Required background modes类型的数组,在其中添加App plays audio or streams audio/video using AirPlay。如图:

BackgroundPlist.png

处理中断事件
    在播放音频时,呼入电话或者FaceTime等都会打断正在播放的音频,因此需要监听中断通知来处理中断事件。
    当中断发生时,系统会自动处理中断信息,停止音频播放,并不需要我们做任何事情。但是当中断结束后,你会发现不管是界面上的按钮状态还是播放状态,都没有恢复,因此我们需要手动处理这些中断。

NSNotificationCenter *nsnc = [NSNotificationCenter defaultCenter];
    [nsnc addObserver:self
             selector:@selector(handleInterrupt:)
                 name:AVAudioSessionInterruptionNotification
               object:[AVAudioSession sharedInstance]];

        通知返回的userInfo 中会包含必要的中断类型信息,通过检索AVAudioSessionInterruptionTypeKey可以获取AVAudioSessionInterruptionType,这是一个枚举类型。

- (void)handleInterrupt:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    AVAudioSessionInterruptionType type = [userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
    if (type == AVAudioSessionInterruptionTypeBegan) {
        //中断开始 暂停播放,设置暂停播放UI
    } else if (type == AVAudioSessionInterruptionTypeEnded) {
        //中断恢复 恢复播放,恢复UI
    }
}

响应线路改变
        在iOS设备上添加或移除音频输入输出线路时,会发生线路变化,如用户插拔耳机。当这些事件发生时,AVAudioSession会广播一个描述该变化的通知给监听者。
注册通知

[nsnc addObserver:self
            selector:@selector(handleRouteChanged:)
                 name:AVAudioSessionRouteChangeNotification
               object:[AVAudioSession sharedInstance]];

接收到通州后首先判断线路变更发生的原因,查看保存在userInfo中的表示原因的AVAudioSessionRouteChangeReasonKey

- (void)handleRouteChanged:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    //线路发生变更原因
    AVAudioSessionRouteChangeReason reason = [userInfo[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
    
}

需要特别注意的是耳机断开事件,该事件对应的原因是AVAudioSessionRouteChangeReasonOldDeviceUnavailable
知道有设备断开连接后,我们可以从userInfo中获取上次的连接方式,做出响应的操作。如耳机断开后,需要停止播放音频。线路的描述信息整合在一个输入NSArray 和一个输出NSArray中。数组中的元素都是AVAudioSessionPortDescription。从上次线路描述中找到第一个输出接口并判断其是否为耳机,并调用对应方法。

if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
        //获取上一次路线状态
        AVAudioSessionRouteDescription *previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey];
        //获取第一个输出接口
        AVAudioSessionPortDescription *portDescription = previousRoute.outputs[0];
        if ([portDescription.portType isEqualToString:AVAudioSessionPortHeadphones]) {
            //判断上一次是耳机状态
        }
    }

使用AVAudioRecorder录制音频

        创建AVAudioRecorder实例时需要为其提供数据的一些信息,分别是:

  • 用于表示音频流写入文件的本地文件URL;
  • 包含用于配置录音会话键值信息的NSDictionary对象;
  • 用于捕捉初始化阶段各种错误的NSError指针。
    NSString *directory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO)[0];
    directory = [directory stringByAppendingPathComponent:@"voice.m4a"];
    NSURL *url = [NSURL URLWithString:directory];
    NSDictionary *setting = @{AVFormatIDKey: @(kAudioFormatMPEG4AAC),
                              AVSampleRateKey: @22050.0f,
                              AVNumberOfChannelsKey: @1
                              };
    NSError *error = nil;
    _audioRecorder = [[AVAudioRecorder alloc] initWithURL:url settings:setting error:&error];
    if (_audioRecorder) {
        [_audioRecorder prepareToRecord];
    } else {
        //创建失败
    }

音频格式
AVFormatIDKey:定义了写入内容的音频格式;如kAudioFormatLinearPCM会将未压缩的音频流写入到文件中,这种格式的保真度最高,不过文件最大。特别注意的是指定的音频格式一定要个URL参数定义的文件类型兼容。如录制名为test.wav的文件,那么AVFromatIDKey必须为kAudioFormatLinearPCM,否则报错:

The operation couldn’t be completed. (OSStatus error 1718449215.)

1718449215错误状态是4字节编码的整数值,'fmt?'已至定义了一种不兼容的音频格式。

采样率
AVSampleRateKey:采样率,对于使用什么采样率没有明确定义,不过尽量使用标准的采样率,如8000、16000、22050等。

通道数
AVNumberOfChannelsKey:定义记录音频内容的通道数,1意味着单声道录制,2意味着立体声录制,除非使用外部硬件,否则通常创建单声道。

配置录制音频的音频会话

默认音频会话分类为AVAudioSessionCategorySoloAmbient,该分类不支持音频输入,所以最合适的分类是AVAudioSessionCategoryPlayAndRecord,具体视应用程序功能而定。
此处应注意:在使用麦克风之前,操作系统要求应用程序必须得到用户的明确许可,需要在Info.plist文件中配置响应权限。

相关文章

网友评论

      本文标题:二、播放和录制音频

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