iOS開發(fā):本地音頻播放

<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];
}

參照博文鏈接這里有更加詳細的介紹鹰服!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悲酷,更是在濱河造成了極大的恐慌套菜,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件设易,死亡現(xiàn)場離奇詭異逗柴,居然都是意外死亡,警方通過查閱死者的電腦和手機顿肺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門戏溺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屠尊,你說我怎么就攤上這事旷祸。” “怎么了讼昆?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵托享,是天一觀的道長。 經(jīng)常有香客問我浸赫,道長闰围,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任既峡,我火速辦了婚禮羡榴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涧狮。我一直安慰自己炕矮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布者冤。 她就那樣靜靜地躺著肤视,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涉枫。 梳的紋絲不亂的頭發(fā)上邢滑,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音愿汰,去河邊找鬼困后。 笑死,一個胖子當(dāng)著我的面吹牛衬廷,可吹牛的內(nèi)容都是我干的摇予。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吗跋,長吁一口氣:“原來是場噩夢啊……” “哼侧戴!你這毒婦竟也來了宁昭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤酗宋,失蹤者是張志新(化名)和其女友劉穎积仗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜕猫,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡寂曹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了回右。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隆圆。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖楣黍,靈堂內(nèi)的尸體忽然破棺而出匾灶,到底是詐尸還是另有隱情,我是刑警寧澤租漂,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布阶女,位于F島的核電站,受9級特大地震影響哩治,放射性物質(zhì)發(fā)生泄漏秃踩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一业筏、第九天 我趴在偏房一處隱蔽的房頂上張望憔杨。 院中可真熱鬧,春花似錦蒜胖、人聲如沸消别。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寻狂。三九已至,卻和暖如春朋沮,著一層夾襖步出監(jiān)牢的瞬間蛇券,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工樊拓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纠亚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓筋夏,卻偏偏與公主長得像蒂胞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子条篷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

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