<h2>一睬棚、音頻</h2>
在iOS中音頻播放從形式上可以分為音效播放和音樂播放第煮。前者主要指一些段音頻播放,通常作為點綴音頻闸拿,對于這類音頻不需要進行進度空盼、循環(huán)控制。后者指的是一些較長的音頻新荤,通常是主音頻,對于這些音頻台汇,播放通常需要精確的控制苛骨。在iOS中播放兩類視頻分別使用AudioToolbox.framework 和 AVFoundation.framework 來完成音效和音樂播放。
<h3>1苟呐、音效</h3>
AudioToolbox.framework 是一套基于C語言的框架痒芝,使用它來播放音效其本質(zhì)是將短音頻注冊到系統(tǒng)聲音服務(wù)(System Sound Service)。System Sound Service 是一種簡單牵素、底層的聲音播放服務(wù)严衬,但它本身存在一些限制:
- 音頻播放時間不能夠超過30s
- 數(shù)據(jù)必須是PCM或者IMA4格式
- 音頻文件必須打包成.caf、.aif笆呆、.wav 中的一種(這是官方說法请琳,實際測試中一些.mp3 也可以播放)
<b>使用System Sound Service 播放音效的步驟如下:</b>
I:調(diào)用<i><b>AudioServicesCreateSystemSoundID(</b>CFURLRef inFileURL, SystemSoundID *outSystemSoundID<b>)</b></i>函數(shù)獲得系統(tǒng)聲音ID粱挡。
II:如果需要監(jiān)聽播放完成操作,則使用<i><b>AudioServicesAddSystemSoundCompletion(</b>SystemSoundID inSystemSoundID, CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, void *inClientData<b>)</b></i>方法注冊回調(diào)函數(shù)俄精。
III:調(diào)用<i><b>AudioServicesPlaySystemSound(</b>SystemSoundID inSystemSoundID<b>)</b></i>或者<i><b>AudioServicesPlayAlertSound(</b>SystemSoundID inSystemSoundID<b>)</b></i> 方法播放音效(后者帶有震動效果)询筏。
下面是一個簡單的示例程序:
<pre>
- (void)playSoundEffect:(NSString *)name
{
NSString audioFile = [[NSBundle mainBundle]pathForResource:name ofType:nil];
NSURL fileUrl = [NSURL fileURLWithPath:audioFile];
// 獲得系統(tǒng)聲音ID
SystemSoundID soundID = 0;
/- inFileUrl:音頻文件url
- outSystemSoundID: 聲音ID(此函數(shù)會將音效文件加入到系統(tǒng)音頻服務(wù)中并返回一個ID)
*/
AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileUrl, &soundID);
// 如果播放完需要進行某些操作,可以調(diào)用如下方法注冊一個播放完的回調(diào)函數(shù)
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
// 播放音頻
AudioServicesPlaySystemSound(soundID);
//AudioServicesPlayAlertSound(soundID); // 帶震動
}
pragma mark 播放完的回調(diào)函數(shù) 這是一個C函數(shù)
void soundCompleteCallback(SystemSoundID soundID, void *clientData) {
NSLog(@"播放完成竖慧。嫌套。。圾旨。");
}
</pre>
<h3>2踱讨、音樂</h3>
<h4>(1)概述</h4>
如果播放較大的音頻或者要對音頻有精確的控制System Sound Service 可能就很難滿足實際需求了,通常這種情況會選擇使用AVFoundation.framework中的AVAudioPlayer來實現(xiàn)砍的。AVAudioPlayer可以看成一個播放器痹筛,它支持多種音頻格式,而且能夠進行進度挨约、音量味混、播放速度等控制。首先簡單看一下AVAudioPlayer常用的屬性和方法:
<h5>屬性</h5>
屬性 | 說明 |
---|---|
<i><b>@property(</b>readonly, getter=isPlaying<b>) BOOL playing</b></i> | 是否正在播放诫惭,只讀 |
<i><b>@property(</b>readonly<b>) NSUInteger numberOfChannels</b></i> | 音頻聲道數(shù)翁锡,只讀 |
<i><b>@property(</b>readonly<b>) NSTimerInterval duration</b></i> | 音頻時長 |
<i><b>@property(</b>readonly<b>) NSURL *url</b> </i> | 音頻文件路徑,只讀 |
<i><b>@property(</b>readonly<b>) NSData *data</b></i> | 音頻數(shù)據(jù)夕土,只讀 |
<i><b>@property(</b><b>) float pan</b></i> | <small> 立體聲平衡馆衔,如果為-1.0 則完全左聲道,如果0.0 則左右聲道平衡怨绣,如果為1.0則往前為右聲道</small> |
<i><b>@property(</b><b>) float volume</b></i> | 音頻大小角溃,范圍0-1.0 |
<i><b>@property(</b><b>) BOOL enableRate</b> | 是否允許改變播放速率 |
<i><b>@property(</b><b>) float rate</b> | <small>播放速率,范圍0.5-2.0篮撑,如果為1.0 則正常播放减细,如果為要需改播放速率則剝削設(shè)置enableRate為YES</small> |
<i><b>@property(</b><b>) currentTime</b> | 當(dāng)前播放時長 |
<i><b>@property(</b>readonly<b>) NSTimeInterval diviceCurrentTime</b> | 輸出設(shè)備播放音頻的時間,如果播放暫停赢笨,此時間也會繼續(xù)累加 |
<i><b>@property(</b><b>) NSInteger numberOfLoops</b> | 循環(huán)播放次數(shù)未蝌,如果為0 則不循環(huán),小于0則無限循環(huán)茧妒,大于0為循環(huán)次數(shù) |
<i><b>@property(</b>readonly<b>) NSDictionary *settings</b> | 音頻播放設(shè)置信息萧吠,只讀 |
<i><b>@property(</b>getter=isMeteringEnabled<b> BOOL meteringEnabled) </b> | <small>是否啟用音頻測量,默認為NO桐筏,一檔醫(yī)用音頻測量可以通過updateMeters方法更新測量值</small> |
<i><b>@property(</b><b>) NSArray *channelAssignments</b></i> | 獲得或設(shè)置播放聲道 |
<h5>方法</h5>
對象方法 | 說明 |
---|---|
<i>- (instancetype)<b>initWithContentsOfURL:</b>(NSURL *)url <b>error:</b>(NSError **)outError </i> | <small>使用文件URL初始化播放器纸型,注意這個URL不能是HTTP URL, AVAudioPlayer不支持加載網(wǎng)絡(luò)媒體流,只能播放本地文件 </small> |
<i>- (instancetype)<b>initWithData:</b>(NSData *)data <b>error:</b>(NSError **)outError</i> | <small>使用NSData初始化播放器狰腌,注意使用此方法是必須文件格式和文件后綴一致除破,否則出錯,所有相比此方法更推薦使用上述方法或<i>- (instancetype)<b>initWithData:</b>(NSData *)data <b>fileTypeHint:</b>(NSString *)utiString <b>error:</b>(NSError **)outError</i></small> |
<i>- (BOOL)<b>prepareToPlay</b><i> | <small>加載音頻文件的緩沖區(qū)癌别,注意即使在播放之前音頻文件沒有加載到緩沖區(qū)皂岔,程序也會隱式調(diào)用次方法。</small> |
<i>- (BOOL)<b>play</b></i> | 播放音頻文件 |
<i>- (BOOL)<b>playAtTime:</b>(NSTimeInterval)time</i> | 在指定的時間開始播放音頻 |
<i>- (void)<b>pause</b></i> | 暫停播放 |
<i>- (void)<b>stop</b></i> | 停止播放 |
<i>- (void)<b>updateMeters</b></i> | <small>更新音頻測量值展姐,注意如果要跟新音頻測量值躁垛,必須設(shè)置meteringEnable 為YES,通過音頻測量值可以即使獲得音頻分貝等信息</small> |
<i>- (float)<b>peakPowerForChannel:</b>(NSUInteger)channelNumber</i> | 獲得指定聲道的分貝峰值圾笨,如果要獲得分貝峰值教馆,必須在此之前調(diào)用updateMeters方法 |
<i>- (float)<b>averagePowerForChannel:</b>(NSUInteger)channelNumber</i> | 獲得指定聲道的分貝平均值,如果要獲得分貝平均值擂达,必須在此之前調(diào)用updateMeters方法 |
<h5>代理方法</h5>
代理方法 | 說明 |
---|---|
<i>- (void)<b>audioPlayerDidFinishPlaying:</b>(AVAudioPlayer *)player <b>successfully:</b>(BOOL)Flag</i> | 音頻播放完成 |
<i>- (void)<b>audioPlayerDecodeErrorDidOccur:</b>(AVAudioPlayer *)player <b>error:</b>(NSError *)error</i> | 音頻解碼發(fā)生錯誤 |
<h4>(2)AVAudioPlayer的使用:</h4>
- 初始化AVAudioPlayer對象土铺,此時通常指定本地文件路徑。
- 設(shè)置播放屬性板鬓,例如重復(fù)此時悲敷、音量大小等。
- 調(diào)用play方法播放俭令。
下面就是要AVAudioPlayer實現(xiàn)一個簡單播放器后德,在這個播放器中實現(xiàn)了播放、暫停抄腔、顯示播放進度瓢湃、調(diào)節(jié)音頻播放狀態(tài)功能,當(dāng)然例如調(diào)節(jié)音量赫蛇、設(shè)置循環(huán)模式绵患。甚至是聲波圖像等功能都可以實現(xiàn),這里不再一一演示悟耘。界面效果如下:
當(dāng)然由于AVAudioPlayer一次只能播放一個音頻文件落蝙,所有上一曲、下一曲其實可以通過創(chuàng)建多個播放器對象來完成暂幼,這里暫不實現(xiàn)掘殴。如果下一曲可以播放的話,通常下一曲功能可以在代理方法中觸發(fā)粟誓。為了不使其他關(guān)于鋪設(shè)界面的代碼干擾AVAudioPlayer的理解,下面只有核心代碼:
#import "ViewController.h"
#import "PlayToolBar.h"
#import "Masonry.h"
#import "UIImage+Common.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()<AVAudioPlayerDelegate>
@property (nonatomic, strong) UILabel *songWordLabel;
@property (nonatomic, strong) UISlider *slider;
@property (nonatomic, strong) UIButton *playBtn;
@property (nonatomic, strong) UILabel *currentTimeLabel;
@property (nonatomic, strong) UILabel *maxTimeLabel;
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;
@property (nonatomic, assign) NSTimer *timer;
@property (nonatomic, strong) NSMutableArray *songWordArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self handleData];
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
[self createSubviews];
[self layoutSubviewsByMasonry];
}
#pragma mark - button slider click
- (void)playAction:(UIButton *)sender
{
if (sender.tag == 100) {
[sender setBackgroundImage:[[UIImage imageNamed:@"mymusic_guess_like_pause"] imageWithColor:[UIColor colorWithRed:0.137 green:0.756 blue:0.49 alpha:1]] forState:UIControlStateNormal];
[self play];
sender.tag = 200;
} else {
[sender setBackgroundImage:[[UIImage imageNamed:@"play"] imageWithColor:[UIColor colorWithRed:0.137 green:0.756 blue:0.49 alpha:1]] forState:UIControlStateNormal];
[self pause];
sender.tag = 100;
}
}
/** slider 的點擊開始 */
- (void)sliderTouchBegin
{
if ([self.audioPlayer isPlaying]) {
_timer.fireDate = [NSDate distantFuture];
}
}
/** slider 的點擊結(jié)束 */
- (void)sliderSwipChangeValue
{
if ([self.audioPlayer isPlaying]) {
_audioPlayer.currentTime = _slider.value;
_timer.fireDate = [NSDate distantPast];
} else {
[_slider setValue:0 animated:YES];
}
}
#pragma mark - 定時器(懶加載)
- (NSTimer *)timer
{
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:YES];
}
return _timer;
}
- (void)updateProgress
{
CGFloat progress = _audioPlayer.currentTime / _audioPlayer.duration;
_currentTimeLabel.text = [self timeFormatted:self.audioPlayer.currentTime];
[_slider setValue:progress * _slider.maximumValue animated:YES];
NSInteger index = [_songWordArray indexOfObject:_currentTimeLabel.text];
if (index >= 0 && index < _songWordArray.count) {
_songWordLabel.text = _songWordArray[index + 1];
}
}
#pragma mark - 播放器
/** audio?Player 創(chuàng)建(懶加載) */
- (AVAudioPlayer *)audioPlayer
{
if (!_audioPlayer) {
NSString *path = [[NSBundle mainBundle]pathForResource:@"周杰倫 - 稻香" ofType:@"mp3"];
NSURL *url = [NSURL fileURLWithPath:path];
_audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
_audioPlayer.delegate = self;
_maxTimeLabel.text = [self timeFormatted:_audioPlayer.duration];
_slider.maximumValue = _audioPlayer.duration;
// 設(shè)置播放屬性
_audioPlayer.numberOfLoops = 0; // 不循環(huán)
[_audioPlayer prepareToPlay]; // 準(zhǔn)備播放起意,加載音頻文件到緩存
}
return _audioPlayer;
}
- (NSString *)timeFormatted:(float)timeInterval
{
int time = (int)timeInterval;
int seconds = time % 60;
int minutes = (time / 60) % 60;
int hours = time / 3600;
if (hours == 0) {
return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
}
return [NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
}
/** 播放 */
- (void)play
{
if (![self.audioPlayer isPlaying]) {
[self.audioPlayer play];
self.timer.fireDate = [NSDate distantPast];//回復(fù)定時器
}
}
- (void)pause
{
if ([self.audioPlayer isPlaying]) {
[self.audioPlayer pause];
self.timer.fireDate = [NSDate distantFuture];
}
}
/** audioPlayer delegate */
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
[_playBtn setBackgroundImage:[[UIImage imageNamed:@"play"] imageWithColor:[UIColor colorWithRed:0.137 green:0.756 blue:0.49 alpha:1]] forState:UIControlStateNormal];
[_timer finalize];
}
參照博文鏈接這里有更加詳細的介紹鹰服!