播放和錄制音頻(AV Foundation開發(fā)秘籍)

主要通過學習AVAudioPlayer和AVAudioRecorder類來實現(xiàn)應用程序中添加音頻播放及音頻錄制的功能。
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的音頻是否繼續(xù)
AllowBluetoothA2DP PlayAndRecord 支持藍牙A2DP耳機
AllowAirPlay PlayAndRecord 支持AirPlay

AVAudioSessionMode

模式 適用的類別 場景
Default 所有類別 默認的模式
VoiceChat PlayAndRecord VoIP
GameChat PlayAndRecord 游戲錄制,由GKVoiceChat自動設置瘫镇,無需手動調(diào)用
VideoRecording PlayAndRecord鼎兽,Record 錄制視頻時
MoviePlayback Playback 視頻播放
Measurement PlayAndRecord,Record铣除,Playback 最小系統(tǒng)
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是回調(diào)
 */
- (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 ;

創(chuàng)建AVAudioPlayer

AVAudioPlayer構建與CoreAudio中的C-based Audio Queue Services的最頂層。它可以提供播放尚粘,循環(huán)择卦,音頻計量等簡單友好的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; // 無限循環(huán)
        player.enableRate = YES;
        [player prepareToPlay];  //相當于預加載Audio Queue的緩沖區(qū)
    } else {
        NSLog(@"Error creating player: %@", [error localizedDescription]);
    }
    return player;
}

播放控制

  • 修改播放器的音量
  • 修改播放器的pan值 允許使用立體聲播放聲音郎嫁,-1.0 到 1.0 之間
  • 調(diào)整播放率rate 0.5表示半速秉继,1表示正常,2表示2倍速播放
  • numberOfLoops 實現(xiàn)無縫循環(huán) -1表示無限循環(huán)泽铛,n > 0表示循環(huán)n次
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

處理中斷事件

音頻播放過程中尚辑,遇到電話呼入、鬧鐘等事件盔腔,會中斷音頻的播放
設置監(jiān)聽中斷事件

初始化AVAudioPlayer之后設置中斷事件的監(jiān)聽
NSNotificationCenter *nsnc = [NSNotificationCenter defaultCenter];
[nsnc addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
//相應回調(diào)
-(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];
            }
        }
    }
}
對線路改變的響應

線路是指輸出線路從揚聲器模式變?yōu)槎鷻C月褥,或者耳機變?yōu)閾P聲器模式。

線路改變原因的枚舉
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.   內(nèi)置揚聲器
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

創(chuàng)建AVAudioRecorder實例時需要為其提供數(shù)據(jù)的一些信息

- (nullable instancetype)initWithURL:(NSURL *)url settings:(NSDictionary<NSString *, id> *)settings error:(NSError **)outError;
  • 音頻流寫入文件的本地url地址
  • 配置錄音會話的設置信息
    在 #import <AVFAudio/AVAudioSettings.h> 文件里有音頻的設置鍵信息瓢喉。
AVFormatIDKey     音頻格式
AVSampleRateKey;    采樣率         
AVNumberOfChannelsKey;      通道數(shù)             

格式:kAudioFormatLinearPCM會將未壓縮的音頻流寫入文件中吓坚。 這中格式保真度最高,kAudioFormatMPEG4AAC的壓縮格式會顯著縮小文件灯荧,還能保證高質(zhì)量的音頻內(nèi)容礁击。
采樣率:對輸入的音頻信號每一秒內(nèi)的采樣數(shù)。一般標準的采樣率為8000逗载、16000哆窿、22050和44100赫茲。
通道數(shù):定義記錄音頻內(nèi)容的通道數(shù)厉斟。默認值為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;
}

//錄音參數(shù)設置
-(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];
}
//實現(xiàn)錄制完成代理
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)success {
   
}
//實現(xiàn)代理方法 錄制中斷
- (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码荔。 在調(diào)用這兩個方法之前需要設置_audioRecorder.meteringEnabled = YES才能使用。

AV Foundation開發(fā)秘籍源碼

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末感挥,一起剝皮案震驚了整個濱河市缩搅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌触幼,老刑警劉巖硼瓣,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異置谦,居然都是意外死亡堂鲤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門媒峡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘟栖,“玉大人,你說我怎么就攤上這事谅阿“胗矗” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵奔穿,是天一觀的道長镜沽。 經(jīng)常有香客問我敏晤,道長贱田,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任嘴脾,我火速辦了婚禮男摧,結果婚禮上蔬墩,老公的妹妹穿的比我還像新娘。我一直安慰自己耗拓,他們只是感情好拇颅,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著乔询,像睡著了一般樟插。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竿刁,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天黄锤,我揣著相機與錄音,去河邊找鬼食拜。 笑死鸵熟,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的负甸。 我是一名探鬼主播流强,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呻待!你這毒婦竟也來了打月?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蚕捉,失蹤者是張志新(化名)和其女友劉穎僵控,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鱼冀,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡报破,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了千绪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片充易。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖荸型,靈堂內(nèi)的尸體忽然破棺而出盹靴,到底是詐尸還是另有隱情,我是刑警寧澤瑞妇,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布稿静,位于F島的核電站,受9級特大地震影響辕狰,放射性物質(zhì)發(fā)生泄漏改备。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一蔓倍、第九天 我趴在偏房一處隱蔽的房頂上張望悬钳。 院中可真熱鬧盐捷,春花似錦、人聲如沸默勾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽母剥。三九已至滞诺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間环疼,已是汗流浹背铭段。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秦爆,地道東北人序愚。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像等限,于是被迫代替她去往敵國和親爸吮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容