播放和录制音频(AV Foundation开发秘籍)

2019-03-17  本文已影响0人  低吟浅唱1990

主要通过学习AVAudioPlayer和AVAudioRecorder类来实现应用程序中添加音频播放及音频录制的功能。
AV Foundation定义了7中分类来描述应用程序所使用的音频行为

AVAudioSessionCategory 7大类别

分类 作用 是否允许混音 音频输入 音频输出 说明
Ambient 游戏、效率应用程序 ✔️ ✔️ 混音播放
SoloAmbient(默认) 游戏、效率应用程序 ✔️ 独占播放
Playback 音频和视频播放器 可选 ✔️ 后台播放,也是独占的
Record 录音机、音频捕捉 ✔️ 录音模式,用于录音时使用
Play and Record VoIP 语音聊天 可选 ✔️ ✔️ 播放和录音,此时可以录音也可以播放
Audio Processing 离线会话处理 硬件解码音频,此时不能播放和录制
Multi-Route 使用外部硬件的高级A/V程序 ✔️ ✔️ 多种输入输出,例如可以耳机、USB设备同时播放

AVAudioSessionCategoryOptions 类别的子选项

Options 使用类别 说明
MixWithOthers PlayAndRecord, Playback, MultiRoute 与其他可以混音
DuckOthers Ambient, PlayAndRecord, Playback,MultiRoute 压低其他声音
AllowBluetooth Record,PlayAndRecord 支持蓝牙耳机
DefaultToSpeaker PlayAndRecord 默认使用免提
InterruptSpokenAudioAndMixWithOthers(9.0) PlayAndRecord,Playback, MultiRoute 当其他app音频暂停是本app的音频是否继续
AllowBluetoothA2DP PlayAndRecord 支持蓝牙A2DP耳机
AllowAirPlay PlayAndRecord 支持AirPlay

AVAudioSessionMode

模式 适用的类别 场景
Default 所有类别 默认的模式
VoiceChat PlayAndRecord VoIP
GameChat PlayAndRecord 游戏录制,由GKVoiceChat自动设置,无需手动调用
VideoRecording PlayAndRecord,Record 录制视频时
MoviePlayback Playback 视频播放
Measurement PlayAndRecord,Record,Playback 最小系统
VideoChat PlayAndRecord 视频通话
配置音频会话
    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]);
    }
/* returns singleton instance */
+ (AVAudioSession *)sharedInstance;

/* Set the session active or inactive. 
*/
- (BOOL)setActive:(BOOL)active error:(NSError **)outError;
- (BOOL)setActive:(BOOL)active withOptions:(AVAudioSessionSetActiveOptions)options error:(NSError **)outError;

/* Asynchronously activate the session. 异步激活会话,handler是回调
 */
- (void)activateWithOptions:(AVAudioSessionActivationOptions)options completionHandler:(void (^)(BOOL activated, NSError * _Nullable error))handler;
// 获取设备可用的SessionCategory
@property (readonly) NSArray<AVAudioSessionCategory> *availableCategories;

/* 设置session的category */
- (BOOL)setCategory:(AVAudioSessionCategory)category error:(NSError **)outError ;
/* set session category with options */
- (BOOL)setCategory:(AVAudioSessionCategory)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError;
/* set session category and mode with options */
- (BOOL)setCategory:(AVAudioSessionCategory)category mode:(AVAudioSessionMode)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError ;
- (BOOL)setCategory:(AVAudioSessionCategory)category mode:(AVAudioSessionMode)mode routeSharingPolicy:(AVAudioSessionRouteSharingPolicy)policy options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError;
@property (readonly) AVAudioSessionCategory category;
@property (readonly) AVAudioSessionCategoryOptions categoryOptions;
@property (readonly) AVAudioSessionRouteSharingPolicy routeSharingPolicy;
@property (readonly) NSArray<AVAudioSessionMode> *availableModes;
- (BOOL)setMode:(AVAudioSessionMode)mode error:(NSError **)outError; /* set session mode */
@property (readonly) AVAudioSessionMode mode; /* get session mode */
/* 返回权限*/
@property (readonly) AVAudioSessionRecordPermission recordPermission;

typedef void (^PermissionBlock)(BOOL granted);
- (void)requestRecordPermission:(PermissionBlock)response;
- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride error:(NSError **)outError;
@property (readonly, getter=isOtherAudioPlaying) BOOL otherAudioPlaying;
@property (readonly) BOOL secondaryAudioShouldBeSilencedHint;
@property (readonly) AVAudioSessionRouteDescription *currentRoute;
- (BOOL)setPreferredInput:(nullable AVAudioSessionPortDescription *)inPort error:(NSError **)outError API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos, macos);
@property (readonly, nullable) AVAudioSessionPortDescription *preferredInput API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos, macos); /* Get the preferred input port.  Will be nil if no preference has been set */

@property (readonly, nullable) NSArray<AVAudioSessionPortDescription *> *availableInputs ;

创建AVAudioPlayer

AVAudioPlayer构建与CoreAudio中的C-based Audio Queue Services的最顶层。它可以提供播放,循环,音频计量等简单友好的Objective-C接口。

- (AVAudioPlayer *)playerForFile:(NSString *)name {

    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:name
                                             withExtension:@"caf"];

    NSError *error;
    AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL
                                                                   error:&error];
    if (player) {
        player.numberOfLoops = -1; // 无限循环
        player.enableRate = YES;
        [player prepareToPlay];  //相当于预加载Audio Queue的缓冲区
    } else {
        NSLog(@"Error creating player: %@", [error localizedDescription]);
    }
    return player;
}

播放控制

THPlayerController.h
@interface THPlayerController : NSObject
@property (nonatomic, getter = isPlaying) BOOL playing; //是否正在播放
// 播放
- (void)play;
//停止
- (void)stop;
//设置速率
- (void)adjustRate:(float)rate;
// 设置pan值
- (void)adjustPan:(float)pan forPlayerAtIndex:(NSUInteger)index;
//设置音量
- (void)adjustVolume:(float)volume forPlayerAtIndex:(NSUInteger)index;

@end
#import "THPlayerController.h"
#import <AVFoundation/AVFoundation.h>

@interface THPlayerController () <AVAudioPlayerDelegate>
@property (strong, nonatomic) NSArray *players;
@end

@implementation THPlayerController

#pragma mark - Initialization

- (id)init {
    self = [super init];
    if (self) {
        AVAudioPlayer *guitarPlayer = [self playerForFile:@"guitar"];
        AVAudioPlayer *bassPlayer = [self playerForFile:@"bass"];
        AVAudioPlayer *drumsPlayer = [self playerForFile:@"drums"];

        guitarPlayer.delegate = self;

        _players = @[guitarPlayer, bassPlayer, drumsPlayer];
    }
    return self;
}

- (AVAudioPlayer *)playerForFile:(NSString *)name {

    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:name
                                             withExtension:@"caf"];

    NSError *error;
    AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL
                                                                   error:&error];
    if (player) {
        player.numberOfLoops = -1; // loop indefinitely
        player.enableRate = YES;
        [player prepareToPlay];
    } else {
        NSLog(@"Error creating player: %@", [error localizedDescription]);
    }

    return player;
}


#pragma mark - Global playback control methods

- (void)play {
    if (!self.playing) {
        NSTimeInterval delayTime = [self.players[0] deviceCurrentTime] + 0.01;
        for (AVAudioPlayer *player in self.players) {
            [player playAtTime:delayTime];
        }
        self.playing = YES;
    }
}

- (void)stop {
    if (self.playing) {
        for (AVAudioPlayer *player in self.players) {
            [player stop];
            player.currentTime = 0.0f; //player.currentTime回到原点
        }
        self.playing = NO;
    }
}

- (void)adjustRate:(float)rate {
    for (AVAudioPlayer *player in self.players) {
        player.rate = rate;
    }
}
- (void)adjustPan:(float)pan forPlayerAtIndex:(NSUInteger)index {
    if ([self isValidIndex:index]) {
        AVAudioPlayer *player = self.players[index];
        player.pan = pan;
    }
}
- (void)adjustVolume:(float)volume forPlayerAtIndex:(NSUInteger)index {
    if ([self isValidIndex:index]) {
        AVAudioPlayer *player = self.players[index];
        player.volume = volume;
    }
}
- (BOOL)isValidIndex:(NSUInteger)index {
    return index == 0 || index < self.players.count;
}
@end

处理中断事件

音频播放过程中,遇到电话呼入、闹钟等事件,会中断音频的播放
设置监听中断事件

初始化AVAudioPlayer之后设置中断事件的监听
NSNotificationCenter *nsnc = [NSNotificationCenter defaultCenter];
[nsnc 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 stop];  
    }else{
        AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
        if (options == AVAudioSessionInterruptionOptionShouldResume) {  //恢复
            [self play];
            }
        }
    }
}
对线路改变的响应

线路是指输出线路从扬声器模式变为耳机,或者耳机变为扬声器模式。

线路改变原因的枚举
AVAudioSessionRouteChangeReasonUnknown = 0,
AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
AVAudioSessionRouteChangeReasonCategoryChange = 3,
AVAudioSessionRouteChangeReasonOverride = 4,
AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
AVAudioSessionRouteChangeReasonRouteConfigurationChange = 8 
音频输入
AVAudioSessionPortUSBAudio,
 AVAudioSessionPortHeadsetMic, 
AVAudioSessionPortBuiltInMic.  
音频输出
AVAudioSessionPortUSBAudio,  usb接口
AVAudioSessionPortLineOut, 
AVAudioSessionPortHeadphones,  耳机输出
AVAudioSessionPortHDMI, 
AVAudioSessionPortBuiltInSpeaker.   内置扬声器
NSNotificationCenter *nsnc = [NSNotificationCenter defaultCenter];
[nsnc addObserver:self
         selector:@selector(handleRouteChange:)
             name:AVAudioSessionRouteChangeNotification
           object:[AVAudioSession sharedInstance]];

//线路的改变  比如耳机断开是停止播放
- (void)handleRouteChange:(NSNotification *)notification {
    NSDictionary *info = notification.userInfo;
    AVAudioSessionRouteChangeReason reason =
        [info[AVAudioSessionRouteChangeReasonKey] unsignedIntValue];
//旧设备断开链接
    if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
        AVAudioSessionRouteDescription *previousRoute =
            info[AVAudioSessionRouteChangePreviousRouteKey];
//端口描述        
AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
        NSString *portType = previousOutput.portType;
        if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
            [self stop];
        }
    }
}
音频录制AVAudioRecorder

创建AVAudioRecorder实例时需要为其提供数据的一些信息

- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;
AVFormatIDKey     音频格式
AVSampleRateKey;    采样率         
AVNumberOfChannelsKey;      通道数             

格式:kAudioFormatLinearPCM会将未压缩的音频流写入文件中。 这中格式保真度最高,kAudioFormatMPEG4AAC的压缩格式会显著缩小文件,还能保证高质量的音频内容。
采样率:对输入的音频信号每一秒内的采样数。一般标准的采样率为8000、16000、22050和44100赫兹。
通道数:定义记录音频内容的通道数。默认值为1意味着使用单声道录制,设置为2采用立体声录制。

-(NSURL *)getSavePath{
    NSString *urlStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    urlStr = [urlStr stringByAppendingPathComponent:kRecordAudioFile];
    NSLog(@"file path:%@",urlStr);
    NSURL *url = [NSURL fileURLWithPath:urlStr];
    return url;
}

//录音参数设置
-(NSDictionary *)getAudioSetting{
    NSMutableDictionary *dicM = @{}.mutableCopy;
    [dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];
    [dicM setObject:@(44100) forKey:AVSampleRateKey];
    [dicM setObject:@(1) forKey:AVNumberOfChannelsKey];
    [dicM setObject:@(16) forKey:AVEncoderBitDepthHintKey];
    [dicM setObject:@(AVAudioQualityMedium) forKey:AVEncoderAudioQualityKey];
    return dicM;
}
//录音器
-(AVAudioRecorder *)audioRecorder{
    if (!_audioRecorder) {
        //保存路径
        NSURL *url = [self getSavePath];
        NSDictionary *setting = [self getAudioSetting];
        NSError *error;
        _audioRecorder = [[AVAudioRecorder alloc] initWithURL:url settings:setting error:&error];
        _audioRecorder.delegate = self;
        _audioRecorder.meteringEnabled = YES;
        [_audioRecorder prepareToRecord];
    }
    return _audioRecorder;
}
//开始录制
- (BOOL)record {
    return [self.audioRecorder record];
}
//中断
- (void)pause {
    [self.audioRecorder pause];
}
// 停止
- (void)stop {
    [self.audioRecorder stop];
}
//实现录制完成代理
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)success {
   
}
//实现代理方法 录制中断
- (void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder {

}
记录分贝等级
- (float)peakPowerForChannel:(NSUInteger)channelNumber; /* returns peak power in decibels for a given channel */
- (float)averagePowerForChannel:(NSUInteger)channelNumber; /* returns average power in decibels for a given channel */

返回用于表示分贝等级的浮点值,最大分贝的0dB到最小分贝的-160dB。 在调用这两个方法之前需要设置_audioRecorder.meteringEnabled = YES才能使用。

AV Foundation开发秘籍源码

上一篇下一篇

猜你喜欢

热点阅读