Android車載應(yīng)用開發(fā)與分析(6)- 車載多媒體(一)- 音視頻基礎(chǔ)知識與MediaPlayer

多媒體應(yīng)用是車載信息娛樂系統(tǒng)的一個重要組成部分,一般包含音視頻播放、收音機、相冊等。車載應(yīng)用多媒體系列初步計劃分為六篇,這是第一篇。

參考資料
視頻和視頻幀:視頻和幀基礎(chǔ)知識整理
百度百科 - 聲道百度百科 - 量化精度
管理音頻焦點 | Android 開發(fā)者 | Android Developers
Android音視頻開發(fā) - 何俊林
MediaPlayer | Android Developers
MediaPlayer 概覽 | Android 開發(fā)者 | Android Developers

1. 音視頻基礎(chǔ)知識

1.1 視頻編碼

視頻編碼就是指通過特定的壓縮技術(shù),將某個視頻格式文件轉(zhuǎn)換成另一種視頻格式文件的方式。
視頻編碼分為以下兩個系列:
MPEG系列:由ISO[國際標準化組織]下屬的MPEG[動態(tài)影像專家組]開發(fā)。視頻編碼方面主要是MPEG1(VCD使用)、MPEG2(DVD使用)、MPEG4(DVD RIP使用的都是它的變種,如DivX、XviD等)、MPEG4 AVC(目前最常用)。其還有音頻編碼方面,主要有MPEG Audio Layer1/2、MPEG Audio Layer 3(MP3)、MPEG-2 AAC、MPEG4-AAC等。DVD音頻沒有采用MPEG。
H.26X系列:由ITU[國際電傳視訊聯(lián)盟]主導(dǎo),側(cè)重網(wǎng)絡(luò)傳輸,但只有視頻編碼。包括H.261、H.262、H.263、H.263+、H.263++、H.264(與MPEG4 AVC合作的產(chǎn)物)。

1.2 音頻編碼

常見的音頻編碼格式有AAC、MP3、AC3。
AAC:AAC,全稱Advanced Audio Coding,是一種專為聲音數(shù)據(jù)設(shè)計的文件壓縮格式。與MP3不同,它采用了全新的算法進行編碼,更加高效,具有更高的“性價比”。利用AAC格式,可使人感覺聲音質(zhì)量沒有明顯降低的前提下,更加小巧。蘋果ipod、諾基亞手機支持AAC格式的音頻文件。優(yōu)點:相較于mp3,AAC格式的音質(zhì)更佳,文件更小。不足:AAC屬于有損壓縮的格式,與時下流行的APE、FLAC等無損格式相比音質(zhì)存在“本質(zhì)上”的差距。加之,傳輸速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC頭上“小巧”的光環(huán)不復(fù)存在。
MP3:MP3是一種音頻壓縮技術(shù),其全稱是動態(tài)影像專家壓縮標準音頻層面3(Moving Picture Experts Group Audio Layer III),簡稱為MP3。它被設(shè)計用來大幅度地降低音頻數(shù)據(jù)量。利用 MPEG Audio Layer 3 的技術(shù),將音樂以1:10 甚至 1:12 的壓縮率,壓縮成容量較小的文件,而對于大多數(shù)用戶來說重放的音質(zhì)與最初的不壓縮音頻相比沒有明顯的下降。
MP3是利用人耳對高頻聲音信號不敏感的特性,將時域波形信號轉(zhuǎn)換成頻域信號,并劃分成多個頻段,對不同的頻段使用不同的壓縮率,對高頻加大壓縮比(甚至忽略信號)對低頻信號使用小壓縮比,保證信號不失真。這樣一來就相當于拋棄人耳基本聽不到的高頻聲音, [1] 只保留能聽到的低頻部分,從而將聲音用1∶10甚至1∶12的壓縮率壓縮。由于這種壓縮方式的全稱叫MPEG Audio Player3,所以人們把它簡稱為MP3。
根據(jù)MPEG規(guī)范的說法,MPEG-4中的AAC(Advanced audio coding)將是MP3格式的下一代。
最高參數(shù)的MP3(320Kbps)的音質(zhì)較之CD的,FLAC和APE無損壓縮格式的差別不多,其優(yōu)點是壓縮后占用空間小,適用于移動設(shè)備的存儲和使用。
AC3:全稱Audio Coding3(音頻編碼3)是杜比數(shù)碼(Dolby Digital)的同義詞,杜比數(shù)碼是一種高級音頻壓縮技術(shù),它最多可以對6個比特率最高為448kbps的單獨聲道進行編碼。杜比AC-3提供的環(huán)繞聲系統(tǒng)由5個全頻域聲道和1個超低音聲道組成,被稱為5.1聲道。5個聲道包括左前、中央、右前、左后、右后。低音聲道主要提供一些額外的低音信息,使一些場景,如爆炸、撞擊等聲音效果更好。

1.3 多媒體播放組件

在Android系統(tǒng)中多媒體播放組件包含MediaPlayer、MediaCodec、OMX、StageFright、AudioTrack 。

  • MediaPlayer:將系統(tǒng)提供的解碼器封裝后提供給應(yīng)用開發(fā)者使用音視頻播放組件,一般支持多種多媒體格式。
  • MediaCodec:音視頻編解碼 。
  • OMX:多媒體部分采用的編解碼標準。
  • Stagefright:Stagefright是位于原生層的媒體播放引擎,內(nèi)置了基于軟件的編解碼器,可用于處理熱門媒體格式。它是用來替代之前OpenCore框架,主要做了一層OMX層,僅僅對OpenCore的omx-component部分做了引用。Stagefright是在MediaPlayerService這一層加入的,和OpenCore是并列的。StageFright在Android中以共享庫的形式存在(libstagefright.so),其中的Module - NuPlayer/AwesomePlayer可用來播放音視頻。NuPlayer/AwesomePlayer提供了許多的API,可以讓上層的應(yīng)用程序(Java/JNI)調(diào)用。
  • AudioTrack:音頻播放組件,與MediaPlayer不同的是,AudioTrack僅支持非壓縮編碼(PCM)的音頻。

1.4 音視頻中的專業(yè)術(shù)語

1.4.1 幀率

幀率(frame rate)是用于測量顯示幀數(shù)的度量。測量單位為“每秒顯示幀數(shù)”(frame per secondFPS)或“赫茲”,一般來說FPS用于描述視頻、電子繪圖或游戲每秒播放多少幀。
每秒的幀數(shù)(fps)或者說幀率表示圖形處理器處理場時每秒鐘能夠更新的次數(shù)。高的幀率可以得到更流暢、更逼真的動畫。一般來說30fps就是可以接受的,但是將性能提升至60fps則可以明顯提升交互感和逼真感,但是一般來說超過75fps一般就不容易察覺到有明顯的流暢度提升了。如果幀率超過屏幕刷新率只會浪費圖形處理的能力,因為監(jiān)視器不能以這么快的速度更新,這樣超過刷新率的幀率就浪費掉了。

1.4.2 分辨率

視頻分辨率,是用于度量圖像內(nèi)數(shù)據(jù)量多少的一個參數(shù),通常表示成ppi。
視頻的320X180是指它在橫向和縱向上的有效像素,窗口小時ppi值較高,看起來清晰;窗口放大時,由于沒有那么多有效像素填充窗口,有效像素ppi值下降,就模糊了。

1.4.3 刷新率

刷新率是指電子束對屏幕上的圖像重復(fù)掃描的次數(shù)。刷新率越高,所顯示的圖像(畫面)穩(wěn)定性就越好。
刷新率分為垂直刷新率和水平刷新率,一般提到的刷新率通常指垂直刷新率。垂直刷新率表示屏幕的圖像每秒鐘重繪多少次,也就是每秒鐘屏幕刷新的次數(shù),以Hz(赫茲)為單位。刷新率越高越好,圖像就越穩(wěn)定,圖像顯示就越自然清晰,對眼睛的影響也越小。刷新頻率越低,圖像閃爍和抖動的就越厲害,眼睛疲勞得就越快。一般來說,如能達到80Hz以上的刷新頻率就可完全消除圖像閃爍和抖動感,眼睛也不會太容易疲勞。

1.4.4 編碼格式

編碼的目的就是指通過特定的壓縮技術(shù),將某個視頻格式的文件轉(zhuǎn)換成另一種視頻格式文件的方式,主要目標是壓縮數(shù)據(jù)量。常用的編碼格式有MPEG和H.26X兩種。

1.4.5 封裝格式

封裝格式(也叫容器),就是將已經(jīng)編碼壓縮好的視頻軌和音頻軌按照一定的格式放到一個文件中,也就是說僅僅是一個外殼。常見的封裝格式有:
AVI:微軟在90年代初創(chuàng)立的封裝標準,是當時為對抗quicktime格式(mov)而推出的,只能支持固定CBR恒定比特率編碼的聲音文件。
FLV:針對于h.263家族的格式。
MKV:萬能封裝器,有良好的兼容和跨平臺性、糾錯性,可帶 外掛字幕。
MOV:MOV是Quicktime封裝。
MP4:主要應(yīng)用于mpeg4的封裝 。
RM/RMVB:Real Video,由RealNetworks開發(fā)的應(yīng)用于rmvb和rm 。
TS/PS:PS封裝只能在HDDVD原版。
WMV:微軟推出的,作為市場競爭。

1.4.6 碼率

比特率又稱“二進制位速率”,俗稱“碼率”。表示單位時間內(nèi)傳送比特的數(shù)目。用于衡量數(shù)字信息的傳送速度,常寫作bit/sec。根據(jù)每幀圖像存儲時所占的比特數(shù)和傳輸比特率,可以計算數(shù)字圖像信息傳輸?shù)乃俣?。碼率越高,消耗的帶寬越多。
文件大小(b) = 碼率(b/s) X 時長(s)

1.4.7 畫質(zhì)&碼率

通常我們有一個錯誤的認識,碼率越大畫質(zhì)越好!實際上視頻質(zhì)量和碼率、編碼算法都有關(guān)系。

1.4.8 DTS&PTS

DTS:解碼時間標簽(Decoding Time Stamp)。主要用于標識讀入內(nèi)存中的比特流在什么時候開始送入解碼器中進行解碼。
PTS:演示時間標簽(Presentation Time Stamp)。主要用于度量解碼后的視頻幀什么時候被顯示出來。

1.4.9 YUV&RGB

YUV:是一種顏色編碼方法。YUV是編譯true-color顏色空間(color space)的種類,Y'UV, YUV, YCbCr,YPbPr等專有名詞都可以稱為YUV,彼此有重疊?!癥”表示明亮度(Luminance或Luma),也就是灰階值,“U”和“V”表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度,用于指定像素的顏色。
RGB:RGB色彩模式是工業(yè)界的一種顏色標準,是通過對紅(R)、綠(G)、藍(B)三個顏色通道的變化以及它們相互之間的疊加來得到各式各樣的顏色的,RGB即是代表紅、綠、藍三個通道的顏色,這個標準幾乎包括了人類視力所能感知的所有顏色,是運用最廣的顏色系統(tǒng)之一。

1.4.10 視頻幀&音頻幀

常見的視頻幀有I、P、B幀等。

  • I幀,Intra Picture,又稱幀內(nèi)編碼幀,俗稱關(guān)鍵幀。一般來說I幀不需要依賴前后幀信息,可獨立進行解碼。
  • P幀,predictive-frame,又稱前向預(yù)測編碼幀,也有幀間預(yù)測編碼幀。顧名思義,P幀需要依賴前面的I幀或者P幀才能進行編解碼,因為一般來說,P幀存儲的是當前幀畫面與前一幀(前一幀可能是I幀也可能是P幀)的差別,較專業(yè)的說法是壓縮了時間冗余信息,或者說提取了運動特性。P幀的壓縮率約在20左右,幾乎所有的H264編碼流都帶有大量的P幀
  • B幀bi-directional interpolatedprediction frame,又稱雙向預(yù)測內(nèi)插編碼幀,簡稱雙向預(yù)測編碼幀B幀非常特殊,它存儲的是本幀與前后幀的差別,因此帶有B幀的視頻在解碼時的邏輯會更復(fù)雜些,CPU開銷會更大。因此,不是所有的視頻都帶有B幀,不過,B幀的壓縮率能夠達到50甚至更高,在壓縮率指標上還是很可觀的。

音頻幀的概念沒有視頻幀那么清晰,音頻幀與編碼格式有關(guān):

  • 對于PCM(脈沖編碼調(diào)制,非壓縮編碼)來說,它不需要幀的概念,根據(jù)采樣率和采樣精度就可以播放。
  • AMR幀比較簡單,它規(guī)定每20ms的音頻就是1幀,每1幀的音頻都是獨立的,有可能采用不同的編碼算法以及不同的編碼參數(shù)。
  • MP3幀較為復(fù)雜一些,包含了更多信息,比如采樣率、比特率等各種參數(shù)。具體如下:音頻數(shù)據(jù)幀個數(shù)由文件大小和幀長決定,每一幀的長度可能不固定,也可能固定,由比特率決定,每一幀又分為幀頭和數(shù)據(jù)實體兩部分,幀頭記錄了MP3的比特率、采樣率、版本等信息,每一幀之間相互獨立。

1.4.11 量化精度

量化精度,是指可以將模擬信號分成多少個等級,量化精度越高,越接近原波形。

1.4.12 采樣率

采樣頻率,也稱為采樣速度或者采樣率,定義了單位時間內(nèi)從連續(xù)信號中提取并組成離散信號的采樣個數(shù),它用赫茲(Hz)來表示。采樣頻率的倒數(shù)是采樣周期或者叫做采樣時間,它是采樣之間的時間間隔。通俗的講采樣頻率是指計算機單位時間內(nèi)能夠采集多少個信號樣本。

1.4.13 聲道

聲道(Sound Channel) 是指聲音在錄制或播放時在不同空間位置采集或回放的相互獨立的音頻信號,所以聲道數(shù)也就是聲音錄制時的音源數(shù)量或回放時相應(yīng)的揚聲器數(shù)量。
立體聲道:單聲道缺乏對聲音的位置定位,而立體聲技術(shù)則徹底改變了這一狀況。聲音在錄制過程中被分配到兩個獨立的聲道,從而達到了很好的聲音定位效果。這種技術(shù)在音樂欣賞中顯得尤為有用,聽眾可以分辨出各種樂器來自的方向,從而使音樂更富想象力,更加接近于臨場感受。立體聲技術(shù)廣泛運用于自Sound Blaster Pro以后的大量聲卡,成為了影響深遠的一個音頻標準。時至今日,立體聲依然是許多產(chǎn)品遵循的技術(shù)標準。
4聲道:四聲道環(huán)繞規(guī)定了4個發(fā)音點:前左、前右,后左、后右,聽眾則被包圍在這中間。同時還建議增加一個低音音箱,以加強對低頻信號的回放處理(這也就是如今4.1聲道音箱系統(tǒng)廣泛流行的原因)。就整體效果而言,四聲道系統(tǒng)可以為聽眾帶來來自多個不同方向的聲音環(huán)繞,可以獲得身臨各種不同環(huán)境的聽覺感受,給用戶以全新的體驗。如今四聲道技術(shù)已經(jīng)廣泛融入于各類中高檔聲卡的設(shè)計中,成為未來發(fā)展的主流趨勢。
5.1聲道:5.1聲道已廣泛運用于各類傳統(tǒng)影院和家庭影院中,一些比較知名的聲音錄制壓縮格式,譬如杜比AC-3(Dolby Digital)、DTS等都是以5.1聲音系統(tǒng)為技術(shù)藍本的,其中“.1”聲道,則是一個專門設(shè)計的超低音聲道,這一聲道可以產(chǎn)生頻響范圍20~120Hz的超低音。其實5.1聲音系統(tǒng)來源于4.1環(huán)繞,不同之處在于它增加了一個中置單元。這個中置單元負責傳送低于80Hz的聲音信號,在欣賞影片時有利于加強人聲,把對話集中在整個聲場的中部,以增加整體效果。相信每一個真正體驗過Dolby AC-3音效的朋友都會為5.1聲道所折服。


7.1聲道:7.1聲道系統(tǒng)的作用簡單來說就是在聽者的周圍建立起一套前后聲場相對平衡的聲場,不同于5.1聲道聲場的是,它在原有的基礎(chǔ)上增加了后中聲場聲道,同時它也不同于普通6.1聲道聲場,因為 [2] 7.1聲道有雙路后中置,而這雙路后中置的最大作用就是為了防止聽者因為沒有坐在皇帝位而在聽覺上產(chǎn)生聲場的偏差。

2.系統(tǒng)播放器 - MediaPlayer

MediaPlayer是Android系統(tǒng)中的一個多媒體播放類,通過它能控制音視頻流或本地音視頻資源的播放過程。在車載音視頻應(yīng)用開發(fā)中,許多時候我們會直接采用MediaPlayer,當然可以使用ExoPlayer,這個挖個坑以后再講。

2.1 MediaPlayer的狀態(tài)與生命周期

音頻/視頻文件和流的播放控制作為狀態(tài)機進行管理。下圖顯示了由支持的播放控制操作驅(qū)動的 MediaPlayer 對象的生命周期和狀態(tài)(這張圖很重要,使用MediaPlayer開發(fā)音視頻應(yīng)用時,務(wù)必參考)。
橢圓形表示 MediaPlayer 對象可能駐留的狀態(tài)?;”硎掘?qū)動對象狀態(tài)過渡的播放控制操作。有兩種類型的弧。具有單個箭頭的弧表示同步方法調(diào)用,而具有雙箭頭的弧表示異步方法調(diào)用。


從此狀態(tài)圖中,可以看到 MediaPlayer 對象具有以下狀態(tài):

  • Idel 和 End 狀態(tài)
    當創(chuàng)建MediaPlayer實例或調(diào)用reset()后,它處于Idle(空閑/就緒)狀態(tài);調(diào)用release()之后,它處于End(結(jié)束)狀態(tài)。在Idle和End之間的狀態(tài)就是**MediaPlayer**對象的生命周期。
  • Error 狀態(tài)
    在創(chuàng)建一個新的MediaPlayer的實例或調(diào)用reset()后,此時MediaPlayer尚處于Idle狀態(tài),如果此時調(diào)用getCurrentPosition()、getDuration()、getVideoHeight()、getVideoWidth()、setAudioAttributes(android.media.AudioAttributes)、setLooping(boolean)、setVolume(float, float)、pause()、start()、stop()、seekTo(long, int)、prepare()、prepareAsync()程序就會出錯,并觸發(fā)setOnErrorListener設(shè)定的OnErrorListener.onError,然后MediaPlayer的生命周期就會進入Error狀態(tài)。
    通常,某些播放控制操作可能會由于各種原因而失敗,例如音頻/視頻格式不受支持、音頻/視頻交錯不良、分辨率過高、流超時等。因此,在這些情況下,錯誤報告和恢復(fù)是一個重要的問題。有時,由于編程錯誤,還可能發(fā)生在無效狀態(tài)下調(diào)用播放控制操作的情況。在所有這些錯誤條件下,都會觸發(fā)setOnErrorListener設(shè)定的OnErrorListener.onError,MediaPlayer的生命周期同樣會進入Error狀態(tài)。
    為了重新使用MediaPlayer,調(diào)用reset()可以將MediaPlayer恢復(fù)到Idle狀態(tài)。
  • End 狀態(tài)
    MediaPlayer會占用寶貴的系統(tǒng)資源。因此,應(yīng)該始終采取額外的預(yù)防措施,確保 MediaPlayer對象保留的時間不會過長。調(diào)用 release()可以確保分配給MediaPlayer的系統(tǒng)資源得到釋放。此時MediaPlayer的生命周期則會進入End(結(jié)束)狀態(tài)。
    MediaPlayer處于End狀態(tài)時,它就不能再使用了,也無法再進入其它的生命狀態(tài)。
  • Initialized 狀態(tài)
    處于Idle狀態(tài)時,調(diào)用setDataSource(java.io.FileDescriptor)setDataSource(java.lang.String)、setDataSource(android.content.Context, android.net.Uri)、setDataSource(java.io.FileDescriptor, long, long)、setDataSource(android.media.MediaDataSource)中的任意方法,MediaPlayer會進入Initialized(已初始化)狀態(tài)。
    如果在非Idle狀態(tài)調(diào)用setDataSource(),會引發(fā)IllegalStateException。
    重載setDataSource(),需要拋出IllegalArgumentException、IOException
  • Prepared 狀態(tài)
    MediaPlayer對象必須先進入Prepared(已準備)狀態(tài),然后才能開始播放。
    有兩種途徑到達Prepared狀態(tài)。一種是調(diào)用prepare(),這是一種同步方法,由于prepare本身是耗時操作,雖然有時候它執(zhí)行的很快,但也不要在主線程執(zhí)行它,否則可能導(dǎo)致ANR。另一種方式是調(diào)用prepareAsync(),它是一種異步方法,可以在主線程中執(zhí)行。調(diào)用prepareAsync()并不會立即進入Prepared狀態(tài),而是先進入Preparing狀態(tài),最后到達Prepared狀態(tài)。需要注意Preparing是一種瞬間狀態(tài),存在時間很短通過注冊setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)可以監(jiān)聽MediaPlayerPrepare狀態(tài)。
    Prepared狀態(tài)下,VolumescreenOnWhilePlaying 、Looping等屬性已經(jīng)可以通過調(diào)用相應(yīng)的set 方法來調(diào)整了。
    如果在非Initialized狀態(tài)調(diào)用prepare() / prepareAsync(),會引發(fā)IllegalStateException。
  • Started狀態(tài)
    在播放之前必須調(diào)用start()并成功返回,此時MediaPlayer狀態(tài)進入Started(已啟動)狀態(tài),通過注冊setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener)可以保持跟蹤音視頻流的buffering(緩沖)status。
    MediaPlayer處于Started狀態(tài)時,可以通過調(diào)用seekTo(long, int)來調(diào)整播放位置。盡管異步調(diào)用會立即返回,但實際的尋道操作可能需要一段時間才能完成,特別是對于正在流式傳輸?shù)囊纛l/視頻。當實際的尋道操作完成時,如果事先注冊了setOnSeekCompleteListener(android.media.MediaPlayer.OnSeekCompleteListener),則內(nèi)部播放器引擎會回調(diào)OnSeekComplete.onSeekComplete()。
    此外,可以通過調(diào)用getCurrentPosition()來檢索實際的當前播放位置,這對于需要跟蹤播放進度的應(yīng)用程序(如 Music 播放器)非常有用。
  • Paused狀態(tài)
    在音視頻播放過程中調(diào)用pause()方法,MediaPlayer的狀態(tài)從Started狀態(tài)進入Paused(已暫停)狀態(tài),這個過程是瞬間的且在播放器內(nèi)部是異步的。反之,從Paused通過調(diào)用start()返回Started也是同樣的。在狀態(tài)更新并調(diào)用isPlaying()方法前,將有一些耗時,對流數(shù)據(jù)可能需要耗費數(shù)秒。
    MediaPlayer處于Paused,此時調(diào)用seekTo(long, int)來調(diào)整播放位置時,如果數(shù)據(jù)流具有視頻并且請求的位置有效,則將顯示一個視頻幀。
  • Stopped狀態(tài)
    當調(diào)用stop()方法時,MediaPlayer無論是處于StartedPaused、Prepared還是PlaybackCompleted中哪種狀態(tài),都將進入Stoped(已停止)狀態(tài)。一旦進入Stoped狀態(tài),playback將不能開始,直到調(diào)用prepare() / prepareAsync(),且處于Prepared狀態(tài)才可以開始。
  • PlaybackCompleted狀態(tài)
    當MediaPlayer播放到數(shù)據(jù)流末尾時,一次播放完成。如果事先setLooping(true)MediaPlayer依然處于Started狀態(tài),并重新開始播放。如果實現(xiàn)setLooping(false),如果事先注冊了setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener),就會回調(diào) OnCompletion.onCompletion(),表示MediaPlayer進入PlaybackCompleted狀態(tài)。處于PlaybackCompleted時調(diào)用start()方法會重啟播放器從頭開始播放數(shù)據(jù),此時狀態(tài)進入Started。
    MediaPlayer處于PlaybackCompleted,此時調(diào)用seekTo(long, int)來調(diào)整播放位置時,如果數(shù)據(jù)流具有視頻并且請求的位置有效,則將顯示一個視頻幀。

2.2 MediaPlayer 簡略使用

2.2.1 權(quán)限聲明

在開始使用MediaPlayer開發(fā)應(yīng)用之前,還需要在manifest中添加適當?shù)穆暶?,這樣才能使用相關(guān)功能。

  • 互聯(lián)網(wǎng)權(quán)限 - 如果需要使用MediaPlayer播放基于網(wǎng)絡(luò)的內(nèi)容,則必須申請網(wǎng)絡(luò)訪問權(quán)限。
<uses-permission android:name="android.permission.INTERNET" />
  • 喚醒鎖定權(quán)限 - 如果播放器應(yīng)用需要防止屏幕變暗或處理器進入休眠狀態(tài),或者要使用
    MediaPlayer.setScreenOnWhilePlaying()MediaPlayer.setWakeMode() 方法,則必須申請此權(quán)限。
<uses-permission android:name="android.permission.WAKE_LOCK" />

2.2.2 播放資源

MediaPlayer支持多種不同的媒體源,例如:

  • 本地資源(打包在應(yīng)用中的資源)
  • 內(nèi)部 URI,例如,從ContentProvider那獲取的 URI
  • 外部網(wǎng)址(流式傳輸)
    以下示例展示了如何播放作為本地原始資源(保存在應(yīng)用的 res/raw/ 目錄中)提供的音頻:
var mediaPlayer: MediaPlayer? = MediaPlayer.create(context, R.raw.sound_file_1)
mediaPlayer?.start() // 不需要調(diào)用prepare,因為create中已經(jīng)替我們做好了

在本例中,“原始”資源是指系統(tǒng)不會嘗試以任何特定方式解析的文件。不過,該資源的內(nèi)容不應(yīng)為原始音頻。它應(yīng)該是采用某種支持的格式且經(jīng)過適當編碼和格式化的媒體文件。
播放系統(tǒng)中本地可用的 URI(例如,可以通過ContentProvider獲?。┲械膬?nèi)容方法如下:

val resUri: Uri = .... // 本地uri
val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
    setAudioStreamType(AudioManager.STREAM_MUSIC)
    setDataSource(applicationContext, resUri)
    prepare()
    start()
}

通過 HTTP 流式傳輸并播放遠程網(wǎng)址上的內(nèi)容如下所示:

val url = "http://........" // 網(wǎng)絡(luò)url
val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
    setAudioStreamType(AudioManager.STREAM_MUSIC)
    setDataSource(url)
    prepare() // 耗時操作,可能導(dǎo)致ANR
    start()
}

注意 :
如果是播放在線媒體文件,則該文件必須能夠進行漸進式下載。
使用 setDataSource() 時,必須捕獲或傳遞 IllegalArgumentException 和 IOException,因為引用的文件可能并不存在。

2.3 后臺播放

如果希望即使當應(yīng)用未在屏幕上顯示時,應(yīng)用仍會在后臺播放媒體內(nèi)容,則必須啟動一個Service并由此控制 MediaPlayer 實例。

2.4 喚醒鎖定

當設(shè)計在后臺播放媒體內(nèi)容的應(yīng)用時,手機等移動設(shè)備可能會在Service運行時進入休眠狀態(tài),車載設(shè)備則無法確定,目前尚無統(tǒng)一的規(guī)范,每個主機廠的策略可能都不相同。
在原生系統(tǒng)中通過調(diào)用setWakeMode()可以保持喚醒鎖定,完成該操作后,MediaPlayer 會在播放時保持指定的鎖定狀態(tài),并在暫停或停止播放時釋放鎖定。

mediaPlayer = MediaPlayer().apply {
// 省略其它代碼
setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

不過,播放流媒體時,還需要保持wifi的鎖定狀態(tài),否則可能出現(xiàn)wifi中斷的現(xiàn)象。

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")
wifiLock.acquire()

當不再需要網(wǎng)絡(luò)時,通過如下方式釋放該鎖定。

wifiLock.release()

2.5 數(shù)字版權(quán)管理 (DRM)

從 Android 8.0(API 26)開始,MediaPlayer 包含支持播放受 DRM 保護的資料的 API。這些 API 與由 MediaDrm 提供的低級別 API 類似,但前者是在較高級別運行,并且不會公開底層提取器、DRM 和加密對象。
車載多媒體中出現(xiàn)DRM的情況可能不多(我還沒遇到過),如有需要可以參考Android官方的文檔:MediaPlayer 概覽 | Android 開發(fā)者 | Android Developers

3. 管理音頻焦點

以下內(nèi)容選自管理音頻焦點 | Android 開發(fā)者 | Android Developers 有增刪。

兩個或兩個以上的 Android 應(yīng)用可同時向同一輸出流播放音頻。系統(tǒng)會將所有音頻流混合在一起。雖然這是一項出色的技術(shù),但卻會給用戶帶來很大的困擾。為了避免所有音樂應(yīng)用同時播放,Android 引入了“音頻焦點”的概念。 一次只能有一個應(yīng)用獲得音頻焦點。
所以當多媒體應(yīng)用需要輸出音頻時,它需要請求音頻焦點,順利獲取后,才可以播放聲音。當其它應(yīng)用請求音頻焦點時,Android系統(tǒng)會根據(jù)內(nèi)部仲裁表中定義的優(yōu)先級決定,該應(yīng)用能否獲取音頻焦點。例如:在用戶使用藍牙電話應(yīng)用時,該應(yīng)用會獲取音頻焦點,收音機等音頻優(yōu)先級較低的應(yīng)用就會失去音頻焦點。

音頻焦點的管理在Android系統(tǒng)中不是強制的,即使應(yīng)用失去音頻焦點,也是可以輸出音頻的。但是在車載多媒體應(yīng)用開發(fā)時務(wù)必不能這么做,因為多數(shù)駕駛員會同時使用收音機和地圖導(dǎo)航,如果導(dǎo)航提示音被收音機的音頻壓制,就極有可能造成駕駛偏航甚至是交通事故??!

3.1 音頻焦點管理準則

多媒體應(yīng)用一般會遵守以下的準則來管理音頻焦點:

  • 在即將開始播放之前調(diào)用 requestAudioFocus(),并驗證調(diào)用是否返回 AUDIOFOCUS_REQUEST_GRANTED
  • 在其他應(yīng)用獲得音頻焦點時,停止或暫停播放,或降低音量。
  • 播放停止后,放棄音頻焦點。

3.2 Android 8.0 及更高版本中的音頻焦點

因為現(xiàn)有車載IVI系統(tǒng)絕大多數(shù)已經(jīng)升級到Android 9.0甚至是更高的版本,所以Android 8.0之前的音頻焦點獲取方式,本文不再介紹。

從 Android 8.0(API 26)開始,調(diào)用requestAudioFocus()時,必須提供AudioFocusRequest參數(shù)。要釋放音頻焦點,請調(diào)用 abandonAudioFocusRequest() 方法,該方法也接受 AudioFocusRequest 作為參數(shù)。在請求和放棄焦點時,應(yīng)使用相同的 AudioFocusRequest 實例。要創(chuàng)建 AudioFocusRequest,請使用 AudioFocusRequest.Builder。由于焦點請求始終必須指定請求的類型,因此此類型會包含在構(gòu)建器的構(gòu)造函數(shù)中。使用構(gòu)建器的方法來設(shè)置請求的其他字段。
FocusGain 字段為必需字段;所有其他字段均為可選字段。

方法 備注
setFocusGain() 每個請求中都必須包含此字段。此字段的值與 Android 8.0 之前的 requestAudioFocus() 調(diào)用中所使用的 durationHint 值相同:AUDIOFOCUS_GAIN、AUDIOFOCUS_GAIN_TRANSIENT、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 或 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE。
setAudioAttributes() AudioAttributes 描述了應(yīng)用的用例。系統(tǒng)會在應(yīng)用獲得和失去音頻焦點時查看這些屬性。這些屬性取代了音頻流類型的概念。在 Android 8.0(API 26)及更高版本中,棄用了除音量控制以外的所有操作的音頻流類型。在焦點請求中使用與音頻播放器中相同的屬性(如此表下面的示例中所示)。首先使用 AudioAttributes.Builder 指定屬性,然后使用此方法將屬性分配給請求。如果未指定,則 AudioAttributes 默認為 AudioAttributes.USAGE_MEDIA。
setWillPauseWhenDucked() 當其他應(yīng)用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 請求焦點時,持有焦點的應(yīng)用通常不會收到 onAudioFocusChange() 回調(diào),因為系統(tǒng)可以自行降低音量。如果需要暫停播放而不是降低音量,請調(diào)用 setWillPauseWhenDucked(true),然后創(chuàng)建并設(shè)置 OnAudioFocusChangeListener,具體如自動降低音量中所述。
setAcceptsDelayedFocusGain() 當焦點被其他應(yīng)用鎖定時,對音頻焦點的請求可能會失敗。此方法可實現(xiàn)延遲獲取焦點,即在焦點可用時異步獲取焦點。請注意,要使“延遲獲取焦點”起作用,必須在音頻請求中指定 AudioManager.OnAudioFocusChangeListener,因為應(yīng)用必須收到回調(diào)才能知道自己獲取了焦點。
setOnAudioFocusChangeListener() 只有在請求中還指定了 willPauseWhenDucked(true) 或 setAcceptsDelayedFocusGain(true) 時,才需要 OnAudioFocusChangeListener。有兩個方法可以設(shè)置監(jiān)聽器:一個帶處理程序參數(shù),一個不帶。處理程序是運行監(jiān)聽器的線程。如果未指定處理程序,則會使用與主 Looper 關(guān)聯(lián)的處理程序。

以下示例展示了如何使用 AudioFocusRequest.Builder 構(gòu)建 AudioFocusRequest 來請求和放棄音頻焦點

audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
    setAudioAttributes(AudioAttributes.Builder().run {
        setUsage(AudioAttributes.USAGE_GAME)
        setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        build()
    })
    setAcceptsDelayedFocusGain(true)
    setOnAudioFocusChangeListener(afChangeListener, handler)
    build()
}
mediaPlayer = MediaPlayer()
val focusLock = Any()

var playbackDelayed = false
var playbackNowAuthorized = false

// ...
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
    playbackNowAuthorized = when (res) {
        AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false
        AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
            playbackNow()
            true
        }
        AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
            playbackDelayed = true
            false
        }
        else -> false
    }
}

// ...
override fun onAudioFocusChange(focusChange: Int) {
    when (focusChange) {
        AudioManager.AUDIOFOCUS_GAIN ->
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false
                    resumeOnFocusGain = false
                }
                playbackNow()
            }
        AudioManager.AUDIOFOCUS_LOSS -> {
            synchronized(focusLock) {
                resumeOnFocusGain = false
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            synchronized(focusLock) {
                resumeOnFocusGain = true
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // ... 暫?;蚧乇苋Q于你的應(yīng)用程序
        }
    }
}

3.3 自動降低音量

在 Android 8.0(API 26)中,當其他應(yīng)用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 請求焦點時,系統(tǒng)可以在不調(diào)用應(yīng)用的 onAudioFocusChange() 回調(diào)的情況下降低和恢復(fù)音量。
雖然自動降低音量的行為對于音樂和視頻播放應(yīng)用來說是可接受的,但在播放語音內(nèi)容時(例如在聽書應(yīng)用)就沒什么用處了。在這種情況下,應(yīng)用應(yīng)該暫停播放。
如果希望應(yīng)用在被要求降低音量時暫停播放,應(yīng)該創(chuàng)建包含 onAudioFocusChange() 回調(diào)方法的 OnAudioFocusChangeListener,該回調(diào)方法可以實現(xiàn)所需的暫停/恢復(fù)行為。 調(diào)用 setOnAudioFocusChangeListener() 來注冊監(jiān)聽器,然后調(diào)用 setWillPauseWhenDucked(true) 告訴系統(tǒng)使用的回調(diào),而不是執(zhí)行自動降低音量。

3.4 延遲獲取焦點

在有些情況下,系統(tǒng)不能批準對音頻焦點的請求,因為焦點被其他應(yīng)用“鎖定”了,例如在通話過程中。在這種情況下,requestAudioFocus() 會返回 AUDIOFOCUS_REQUEST_FAILED。在這種情況下,應(yīng)用將不會播放音頻,因為它未獲得焦點。
方法setAcceptsDelayedFocusGain(true)可讓應(yīng)用異步處理焦點請求。設(shè)置此標記后,在焦點鎖定時發(fā)出的請求會返回AUDIOFOCUS_REQUEST_DELAYED。當鎖定音頻焦點的情況不再存在時(例如當通話結(jié)束時),系統(tǒng)會批準待處理的焦點請求,并調(diào)用onAudioFocusChange()來通知應(yīng)用。
為了處理“延遲獲取焦點”,必須創(chuàng)建包含onAudioFocusChange()回調(diào)方法的OnAudioFocusChangeListener,該回調(diào)方法會通過調(diào)用 setOnAudioFocusChangeListener()來實現(xiàn)所需行為并注冊監(jiān)聽器。

3.5 響應(yīng)音頻焦點更改

當應(yīng)用獲得音頻焦點后,它必須能夠在其他應(yīng)用為自己請求音頻焦點時釋放該焦點。出現(xiàn)這種情況時,應(yīng)用會收到對AudioFocusChangeListener中的onAudioFocusChange()方法的調(diào)用,該方法是應(yīng)用調(diào)用requestAudioFocus()時指定的。傳遞給onAudioFocusChange()focusChange參數(shù)表示所發(fā)生的更改類型。它對應(yīng)于獲取焦點的應(yīng)用所使用的持續(xù)時間提示。應(yīng)用應(yīng)該做出適當?shù)捻憫?yīng)。

3.5.1 暫時性失去焦點

如果焦點更改是暫時性的AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKAUDIOFOCUS_LOSS_TRANSIENT,應(yīng)用應(yīng)該降低音量(如果不依賴于自動降低音量)或暫停播放,否則保持相同的狀態(tài)。在暫時性失去音頻焦點時,應(yīng)該繼續(xù)監(jiān)控音頻焦點的變化,并準備好在重新獲得焦點后恢復(fù)正常播放。當搶占焦點的應(yīng)用放棄焦點時,收到一個回調(diào)AUDIOFOCUS_GAIN。此時,可以將音量恢復(fù)到正常水平或重新開始播放。

3.5.2 永久性失去焦點

如果是永久性失去音頻焦點 (AUDIOFOCUS_LOSS),則其他應(yīng)用會播放音頻。您的應(yīng)用應(yīng)立即暫停播放,因為它不會收到AUDIOFOCUS_GAIN回調(diào)。要重新開始播放,用戶必須執(zhí)行明確的操作,例如在通知或應(yīng)用界面中按播放傳輸控件。
以下代碼段展示了如何實現(xiàn)OnAudioFocusChangeListener及其onAudioFocusChange()回調(diào)。請注意這里使用Handler延遲了對永久性失去音頻焦點的停止回調(diào)。

private val handler = Handler()
private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
          AudioManager.AUDIOFOCUS_LOSS -> {
                // 永久性失去音頻焦點,立即暫停播放
                mediaController.transportControls.pause()
                // 等待30秒后停止播放
                handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30))
          }
          AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                // 暫停播放
          }
          AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
                // 降低音量,繼續(xù)播放
          }
          AudioManager.AUDIOFOCUS_GAIN -> {
                // 再次被授予音頻焦點,將音量調(diào)至正常,必要時重新開始播放
          }
   }
}

private var delayedStopRunnable = Runnable {
        mediaController.transportControls.stop()
}

為了確保在用戶重新開始播放時不會觸發(fā)延遲停止,請調(diào)用 mHandler.removeCallbacks(mDelayedStopRunnable)來響應(yīng)任何狀態(tài)變化。例如,在回調(diào)的onPlay()、onSkipToNext() 等中調(diào)用 removeCallbacks()。此外,在清理服務(wù)使用的資源時,也應(yīng)該在服務(wù)的 onDestroy() 回調(diào)中調(diào)用此方法。

3.6 AudioFocus 常用常量

常量 備注
AUDIOFOCUS_NONE 沒有獲得、丟失音頻焦點,也沒有請求音頻焦點。
AUDIOFOCUS_GAIN 用于請求持續(xù)時間未知的音頻焦點。此參數(shù)會觸發(fā)其他監(jiān)聽器的AudioManager.AUDIOFOCUS_LOSS。
AUDIOFOCUS_GAIN_TRANSIEN 用于請求短暫性音頻焦點。此參數(shù)會觸發(fā)其他監(jiān)聽器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT。
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 用于申請短暫性音頻焦點,一般是錄音或者語音識別,此參數(shù)會觸發(fā)其他監(jiān)聽器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 用于申請短暫性音頻焦點并要求其它應(yīng)用降低音量,此時會混音播放,此參數(shù)會觸發(fā)其他監(jiān)聽器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK。
AUDIOFOCUS_REQUEST_DELAYED 用于申請延遲授予的音頻焦點:請求成功,但只有在阻止立即授予的條件結(jié)束后,才會授予請求者音頻焦點。
AUDIOFOCUS_LOSS 音頻焦點丟失。
AUDIOFOCUS_LOSS_TRANSIENT 暫時失去音頻焦點。
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 暫時失去音頻焦點,失去音頻焦點的程序如果想繼續(xù)播放,可以降低輸出音量,因為新的焦點所有者不要求其他人保持沉默。
AUDIOFOCUS_REQUEST_FAILED 音頻焦點請求失敗。
AUDIOFOCUS_REQUEST_GRANTED 音頻焦點請求成功。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容