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