音頻初見
基本介紹
音頻: 從形式上分為
短音頻(音效播放):不需要進度|循環(huán)等控制 AudioToolbox.framework
(C語言框架
- 本質(zhì):將音頻注冊到system sound service(簡單底層的聲音播放服務(wù)))
特點: - 播放時間不超過30s
- 數(shù)據(jù)格式PCM/IMA4
- 音頻文件必須打包成.caf,.aif,.wav,(.MP3也可以)
長音頻(音樂播放)
- 較大的音頻文件,需要進行精確的控制的場景
- AVFoundation.framework
- 支持的音頻格式有:AAC,ALAC,HE-AAC,iLBC,IMA4,MP3等.
音效播放
步驟:
- 不要忘記導(dǎo)入頭文件: #import <AudioToolbox/AudioToolbox.h>
- 1.調(diào)用AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函數(shù)獲得系統(tǒng)聲音ID。
- 2.如果需要監(jiān)聽播放完成操作,則使用AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID,CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)方法注冊回調(diào)函數(shù)歇万。
- 3.調(diào)用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)
AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(后者帶有震動效果)
- (void)audioBox{
//獲取資源路徑
NSString *pathSource = [[NSBundle mainBundle]pathForResource:@"audiobox" ofType:@".caf"];
NSURL *sourceUrl = [NSURL fileURLWithPath:pathSource];
//系統(tǒng)聲音id
SystemSoundID soundID = 0;
/**
注冊音效服務(wù)
@param inFileURL#> 音頻文件URL 需要橋接->C語言
@param outSystemSoundID#> 輸出音效ID
@return 將音效文件加入到系統(tǒng)音頻服務(wù)中并返回一個長整型音頻ID
*/
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(sourceUrl), &soundID);
/**
添加播放完畢回調(diào)函數(shù)
@param inSystemSoundID#> 音效ID
@param inRunLoop#> 添加到Runloop
@param inRunLoopMode#> RunloopMode
@param inCompletionRoutine#> 回調(diào)函數(shù)
@param inClientData#> 傳遞給回調(diào)函數(shù)的相關(guān)數(shù)據(jù)
@return ..
*/
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, playCompletion, NULL);
//播放
AudioServicesPlayAlertSound(soundID); //播放音效并震動
// AudioServicesPlaySystemSound(soundID); //播放音效
}
/**
回調(diào)函數(shù)
@param ssID 音效id
@param clientData 回調(diào)時傳遞的數(shù)據(jù)
*/
void playCompletion(SystemSoundID ssID, void* __nullable clientData){
NSLog(@"播放完成");
//摧毀音效
AudioServicesDisposeSystemSoundID(ssID);
}
音樂播放 AVAudioPlayer
AVAudioPlayer支持多種音頻格式,而且能夠?qū)M度,音量,播放速度等控制
頭文件 #import <AVFoundation/AVFoundation.h>
步驟:
- 1.給定資源路徑,或者二進制數(shù)據(jù)初始化播放器
- 2.設(shè)置相關(guān)屬性
- 3.執(zhí)行播放,更新相關(guān)UI(進度更新,歌詞顯示(CADislinkplay/NSTimer))
初始化方法
AVAudioPlayer可以接受本地資源路徑(NSURL)/NSData類型數(shù)據(jù)(可以用來加載網(wǎng)絡(luò)鏈接),但是實質(zhì)上AVAudioPlayer不支持流式播放,只能播放完整的文件,使用后者加載網(wǎng)絡(luò)鏈接,當(dāng)播放的音頻文件較大或者網(wǎng)絡(luò)不好就需要過長的時間等待
每一個實例化方法返回一個AVAudioPlayer對象,每一個文件/Data對應(yīng)一個AVAudioPlayer對象,傳入一個NSError的指針,用于判斷初始化是否成功.
- (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError;
- (nullable instancetype)initWithData:(NSData *)data error:(NSError **)outError;
播放控制相關(guān)API
//準(zhǔn)備播放,分配播放所需的資源,并將其加入內(nèi)部播放隊列,加載音頻文件到緩沖區(qū)筐钟,注意:即使在播放之前音頻文件沒有加載到緩沖區(qū)程序也會隱式調(diào)用此方法。
- (BOOL)prepareToPlay;
//播放,如果資源還沒準(zhǔn)備好,會隱式調(diào)用prepareToPlay方法,是異步的
- (BOOL)play;
//相對當(dāng)前設(shè)備時間或指定的時間開始播放音頻
- (BOOL)playAtTime:(NSTimeInterval)time
NSTimeInterval nowTime = self.audioPlayer.deviceCurrentTime;
[self.audioPlayer playAtTime:nowTime + 2.0];
//暫停播放 并且準(zhǔn)備好繼續(xù)播放
- (void)pause; /* pauses playback, but remains ready to play. */
//停止播放 不再準(zhǔn)備好繼續(xù)播放,再次調(diào)用會重新開始播放
- (void)stop; /* stops playback. no longer ready to play. */
屬性相關(guān)
//獲取是否正在播放
@property(readonly, getter=isPlaying) BOOL playing;
//獲取當(dāng)前音頻聲道數(shù)
@property(readonly) NSUInteger numberOfChannels;
//獲取當(dāng)前音頻時長
@property(readonly) NSTimeInterval duration;
//獲取創(chuàng)建時的音頻路徑 跟初始化方法相關(guān)聯(lián)
@property(readonly, nullable) NSURL *url;
//獲取創(chuàng)建時的音頻數(shù)據(jù) 跟初始化方法相關(guān)聯(lián)
@property(readonly, nullable) NSData *data;
//聲道 -1.0~0.0左聲道 0.0臨界值 0.0~1.0為右聲道
@property float pan
//音量 正常范圍在0.0~1.0
@property float volume;
- (void)setVolume:(float)volume fadeDuration:(NSTimeInterval)duration NS_AVAILABLE(10_12, 10_0); /* fade to a new volume over a duration */
//是否可以改變播放速率 必須在prepareToPlay方法前調(diào)用
@property BOOL enableRate
//播放速率 1.0為正常速率 0.5一半 2.0雙倍速播放
@property float rate
//當(dāng)前播放的時間點,可以實現(xiàn)定點進行播放
@property NSTimeInterval currentTime;
//輸出設(shè)備播放音頻的時間赋朦,注意:如果播放中被暫停此時間也會繼續(xù)累加
@property(readonly) NSTimeInterval deviceCurrentTime
//設(shè)置音頻播放循環(huán)次數(shù) 如果為0則不循環(huán)篓冲,如果小于0則無限循環(huán),大于0則表示循環(huán)次數(shù)
@property NSInteger numberOfLoops;
//獲取音頻設(shè)置字典
@property(readonly) NSDictionary<NSString *, id> *settings NS_AVAILABLE(10_7, 4_0); /* returns a settings dictionary with keys as described in AVAudioSettings.h */
/* returns the format of the audio data */
@property(readonly) AVAudioFormat *format NS_AVAILABLE(10_12, 10_0);
//是否開啟儀表計數(shù)功能(啟用音頻測量) 默認(rèn)為NO北发,一旦啟用音頻測量可以通過updateMeters方法更新測量值
@property(getter=isMeteringEnabled) BOOL meteringEnabled; /* turns level metering on or off. default is off. */
//更新儀表計數(shù)的值
- (void)updateMeters; /* call to refresh meter values */
//獲得指定聲道的分貝峰值纹因,如果要獲得分貝峰值必須在此之前調(diào)用updateMeters方法
- (float)peakPowerForChannel:(NSUInteger)channelNumber; /* returns peak power in decibels for a given channel */
//獲得指定聲道的分貝平均值喷屋,注意如果要獲得分貝平均值必須在此之前調(diào)用updateMeters方法
- (float)averagePowerForChannel:(NSUInteger)channelNumber; /* returns average power in decibels for a given channel */
/* The channels property lets you assign the output to play to specific channels as described by AVAudioSession's channels property */
/* This property is nil valued until set. */
/* The array must have the same number of channels as returned by the numberOfChannels property. */
//獲得或設(shè)置播放聲道
@property(nonatomic, copy, nullable) NSArray<AVAudioSessionChannelDescription *> *channelAssignments
代理方法
<!--音頻播放結(jié)束后調(diào)用的函數(shù) 被中斷停止播放并不會被調(diào)用-->
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
<!--播放遇到錯誤時調(diào)用的函數(shù)-->
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error;
/**
音樂播放器被打斷
@param player .
*/
-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player{
NSLog(@"播放被中斷");
//如果音頻被中斷琳拨,比如有電話呼入,該方法就會被回調(diào)屯曹,該方法可以保存當(dāng)前播放信息狱庇,以便恢復(fù)繼續(xù)播放的進度
}
/**
* 音樂播放器停止打斷
*
* @param player .
*/
-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player
{
NSLog(@"audioPlayerEndInterruption-停止打斷");
[player play];
//繼續(xù)播放
}
后臺播放
1.需要添加info.plist
![1](https://github.com/oRhino/oRhino.github.io/blob/master/images/posts/2017-04-07-Audio/1.png?raw=true)
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
或者
![2](https://github.com/oRhino/oRhino.github.io/blob/master/images/posts/2017-04-07-Audio/2.png?raw=true)
2.添加代碼支持
//設(shè)置鎖屏仍能繼續(xù)播放
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
監(jiān)聽耳機事件
//添加通知,拔出耳機后暫停播放
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];
/**
* 一旦輸出改變則執(zhí)行此方法
*
* @param notification 輸出改變通知對象
*/
-(void)routeChange:(NSNotification *)notification{
NSDictionary *dic=notification.userInfo;
int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue];
//等于AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示舊輸出不可用
if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey];
AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject];
//原設(shè)備為耳機則暫停
if ([portDescription.portType isEqualToString:@"Headphones"]) {
[self.audioPlayer pause];
}
}
}
后臺播放信息顯示
1.導(dǎo)入框架 #import <MediaPlayer/MediaPlayer.h>
2.添加代碼
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
//設(shè)置歌曲題目
[dict setObject:@"題目" forKey:MPMediaItemPropertyTitle];
//設(shè)置歌手名
[dict setObject:@"歌手" forKey:MPMediaItemPropertyArtist];
//設(shè)置專輯名
[dict setObject:@"專輯" forKey:MPMediaItemPropertyAlbumTitle];
//設(shè)置顯示的圖片
UIImage *newImage = [UIImage imageNamed:@"43.png"];
[dict setObject:[[MPMediaItemArtwork alloc] initWithImage:newImage]
forKey:MPMediaItemPropertyArtwork];
//設(shè)置歌曲時長
[dict setObject:[NSNumber numberWithDouble:300] forKey:MPMediaItemPropertyPlaybackDuration];
//設(shè)置已經(jīng)播放時長
[dict setObject:[NSNumber numberWithDouble:150] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
//更新字典
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
注:實現(xiàn)后臺播放顯示歌詞的方法實現(xiàn)思路:將歌詞信息,繪制到專輯圖片上進行顯示
后臺播放遠(yuǎn)程控制
1.注冊接受遠(yuǎn)程控制
- (void)viewDidAppear:(BOOL)animated {
// 接受遠(yuǎn)程控制
[self becomeFirstResponder];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
- (void)viewDidDisappear:(BOOL)animated {
// 取消遠(yuǎn)程控制
[self resignFirstResponder];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
2.實現(xiàn)監(jiān)聽方法
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl) {
//判斷是否為遠(yuǎn)程控制
switch (event.subtype) {
//播放
case UIEventSubtypeRemoteControlPlay:
if (![self.audioPlayer isPlaying]) {
[self.audioPlayer play];
}
break;
case UIEventSubtypeRemoteControlPause:
//暫停
if ([self.audioPlayer isPlaying]) {
[self.audioPlayer pause];
}
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(@"下一首");
break;
case UIEventSubtypeRemoteControlPreviousTrack:
NSLog(@"上一首 ");
break;
default:
break;
}
}
}
注:
event是事件類型對象,type是事件類型枚舉
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches, //觸摸事件
UIEventTypeMotion, //運動事件
UIEventTypeRemoteControl, // 遠(yuǎn)程控制事件
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),//物理按鈕事件類型
};
subtype中的枚舉便是點擊這些控制鍵后傳遞給我們的消息恶耽,我們可以根據(jù)這些消息在app內(nèi)做邏輯處理
typedef NS_ENUM(NSInteger, UIEventSubtype) {
// available in iPhone OS 3.0
UIEventSubtypeNone = 0,
// for UIEventTypeMotion, available in iPhone OS 3.0
//搖晃
UIEventSubtypeMotionShake = 1,
//UIEventTypeRemoteControl相關(guān)的枚舉信息
// for UIEventTypeRemoteControl, available in iOS 4.0
//點擊播放按鈕或者耳機線控中間那個按鈕
UIEventSubtypeRemoteControlPlay = 100,
//點擊暫停按鈕
UIEventSubtypeRemoteControlPause = 101,
//點擊停止按鈕
UIEventSubtypeRemoteControlStop = 102,
//點擊播放與暫停開關(guān)按鈕(iphone抽屜中使用這個)
UIEventSubtypeRemoteControlTogglePlayPause = 103,
//點擊下一曲按鈕或者耳機中間按鈕兩下
UIEventSubtypeRemoteControlNextTrack = 104,
//點擊上一曲按鈕或者耳機中間按鈕三下
UIEventSubtypeRemoteControlPreviousTrack = 105,
//快退開始 點擊耳機中間按鈕三下不放開
UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
//快退結(jié)束 耳機快退控制松開后
UIEventSubtypeRemoteControlEndSeekingBackward = 107,
//開始快進 耳機中間按鈕兩下不放開
UIEventSubtypeRemoteControlBeginSeekingForward = 108,
//快進結(jié)束 耳機快進操作松開后
UIEventSubtypeRemoteControlEndSeekingForward = 109,
};
拓展:后臺播放遠(yuǎn)程控制的相關(guān)自定義
MPRemoteCommandCenter屬于<MediaPlayer/MediaPlayer.h>框架,是一個單例類,處理遠(yuǎn)程控制中心事件
可以自定義你的控制中心,鎖屏?xí)r控制,siri語音控制.它管理一系列命令Playback Commands,Feedback Command,Previous/Next Track Commands),Skip Interval Commands,Seek Command等
MPRemoteCommand是這些所有命令的父類
下面簡單介紹:
//是否開啟命令
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
//Target-action 添加事件處理
- (void)addTarget:(id)target action:(SEL)action;
//當(dāng)屬性enable = NO時,移除
- (void)removeTarget:(id)target action:(nullable SEL)action;
- (void)removeTarget:(nullable id)target;
//返回一個事件對象 MPRemoteCommandEvent是一個命令事件的父類,對應(yīng)不同的命令進行了繼承擴展,主要添加一些屬性,比如拖拽音樂進度的positionTime
- (id)addTargetWithHandler:(MPRemoteCommandHandlerStatus(^)(MPRemoteCommandEvent *event))handler;
遠(yuǎn)程控制三部曲
1:監(jiān)聽遠(yuǎn)程控制事件
- (void)beginReceivingRemoteControlEvents
2:捕獲遠(yuǎn)程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
3.取消遠(yuǎn)程控制事件
- (void)endReceivingRemoteControlEvents
網(wǎng)易云的鎖屏實現(xiàn)
注:添加的RemoteComand必須設(shè)置Enable為YES,并添加Target才會顯示,并且添加相同類型RemoteComand,有優(yōu)先級的選擇顯示,更改原有默認(rèn)顯示的上一曲,播放/暫停,下一曲會失效,需要重新添加
MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter];
//feedBack
[rcc.likeCommand setEnabled:YES];
//使用block
[rcc.likeCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(@"like!");
return MPRemoteCommandHandlerStatusSuccess;
}];
rcc.likeCommand.localizedTitle = @"喜歡";
[rcc.dislikeCommand setEnabled:YES];
//使用target-action 不喜歡
[rcc.dislikeCommand addTarget:self action:@selector(previousCommandAction)];
rcc.dislikeCommand.localizedTitle = @"上一首";
[rcc.bookmarkCommand setEnabled:YES];
//取消Action
// [rcc.dislikeCommand removeTarget:self action:@selector(previousCommandAction)];
//書簽
[rcc.bookmarkCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(@"bookmark");
return MPRemoteCommandHandlerStatusSuccess;
}];
rcc.bookmarkCommand.localizedTitle = @"收藏";
效果圖:
![3](https://github.com/oRhino/oRhino.github.io/blob/master/images/posts/2017-04-07-Audio/3.jpg?raw=true)
鎖屏控制播放進度
主要使用:MPChangePlaybackPositionCommand
//改變音樂播放進度
MPChangePlaybackPositionCommand *changePlayBackPosition = [rcc changePlaybackPositionCommand];
[changePlayBackPosition setEnabled:YES];
[changePlayBackPosition addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
MPChangePlaybackPositionCommandEvent *events = (MPChangePlaybackPositionCommandEvent *)event;
self.audioPlayer.currentTime = events.positionTime;
return MPRemoteCommandHandlerStatusSuccess;
}];
流式音頻播放(播放網(wǎng)絡(luò)音頻)
使用AudioToolbox框架中的音頻隊列服務(wù)Audio Queue Services密任。
第三方框架:FreeStreamer
import "FSAudioStream.h"
//初始化
FSAudioStream *player = [[FSAudioStream alloc]initWithUrl:[self getFileUrl]];
player.onFailure = ^(FSAudioStreamError error, NSString *errorDescription) {
NSLog(@"播放錯誤error:%@",errorDescription);
};
player.onCompletion = ^{
NSLog(@"播放完成");
};
//設(shè)置音量
[player setVolume:0.5];
//播放
[player play];
//添加引用
self.audioStream = player;
豆瓣:DOUAudioStreamer
#import "DOUAudioStreamer.h"
@interface AudioModel : NSObject<DOUAudioFile>
@property (nonatomic, strong) NSURL *audioFileURL;
@end
@implementation AudioModel
- (NSURL *)audioFileURL{
NSString *urlStr= @"http://sc1.111ttt.com/2017/1/03/07/296072048390.mp3";
NSURL *url=[NSURL URLWithString:urlStr];
self.audioFileURL = url;
return url;
}
@end
AudioModel *file = [[AudioModel alloc]init];
[file audioFileURL];
self.stream = [[DOUAudioStreamer alloc]initWithAudioFile:file];
[self.stream play];
參考:
https://developer.apple.com/library/content/samplecode/MPRemoteCommandSample/Introduction/Intro.html