《AV Foundation 开发秘籍》读书笔记(二)

来源:互联网 发布:网络上jr是什么意思 编辑:程序博客网 时间:2024/05/21 22:25

第二章 音频播放和录制

1. 音频会话

音频会话分类

Category 播放类型 后台播放 静音或屏幕关闭 音频输入 音频输出 作用 AVAudioSessionCategoryAmbient 混合播放 有影响 支持 游戏背景音乐 AVAudioSessionCategorySoloAmbient(默认) 独占播放 有影响 支持 微信中播放语音 AVAudioSessionCategoryPlayback 可选 支持 支持 音频播放器 AVAudioSessionCategoryRecord 独占录音 支持 支持 微信中录制语音 AVAudioSessionCategoryPlayAndRecord 可选 支持 支持 支持 微信语音聊天 AVAudioSessionCategoryAudioProcessing —— —— —— 硬件解码音频 AVAudioSessionCategoryMultiRoute 支持 支持 多设备输入输出

上述分类所提供的几种常见行为可以满足大部分应用程序的需要,如果需要更复杂的功能,上述其中一种分类可以通过使用 options 和 modes 方法进一步自定义开发。

激活音频会话

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    AVAudioSession *session = [AVAudioSession sharedInstance];    NSError *error;    if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {        NSLog(@"Category Error : %@", error.localizedDescription);    }    if (![session setActive:YES error:&error]) {        NSLog(@"Activation Error : %@", error.localizedDescription);    }    return YES;}

如果分类允许后台播放,则应该打开 Capabilities 中的 Background Modes 继而勾选后台播放音频选项

2. 音频播放

除非需要从网络流中播放音频、需要访问原始音频样本,或者需要非常低的时延,否则 AVAudioPlayer 都能胜任

- (void)viewDidLoad {    [super viewDidLoad];    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"白洁01" withExtension:@"mp3"];    // Must Maintain a strong reference to player    self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];    if (self.player) {        self.player.numberOfLoops = -1;  // 循环播放        [self.player prepareToPlay];    }}- (IBAction)play {    [self.player play];}

prepareToPlay 方法是可选的,在调用 play 方法前也会自动调用,作用是取得需要的音频硬件并预加载 Audio Queue 的缓冲区,降低调用 play 方法后的延时。

- (IBAction)pause {    [self.player pause];}- (IBAction)stop {    [self.player stop];    self.player.currentTime = 0.0f;}

通过 pause 和 stop 方法停止的音频都会继续播放。最主要的区别在底层处理上,调用 stop 方法会撤销调用 prepareToPlay 时所做的设置,而调用 pause 方法则不会。

// 音量 0 ~ 1- (IBAction)voice:(UISlider *)sender {    self.player.volume = sender.value;}// 声道 -1 ~ 1- (IBAction)pan:(UISlider *)sender {    self.player.pan = sender.value;}// 速率 0.5 ~ 2- (IBAction)speed:(UISlider *)sender {    self.player.rate = sender.value;}

如果要改变速率,在初始化 AVAudioPlayer 时应做出如下设置

self.player.enableRate = YES;

3. 处理中断事件

当有电话呼入、闹钟响起的时候,播放中的音频会慢慢消失和暂停,但是终止通话后,播放、停止按钮的控件和音频的播放没有恢复。为了优化用户体验,需要监听这些事件,并作出处理:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
- (void)handleInterruption:(NSNotification *)notification{    NSDictionary *info = notification.userInfo;    AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];    if (type == AVAudioSessionInterruptionTypeBegan) {        // 中断开始,设置停止音乐        [self.player pause];    } else {        // 中断结束,判断是否允许继续播放        AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];        if (options == AVAudioSessionInterruptionOptionShouldResume) {            // 允许继续播放,则继续播放            [self.player play];        }    }}

4. 对线路改变的响应

播放音频期间插入耳机,音频输出线路变成耳机插孔并继续播放。断开耳机连接,音频线路再次回到设备的内置扬声器播放。虽然线路变化和预期一样,不过按照苹果官方文档,认为该音频应该处于静音状态。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];
- (void)handleRouteChange:(NSNotification *)notification{    NSDictionary *info = notification.userInfo;    AVAudioSessionRouteChangeReason reason = [info[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];    if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {        AVAudioSessionRouteDescription *previousRoute  = info[AVAudioSessionRouteChangePreviousRouteKey];        AVAudioSessionPortDescription  *previousOutput = previousRoute.outputs[0];        if ([previousOutput.portType isEqualToString:AVAudioSessionPortHeadphones]) {            // 停止播放音乐            [self.player stop];        }    }}

5. 音频录制

一般情况存在录音功能,必然会有播放功能,所以不能使用默认的录制音频会话,应该使用既可以录制又能播放的 AVAudioSessionCategoryPlayAndRecord

- (void)viewDidLoad {    [super viewDidLoad];    NSURL *url = [NSURL fileURLWithPath:[@"Users/mayan/Desktop" stringByAppendingPathComponent:@"voice.caf"]];    NSDictionary *settings = @{                               AVFormatIDKey            : @(kAudioFormatAppleIMA4),                               AVSampleRateKey          : @22050.0f,                               AVNumberOfChannelsKey    : @1,                               };    self.recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:nil];    if (self.recorder) {        self.recorder.delegate = self;        [self.recorder prepareToRecord];    }}- (IBAction)record {    [self.recorder record];}- (IBAction)recordFinish {    [self.recorder stop];}// 音频录制完成调用- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{    if (flag) {        // 一般把录制好的音频复制或者剪切到目的文件夹下        NSURL *srcURL = self.recorder.url;        NSURL *destURL = [NSURL URLWithString:@"目的文件路径/音频文件名称.caf"];        [[NSFileManager defaultManager] copyItemAtURL:srcURL toURL:destURL error:nil];    }}

在录制音频过程中,Core Audio Format(CAF)通常是最好的容器格式,因为它和内容无关可以保存 Core Audio 支持的任何音频格式。在设置字典中指定的键值信息也值得讨论一番:

音频格式

AVFormatIDKey 定义了写入内容的音频格式,下面是常用格式:

  • kAudioFormatLinearPCM:将未压缩的音频流写入到文件中。保真度最高,文件也最大;
  • kAudioFormatMPEG4AAC(AAC) 或 kAudioFormat-AppleIMA4(Apple IMA4):文件显著缩小,还能保证高质量音频

采样率

AVSampleRateKey 定义了录音器的采样率,采样率定义了对输入的模拟音频信号每一秒的采样数。采样率越高,越能得到高质量的内容,不过文件相对越大。标准的采样率:8000、16000、22050、44100

通道数

AVNumberOfChannelsKey 定义记录音频内容的通道数。默认值 1 是单声道录制,2 是立体声录制。除非使用外部硬件录制,否则应该创建单声道录音。

6. 音频测量

AVAudioPlayer 和 AVAudioRecorder 中最实用的功能就是对音频进行测量。Audio Metering 可以读取音频的平均分贝和峰值分贝数据,并使用这些数据以可视化方式将声音大小呈现给用户。

首先在初始化 AVAudioPlayer 或 AVAudioRecorder 时应做出如下设置

self.player.meteringEnabled = YES;
self.recorder.meteringEnabled = YES;

点击音频播放或者音频录制,开始测量

- (void)startMeterTimer{    [self.link invalidate];    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateMeter)];    self.link.frameInterval = 4;  // 时间间隔为刷新率的 1/4    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];}- (void)stopMeterTimer{    [self.link invalidate];    self.link = nil;}

下面分别是音频播放情况下测量、音频录制情况下测量

- (void)updateMeter{    [self.player updateMeters];  // 刷新    CGFloat num1 = [self.player averagePowerForChannel:0];    CGFloat num2 = [self.player peakPowerForChannel:0];    NSLog(@"平均分贝:%f, 峰值分贝:%f", num1, num2);}
- (void)updateMeter{    [self.recorder updateMeters];  // 刷新    CGFloat num1 = [self.recorder averagePowerForChannel:0];    CGFloat num2 = [self.recorder peakPowerForChannel:0];    NSLog(@"平均分贝:%f, 峰值分贝:%f", num1, num2);}

上面方法都会返回用于表示声音分贝(dB)等级的浮点值,这个值的范围是 -160dB ~ 0dB

原创粉丝点击