音頻 (三) : AudioToolbox

AudioToolbox概述

通過AudioToolbox框架缀棍,可以將短聲音注冊到system sound服務(wù)上摄狱,被注冊到system sound服務(wù)上的聲音稱之為system sounds欣鳖。它必須滿足下面幾個條件漫蛔。

(1).播放的時間不能超過30秒

(2).數(shù)據(jù)必須是PCM或者IMA4流格式

(3).必須被打包成下面三個格式之一:Core Audio Format (.caf), Waveform audio
(.wav), 或者Audio Interchange File (.aiff)

聲音文件必須放到設(shè)備的本地文件夾下面抛蚤。通過AudioServicesCreateSystemSoundID方法注冊這個聲音文件.

音效

AudioToolbox.framework是一套基于C語言的框架低淡,使用它來播放音效其本質(zhì)是將短音頻注冊到系統(tǒng)聲音
服務(wù)(System Sound Service)姓言。System Sound Service是一種簡單、底層的聲音播放服務(wù)蔗蹋,但是它本身也存在著一些限制:

音頻播放時間不能超過30s

數(shù)據(jù)必須是PCM或者IMA4格式

音頻文件必須打包成.caf何荚、.aif、.wav中的一種(注意這是官方文檔的說法猪杭,實(shí)際測試發(fā)現(xiàn)一些.mp3也可以播放)

使用System
Sound Service 播放音效的步驟如下:

調(diào)用AudioServicesCreateSystemSoundID(
CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函數(shù)獲得系統(tǒng)聲音ID餐塘。

如果需要監(jiān)聽播放完成操作,則使用AudioServicesAddSystemSoundCompletion(
SystemSoundID inSystemSoundID,
CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode,
AudioServicesSystemSoundCompletionProc inCompletionRoutine,
void* inClientData)方法注冊回調(diào)函數(shù)胁孙。

調(diào)用AudioServicesPlaySystemSound(SystemSoundID
inSystemSoundID) 或者AudioServicesPlayAlertSound(SystemSoundID
inSystemSoundID) 方法播放音效(后者帶有震動效果)唠倦。

用到了AudioToolbox這個音頻接口称鳞,總結(jié)下,希望對需要的朋友有幫助稠鼻。AudioToolbox這個庫是C的接口冈止,偏向于底層,用于在線流媒體音樂的播放候齿,可以調(diào)用該庫的相關(guān)接口自己封裝一個在線播放器類熙暴,AudioStreamer是老外封裝的一個播放器類,有興趣的朋友可以研究下慌盯。

其實(shí)IOS庫中有兩個可以播放在線音樂的播放器類周霉,AVPlayer和MPMusicPlayerController

這兩個做簡單的播放還不錯,但是如果要做專業(yè)的音樂播放項目亚皂,功能還不夠強(qiáng)大俱箱,例如:邊聽邊存、斷點(diǎn)續(xù)傳灭必、播放事件等等都無法滿足狞谱。一下是以前做的筆記,僅供參考:

1.png

音樂

如果播放較大的音頻或者要對音頻有精確的控制則System Sound Service可能就很難滿足實(shí)際需求了禁漓,通常這種情況會選擇使用AVFoundation.framework中的AVAudioPlayer來實(shí)現(xiàn)跟衅。AVAudioPlayer可以看成一個播放器,它支持多種音頻格式播歼,而且能夠進(jìn)行進(jìn)度伶跷、音量、播放速度等控制秘狞。首先簡單看一下AVAudioPlayer常用的屬性和方法:

屬性 說明

@property(readonly, getter=isPlaying) BOOL playing 是否正在播放叭莫,只讀

@property(readonly)
NSUInteger numberOfChannels 音頻聲道數(shù),只讀

@property(readonly)
NSTimeInterval duration 音頻時長

@property(readonly) NSURL
*url 音頻文件路徑烁试,只讀

@property(readonly)
NSData *data 音頻數(shù)據(jù)食寡,只讀

@property float pan 立體聲平衡,如果為-1.0則完全左聲道廓潜,如果0.0則左右聲道平衡抵皱,如果為1.0則完全為右聲道

@property float volume 音量大小,范圍0-1.0

@property BOOL enableRate
是否允許改變播放速率

@property float rate 播放速率辩蛋,范圍0.5-2.0呻畸,如果為1.0則正常播放,如果要修改播放速率則必須設(shè)置enableRate為YES

@property NSTimeInterval
currentTime 當(dāng)前播放時長

@property(readonly)
NSTimeInterval deviceCurrentTime 輸出設(shè)備播放音頻的時間悼院,注意如果播放中被暫停此時間也會繼續(xù)累加

@property NSInteger
numberOfLoops 循環(huán)播放次數(shù)伤为,如果為0則不循環(huán),如果小于0則無限循環(huán),大于0則表示循環(huán)次數(shù)

@property(readonly)
NSDictionary *settings 音頻播放設(shè)置信息绞愚,只讀

@property(getter=isMeteringEnabled)
BOOLmeteringEnabled 是否啟用音頻測量叙甸,默認(rèn)為NO,一旦啟用音頻測量可以通過updateMeters方法更新測量值

對象方法 說明

- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError
**)outError 使用文件URL初始化播放器位衩,注意這個URL不能是HTTP URL裆蒸,AVAudioPlayer不支持加載網(wǎng)絡(luò)媒體流,只能播放本地文件

- (instancetype)initWithData:(NSData *)data error:(NSError
**)outError 使用NSData初始化播放器糖驴,注意使用此方法時必須文件格式和文件后綴一致僚祷,否則出錯,所以相比此方法更推薦使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString
*)utiString error:(NSError **)outError方法進(jìn)行初始化

- (BOOL)prepareToPlay; 加載音頻文件到緩沖區(qū)贮缕,注意即使在播放之前音頻文件沒有加載到緩沖區(qū)程序也會隱式調(diào)用此方法辙谜。

- (BOOL)play; 播放音頻文件

- (BOOL)playAtTime:(NSTimeInterval)time 在指定的時間開始播放音頻

- (void)pause; 暫停播放

- (void)stop; 停止播放

- (void)updateMeters 更新音頻測量值,注意如果要更新音頻測量值必須設(shè)置meteringEnabled為YES感昼,通過音頻測量值可以即時獲得音頻分貝等信息

- (float)peakPowerForChannel:(NSUInteger)channelNumber; 獲得指定聲道的分貝峰值装哆,注意如果要獲得分貝峰值必須在此之前調(diào)用updateMeters方法

- (float)averagePowerForChannel:(NSUInteger)channelNumber 獲得指定聲道的分貝平均值,注意如果要獲得分貝平均值必須在此之前調(diào)用updateMeters方法

@property(nonatomic, copy) NSArray *channelAssignments 獲得或設(shè)置播放聲道

代理方法 說明

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 音頻播放完成

- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError
*)error 音頻解碼發(fā)生錯誤

AVAudioPlayer的使用比較簡單:

初始化AVAudioPlayer對象定嗓,此時通常指定本地文件路徑烂琴。

設(shè)置播放器屬性,例如重復(fù)次數(shù)蜕乡、音量大小等。

調(diào)用play方法播放梗夸。

下面就使用AVAudioPlayer實(shí)現(xiàn)一個簡單播放器层玲,在這個播放器中實(shí)現(xiàn)了播放、暫停反症、顯示播放進(jìn)度功能辛块,當(dāng)然例如調(diào)節(jié)音量、設(shè)置循環(huán)模式铅碍、甚至是聲波圖像(通過分析音頻分貝值)等功能都可以實(shí)現(xiàn)润绵,這里就不再一一演示。界面效果如下:


2.png

音頻會話

事實(shí)上上面的播放器還存在一些問題胞谈,例如通常我們看到的播放器即使退出到后臺也是可以播放的尘盼,而這個播放器如果退出到后臺它會自動暫停。如果要支持后臺播放需要做下面幾件事情:

1.設(shè)置后臺運(yùn)行模式:在plist文件中添加Required
background modes烦绳,并且設(shè)置item 0=App plays audio or
streams audio/video using AirPlay(其實(shí)可以直接通過Xcode在Project
Targets-Capabilities-Background Modes中設(shè)置)

3.png

2.設(shè)置AVAudioSession的類型為AVAudioSessionCategoryPlayback并且調(diào)用setActive::方法啟動會話卿捎。

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

3.為了能夠讓應(yīng)用退到后臺之后支持耳機(jī)控制,建議添加遠(yuǎn)程控制事件(這一步不是后臺播放必須的)
前 兩步是后臺播放所必須設(shè)置的径密,第三步主要用于接收遠(yuǎn)程事件午阵,這部分內(nèi)容之前的文章中有詳細(xì)介紹,如果這一步不設(shè)置雖讓也能夠在后臺播放享扔,但是無法獲得音頻 控制權(quán)(如果在使用當(dāng)前應(yīng)用之前使用其他播放器播放音樂的話底桂,此時如果按耳機(jī)播放鍵或者控制中心的播放按鈕則會播放前一個應(yīng)用的音頻)植袍,并且不能使用耳機(jī) 進(jìn)行音頻控制。第一步操作相信大家都很容易理解籽懦,如果應(yīng)用程序要允許運(yùn)行到后臺必須設(shè)置于个,正常情況下應(yīng)用如果進(jìn)入后臺會被掛起,通過該設(shè)置可以上應(yīng)用程序 繼續(xù)在后臺運(yùn)行猫十。但是第二步使用的AVAudioSession有必要進(jìn)行一下詳細(xì)的說明览濒。

在iOS中每個應(yīng)用都有一個音頻會話,這個會話 就通過AVAudioSession來表示拖云。AVAudioSession同樣存在于AVFoundation框架中贷笛,它是單例模式設(shè)計,通過 sharedInstance進(jìn)行訪問宙项。在使用Apple設(shè)備時大家會發(fā)現(xiàn)有些應(yīng)用只要打開其他音頻播放就會終止乏苦,而有些應(yīng)用卻可以和其他應(yīng)用同時播放, 在多種音頻環(huán)境中如何去控制播放的方式就是通過音頻會話來完成的尤筐。下面是音頻會話的幾種會話模式:

4.png.png

注意:是否遵循靜音鍵表示在播放過程中如果用戶通過硬件設(shè)置為靜音是否能關(guān)閉聲音汇荐。
擴(kuò)展--播放音樂庫中的音樂

眾所周知音樂是iOS的重要組成播放,無論是iPod盆繁、iTouch掀淘、iPhone還是iPad都可以在iTunes購買音樂或添加本地音樂到音樂庫中同步到你的iOS設(shè)備。在MediaPlayer.frameowork中有一個MPMusicPlayerController用于播放音樂庫中的音樂油昂。

下面先來看一下MPMusicPlayerController的常用屬性和方法:

屬性 說明

@property (nonatomic, readonly) MPMusicPlaybackState playbackState 播放器狀態(tài)革娄,枚舉類型:

MPMusicPlaybackStateStopped:停止播放
MPMusicPlaybackStatePlaying:正在播放

MPMusicPlaybackStatePaused:暫停播放

MPMusicPlaybackStateInterrupted:播放中斷

MPMusicPlaybackStateSeekingForward:向前查找

MPMusicPlaybackStateSeekingBackward:向后查找

 

@property (nonatomic)
MPMusicRepeatMode repeatMode 重復(fù)模式,枚舉類型:

MPMusicRepeatModeDefault:默認(rèn)模式冕碟,使用用戶的首選項(系統(tǒng)音樂程序設(shè)置)

MPMusicRepeatModeNone:不重復(fù)

MPMusicRepeatModeOne:單曲循環(huán)

MPMusicRepeatModeAll:在當(dāng)前列表內(nèi)循環(huán)

 

@property (nonatomic)
MPMusicShuffleMode shuffleMode 隨機(jī)播放模式拦惋,枚舉類型:

MPMusicShuffleModeDefault:默認(rèn)模式,使用用戶首選項(系統(tǒng)音樂程序設(shè)置)

MPMusicShuffleModeOff:不隨機(jī)播放

MPMusicShuffleModeSongs:按歌曲隨機(jī)播放

MPMusicShuffleModeAlbums:按專輯隨機(jī)播放

 

@property (nonatomic,
copy) MPMediaItem *nowPlayingItem 正在播放的音樂項

@property (nonatomic,
readonly) NSUInteger indexOfNowPlayingItem 當(dāng)前正在播放的音樂在播放隊列中的索引

@property(nonatomic,
readonly) BOOLisPreparedToPlay 是否準(zhǔn)好播放準(zhǔn)備

@property(nonatomic)
NSTimeInterval currentPlaybackTime 當(dāng)前已播放時間安寺,單位:秒

@property(nonatomic) float currentPlaybackRate 當(dāng)前播放速度厕妖,是一個播放速度倍率,0表示暫停播放挑庶,1代表正常速度

類方法說明
+(MPMusicPlayerController *)applicationMusicPlayer; 獲取應(yīng)用播放器言秸,注意此類播放器無法在后臺播放
+(MPMusicPlayerController *)systemMusicPlayer 獲取系統(tǒng)播放器,支持后臺播放

 

對象方法說明
- (void)setQueueWithQuery:(MPMediaQuery *)query 使用媒體隊列設(shè)置播放源媒體隊列
- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection 使用媒體項集合設(shè)置播放源媒體隊列

- (void)skipToNextItem 下一曲

- (void)skipToBeginning 從起始位置播放

- (void)skipToPreviousItem 上一曲

- (void)beginGeneratingPlaybackNotifications 開啟播放通知迎捺,注意不同于其他播放器井仰,MPMusicPlayerController要想獲得通知必須首先開啟,默認(rèn)情況無法獲得通知

- (void)endGeneratingPlaybackNotifications 關(guān)閉播放通知

- (void)prepareToPlay 做好播放準(zhǔn)備(加載音頻到緩沖區(qū))破加,在使用play方法播放時如果沒有做好準(zhǔn)備回自動調(diào)用該方法

- (void)play 開始播放

- (void)pause 暫停播放

- (void)stop 停止播放

- (void)beginSeekingForward 開始向前查找(快進(jìn))

- (void)beginSeekingBackward 開始向后查找(快退)

- (void)endSeeking 結(jié)束查找

通知說明

(注意:要想獲得MPMusicPlayerController通知必須首先調(diào)用beginGeneratingPlaybackNotifications開啟通知)

MPMusicPlayerControllerPlaybackStateDidChangeNotification 播放狀態(tài)改變

MPMusicPlayerControllerNowPlayingItemDidChangeNotification 當(dāng)前播放音頻改變

MPMusicPlayerControllerVolumeDidChangeNotification 聲音大小改變

MPMediaPlaybackIsPreparedToPlayDidChangeNotification 準(zhǔn)備好播放

MPMusicPlayerController有兩種播放器:applicationMusicPlayer和systemMusicPlayer俱恶,前者在應(yīng)用退出后音樂播放會自動停止,后者在應(yīng)用停止后不會退出播放狀態(tài)。

MPMusicPlayerController加載音樂不同于前面的AVAudioPlayer是通過一個文件路徑來加載合是,而是需要一個播放隊列了罪。在MPMusicPlayerController中提供了兩個方法來加載播放隊列:- (void)setQueueWithQuery:(MPMediaQuery *)query和- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection,正是由于它的播放音頻來源是一個隊列聪全,因此MPMusicPlayerController支持上一曲泊藕、下一曲等操作。

那么接下來的問題就是如何獲取MPMediaQueue或者M(jìn)PMediaItemCollection难礼?MPMediaQueue對象有一系列的類方法來獲得媒體隊列:

+ (MPMediaQuery *)albumsQuery;
+ (MPMediaQuery *)artistsQuery;
+ (MPMediaQuery *)songsQuery;
+ (MPMediaQuery *)playlistsQuery;
+ (MPMediaQuery *)podcastsQuery;
+ (MPMediaQuery *)audiobooksQuery;
+ (MPMediaQuery *)compilationsQuery;
+ (MPMediaQuery *)composersQuery;
+ (MPMediaQuery *)genresQuery;

有了這些方法娃圆,就可以很容易獲到歌曲、播放列表蛾茉、專輯媒體等媒體隊列了讼呢,這樣就可以通過:

- (void)setQueueWithQuery:(MPMediaQuery *)query方法設(shè)置音樂來源了。又或者得到MPMediaQueue之后創(chuàng)建MPMediaItemCollection谦炬,
使用-(void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection設(shè)置音樂來源悦屏。

有時候可能希望用戶自己來選擇要播放的音樂,這時可以使用MPMediaPickerController键思,它是一個視圖控制器础爬,類似于UIImagePickerController,選擇完播放來源后可以在其代理方法中獲得MPMediaItemCollection對象吼鳞。

無論是通過哪種方式獲得MPMusicPlayerController的媒體源看蚜,可能都希望將每個媒體的信息顯示出來,這時候可以通過MPMediaItem對象獲得赔桌。一個MPMediaItem代表一個媒體文件供炎,通過它可以訪問媒體標(biāo)題、專輯名稱纬乍、專輯封面、音樂時長等等裸卫。無論是MPMediaQueue還是MPMediaItemCollection都有一個items屬性仿贬,它是MPMediaItem數(shù)組,通過這個屬性可以獲得MPMediaItem對象墓贿。

音樂的選擇茧泪、播放、暫停聋袋、通知队伟、下一曲、上一曲功能幽勒,相信有了上面的概念嗜侮,代碼讀起來并不復(fù)雜(示例中是直接通過MPMeidaPicker進(jìn)行音樂選擇的,但是仍然提供了兩個方法getLocalMediaQuery和getLocalMediaItemCollection來演示如何直接通過MPMediaQueue獲得媒體隊列或媒體集合):

錄音

除了上面說的,在AVFoundation框架中還要一個AVAudioRecorder類專門處理錄音操作锈颗,它同樣支持多種音頻格式顷霹。與AVAudioPlayer類似,你完全可以將它看成是一個錄音機(jī)控制類击吱,下面是常用的屬性和方法:

屬性說明

@property(readonly, getter=isRecording) BOOL recording; 是否正在錄音淋淀,只讀

@property(readonly) NSURL *url 錄音文件地址,只讀

@property(readonly) NSDictionary *settings 錄音文件設(shè)置覆醇,只讀

@property(readonly) NSTimeInterval currentTime 錄音時長朵纷,只讀,注意僅僅在錄音狀態(tài)可用

@property(readonly) NSTimeInterval deviceCurrentTime 輸入設(shè)置的時間長度永脓,只讀袍辞,注意此屬性一直可訪問

@property(getter=isMeteringEnabled) BOOLmeteringEnabled; 是否啟用錄音測量,如果啟用錄音測量可以獲得錄音分貝等

數(shù)據(jù)信息

@property(nonatomic, copy) NSArray *channelAssignments 當(dāng)前錄音的通道

對象方法說明

- (instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError 錄音機(jī)對象初始化方法憨奸,注意其中的url必須是本地文件url革屠,settings是錄音格式、編碼等設(shè)置

- (BOOL)prepareToRecord 準(zhǔn)備錄音排宰,主要用于創(chuàng)建緩沖區(qū)似芝,如果不手動調(diào)用,在調(diào)用record錄音時也會自動調(diào)用

- (BOOL)record 開始錄音

- (BOOL)recordAtTime:(NSTimeInterval)time 在指定的時間開始錄音板甘,一般用于錄音暫停再恢復(fù)錄音

- (BOOL)recordForDuration:(NSTimeInterval) duration 按指定的時長開始錄音

- (BOOL)recordAtTime:(NSTimeInterval)time forDuration:(NSTimeInterval) duration
在指定的時間開始錄音党瓮,并指

定錄音時長

- (void)pause; 暫停錄音

- (void)stop; 停止錄音

- (BOOL)deleteRecording; 刪除錄音,注意要刪除錄音此時錄音機(jī)必須處于停止?fàn)顟B(tài)

- (void)updateMeters; 更新測量數(shù)據(jù)盐类,注意只有meteringEnabled為YES此方法才可用

- (float)peakPowerForChannel:(NSUInteger)channelNumber; 指定通道的測量峰值寞奸,注意只有調(diào)用完updateMeters才有值

- (float)averagePowerForChannel:(NSUInteger)channelNumber 指定通道的測量平均值,注意只有調(diào)用完updateMeters才有值

 

代理方法說明

- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder
successfully:(BOOL)flag 完成錄音

- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder
error:(NSError *)error 錄音編碼發(fā)生錯誤

AVAudioRecorder很多屬性和方法跟AVAudioPlayer都是類似的,但是它的創(chuàng)建有所不同在跳,在創(chuàng)建錄音機(jī)時除了指定路徑外還必須指定錄音設(shè)置信息枪萄,因為錄音機(jī)必須知道錄音文件的格式、采樣率猫妙、通道數(shù)瓷翻、每個采樣點(diǎn)的位數(shù)等信息,但是也并不是所有的信息都必須設(shè)置割坠,通常只需要幾個常用設(shè)置齐帚。關(guān)于錄音設(shè)置詳見幫助文檔中的“AV Foundation Audio Settings Constants”。

5.png

在這個示例中將實(shí)行一個完整的錄音控制彼哼,包括錄音对妄、暫停、恢復(fù)敢朱、停止剪菱,同時還會實(shí)時展示用戶錄音的聲音波動摩瞎,當(dāng)用戶點(diǎn)擊完停止按鈕還會自動播放錄音文件。程序的構(gòu)建主要分為以下幾步:
1. 設(shè)置音頻會話類型為AVAudioSessionCategoryPlayAndRecord琅豆,因為程序中牽扯到錄音和播放操作愉豺。
2. 創(chuàng)建錄音機(jī)AVAudioRecorder,指定錄音保存的路徑并且設(shè)置錄音屬性茫因,注意對于一般的錄音文件要求的采樣率蚪拦、位數(shù)并不高,需要適當(dāng)設(shè)置以保證錄音文件的大小和效果冻押。
3. 設(shè)置錄音機(jī)代理以便在錄音完成后播放錄音驰贷,打開錄音測量保證能夠?qū)崟r獲得錄音時的聲音強(qiáng)度。(注意聲音強(qiáng)度范圍-160到0,0代表最大輸入)
4. 創(chuàng)建音頻播放器AVAudioPlayer洛巢,用于在錄音完成之后播放錄音括袒。
5. 創(chuàng)建一個定時器以便實(shí)時刷新錄音測量值并更新錄音強(qiáng)度到UIProgressView中顯示。
添加錄音稿茉、暫停锹锰、恢復(fù)、停止操作漓库,需要注意錄音的恢復(fù)操作其實(shí)是有音頻會話管理的恃慧,恢復(fù)時只要再次調(diào)用record方法即可,無需手動管理恢復(fù)時間等渺蒿。

音頻隊列服務(wù)
大家應(yīng)該已經(jīng)注意到了痢士,無論是前面的錄音還是音頻播放均不支持網(wǎng)絡(luò)流媒體播放,當(dāng)然對于錄音來說這種需求可能不大茂装, 但是對于音頻播放來說有時候就很有必要了怠蹂。AVAudioPlayer只能播放本地文件,并且是一次性加載所以音頻數(shù)據(jù)少态,初始化 AVAudioPlayer時指定的URL也只能是File URL而不能是HTTP URL城侧。當(dāng)然,將音頻文件下載到本地然后再調(diào)用AVAudioPlayer來播放也是一種播放網(wǎng)絡(luò)音頻的辦法彼妻,但是這種方式最大的弊端就是必須等到整個音 頻播放完成才能播放嫌佑,而不能使用流式播放,這往往在實(shí)際開發(fā)中是不切實(shí)際的澳骤。那么在iOS中如何播放網(wǎng)絡(luò)流媒體呢歧强?就是使用AudioToolbox框架 中的音頻隊列服務(wù)Audio Queue Services澜薄。
使用音頻隊列服務(wù)完全可以做到音頻播放和錄制为肮,首先看一下錄音音頻服務(wù)隊列:

6.png

一個音頻服務(wù)隊列Audio Queue有三部分組成:
三個緩沖器Buffers:每個緩沖器都是一個存儲音頻數(shù)據(jù)的臨時倉庫。
一個緩沖隊列Buffer Queue:一個包含音頻緩沖器的有序隊列肤京。
一個回調(diào)Callback:一個自定義的隊列回調(diào)函數(shù)颊艳。
聲音通過輸入設(shè)備進(jìn)入緩沖隊列中茅特,首先填充第一個緩沖器;當(dāng)?shù)谝粋€緩沖器填充滿之后自動填充下一個緩沖器棋枕,同時會調(diào)用回調(diào)函數(shù)白修;在回調(diào)函數(shù)中需要將緩沖器中的音頻數(shù)據(jù)寫入磁盤,同時將緩沖器放回到緩沖隊列中以便重用重斑。下面是Apple官方關(guān)于音頻隊列服務(wù)的流程示意圖:

7.png

類似的兵睛,看一下音頻播放緩沖隊列,其組成部分和錄音緩沖隊列類似窥浪。


8.png

但 是在音頻播放緩沖隊列中祖很,回調(diào)函數(shù)調(diào)用的時機(jī)不同于音頻錄制緩沖隊列,流程剛好相反漾脂。將音頻讀取到緩沖器中假颇,一旦一個緩沖器填充滿之后就放到緩沖隊列中, 然后繼續(xù)填充其他緩沖器骨稿;當(dāng)開始播放時笨鸡,則從第一個緩沖器中讀取音頻進(jìn)行播放;一旦播放完之后就會觸發(fā)回調(diào)函數(shù)坦冠,開始播放下一個緩沖器中的音頻形耗,同時填充 第一個緩沖器放;填充滿之后再次放回到緩沖隊列蓝牲。下面是詳細(xì)的流程:

1.png

當(dāng) 然趟脂,要明白音頻隊列服務(wù)的原理并不難,問題是如何實(shí)現(xiàn)這個自定義的回調(diào)函數(shù)例衍,這其中我們有大量的工作要做昔期,控制播放狀態(tài)、處理異常中斷佛玄、進(jìn)行音頻編碼等 等硼一。由于牽扯內(nèi)容過多,而且不是本文目的梦抢,如果以后有時間將另開一篇文章重點(diǎn)介紹般贼,目前有很多第三方優(yōu)秀框架可以直接使用,例如AudioStreamer奥吩、FreeStreamer哼蛆。由于前者當(dāng)前只有非ARC版本,所以下面不妨使用FreeStreamer來簡單演示在線音頻播放的過程霞赫,當(dāng)然在使用之前要做如下準(zhǔn)備工作:
1.拷貝FreeStreamer中的Reachability.h腮介、Reachability.m和Common、astreamer兩個文件夾中的內(nèi)容到項目中端衰。
2.添加FreeStreamer使用的類庫:CFNetwork.framework叠洗、AudioToolbox.framework甘改、AVFoundation.framework
、libxml2.dylib灭抑、MediaPlayer.framework十艾。
3.如果引用libxml2.dylib編譯不通過,需要在Xcode的Targets-Build Settings-Header Build Path中添加$(SDKROOT)/usr/include/libxml2腾节。
4.將FreeStreamer中的FreeStreamerMobile-Prefix.pch文件添加到項目中并將Targets-Build Settings-Precompile Prefix Header設(shè)置為YES忘嫉,在Targets-Build Settings-Prefix Header設(shè)置為$(SRCROOT)/項目名稱/FreeStreamerMobile-Prefix.pch(因為Xcode6默認(rèn)沒有pch文件)
MPMoviePlayerController

在iOS中播放視頻可以使用MediaPlayer.framework種的MPMoviePlayerController類來完成,它支持本地視頻和網(wǎng)絡(luò)視頻播放案腺。這個類實(shí)現(xiàn)了MPMediaPlayback協(xié)議榄融,因此具備一般的播放器控制功能,例如播放救湖、暫停愧杯、停止等。但是MPMediaPlayerController自身并不是一個完整的視圖控制器鞋既,如果要在UI中展示視頻需要將view屬性添加到界面中力九。下面列出了MPMoviePlayerController的常用屬性和方法:


屬性 說明

@property (nonatomic, copy) NSURL *contentURL 播放媒體URL,這個URL可以是本地路徑邑闺,也可以是網(wǎng)絡(luò)路徑

@property (nonatomic,readonly) UIView *view 播放器視圖跌前,如果要顯示視頻必須將此視圖添加到控制器視圖中

@property (nonatomic,readonly) UIView *backgroundView 播放器背景視圖

@property (nonatomic,readonly) MPMoviePlaybackState playbackState 媒體播放狀態(tài),枚舉類型:

MPMoviePlaybackStateStopped:停止播放

MPMoviePlaybackStatePlaying:正在播放

MPMoviePlaybackStatePaused:暫停

MPMoviePlaybackStateInterrupted:中斷

MPMoviePlaybackStateSeekingForward:向前定位

MPMoviePlaybackStateSeekingBackward:向后定位

 
@property (nonatomic,readonly) MPMovieLoadState loadState 網(wǎng)絡(luò)媒體加載狀態(tài)陡舅,枚舉類型:

MPMovieLoadStateUnknown:位置類型

MPMovieLoadStatePlayable:

MPMovieLoadStatePlaythroughOK:這種狀態(tài)如果shouldAutoPlay為YES將自動播放

MPMovieLoadStateStalled:停滯狀態(tài)

@property (nonatomic)MPMovieControlStyle controlStyle 控制面板風(fēng)格抵乓,枚舉類型:

MPMovieControlStyleNone:無控制面板

MPMovieControlStyleEmbedded:嵌入視頻風(fēng)格

MPMovieControlStyleFullscreen:全屏

MPMovieControlStyleDefault:默認(rèn)風(fēng)格

 

@property (nonatomic)MPMovieRepeatMode repeatMode; 重復(fù)播放模式,枚舉類型:

MPMovieRepeatModeNone:不重復(fù)靶衍,默認(rèn)值

MPMovieRepeatModeOne:重復(fù)播放

 

@property (nonatomic) BOOLshouldAutoplay 當(dāng)網(wǎng)絡(luò)媒體緩存到一定數(shù)據(jù)時是否自動播放灾炭,默認(rèn)為YES

@property (nonatomic,
getter=isFullscreen) BOOLfullscreen 是否全屏展示,默認(rèn)為NO颅眶,注意如果要通過此屬性設(shè)置全屏必須在視圖顯示完成后設(shè)置蜈出,否則無效

 

@property (nonatomic)
MPMovieScalingMode scalingMode 視頻縮放填充模式,枚舉類型:

MPMovieScalingModeNone:不進(jìn)行任何縮放

MPMovieScalingModeAspectFit:固定縮放比例并且盡量全部展示視頻涛酗,不會裁切視頻

MPMovieScalingModeAspectFill:固定縮放比例并填充滿整個視圖展示铡原,可能會裁切視頻

MPMovieScalingModeFill:不固定縮放比例壓縮填充整個視圖,視頻不會被裁切但是比例失衡

 

@property (nonatomic,readonly) BOOLreadyForDisplay 是否有相關(guān)媒體被播放

@property (nonatomic,readonly) MPMovieMediaTypeMask movieMediaTypes 媒體類別商叹,枚舉類型:

MPMovieMediaTypeMaskNone:未知類型

MPMovieMediaTypeMaskVideo:視頻

MPMovieMediaTypeMaskAudio:音頻

 

@property (nonatomic)MPMovieSourceType movieSourceType 媒體源燕刻,枚舉類型:

MPMovieSourceTypeUnknown:未知來源

MPMovieSourceTypeFile:本地文件

MPMovieSourceTypeStreaming:流媒體(直播或點(diǎn)播)

 

@property (nonatomic,readonly) NSTimeInterval duration 媒體時長,如果未知則返回0

@property (nonatomic,readonly) NSTimeInterval playableDuration 媒體可播放時長剖笙,主要用于表示網(wǎng)絡(luò)媒體已下載視頻時長

@property (nonatomic,readonly) CGSize naturalSize 視頻實(shí)際尺寸卵洗,如果未知則返回CGSizeZero

@property (nonatomic)NSTimeInterval initialPlaybackTime 起始播放時間

@property (nonatomic)NSTimeInterval endPlaybackTime 終止播放時間

@property (nonatomic) BOOL allowsAirPlay 是否允許無線播放,默認(rèn)為YES

@property (nonatomic,readonly, getter=isAirPlayVideoActive) BOOL airPlayVideoActive 當(dāng)前媒體是否正在通過AirPlay播放

@property(nonatomic,readonly) BOOLisPreparedToPlay 是否準(zhǔn)備好播放

@property(nonatomic)NSTimeInterval currentPlaybackTime 當(dāng)前播放時間枯途,單位:秒

@property(nonatomic) float currentPlaybackRate 當(dāng)前播放速度忌怎,如果暫停則為0,正常速度為1.0酪夷,非0數(shù)據(jù)表示倍率

 

對象方法 說明

- (instancetype)initWithContentURL:(NSURL *)url 使用指定的URL初始化媒體播放控制器對象

- (void)setFullscreen:(BOOL)fullscreen animated:(BOOL)animated 設(shè)置視頻全屏榴啸,注意如果要通過此方法設(shè)置全屏則必須在其視圖顯示之后設(shè)置,否則無效

- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes
timeOption:(MPMovieTimeOption)option 獲取在指定播放時間的視頻縮略圖晚岭,第一個參數(shù)是獲取縮略圖的時間點(diǎn)數(shù)組鸥印;第二個參數(shù)代表時間點(diǎn)精度,枚舉類型:

MPMovieTimeOptionNearestKeyFrame:時間點(diǎn)附近

MPMovieTimeOptionExact:準(zhǔn)確時間

- (void)cancelAllThumbnailImageRequests 取消所有縮略圖獲取請求

- (void)prepareToPlay 準(zhǔn)備播放坦报,加載視頻數(shù)據(jù)到緩存库说,當(dāng)調(diào)用play方法時如果沒有準(zhǔn)備好會自動調(diào)用此方法

- (void)play 開始播放

- (void)pause 暫停播放

- (void)stop 停止播放

- (void)beginSeekingForward 向前定位

- (void)beginSeekingBackward 向后定位

- (void)endSeeking 停止快進(jìn)/快退

 

通知 說明

MPMoviePlayerScalingModeDidChangeNotification
視頻縮放填充模式發(fā)生改變

MPMoviePlayerPlaybackDidFinishNotification
媒體播放完成或用戶手動退出,具體完成原因可以通過通知userInfo中的key為MPMoviePlayerPlaybackDidFinishReasonUserInfoKey的對象獲取

MPMoviePlayerPlaybackStateDidChangeNotification
播放狀態(tài)改變片择,可配合playbakcState屬性獲取具體狀態(tài)

MPMoviePlayerLoadStateDidChangeNotification
媒體網(wǎng)絡(luò)加載狀態(tài)改變

MPMoviePlayerNowPlayingMovieDidChangeNotification
當(dāng)前播放的媒體內(nèi)容發(fā)生改變

MPMoviePlayerWillEnterFullscreenNotification
將要進(jìn)入全屏

MPMoviePlayerDidEnterFullscreenNotification
進(jìn)入全屏后

MPMoviePlayerWillExitFullscreenNotification
將要退出全屏

MPMoviePlayerDidExitFullscreenNotification
退出全屏后

MPMoviePlayerIsAirPlayVideoActiveDidChangeNotification
當(dāng)媒體開始通過AirPlay播放或者結(jié)束AirPlay播放

MPMoviePlayerReadyForDisplayDidChangeNotification
視頻顯示狀態(tài)改變

MPMovieMediaTypesAvailableNotification
確定了媒體可用類型后

MPMovieSourceTypeAvailableNotification
確定了媒體來源后

MPMovieDurationAvailableNotification
確定了媒體播放時長后

MPMovieNaturalSizeAvailableNotification
確定了媒體的實(shí)際尺寸后

MPMoviePlayerThumbnailImageRequestDidFinishNotification
縮略圖請求完成之后

MPMediaPlaybackIsPreparedToPlayDidChangeNotification
做好播放準(zhǔn)備后

注意MPMediaPlayerController的狀態(tài)等信息并不是通過代理來和外界交互的潜的,而是通過通知中心,因此從上面的列表中可以看到常用的一 些通知字管。由于MPMoviePlayerController本身對于媒體播放做了深度的封裝啰挪,使用起來就相當(dāng)簡單:創(chuàng)建 MPMoviePlayerController對象,設(shè)置frame屬性嘲叔,將MPMoviePlayerController的view添加到控制器視圖中亡呵。

擴(kuò)展--使用AVFoundation生成縮略圖
通過前面的方法大家應(yīng)該已經(jīng)看到,使用MPMoviePlayerController來生成縮略圖足夠簡單硫戈,但是如果僅僅是是為了生成縮略圖而不 進(jìn)行視頻播放的話锰什,此刻使用MPMoviePlayerController就有點(diǎn)大材小用了。其實(shí)使用AVFundation框架中的 AVAssetImageGenerator就可以獲取視頻縮略圖丁逝。使用AVAssetImageGenerator獲取縮略圖大致分為三個步驟:
1. 創(chuàng)建AVURLAsset對象(此類主要用于獲取媒體信息汁胆,包括視頻、聲音等)霜幼。
2. 根據(jù)AVURLAsset創(chuàng)建AVAssetImageGenerator對象沦泌。
3. 使用AVAssetImageGenerator的copyCGImageAtTime::方法獲得指定時間點(diǎn)的截圖。

MPMoviePlayerViewController
其實(shí)MPMoviePlayerController如果不作為嵌入視 頻來播放(例如在新聞中嵌入一個視頻)辛掠,通常在播放時都是占滿一個屏幕的谢谦,特別是在iPhone、iTouch上萝衩。因此從iOS3.2以后蘋果也在思考既 然MPMoviePlayerController在使用時通常都是將其視圖view添加到另外一個視圖控制器中作為子視圖回挽,那么何不直接創(chuàng)建一個控制器 視圖內(nèi)部創(chuàng)建一個MPMoviePlayerController屬性并且默認(rèn)全屏播放,開發(fā)者在開發(fā)的時候直接使用這個視圖控制器猩谊。這個內(nèi)部有一個 MPMoviePlayerController的視圖控制器就是MPMoviePlayerViewController千劈,它繼承于 UIViewController。MPMoviePlayerViewController內(nèi)部多了一個moviePlayer屬性和一個帶有url的 初始化方法牌捷,同時它內(nèi)部實(shí)現(xiàn)了一些作為模態(tài)視圖展示所特有的功能墙牌,例如默認(rèn)是全屏模式展示涡驮、彈出后自動播放、作為模態(tài)窗口展示時如果點(diǎn)擊“Done”按鈕 會自動退出模態(tài)窗口等喜滨。在下面的示例中就不直接將播放器放到主視圖控制器捉捅,而是放到一個模態(tài)視圖控制器中,簡單演示 MPMoviePlayerViewController的使用虽风。

這里需要強(qiáng)調(diào)一下棒口,由于MPMoviePlayerViewController的初始化方法做了大量工作(例如設(shè)置URL、自動播放辜膝、添加點(diǎn)擊Done 完成的監(jiān)控等)痴怨,所以當(dāng)再次點(diǎn)擊播放彈出新的模態(tài)窗口的時如果不銷毀之前的MPMoviePlayerViewController税课,那么新的對象就無法 完成初始化,這樣也就不能再次進(jìn)行播放。

AVPlayer
MPMoviePlayerController足夠強(qiáng)大赞哗,幾乎不用寫幾行代碼就能完成一個播放器洋丐,但是正是由 于它的高度封裝使得要自定義這個播放器變得很復(fù)雜借卧,甚至是不可能完成泄隔。例如有些時候需要自定義播放器的樣式,那么如果要使用 MPMoviePlayerController就不合適了耕蝉,如果要對視頻有自由的控制則可以使用AVPlayer崔梗。AVPlayer存在于 AVFoundation中,它更加接近于底層垒在,所以靈活性也更強(qiáng):

2.png

AVPlayer 本身并不能顯示視頻蒜魄,而且它也不像MPMoviePlayerController有一個view屬性。如果AVPlayer要顯示必須創(chuàng)建一個播放器層 AVPlayerLayer用于展示场躯,播放器層繼承于CALayer谈为,有了AVPlayerLayer之添加到控制器視圖的layer中即可。要使用 AVPlayer首先了解一下幾個常用的類:
AVAsset:主要用于獲取多媒體信息踢关,是一個抽象類伞鲫,不能直接使用。
AVURLAsset:AVAsset的子類签舞,可以根據(jù)一個URL路徑創(chuàng)建一個包含媒體信息的AVURLAsset對象秕脓。
AVPlayerItem:一個媒體資源管理對象,管理者視頻的一些基本信息和狀態(tài)儒搭,一個AVPlayerItem對應(yīng)著一個視頻資源吠架。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搂鲫,隨后出現(xiàn)的幾起案子傍药,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拐辽,死亡現(xiàn)場離奇詭異拣挪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)俱诸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門菠劝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乙埃,你說我怎么就攤上這事【忉” “怎么了介袜?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長出吹。 經(jīng)常有香客問我遇伞,道長,這世上最難降的妖魔是什么捶牢? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任鸠珠,我火速辦了婚禮,結(jié)果婚禮上秋麸,老公的妹妹穿的比我還像新娘渐排。我一直安慰自己,他們只是感情好灸蟆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布驯耻。 她就那樣靜靜地躺著,像睡著了一般炒考。 火紅的嫁衣襯著肌膚如雪可缚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天斋枢,我揣著相機(jī)與錄音帘靡,去河邊找鬼。 笑死瓤帚,一個胖子當(dāng)著我的面吹牛描姚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戈次,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轰胁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了朝扼?” 一聲冷哼從身側(cè)響起赃阀,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后榛斯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體观游,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年驮俗,在試婚紗的時候發(fā)現(xiàn)自己被綠了懂缕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡王凑,死狀恐怖搪柑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情索烹,我是刑警寧澤工碾,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站百姓,受9級特大地震影響渊额,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垒拢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一旬迹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧求类,春花似錦奔垦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仓技,卻和暖如春鸵贬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脖捻。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工阔逼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人地沮。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓嗜浮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親摩疑。 傳聞我的和親對象是個殘疾皇子危融,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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