圖3:該模塊圖說明了SimpleAudio數(shù)字音頻應用的體系結構
3 數(shù)據(jù)音頻應用的實現(xiàn)
圖3給出了數(shù)字音頻應用的體系結構。它共有6個線程,包括主線程和用Orchestrator實例表示的異步事件處理器。BufferPair將每個插座接口連接至相應的DSP接口。主線程監(jiān)控用戶指令,并在用戶請求關閉會話時調用SimpleAudio實例的terminateActivity()方法。所有其它線程通過調用continueActivity()業(yè)務,定期輪詢SimpleAudio實例。到了關機時,該方法返回false值。
在缺省配置中,該應用以8kHz采樣頻率對麥克風輸入進行采樣,每次采樣采集8比特數(shù)據(jù)。這種配置每秒鐘產生8k字節(jié)的數(shù)字音頻數(shù)據(jù),這對簡單的語音應用來說已經(jīng)足夠。但是,它不適合高保真立體聲信號。一般的CD錄制以44.1kHz的采樣頻率對兩個立體聲信道每次采樣16比特。這種高保真度信號的帶寬要求為176.4千字節(jié)/秒。
在缺省配置中,插槽讀模塊和寫模塊采用足夠的帶寬進行可靠傳輸,以可靠提供所有從DSPReader模塊采集的數(shù)據(jù)。我們采用了一種直接壓縮技術,一連串同樣的字節(jié)值(像出現(xiàn)在靜音期間的那樣)由一個專用的轉義(escape)值、重復次數(shù)和重復值表示。當然,更先進的壓縮技術將更為合適。
在實時系統(tǒng)中,由抖動描述特定實時組件的理想執(zhí)行時間的預期偏離,由一個確切的線程描述數(shù)字音頻應用的每個組件。SocketWriter線程接收來自DSPReader模塊的原始數(shù)據(jù)流,對數(shù)據(jù)進行壓縮,并將數(shù)據(jù)傳送至網(wǎng)絡插座通道。如果網(wǎng)絡插座通道的帶寬有限,只能達到預算的8千字節(jié)/秒,那么任何導致SocketWriter延遲數(shù)據(jù)傳輸?shù)亩秳佑绊憣㈦S著時間而累積。
在缺省配置中,預計SocketWriter每125s傳輸1字節(jié)數(shù)據(jù)。如果每秒的音頻數(shù)據(jù)有1個字節(jié)延遲半毫秒,則1小時后,累積延遲將約為2秒。為防止抖動延遲的累積,該架構包含一個運行在16Hz的監(jiān)視線程。
在每個周期內,該線程強制讓SocketWriter和DSPWriter組件丟棄62.5ms之前的數(shù)據(jù)。由于我們處理的是音頻數(shù)據(jù),所以通常來講,丟棄的臨時數(shù)據(jù)值比允許數(shù)據(jù)到達時間偏移更可取一些。人們通常不會注意到丟棄臨時數(shù)據(jù)字節(jié)的影響。
請注意在第1行出現(xiàn)的@StaticAnalyzable注釋,源碼列表中的@ StaticAnalyzable(enforce_time_analysis = {false}, enforce_non_blocking = {false})。這代表了部分方法簽名(method signature)。注意該注釋給出了enforce_time_analysis 和enforce_non_blocking屬性的參數(shù)值,兩者都是false。這表示該方法的實現(xiàn)無需將其本身限制在子集內,對于該子集,靜態(tài)分析器可從中推斷執(zhí)行該方法所需要的嚴格CPU時間上限,也不要求靜態(tài)分析器驗證該方法執(zhí)行時永遠不會阻斷。
如果這些屬性定義沒有給出,硬實時驗證器將認為程序不合法,因為在源碼列表的(!orchestrator.destroy()) { through 57, }執(zhí)行時,靜態(tài)分析器無法確定該循環(huán)包含了多少次第55行。此外,main方法的執(zhí)行可能會在第59行的socket_ reader_thread.join()至63行的orchestrator_thread.join()之間阻斷,以及在第51行sa.awaitTermination()調用的await-Termination()方法中阻斷。
在@StaticAnalyzable注釋中未注明的是enforce_memory_analysis屬性的值。該屬性的缺省值為true,這意味著該方法的實現(xiàn)必須符合限定的指導方針以使執(zhí)行該方法時靜態(tài)分析器能夠確定將要分配的最大內存。假設該環(huán)境的實時Java規(guī)則將內存作為運行棧的一部分,則臨時內存分配的上限就表示必需的主線程的運行時棧大小。
注釋有助于軟件開發(fā),并大大簡化軟件維護工作。通常,系統(tǒng)架構師將復雜的系統(tǒng)功能分為較小的組件,以便由不同的開發(fā)小組實現(xiàn)。因此,描述不同組件之間連接的接口定義,不僅詳細說明了可以在組件間傳遞的參數(shù)類型,還包括在每個組件中必須實現(xiàn)的實時處理的限制,能減少軟件維護方面的開銷。
對于現(xiàn)有軟件的修改必須遵從組件接口注釋中描述的所有其它特殊實時限制。如果軟件維護人員違反了這些接口要求,他們可以從字節(jié)碼驗證器得到直接、明確的反饋。從而確保現(xiàn)有大型實時軟件系統(tǒng)的不斷變化不會動搖現(xiàn)有系統(tǒng)的穩(wěn)定性。
在對可靠運行該主程序所需的堆棧內存進行分析時,靜態(tài)分析器必須確定在該方法以及該方法所調用的方法中,每個對象要求分配多大內存。為了支持靜態(tài)分析結果的模塊化合成,字節(jié)碼驗證器要求每個由主程序調用的方法被聲明為@Static-Analyzable,而enforce_time_ analysis屬性設置為true。快速復查main方法的實現(xiàn)可確保無限循環(huán)內不產生分配。這是字節(jié)碼驗證器所要執(zhí)行的任務之一。
在第37行的socket_reader_thread = new Thread-Stack(SocketReader.class);到41行的orchestrator_thread = new ThreadStack(Orchestrator.class)之間分配了幾個新的ThreadStack對象;每次分配描述了主程序派生的線程所使用的堆棧內存。一般來說,靜態(tài)分析工具可能難以確定可靠執(zhí)行這些子線程所必需的堆棧內存數(shù)量。
每個ThreadStack構造函數(shù)的參數(shù)為提供代碼由相應線程執(zhí)行的類(Class)。靜態(tài)分析器要求每個在該環(huán)境中傳遞的NoHeapRealtimeThread子類具有帶@ StaticAnalyzable注釋,且enforce_ memory_analysis屬性設置為true的run()方法。如果ThreadStack構造函數(shù)的參數(shù)并非來自BoundAsyncEventHandler(例如在Orchestrator類的情況下),則靜態(tài)分析器要求該類的asyncEventHandler()方法采用@StaticAnalyzable注釋來聲明,且enforce_memory_analysis屬性設置為true。
當前線程的運行時棧能滿足所有臨時內存需要。請注意,我們在第23行分配了兩個臨時BufferPair實例,microphone_stream = new BufferPair();而在第24行,speaker_stream = new Buffer-Pair();然后這些對象的參數(shù)被傳遞至構造函數(shù),用于包含該軟件應用的不同功能組件的各個線程。硬實時驗證器實施的限制之一在于,stack-allocated對象的參數(shù)不能比引用參數(shù)的對象本身生存時間更長,同樣是通過注釋機制來執(zhí)行。我們來看一下SocketReader類的構造函數(shù):
@ScopedPure
@StaticAnalyzable(enforce_time_analysis = {false}, enforce_non_blocking = {false})
SocketReader(SimpleAudio sa, Buffer-Pair buffers, String socket_name) throws
FileNotFoundException
@ScopedPure注釋說明該構造函數(shù)的每個輸入引用參數(shù)(reference parameter)可以指代那些位于外部嵌套作用域的運行時棧的對象。字節(jié)碼驗證器確保這些參數(shù)的內容絕不會復制到那些由于具有@Scoped指派而未被同樣區(qū)分的變量上。
此外,它禁止將內部嵌套作用域變量的值復制到外部嵌套作用域變量。一個例外情況是,在特殊環(huán)境下,它可證明帶參數(shù)對象位于與要賦值變量相同或更外層嵌套的作用域。如果這一構造函數(shù)的參數(shù)未由@Scoped注釋指定,字節(jié)碼驗證器將不允許主程序將參數(shù)傳至堆棧分配的BufferPair和SimpleAudio對象。
本應用展示的RTSJ支持的實時編程抽象之一為PeriodicTimer類。注意,本應用在第49行舉例說明了PeriodicTimer對象,drumbeat = new PeriodicTimer(start_time, period, orchestrator);并將結果賦值給本地drumbeat變量。參數(shù)之一為orchestrator對象的引用參數(shù),其本身是BoundAsyncEventHandler的一個實例。該drumbeat周期計時器被設置為每秒觸發(fā)orchestrator對象的handleAsyncEvent()方法執(zhí)行16次,即每62.5微秒一次。
采用C或C++語言的實時開發(fā)人員可以實現(xiàn)這些實時Java技術所支持的許多相同構造。但是,C或C++程序員必須產生懸掛指針(dangling pointer)以及內存泄漏,他們還缺乏標準工具的支持來自動分析執(zhí)行時間和堆棧大小。
另外,C和C++程序員還缺乏完整性檢查以確保方法的實現(xiàn)能夠滿足文檔化實時接口的要求,并確保方法調用能夠傳遞同樣滿足文檔接口要求的參數(shù)。最后,在對現(xiàn)在軟件系統(tǒng)進行維護的過程中,C和C++程序員沒有工具支持來保證對現(xiàn)有軟件的修改與在原軟件開發(fā)過程中假設的各種組成要求是相符的。
傳統(tǒng)Java在生產效率和成本上具有許多優(yōu)勢。規(guī)范地使用實時Java技術可提供許多這樣的優(yōu)勢。與使用C和C++相比,一般Java程序員在新代碼開發(fā)期間具有2倍的生產率,而在現(xiàn)在軟件維護期間具有5到10倍的生產率。隨著嵌入實時軟件的大小和復雜度增加,這些激發(fā)人們向更現(xiàn)代的軟件工程技術(如由實時Java實現(xiàn)的工程技術)轉化的因素已越來越重要了。
評論