2010年4月26日 星期一

在Ubuntu下使用What U Hear的錄音功能

從9.10版之後,Ubuntu便大幅簡化使用者控制音效系統的複雜度,除了三大常用的音效程式庫Alsa、OSS、PulseAudio有更好的整合以外,還加入了類似Vista系統的方式,可獨立控制每個應用程式的音量,對於系統中同時存在多個音效卡的輸入輸出選擇,更是比以前簡單太多。以往常遇到A程式有聲音,B程式就沒有聲音的問題,在9.10後幾乎不復見。

但是以前亂調亂選的時候還可以使用類似Windows的What U Hear(有些音效卡驅動程式翻譯成「你聽到的聲音」或者「立體聲混音」),來錄下系統發出的聲音,9.10後除了硬體輸入的端子以外(Line-in、Mic等),聲音來源就沒別的可以選了,使得每次要做這類應用時,就得要改用舊版Ubuntu或Windows才能進行。

Google了一下,原來有不少人需要這個功能,也不少人提出自己的解決方法,不過我認為最簡單的是以下的方法:要在Ubuntu下使用What U Hear的功能,請先安裝PulseAudio Volume Control(可透過Synaptic套件管理程式安裝pavucontrol套件)。安裝完成後,啟動在「應用程式->影音->PulseAudio Volume Control」下的音量控制程式,點選Input Devices,然後在Show那邊把選項改成All Input Devices,就可以看到「Monitor of Internal Audio Stereo」裝置了,按下旁邊綠色打勾的按鈕,預設的錄音裝置就變成What U Hear了,這時就可以快樂的進行錄音囉!不過最終還是希望未來版本的Ubuntu可以在內建音量控制程式開放這個功能啦。




2010年4月12日 星期一

NUC501-實作虛擬記憶體(RAM篇)

自NUC501上實作ROM虛擬記憶體成功之後,便想趁著熱血還沒退燒之前,規劃RAM虛擬記憶體的實作。最原始的想法,是在SD卡上建立一個交換檔,接下來跟ROM虛擬記憶體的方式一樣,利用資料例外服務函式,透過交換檔完成記憶體的回寫、載入與mapping的動作,這樣就能在程式未察覺的情況下,順利的把程式所需要的記憶體生出來!

為了達成這個目的,最基本的,就是需要一個能夠處理檔案系統的程式庫。從NUC501的資料上得知,NUC501有已經編譯好的MiniNVTFAT程式庫,能夠讓我們在NUC501上處理檔案系統,經過一番考慮之後,讓我打消原先想移植自己寫的APSlib到NUC501上的念頭,雖然MiniNVTFAT沒支援長檔名,也不是什麼大問題,畢竟人家有寫好的東西幹麼不用呢?

但進行MiniNVTFAT初步測試時,便遇到莫大的阻礙,ld一直說無法連結某個section,在檢查程式庫的組成之後,才發現這個程式庫是用ARM Developer System編譯的,且因為採用不同的section命名,所以ld才無法連結!原本想自行重新編譯程式庫,CD上卻沒附上MiniNVTFAT的原始程式...好吧,看來還是維持原案,移植APSlib到NUC501上了。

不過APSlib從出生到現在,雖然是堪用的狀態,但內部已存在的bug一直都沒有修正,再加上大量使用malloc/free函式,除了拖慢速度以外,我也不確定目前所使用的GCC程式庫是否能正確執行malloc/free(畢竟這套開發系統是從其他系統硬改來的),所以想改成靜態陣列的方式,並縮小記憶體的使用量,剛好可以趁這個機會好好地把APSlib整理一番。

在這邊介紹一下APSLib,APS全名是Adaptability PocketOS Serve(適應性小型作業系統服務),主要的目的,是當時為了能應付老闆經常變化的硬體軟體規格,希望能寫一套不管硬體怎麼變化,軟體需求怎麼變化,都有適應性的檔案處理程式庫,所以規劃出幾個模組:

  1. 最底層的I/O驅動程式,提供基本的讀寫功能。這樣可以在硬體尚未完成前,先在PC上利用fake file模擬磁碟機或記憶卡的運作,等硬體完成之後再改成控制該I/O實際運作的驅動程式。在NUC501的移植上,這部份可以直接呼叫DrvSDCard程式庫來處理,只要幫DrvSDCard弄個殼層,使其相容APSlib就好了,這是APSlib移植工作最輕鬆的部份。
  2. I/O管理服務,其運作方式就如同PC上的BIOS INT 13h,提供上層直接存取磁碟的服務。由於之前記憶卡插拔的相關處理沒做好,所以趁這個機會加上去,快做完了才發現開發版上SD Detect沒接,DrvSDCard程式庫也沒偵測...
  3. 統一快取,架構在I/O管理服務之上,目的是為解決上層程式需要巨大緩衝區,卻沒有足夠記憶體的困擾。
  4. 檔案系統過濾器(file system filter),主要提供檔案系統的管理,檔案過濾器包含一個呼叫通道供上層使用,舉凡開檔、關檔、目錄處理等相關的檔案動作,都是過濾器提供的功能。處理過程中過濾器需將原生檔案系統的資訊,轉換為APSlib自訂資訊,這樣不管哪個檔案系統都會傳遞一致的資訊,異種檔案架構便能以相同的介面處理,而上層程式就不需要額外關心某些檔案系統的細節了。
  5. 檔案系統過濾器服務,負責管理檔案系統過濾器,並將過濾器提供的功能,以API的形式供上層呼叫。
  6. 系統服務,負責進行目錄分析、模擬已知的函式庫功能,如fopen等。

現在想想這樣的架構不太適合小系統,應該要再簡化才是。移植過程中,也順便思考RAM虛擬記憶體的實作細節,不過愈是深入思考,愈覺得透過資料例外的方式達成很困難。主要的原因是:

  1. APSlib及相關程式碼必須存放在RAM中,才能避免指令預取與資料例外重入問題,而RAM扣除虛擬記憶體使用的頁面已經剩下不多了,根本放不下!解決之道是把APSlib放在SpiROM中執行,但發生swap時效能可能會慢到不行。
  2. 就算把APSlib放到SpiROM中的執行效能可以接受,APSlib的某些設計會借用堆疊暫時存放資料,而例外能提供的堆疊很小。就算擴大,也會因為這區域必須保留而嚴重壓縮到可用的變數空間。
  3. ARM7TDMI沒有記憶體讀寫的管理旗標,無法知道頁面是否已修改過,所以為了防止資訊流失,只好假設頁面被修改過,那頁面切換時,就一定要執行回寫的動作。當發生大量只讀取虛擬記憶體時,對效能的衝擊會很大。
因為以上幾點的問題還蠻棘手的,所以用例外主動切換頁面的方式可以說無法運作,那怎麼辦呢?想想只好改為被動執行,由程式主動告知何時該切換頁面。因為不想弄得太複雜,所以以最簡單的方式,只要三個函式來處理虛擬記憶體就夠了:

bool InititalVM(void); // 初始化虛擬記憶體

// 要求以唯讀方式鎖定虛擬記憶體
_ULONG vmLockReadMemory(void *pVMAdr,_ULONG ByteCount);

// 要求以寫入方式鎖定虛擬記憶體
_ULONG vmLockWriteMemory(void *pVMAdr,_ULONG ByteCount);

其運作方式如下:
  1. 在link script中,建立一個.vm的section,然後把需要放在虛擬記憶體的變數,都安排在這個section內。目前我們指定的位址是在0x04000000的空間上。
  2. 初始化虛擬記憶體時,在記憶卡上建立一個交換檔,並將.vm的資料從SpiROM全部寫到該交換檔內,這樣就等於完成.vm區的變數初始化。
  3. 當程式需要使用虛擬記憶體內的變數時,程式必須告訴vm管理程式,所需的記憶體位址與長度,屬性是要寫還是唯讀,程式可以透過vmLockWriteMemory及vmLockReadMemory來要求。mapping成功後,這二個函式會回報實際可用的長度,讓程式可以在安全範圍內讀寫虛擬記憶體。
  4. 當執行vmLockWriteMemory及vmLockReadMemory時,函式內會先調整程式需求的長度,由於目前可用的頁面只有二頁,所以如果超過二個頁面能呈現的長度(目前是4096 bytes)就要進行調整,再來檢查頁面資訊與新頁面是否相同,如果不同,vm管理程式會依頁面的屬性來決定是否透過fseek及fwrite的方式將該頁面資料回寫到交換檔中,最後將RAM mapping到新的位址上,再透過fseek及fread將新頁面的資料載入,結束時回傳可用的長度給呼叫的程式。
  5. 程式得到可用的長度後,便可以愉快的在安全範圍內直接使用虛擬記憶體變數了。

以下是簡單的程式範例:
// 存放在虛擬記憶體的變數
__attribute__((section(".vm")) _ULONG TestVM[256];


// 鎖定記憶體,且屬性是寫入,長度為4 bytes
_ULONG cLock=vmLockWriteMemory(&TestVM[7],sizeof(_ULONG));

// 直接存取該變數
TestVM[7]=0x12345678;

只要遵守這樣的規則,用起來其實還算簡單,也不會增加太多程式碼,效能上就必須由程式主動關心了,比方說在虛擬記憶體的排列上多費點心思,可以大幅減少交換檔的讀寫動作。

既然已經有了簡易的虛擬記憶體系統了,也可以用堆疊的方式做簡單的動態配置,只要把交換檔設大一點就好了。

// 從虛擬記憶體要求一塊長度為ByteCount的記憶體
void *vmPushBuffer(_ULONG ByteCount);
// 將配置的記憶體數量還回去
void vmPopBuffer(_ULONG ByteCount);
  1. vmPushBuffer只要將堆疊指標減去ByteCount,並回傳該位址在虛擬記憶體內的位址即可。
  2. vmPopBuffer只是簡單的把ByteCount加回堆疊指標。
由於這個功能沒有進行檢查,所以一切都需要由程式自行把關,以下是使用範例:

_UBYTE *pVMBuf=vmPushBuffer(262144);//配置256K的記憶體
_ULONG ByteLen,cLock;

ByteLen=262144;

// 清除配置區內的資料

while(ByteLen)
{
cLock=vmLockWriteAddress(pVMBuf,ByteLen);
memset(pVMBuf,0,cLock);
pVMBuf+=cLock;
ByteLen-=cLock;
}

// 用完配置區就歸還

vmPopBuffer(262144);

雖然整體的解決方案看起來有點可笑,不過完整的管理程式也代表會多耗費記憶體,這對我們希望記憶體盡量留給其他程式的需求不合,所以能用簡單的方法擴充可用記憶體,而且不難用才是我們要的結果囉~~