==> 2012年10月11日 星期四 <==

ubifs 應用案例




1. 環境
CPU : Freescale MX27
NAND FLASH : Samsung K9F2G08U0B, 2Gbits = 256MB
操作系統: linux
內核版本: 2.6.27

2. linux 編譯選項
2.2 支持UBI
Device Drivers  ---> 
<*> Memory Technology Device (MTD) support  --->    
    [*]   MTD partitioning support
    <*>   NAND Device Support  --->
    UBI - Unsorted block images  ---> 
        <*> Enable UBI 

2.3 支持UBIFS 文件系統
File systems  --->
Miscellaneous filesystems  --->
    <*> UBIFS file system support

3. linux 啟動命令行參數

noinitrd console=ttymxc0,115200 ubi.mtd=2 root=ubi0:rootfs rootfstype=ubifs rw ip=off init=/linuxrc video=imx-fb:Innolux-WVGA

我們的flash 在內核裡被分為三個 mtd 區: bootloader, kernel, filesystem. 因此 ubi.mtd=2, 即選中第三個 mtd 分區作為 ubifs 根分區. 至於 ubi0:rootfs 則跟創建文件系統鏡像時的 *.cfg 有關.

需要注意的是, 很多 bootloader 會覆蓋 linux 本身的默認命令行參數, 所以, 如果存在這樣的 bootloader 就必須修改 bootloader 調用 linux 時的命令行參數! 否則 ubifs 完全不起作用.

4. 創建 ubifs 文件系統鏡像

mkfs.ubifs -m 2048 -e 129024 -c 992 -r filesystem_base filesystem_base.ubifs

ubifs 文件系統鏡像不能直接寫入 flash, 需要再根據這個生成的 ubifs 格式的文件系統鏡像生成 ubi 鏡像才能直接寫入 flash .

參數 "-m" : 為 flash 分頁大小, 可從 nand flash 的 datasheet 得知. 有些 flash 的分頁會有附加空間, 如我們所使用的這個 flash 每個分頁就帶有 64 字節的附加空間(1), 附加空間不計入我們的 -m 參數中!

參數 "-e" : 把 128KiB 減去一個分頁(2048)大小, 則等於 129024. 如果加載 ubifs 出現類似以下的錯誤:
UBIFS error (pid 1): validate_sb: LEB size mismatch: 131072 in superblock, 129024 real
則以那個 real 值為準!

參數 "-c" : 最大邏輯刷寫塊數量, 似乎可以是任意數值, 只要不超過邏輯塊總數即可.

5. 創建 ubi 鏡像

ubinize -o ubi.img -m 2048 -p 128KiB -s 512 -O 512 ubiimage.cfg

參數 "-m" : 參考 4. 中的 "-m" 參數

參數 "-p" : 為一次性擦除塊的大小, 可從 nand flash 的 datasheet 得知. 附加空間不計在內.

參數 "-s" : 與 -O 必須一致, 分頁子頁大小, 似乎跟 flash 的分頁組成有關, 似乎我們所用的 flash 每一頁由4 個子分頁組成, 一個子分頁為 512. 當出現以下錯誤時:
UBI error: validate_ec_hdr: bad VID header offset 2048, expected 512.
則以 expected 值為準.

其中 ubiimage.cfg 內容如下:

[ubifs]
mode=ubi
image=filesystem_base.ubifs
vol_id=0
vol_size=100MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresize

6. 燒寫並啟動

把 5. 裡生成的 ubi.img 燒寫進板子, 啟動, 當出現類似以下信息時表示, ubifs 已經成功加載:

UBI: attaching mtd2 to ubi0
UBI: physical eraseblock size:   131072 bytes (128 KiB)
UBI: logical eraseblock size:    129024 bytes
UBI: smallest flash I/O unit:    2048
UBI: sub-page size:              512
UBI: VID header offset:          512 (aligned 512)
UBI: data offset:                2048
UBI: attached mtd2 to ubi0
UBI: MTD device name:            "nand.rootfs"
UBI: MTD device size:            123 MiB
UBI: number of good PEBs:        985
UBI: number of bad PEBs:         6
UBI: max. allowed volumes:       128
UBI: wear-leveling threshold:    4096
UBI: number of internal volumes: 1
UBI: number of user volumes:     1
UBI: available PEBs:             0
UBI: total number of reserved PEBs: 985
UBI: number of PEBs reserved for bad PEB handling: 9
UBI: max/mean erase counter: 1/0

7. 註釋
(1) 附加空間可以使用也可以不用, 一般用於存儲文件系統的附帶信息.

8. 錯誤信息
8.1 UBIFS error (pid 1): validate_sb: LEB size mismatch: 131072 in superblock, 129024 real
參考 4. 參數 "-e".

8.2 UBI error: validate_ec_hdr: bad VID header offset 2048, expected 512.
參考 5. 參數 "-s".

8.3 UBI warning: ubi_io_read_ec_hdr: no EC header found at PEB 845, only 0xFF bytes
可忽略, 剛刷寫完的, 很多這種提示.

8.4 VFS: Cannot open root device "ubi0:rootfs" or unknown-block(0,0 ...
mtd分區不是 ubi 格式 或者 創建 ubi 鏡像時的 cfg 文件有誤.




==> 2010年1月31日 星期日 <==

UDP下嘅實時音視頻傳輸機制




經歷咗一兩年係項目當中嘅實時音視頻實踐, 覺得要實現好嘅實時音視頻傳輸實屬不易. 以下呢套UDP下嘅實現流程, 係血嘅經驗, 每一點都是來之不易.

全局要點: 視頻可跳, 但聲音不斷.

1. 發送方

(1). 開始時, 編碼I幀, 發送

(2). 接著, 發P幀 ( 聲音與視頻編碼是不同的, 每次真實發送幀前, 讀取錄音緩沖區, 並打包 ), 發N個P幀後, 跳到(1)

(3). 如果在發P幀過程中, 接收到對方發來的 "重發I 幀" 請求, 馬上放棄目前所編碼的幀(視頻幀,不是聲音幀), 跳轉到 (1)

2. 接收方
(1). 建立幀列表, 至少存儲3幀以上才開始播放, 使用順序插入法, 尋找相應幀序號的位置, 並插入.

(2). 播放開始時, 不停從幀列表中取得一幀(由於此時幀列表是順序的,故此取列表頭的那一幀即可), 如果是P幀時則放棄, 直到找到I幀才開始真正播放. 聲音幀不需要等I幀, 只要按著順序播放即可.

(3). 播放過程中, 不停從幀列表中取得一幀進行播放( 聲音播放與視頻播放線程需要分開!!切記! )

(4). 當視頻播放線程發現聲音播放的幀號大於正要播放的視頻2幀以上時, 馬上放棄當前播放的視頻, 並請求對方 "重發I幀", 並一直讀取到幀緩沖列表中大於等於音頻幀號的視頻 "I" 幀為止, 再播放 ( 此處是解決音視頻不同步的情況, 即視頻慢於音頻並隨時間累積 )

(5). 如果在(3) 當中, 發現掉幀了( 要播放的幀號不等於上一次播放的幀號 +1 ), 請求對方 "重發I幀", 並等待下一個 "I幀" (針對的是視頻幀, 音頻幀只要是大於上一次播放的幀號即可以播放 )

3. 上面缺了任何一點, 都會導致播放出現問題.




==> 2010年1月21日 星期四 <==

班得瑞(Bandari)




最近先至發現原來有十幾首好好聽嘅歌,原來喺屬於 Bandari 兩個專輯嘅音樂。再搜索咗一下呢個樂隊嘅信息,好似幾有意思甘,似乎眾說紛紜,有人話呢個樂隊係假嘅,亦有人話呢個樂隊衹係平時好低調,仲有人話 Bandari 係瑞士唱片公司 Audio Video Communications AG(簡稱AVC)嘅產品,似乎最後一種講法比較靠譜,上他們的主頁可以揾到 Bandari 嘅專輯。

似乎 Bandari 華人區更受歡迎,試下係Google搜索一下就知道,如果查英文,基本揾唔到真正同 Bandari 專輯有關嘅信息,但搜索中文網頁就一大堆。

但無關系啦,只要好聽就得~~話知佢係假定系真啦~~




==> 2009年4月9日 星期四 <==

終於可以用Google音樂下載啦~~




  前一排,聽講Google可以免費下載正版音樂,好開心,於是上Google,發現真係有Google音樂下載!但呢個試聽同下載服務僅對中國大陸境內提供。

  但係好失望,我一點下載就彈出個網頁話 “403 forbidden”,頂!我本來就係大陸境內,冇理由唔得咖?!開始我以為係瀏覽器嘅安全選項問題,於是我將D防火牆啊、安全選項啊全部關晒,但係都唔得,搞左幾日,上網抄左幾日都冇見到有講呢個問題,無法子啦,只好唔搞~~

  後尾翻到公司,啱好見到個同事用Google音樂聽緊歌,我以為係公司可以,於是嗱嗱臨開機試下,點知又係唔得,甘就出奇啦,我地公司係用一個IP出口嘅,冇理由,佢得我唔得咖?!於是我比較左下我台機同同事台機有乜唔同,比較之下先發現,佢個DNS同我個DNS唔一樣,改左個DNS之後,掂晒!!終於可以下載啦!

  因為我用開嘅係 OpenDNS,所以所有我用嘅機都會設置個DNS為OpenDNS 嘅地址,估計係中國境內 Google 音樂下載網址解釋僅對中國境內DNS有效,所以在國際DNS服務器內解釋呢個地址到其它位置,於是通過OpenDNS解釋到其它嘅IP導致出現 forbidden 嘅現象。




==> 2009年4月5日 星期日 <==

點解要點解?




  記得係大學果陣,有一次有個課程設計要做一個C語言嘅詞法解釋器。

  果陣起左個念頭想做一個通用嘅語言編譯器,即係一個萬能既語言編譯器,類似於C語言編譯器、Pascal編譯器等等,但係唔需要硬性為每個語言寫程式(GCC就係呢類型嘅實作),只需要寫一個簡單嘅詞法語法配置文本文檔即可以支持編譯出不同平台嘅程式(二進制文件格式)。

  後來同老師講左下自己嘅諗法同埋一D唔成熟嘅思路,同埋打算用幾十年時間嚟做呢樣嘢。

  老師同我講(大概意思):「呢種編譯器到目前都未出現,起碼證明呢樣嘢難度好高或者幾乎唔可能實現,點解要做呢D無用功呢?何況你做咗出嚟,佢又有乜價值呢?又有乜市場呢?何況做通用嘅編譯器你嘅編譯速度就自然比特定嘅編譯器速度慢,甘你呢個編譯器又有乜用呢?」

  聽到老師甘講,有D愕然,講真啦,老師講得係啱,但係……我只係想做呢樣嘢,無話想攞佢嚟用,只係想做,點解一定要有用、一定要有價值、一定要有市場啫?就好似細路嘅玩具咁,只係因為有興趣,想玩就玩啫,當時亦係甘回答嘅。

  人嘅一生,短短百年,雖則現實一分錢可以逼死英雄漢,但我覺得人一世物一世,樣樣事情都講錢、講有用、講意義未免太無意思啦,更有意思嘅事情例如靜靜的聽一首歌、諗辦法實現一個自己諗法嘅程式、嘗試一個月坐看夕陽西下、學習一下道家無為之法……人死如燈滅,生前嘅一切名聲、金錢、榮譽、利欲終將歸於塵土,千百年後或許無人知道你、或許有人知道你,又如何?一切不過碌碌,重不如趁住生命尚存做D自己鍾意嘅事情。遊戲人間亦係個唔錯的人生態度。

  好彩一直堅持自己選擇自己嘅道路,而家做嘅係自己喜歡嘅編程工作,雖然唔係做自己最
鍾意嘅遊戲引擎,但只要係寫程式我就好開心啦,何況而家通常寫嘅系嵌入式程式同埋驅動程式,都系自己鍾意做嘅部分,而家好開心~~~




==> 2009年2月9日 星期一 <==

Linux or ReactOS ?




  用左 Linux 嚟做開發有一段時間,慢慢發覺,Linux 原來面對嘅用戶群應該喺程式撰寫員先至啱 =_=b

  剛開始用 Ubuntu 時,諗住佢好似 Windows 咁樣好容易上手嘅,點知到喺使用嘅過程中遇到好多奇奇怪怪嘅問題,Google 之後,先知道又翻到石器時代,大多數問題唔可以通過 GUI 嚟解決,基本上呢D問題都要直接喺命令行底下解決,或者需要從某D源代碼重新編譯安裝,慢慢咁,到最後,習慣左連GUI都唔用,只用命令行。講真啦,都喺鍾意乜嘢都用鼠標喺各個GUI裡面跳來跳去呢種直觀嘅方式嚟解決問題 -_-|||

  直到上年年尾,發現左一個好玩嘅 OS —— ReactOS,佢哋既目標似乎喺要做一個開源板嘅 Windows NT 內核 OS!哇哈哈,全部繼承左 Windows 系列嘅操作方式,而且完全兼容 Windows NT 及以上 OS 嘅軟體同埋驅動程式!!特別喺驅動程式,雖然話有唔少硬體開始有 Linux 嘅驅動程式并且有成班人喺喥開發支持舊硬體嘅驅動程式,但仲喺有唔少廠商為左方便只開發 Windows 平台嘅驅動程式,咁樣如果你買左果D硬體,你焗住只能喺 Windows 下用啦。

  下載左個 ReactOS 嚟玩左一下,發覺佢已經可以跑起身,同個 Windows 2000 好鬼似。可以開一D類似於 FireFox 2.0 之類嘅軟件,只喺佢仲處於非常唔穩定嘅開發程度,經常郁下就死機藍屏。不過佢哋嘅開發進度好快,睇左下,平均每日有成十個修改提交,唔知幾時可以真正進入實用嘅階段呢,真喺好期待!

  比較 Linux 同埋 ReactOS,我覺得 ReactOS 嘅理念會更有前景,畢竟好多公司或者家庭購買左嘅軟體硬體都只能用喺 Windows 上,如果可以直接轉到呢個 ReactOS 上,買左新機後,就可以慳翻筆買 OS 嘅錢,畢竟叫個個都轉到 Linux 底下確實系有難度,特別喺遇著好似我大姐夫D甘嘅人,用 Windows 只會點鼠標,又唔願學新嘢,叫佢去用 Linux 估計同儸佢條命差唔多……

  Linux?唉,如果界面嘅易用性同埋速度可以提高上去,估計可以搶到 Windows 平台部分做開發嘅人員、願意嚐新的人同埋打算慳成本而進行平台轉換嘅團體。




==> 2008年10月12日 星期日 <==

啓明星




今日瀏覽網頁,無意中查左下啓明星,先知道,原來啓明星即系金星,亦系古時所講既「太白」或者「長庚」。傍晚出現時就稱之為「長庚」,清晨出現時稱「啟明」。其它既稱呼仲有:殷星,大正,營星,明星,觀星,大衣,大威,太(白+皋),終星,大相,大囂,爽星,太皓,序星。

可以通過維基百科查詢到更詳細的金星資料




==> 2008年6月23日 星期一 <==

Mingw 下 編譯 wxSQLite




1. 初始條件

  首先確定環境變量只有 Mingw 的路徑而沒有 msys 的路徑,因為使用 msys 編譯會有些問題。

2. 修改 makefile

  打開 wxsqlite3\build 下的 makefile.gcc,設定你需要的條件,如若妳不需要加密模塊並且擁有自己的SQLite3源代碼目錄,妳可以設定 SQLITE3_DIR

3. 編譯

  make -f makefile.gcc clean
  make -f makefile.gcc




==> 2008年6月19日 星期四 <==

分佈式版本控制系統SVK搭配TortoiseSVN的使用




  SVK與Linus所開發的Git相似,是一種分布式的版本控制系統,但它並不是完全從頭編寫的版本控制系統,而是基于 Subversion 的分布式的版本控制系统。如 CVS,Subversion 這些集中式管理系统存在对唯一的版本库过分依赖的缺陷:一旦不能正常连接到集中式的版本库,整个系统陷入瘫痪。分布式的版本控制系統最大的好處在于可以维护分布式的版本库,分散的开发人员可以通过 SVK 建立远程的 CVS,Subversion,P4 协议的版本库镜像,选择工作在自己合适的镜像版本库,这个镜像甚至可以是本地的,整个工作可以离线进行,然后在需要的时候同步镜像版本库到主版本库。

1. 假定條件

  首先假定妳已經熟悉所有SVN的操作,
  再假定 c:\svnlib 為SVN的對外的倉庫根目錄,
  假定妳機子已經安裝好SVN服務,
  若 c:\svnlib 下有一項目 test 倉庫(c:\svnlib\test), 假定妳能正確通過Tortoise讀寫其項目文件( svn://localhost/test ),
  假定網絡版本庫為 svn://192.168.1.100/MyProject.

2. 初始化

  在 c:\svnlib 下創建目錄 svklib (目錄名隨便你定義)
  在 c:\svnlib\svklib 使用 TortoiseSVN 右鍵在此創建版本庫(注意:這一步是鏡像的基礎!因為SVK用的就是SVN的版本庫)

  SVK是默認在 "C:\Documents and Settings\當前用戶名\.svk\Local" 創建鏡像倉庫的,所以需要先把鏡像倉庫位置重定位到你需要的位置( 當然, 如果妳喜歡默認的位置, 那你可以跳過這一步 ) 

  svk depotmap --relocate // c:\svnlib\svklib

  上面這行命令有一個概念, 就是depot-map -- 倉庫表, 妳可以使用svk depotmap 建立多個鏡像倉庫, 而不重定位, 這裡為了方便解說並盡量減少旁枝末節的繁雜概念問題而直接把默認位置重定位了. "//" 是一個倉庫名, 類似於Linux的那個根目錄, SVK把默認路徑當作根目錄, 所有的鏡像倉庫均建立在根目錄以上.

  妳用 svk depotmap --list 命令就可以看到SVK的根目錄被定位到 c:\svnlib\svklib 下了

  接下來我們建立一個鏡像目錄.
  我們必須建立一個鏡像目錄, 因為所有提交到此鏡像目錄的操作均被認為提交到網絡上的原始版本庫!!!

  下面這裡的 "-m test" 是設定此次操作的提交註釋,具體的詳情請參考SVK的幫助。

  svk mkdir //mirror -m test

3. 日常使用

  镜像! 把網絡版本庫鏡像下來.

  svk mirror svn://192.168.1.100/MyProject //mirror/MyProject

  同步! 注意: 這裡不需要再輸入那個網絡路徑了, 網絡路徑在上面進行鏡像時會記錄下來的!

  svk sync //mirror/MyProject

  成功!

  由於對鏡像目錄進行修改即相當於直接提交到網絡版本庫, 因此不能直接對鏡像版本庫進行修改, 所以必須新建一個本地的挎貝(分枝)! 注意!! 是必須!!

  svk mkdir //local
  svk cp //mirror/MyProject //local/MyProject

  接下來妳就可以使用TortoiseSVN的功能方便地對你剛才分枝下來的版本(//local/MyProject 即 svn://localhost/svklib/local/MyProject )進行取出\修改\提交了
  只是要記住, 妳所做的一切操作只能是修改妳的本地分枝, 即SVK源代碼目錄下面那個Local下的MyProject!

  當妳需要把修改提交到網絡原始數據庫時, 妳需要先更新鏡像目錄
  svk sync //mirror/MyProject

  再提交妳的代碼,此過程會自動 Merge 妳的修改到遠程版本庫。

  svk push //local/
MyProject

  記得, 修改鏡像庫時, 必須且只能使用SVK的方法, 否則更新不能被更新到網絡版本庫!

  各種關係及操作如下圖: 




==> 2008年6月17日 星期二 <==

Mingw 下編譯 gettext 0.17




  這兩天編譯gettext搞得焦頭爛額,機子裡裝了大量的開源工具,結果各種動態鏈接庫版本不一致,編譯工具不一致,導致編譯時老是不成功,後來乾脆寫個批處理,把 path 環境變量只設置為 mingw 及 msys 的Bin,把 include 中除 mingw 及 msys的路徑外(例如Gtk)全刪除,然後在Dos下使用以下的編譯命令一次編譯成功:

sh ./configure --enable-threads=win32 --with-libiconv-prefix=d:/sb/sdks/libiconv

==========
當前使用到的編譯環境變量(Mingw GCC4.21):

CPPFLAGS=-mno-cygwin -Wall -pipe -mthreads -fno-strict-aliasing
CFLAGS=-mno-cygwin -O2 -g -pipe -mthreads -fno-strict-aliasing
CXXFLAGS=-mno-cygwin -O2 -g -pipe -mthreads -fno-strict-aliasing
LDFLAG=-mno-cygwin




==> 2008年6月14日 星期六 <==

使用 OTL 連接 SQLite




  本文章假定妳熟悉SQLite數據庫,假定妳對OTL有 一定了解,假定妳所使用的操作系統為Windows平台。

  OTL 採用的是ODBC數據源機制,到 http://www.ch-werner.de/sqliteodbc/ 可下載到最新的SQLiteODBC數據源驅動。

  假定你已經創建了一個名為 MyTestDB 的數據源連接到你的數據庫,數據庫中有一表 Users, 表中有字段 id 及 value,id 为整型,value為字符串50個字節。

#include <iostream>
#include <string>


// 配置ODBC連接方式,其它方式可查看頭文件或文檔
#define OTL_ODBC
#include <otlv4.h>

otl_connect db; // 連接物件

void
test_select( void )
{


  otl_stream dbstream( 1 , " select * from Users " , db );
  int
     Usersid;

  char
     Usersvalue[ 50 ] = "" ;

  while
( !dbstream.eof() ) // 循環讀取記錄

  {
    dbstream >> Usersid >> Usersvalue;
    std::cout
      <<
"Users.id : " << Usersid

      <<
"Users.value : " << Usersvalue << std::endl;
  }
}


int
main()
{


  otl_connect::otl_initialize(); // 初始化OTL環境

  db.rlogon( "DSN=MyTestDB" );
  if
( db.connected )
  {


    test_select();
  }

  db.logoff(); // 斷開連接

  system( "PAUSE" ); // 暫停

  return 0 ;
}




==> 2008年6月13日 星期五 <==

數據包在互聯網




1.前言
  前段時間做一個NDIS網絡驅動,功能是修改IP包的IP地址并傳送到指定的機器,在測試時發現一個問題,假如在內網中傳送數據包到在內網的一台不同網域的機器上,數據包不能傳達,具體問題在[1]有詳
細說明。在 Google查了很多數據,經過多次測試,最終發覺,原來是路由器(Router)的問題。在這過程中,發現很多網友對數據包在網絡上的傳輸有誤解,因此我想對這個問題進行一次詳細的說明,以備以后的不時之需。
  網上有很多網友對以太網的理解是:數據包在互聯網是在IP層傳輸的,以IP地址來判斷地址并傳輸的。這是一個誤解,互聯網不認識IP協議層,只是由於現在有些寬帶運營商(ISP)使用了“釆用執行在協議層
的路由器”,所以導致出現由於 IP不在該路由器所控制的范圍則自動被放棄,我們看見的就是不符合IP協議的均會被放棄。這種做法好處是避免了廣播風暴問題,但同時又削弱了互聯網的功能,例如我們就不能直接把一個不釆用IP協議的數據包傳輸至互聯網某一個物理地址(MAC地址,下同)的機器。

2.互聯網

  當前互聯網釆用的是以太網,即釆用以太網交換機實現的,這種網絡的好處是,只要你知道網絡上某一機器的物理地址,即可以把數據包通過廣播傳輸到相應的機器上,但同時這樣也會造成廣播風暴,致使網絡癱瘓,當然這種情況是可以通過某些手段進行抑制的,下面會進一步說明。

2.1.以太交換機

  為了簡化模模型,我們先假設所有機器均以獨立IP連接到互聯網。(如圖)

 以太網交換機處於的是數據鏈路層,它只處理以太網頭,也就是說它只認識數據包的物理地址,舊式的交換機的處理過程是:只要數據包在互聯網某一個點發出數據包,數據包在經過每一個交換機時,交換機自動把數據包向所有端口廣播,這樣,通過一個又一個交換機的廣播,終端機的機器判斷該數據包的物理地址是否自身的物理地址,是就通知上層數據包的到達,否則直接掉棄。這種處理方法,在小數據量時很有效,但當有大量數據傳輸時,就難以保證了,由於交換機不停地廣播數據包,就會致使網絡由於廣播的數據太多而癱瘓。這時人們就想出了辦法,就是物理地址查詢機制:每一個從交換機輸入端的經過的數據包源物理地址將被存儲起來,然后當有數據包來臨時,就向每個端口查詢是否存在該數據包的目的物理地址,如果存在,則直接把數據包傳送往該端口,如果都尋找不到,則廣播該數據包。這樣就有效地抑制了大部分的數據包廣播了。

2.1.1.優點

2.1.1.1.增加協議靈活性

  例如你需要設計一個不使用IP協議的嵌入式設備,有人可能會說為什么不釆用IP協議?IP協議有更成熟的技朮,以及現成的各種函數例程。只要是做過工控、嵌入式設備、智能家居設備、寫過51程序或者是在DOS時代做過程序都知道,有大部分的項目對內存及空間的要求是非常嚴格的,其內存是寸金尺土,少几個字節就能換來整個產品成本下降數千元以至更多,通常一些嵌入式設備,需要的控制指令是很短的,只需要几個字節就足矣,沒必要攜帶龐大的IP頭。而且在互聯網上通過物理地址即可以把數據包傳輸到指定的設備,IP地址也就沒必要了。

2.1.1.2.准確到達目的地

  由以太交換網的結構及處理方式我們可以知道,數據包是可以通過物理地址直接到達目的地的,這也是為什麼有些技術書籍寫的可以通過數據包獲取源目的地物理地址的原理。物理地址類似於IP地址,IP地址代表的是互聯網上一個虛擬機器,而物理地址代表的是互聯網上一個實際設備,IP地址只有4位字節,而我們平常使用的物理地址是6位字節,擁有 281474976710656(281萬億)個地址,比起IP地址多多了,從當前情況來看,足夠各種計算機及設備都配備一個物理地址的,暫時也不需要擔心類似IP地址那樣的地址不夠問題。

2.1.2.缺點

2.1.2.1.沒有更多的控制參數

  以太網協議頭只有兩個物理地址及一個后續協議(或長度)參數。此缺點可以通過自定上層協議來實現更精細的控制要求來解決。

2.1.2.2.容易造成廣播風暴

  由於數據包釆用廣播來進行數據包傳輸,因此當各處傳送的數據包量大時,會導致網絡崩潰。此缺點可以釆用物理地址查詢機制來抑制。

2.1.3.路由器

  一些公司租用一倏寬帶后,希望能物盡其用,通常會把全公司的機器都搭上互聯網這趟快車,這時候就有几種方案:1、集線器;2、交換機;3、路由器。我們看下三種方案的優缺點:首先是集線器,嗯,這個方案效率有點低,當多個端口同時出現傳送或接收數據時,只能喊“My God”了,因此此方案不予釆用。剩下的兩個方案,各有各優點,只能見仁見智了,在出口IP不限制出口物理地址時,推荐釆用交換機以換取更大自由度否則就釆用路由器吧。
  路由器實際上跟以太網交換非常相似,只是功能更復雜了。路由器會自動根據網絡拓撲、負荷的改變及時維護該路由表,并擁有更強大的數據管理功能,例如防火牆、流量控制、自動撥號等,當然現在交換機也有這些功能的了,所以交換機跟路由器的功能很多時候重復了。當路由器找不到某一端口輸入的數據包對應的輸出端口時,即刪除該包。注意,這一點它跟交換機是不一樣的,交換機找不到時即廣播該數據包,因此,如果有一設備在路由器控制范圍內,而路由器不知道的話,那么該數據不會到達該設備!雖然路由器極好地抑制了數據包的廣播,但同時也損失了數據包到達准確地址的機會。
  同時,由於中國人口太多,過多的人口,過少的接入IP,導致網絡的發展畸形化,現在國內大部分中小規模路由器直接釆用了IP協議控制!!這一點導致了所有非IP協議數據包不能傳送!而且,就算明知道路由器控制的網域存在相應的物理地址,只要IP地址不對應,它也不進行傳送,結果可想而知,嵌入式設備這類就必須釆用 IP協議了,由此也帶來了設備成本的提高。釆用了IP協議,還可通過透明橋接來解決廣播技朮問題。但如果像我所釆用的那款路由器就慘了,它還把進出的數據包物理地址給修改了,這樣,內外就不能進行正常的UDP或者其它協議的數據傳輸了,只能通過中轉形式,如再買一倏電信或網通的寬帶,接一台計算機作中轉站,服務器長期維護一倏中轉站的TCP連接,然后所有外來數據均通過中轉站打包,傳送往服務器,再在服務器解包,再轉發相應端口。當然,這種修改數據包物理地址的方案,估計是為了針對現在寬帶運營商出現的封路由器而設計的,也是迫於無奈啊。

溫情提示:

  在國內,建議采用電信或網通的互聯網服務,目前就我所知,只有電信和網通是能讓你機子直接擁有公網IP的,雖然是動態的,也比其它的ISP如鐵通、視通等的內網 IP好,例如我使用的鐵通(後悔!),別人就不能根據我的出口IP直接連接到我的機子,也就是說我付的費用跟電信和網通一樣,但我卻只能擁有一半的功能,只能出不能進,如果需要外網能訪問我的機子,需要付一大筆的費用,很不划算。鐵通、視通這些是租用電信和網通的IP,然後采用分級路由器建立了一個龐大的局域網,因此所有出去的數據包的物理地址和進來的數據包的源物理地址均被修改為路由器的物理地址,也由於物理地址被修改,互聯網上別的設備或機器就不能通過物理地址定位我們的機子!同時由於路由器內網全部采用內網地址,因此只有鐵通或視通他們內部網的并用處於同一路由器級的用戶才能直接訪問相互的機子,其實就是局域網互訪,而因特網用戶是不能訪問你的機子的!當然,如果你不需要建立自己的網站、不需要別人訪問自己的機子、不需要建立自己的服務器的話,你家裡也沒有采用因特網的智能家居之類的嵌入式設備,這樣,鐵通、視通的安全性卻反而比電信和網通高,因為你不在公網內,被局域網保護起來了,特別是路由器有防火牆的情況下。如若你只能使用鐵通,那也不是沒辦法使你的機子公開的,只是方法就複雜多了,還需要一筆額外的費用,而且速度和穩定性沒有保障,那就是采用花生米的服務了。其實就是做了一個NAT轉換,或者是數據包的轉發,在這情況下,你的機子需要一直開著一個TCP連接,以維持數據的傳送。

[1] 偽IP如何實現與客戶機進行TCP通訊?
http://starofrainnight.blogspot.com/2008/04/iptcp.html




==> 2008年4月5日 星期六 <==

偽IP如何實現與客戶機進行TCP通訊?




-----------
* 軟件環境:

WinXP sp2

截取數據包的Ndis5.0驅動已經寫好。
在用戶層,能夠修改驅動發來的數據包的IP地址,客戶機也能接收到該數據包。

我這個軟件是用于模擬多客戶機與服務器連接實現數據傳輸的測試軟件。

-----------
* 硬件環境:

(33.33.33.33(假定是這個))    |—— 客戶機2 (192.168.1.105)
客戶機1——路由 (192.168.1.1)——|
                |—— 發送機 (192.168.1.100) (偽裝 99.99.99.99)

現在需要發送機偽裝IP 99.99.99.99 與兩客戶機進行TCP通信

在客戶機2安裝的Ethereal 檢測到發送機發來的IP為 99.99.99.99 數據包,物理地址(MAC)也為發送機的物理地址。

但 問題是,當客戶機2接收到第一次TCP握手包后,它返回的響應包雖然目的IP地址仍然是99.99.99.99,但物理地址卻是網關 192.168.1.1的物理地址!我再查看客戶機2的數據包,發現在它接收到發送機發送的握手包時,它先發送ARP數據包查詢了網關 (192.168.1.1)的物理地址再發到網關。也就是說,當客戶機發現接收的數據包IP與自己不在同一子域則自動把數據包發往網關,但現在問題是由于 發送的物理地址改變了,網關只能查詢正確的99.99.99.99的物理地址,那這時候,數據包也就不能再返回偽裝的發送機上了。于是TCP連接被逼中 斷。

而且,兩客戶機均開啟了防火牆,不能使用ICMP實現數據包路徑轉移。

我嘗試發送ARP包去修改客戶機2的ARP緩沖,但沒用,不同一子域的IP地址不會受到緩沖,結果IP包依然發往網關。

有何方法可以實現發送機與兩客戶機用偽IP通信?鬱悶中……




==> 2007年3月20日 星期二 <==

16位色AlphaBlend深度探索




  在網上看了很多關于16位色的AlphaBlend計算方法,在GameRes上看到很多高手寫的文章,深有感觸,這些文章要不就是單說原理卻沒有程序,要不就有程序卻沒有很好的原理解釋,要不兩樣都有,但就程序跟原理就像硬拼在一起一樣,看了原理看程序卻不知道程序在干什么,要知道很多16位色處理的AlphaBlend程序是用彙編寫的,這更頭疼了,看見一堆沒有解釋的彙編代碼,頭都大了……也許是我太笨了……

  因此萌生了寫一篇對16位色AlphaBlend的原理及程序的詳解文章,希望我這篇文章能對一些初涉圖像編程的后來者有所幫助:)

  為什么單單研究16位色的Alpha混合?而不研究24位,32位的?

  第一、節省空間,它只有兩個字節,比起24位(三字節),32位(四節節)色的存儲容量會省很多,雖然對于目前的大內存系統,不算什么,但放在顯存中時,就很重要了,雖然目前顯卡的緩存容量也在向內存靠攏,但省點還是好的:) 畢竟你也不希望你的程序是個吃內存的怪獸吧?!

  第二、速度,有人可能會不贊同這個觀點:24位、32位色的RGB分量是單獨的一個字節,計算起來起碼沒有16位那么麻煩!方法是人想出來的,16位的處理另有方法計算,可以不用把RGB分量拆開而直接計算,稍后會講到。而24位不是4字節對齊,在內存的計算中會比較吃虧,相對16位,32位,特別是大面積復制及計算時會差些,而32位與16位呢?呃,在數字上我們可以看到一點,在處理32位數據時,我們可以處理兩個16位的數據……

  第三、視覺,在人的視覺方面來說,高于16位的顏色,人眼几乎分辨不出區別來,那么要那么大的顏色干啥呢?當然,這里只針對做游戲來說的,如果你是想做圖像處理的,那么24位以上的顏色是必要的,畢竟那些細節不能掉失:)

  接下來開始入正題了。

  在16位色狀態下,根據不同顯卡對像素的處理不同,16位色的像素格式共有兩種,一種的RGB分量為(R:5, G:5, B:5),另一種的RGB分量為(R:5, G:6, B:5)。數字是代表所占的位(Bit)數。前一種只有15位是有效的。

  注意,在16位模式時要先取得顯卡的像素模式,否則,處理時如果跟顯卡的像素格式不一致會產生偏色的現像!還有一點,Windows Bitmap的16位色格式是倒過來的,即顏色順序是BGR,剛好跟顯卡的顏色順序調了個頭,當然你可以設定Bitmap的顏色掩碼來實現Bitmap與顯卡格式一致。

  接下來,我們要介紹的是這個AlphaBlend算法了,AlphaBlend算法即我們通常所說的半透明效果算法,我們可以設置Alpha值來把兩圖進行不同透明度混合。

  AlphaBlend常規算法如下:
  提示:獲取某個顏色的值可使用顏色掩碼,如:555色顯卡的顏色掩碼為 R(0x7C00), G(0x03E0), B(0x001F), 設顏色值為Value,則 R = ( Value & 0x7C00 ) >> 10; G = ( Value & 0x03E0 ) >> 5; B = ( Value & 0x001F ) >> 5; 計算完畢后要把顏色分量結合為一個顏色值時使用逆操作即可。

  如若你的程序不需要太高效率,那么這種算法是挺適合你的,容易理解也容易實現。

  接下來我們對此算法作一下改進,把括號拆開,現在變成這樣了,這几條公式跟上面的有什么區別呢?要知道,在計算機中,一般來說,乘除計算是比加減要慢得多的,現在我們把它由原來的兩次乘法變成一次乘法:
  想想,還有沒有得再進一步優化呢?有!我們知道Alpha值是在 0 ~ 1范圍內的,是一個浮點數,計算機的浮點數計算也是一個效率殺手,我們應該怎樣去掉它呢?把它變成整數!

  但怎樣變呢?把它的范圍擴大!對啦,正解!在16位色 555或565模式,平均來說每個分量最大取5位(bit),而25=30,我們可以把它擴大32倍,足夠用了!即取 0 <= Alpha <= 32 內的整數值。那么只需要公式兩邊均乘以32即可。現在我們的Alpha值不再是原來的Alpha了,而是一個大于等于0且小于等于32的Alpha值,我們把它命名為 Alpha32。于是,公式再次變成:   提示:除以32可以使用C語言中的移位運算來實現。

  到目前為止,好像能優化的地方已經很難找了,還有其它方法能更快地實現運算嗎?當然是有的,否則我還用寫嗎?!那就是不要把各個分量拆開再運算,而把整個顏色值進行運算,這樣就省去了拆分的步驟,也不用進行三次相同的操作了!但不是直接把顏色值拿來進行運算,需要對顏色值進行一些前期處理再進行運算。事實上,我們接下來的這個方法還是針對每個分量進行處理的,只不過,我們把它這個處理變成一次處理三個分量。這樣運算一次即相當于三次的運算。

  以下的所有的文字如無特別說明則均針對16位色555模式RGB順序進行處理。

  首先,我們分析一下像素的格式,其格式為(二進制) : 0RRR RRGG GGGB BBBB。

  如果我們直接對像素進行運算,那么其運算過程如下:
  但大家需要留意的是,紅綠藍分量乘后,如果有進位,向哪里進位?必然會溢出到相鄰的分量上,這樣計算結果就會出現錯誤了!那么需要有多大空間才不會相互影響呢?我們知道25=32,而我們的Alpha位數為5,5位的分量乘以最大級數32也只是向左移5位,所以我們只需要5位就足夠了,這里只是作下解釋,下面這個方法不需要你親自去移位D:)   因此,我們需要想一個辦法來讓它有足夠多的位置來進位且不用過多的復雜計算。我們可以把這個像素復制一份,這樣,像素就變成了32位了(Value32): 0RRR RRGG GGGB BBBB 0RRR RRGG GGGB BBBB。然后怎樣做呢?我們使用一個掩碼(Mask32): 0000 0011 1110 0000 0111 1100 0001 1111(0x3E07C1F)。我們把 Value32 & Mask32 即可得  0000 00GG GGG0 0000 0RRR RR00 000B BBBB。大家可以留意到,剛好它們之間就間隔了5位,如果兩個這樣的32位值相乘,它們就有足夠的進位空間了。如下:  但是,我們不能給你5位,你給我生成10位數啊,那樣我們還怎樣運算?好的,現在把它變回5位,不知道你留意到沒有,在上面我們講述公式3時,Alpha32后來還需要除以32的,除以32是什么意思?剛好是右移5位!這樣 10 – 5 = 5剛剛好!現在還有個問題,有人會說進行乘或加還好辦,有足夠位可以進位,那么當Color1-Color2這樣時怎么辦呢?它會向前借位的啊!呃……這個是一個問題,但似乎這個問題并不影響大局,只要你眼力不是特別好的話,在效果上看不出有什么太大區別的,我們是求速度嘛,別計較那么多!

  好,假設我們通過運算得到了一個源點及目標點的計算結果Color3:GGGG GGGG GGG0 RRRR RRRR RRBB BBBB BBBB,我們怎樣把它還原到我們的16位的顏色值呢?OK,執行一下我們怎樣把它變成這個樣子的逆運算就行了!如下:  哈哈,還是用C語言描述舒服啊,不用寫那么多公式!

  這樣我們就得到了一個經過AlphaBlend的結果值

  下面就是我們的重頭戲了,代碼實現!可能會有人奇怪,為什么要寫三份實現代碼呢?那是因為,使用純C語言實現的函數雖然可移植性佳,但同時,它也失去了使用機器更強大功能的機會。使用普通彙編代碼也是同類問題,就是在沒有MMX的機器上,普通彙編代碼能實現比純C更高的效率,當然,如果編譯器的能力足夠強大,純C程序與彙編的速度可以几乎相等。還有個版本就是MMX版本,此版本利用了MMX加速功能,速度比普通的彙編代碼提高一倍不止!要注意的是,MMX版本我們釆用的仍然是顏色量計算的,原因在后面在該算法前再詳述。     

  好了,廢話少說:

//首先是登場的是使用C++的實現
void DrawAlphaBlend_cpp(

  WORD * dstAddr, // 目標圖的起始地址
  WORD * srcAddr, // 源圖的起始地址
  DWORD cntX, // 橫軸要處理的寬度

  DWORD cntY, // 縱軸要處理的高度
  DWORD dstSkipBytes, // 目標地址在處理完橫向寬度最后一個點后要跳過多少個字節
  DWORD srcSkipBytes, // 源地址在處理完橫向寬度最后一個點后要跳過多少個字節
  DWORD srcAlphaLevel )// Alpha級數 0 ~ 32
{

  DWORD srcColor; // 源點顏色
  DWORD dstColor; // 目標點顏色
  // 此值要根據你所需要處理的像素格式而定,這里僅指 16 位色 555模式 RGB順序
  DWORD Mask32 = 0x3E07C1F;
  WORD xCnt = cntX; // 暫存寬度值,以便循環運算


  for( ;cntY>0; --cntY, cntX = xCnt )
  {

    for( ; cntX>0; --cntX )
    {
      srcColor = *srcAddr | (*srcAddr<<16); dstcolor =" *dstAddr" srccolor =" (srcColor" srccolor =" (srcColor">>16 | (srcColor & 0x0000ffff );

      
      // 改變目標圖的點為結果點,這樣直接顯示目標圖即可知道整個結果
      *dstAddr = srcColor;
      ++dstAddr;
      ++srcAddr;
    }
    dstAddr = (WORD*)((BYTE*)dstAddr + dstSkipBytes);

    srcAddr = (WORD*)((BYTE*)srcAddr + srcSkipBytes);
  }
}

// 接下來是普通彙編的實現
void DrawAlphaBlend_asm(

  WORD * dstAddr, // 目標圖的起始地址
  WORD * srcAddr, // 源圖的起始地址
  WORD cntX, // 橫軸要處理的寬度

  WORD cntY, // 縱軸要處理的高度
  DWORD dstSkipBytes, // 目標地址在處理完橫向寬度最后一個點后要跳過多少個字節到下一行
  DWORD srcSkipBytes, // 源地址在處理完橫向寬度最后一個點后要跳過多少個字節到下一行
  WORD srcAlphaLevel )// Alpha級數 0 ~ 32
{

  DWORD Mask32 = 0x3E07C1F;

  // 簡單 alpha 運算  
  __asm
  {
    mov edi, dword ptr dstAddr; // 源地址

    mov esi, dword ptr srcAddr; // 目標地址
    mov cx, cntY; // cx 為行數

Next_Row:

    cmp cx, 0; // 如果 高度(即要處理的行數) 為零則結束運算
    je All_End;

    mov dx, cntX;

    mov word ptr PointCnt, dx; // PointCnt 為一行的點數
Next_Point:
    cmp PointCnt, 0; // 點數為零則開始下一行的計算

    je Pre_Next_Row;

    mov ax, [esi]; // 裝載 源點顏色值
    mov bx, [edi]; // 裝載 目標點顏色值

    shl eax, 16; // eax, 左移 16位,
    mov ax, [esi]; // eax = 0RRR RRGG GGGB BBBB 0RRR RRGG GGGB BBBB

    shl ebx, 16; // ebx 左移 16 位

    mov bx, [edi]; // ebx = 0RRR RRGG GGGB BBBB 0RRR RRGG GGGB BBBB

    // ... 源點及目標點顏色拆包, 不懂什么叫拆包?算了這個稱呼是我隨便安上去的……
    and eax, Mask32; // eax = 0000 00GG GGG0 0000 0RRR RR00 000B BBBB
    and ebx, Mask32; // ebx = 0000 00GG GGG0 0000 0RRR RR00 000B BBBB
    // ... 源點及目標點顏色拆包 完畢, 下面進行計算

    sub eax, ebx; // 源顏色減去目標顏色
    xor edx, edx; // 清空 edx
    mov dx, word ptr srcAlphaLevel ; // dx = srcAlphaLevel

    mul edx; // 乘 Alpha 值, edx 被沖掉數據
    shr eax, 5; // 除以32
    add eax, ebx; // 加上 ebx目標顏色

    and eax, Mask32; // 下面進行打包
    mov bx, ax; // bx = Low_Word( eax );
    shr eax, 16; // 把 結果顏色右移 16位,現在 ax = High_Word( eax );

    or ax, bx; // 把ax 與 bx作一下或,即成為16位的結果顏色值, 保存在 ax中

    mov word ptr [edi], ax; // 得到結果

    add esi, 2;
    add edi, 2;
    dec PointCnt;
    jmp Next_Point; // 下一點處理

Pre_Next_Row: // 在開始下一行時的准備工作
    add esi, srcSkipBytes;
    add edi, dstSkipBytes;    
    dec cx;

    jmp Next_Row;
All_End:
  }
}

  
接下來,是我們的MMX算法了,但我們注意到MMX沒有針對于DWORD及QWORD的乘法,每次我們對像素進行處理時,我們必須進行兩次乘法,實際來說,釆用以上的算法在我的賽揚M處理器 1.6G,512DDR內存,40GSTAT硬槃上,用MMX算法會比使用常規分色量MMX算法要慢,兩圖互換的速度是170fps 左右而常規分分色量MMX能達到270fps,在云風前輩的文章(參考資料13)說能帶來10%的效率提升,但我做不到……因此這里提供是常規的分色量 MMX算法。


void DrawAlphaBlend_MMX(
  WORD * dstAddr,
  WORD * srcAddr,

  DWORD cntX,
  DWORD cntY,
  DWORD dstSkipBytes,
  DWORD srcSkipBytes,
  DWORD srcAlphaLevel )
{
  DWORD LeavePoint = cntX & 0x03; // 每次處理四個點,有沒剩余點?

  DWORD RMask = 0x1F<<10;
  DWORD GMask = 0x1F<<5;
  DWORD BMask = 0x1F;

  cntX >>= 2;// 每行取4的倍數個點
  __asm
  {
    mov esi, dword ptr srcAddr; // ebx 為源地址

    mov edi, dword ptr dstAddr; // eax 為目標地址  

    // red mask
    // mm4 = (2進制) 0111 1100 0000 0000 | 0111 1100 0000 0000 | 0111 1100 0000 0000 | 0111 1100 0000 0000
    mov eax, RMask;
    mov ebx, eax;

    shl eax, 16;
    mov ax, bx;
    movd mm4, eax;

    movq mm1, mm4;
    psllq mm4, 32;
    por mm4, mm1;

    // green mask
    // mm5 = (2進制) 0000 0011 1110 0000 | 0000 0011 1110 0000 | 0000 0011 1110 0000 | 0000 0011 1110 0000
    mov eax, GMask;
    mov ebx, eax;
    shl eax, 16;

    mov ax, bx;
    movd mm5, eax;
    movq mm1, mm5;

    psllq mm5, 32;
    por mm5, mm1;

    // blue mask
    // mm6 = (2進制) 0000 0000 0001 1111 | 0000 0000 0001 1111 | 0000 0000 0001 1111 | 0000 0000 0001 1111
    mov eax, BMask;

    mov ebx, eax;
    shl eax, 16;
    mov ax, bx;

    movd mm6, eax;
    movq mm1, mm6;
    psllq mm6, 32;

    por mm6, mm1;

    // alpha group
    // mm7 = (16進制) 00AA 00AA 00AA 00AA
    mov eax, srcAlphaLevel;
    mov bx, ax;

    shl eax, 16;
    mov ax, bx;
    movd mm7, eax;

    movq mm1, mm7;
    psllq mm7, 32;
    por mm7, mm1;

    mov ecx, cntY; // ecx 為要復制的行數
Next_Row: // 下一行處理
    cmp ecx, 0; // 如果 ecx 計數為零,則退出處理

    je All_End;

    mov ebx, cntX; // edx 存儲的是要處理的次數 (每兩個點為一次,兩個點為同時處理的)
Next_Point: // 下兩個點處理
    cmp ebx, 0; // 如果 edx 為零, 則跳到下一行

    je Prepare_Next_Row;

Calculate_Points:
    movq mm0, [esi]; // 取源四點像素
    movq mm1, [edi]; // 取目標四點像素


    movq mm2, mm0;
    movq mm3, mm1;

    // Red
    pand mm2, mm4;

    pand mm3, mm4;
    psrlw mm2, 5; // 右移5位,這個沒什么關系,只要有足夠的進位位置就行了
    psrlw mm3, 5;

    psubsw mm2, mm3; // 有符號減    
    pmullw mm2, mm7; // 有符號乘取低位
    psraw mm2, 5; // mm2 /= 32;

    paddsw mm2, mm3;
    psllw mm2, 5;
    pand mm2, mm4; // mm2 存儲的是紅分量的結果

    // Green

    movq mm3, mm0; // 備份mm0 ---> mm3
    pand mm0, mm5;
    pand mm1, mm5;

    psubsw mm0, mm1; // 有符號減    
    pmullw mm0, mm7; // 有符號乘取低位
    psraw mm0, 5; // mm0 /= 32

    paddsw mm0, mm1;
    pand mm0, mm5;// mm0 存儲的是紅分量的結果

    // Blue
    movq mm1, [edi]; // mm1 = dstColor ( B )

    pand mm3, mm6;
    pand mm1, mm6;
    psubsw mm3, mm1; // 有符號減

    pmullw mm3, mm7; // 有符號乘取低位
    psraw mm3, 5; // mm3 /= 32
    paddsw mm3, mm1;    
    pand mm3, mm6;// mm3 存儲的是紅分量的結果


    por mm0, mm3;
    por mm0, mm2;

    cmp ebx, 0; // 為了復用以上的計算代碼,因此加多一步檢測操作

    je Handle_Leave_Point;

    movq qword ptr [edi], mm0; // 存數據

    // 地址增加
    add esi, 8;

    add edi, 8;
    dec ebx;
    jmp Next_Point; // 跳到下兩個點

Prepare_Next_Row:

    cmp LeavePoint, 0;
    ja Calculate_Points;

Prepare_Next_Row_2:
    add esi, srcSkipBytes; // 源地址跳過不處理字節

    add edi, dstSkipBytes; // 目標地址跳過不處理字節    
    dec ecx;
    jmp Next_Row; // 跳去處理下一行

Handle_Leave_Point: // 處理剩下的點
    // 來到這里肯定是有 1 ~ 3個點

    movd eax, mm0;
    mov word ptr [edi], ax;
    inc edi;

    inc edi;
    inc esi;
    inc esi;

    // 判斷是否大于1個點
    cmp LeavePoint, 1;

    jna Prepare_Next_Row_2;

    shr eax, 16;
    mov word ptr [edi], ax;

    inc edi;
    inc edi;
    inc esi;
    inc esi;

    // 判斷是否大于2個點
    cmp LeavePoint, 2;

    jna Prepare_Next_Row_2;

    psrlq mm0, 32;
    movd eax, mm0;
    mov word ptr [edi], ax;

    inc edi;
    inc edi;
    inc esi;
    inc esi;

    jmp Prepare_Next_Row_2;

All_End: // 操作結束
    emms;
  };
}

參考資料:
1. http://dev.gameres.com/Program/Visual/2D/WindowsAlpha.mht Windows的位图alpha混合技术
2. http://dev.gameres.com/Program/Visual/2D/256Alpha.htm 基于256色的Alpha混合方法(查表法)的实现方法
3. http://dev.gameres.com/Program/Visual/2D/Alphajd.htm 16位Alpha混合的简单算法
4. http://dev.gameres.com/Program/Visual/2D/Ddutil.htm 来自alpha混合的困惑
5. http://dev.gameres.com/Program/Visual/2D/IntelAlpha.htm 可能是最快的算法alpha blend汇编源代码,Intel官方提供
6. http://dev.gameres.com/Program/Visual/2D/qAlpha.htm 快速Alpha混合
7. http://dev.gameres.com/Program/Visual/2D/AlphaQiantan.htm Alpha混合浅谈
8. http://dev.gameres.com/Program/Visual/2D/16MMXa.htm 16位Alpha混合的MMX优化
9. http://dev.gameres.com/Program/Visual/2D/AlphaBlending.htm Alpha-Blending 技术简介
10. http://dev.gameres.com/Program/Visual/2D/mmxaddalpha.htm MMX版本的Alpha Blend算法实现
11. http://dev.gameres.com/Program/Visual/2D/16bitalpha.htm 16位BIT模式下的ALPHA运算
12. http://dev.gameres.com/Program/Visual/2D/64Kalpha.htm 64K 色模式下的快速 Alpha混合算法
13. http://dev.gameres.com/Program/Visual/2D/MMXAlpha.htm 利用MMX优化64K色Alpha混合算法
14. http://dev.gameres.com/Program/Visual/2D/JianYiAlpha.htm 简易Alpha混合算法




==> 2007年3月13日 星期二 <==

製作綠色版FireFox




  在你的FireFox目錄(X:\XX\Mozilla Firefox )下新建一個目錄 Profile

  把你機子上的FireFox配置文件( X:\Documents and Settings\XXX\Application Data\Mozilla\FireFox\Profiles\xxxxxxx.default\*.*)復制到 X:\XX\Mozilla Firefox\Profile 下

  然后創建一個到 FireFox.exe 的快捷方式,目標項填入 ("D:\Tools\Mozilla Firefox\firefox.exe" -profile "profile" )

  以后只要把這個(X:\XX\Mozilla Firefox)目錄復制到U槃,到任意機子上,使用該快捷方式打開(記得要改那個FireFox的路徑名),即可以使用了!




==> 2007年3月7日 星期三 <==

Ie不能顯示PNG的解決




  (希望各位不要嫌我的文章羅嗦:) 因我希望寫的文章有個案情記錄,如果我的方法無效,則至少后來的人少走几步路)
  前几天打開QQ空間,寫些文章,寫好后,想輸入驗證碼時,發現,驗證碼是個大紅叉!以為是QQ空間出問題了,沒去管它,無聊之下翻開Doxygen的幫助文件(CHM格式)看,結果發現很多圖片都是紅叉!奇怪了,查看圖片屬性,發現沒顯示的圖片都是PNG格式圖片的鏈接,因為CHM文件查看器調用的正是IE內核,我又打開QQ空間,把那驗證碼位置的圖片下載下來,一看,正是PNG格式的!而其它的圖片均沒事,奇怪了?!
  上百度搜索,有人說是IE6不能查看PNG是個BUG(請參照http://support.microsoft.com/kb/822071/zh-cn),但老大啊,我之前是能看的,且圖片也不只是4,097 字節或 4,098 字節,無論大小怎樣,都不能看。還有人說是HKEY_LOCAL_MACHINE/SOFTWARE/MICROSOFT/INTERNET EXPLORER/EMBEDEXTNTOCLSIDMAPPINGS/ 下加個.png的子鍵然后還要修改什么鍵值的,這個,我試過也是無效,在朋友正常的機子里發現這個路徑下也是沒有.png 子鍵的,也就是說這個說法也是不正確的。有人說重裝IE6,我重裝了,無效!有人說要裝IE7,無效!有人說要重裝系統,沒試過……
  后來查到外國的PNG格式開發主頁,在FAQ中講到IE顯示不了PNG的問題,其中給了几個方法,雖然沒有真正解決我的問題,但相信會有人對得上號的:)
  1、使用 開始->運行,在運行輸入框中輸入 「regsvr32 c:\windows\system32\pngfilt.dll」(然后點擊確定)
  注意,這個pngfilt.dll在有的系統中是在 c:\windows\system中的,要自己查看一下這文件在哪里,根據自己的系統修改一下路徑。如果在注冊時出現 「已加載 c:\windows\system32\pngfilt.dll,但沒有找到DllRegisterSever 輸入點。無法注冊這個文件」,則表明這個文件可能損壞了,你要去別的機子去Copy一個好的過來。再進行一次注冊。
  2、有些人是因為自己系統的設置問題,即任意打開一個文件夾,在上方菜單上選擇「工具」->「文件夾選項」->「文件類型」,選擇下方的「還原」按鈕。(如插圖1)。



  3、開始 -> 運行,在運行輸入框中輸入「Regedit」,到這個路徑「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft \Windows\CurrentVersion\Internet Settings\Accepted Documents
」,在右邊右鍵「新建」->「字符串值」 緊接着最大的數字命名,我這里是3,故命名為4,并賦值為「image/png」。(見插圖2)。


  在國外網站并沒有找到真正的解決方法,后來,再搜索時,看到某個論壇的一個回復:http://www.msusenet.com/archive/topic.php/t-1874263358.html,這里面說到有個特別的注冊表位置[HKEY_CLASSES_ROOT\MIME\Database\Content Type\image/png] !雖然這個回復似乎沒有解決發言人的問題,但卻啟發了我,我打開Regedit,去到﹝HKEY_CLASSES_ROOT\MIME\Database﹞一看,天哪!!!我這個鍵完全是空的!!!什么都沒有!我再去朋友正常的機子(與我一樣是WinXPSP2)上一看,這個鍵里的項目不止一百項………………郁悶哪!于是我把他機子的﹝HKEY_CLASSES_ROOT\MIME﹞整個鍵導出到mime.reg,再到我機子導入,咦,奇怪,什么都沒有改變??什么都沒有增加!!這里我想到了鍵的權限問題,在MIME鍵上右鍵,發現,權限里居然一個人都沒有!于是我把MIME項整個刪除,導入,再右鍵查看權限,正常(自己賬號是全權控制的)!

  連忙打開IE,登錄QQ,哈哈!搞定,驗證碼出來了!!!!




==> 2006年12月25日 星期一 <==

RealPlayer安裝曆險記




  這兩天找了首音樂,哪知道下載來的音樂文件是RM文件!本人不大喜歡看電影, Realone 或 RealPlayer 這樣的武器不是我的常規裝備,于是興沖沖去華軍下載了個 RealPlayer來, 准備給機子加強火力。

  哪知道,一點安裝,它給我彈個錯誤對話框來,說什么:“作為受限用戶,您無足夠的windows?操作系統權限使用此程序安裝軟件,請聯系管理員尋求幫助”!我靠!它真是欺負我讀書少呢!我這個賬戶正是初始那個Administrator賬戶改名而已,本來就是管理員賬戶!我懷疑是安裝程序下載錯了,于是又跑去天空下載,一試也是這樣,不信邪,再去下載,這次到pconline,還是不行!我納悶了,難道全部都是安裝文件錯了?!于是直接跑上Real的主頁下載個最新的RealPlayer10.6簡體中文官方版……結果不想而知,倒霉的人始終倒霉……

  到了這時,我認命了,看來錯不在它,于是駕起牛車(10M寬帶)直奔百度、Google,搜索那個錯誤信息。嘩!好多人問這個問題哦!嘻嘻,看來像我一樣倒霉的不少嘛,當然,也讓我看到希望的曙光J 但當我花了兩天把百度和Google那几十頁的搜索結果看遍后,傻眼了,居然問的人多,真正解決了問題的一個都沒有……

  看來這個問題還得自己來解決了,着手還得從那個該死的安裝文件RealPlayer.exe 起,我抬來X光機(BoundsChecker),把API記錄的選項打開,對准 RealPlayer.exe,點下Program -> Start,運行到彈出錯誤對話框處,然后點確定,這時,程序的所有調用過的API及其參數、返回值全部記錄在案。在 Transcript 面板左邊我們可以看到出現錯誤的地方:



  看到沒有?注意,就是第309 行,這里就是錯誤對話框的出現地方!那在這之前,它干過什么,導致出錯呢?往上看,留意到上面第293行的RegCreateKeyExA函數的返回值有異常!正常應該返回ERROR_SUCCESS(零值) ,此時它返回是API錯誤值5!BoundsChecker的右面可以看到它的調用參數及返回值:



  我們知道 RegCreateKeyExA 是一個注冊表鍵的創建函數,第二個參數lpSubKey 正是它要創建的鍵路徑,那根鍵 hKey 是哪個呢,單看 0x80000000也不知道是哪個啊?!打開windows SDK的winreg.h,可以看到HKEY_CLASSES_ROOT 的值正是 0x80000000 !啊?!這里可是系統各種類設置及文件關聯的重地哦!好家伙!居然在這個危險的地方創建鍵值,究竟它想干什么?我們再扛出蓋茨大叔的私家手朮刀 Regedit.exe,翻到HKEY_CLASSES_ROOT\Software 一看——沒什么特別呀!看樣子它是在這里創建鍵出錯了,動手給它加上 RealNetworks 看看……在Software項上右鍵,選擇新建項——出事了!它彈出一個對話框來說:“無法寫入到注冊表項!”看來問題在這里了。我試了下用注冊表文件來進行鍵值的創建也會出現這個錯誤的,那么也就是說調用Win32API也不能在此項中進行鍵值的新建。

為什么不能寫入呢?我又在HKEY_CLASSES_ROOT的其它的項下新建項,沒事呀!上車,再奔百度……找了半天注冊表中出現的那個錯誤,漸漸的,目光落到Software的權限上,它的權限如下:



  沒錯呀,管理員權限是完全控制,自己的賬戶也是完全控制,但為什么自己不能創建鍵值呢?無奈之下,去室友Amsonl的機子上查他注冊表中的同一項,發現他的設置跟我是一樣的!

  到這里,有點山窮水盡的意味了,突然間,想到,如果我把它刪掉重建呢?有點冒險,如果不能重建就搞大發了……狠了狠心,大不了重裝!于是,把Software項導出到save.reg,然后刪除 Software項,很順利!再導入——呃?!不行!!居然彈出同一錯誤來!到這時,几乎要絕望了,但卻留意到Software項被建立了起來,但里面的項及鍵值不全!我嘗試着在Software上右鍵,新建項,不行!再看下Software的權限,發現里面各個組都沒有設置完全控制,設置了一下,再新建 ——Ok!!!!再一次導入save.reg——沒有任何提示,成功!

  馬上找出RealPlayer.exe雙擊,哈哈!終于出現安裝進度了!



  原來RealPlayer被這個Software鍵給卡死了!

  這個問題還會導致Windows Media Player10及11到最后一步時,安裝不成功的錯誤。因為這個Software項里的Windows子項中有Media Player的設置。

  最后,實際上這個鍵值錯誤是怎樣導致的,百思不得其解,因為在網上看到有些人新裝系統然后馬上裝RealPlayer也會出現這個錯誤,也就是說在新系統一開始此鍵就被鎖住了。

  補充:期間有人說裝realone的解碼器可以解決問題,但如果是本文所說的問題則裝realone的解碼器也是不能解決這個問題的,因為裝realone的解碼器還是需要讀寫此鍵值的!




==> 2006年12月3日 星期日 <==

尘缘花开




今天接到家里的报信, 姨丈检查出癌症! 心里感到一阵悲凉 ... 不久前, 也有一个亲戚得癌症, 刚去世, 现在又听到一个亲戚得病, 而且这个亲戚跟我家的关系更为密切...

有时会感叹人生的无奈, 生老病死总难避免 ... 或者, 正是如此, 人才会懂得珍惜吧?!

其实, 从小到大看过不少的生生死死, 经历过不少的离离别别, 开始的十几年, 碰到这些事情我总会禁不住伤心落泪, 有道是, 男儿有泪不轻弹, 只因未到伤心处. 近几年, 特别是上大学以来, 学会用佛道两教的理论来调整自己的心态, 现在接触这些事时终于能坦然面对了, 是福耶? 是祸耶? 不管! 人总是要生存的, 生死离别总是会面对的 ... 花开花落, 缘起缘灭, 谁能料计?

虽然不相信有天堂, 但在这凡尘俗世中给自己一个心灵的寄托也未尝不可 ...

愿已经过去的人不再有俗世的烦恼, 愿还在这边的人为我们停下过去的脚步...




==> 2006年11月18日 星期六 <==

畢業感懷




忙忙忙,今日辛苦為誰忙?徨徨徨,華髪早生心徬徨。一步踏出校園門,前途遠景均渺茫,今朝有酒今朝醉,他朝事情天知曉!萬裡晴空臨碧海,聰明人亦有糊塗時,何必心擔明日憂?




==> 2006年7月22日 星期六 <==

踏碎的夢




走過昨日的昨日,步過今天的今天,邁向明天的明天……曾經多少的夢想與理想,曾經多少的渴望與期待,都逐漸在歲月的年輪上冷卻下來,多了几分理性,但那顆 心卻未曾褪色,依然如舊,提醒我其實那份熱情只是被理性的外殼包裹着。我相信,終有一天它會迸發出來,放出耀眼的光芒,實踐我那超天的志向。即使那天已經 被未來塵封于永久的虛空,我也不會失去對它的信心。

曾經的我低聲沉吟:“假使世上所有的人不理解,我仍要繼續前路”。

今天的我輕聲笑說:“如若天要亡我,則我要逆天!”。

明天的我仰天長嘯:“世上庶几無難事!”