ios 音頻開發(fā)

最近接觸到新項目里的音頻業(yè)務哭当,根據(jù)這幾天的整理猪腕,總結(jié)一點內(nèi)容,方便記錄钦勘。后續(xù)不斷更新陋葡。。彻采。

在iOS程序中脖岛,音頻播放隨處可見,有的聲音只有1秒颊亮,有的聲音好幾分鐘 。iOS支持的音頻格式AAC陨溅、ALAC终惑、IMA4、linear门扇、MP3雹有。

AVAudioPlayer

AVAudioPlayer類用于回放音頻數(shù)據(jù)。是一個易于使用的類臼寄,它提供了大量的功能霸奕。使用該類可以實現(xiàn)音頻的載入,播放吉拳,暫停质帅,停止。需要加入AVFoundation.framework框架留攒,在使用的類中引入<AVFoundation/AVFoundation.h>煤惩。

  • AVAudioPlayer只能播放本地音樂文件

可以通過音頻的NSData或者本地音頻文件的url,來創(chuàng)建一個AVAudioPlayer實例炼邀,如加載本地的music.mp3的音頻文件:

NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"music" withExtension:@"mp3"];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];

if (self.player) {
    [self.player prepareToPlay];
}

加載音頻文件后魄揉,可以調(diào)用prepareToPlay方法,這樣可以提前獲取需要的硬件支持拭宁,并加載音頻到緩沖區(qū)洛退。在調(diào)用play方法時,減少開始播放的延遲杰标。

當調(diào)用play方法后兵怯,開始播放音樂:

[self.player play];

可以調(diào)用pause或stop來暫停播放,這里的stop方法的效果也只是暫停播放在旱,不同之處是stop會撤銷prepareToPlay方法所做的準備摇零。

[self.player stop];

另外,我們可以進行更多的操作:

單獨設置音樂的音量(默認1.0,可設置范圍為0.0至1.0驻仅,兩個極端為靜音谅畅、系統(tǒng)音量):

self.player.volume = 0.5;

修改左右聲道的平衡(默認0.0,可設置范圍為-1.0至1.0噪服,兩個極端分別為只有左聲道毡泻、只有右聲道):

self.player.pan = -1;

設置播放速度(默認1.0,可設置范圍為0.5至2.0粘优,兩個極端分別為一半速度仇味、兩倍速度):

self.player.rate = 0.5;

設置循環(huán)播放(默認1,若設置值大于0雹顺,則為相應的循環(huán)次數(shù)丹墨,設置為-1可以實現(xiàn)無限循環(huán)):

self.player.numberOfLoops = -1;

AVPlayer

支持播放本地、分步下載嬉愧、或在線流媒體音視頻贩挣,不僅可以播放音頻,配合AVPlayerLayer類可實現(xiàn)視頻播放没酣。另外支持播放進度監(jiān)聽王财。

  • AVPLayer:可以用來播放在線及本地音視頻
  • AVAudioSession:音頻會話,主要用來管理音頻設置與硬件交互
    使用時需要導入

1.AVPlayer需要通過AVPlayerItem來關(guān)聯(lián)需要播放的媒體裕便。

  #import <AVFoundation/AVFoundation.h>
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:urlStr]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];
  • 2.在準備播放前绒净,通過KVO添加播放狀態(tài)改變監(jiān)聽

[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

處理KVO回調(diào)事件:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"status"]) {
        switch (self.player.status) {
            case AVPlayerStatusUnknown:
            {
                NSLog(@"未知轉(zhuǎn)態(tài)");
            }
                break;
            case AVPlayerStatusReadyToPlay:
            {
                NSLog(@"準備播放");
            }
                break;
            case AVPlayerStatusFailed:
            {
                NSLog(@"加載失敗");
            }
                break;

            default:
                break;
        }

    }
}
  • 3.KVO監(jiān)聽音樂緩沖狀態(tài):
[self.player.currentItem addObserver:self
                          forKeyPath:@"loadedTimeRanges"
                             options:NSKeyValueObservingOptionNew
                             context:nil];



-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context

{
    //...

    if ([keyPath isEqualToString:@"loadedTimeRanges"]) {

        NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;
        //本次緩沖的時間范圍
        CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
        //緩沖總長度
        NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
        //音樂的總時間
        NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);
        //計算緩沖百分比例
        NSTimeInterval scale = totalLoadTime/duration;
        //更新緩沖進度條
        //        self.loadTimeProgress.progress = scale;
    }
}
  • 4.開始播放后,通過KVO添加播放結(jié)束事件監(jiān)聽
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(playFinished:)
                                             name:AVPlayerItemDidPlayToEndTimeNotification
                                           object:_player.currentItem];
  • 5.開始播放時偿衰,通過AVPlayer的方法監(jiān)聽播放進度挂疆,并更新進度條(定期監(jiān)聽的方法):
__weak typeof(self) weakSelf = self;
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
    //當前播放的時間
    float current = CMTimeGetSeconds(time);
    //總時間
    float total = CMTimeGetSeconds(item.duration);
    if (current) {
        float progress = current / total;
        //更新播放進度條
        weakSelf.playSlider.value = progress;
    }
}];
  • 6.用戶拖動進度條,修改播放進度
- (void)playSliderValueChange:(UISlider *)sender
{
    //根據(jù)值計算時間
    float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);
    //跳轉(zhuǎn)到當前指定時間
    [self.player seekToTime:CMTimeMake(time, 1)];
}

后臺播放

AVAudioSession中配置選項:

  • AVAudioSessionCategory
1.AVAudioSessionCategoryAmbient
當前App的播放聲音可以和其他app播放的聲音共存哎垦,當鎖屏或按靜音時停止囱嫩。

2.AVAudioSessionCategorySoloAmbient
只能播放當前App的聲音,其他app的聲音會停止漏设,當鎖屏或按靜音時停止墨闲。

3.AVAudioSessionCategoryPlayback
只能播放當前App的聲音,其他app的聲音會停止郑口,當鎖屏或按靜音時不會停止鸳碧。

4.AVAudioSessionCategoryRecord
只能用于錄音,其他app的聲音會停止犬性,當鎖屏或按靜音時不會停止

5.AVAudioSessionCategoryPlayAndRecord
在錄音的同時播放其他聲音瞻离,當鎖屏或按靜音時不會停止
可用于聽筒播放,比如微信語音消息聽筒播放

6.AVAudioSessionCategoryAudioProcessing
使用硬件解碼器處理音頻乒裆,該音頻會話使用期間套利,不能播放或錄音

7.AVAudioSessionCategoryMultiRoute
多種音頻輸入輸出,例如可以耳機、USB設備同時播放等
  • AVAudioSessionCategoryOptions
1.AVAudioSessionModeDefault
默認的模式肉迫,適用于所有的場景验辞,可用于場景還原

2.AVAudioSessionModeVoiceChat
適用類別 :
AVAudioSessionCategoryPlayAndRecord 
應用場景VoIP

3.AVAudioSessionModeGameChat
適用類別:
AVAudioSessionCategoryPlayAndRecord 
應用場景游戲錄制,由GKVoiceChat自動設置喊衫,無需手動調(diào)用

4.AVAudioSessionModeVideoRecording
適用類別:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryRecord 
應用場景視頻錄制

5.AVAudioSessionModeMoviePlayback
適用類別:
AVAudioSessionCategoryPlayBack
應用場景視頻播放

6.AVAudioSessionModeVideoChat
適用類別:
AVAudioSessionCategoryPlayAndRecord
應用場景視頻通話

7.AVAudioSessionModeMeasurement
適用類別:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryRecord
AVAudioSessionCategoryPlayback
AVAudioSessionModeSpokenAudio
  • AVAudioSessionMode
1.AVAudioSessionCategoryOptionMixWithOthers
適用于:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute 
用于可以和其他app進行混音

2.AVAudioSessionCategoryOptionDuckOthers
適用于:
AVAudioSessionCategoryAmbient
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute
用于壓低其他聲音播放的音量跌造,使期音量變小

3.AVAudioSessionCategoryOptionAllowBluetooth
適用于:
AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord
用于是否支持藍牙設備耳機等

4.AVAudioSessionCategoryOptionDefaultToSpeaker
適用于:
AVAudioSessionCategoryPlayAndRecord
用于將聲音從Speaker播放,外放族购,即免提

5.AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
適用于:
AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryPlayback
AVAudioSessionCategoryMultiRoute

6.AVAudioSessionCategoryOptionAllowBluetoothA2DP
適用于:
AVAudioSessionCategoryPlayAndRecord
藍牙和a2dp

7.AVAudioSessionCategoryOptionAllowAirPlay
適用于:
AVAudioSessionCategoryPlayAndRecord
airplay

系統(tǒng)中斷響應

上面說的這些Category啊壳贪、Option啊以及Mode都是對自己作為播放主體時的表現(xiàn),但是假設寝杖,現(xiàn)在正在播放著违施,突然來電話了、鬧鐘響了或者你在后臺放歌但是用戶啟動其他App用上面的方法影響的時候瑟幕,我們的App該如何表現(xiàn)呢醉拓?最常用的場景當然是先暫停,待恢復的時候再繼續(xù)收苏。那我們的App要如何感知到這個終端以及何時恢復呢?

AVAudioSession提供了多種Notifications來進行此類狀況的通知愤兵。其中將來電話鹿霸、鬧鈴響等都歸結(jié)為一般性的中斷,用
AVAudioSessionInterruptionNotification來通知秆乳。其回調(diào)回來的userInfo主要包含兩個鍵:

AVAudioSessionInterruptionTypeKey: 取值為AVAudioSessionInterruptionTypeBegan表示中斷開始懦鼠,我們應該暫停播放和采集

取值為AVAudioSessionInterruptionTypeEnded表示中斷結(jié)束,我們可以繼續(xù)播放和采集屹堰。

AVAudioSessionInterruptionOptionKey: 當前只有一種值AVAudioSessionInterruptionOptionShouldResume表示此時也應該恢復繼續(xù)播放和采集肛冶。
而將其他App占據(jù)AudioSession的時候用AVAudioSessionSilenceSecondaryAudioHintNotification來進行通知。其回調(diào)回來的userInfo鍵為:

AVAudioSessionSilenceSecondaryAudioHintTypeKey
可能包含的值:
AVAudioSessionSilenceSecondaryAudioHintTypeBegin: 表示其他App開始占據(jù)Session
AVAudioSessionSilenceSecondaryAudioHintTypeEnd: 表示其他App開始釋放Session

首先在AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:方法中添加代碼:

AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];

然后在Info.plist文件中添加:

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>

鎖屏信息

  • 在每次準備播放下一首時扯键,更新鎖屏信息:
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
    MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"封面圖片"]];
    infoCenter.nowPlayingInfo = @{
                                  MPMediaItemPropertyTitle :@"歌曲名",
                                  MPMediaItemPropertyArtist :@"歌手名",
                                  MPMediaItemPropertyPlaybackDuration :歌曲時間長度,
                                  MPNowPlayingInfoPropertyElapsedPlaybackTime : @(已播放時間長度),
                                  MPMediaItemPropertyArtwork : artwork
                                  };

通過耳機睦袖、鎖屏界面控制

  • 在需要處理遠程控制事件的具體控制器或其它類中調(diào)用下面這個方法
 #import <MediaPlayer/MPRemoteCommandCenter.h>
 #import <MediaPlayer/MPRemoteCommand.h>
- (void)remoteControlEventHandler
{
    // 直接使用sharedCommandCenter來獲取MPRemoteCommandCenter的shared實例
    MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];

// 啟用播放命令 (鎖屏界面和上拉快捷功能菜單處的播放按鈕觸發(fā)的命令)
commandCenter.playCommand.enabled = YES;
// 為播放命令添加響應事件, 在點擊后觸發(fā)
[commandCenter.playCommand addTarget:self action:@selector(playAction:)];

// 播放, 暫停, 上下曲的命令默認都是啟用狀態(tài), 即enabled默認為YES
// 為暫停, 上一曲, 下一曲分別添加對應的響應事件
[commandCenter.pauseCommand addTarget:self action:@selector(pauseAction:)];
[commandCenter.previousTrackCommand addTarget:self action:@selector(previousTrackAction:)];
[commandCenter.nextTrackCommand addTarget:self action:@selector(nextTrackAction:)];

// 啟用耳機的播放/暫停命令 (耳機上的播放按鈕觸發(fā)的命令)
commandCenter.togglePlayPauseCommand.enabled = YES;
// 為耳機的按鈕操作添加相關(guān)的響應事件
[commandCenter.togglePlayPauseCommand addTarget:self action:@selector(playOrPauseAction:)];
}

-(void)playAction:(id)obj{
    [[HYPlayerTool sharePlayerTool] play];
}
-(void)pauseAction:(id)obj{
    [[HYPlayerTool sharePlayerTool] pause];
}
-(void)nextTrackAction:(id)obj{
    [[HYPlayerTool sharePlayerTool] playNext];
}
-(void)previousTrackAction:(id)obj{
    [[HYPlayerTool sharePlayerTool] playPre];
}
-(void)playOrPauseAction:(id)obj{
    if ([[HYPlayerTool sharePlayerTool] isPlaying]) {
        [[HYPlayerTool sharePlayerTool] pause];
    }else{
        [[HYPlayerTool sharePlayerTool] play];
    }
}

AVQueuePlayer

AVPlayer只支持單個媒體資源的播放,我們可以使用AVPlayer的子類AVQueuePlayer實現(xiàn)列表播放荣刑。在AVPlayer的基礎上馅笙,增加以下方法:

//通過給定的AVPlayerItem數(shù)組創(chuàng)建一個AVQueuePlayer實例
+ (instancetype)queuePlayerWithItems:(NSArray<AVPlayerItem *> *)items;

//通過給定的AVPlayerItem數(shù)組初始化AVQueuePlayer實例
- (AVQueuePlayer *)initWithItems:(NSArray<AVPlayerItem *> *)items;

//獲得當前的播放隊列數(shù)組
- (NSArray<AVPlayerItem *> *)items;

//停止播放當前音樂,并播放隊列中的下一首
- (void)advanceToNextItem;

//往播放隊列中插入新的AVPlayerItem
- (void)insertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem;

//從播放隊列中移除指定AVPlayerItem
- (void)removeItem:(AVPlayerItem *)item;

//清空播放隊列
- (void)removeAllItems;
  • 官方API中沒找到播放上一首的方法厉亏,所以其實直接用AVPlayer做列表播放也是可以的董习,自己維護一個播放列表數(shù)組,監(jiān)聽用戶點擊上一首和下一首按鈕爱只,并監(jiān)聽播放結(jié)束事件皿淋,調(diào)用 AVPlayer 實例的replaceCurrentItemWithPlayerItem:方法傳入播放列表中的上一首或下一首就行。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窝趣,隨后出現(xiàn)的幾起案子疯暑,更是在濱河造成了極大的恐慌,老刑警劉巖高帖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缰儿,死亡現(xiàn)場離奇詭異,居然都是意外死亡散址,警方通過查閱死者的電腦和手機乖阵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來预麸,“玉大人瞪浸,你說我怎么就攤上這事±艋觯” “怎么了对蒲?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贡翘。 經(jīng)常有香客問我蹈矮,道長,這世上最難降的妖魔是什么鸣驱? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任泛鸟,我火速辦了婚禮,結(jié)果婚禮上踊东,老公的妹妹穿的比我還像新娘北滥。我一直安慰自己,他們只是感情好闸翅,可當我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布再芋。 她就那樣靜靜地躺著,像睡著了一般坚冀。 火紅的嫁衣襯著肌膚如雪济赎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天记某,我揣著相機與錄音联喘,去河邊找鬼。 笑死辙纬,一個胖子當著我的面吹牛豁遭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贺拣,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蓖谢,長吁一口氣:“原來是場噩夢啊……” “哼捂蕴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起闪幽,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤啥辨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盯腌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溉知,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年腕够,在試婚紗的時候發(fā)現(xiàn)自己被綠了级乍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡帚湘,死狀恐怖玫荣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情大诸,我是刑警寧澤捅厂,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站资柔,受9級特大地震影響焙贷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贿堰,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一盈厘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧官边,春花似錦、人聲如沸外遇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽傅瞻。三九已至泡孩,卻和暖如春俭厚,著一層夾襖步出監(jiān)牢的瞬間摸柄,已是汗流浹背谎砾。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工春瞬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惫确,地道東北人山上。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓眼耀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親佩憾。 傳聞我的和親對象是個殘疾皇子哮伟,可洞房花燭夜當晚...
    茶點故事閱讀 45,876評論 2 361

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