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

本文详细介绍iOS平台音频处理技术,包括音频会话管理、播放、录制、测量等核心功能实现及优化技巧,帮助开发者掌握iOS应用中高效处理音频的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第二章 音频播放和录制

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值