蔡曉麗 程曉蘭
(四川長虹網(wǎng)絡(luò)科技有限責(zé)任公司 四川省綿陽市 621000)
長期以來數(shù)字機(jī)頂盒上多媒體播放器嚴(yán)重的依耐于各個(gè)芯片SDK 提供的媒體播放接口實(shí)現(xiàn)。在機(jī)頂盒應(yīng)用開發(fā)中,對(duì)于每一個(gè)芯片平臺(tái)都需要適配媒體播放接口。加之流媒體的興起,各種芯片平臺(tái)對(duì)流媒體的支持能力更是參差不齊。基于此設(shè)計(jì)可跨平臺(tái)的播放器可以減少對(duì)芯片平臺(tái)的依賴,引進(jìn)新平臺(tái)時(shí)可以快速的實(shí)現(xiàn)播放器成型。通過集成統(tǒng)一的流媒體接口可以更好的規(guī)劃產(chǎn)品定型。
多媒體涉及MKV, AVI, FLV, WMV, TS 等容器格式以及流媒體中的HLS, Smooth streaming, DASH 等協(xié)議內(nèi)容。媒體完成解析后便形成獨(dú)立的字幕流,音頻流,視頻流。將音頻流,視頻流分別注入芯片底層的解碼器實(shí)現(xiàn)解碼完成播放。利用開源軟件實(shí)現(xiàn)對(duì)多媒體解析,并封裝統(tǒng)一的音視頻同步控制接口,寫解碼器控制接口和文件讀寫控制接口最大程度的弱化媒體播放器對(duì)芯片平臺(tái)的依耐性。另外現(xiàn)有的VLC,F(xiàn)FMPEG 等開源代碼均能很好完成多媒體容器解析。而VLC 作為優(yōu)秀的開源播放器支持多種媒體封裝格式解析,而且適用于多個(gè)平臺(tái)集成。
本文提出一種基于開源軟件VLC 的可跨平臺(tái)應(yīng)用的多媒體播放器系統(tǒng)。以C/S 模式,VLC 進(jìn)程作為服務(wù)器端等待多媒體解析請(qǐng)求,應(yīng)用平臺(tái)端作為客戶端向服務(wù)端發(fā)起播放請(qǐng)求,經(jīng)進(jìn)程通信控制模塊(IPC)傳遞播放地址,開啟音視頻讀數(shù)據(jù)線程并打開底層解碼器,之后等待共享內(nèi)存中視頻PES 數(shù)據(jù)量達(dá)到起播值SIZE_READ_START。VLC 進(jìn)程獲取媒體播放地址后開啟媒體容器解析線程。一方面將獲取的媒體解碼器信息包括音視頻編碼格式,音視頻PID 值和文件時(shí)長等信息通過IPC 接口傳回給客戶端。另一方面將音視頻流PES 數(shù)據(jù)寫入分配的共享內(nèi)存BUFFER 供客戶端取用。客戶端在獲取媒體解碼信息后配置音視頻解碼器解碼格式。讀視頻數(shù)據(jù)線程等待共享內(nèi)存內(nèi)數(shù)據(jù)達(dá)到起播閾值后立即開始將數(shù)據(jù)整塊寫入解碼器緩存,并開啟音頻流讀數(shù)據(jù)線程。
如圖1 所示,播放器系統(tǒng)由兩個(gè)進(jìn)程協(xié)調(diào)實(shí)現(xiàn)平臺(tái)應(yīng)用客戶端進(jìn)程與VLC 服務(wù)端進(jìn)程。平臺(tái)應(yīng)用客戶端進(jìn)程內(nèi)包括播放器UI 模塊,讀共享內(nèi)存控制模塊,音視頻同步控制模塊,解碼器模塊,輸出控制模塊,平臺(tái)抽象層模塊。VLC 服務(wù)端進(jìn)程包括媒體解析模塊,寫共享內(nèi)存控制模塊。各個(gè)功能模塊的具體實(shí)現(xiàn)功能如下。
播放器UI 模塊,負(fù)責(zé)視頻元信息展示及視頻播放;并與用戶交付如暫停,播放,快進(jìn)快退,SEEK 等播放控制指令。
讀共享內(nèi)存控制模塊,負(fù)責(zé)從共享內(nèi)存中讀視頻數(shù)據(jù)與讀音頻數(shù)據(jù)。讀取的同時(shí)寫入解碼器控制模塊。讀視頻數(shù)據(jù)控制等待視頻緩存內(nèi)可用數(shù)據(jù)量超過閾值SIZE_READ_START 開啟讀操作。SIZE_READ_START 值的大小會(huì)影響起播時(shí)間與起播后的流暢性。現(xiàn)在改值量化為20KByte,播放器的起播時(shí)間小于15 秒。
讀共享內(nèi)存控制模塊,判定當(dāng)前已寫入的數(shù)據(jù)量Video_writetotalsize 與可讀數(shù)據(jù)量Video_readablesize 進(jìn)行比較確定讀取的數(shù)據(jù)量Inject_Data_Size。將視頻PES 緩存設(shè)置成一個(gè)循環(huán)BUFFER。已寫入的數(shù)據(jù)量Video_writetotalsize 為寫入的循環(huán)次數(shù)Video_writecycle 乘以分配的內(nèi)存大小VIDEO_BUFFER_MAX_SIZE 在與當(dāng)前的寫地址偏移Video_writesize 求和而得。以讀的數(shù)據(jù)量Video_readtotalsize 為已讀的循環(huán)次數(shù)Video_readcycle 乘以分配的內(nèi)存大小VIDEO_BUFFER_MAX_SIZE 再與當(dāng)前的讀地址偏移量Video_readsize 求和而得。如圖2 所示,根據(jù)上述幾者的關(guān)系確定Inject_Data_Size。讀音頻數(shù)據(jù)控制流程與讀視頻數(shù)據(jù)控制流程類似不在贅述。
音視頻同步控制模塊,平臺(tái)應(yīng)用第一次讀取到視頻PES 數(shù)據(jù)時(shí)提取視頻第一幀的PTS 值V_PTS。并將此值作為初始值寫入平臺(tái)底層系統(tǒng)同步時(shí)間系統(tǒng),從而初次同步到系統(tǒng)時(shí)間戳,再以系統(tǒng)時(shí)間戳作為時(shí)間基準(zhǔn)進(jìn)行音視頻同步。每隔時(shí)間T0 抽取音頻幀與視頻幀的的PTS 時(shí)間值與系統(tǒng)時(shí)間戳進(jìn)行對(duì)比,如果音頻幀PTS 與時(shí)間戳的差值遠(yuǎn)遠(yuǎn)大于視頻幀PTS 值與時(shí)間戳的差值則重新收取下一個(gè)比較接近PTS 數(shù)據(jù)的音頻幀寫入底層解碼器實(shí)現(xiàn)同步。
解碼器模塊,如圖3 所示,解碼器模塊下設(shè)置音頻解碼器寫控制與視頻解碼器寫控制。以視頻解碼器寫控制為例,讀共享內(nèi)存控制模塊獲取Inject_Data_Size 后,從共享內(nèi)存拷貝大小Inject_Data_Size 的數(shù)據(jù)量寫入底層解碼器。最終根據(jù)解碼器驅(qū)動(dòng)接口返回的寫入數(shù)據(jù)值作為真正寫入的數(shù)據(jù)量,并更新Video_readtotalsize 作為下一次的判定標(biāo)準(zhǔn)。輸出模塊,對(duì)接音頻輸出驅(qū)動(dòng)和視頻輸出驅(qū)動(dòng)。
平臺(tái)抽象層模塊,根據(jù)媒體播放對(duì)平臺(tái)的依賴性,該模塊需要提供內(nèi)存管理,文件系統(tǒng),內(nèi)核管理等平臺(tái)抽象封裝。對(duì)平臺(tái)抽象層的每個(gè)模塊進(jìn)行類定義,在類的設(shè)計(jì)采用C++虛函數(shù)機(jī)制,該種機(jī)制能夠?qū)崿F(xiàn)運(yùn)行時(shí)綁定的特性,因此派生類的行為不影響基類以及基類同一基類的派生類的行為。
媒體解析模塊,VLC2.2.0 在DecoderThread 線程內(nèi)查詢共享內(nèi)存的視頻數(shù)據(jù)的緩存量確定是否繼續(xù)向共享內(nèi)存數(shù)據(jù)寫入。Video_readtotalsize 減去Video_writetotalsize 得到的差值Sub_videosize,如果差值大于分配的視頻數(shù)據(jù)緩存量VIDEO_BUFFER_MAX_SIZE*(3/5)就需要暫停從解析線程中讀取數(shù)據(jù)到共享內(nèi)存。寫共享內(nèi)存控制模塊,根據(jù)音視頻的PID 值分別將音視頻PES 數(shù)據(jù)寫入共享內(nèi)存。

圖1:基于VLC 的可跨平臺(tái)應(yīng)用的播放器架構(gòu)圖

圖3:播放器系統(tǒng)中解析解碼功能結(jié)構(gòu)框圖
該播放系統(tǒng)的一個(gè)重要組成是PES 數(shù)據(jù)的進(jìn)程交付控制。一種VLC 提取媒體文件PES 數(shù)據(jù)以共享內(nèi)存方式交付另一進(jìn)程的方法包括以下步驟。
(1)根據(jù)需要設(shè)置VLC 以文件寫入方式輸出媒體文件解析的ES 數(shù)據(jù)到移動(dòng)硬盤,具體控制命令為:argv[argc++]= "-vvv";argv[argc++]= "--no-loop";argv[argc++]= "--sout";argv[argc++]= "#es{access=file, dst-video=/mnt/usb/video_%d.%c, dst-audio=/mnt/usb/audio_%d.%c}";該控制指令為將解析輸出的ES 數(shù)據(jù)寫入到存儲(chǔ)盤/mnt/usb。
(2)VLC 進(jìn)程運(yùn)行初始化階段關(guān)聯(lián)共享內(nèi)存地址到本地內(nèi)存地址,內(nèi)存大小分配為視頻PES 數(shù)據(jù)緩存為3.768MByte,音頻PES數(shù)據(jù)緩存為1.88MByte,控制信息緩存1024KByte。
(3)VLC 進(jìn)程接收播放請(qǐng)求后,首先獲取通道音視頻編解碼信息。其次開啟媒體播放線程。在獲取文件播放地址后調(diào)用libvlc_media_get_duration 獲取媒體的時(shí)長信息,通過接口libvlc_media_get_tracks_info 獲取音視頻通道解碼信息。通過調(diào)用接口libvlc_media_player_play 開啟播放流程。
(4)VLC 將解析獲取的通道音視頻解碼信息,寫入共享內(nèi)存控制信息緩存。VLC 需要將解析的視頻ES 數(shù)據(jù)轉(zhuǎn)換成視頻PES 數(shù)據(jù)并寫入視頻數(shù)據(jù)緩存,解析的音頻ES 數(shù)據(jù)轉(zhuǎn)換成音頻PES 數(shù)據(jù)寫入音頻數(shù)據(jù)緩存。在ES 輸出的指令驅(qū)使下,VLC2.2.0 在線程DecoderThread 中將解析器輸出的數(shù)據(jù)包經(jīng)接口DecoderProcessSout寫入到對(duì)應(yīng)的存儲(chǔ)盤地址/mnt/usb/。此處需要阻斷上述過程,修改函數(shù)DecoderProcessSout 在得到解析ES 包數(shù)據(jù)后經(jīng)重新封裝的接口sout_AccessOutMemoryWrite 寫入到共享內(nèi)存。重新封裝的接口sout_AccessOutMemoryWrite 首先將ES 數(shù)據(jù)轉(zhuǎn)換成PES 數(shù)據(jù)然后寫入共享內(nèi)存。音視頻PES 數(shù)據(jù)寫入以PID 信息作為區(qū)分標(biāo)志。VLC2.2.0 中線程DecoderThread 需輪詢共享內(nèi)存區(qū)域狀態(tài),根據(jù)狀態(tài)確定是否繼續(xù)向共享內(nèi)存寫入數(shù)據(jù),否則會(huì)造成數(shù)據(jù)溢出花屏問題; 輪詢平臺(tái)解碼器狀態(tài),確定是否繼續(xù)向共享內(nèi)存寫入數(shù)據(jù)。
本文提出的可跨平臺(tái)的播放系統(tǒng)將多媒體播放器劃分為媒體解析,音視頻同步,解碼,渲染等幾個(gè)組成部分。以多進(jìn)程的方式利用開源軟件VLC2.2.0 實(shí)現(xiàn)媒體解析,以共享內(nèi)存方式進(jìn)行數(shù)據(jù)通信,僅利用芯片平臺(tái)提供的解碼器解碼獨(dú)立的音視頻數(shù)據(jù)流實(shí)現(xiàn)多媒體播放器。利用該方法可以方便在不同芯片平臺(tái)上集成具有統(tǒng)一播放體驗(yàn)的多媒體播放器。