VLC架構剖析

VLC架構剖析

1. VideoLan簡介

1.1 videolan組成

Videolan有以下兩部分組成:

VLC:一個最主要的部分,它可以播放各種類型的媒體文件和流媒體文件,並且可以創造媒體流並保存成各種格式的媒體文件,這些文件的質量要比沒保存前的件好。videolan作爲客戶端可以播放本地文件,httP://,rtsp://。

VLS:是一種流服務器,專門用來解決流的各種問題,它也具有一些VLC的特徵。videolan作爲服務器可以輸出httP,rtP,rtsp的流。

1.2 VLC優點

VLC是一種跨平臺的媒體播放器和流媒體服務器,最初爲videolan的客戶端,它是一種非常簡便的多媒體播放器,它可以用來播放各種各樣的音視頻的格式文件(MPEG-1、MPEG- 2、MPEG- 4、DivX、WMV、mp3、OGG、Vorbis、AC3、AAC等等)流媒體協議,最具特色的功能是可以邊下載邊觀看Divx媒體文件,並可以播放不完全的AVI文件。並且支持界面的更改。VLC支持多種的操作系統,linux(rh9,Debian,Mandrake,Gentoo),BSD,windows,Mac OS X,Be OS,Solaris等等。支持帶菜單的VCD,SVCD,和DVD,數字衛星頻道、數字地球電視頻道(digital terrestrial television channels),在這些操作系統下通過寬帶IPv4、IPv6網絡播放線上影片。此軟件開發項目是由法國學生所發起的,參與者來自於世界各地,設計了多平臺的支持,可以用於播放網絡流媒體及本機多媒體文件,特別是它能直接播放未下載完整的多媒體文件。

下圖表示出了VideoLan的解決方案:

VideoLan Client是VideoLan項目(一個完整的MPEG-2客戶/服務器解決方案)的一個組成部分。不過VideoLan Client也可以作爲一個獨立的程序來播放來自硬盤或者DVDROM的MPEG數據流。它目前支持GTK+、GNOME、KDE和QT,並且可以使用X11、Xvideo、SDL或者DirectX作爲視頻輸出。對於聲音,VideoLan Client支持OSS、ALSA和ESD。要訪問DVD,VideoLan Client使用的是Libdvdcss庫。它是一個簡單的專爲DVD訪問設計的庫。它可以像訪問塊設備一樣訪問DVD,而不用考慮解密問題。

2. VLC整體架構分析

2.1 LibVLC

LibVLC是VLC的核心部分。它是一個提供接口的庫,比如給VLC提供些功能接口:流的接入,音頻和視頻輸出,插件管理,線程系統。所有的LibVLC源碼位於src\及其子目錄:

Interface/:包含與用戶交互的代碼如按鍵和設備彈出。

Playlist/:管理播放列表的交互,如停止,播放,下一個,或者隨機播放。

Input/:打開一個輸入組件,讀包,解析它們並且將被還原的基本流傳遞給解器。

Video_output/:初始化video顯示器,從解碼器得到所有的圖片和子圖片(如subtitles)。隨意將它們轉換爲其它格式(如:YUV到RGB)並且播放。

Audio_output/:初始化音頻mixer(混合器)。如:發現正確的播放頻率,然後重新制作從解碼器接收過來的音頻幀。

Stream_output/:類似Audio_output。

Misc/:被libvlc其它部分使用的雜項,如線程系統,消息隊列,CPU探測,對象查詢系統,或者特定平臺代碼。

 

2.2 VLC

VLC是一個純粹圍繞着LibVLC寫成的程序。它是非常小的,但是功能很齊全的媒體播放器,歸功於LibVLC的動態組件支持。

2.3 組件

         組件位於modules\子目錄,在運行時被加載。每一個組件提供不同的特徵適應特定的文件的環境。另外,大量的不斷編寫的可移植功能位於audio_output\,vidco_output\和interface\組件,以支持新的平臺(如:BeoS Mae OS X)。

組件中的插件被位於src\misc\modules.c和include\modules*.h中的函數動態加載和卸載。寫組件的API描述如下,共3種:

(l)組件描述宏:聲明組件具有哪種優先級的能力(接口,demux2等等),還有GUI組件的實現參數,特定組件的配置變量,快捷方式,子組件等等;

(2)Open(vlc_objeet_t*p_object):被VLC調用初始化這個組件,它被組件描述宏賦值給了結構體module_t中的pf_activate函數指針,被Module_Need調用;

(3)Close(vlc_objeet_t*p_object):被VLC調用負初始化這個組件,保證消耗Open分配的所有資源。它被組件描述宏賦值給了結構體module_t中的pf_deactivate函數指針,被Module_Unneed調用。

用LibVLC寫的組件能夠直接被編譯進VLC,因爲有的OS不支持動態加載代碼。被靜態編譯進VLC的組件叫做內置組件。

2.4 線程分析

(l)線程管理:

VLC是一個密集的多線程應用。由於解碼器必須預先清空和播放工序必須預先做好流程(比如說解碼器和輸出必須被分開使用,否則無法保證在要求的時間裏播放文件),因此VLC不採用單線程方法。目前不支持單線程的客戶端,多線程的解碼器通常就意味着更多的開銷(各線程共享內存的問題等),進程間的通信也會比較複雜。

VLC的線程結構基於pthreads線程模型。爲了可移植的目的,沒有直接使用pthreads函數,而是做了一系列類似的包裹函數:vlc_thread_create,vlc_thread_exit,vlc_thread_join,vlc_mutex_init,vlc_mutex_lock,vlc_mutex_unlock,vlc_mutex_destroy,vlc_cond_init,vlc_cond_signal,vlc_cond_broadcast,vlc_cond_wait,vlc_cond_destroy和類似結構:vlc_thread_t,vlc_mutex_t,and  vlc_cond_t。

(2)線程同步:

VLC的另一個關鍵特徵就是解碼和播放是異步的:解碼由一個解碼器線程工作,播放由音頻輸出線程或者視頻輸出線程工作。這個設計的主要目的是不會阻塞任何解碼器線程,能夠及時播放正確的音頻幀或者視頻幀。這樣實現也導致產生了在接口,輸入,解碼器和輸出之間的一個複雜的通訊結構。

雖然當前接口並不允許,但是讓若干個輸入和視頻輸出線程在同一時刻讀取多個文件是可行的(這是VLC未來改進的主要方向)。現在的客戶端就是用這種思想實現的,這就意味着如果沒有用到全局鎖的話那麼一個不能重入的庫是不能被使用的(尤其是liba52庫)。

VLC輸出的流裏包含時間戳,被傳遞給解碼器,所有有時間戳標記的流也均被記錄,這樣輸出層可以正確及時的播放這些流。時間mtime_t是一個有符號的64-bit整形變量,單位是百萬分之一秒,是從1970年7月1日以來的絕對時間。

當前時間能夠被mdate()函數恢復。一個線程可以被阻塞到mwait(mtime_t date)等到一個確定的時間才被執行。也可以用msleep(mtime_t delay)休眠一段時間。如果有重要的事情要處理的話,那麼應該在正常時間到來之前被喚醒(如色度變換)。例如在modules\codec\mpeg_vldeo\synchro.c中,通常的解碼時間被記錄,保證圖像被即時解碼。

3. VLC接口技術分析

3.1 VLC運行過程

通過對相關資料和自己的分析,VLC的運行過程如下:

ELF(Linux下可執行文件的格式)先被動態加載,然後主線程就變成了接口線程並且在src/interface/interface.c中開始。它執行下列步驟:

1.cpu探測:什麼型號?所有能力(MMX,MMXEXT,3DNow,AltiVec等等)

2.消息接口初始化;

3.命令行選項解析組件

4.創建播放列表

5.倉庫初始化

6.加載所有內置和動態組件

7.打開接口

8.安裝信號處理器:SIGHUP,SIGINT和SIGQUIT(捕獲一個,忽略後來的並退出)。

9.派生音頻輸出線程;

10.派生視頻輸出線程;

11.主循環:事件管理;

下圖表示了這些步驟的執行過程:

 

VLC的運行過程圖

3.2 消息接口

由於printf()函數不是線程安全的,因此在調用printf()函數時一個線程的執行將會受到干擾,當這個線程被另一個函數所調用時就會其狀態被破壞而退出程序。所以VLC構造了自己的線程安全的消息接口。

VLC的線程安全的消息接口有兩種實現方式:如果在config.h裏定義了INTF_MSG_QUEUE的話,每一個類似printf()的函數將會把排隊的消息放到鏈表裏,這個鏈表將會在事件循環中被線程接口用紅色標記的方式打印出來。如果INTF_MSG_QUEUE沒被定義的話,調用線程將會獲得一個print lock(用來防止在同一時刻有兩個printf操作被執行)同時直接打印出消息(默認操作)。

以下爲VLC線程安全消息的API:

QueueMsg:添加一條消息到消息隊列,如果消息隊列滿了,先打印所有的消息;

FlushMsg:打印所有在消息隊列裏的消息,特別的,消息隊列必須被提前加鎖,因爲該函數不檢查鎖。

PrintMsg:打印一條消息到stderr,可以打印彩色消息。

3.3 命令行選項

         VLC用GNU的getopt解析命令行選項。Getopt結構定義在src\extras\getopt.h裏。所有的配置也可以用環境變量改變:調用函數main_Put*Variable和main_Get*Variable。所以,.\vlc--height=240和 .\vic_height=240./vlc(這種方式用於所有地方,包括插件)是一樣的。但是爲了線程安全的考慮,當第二個線程派生了,main_Put*Variable便不能被使用了。

3.4 播放列表管理

當VLC得到輸入媒體文件的時候播放列表被創建。一個合適的接口插件能夠從這個播放列表添加和刪除文件。在src/Playlist目錄下的這些被使用的函數被描述。

播放列表既不是動態組件也不是內置組件,只是可以被外部調用的API:

Playlist_Create:初始化播放列表,派生兩個線程。一個是播放列表主線程RunThread調用Input_CreateThread爲每個被讀的文件派生輸入線程。一個是播放列表裏的項目排隊預解析線程RunPreparse。

Intf_playlistadd和intf_playlistdelete是兩個典型的最常用的添加和刪除播放列表的命令函數。此時接口主循環函數inif_manage將被啓動同時在必要的時候終止輸入的線程。

3.5 組建倉庫

         在啓動的時候,VLC創建一個包含所有插件接口(.so和內置插件)的倉庫,每一個插件都會被檢查其實現的功能,這些功能如下:

MODULE_CAPABILITY_INTF:一個接口插件。

MODULE_CAPABILITY_ACCESS:A Sam- ism,目前還沒有用到。

MODULE_CAPABILITY_PUT:一個輸入插件比如說PS和DVD的播放要用到。

MODULE_CAPABILITY_DECAPS: A Sam-ism,unused at present。

MODULE_CAPABILITY_ADEC:音頻解碼器。

MODULE_CAPABILITY_VDEC:視頻解碼器。

MODULE_CAPABILITY_MOTION:視頻解碼器的補充動態組件。

MODULE_CAPABILITY_IDCT:視頻解碼器的IDCT組件。

MODULE_CAPABILITY_AOUT:一個音頻輸出組件。

MODULE_CAPABILITY_VOUT:一個視頻輸出組件。

MODULE_CAPABILITY_YUV:視頻輸出的YUV組件。

MODULE_CAPABILITY_AFX:音頻輸出的音頻效果插件,目前還沒實現。

MODULE_CAPABILITY_VFX:視頻輸出的音頻效果插件,目前還沒實現。

管理這些插件的API如下:

Module_InitBank:創建組件倉庫,然後調用module_LoadMain將主程序信息導入組件銀行。

Module_LoadMain:將主程序信息導入組件倉庫。

Module_LoadBulltins:加載所有內置組件。

Module_Loadplugins:加載所有動態組件。

Module_EndBank:清空組件倉庫。

Module_ReSetBank:通過卸載所有無用的動態(插件)組件,重置組件倉庫。

Module_EndBank:卸載所有動態(插件)組件,清空模倉庫。

Module_Need:得到能力最符合要求的組件。

Module_Unneed:減少一個組件的引用計數,必須被Module_Need的同一個線程調用。

3.6 接口主循環

         這個接口線程首先選取合適的接口動態插件,然後和這個插件的pf_run()函數一起進入主接口循環。pf_run()函數將實行其該實現的功能並且每隔100ms調用intf_Manage一次(典型的爲用戶圖形界面的時間回調)。intf_Manage通過卸載不必要的組件來清空組件倉庫,並且管理播放列表和當消息隊列正在用時對排隊的消息進行紅色標記。如果在linux下編譯有圖形界面,那麼這個動態插件是modules\gui\wxwindows\xwindows.cpp。

3.7 接口動態組件

         這兩種組件都位於modules\目錄,接口動態組件除了具有普通動態組件的定義外,還需要定義以下標準API:Run或者Runlntf:這個函數就是pf_run,履行接口動態組件的一切功能(等待用戶輸入並且顯示信息)。

4. 功能組件分解

4.1 複合的多層輸入技術

輸入組件的中心思想就是處理包,但又不必知道包裏的具體內容。它只是讀包的ID,在包頭(在MPEG中是SCR和PCR字段)的指示的正確時刻將包投遞到解碼器。輸入組件不需要具體細節。如,不需要知道怎樣播放一幀或者幾幀,也不需要知道什麼是“幀”。像視頻圖像一樣,基本流沒有優先級。

經過分析發現輸入組件對一個媒體文件做了以下這些事情:爲每一個讀取的文件派生輸入線程。實際上,由於流是不一樣的,因此輸入文件的結構和解碼器需要重新被初始化。這些工作由接口線程(播放列表組件)調用input_CreateThread來完成:首先尋找一個能讀取這個文件的輸入插件(首先我們要打開文件的socket,然後探測文件流的開始處以確定哪個插件可以讀取這個文件),文件的socket是被input_FileOpen,input_NetworkOpen或者input_DvdOpen函數所打開的,這些函數需要設置兩個非常重要的參數:b_pace_control和b_seekable。然後我們可以運行輸入插件的pf_init函數和一個無限循環來實現pf_read和pf_demux函數的功能,這個插件是用來初始化流結構(p—input->stream),管理文件包緩存,讀取文件包並且使這些文件包分路。

但是這些最重要的任務是在輸入的API高級函數的協助下完成的。

4.2 文件流的管理

這個己經打開輸入socket的功能模塊必須先規範兩個屬性如下:

(1)p_input->stream.b_pace_control:

不管文件流是否以客戶端所要求的速率被讀取(這個由流本身的頻率和客戶電腦系統的時鐘所確定),比如說如果客戶端不能讀取文件足夠快得話,這時一個文件或者一個管道(包括TCP/IP連接)可以以客戶端速率被讀取,那麼這個管道的另一端將會被write()阻塞掉;相反,如果客戶端不能讀取文件足夠快得話,UDP流(比如說被VLS所使用的流)以服務器端的速率被讀取,當內核緩衝器滿的時候文件包就會丟失,因此服務器時鐘的漂移策略將會用來彌補這種包的丟失。不管文件流是怎樣的速率,這個屬性是用來控制時鐘的管理。

時鐘管理的子菜單:當用到UDPsocket和遠程服務器時,服務器時鐘的漂流策略是必不可少的,這是由於如果在流文件傳輸過程中,兩端的時鐘有一個哪怕是一點的混亂,那麼利用漂流策略則可以讓流文件說明這個時鐘的偏差。這也就意味着在每一個基本流給出的頻率中,由輸入線程顯示的日期將會有某種程度的不同步,輸出線程(通常說的是解碼線程)將會處理這個問題。不同步的問題也可以出現在讀取己連接上的設備文件上,比如說把這些文件讀取到視頻編碼盤上。由於cat foo.mpg|vlc沒任何顯示時鐘問題的功能,僅僅從簡單的cat foo.mpg|vlc上客戶端是不可能覺察出時間的不同步的,因此此時應該知道用戶的b_pace_control值。總之,當客戶端和服務器都能同步於同一個CPU時鐘時,服務器時鐘的漂流策略就可以被忽略掉。

(2)p_input->stream.b_seekable:

不管lseek()函數在文件中有沒有被描述,我們都要調用它來管理流。基本上我們要麼跳轉到文件流的任何地方(在滾動欄上顯示),要麼就一字節一字節的讀取文件流。這個屬性沒有第一個屬性對流的管理來的重要,但是它也不是多餘的,這是因爲catfoo.mpg1vlc中b_pace_control和b_seekable有關聯,比如當b_pace_control=1時b_seekable =0,相反就是當b_pace_control=0時b_seekable=1是不可能成立的。如果流是可以被搜尋的,p_input->stream.p_selectede_area->i_size必須被設置(比如說在任意的字節單位中,p_input->stream.p_selectede_area->i_size必須設置成和p_input->i_tell一樣,這是因爲p_input->i_tell表示當前從流讀取的字節)。

時間轉換的偏移:時鐘管理函數位於src\input\input_clock.c目錄下。客戶端所能知道的就是文件開頭和結尾的偏移量(p_input->stream.p_selectede_area->i_size),目前這個偏移量是用字節來表示的而且是依賴於插件的。例如如何在界面上以秒來顯示hell這個文件的時間信息呢?那就是客戶端要獲得PS流中的可以表明每秒讀取多少字節的mux_rate屬性,這個屬性可以隨時更改但它在整個流傳輸過程中是不變的,這樣可以用它來確定時間的偏移量。

4.3 輸出到接口的結構分析

這裏着重說明輸入組件和界面之間的API通信。最重要的文件是include\input_ext-intf.h,它定義了input_thread_t structure,the stream_descriptor_t和ES描述符(可看成樹的結構)。

注意到input_thread_t structure的特色是有兩個void型的指針,這兩個指針是p_method_data和p_plugin_data,創門分別用於緩衝管理數據和插件數據。並且文件流的描述放在了一個樹形結構的程序描述符裏,這個程序描述符裏包含了幾種基本流的描述符。

下圖表示出了這個樹形結構,這裏一個流啓動了兩個線程。而在多數情況下只能有一個線程,目前只有TS流下可以有啓動多個線程,比如說一個電影和一場足球比賽可以同時播放,這對於衛星和光纖廣播是足夠用的了。

 

VLC輸入組件和界面之間的API通信的樹形結構圖

這裏需要注意的是在對p_input->stream結構進行修改和存取時,必須先對其加鎖。

Es被一個ID(這個ID適合於多路信號分離器來尋找),一個stream_id(the real MPEG stream ID),一個模式(在ISO/IEC 13818-1 table 2-29裏被定義)和一些描述符來標示,它同樣提供其他有用的信息給多路信號分離器。如果需要讀取的流文件不是MPEG系統層的流(比如AVI或者RTP),那麼需要寫一個規範的多路信號分離器,在這種情況下,如果要攜帶額外的信息時,就可能要用到一個void型的p_demux_data的指針,這個指針在不傳輸流時會自動的被釋放掉。

下面說明爲什麼要用ID而不是簡單的用stream_id:當一個信息包(可以是TS包,PS包或者其他類型的包)被讀取時,多路信號分離器將會從這個包裏尋找這個ID來找到相關的基本流,並且如果用戶選擇了基本流的話就分離這個流。對於TS包,我們能知道的唯一信息就是ES PID,因此我們保存的參考ID就是PID,PID在PS流裏並不存在,因此需要寫這個PID。當然所有在PS包裏能找到的信息都是基於stream_id的,但是既然每個私有流(AC3,SPU,LPCM的等等)都在使用同一個stream_id,因此僅僅基於stream_id是不夠的,在這種情況下,PES有效負載的第一個字節就是流的私有ID,把這個私有ID和stream_id經過某種形式的合併來獲得想要的唯一ID。

流程序和ES的結構都寫在了插件裏的pf_init()函數裏,這個函數可以隨時被更改(在vic-0.8.0版本前都放在了src\input\input_programs.c中)。DVD插件解析.ifo文件後知道是哪個ES在信息流裏,TS插件讀取流裏的PAT和PMT結構,PS插件也可以解析PSM結構(目前很少見),或者通過預解析數據的第一個兆字節來編譯樹結構上不工作的部分。這裏需要注意的是:因爲幾乎從來沒有PSM(program stream map)結構,因此在大多說境況下我們需要預解析(也就是說讀取數據的第一個兆字節,然後又回到數據的開始處)PS流,雖然並不適合這樣做,但是這是唯一的選擇。這樣做會出現以下兩個問題:首先不能解析不可被搜索到的流,因此ES樹在不工作的時候被編譯;再者,如果一個新的ES流在上一個數據包的第一個兆字節後出現的話(比如說在節目字幕中不會出現對其的說明字幕),在沒遇到第一個數據包前是不會出現在菜單裏的。由於要花費很長的時間(即使包沒被解碼),因此無法解析全部的流。

通常輸入插件的任務是派生出必要的解碼線程,它必須在其選擇的ES流裏調用input_seleetES(input_thread_t*p_input,es_descriptor_t*p_es),流的描述符也包含一些區域(比如說DVD裏的章目和標題),這些區域在流裏呈現出邏輯的不連續性。儘管當PSM(或者PAT/PMT)改變時要用到這些區域,但是在TS和PS流裏只有一個這樣的區域。這個區域的目標是當尋找另一個區域時,輸入插件要導入一個新的流描述符的樹結構(這時選擇流的ID可能是不正確的)。

4.4 接口用到的方法

Input_ext-intf.c提供了一些函數來控制讀取流:

(l)input_SetStatus(input_thread_t*p_input,int i_mode):改變讀取流的速度。i_mode可以是INPUT_STATUS_END,INPUT_STATUS_PLAY,INPUT_STATUS_PAUSE,INPUT_STATUS_FASTER,INPUT_STATUS_SLOWER的一種。讀取流速度的主要是由變量p_input->stream.control.i_rate來決定。它的默認值爲DEFAULT_RATE,這個值越小,讀取得速度就會越快,速度的改變要考慮到工input_ClockManageRef。暫停是通過簡單的停止輸入線程來獲得的(此時被一個

pthread信號喚醒),在這種情況下,解碼器也會停止。當統計解碼時間(src/video_out/vout_synchro.c來統計)時需注意到這一點,如果p_input->b_pace_control==0時則不要調用這個函數。

(2)input_Seek(input_thread_t*p_input,off_t i_position):改變讀取速度的偏移量。如果p_input->stream.b_seekable=0時不能調用這個函數。它的位置在p_input->p_selected_area->i_start和p_input->p_selected_area->i_size之間。(當前值是在p_input->p_selected_area->i_tell)。由於多媒體文件可能會很大(尤其當我們讀取像DVD這樣的設備時),因此偏移量必須要有64bit這麼大。在很多系統下(比如FreeBSD),off_t的默認值就是64bit,但是在GNU libc 2.x下並不是這樣,這也是爲什麼需要用-D_FILE_OFFSETBITS=64-D_USE_UNIX98來編譯VLC的原因。隨機的改變讀取流的位置會導致流的混亂,讀取流的解碼器也會出錯,爲了避免這種情況的發生,在改變讀取流位置的之前,需要先發送一些空包(全零)。事實上,對於大多數的音視頻格式,足夠長的零流就是流的溢出並且解碼器也可以不出錯。

(3)input_OffsetToTime(input_thread_t*p_input,char *psz_buffer,off_t i_offset):把偏移值轉換成和時間一致(用於界面播放),通常在播放MPEG-2文件時這個一致性會受到破壞。

(4) input_ChangeEs(input_thread_t*p_input,es_descriptor_t*p_es,u8 i_cat):選擇部分基本流的i_cat類型和p_es,用於更改語言和字幕路徑。

(5)input_ToggleEs(input_thread_t*p_input,es_descriptor_t*p-es,boolean_t b_select):用來清除界面上的被選擇的基本流。

4.5 緩衝器的管理

輸入插件必須要執行分配和刪除分配包的功能,這個功能的實現要用到以下四個基本函數:

(1)pf_new_packet(void*p_private_data,size_t i_buffer_size):關聯到緩衝器的i_buffer_size的大小,分配一個新的data_packet_t。

(2) pf_new_es(void*p_private_data):分配一個新的pes_packet_t。

(3)pf_delete_packet(void*p_private_data,data_packet_t*pes_data):刪除p_data。

(4) pf_delete_es(void*p_private_data,pes_packet_t*p_pes):刪除p_pes。

以上四個函數都把p_input->p_method_data(即*p_private_data)作爲第一個參數,因此可以保存這個分配紀錄和釋放包。緩衝器的管理可以有以下三種方法:

(l)傳統的libc分配:在PS插件中每次經常要用到malloc()和free()函數來分配和刪除分配包,這並不像想象中的那麼慢。

(2)Netlist:在包有問題的開頭處用這種方法來分配一個非常大的緩衝空間,然後通過管理指針列表來釋放包(這就是netlist)。這種方法只有當所有的包都是同樣大小時效果纔會很好,它長時間用在TS輸入上。DVD的插件也會用到它,但是要把加入的標誌符也考慮進去,這是因爲緩衝器(2048bytes)通常被若干個包所共享。現在這個方法己經不被使用而且也沒有文檔記錄。

(3)緩衝器的cache:這種方法是目前在發展中的最新方法,它己經用在了PS的插件中。它的思想是調用malloc()和free()來獲得流的不規則規律,然後經過cache系統後重新使用所有已被分配好的緩衝空間。現在的工作就是要擴展它在性能沒妨礙的前提下可以用到任何插件裏,但是目前還沒有這方面的參考文檔。

4.6 流的分路

流文件被pf_read讀取後,插件必須把一個函數指針給分路函數,這個分路函數用來解析包,收集PES並把包送到解碼器裏。這個分路函數是專門爲標準的MPEG結構(PS和TS)來寫的,必須對pf_demux表明input_DemuxPS和input_DemuxTS,當然也可以自己寫這個函數。

5. 解碼器技術

解碼器做播放流的數學處理部分。它從輸入組件的多路分解器(modules/demux目錄下,管理包以重新編譯連續的基本流)和輸出線程(包的樣本被解碼器重構並且播放它)分離出來。基本上解碼器只是純粹的算法,不和硬件打交道。

5.1 解碼器結構分析

         輸入線程調用Input_DecoderNew(src\input\decoder.c)派生合適的解碼器,通過CreateDecoder選擇更恰當的解碼器組件,每一個解碼器組件都要檢查decoder_config.i_type然後返回一個記錄,然後和decoder_config_t一起運行module.pf_run()。一般的decoder_config_t的結構給出瞭解碼器的ES ID和其類型,一個指向stream_control_t結構(給出播放狀態的信息)的指針,decoder_fifo_t和pf_init_bit_stream函數,然後運行DecoderThread(解碼器主循環)來解碼。CreateDecoder:將解碼器結構中的輸出回調函數指針(音頻,視頻,SPU)附值,這些函數建立或者銷燬這些輸出設備,解碼後的數據由此被正確的傳遞出去;調用Module_Need將特定的解碼器組件加載進解碼器結構,組件的回調函數指針pf_activate(通常是open函數)完成對pf_decode_XXX函數指針的附值(其中的音頻,視頻函數指針用剛纔解碼器的相應輸出回調函數的包裹函數附值)。DecoderThread:調用被賦予特定函數值的pf_decode_XXX函數指針進行解碼工作。

5.2 數據流包結構分析

         這個輸入組件(包)爲傳遞到解碼器的流數據提供高級的API。首先先看這個包的結構,它被定義在include/input_ext-dec.h裏。

data_packet_t包含一個指向數據物理地址的指針,解碼器讀取數據只能從p_payload_start開始到p_payload_end結束,然後如果p_next不空的話,解碼器就跳轉到下一個包。如果b_discard_payload的標誌位顯示時則說明包地內容是混亂的因此這個包就會被丟棄。data_packet_t被包含進pes_packet_t裏,pes_packet_t表示出了可以表明一個完全的pes包的data_packet_t鏈表目錄。對於PS流,一個pes_packet_t通常只包含一個data_packet_t。而對於TS流,一個PES被分解在許多的TS包裏。PES包裏含有PTS日期(在MPEG規範裏有詳細說明)和目前的讀取速度,這個可以用於修改日期(i_rate)。

b_data_alignment(如果在系統層裏可用的話)表明假如這個包是隨機的接入點的話,那麼b_discontinuity就會知道先前的包是不是被丟棄過。

PES Packet 對於節目流的PES包結構圖

對於節目流,PES包只含有一個數據包,它的緩衝器包括PS頭,PES頭和數據的有效負載。

對於傳輸流的PES包結構圖

對於傳輸流,PES包可以含有無限制的數據包(上圖是三個),它的緩衝器包括TS頭,PES頭和數據的有效負載。

這個結構由輸入和decoder_fifo_t解碼器共同組成,它的功能是PES包的循環FIFO被解碼。輸入爲FIFO提供了一些宏指令:DECODER_FIFO_ISEMPTY,DECODER_FIFO_ISFULL,DECODER_FIFO_START,DECODER_FIFO_INCSTART,DECODER_FIFO_END,DECODER_FIFO_INCEND。在對FIFO進行任何操作前一定要用p_decoder_fifo->date_lock。下一個包被DECODER_FIFO_START(*p_decoder_fifo)所解碼。當這些完成後,要先調用p_decoder_fifo->pf_delete_pes(p_decoder_fifo->p_packets_mgt,decoder_fifo_start(*p_decoder_fifo))然後調用ecoder_fifo_nestart(*p_decoder_fifo),使得把PES返回到緩衝器管理。如果FIFO是空的話(DECODER_FIFO_ISEMPTY),可以在攜帶着條件信號(Vlc_cond_wait(&p_fifo->data_wait,&p_fifo->data_lock))的新的信息包到達之前先中斷它(加鎖)。當文件被播放完或者用戶退出時,p_fifo->b_die要設置成1,這意味着要釋放所有的數據結構並且要儘快調用到vic_thread_exit()。

5.3 比特流分析

由於基本流可以任意地別分割,因此傳統的讀包方法並不方便。這個輸入組件就提供了容易讀取比特流的方法。這個可選可不選,如果選了話就不必再進入緩衝器了。

這個比特流允許調用GetBits(),當有必要時這個函數在不干擾用戶的情況下讀取包的緩存、修改數據包和pes包。因此對於讀取連續的基本流會非常方便,用戶不必去處理包的邊界和FIFO,這些由比特流來處理。

下面着重分析一個32位的緩衝器:bit_fifo_t。它包括字緩衝和一些重要的bit(高位),這個輸入組件提供了以下五個內聯函數來管理這個緩衝器:

(1)GetBits(bit_stream_t*p_bit_stream,unsigned_int i_bits):

從比特緩衝返回到下一個1bit。如果沒有足夠多的比特時,這個函數就從decoder_fifo_t裏抓取一個字的大小來補充。這個函數只能保證工作在24bits,有時也會工作在31bits,但這是副作用。要是使其可以讀取32bit,就必須修改這個函數。

(2)RemoveBits(bit_stream_t*p_bit_stream,unsigned_int i_bits):

這個函數除了比特不返回外(爲了節省CPU循環)和GetBits()一樣,它同樣有其的侷限,因此在必要時也得修改這個函數。

(3)ShowBits(bit_stream_t*p_bit_stream,unsigned_int i_bits):

除了在讀取完沒獲得多的比特外和GetBits()一樣,因此接着需要調用RemoveBits()。要注意的是除非對字節進行排列的話那麼這個函數不能工作在高於24bits的情況下

(4)RealignBits(bit_stream_t*p_bit_stream):

爲了使緩衝的前幾個比特排列成字節的邊界這個函數丟棄掉比特流的高n(n<8)位,這有利於找到排列好的開始代碼(MPEG)。

(5)GetChunk(bit_stream_t*p_bit_stream,byte_t*p_buffer,size_t i_buf_len):

它和函數memcpy())類似,只不過它把比特流作爲第一個參數。P_buffer必須被分配並且至少要i_buf_len這麼大,這有利於用戶要保存路徑時備份數據。所有以上的函數重新構造了連續的基本流範例。當比特緩衝器是空的話,這些函數就在當前包裏取隨後的字;當包是空的話,就轉到下一個data_packet_t,如果轉不到的話就轉到下一個pes_packet_t(見

p_bit_stream->pf_next_data_packet),所有的這些都是明晰的。爲了能用到比特流,必須要調用p_decoder_config->pf_init_bit_stream(bit_stream_t*p_bit_stream,decoder_fifo_t*p_fifo)來設置所有的變量。這可能需要從包裏獲得規範的信息(比如說對於PTS)。如果p_bit_stream->p_bit_stream_callback不空的話,那麼包必須要改變(在較低版本中的video_parser.c有例子說明),調用的函數的第二個參數表明它是個新的data_packet還是新的pes_Packet_t,這個結構可以保存到p_bit_sream->p_callback_arg。

當要調用pf_init_bit_stream時,pf_bitstream_callback還沒有被定義,因此只能先轉到第一個包,這個可以在調用pf_init_bit_stream後通過指針再來回調比特流。

5.4 VLC內置解碼器

         VLC內置了MPEGI,2層的音頻解碼器,一個MPEG MP@ML的視頻解碼器,一個AC3(來自LiViD)解碼器,一個DVD SPU解碼器和一個LPCM解碼器。可以仿照視頻解析器用戶可以自己寫特定功能的解碼器。

MPEG音頻解碼器雖然是本地化的,但是並不支持第三層解碼(這個非常不方便),AC3解碼器是來自於Aaron Holtzman’s libac3的端口,SPU解碼器也是本地化的。這些可以在AC3解碼器裏的比特流的回調裏有說明。這這種情況下,必須要跳過PES的前三個字節(不是基本流的部分)。

對於MPEG視頻解碼器,VLC媒體播放器在其解碼器的主層上提供了MPEG-1,MPEG-2的主要框架,這對於VLC來說己經非常成熟。既然這個主要框架被視頻解析器和視頻解碼器這兩個邏輯實體所分開,因此它是面向比特的,它最初的目的是把比特流解析功能從高並行的數學算法中分離出來。在理論上,這裏必須只有一個視頻解析線程(否則在讀取比特流時會產生混亂)和視頻解碼線程池,這些用來做即時IDCT和在一些塊上做動態補償。它不支持也不會支持MPEG-4或者DivX解碼。它不是一個編碼器。儘管還有部分沒被測試(比如說差分動態向量)但是它完全支持MPEG-2 MP@ML規範。對於這個解碼器最有趣的文件是vpar_synchro.c,它解釋了dropping算法的全部框架。在nutshell裏,如果它足夠強大的話就能對全部的IPB進行解碼,或者在用戶有足夠的時間的話就能對全部的IP和B進行解碼(這個基於on-the-fly時間統計)。另一個有趣的文件vpar_blocks.c,它描述了全部的塊(包括係數和動態向量)解析算法,在這個文件的最後需要產生一個對普通圖片類型的最優化函數和一個減緩類屬函數。這裏同樣有不同程度的最優化函數(可以慢速編輯文件但快速解碼)爲VPAR_OPTIM_LEVEL,level 0表示沒優化,level 1表示優化MPEG-l和MPEG-2圖片框架,level 2表示優化MPEG-1和MPEG-2域和圖片框架。

5.5 動態補償技術分析

         動態補償技術是非常依賴於平臺的(比如MMX或者AltiVec版本),因此必須把它放plugins\Motion目錄下。這對於視頻解碼器來說是非常方便的,這樣的插件可以用到其他的視頻解碼器中。一個動態的插件必須定義6個函數(直接來自於規範): vdec_MotionFieldField420,vdec_MotionField16x8420,vdec_MotionFieldDMV420,vdec_MotionFrameFrame420,vdec_MotionFrameField420,vdec_MotionFrameDMV420。而對於在MP@ML標準裏被禁止的格式自然是不能用(因爲需要的編譯的時間長)。

5.6 IDCT技術分析

         像動態補償一樣,IDCT技術也是一種平臺規範。因此需要把它放到phigins\idct目錄下。這個模塊是用來做IDCT運算和把數據複製到最後的圖片上,這裏需要定義以下7種方法:

(1)vdec_IDCT(decode_config_t*p_config,dctelem_t*p_clock,int):

做完全的2-D IDCT運算。64個係數在p_block裏。

(2)vdec_SparseIDCT(vdec_thread_t*p_vdec,dctelem_t*p_clock,int i_sparse_pos):

只用一個非零係數(有i_sparse_pos來設計)在塊上做IDCT運算。可以把這個函數定義在phigins\idct\idct_common.c(在初始化時間段裏對這些64位的矩陣進行預計算)裏。

(3)vdec_InitIDCT(vdec_thread_t*p_vdec):

對vdec_SparselDCT做初始化的填充。

(4)vdec_NormScan(ppi_scan):

通常情況下,這個函數不做任何運算。在做一些較小的優化時,用它來對MPEG導航矩陣(在ISO/IEC 13818-2裏有詳細介紹)做某些係數的求逆運算。

(5)vdec_InitDecode(struct vdec_thread_s*p_vdec):

初始化IDCT和選擇配置列表。

(6)vdec_DecodeMacroblockC(struct vdec_thread_s*p_vdec,struct macroblock_s*p_mb):

對整個宏塊解碼並把這個數據複製到最終要的圖片裏(包括彩色信息)。

(7) vdec_DecodeMacroblockBW(struct vdec_thread_s*p_vdec, struct macroblock_s*p_mb):

對整個宏塊解碼並把這個數據複製到最終要的圖片裏(除了彩色信息),它用在gayscale模式裏。

5.7 對稱多處理插件技術分析

在必要情況下,VLC的MPEG視頻解碼器將會利用到多處理器。這個思想來自於解碼器的池,這個池可以在多個宏塊上同時實現IDCT/動態補償功能。這個管理池的模塊在src\video——decoder\vpar_pool.c裏。但是這個模塊的處理速度要慢一些。

6 視頻輸出層技術

6.1 數據結構和主循環

重要的數據結構被定義在inchide\vlc--vidco.h和indude\video-output.h裏。事實上VLC的SPU解碼器只能解析SPU報頭和把SPU圖片數據轉化成VLC的格式,這樣做是爲了爲能處理的快一點。部分類似SPU解碼器在src\video_output\video\spu.c裏。

picture_t:是主要的數據結構,描述了視頻解碼器線程需要的一切。其中的p_data是一個YUV平面圖的指針;

subpicture_t:存儲subtitle部分(一個視頻文件包括audio,video,subtiiles);

vout_thread_t:一個很複雜的合成結構。

基本上video輸出線程管理一堆圖像和子圖像(默認5個)。每一個圖像有一個狀態(顯示,消耗,清空等等)和確定的播放時間。視頻輸出的主要工作是一個無限循環:

(1)在堆上找到下一個要顯示的圖像。

(2)找到當前要顯示的子圖像。

(3)翻譯圖像(如:視頻輸出插件不支持YIJV):調用最佳的YUV插件,做縮放比例,添加subtitles和一個可選的圖像字段。

(4)在特定的時間到來之前休眠。

(5)視頻插件顯示圖像(通常在緩衝器轉換時輸出)。p_vout->p_buffer是兩個緩衝器(用於YUV的轉換)的排列,p_vout->i_buffer_index表明當前顯示的緩衝器。

(6)管理事件。

6.2視頻解碼器的方法

視頻輸出導出了一些函數,以便解碼器能夠發送它們已經解碼過的數據。

(1)picture_t*vout_CreatePicture(vout_thread_t*p_vout,vlc_bool_t b_progressive,vlc_bool_t b_top_field_first,unsigned int i_nb_fields):

這個是最重要的函數,它分配視頻解碼器指示的緩衝器,然後用解碼後的數據反饋到(void*)p_picture->p_data裏,必要時調用vout_-DisplayPicture和vout_DatePicture。最後返回已分配好的視頻緩衝器。比如i_type表示YUV_420_PICTURE,i_width和I_height表示像素。如果堆裏沒有圖像的話,那麼這個函數將返回爲空。

(2)vout_Lillkpieture(vout_thread_t*p_vout,pieture_t*p_pic):

增加圖像的引用計數,以便在解碼器還需要它的時候不會意外將它釋放。比如說一個I圖片或者P圖片在已經被解碼和交叉存取爲B圖片後可能還要用到。

(3)vout_UnlinkPicture(vout_thread_t*p_vout,picture_t*p_pic):

減少圖像的引用計數,等於0時就可以將圖像釋放。

(4)vout_DatePicture(vout_thread_t*p_vout,picture*p_pic):

賦予圖像的播放時間,可以在圖像在開始播放時就知道它什麼時候播放結束。比如說當賦予I或則P圖片時間時,必須等到先前所有的B圖片全部被解碼完。

(5)vout_DisplayPieture(vout_thread_t*p_vout,picture_t*p_pic):

通知視頻輸出端一個圖像已經被解碼等候顯示。可以在vout_DatePicture前後調用。

(6)vout_DestroyPicture(vout_thread_t*p_vout,picture_t*p_pic):

爲圖片做空標記,減小視頻輸出的內存堆的尺寸(在流解析錯誤時非常有用)。

(7)subpieture_t*vout_CreateSubPicture(vout_thread_t*p_vout,int i_channel,inti_type):

返回到一個已分配好的子圖片緩衝器。i_channel表示子圖片信道的ID,i_type表示是DVDSUBPICTURE還是TEXTSUBPICTURE,i_size表示包的字節長度。

(8)vout_DisplaySubPicture(vout_thread_t*p_vout,subpicture_t*p_subpic):

通知視頻輸出一個子圖象已被完全解碼,廢除解碼前的子圖象。

(9)vout_DestroySubPicture(vout_thread_t*p_vout,subpicture_t*p_subpic):

爲子圖片做空標記。

7 音頻輸出層技術

7.1 音頻輸出概況

音頻輸出的主要目的是從一個或者幾個解碼器(也叫“輸入流“)得到聲音採樣,然後混合它們並且寫到輸出設備(也叫“輸出流”)。在這個過程中,轉換可能需要,也可能被用戶要求,由音頻過濾器來完成這個過程。

以下爲常用的音頻術語:

採樣:採樣是音頻信息的基本部分,包括所有信道的值。例如不管有多少信道被編碼也不管係數的編碼類型,流工作在44100HZ就是表明流每秒採樣44100個音頻元素。

幀:任意大小的一組採樣。編解碼通常有固定的幀大小(比如一個A/52幀含有1536個採樣元素)。既然幀以任意大小的方式管理緩衝器,因此它對於音頻輸出不是很重要。但是對於還沒被解碼的文件格式,由於要依賴於流的壓縮率,因此必須要指明一個有n個採樣元素的幀所需要的字節數。

係數:一採樣元素對於每個信道都含有一個係數。例如一個立體聲流的每一個元素裏有兩個係數。許多音頻項(例如float32位的音頻混音器)直接處理係數。由於一採樣元素不能單獨的在流裏被事例化,因此一個沒被解碼的元素格式當然就沒有係數的概念。

重採樣:改變音頻流每秒的採樣數。

上下混頻:改變信道的配置。

7.2 音頻採樣格式

整個音頻輸出可以別看成以連續的步驟來把一種音頻格式轉換成另一種音頻格式的管道,這對於理解音頻採樣格式非常重要。audio_sample_format_t結構被定義在incfude/audio_output.h裏,它包括以下的成員:

(1)i_format:

定義了係數的格式。比如:’fl32’(float32),’fi32’(fixed32),’s16b’(有符號的16bit字節以big-endian方式存儲),‘s161’(有符號的16bit字節以little-endian方式存儲),AOUT_FMT_S16_NE(‘sl6b’或者’s16l’的截短),’u16b’,’u16l’,’s8’,’u8’,’ac3’,’spdi’(S/PDIF)。沒被解碼的採樣格式包括:’a52’,’dts’,’spdi’,’mpga’(MPEG音頻I,II層),’mpg3’(MPEG音頻III層)。音頻過濾器(允許從一種格式到另一種格式)被定義爲轉換器,有些轉換器扮演着解碼器的角色(比如a52tonoat32.c),但是實際上它是音頻過濾器。

(2)i_rate:

定義了音頻輸出每秒要處理的採樣數。通常這個值爲22050,24000,44100,48000,單位爲HZ。

(3)i_physical_channels:

定義在緩衝器裏被自然編碼的信道。它掩蓋了在audio_output.h定義的比特值(比如AOUT_CHAN_CENTER,AOUT_CHAN_LEFT等)。要注意的是:這個數字值並不代表每個採樣含有多少個係數(aout_FormatNbChannels()裏有說明),由於對於混音器處理交叉存取的係數是比較容易的,因此每個信道的係數也總是交叉存取的,從而一個來可以輸出平面數據的解碼器必須能實現交叉存取的功能。

(4)i_original_channels:

定義用於組成緩衝器的原始流的信道。比如說只有單一輸出的插件但要輸出的音頻流是立體聲時,那麼可以同時使用流的信道(i_original_channels == AOUT_CHAN_LEFT| AOUT_CHAN_RIGHT)或者選擇一個信道。

i_original_channels和i_physical_channels使用同一個比特掩碼,並且表明了專用的比特流AOUT_CHAN_DOLBYSTEREO(表明輸入流是否下行混音成杜比環繞聲)和AOUT_CHAN_DUALMONO(表明立體聲流實際上是由兩個單一的流所組成的),這兩個專用的比特流只能選擇一個(就像在一個VCD上可以有兩種語言)。

對於16位的整型格式的音頻類型,可以區分開big-endian和little-endian的存儲類型。但對於浮點型的音頻類型(可以以big-endian存儲或者用little-endian存儲)卻無法分別開來。這是因爲採樣的數據是在文件裏被強制儲存成32位的浮點類型並且從一個機器轉移到另一個機器中,因此這32位的浮點類型數據以本地機器的endian來默認儲存。然而採樣的數據一般都是以16位的整型類型big-endian方式來儲存的(比如DVD的LPCM格式),因此LPCM解碼器分配’s16b’,給輸入流,在little-endian機器上也是這樣,’s16b’->’s16l’的轉換被輸入管道自動的激活。(在大多數情況下,AOUT_FMT_S16_NE和AOUT_FMT_U16_NE要被用到)。音頻輸出內核提供了宏來比較兩種音頻採樣格式。AOUT_FMT_IDENTICAL)用來驗證是否i_format,i_rate,i_physical _channels和i_original_channels一樣,AOUT_FMT_SIMILAR()用來驗證i_rate和i_channels是否一樣(有利於寫一個純粹的轉換過濾器)。

audio_sample_format_t結構包含兩個額外的參數(除非處理沒被解碼的音頻格式就不需要有)。對於PCM格式這兩個參數被自動添充到aout_FormatPrepare()(必要時被內核模塊所調用)。下面說明這兩個額外的參數:

i_frame_lenth:定義普通幀採樣的數目。對於A\52,1536個採樣是壓縮後在沒被解碼的緩衝器裏的數目,那麼i_frame_length=1536;對於PCM格式,幀的大小爲1(在緩衝器裏的每一個採樣值都能獨立的獲得)。

i_bytes_per_frame:定義幀的大小(字節)。對於A\52它取決於輸入流(同步讀取)的比特率,例如對於32位浮點類型的立體聲採樣,i_bytes_per_frame == 8(i_frame_length=1l)。

以上兩個參數(對於調用aout_FormatPrepare()非常有意義)方便計算音頻過濾器(i_nb_samples*i_bytes_per_frame/i_frame_length)的大小。

       7.3 音頻運行過程

輸入派生了一個新的音頻解碼器(比如A\52解碼器)。A\52解碼器爲格式信息解析同步信息並且用aout_InPutNew()來創造出輸入流的新的音頻輸出,這個採樣格式爲:

i_format=’a52’

i_rate=48000

i_physical_channels=i_original_channels=AOUT_CHAN_LEFT|AOUT_CHAN_RIGHT|AOUT_CHAN_CENTER|AOUT_CHAN_REARLEFT|AOUT_CHAN_REARRIGHT|AOUT_CHAN_LFE

i_frame_length=1536

i_bytes_per_frame=24000

這個輸入格式不能被修改且存儲在aout_input_t structure裏和p_aout->pp_input[0]->input這個輸入流一致。既然這個是輸入的第一個流,音頻輸出將會用這個音頻採樣格式(p_aout->output.output),來配置輸出設備以避免不必要的轉換。

音頻內核用通常的方式來探測輸出模塊,輸出模塊的行爲取決於輸出設備,如果輸出設備有S/PDIF能力的話,那麼就需要設置p_aout->output,把output .i_format轉換成’spdi’格式,如果只是PCM設備的話就需要知道本地的採樣格式(比如用於Darwin CoreAudio的’fl32’或者用於OSS的AOUT_FMT_S16_NE)。輸出設備也可能因信道的數目和流的速率不同而受到限制(例如p_aout->output)。輸出結構可以如下:

i_format =AOUT_FMT_S16_NE

i_rate =44100

i_channels=AOUT_CHAN_LEFT|AOUT_CHAN_RIGHT

i_frame_length=1

i_bytes_per_frame=4

一旦獲得了輸出的格式,那麼需要根據它來推出混音器的格式。除了i_format以外,禁止在混音器和輸出之間來改變音頻採樣格式(所有的轉換在輸入管道里發生),這是因爲需要開發出三個混音器(內置設備的float32 and S/PDIF和plus fixed32),所有其它的類型必須轉化成這三個中的一個。下面以p_aout->mixer爲例,混音器的結構如下:

i_format =’fl32’

i_rate=4410

i_channels=AOUT_CHAN_EFT|AOUT_CHAN_RIGHT

i_frame_length=1

i_bytes_per_frame=8

音頻輸出內核因此要分配音頻過濾器來把’fl32’轉換成AOUT_FMT_S16_NE,這是在輸出管道里唯一的音頻過濾器,同時也要分配一個32位浮點類型的混音器。由於只是一個輸入流,因此用一個很小的混音器就好了(只需從第一個輸入流裏複製採樣數據即可),否則要用到更準確的32位浮點類型的混音器。

初始化的最後一步是編譯輸入管道。當需要更改幾個屬性時,音頻輸出內核將會優先搜索音頻過濾器可以更改的屬性:

(l)所有的參數

(2)i_format和i_physical_channels/i_original_channels

(3)i_format

如果整個的轉換不能用一個音頻過濾器來完成的話,音頻內核就需要分配第二個甚至第三個過濾器來處理剩餘的轉換。接着上面的例子,分配兩個過濾器:

a52到float32(用來處理這種轉換和下行混音)和重採樣器。出於對效率的考慮,通常對於沒被解碼的格式轉換器也要處理下行混音。

當初始化完成後,解碼器插件模塊就會運行自己的主循環。通常解碼器需要i_nb_samples大小的緩衝器並且複製沒被解碼的採樣值到這個緩衝器裏(用到GetChunk()函數)。然後這個緩衝器和輸入管道一起編碼(爲’fl32’格式),下行混音和重採樣。如果輸出層需要暫時的調節其快慢速率以獲得和輸入流完美的同步(由每一個緩衝器的基層來確定)時就需要重採樣。在輸入管道的底端,這個緩衝器將放置到FIFO上,同時解碼器線程運行音頻混音器。

這時音頻混音器就會計算採樣數是否足夠多來編譯一個新的輸出緩衝器。如果足夠的話,它就和輸入流混合經過緩衝器到達輸出層。爲了輸出設備緩衝器就順着輸出管道(在上面的例子裏只含有轉換過濾器)到達輸出FIFO。這時輸出設備就會從輸出FIFO上取出下一個緩衝器,取出的方法爲通過回調音頻子系統(Mac OS X’CoreAudio,SDL)或者通過音頻輸出線程(OSS,ALSA…)來達到。這種機制用到了aout_OutputNextBuffer()函數,並且給出了緩衝器大致的播放時間。如果計算出的播放時間和估計的播放時間不一致時(小的差別),輸出層就會在音頻輸出模塊上修改所有的緩衝器時間,當下一個從解碼器出來的緩衝到來的時候就會在輸入管道的開始處觸發再採樣。用這種方法需要重同步音視頻流,當緩衝播放時,就會最終釋放它。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章