<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    在上一講中,筆者介紹了DirectShow的總體系統(tǒng)框架。從這一講開始,我們要從程序員的角度,進(jìn)一步深入探討一下DirectShow的應(yīng)用以及Filter的開發(fā)。
    在這之前,筆者首先要特別提一下微軟提供的一個(gè)Filter測(cè)試工具——GraphEdit,它的路徑在DXSDK\bin\DXUtils\GraphEdit.exe。(如果您還沒有安裝DirectX SDK,請(qǐng)到微軟的網(wǎng)站上去下載。)通過這個(gè)工具,我們可以很直觀地看到Filter Graph的運(yùn)行及處理流程,方便我們進(jìn)行程序調(diào)試。(如果您手邊就有電腦,還等什么,馬上體驗(yàn)一下吧:運(yùn)行GraphEdit,執(zhí)行File->Render Media File…選擇一個(gè)媒體文件;當(dāng)Filter Graph構(gòu)建成功后,按下工具欄的運(yùn)行按鈕;您就能看到剛才選擇的媒體文件被回放出來了!看到了吧,寫一個(gè)媒體播放器也就這么回事!)
    接下去,我們開講Filter的開發(fā)。
    學(xué)習(xí)DirectShow Filter的開發(fā),不外乎以下幾種方法:看幫助文檔、看示例代碼和看SDK基類源代碼。看幫助文檔,應(yīng)著重于總體概念上的理解;看示例代碼應(yīng)與基類源代碼的研究同步進(jìn)行,因?yàn)樽约簩慒ilter,關(guān)鍵的第一步是選擇一個(gè)合適的Filter基類和Pin的基類。對(duì)于Filter的把握,一般認(rèn)為要掌握以下三方面的內(nèi)容:Filter之間Pin的連接、Filter之間的數(shù)據(jù)傳輸以及流媒體的隨機(jī)訪問(或者說流的定位)。下面就開始分別進(jìn)行闡述。
    所謂的Filter Pin之間的連接,實(shí)際上是Pin之間Media Type(媒體類型)的一個(gè)協(xié)商過程。連接總是從輸出Pin指向輸入Pin的。要想深入了解具體的連接過程,就必須認(rèn)真研讀SDK的基類源代碼(位于DXSDK\samples\Multimedia\DirectShow\BaseClasses\amfilter.cpp,類CBasePin的Connect方法)。連接的大致過程為,枚舉欲連接的輸入Pin上所有的媒體類型,逐一用這些媒體類型與輸出Pin進(jìn)行連接,如果輸出Pin也接受這種媒體類型,則Pin之間的連接宣告成功;如果所有輸入Pin上枚舉的媒體類型輸出Pin都不支持,則枚舉輸出Pin上的所有媒體類型,并逐一用這些媒體類型與輸入Pin進(jìn)行連接。如果輸入Pin接受其中的一種媒體類型,則Pin之間的連接到此也宣告成功;如果輸出Pin上的所有媒體類型,輸入Pin都不支持,則這兩個(gè)Pin之間的連接過程宣告失敗。
    有一點(diǎn)需要注意的是,上述的輸入Pin與輸出Pin一般不屬于同一個(gè)Filter,典型的是上一級(jí)Filter(也叫Upstream Filter)的輸出Pin連向下一級(jí)Filter(也叫Downstream Filter)的輸入Pin。如下圖所示:



    當(dāng)Filter的Pin之間連接完成,也就是說,連接雙方通過協(xié)商取得了一種大家都支持的媒體類型之后,即開始為數(shù)據(jù)傳輸做準(zhǔn)備。這些準(zhǔn)備工作中,最重要的是Pin上的內(nèi)存分配器的協(xié)商,一般也是由輸出Pin發(fā)起。在DirectShow Filter之間,數(shù)據(jù)是通過一個(gè)一個(gè)數(shù)據(jù)包傳送的,這個(gè)數(shù)據(jù)包叫做Sample。Sample本身是一個(gè)COM對(duì)象,擁有一段內(nèi)存用以裝載數(shù)據(jù),Sample就由內(nèi)存分配器(Allocator)來統(tǒng)一管理。已成功連接的一對(duì)輸出、輸入Pin使用同一個(gè)內(nèi)存分配器,所以數(shù)據(jù)從輸出Pin傳送到輸入Pin上是無需內(nèi)存拷貝的。而典型的數(shù)據(jù)拷貝,一般發(fā)生在Filter內(nèi)部,從Filter的輸入Pin上讀取數(shù)據(jù)后,進(jìn)行一定意圖的處理,然后在Filter的輸出Pin上填充數(shù)據(jù),然后繼續(xù)往下傳輸。下面,我們就具體闡述一下Filter之間的數(shù)據(jù)傳送。
    首先,大家要區(qū)分一下Filter的兩種主要的數(shù)據(jù)傳輸模式:推模式(Push Model)和拉模式(Pull Model)。參考圖如下:


      
    所謂推模式,即源Filter(Source Filter)自己能夠產(chǎn)生數(shù)據(jù),并且一般在它的輸出Pin上有獨(dú)立的子線程負(fù)責(zé)將數(shù)據(jù)發(fā)送出去,常見的情況如代表WDM模型的采集卡的Live Source Filter;而所謂拉模式,即源Filter不具有把自己的數(shù)據(jù)送出去的能力,這種情況下,一般源Filter后緊跟著接一個(gè)Parser Filter或Splitter Filter,這種Filter一般在輸入Pin上有個(gè)獨(dú)立的子線程,負(fù)責(zé)不斷地從源Filter索取數(shù)據(jù),然后經(jīng)過處理后將數(shù)據(jù)傳送下去,常見的情況如文件源。推模式下,源Filter是主動(dòng)的;拉模式下,源Filter是被動(dòng)的。而事實(shí)上,如果將上圖拉模式中的源Filter和Splitter Filter看成另一個(gè)虛擬的源Filter,則后面的Filter之間的數(shù)據(jù)傳輸也與推模式完全相同。
    那么,數(shù)據(jù)到底是怎么通過連接著的Pin傳輸?shù)哪兀渴紫葋砜赐颇J健T谠碏ilter后面的Filter輸入Pin上,一定實(shí)現(xiàn)了一個(gè)IMemInputPin接口,數(shù)據(jù)正是通過上一級(jí)Filter調(diào)用這個(gè)接口的Receive方法進(jìn)行傳輸?shù)摹V档米⒁獾氖牵ㄉ厦嬉呀?jīng)提到過),數(shù)據(jù)從輸出Pin通過Receive方法調(diào)用傳輸?shù)捷斎隤in上,并沒有進(jìn)行內(nèi)存拷貝,它只是一個(gè)相當(dāng)于數(shù)據(jù)到達(dá)的“通知”。再看一下拉模式。拉模式下的源Filter的輸出Pin上,一定實(shí)現(xiàn)了一個(gè)IAsyncReader接口;其后面的Splitter Filter,就是通過調(diào)用這個(gè)接口的Request方法或者SyncRead方法來獲得數(shù)據(jù)。Splitter Filter然后像推模式一樣,調(diào)用下一級(jí)Filter輸入Pin上的IMemInputPin接口Receive方法實(shí)現(xiàn)數(shù)據(jù)的往下傳送。深入了解這部分內(nèi)容,請(qǐng)認(rèn)真研讀SDK的基類源代碼(位于DXSDK\samples\Multimedia\DirectShow\BaseClasses\source.cpp和pullpin.cpp)。
    下面,我們來講一下流的定位(Media Seeking)。在GraphEdit中,當(dāng)我們成功構(gòu)建了一個(gè)Filter Graph之后,我們就可以播放它。在播放中,我們可以看到進(jìn)度條也在相應(yīng)地前進(jìn)。當(dāng)然,我們也可以通過拖動(dòng)進(jìn)度條,實(shí)現(xiàn)隨機(jī)訪問。要做到這一點(diǎn),在應(yīng)用程序級(jí)別應(yīng)該可以知道Filter Graph總共要播放多長時(shí)間,當(dāng)前播放到什么位置等等。那么,在Filter級(jí)別,這一點(diǎn)是怎么實(shí)現(xiàn)的呢?
    我們知道,若干個(gè)Filter通過Pin的相互連接組成了Filter Graph。而這個(gè)Filter Graph是由另一個(gè)COM對(duì)象Filter Graph Manager來管理的。通過Filter Graph Manager,我們就可以得到一個(gè)IMediaSeeking的接口來實(shí)現(xiàn)對(duì)流媒體的定位。在Filter級(jí)別,我們可以看到,F(xiàn)ilter Graph Manager首先從最后一個(gè)Filter(Renderer Filter)開始,詢問上一級(jí)Filter的輸出Pin是否支持IMediaSeeking接口。如果支持,則返回這個(gè)接口;如果不支持,則繼續(xù)往上一級(jí)Filter詢問,直到源Filter。一般在源Filter的輸出Pin上實(shí)現(xiàn)IMediaSeeking接口,它告訴調(diào)用者總共有多長時(shí)間的媒體內(nèi)容,當(dāng)前播放位置等信息。(如果是文件源,一般在Parser Filter或Splitter Filter實(shí)現(xiàn)這個(gè)接口。)對(duì)于Filter開發(fā)者來說,如果我們寫的是源Filter,我們就要在Filter的輸出Pin上實(shí)現(xiàn)IMediaSeeking這個(gè)接口;如果寫的是中間的傳輸Filter,只需要在輸出Pin上將用戶的獲得接口請(qǐng)求往上傳遞給上一級(jí)Filter的輸出Pin;如果寫的是Renderer Filter,需要在Filter上將用戶的獲得接口請(qǐng)求往上傳遞給上一級(jí)Filter的輸出Pin。進(jìn)一步的了解,請(qǐng)認(rèn)真研讀SDK的基類源代碼(位于DXSDK\samples\Multimedia\DirectShow\BaseClasses\transfrm.cpp的類方法CTransformOutputPin::NonDelegatingQueryInterface實(shí)現(xiàn)和ctlutil.cpp中類CPosPassThru的實(shí)現(xiàn))。
    以上我們介紹了一下如何學(xué)習(xí)DirectShow Filter開發(fā),以及一些開始寫自己的Filter之前的預(yù)備知識(shí)。下一講,筆者將根據(jù)自己開發(fā)Filter的經(jīng)驗(yàn),手把手教你如何寫自己的Filter。

    posted @ 2008-09-19 15:06 小馬歌 閱讀(395) | 評(píng)論 (0)編輯 收藏
     
    來源:http://blog.csdn.net/happydeer/archive/2003/01/04/8769.aspx

    流媒體的處理,以其復(fù)雜性和技術(shù)性,一向廣受工業(yè)界的關(guān)注。特別伴隨著因特網(wǎng)的普及,流媒體在網(wǎng)絡(luò)上的廣泛應(yīng)用,怎樣使流媒體的處理變得簡單而富有成效逐漸成為了焦點(diǎn)問題。選擇一種合適的應(yīng)用方案,事半功倍。此時(shí),微軟的DirectShow,給了我們一個(gè)不錯(cuò)的選擇。
    DirectShow是微軟公司提供的一套在Windows平臺(tái)上進(jìn)行流媒體處理的開發(fā)包,與DirectX開發(fā)包一起發(fā)布。目前,DirectX最新版本為9.0。
    那么,DirectShow能夠做些什么呢?且看,DirectShow為多媒體流的捕捉和回放提供了強(qiáng)有力的支持。運(yùn)用DirectShow,我們可以很方便地從支持WDM驅(qū)動(dòng)模型的采集卡上捕獲數(shù)據(jù),并且進(jìn)行相應(yīng)的后期處理乃至存儲(chǔ)到文件中。它廣泛地支持各種媒體格式,包括Asf、Mpeg、Avi、Dv、Mp3、Wave等等,使得多媒體數(shù)據(jù)的回放變得輕而易舉。另外,DirectShow還集成了DirectX其它部分(比如DirectDraw、DirectSound)的技術(shù),直接支持DVD的播放,視頻的非線性編輯,以及與數(shù)字?jǐn)z像機(jī)的數(shù)據(jù)交換。更值得一提的是,DirectShow提供的是一種開放式的開發(fā)環(huán)境,我們可以根據(jù)自己的需要定制自己的組件。
    接下去,我們需要對(duì)DirectShow系統(tǒng)有個(gè)整體的印象。參見以下DirectShow的系統(tǒng)示意圖:


    圖中央最大的一塊即是DirectShow系統(tǒng)。DirectShow使用一種叫Filter Graph的模型來管理整個(gè)數(shù)據(jù)流的處理過程;參與數(shù)據(jù)處理的各個(gè)功能模塊叫做Filter;各個(gè)Filter在Filter Graph中按一定的順序連接成一條“流水線”協(xié)同工作。大家可以看到,按照功能來分,F(xiàn)ilter大致分為三類:Source Filters、Transform Filters和Rendering Filters。Source Filters主要負(fù)責(zé)取得數(shù)據(jù),數(shù)據(jù)源可以是文件、因特網(wǎng)、或者計(jì)算機(jī)里的采集卡、數(shù)字?jǐn)z像機(jī)等,然后將數(shù)據(jù)往下傳輸;Transform Fitlers主要負(fù)責(zé)數(shù)據(jù)的格式轉(zhuǎn)換、傳輸;Rendering Filtes主要負(fù)責(zé)數(shù)據(jù)的最終去向,我們可以將數(shù)據(jù)送給聲卡、顯卡進(jìn)行多媒體的演示,也可以輸出到文件進(jìn)行存儲(chǔ)。值得注意的是,三個(gè)部分并不是都只有一個(gè)Filter去完成功能。恰恰相反,每個(gè)部分往往是有幾個(gè)Fitler協(xié)同工作的。比如,Transform Filters可能包含了一個(gè)Mpeg的解碼Filter、以及視頻色彩空間的轉(zhuǎn)換Filter、音頻采樣頻率轉(zhuǎn)換Filter等等。除了系統(tǒng)提供的大量Filter外,我們可以定制自己的Filter,以完成我們需要的功能。下圖是一條典型的Avi文件回放Filter Graph鏈路:



    在DirectShow系統(tǒng)之上,我們看到的,即是我們的應(yīng)用程序(Application)。應(yīng)用程序要按照一定的意圖建立起相應(yīng)的Filter Graph,然后通過Filter Graph Manager來控制整個(gè)的數(shù)據(jù)處理過程。DirectShow能在Filter Graph運(yùn)行的時(shí)候接收到各種事件,并通過消息的方式發(fā)送到我們的應(yīng)用程序。這樣,就實(shí)現(xiàn)了應(yīng)用程序與DirectShow系統(tǒng)之間的交互。下圖給出了DirectShow應(yīng)用程序開發(fā)的一般過程:




    以上簡單介紹了DirectShow的系統(tǒng)結(jié)構(gòu),希望大家對(duì)這個(gè)強(qiáng)勁的應(yīng)用框架已經(jīng)有了大概的認(rèn)識(shí)。如果你有興趣,可以詳細(xì)研究DirectX的幫助文檔。DirectShow是一個(gè)強(qiáng)大的開發(fā)包;另外,它是基于COM的,因此要求程序員具有COM編程的一些基本知識(shí)。關(guān)于如何深入學(xué)習(xí)DirectShow應(yīng)用結(jié)構(gòu)以及開發(fā)自己的Filter,請(qǐng)參閱筆者的后續(xù)文章。筆者將從編程的角度,詳細(xì)講述來源于實(shí)際工作中的經(jīng)驗(yàn)之談。

    posted @ 2008-09-19 15:05 小馬歌 閱讀(247) | 評(píng)論 (0)編輯 收藏
     

    來源:http://blog.csdn.net/happydeer

    小知識(shí):AVI文件格式----摘自《DirectShow實(shí)務(wù)精選》 作者:陸其明

    AVI(Audio Video Interleaved的縮寫)是一種RIFF(Resource Interchange File Format的縮寫)文件格式,多用于音視頻捕捉、編輯、回放等應(yīng)用程序中。通常情況下,一個(gè)AVI文件可以包含多個(gè)不同類型的媒體流(典型的情況下有一個(gè)音頻流和一個(gè)視頻流),不過含有單一音頻流或單一視頻流的AVI文件也是合法的。AVI可以算是Windows操作系統(tǒng)上最基本的、也是最常用的一種媒體文件格式。


    先來介紹RIFF文件格式。RIFF文件使用四字符碼FOURCC(four-character code)來表征數(shù)據(jù)類型,比如‘RIFF’、‘AVI ’、‘LIST’等。注意,Windows操作系統(tǒng)使用的字節(jié)順序是little-endian,因此一個(gè)四字符碼‘abcd’實(shí)際的DWORD值應(yīng)為0x64636261。另外,四字符碼中像‘AVI ’一樣含有空格也是合法的。


    RIFF文件首先含有一個(gè)如圖3.31的文件頭結(jié)構(gòu)。

     

    圖3.31 RIFF文件結(jié)構(gòu)

    最開始的4個(gè)字節(jié)是一個(gè)四字符碼‘RIFF’,表示這是一個(gè)RIFF文件;緊跟著后面用4個(gè)字節(jié)表示此RIFF文件的大小;然后又是一個(gè)四字符碼說明文件的具體類型(比如AVI、WAVE等);最后就是實(shí)際的數(shù)據(jù)。注意文件大小值的計(jì)算方法為:實(shí)際數(shù)據(jù)長度 + 4(文件類型域的大小);也就是說,文件大小的值不包括‘RIFF’域和“文件大小”域本身的大小。


    RIFF文件的實(shí)際數(shù)據(jù)中,通常還使用了列表(List)和塊(Chunk)的形式來組織。列表可以嵌套子列表和塊。其中,列表的結(jié)構(gòu)為:‘LIST’ listSize listType listData ——‘LIST’是一個(gè)四字符碼,表示這是一個(gè)列表;listSize占用4字節(jié),記錄了整個(gè)列表的大小;listType也是一個(gè)四字符碼,表示本列表的具體類型;listData就是實(shí)際的列表數(shù)據(jù)。注意listSize值的計(jì)算方法為:實(shí)際的列表數(shù)據(jù)長度 + 4(listType域的大小);也就是說listSize值不包括‘LIST’域和listSize域本身的大小。再來看塊的結(jié)構(gòu):ckID ckSize ckData ——ckID是一個(gè)表示塊類型的四字符碼;ckSize占用4字節(jié),記錄了整個(gè)塊的大小;ckData為實(shí)際的塊數(shù)據(jù)。注意ckSize值指的是實(shí)際的塊數(shù)據(jù)長度,而不包括ckID域和ckSize域本身的大小。(注意:在下面的內(nèi)容中,將以LIST ( listType ( listData ) )的形式來表示一個(gè)列表,以ckID ( ckData )的形式來表示一個(gè)塊,如[ optional element ]中括號(hào)中的元素表示為可選項(xiàng)。)


    接下來介紹AVI文件格式。AVI文件類型用一個(gè)四字符碼‘AVI ’來表示。整個(gè)AVI文件的結(jié)構(gòu)為:一個(gè)RIFF頭 + 兩個(gè)列表(一個(gè)用于描述媒體流格式、一個(gè)用于保存媒體流數(shù)據(jù)) + 一個(gè)可選的索引塊。AVI文件的展開結(jié)構(gòu)大致如下:

     

    RIFF (‘AVI ’
          LIST (‘hdrl’
                ‘avih’(主AVI信息頭數(shù)據(jù))
                LIST (‘strl’
                      ‘strh’ (流的頭信息數(shù)據(jù))
                      ‘strf’ (流的格式信息數(shù)據(jù))
                      [‘strd’ (可選的額外的頭信息數(shù)據(jù)) ]
                      [‘strn’ (可選的流的名字) ]
                      ...
                     )
                 ...
               )
          LIST (‘movi’
                { SubChunk | LIST (‘rec ’
                                  SubChunk1
                                  SubChunk2
                                  ...
                                 )
                   ...
                }
                ...
               )
          [‘idx1’ (可選的AVI索引塊數(shù)據(jù)) ]
         )

    首先,RIFF (‘AVI ’…)表征了AVI文件類型。然后就是AVI文件必需的第一個(gè)列表——‘hdrl’列表,用于描述AVI文件中各個(gè)流的格式信息(AVI文件中的每一路媒體數(shù)據(jù)都稱為一個(gè)流)。‘hdrl’列表嵌套了一系列塊和子列表——首先是一個(gè)‘avih’塊,用于記錄AVI文件的全局信息,比如流的數(shù)量、視頻圖像的寬和高等,可以使用一個(gè)AVIMAINHEADER數(shù)據(jù)結(jié)構(gòu)來操作:

    typedef struct _avimainheader {
        FOURCC fcc;   // 必須為‘avih’
        DWORD  cb;    // 本數(shù)據(jù)結(jié)構(gòu)的大小,不包括最初的8個(gè)字節(jié)(fcc和cb兩個(gè)域)
        DWORD  dwMicroSecPerFrame;   // 視頻幀間隔時(shí)間(以毫秒為單位)
        DWORD  dwMaxBytesPerSec;     // 這個(gè)AVI文件的最大數(shù)據(jù)率
        DWORD  dwPaddingGranularity; // 數(shù)據(jù)填充的粒度
        DWORD  dwFlags;         // AVI文件的全局標(biāo)記,比如是否含有索引塊等
        DWORD  dwTotalFrames;   // 總幀數(shù)
        DWORD  dwInitialFrames; // 為交互格式指定初始幀數(shù)(非交互格式應(yīng)該指定為0)
        DWORD  dwStreams;       // 本文件包含的流的個(gè)數(shù)
        DWORD  dwSuggestedBufferSize; // 建議讀取本文件的緩存大小(應(yīng)能容納最大的塊)
        DWORD  dwWidth;         // 視頻圖像的寬(以像素為單位)
        DWORD  dwHeight;        // 視頻圖像的高(以像素為單位)
        DWORD  dwReserved[4];   // 保留
    } AVIMAINHEADER;

    然后,就是一個(gè)或多個(gè)‘strl’子列表。(文件中有多少個(gè)流,這里就對(duì)應(yīng)有多少個(gè)‘strl’子列表。)每個(gè)‘strl’子列表至少包含一個(gè)‘strh’塊和一個(gè)‘strf’塊,而‘strd’塊(保存編解碼器需要的一些配置信息)和‘strn’塊(保存流的名字)是可選的。首先是‘strh’塊,用于說明這個(gè)流的頭信息,可以使用一個(gè)AVISTREAMHEADER數(shù)據(jù)結(jié)構(gòu)來操作:

    typedef struct _avistreamheader {
         FOURCC fcc;  // 必須為‘strh’
         DWORD  cb;   // 本數(shù)據(jù)結(jié)構(gòu)的大小,不包括最初的8個(gè)字節(jié)(fcc和cb兩個(gè)域)
    FOURCC fccType;    // 流的類型:‘auds’(音頻流)、‘vids’(視頻流)、
                       //‘mids’(MIDI流)、‘txts’(文字流)
         FOURCC fccHandler; // 指定流的處理者,對(duì)于音視頻來說就是解碼器
         DWORD  dwFlags;    // 標(biāo)記:是否允許這個(gè)流輸出?調(diào)色板是否變化?
         WORD   wPriority;  // 流的優(yōu)先級(jí)(當(dāng)有多個(gè)相同類型的流時(shí)優(yōu)先級(jí)最高的為默認(rèn)流)
         WORD   wLanguage;
         DWORD  dwInitialFrames; // 為交互格式指定初始幀數(shù)
         DWORD  dwScale;   // 這個(gè)流使用的時(shí)間尺度
         DWORD  dwRate;
         DWORD  dwStart;   // 流的開始時(shí)間
         DWORD  dwLength;  // 流的長度(單位與dwScale和dwRate的定義有關(guān))
         DWORD  dwSuggestedBufferSize; // 讀取這個(gè)流數(shù)據(jù)建議使用的緩存大小
         DWORD  dwQuality;    // 流數(shù)據(jù)的質(zhì)量指標(biāo)(0 ~ 10,000)
         DWORD  dwSampleSize; // Sample的大小
         struct {
             short int left;
             short int top;
             short int right;
             short int bottom;
    }  rcFrame;  // 指定這個(gè)流(視頻流或文字流)在視頻主窗口中的顯示位置
                 // 視頻主窗口由AVIMAINHEADER結(jié)構(gòu)中的dwWidth和dwHeight決定
    } AVISTREAMHEADER;

    然后是‘strf’塊,用于說明流的具體格式。如果是視頻流,則使用一個(gè)BITMAPINFO數(shù)據(jù)結(jié)構(gòu)來描述;如果是音頻流,則使用一個(gè)WAVEFORMATEX數(shù)據(jù)結(jié)構(gòu)來描述。


    當(dāng)AVI文件中的所有流都使用一個(gè)‘strl’子列表說明了以后(注意:‘strl’子列表出現(xiàn)的順序與媒體流的編號(hào)是對(duì)應(yīng)的,比如第一個(gè)‘strl’子列表說明的是第一個(gè)流(Stream 0),第二個(gè)‘strl’子列表說明的是第二個(gè)流(Stream 1),以此類推),‘hdrl’列表的任務(wù)也就完成了,隨后跟著的就是AVI文件必需的第二個(gè)列表——‘movi’列表,用于保存真正的媒體流數(shù)據(jù)(視頻圖像幀數(shù)據(jù)或音頻采樣數(shù)據(jù)等)。那么,怎么來組織這些數(shù)據(jù)呢?可以將數(shù)據(jù)塊直接嵌在‘movi’列表里面,也可以將幾個(gè)數(shù)據(jù)塊分組成一個(gè)‘rec ’列表后再編排進(jìn)‘movi’列表。(注意:在讀取AVI文件內(nèi)容時(shí),建議將一個(gè)‘rec ’列表中的所有數(shù)據(jù)塊一次性讀出。)但是,當(dāng)AVI文件中包含有多個(gè)流的時(shí)候,數(shù)據(jù)塊與數(shù)據(jù)塊之間如何來區(qū)別呢?于是數(shù)據(jù)塊使用了一個(gè)四字符碼來表征它的類型,這個(gè)四字符碼由2個(gè)字節(jié)的類型碼和2個(gè)字節(jié)的流編號(hào)組成。標(biāo)準(zhǔn)的類型碼定義如下:‘db’(非壓縮視頻幀)、‘dc’(壓縮視頻幀)、‘pc’(改用新的調(diào)色板)、‘wb’(音縮視頻)。比如第一個(gè)流(Stream 0)是音頻,則表征音頻數(shù)據(jù)塊的四字符碼為‘00wb’;第二個(gè)流(Stream 1)是視頻,則表征視頻數(shù)據(jù)塊的四字符碼為‘00db’或‘00dc’。對(duì)于視頻數(shù)據(jù)來說,在AVI數(shù)據(jù)序列中間還可以定義一個(gè)新的調(diào)色板,每個(gè)改變的調(diào)色板數(shù)據(jù)塊用‘xxpc’來表征,新的調(diào)色板使用一個(gè)數(shù)據(jù)結(jié)構(gòu)AVIPALCHANGE來定義。(注意:如果一個(gè)流的調(diào)色辦中途可能改變,則應(yīng)在這個(gè)流格式的描述中,也就是AVISTREAMHEADER結(jié)構(gòu)的dwFlags中包含一個(gè)AVISF_VIDEO_PALCHANGES標(biāo)記。)另外,文字流數(shù)據(jù)塊可以使用隨意的類型碼表征。


    最后,緊跟在‘hdrl’列表和‘movi’列表之后的,就是AVI文件可選的索引塊。這個(gè)索引塊為AVI文件中每一個(gè)媒體數(shù)據(jù)塊進(jìn)行索引,并且記錄它們?cè)谖募械钠疲赡芟鄬?duì)于‘movi’列表,也可能相對(duì)于AVI文件開頭)。索引塊使用一個(gè)四字符碼‘idx1’來表征,索引信息使用一個(gè)數(shù)據(jù)結(jié)構(gòu)來AVIOLDINDEX定義。

     

    typedef struct _avioldindex {
       FOURCC  fcc;  // 必須為‘idx1’
       DWORD   cb;   // 本數(shù)據(jù)結(jié)構(gòu)的大小,不包括最初的8個(gè)字節(jié)(fcc和cb兩個(gè)域)
       struct _avioldindex_entry {
          DWORD   dwChunkId;   // 表征本數(shù)據(jù)塊的四字符碼
          DWORD   dwFlags;     // 說明本數(shù)據(jù)塊是不是關(guān)鍵幀、是不是‘rec ’列表等信息
          DWORD   dwOffset;    // 本數(shù)據(jù)塊在文件中的偏移量
          DWORD   dwSize;      // 本數(shù)據(jù)塊的大小
      } aIndex[]; // 這是一個(gè)數(shù)組!為每個(gè)媒體數(shù)據(jù)塊都定義一個(gè)索引信息
    } AVIOLDINDEX;

    注意:如果一個(gè)AVI文件包含有索引塊,則應(yīng)在主AVI信息頭的描述中,也就是AVIMAINHEADER結(jié)構(gòu)的dwFlags中包含一個(gè)AVIF_HASINDEX標(biāo)記。


    還有一種特殊的數(shù)據(jù)塊,用一個(gè)四字符碼‘JUNK’來表征,它用于內(nèi)部數(shù)據(jù)的隊(duì)齊(填充),應(yīng)用程序應(yīng)該忽略這些數(shù)據(jù)塊的實(shí)際意義。

    posted @ 2008-09-19 15:03 小馬歌 閱讀(301) | 評(píng)論 (0)編輯 收藏
     
    在我的另一篇日志中,說到利用FFmpeg從視頻截圖的命令,那天在找從視頻截取指定幀的圖片的辦法,這么多天沒有進(jìn)展,原來我從網(wǎng)上找的關(guān)于FFmpeg的參數(shù)命令列表并不全,少了-ss這么一個(gè)參數(shù).于是這個(gè)問題也到現(xiàn)在才解決.

        今天利用FFmpeg -h > ffmpeg.txt,把FFmpeg的命令打印出來后,才發(fā)現(xiàn)了這一參數(shù):

    -ss time_off set the start time offset

    使用-ss參數(shù),可以從指定的時(shí)間開始處理轉(zhuǎn)換任務(wù).如:

    ffmpeg -i test2.asf -y -f image2 -ss 08.010 -t 0.001 -s 352x240 b.jpg

    那么從任意一幀截圖的問題也就解決了.只要-ss后的時(shí)間參數(shù)是隨機(jī)產(chǎn)生,并且在視頻的有效時(shí)間內(nèi),就可以了.

     另外,-ss后跟的時(shí)間單位為秒。

    2006
    -06-27 補(bǔ)充:


    另外,通過指定
    -ss,和-vframes也可以達(dá)到同樣的效果。

    這時(shí)候
    -ss參數(shù)后跟的時(shí)間有兩種寫法,hh:mm:ss 或 直接寫秒數(shù) :

    ffmpeg 
    -i test.asf --f  image2  -ss 00:01:00 -vframes 1  test1.jpg
    or
    ffmpeg 
    -i test.asf --f  image2  -ss 60 -vframes 1  test1.jpg

    這樣輸出的圖片是相同的。
    posted @ 2008-09-19 15:01 小馬歌 閱讀(710) | 評(píng)論 (0)編輯 收藏
     
    來源:http://ffmpeg.blogbus.com/logs/445361.html

    在為Linux開發(fā)應(yīng)用程序時(shí),絕大多數(shù)情況下使用的都是C語言,因此幾乎每一位Linux程序員面臨的首要問題都是如何靈活運(yùn)用C編譯器。目前Linux下最常用的C語言編譯器是GCC(GNU Compiler Collection),它是GNU項(xiàng)目中符合ANSI C標(biāo)準(zhǔn)的編譯系統(tǒng),能夠編譯用C、C++和Object C等語言編寫的程序。GCC不僅功能非常強(qiáng)大,結(jié)構(gòu)也異常靈活。最值得稱道的一點(diǎn)就是它可以通過不同的前端模塊來支持各種語言,如Java、Fortran、Pascal、Modula-3和Ada等。

    開放、自由和靈活是Linux的魅力所在,而這一點(diǎn)在GCC上的體現(xiàn)就是程序員通過它能夠更好地控制整個(gè)編譯過程。在使用GCC編譯程序時(shí),編譯過程可以被細(xì)分為四個(gè)階段:

    ◆ 預(yù)處理(Pre-Processing)

    ◆ 編譯(Compiling)

    ◆ 匯編(Assembling)

    ◆ 鏈接(Linking)

    Linux程序員可以根據(jù)自己的需要讓GCC在編譯的任何階段結(jié)束,以便檢查或使用編譯器在該階段的輸出信息,或者對(duì)最后生成的二進(jìn)制文件進(jìn)行控制,以便通過加入不同數(shù)量和種類的調(diào)試代碼來為今后的調(diào)試做好準(zhǔn)備。和其它常用的編譯器一樣,GCC也提供了靈活而強(qiáng)大的代碼優(yōu)化功能,利用它可以生成執(zhí)行效率更高的代碼。

    GCC提供了30多條警告信息和三個(gè)警告級(jí)別,使用它們有助于增強(qiáng)程序的穩(wěn)定性和可移植性。此外,GCC還對(duì)標(biāo)準(zhǔn)的C和C++語言進(jìn)行了大量的擴(kuò)展,提高程序的執(zhí)行效率,有助于編譯器進(jìn)行代碼優(yōu)化,能夠減輕編程的工作量。

    GCC起步

    在學(xué)習(xí)使用GCC之前,下面的這個(gè)例子能夠幫助用戶迅速理解GCC的工作原理,并將其立即運(yùn)用到實(shí)際的項(xiàng)目開發(fā)中去。首先用熟悉的編輯器輸入清單1所示的代碼:

    清單1:hello.c

     #include <stdio.h>
     int main(void)
     {
      printf ("Hello world, Linux programming!\n");
      return 0;
     }
     
    然后執(zhí)行下面的命令編譯和運(yùn)行這段程序:

     # gcc hello.c -o hello # ./hello Hello world, Linux programming!
     
    從程序員的角度看,只需簡單地執(zhí)行一條GCC命令就可以了,但從編譯器的角度來看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調(diào)用預(yù)處理程序cpp,由它負(fù)責(zé)展開在源文件中定義的宏,并向其中插入“#include”語句所包含的內(nèi)容;接著,GCC會(huì)調(diào)用ccl和as將處理后的源代碼編譯成目標(biāo)代碼;最后,GCC會(huì)調(diào)用鏈接程序ld,把生成的目標(biāo)代碼鏈接成一個(gè)可執(zhí)行程序。

    為了更好地理解GCC的工作過程,可以把上述編譯過程分成幾個(gè)步驟單獨(dú)進(jìn)行,并觀察每步的運(yùn)行結(jié)果。第一步是進(jìn)行預(yù)編譯,使用-E參數(shù)可以讓GCC在預(yù)處理結(jié)束后停止編譯過程:

     # gcc -E hello.c -o hello.i
     
    此時(shí)若查看hello.cpp文件中的內(nèi)容,會(huì)發(fā)現(xiàn)stdio.h的內(nèi)容確實(shí)都插到文件里去了,而其它應(yīng)當(dāng)被預(yù)處理的宏定義也都做了相應(yīng)的處理。下一步是將hello.i編譯為目標(biāo)代碼,這可以通過使用-c參數(shù)來完成:

     # gcc -c hello.i -o hello.o
     
    GCC默認(rèn)將.i文件看成是預(yù)處理后的C語言源代碼,因此上述命令將自動(dòng)跳過預(yù)處理步驟而開始執(zhí)行編譯過程,也可以使用-x參數(shù)讓GCC從指定的步驟開始編譯。最后一步是將生成的目標(biāo)文件鏈接成可執(zhí)行文件:

     # gcc hello.o -o hello
     
    在采用模塊化的設(shè)計(jì)思想進(jìn)行軟件開發(fā)時(shí),通常整個(gè)程序是由多個(gè)源文件組成的,相應(yīng)地也就形成了多個(gè)編譯單元,使用GCC能夠很好地管理這些編譯單元。假設(shè)有一個(gè)由foo1.c和foo2.c兩個(gè)源文件組成的程序,為了對(duì)它們進(jìn)行編譯,并最終生成可執(zhí)行程序foo,可以使用下面這條命令:

     # gcc foo1.c foo2.c -o foo
     
    如果同時(shí)處理的文件不止一個(gè),GCC仍然會(huì)按照預(yù)處理、編譯和鏈接的過程依次進(jìn)行。如果深究起來,上面這條命令大致相當(dāng)于依次執(zhí)行如下三條命令:

     # gcc -c foo1.c -o foo1.o # gcc -c foo2.c -o foo2.o # gcc foo1.o foo2.o -o foo
     
    在編譯一個(gè)包含許多源文件的工程時(shí),若只用一條GCC命令來完成編譯是非常浪費(fèi)時(shí)間的。假設(shè)項(xiàng)目中有100個(gè)源文件需要編譯,并且每個(gè)源文件中都包含10000行代碼,如果像上面那樣僅用一條GCC命令來完成編譯工作,那么GCC需要將每個(gè)源文件都重新編譯一遍,然后再全部連接起來。很顯然,這樣浪費(fèi)的時(shí)間相當(dāng)多,尤其是當(dāng)用戶只是修改了其中某一個(gè)文件的時(shí)候,完全沒有必要將每個(gè)文件都重新編譯一遍,因?yàn)楹芏嘁呀?jīng)生成的目標(biāo)文件是不會(huì)改變的。要解決這個(gè)問題,關(guān)鍵是要靈活運(yùn)用GCC,同時(shí)還要借助像Make這樣的工具。

    警告提示功能

    GCC包含完整的出錯(cuò)檢查和警告提示功能,它們可以幫助Linux程序員寫出更加專業(yè)和優(yōu)美的代碼。先來讀讀清單2所示的程序,這段代碼寫得很糟糕,仔細(xì)檢查一下不難挑出很多毛病:

    ◆main函數(shù)的返回值被聲明為void,但實(shí)際上應(yīng)該是int;

    ◆使用了GNU語法擴(kuò)展,即使用long long來聲明64位整數(shù),不符合ANSI/ISO C語言標(biāo)準(zhǔn);

    ◆main函數(shù)在終止前沒有調(diào)用return語句。

    清單2:illcode.c

     #include <stdio.h>
     void main(void)
     {
      long long int var = 1;
      printf("It is not standard C code!\n");
     }

    下面來看看GCC是如何幫助程序員來發(fā)現(xiàn)這些錯(cuò)誤的。當(dāng)GCC在編譯不符合ANSI/ISO C語言標(biāo)準(zhǔn)的源代碼時(shí),如果加上了-pedantic選項(xiàng),那么使用了擴(kuò)展語法的地方將產(chǎn)生相應(yīng)的警告信息:

     # gcc -pedantic illcode.c -o illcode illcode.c: In function `main': illcode.c:9: ISO C89 does not support `long long' illcode.c:8: return type of `main' is not `int'

    需要注意的是,-pedantic編譯選項(xiàng)并不能保證被編譯程序與ANSI/ISO C標(biāo)準(zhǔn)的完全兼容,它僅僅只能用來幫助Linux程序員離這個(gè)目標(biāo)越來越近。或者換句話說,-pedantic選項(xiàng)能夠幫助程序員發(fā)現(xiàn)一些不符合ANSI/ISO C標(biāo)準(zhǔn)的代碼,但不是全部,事實(shí)上只有ANSI/ISO C語言標(biāo)準(zhǔn)中要求進(jìn)行編譯器診斷的那些情況,才有可能被GCC發(fā)現(xiàn)并提出警告。

    除了-pedantic之外,GCC還有一些其它編譯選項(xiàng)也能夠產(chǎn)生有用的警告信息。這些選項(xiàng)大多以-W開頭,其中最有價(jià)值的當(dāng)數(shù)-Wall了,使用它能夠使GCC產(chǎn)生盡可能多的警告信息:

     # gcc -Wall illcode.c -o illcode illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var'

    GCC給出的警告信息雖然從嚴(yán)格意義上說不能算作是錯(cuò)誤,但卻很可能成為錯(cuò)誤的棲身之所。一個(gè)優(yōu)秀的Linux程序員應(yīng)該盡量避免產(chǎn)生警告信息,使自己的代碼始終保持簡潔、優(yōu)美和健壯的特性

    在處理警告方面,另一個(gè)常用的編譯選項(xiàng)是-Werror,它要求GCC將所有的警告當(dāng)成錯(cuò)誤進(jìn)行處理,這在使用自動(dòng)編譯工具(如Make等)時(shí)非常有用。如果編譯時(shí)帶上-Werror選項(xiàng),那么GCC會(huì)在所有產(chǎn)生警告的地方停止編譯,迫使程序員對(duì)自己的代碼進(jìn)行修改。只有當(dāng)相應(yīng)的警告信息消除時(shí),才可能將編譯過程繼續(xù)朝前推進(jìn)。執(zhí)行情況如下:

     # gcc -Wall -Werror illcode.c -o illcode cc1: warnings being treated as errors illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var'

    對(duì)Linux程序員來講,GCC給出的警告信息是很有價(jià)值的,它們不僅可以幫助程序員寫出更加健壯的程序,而且還是跟蹤和調(diào)試程序的有力工具。建議在用GCC編譯源代碼時(shí)始終帶上-Wall選項(xiàng),并把它逐漸培養(yǎng)成為一種習(xí)慣,這對(duì)找出常見的隱式編程錯(cuò)誤很有幫助。

    庫依賴

    在Linux下開發(fā)軟件時(shí),完全不使用第三方函數(shù)庫的情況是比較少見的,通常來講都需要借助一個(gè)或多個(gè)函數(shù)庫的支持才能夠完成相應(yīng)的功能。從程序員的角度看,函數(shù)庫實(shí)際上就是一些頭文件(.h)和庫文件(.so或者.a)的集合。雖然Linux下的大多數(shù)函數(shù)都默認(rèn)將頭文件放到/usr/include/目錄下,而庫文件則放到/usr/lib/目錄下,但并不是所有的情況都是這樣。正因如此,GCC在編譯時(shí)必須有自己的辦法來查找所需要的頭文件和庫文件。

    GCC采用搜索目錄的辦法來查找所需要的文件,-I選項(xiàng)可以向GCC的頭文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/include/目錄下有編譯時(shí)所需要的頭文件,為了讓GCC能夠順利地找到它們,就可以使用-I選項(xiàng):

     # gcc foo.c -I /home/xiaowp/include -o foo

    同樣,如果使用了不在標(biāo)準(zhǔn)位置的庫文件,那么可以通過-L選項(xiàng)向GCC的庫文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫文件libfoo.so,為了讓GCC能夠順利地找到它,可以使用下面的命令:

     # gcc foo.c -L /home/xiaowp/lib -lfoo -o foo

    值得好好解釋一下的是-l選項(xiàng),它指示GCC去連接庫文件libfoo.so。Linux下的庫文件在命名時(shí)有一個(gè)約定,那就是應(yīng)該以lib三個(gè)字母開頭,由于所有的庫文件都遵循了同樣的規(guī)范,因此在用-l選項(xiàng)指定鏈接的庫文件名時(shí)可以省去lib三個(gè)字母,也就是說GCC在對(duì)-lfoo進(jìn)行處理時(shí),會(huì)自動(dòng)去鏈接名為libfoo.so的文件。

    Linux下的庫文件分為兩大類分別是動(dòng)態(tài)鏈接庫(通常以.so結(jié)尾)和靜態(tài)鏈接庫(通常以.a結(jié)尾),兩者的差別僅在程序執(zhí)行時(shí)所需的代碼是在運(yùn)行時(shí)動(dòng)態(tài)加載的,還是在編譯時(shí)靜態(tài)加載的。默認(rèn)情況下,GCC在鏈接時(shí)優(yōu)先使用動(dòng)態(tài)鏈接庫,只有當(dāng)動(dòng)態(tài)鏈接庫不存在時(shí)才考慮使用靜態(tài)鏈接庫,如果需要的話可以在編譯時(shí)加上-static選項(xiàng),強(qiáng)制使用靜態(tài)鏈接庫。例如,如果在/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫文件libfoo.so和libfoo.a,為了讓GCC在鏈接時(shí)只用到靜態(tài)鏈接庫,可以使用下面的命令:

     # gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo
     
    代碼優(yōu)化

    代碼優(yōu)化指的是編譯器通過分析源代碼,找出其中尚未達(dá)到最優(yōu)的部分,然后對(duì)其重新進(jìn)行組合,目的是改善程序的執(zhí)行性能。GCC提供的代碼優(yōu)化功能非常強(qiáng)大,它通過編譯選項(xiàng)-On來控制優(yōu)化代碼的生成,其中n是一個(gè)代表優(yōu)化級(jí)別的整數(shù)。對(duì)于不同版本的GCC來講,n的取值范圍及其對(duì)應(yīng)的優(yōu)化效果可能并不完全相同,比較典型的范圍是從0變化到2或3。

    編譯時(shí)使用選項(xiàng)-O可以告訴GCC同時(shí)減小代碼的長度和執(zhí)行時(shí)間,其效果等價(jià)于-O1。在這一級(jí)別上能夠進(jìn)行的優(yōu)化類型雖然取決于目標(biāo)處理器,但一般都會(huì)包括線程跳轉(zhuǎn)(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優(yōu)化。選項(xiàng)-O2告訴GCC除了完成所有-O1級(jí)別的優(yōu)化之外,同時(shí)還要進(jìn)行一些額外的調(diào)整工作,如處理器指令調(diào)度等。選項(xiàng)-O3則除了完成所有-O2級(jí)別的優(yōu)化之外,還包括循環(huán)展開和其它一些與處理器特性相關(guān)的優(yōu)化工作。通常來說,數(shù)字越大優(yōu)化的等級(jí)越高,同時(shí)也就意味著程序的運(yùn)行速度越快。許多Linux程序員都喜歡使用-O2選項(xiàng),因?yàn)樗趦?yōu)化長度、編譯時(shí)間和代碼大小之間,取得了一個(gè)比較理想的平衡點(diǎn)。

    下面通過具體實(shí)例來感受一下GCC的代碼優(yōu)化功能,所用程序如清單3所示。

    清單3:optimize.c

     #include <stdio.h>
     int main(void)
     {
      double counter;
      double result;
      double temp;
      
      for (counter = 0; counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5 - 1) / 4)
      {
        temp = counter / 1979; result = counter;
      }
      printf("Result is %lf\n", result); return 0;
     }

    首先不加任何優(yōu)化選項(xiàng)進(jìn)行編譯:

     # gcc -Wall optimize.c -o optimize
     
    借助Linux提供的time命令,可以大致統(tǒng)計(jì)出該程序在運(yùn)行時(shí)所需要的時(shí)間:

     # time ./optimize Result is 400002019.000000 real 0m14.942s user 0m14.940s sys 0m0.000s
     
    接下去使用優(yōu)化選項(xiàng)來對(duì)代碼進(jìn)行優(yōu)化處理:

     # gcc -Wall -O optimize.c -o optimize
     
    在同樣的條件下再次測(cè)試一下運(yùn)行時(shí)間:

     # time ./optimize Result is 400002019.000000 real 0m3.256s user 0m3.240s sys 0m0.000s
     
    對(duì)比兩次執(zhí)行的輸出結(jié)果不難看出,程序的性能的確得到了很大幅度的改善,由原來的14秒縮短到了3秒。這個(gè)例子是專門針對(duì)GCC的優(yōu)化功能而設(shè)計(jì)的,因此優(yōu)化前后程序的執(zhí)行速度發(fā)生了很大的改變。盡管GCC的代碼優(yōu)化功能非常強(qiáng)大,但作為一名優(yōu)秀的Linux程序員,首先還是要力求能夠手工編寫出高質(zhì)量的代碼。如果編寫的代碼簡短,并且邏輯性強(qiáng),編譯器就不會(huì)做更多的工作,甚至根本用不著優(yōu)化。

    優(yōu)化雖然能夠給程序帶來更好的執(zhí)行性能,但在如下一些場(chǎng)合中應(yīng)該避免優(yōu)化代碼:

    ◆ 程序開發(fā)的時(shí)候 優(yōu)化等級(jí)越高,消耗在編譯上的時(shí)間就越長,因此在開發(fā)的時(shí)候最好不要使用優(yōu)化選項(xiàng),只有到軟件發(fā)行或開發(fā)結(jié)束的時(shí)候,才考慮對(duì)最終生成的代碼進(jìn)行優(yōu)化。

    ◆ 資源受限的時(shí)候 一些優(yōu)化選項(xiàng)會(huì)增加可執(zhí)行代碼的體積,如果程序在運(yùn)行時(shí)能夠申請(qǐng)到的內(nèi)存資源非常緊張(如一些實(shí)時(shí)嵌入式設(shè)備),那就不要對(duì)代碼進(jìn)行優(yōu)化,因?yàn)橛蛇@帶來的負(fù)面影響可能會(huì)產(chǎn)生非常嚴(yán)重的后果。

    ◆ 跟蹤調(diào)試的時(shí)候 在對(duì)代碼進(jìn)行優(yōu)化的時(shí)候,某些代碼可能會(huì)被刪除或改寫,或者為了取得更佳的性能而進(jìn)行重組,從而使跟蹤和調(diào)試變得異常困難。

    調(diào)試

    一個(gè)功能強(qiáng)大的調(diào)試器不僅為程序員提供了跟蹤程序執(zhí)行的手段,而且還可以幫助程序員找到解決問題的方法。對(duì)于Linux程序員來講,GDB(GNU Debugger)通過與GCC的配合使用,為基于Linux的軟件開發(fā)提供了一個(gè)完善的調(diào)試環(huán)境。

    默認(rèn)情況下,GCC在編譯時(shí)不會(huì)將調(diào)試符號(hào)插入到生成的二進(jìn)制代碼中,因?yàn)檫@樣會(huì)增加可執(zhí)行文件的大小。如果需要在編譯時(shí)生成調(diào)試符號(hào)信息,可以使用GCC的-g或者-ggdb選項(xiàng)。GCC在產(chǎn)生調(diào)試符號(hào)時(shí),同樣采用了分級(jí)的思路,開發(fā)人員可以通過在-g選項(xiàng)后附加數(shù)字1、2或3來指定在代碼中加入調(diào)試信息的多少。默認(rèn)的級(jí)別是2(-g2),此時(shí)產(chǎn)生的調(diào)試信息包括擴(kuò)展的符號(hào)表、行號(hào)、局部或外部變量信息。級(jí)別3(-g3)包含級(jí)別2中的所有調(diào)試信息,以及源代碼中定義的宏。級(jí)別1(-g1)不包含局部變量和與行號(hào)有關(guān)的調(diào)試信息,因此只能夠用于回溯跟蹤和堆棧轉(zhuǎn)儲(chǔ)之用。回溯跟蹤指的是監(jiān)視程序在運(yùn)行過程中的函數(shù)調(diào)用歷史,堆棧轉(zhuǎn)儲(chǔ)則是一種以原始的十六進(jìn)制格式保存程序執(zhí)行環(huán)境的方法,兩者都是經(jīng)常用到的調(diào)試手段。

    GCC產(chǎn)生的調(diào)試符號(hào)具有普遍的適應(yīng)性,可以被許多調(diào)試器加以利用,但如果使用的是GDB,那么還可以通過-ggdb選項(xiàng)在生成的二進(jìn)制代碼中包含GDB專用的調(diào)試信息。這種做法的優(yōu)點(diǎn)是可以方便GDB的調(diào)試工作,但缺點(diǎn)是可能導(dǎo)致其它調(diào)試器(如DBX)無法進(jìn)行正常的調(diào)試。選項(xiàng)-ggdb能夠接受的調(diào)試級(jí)別和-g是完全一樣的,它們對(duì)輸出的調(diào)試符號(hào)有著相同的影響。

    需要注意的是,使用任何一個(gè)調(diào)試選項(xiàng)都會(huì)使最終生成的二進(jìn)制文件的大小急劇增加,同時(shí)增加程序在執(zhí)行時(shí)的開銷,因此調(diào)試選項(xiàng)通常僅在軟件的開發(fā)和調(diào)試階段使用。調(diào)試選項(xiàng)對(duì)生成代碼大小的影響從下面的對(duì)比過程中可以看出來:

     # gcc optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 20 08:53 optimize (未加調(diào)試選項(xiàng)) # gcc -g optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 15889 Nov 20 08:54 optimize (加入調(diào)試選項(xiàng))
     
    雖然調(diào)試選項(xiàng)會(huì)增加文件的大小,但事實(shí)上Linux中的許多軟件在測(cè)試版本甚至最終發(fā)行版本中仍然使用了調(diào)試選項(xiàng)來進(jìn)行編譯,這樣做的目的是鼓勵(lì)用戶在發(fā)現(xiàn)問題時(shí)自己動(dòng)手解決,是Linux的一個(gè)顯著特色。

    下面還是通過一個(gè)具體的實(shí)例說明如何利用調(diào)試符號(hào)來分析錯(cuò)誤,所用程序見清單4所示。

    清單4:crash.c

     #include <stdio.h>
     int main(void)
     {
      int input =0;
      
      printf("Input an integer:");
      scanf("%d", input);
      printf("The integer you input is %d\n", input);
      return 0;
     }
     
    編譯并運(yùn)行上述代碼,會(huì)產(chǎn)生一個(gè)嚴(yán)重的段錯(cuò)誤(Segmentation fault)如下:

     # gcc -g crash.c -o crash # ./crash Input an integer:10 Segmentation fault
     
    為了更快速地發(fā)現(xiàn)錯(cuò)誤所在,可以使用GDB進(jìn)行跟蹤調(diào)試,方法如下:

     # gdb crash GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)
     
    當(dāng)GDB提示符出現(xiàn)的時(shí)候,表明GDB已經(jīng)做好準(zhǔn)備進(jìn)行調(diào)試了,現(xiàn)在可以通過run命令讓程序開始在GDB的監(jiān)控下運(yùn)行:

     (gdb) run Starting program: /home/xiaowp/thesis/gcc/code/crash Input an integer:10 Program received signal SIGSEGV, Segmentation fault. 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
     
    仔細(xì)分析一下GDB給出的輸出結(jié)果不難看出,程序是由于段錯(cuò)誤而導(dǎo)致異常中止的,說明內(nèi)存操作出了問題,具體發(fā)生問題的地方是在調(diào)用_IO_vfscanf_internal ( )的時(shí)候。為了得到更加有價(jià)值的信息,可以使用GDB提供的回溯跟蹤命令backtrace,執(zhí)行結(jié)果如下:

     (gdb) backtrace #0 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6 #1 0xbffff0c0 in ?? () #2 0x4008e0ba in scanf () from /lib/libc.so.6 #3 0x08048393 in main () at crash.c:11 #4 0x40042917 in __libc_start_main () from /lib/libc.so.6
     
    跳過輸出結(jié)果中的前面三行,從輸出結(jié)果的第四行中不難看出,GDB已經(jīng)將錯(cuò)誤定位到crash.c中的第11行了。現(xiàn)在仔細(xì)檢查一下:

     (gdb) frame 3 #3 0x08048393 in main () at crash.c:11 11 scanf("%d", input);
     
    使用GDB提供的frame命令可以定位到發(fā)生錯(cuò)誤的代碼段,該命令后面跟著的數(shù)值可以在backtrace命令輸出結(jié)果中的行首找到。現(xiàn)在已經(jīng)發(fā)現(xiàn)錯(cuò)誤所在了,應(yīng)該將

     scanf("%d", input); 改為 scanf("%d", &input);
     
    完成后就可以退出GDB了,命令如下:

     (gdb) quit
     
    GDB的功能遠(yuǎn)遠(yuǎn)不止如此,它還可以單步跟蹤程序、檢查內(nèi)存變量和設(shè)置斷點(diǎn)等。

    調(diào)試時(shí)可能會(huì)需要用到編譯器產(chǎn)生的中間結(jié)果,這時(shí)可以使用-save-temps選項(xiàng),讓GCC將預(yù)處理代碼、匯編代碼和目標(biāo)代碼都作為文件保存起來。如果想檢查生成的代碼是否能夠通過手工調(diào)整的辦法來提高執(zhí)行性能,在編譯過程中生成的中間文件將會(huì)很有幫助,具體情況如下:

     # gcc -save-temps foo.c -o foo # ls foo* foo foo.c foo.i foo.s
     
    GCC支持的其它調(diào)試選項(xiàng)還包括-p和-pg,它們會(huì)將剖析(Profiling)信息加入到最終生成的二進(jìn)制代碼中。剖析信息對(duì)于找出程序的性能瓶頸很有幫助,是協(xié)助Linux程序員開發(fā)出高性能程序的有力工具。在編譯時(shí)加入-p選項(xiàng)會(huì)在生成的代碼中加入通用剖析工具(Prof)能夠識(shí)別的統(tǒng)計(jì)信息,而-pg選項(xiàng)則生成只有GNU剖析工具(Gprof)才能識(shí)別的統(tǒng)計(jì)信息。

    最后提醒一點(diǎn),雖然GCC允許在優(yōu)化的同時(shí)加入調(diào)試符號(hào)信息,但優(yōu)化后的代碼對(duì)于調(diào)試本身而言將是一個(gè)很大的挑戰(zhàn)。代碼在經(jīng)過優(yōu)化之后,在源程序中聲明和使用的變量很可能不再使用,控制流也可能會(huì)突然跳轉(zhuǎn)到意外的地方,循環(huán)語句有可能因?yàn)檠h(huán)展開而變得到處都有,所有這些對(duì)調(diào)試來講都將是一場(chǎng)噩夢(mèng)。建議在調(diào)試的時(shí)候最好不使用任何優(yōu)化選項(xiàng),只有當(dāng)程序在最終發(fā)行的時(shí)候才考慮對(duì)其進(jìn)行優(yōu)化。

    上次的培訓(xùn)園地中介紹了GCC的編譯過程、警告提示功能、庫依賴、代碼優(yōu)化和程序調(diào)試六個(gè)方面的內(nèi)容。這期是最后的一部分內(nèi)容。

    加速

    在將源代碼變成可執(zhí)行文件的過程中,需要經(jīng)過許多中間步驟,包含預(yù)處理、編譯、匯編和連接。這些過程實(shí)際上是由不同的程序負(fù)責(zé)完成的。大多數(shù)情況下GCC可以為Linux程序員完成所有的后臺(tái)工作,自動(dòng)調(diào)用相應(yīng)程序進(jìn)行處理。

    這樣做有一個(gè)很明顯的缺點(diǎn),就是GCC在處理每一個(gè)源文件時(shí),最終都需要生成好幾個(gè)臨時(shí)文件才能完成相應(yīng)的工作,從而無形中導(dǎo)致處理速度變慢。例如,GCC在處理一個(gè)源文件時(shí),可能需要一個(gè)臨時(shí)文件來保存預(yù)處理的輸出、一個(gè)臨時(shí)文件來保存編譯器的輸出、一個(gè)臨時(shí)文件來保存匯編器的輸出,而讀寫這些臨時(shí)文件顯然需要耗費(fèi)一定的時(shí)間。當(dāng)軟件項(xiàng)目變得非常龐大的時(shí)候,花費(fèi)在這上面的代價(jià)可能會(huì)變得很沉重。

    解決的辦法是,使用Linux提供的一種更加高效的通信方式—管道。它可以用來同時(shí)連接兩個(gè)程序,其中一個(gè)程序的輸出將被直接作為另一個(gè)程序的輸入,這樣就可以避免使用臨時(shí)文件,但編譯時(shí)卻需要消耗更多的內(nèi)存。

    在編譯過程中使用管道是由GCC的-pipe選項(xiàng)決定的。下面的這條命令就是借助GCC的管道功能來提高編譯速度的:

     # gcc -pipe foo.c -o foo
     
    在編譯小型工程時(shí)使用管道,編譯時(shí)間上的差異可能還不是很明顯,但在源代碼非常多的大型工程中,差異將變得非常明顯。

    文件擴(kuò)展名

    在使用GCC的過程中,用戶對(duì)一些常用的擴(kuò)展名一定要熟悉,并知道其含義。為了方便大家學(xué)習(xí)使用GCC,在此將這些擴(kuò)展名羅列如下:

    .c C原始程序;

    .C C++原始程序;

    .cc C++原始程序;

    .cxx C++原始程序;

    .m Objective-C原始程序;

    .i 已經(jīng)過預(yù)處理的C原始程序;

    .ii 已經(jīng)過預(yù)處理之C++原始程序;

    .s 組合語言原始程序;

    .S 組合語言原始程序;

    .h 預(yù)處理文件(標(biāo)頭文件);

    .o 目標(biāo)文件;

    .a 存檔文件。

    GCC常用選項(xiàng)

    GCC作為Linux下C/C++重要的編譯環(huán)境,功能強(qiáng)大,編譯選項(xiàng)繁多。為了方便大家日后編譯方便,在此將常用的選項(xiàng)及說明羅列出來如下:

    -c 通知GCC取消鏈接步驟,即編譯源碼并在最后生成目標(biāo)文件;

    -Dmacro 定義指定的宏,使它能夠通過源碼中的#ifdef進(jìn)行檢驗(yàn);

    -E 不經(jīng)過編譯預(yù)處理程序的輸出而輸送至標(biāo)準(zhǔn)輸出;

    -g3 獲得有關(guān)調(diào)試程序的詳細(xì)信息,它不能與-o選項(xiàng)聯(lián)合使用;

    -Idirectory 在包含文件搜索路徑的起點(diǎn)處添加指定目錄;

    -llibrary 提示鏈接程序在創(chuàng)建最終可執(zhí)行文件時(shí)包含指定的庫;

    -O、-O2、-O3 將優(yōu)化狀態(tài)打開,該選項(xiàng)不能與-g選項(xiàng)聯(lián)合使用;

    -S 要求編譯程序生成來自源代碼的匯編程序輸出;

    -v 啟動(dòng)所有警報(bào);

    -Wall 在發(fā)生警報(bào)時(shí)取消編譯操作,即將警報(bào)看作是錯(cuò)誤;

    -Werror 在發(fā)生警報(bào)時(shí)取消編譯操作,即把報(bào)警當(dāng)作是錯(cuò)誤;

    -w 禁止所有的報(bào)警。

    小結(jié)

    GCC是在Linux下開發(fā)程序時(shí)必須掌握的工具之一。本文對(duì)GCC做了一個(gè)簡要的介紹,主要講述了如何使用GCC編譯程序、產(chǎn)生警告信息、調(diào)試程序和加快GCC的編譯速度。對(duì)所有希望早日跨入Linux開發(fā)者行列的人來說,GCC就是成為一名優(yōu)秀的Linux程序員的起跑線。

    posted @ 2008-09-19 14:55 小馬歌 閱讀(305) | 評(píng)論 (0)編輯 收藏
     
    來源:http://ffmpeg.blogbus.com/logs/578829.html

    2004年4月20日最新版本的GCC編譯器3.4.0發(fā)布了。目前,GCC可以用來編譯C/C++、FORTRAN、JAVA、OBJC、ADA等語言的程序,可根據(jù)需要選擇安裝支持的語言。GCC 3.4.0比以前版本更好地支持了C++標(biāo)準(zhǔn)。本文以在Redhat Linux上安裝GCC3.4.0為例,介紹了GCC的安裝過程。

      安裝之前,系統(tǒng)中必須要有cc或者gcc等編譯器,并且是可用的,或者用環(huán)境變量CC指定系統(tǒng)上的編譯器。如果系統(tǒng)上沒有編譯器,不能安裝源代碼形式的GCC 3.4.0。如果是這種情況,可以在網(wǎng)上找一個(gè)與你系統(tǒng)相適應(yīng)的如RPM等二進(jìn)制形式的GCC軟件包來安裝使用。本文介紹的是以源代碼形式提供的GCC軟件包的安裝過程,軟件包本身和其安裝過程同樣適用于其它Linux和Unix系統(tǒng)。

      系統(tǒng)上原來的GCC編譯器可能是把gcc等命令文件、庫文件、頭文件等分別存放到系統(tǒng)中的不同目錄下的。與此不同,現(xiàn)在GCC建議我們將一個(gè)版本的GCC安裝在一個(gè)單獨(dú)的目錄下。這樣做的好處是將來不需要它的時(shí)候可以方便地刪除整個(gè)目錄即可(因?yàn)镚CC沒有uninstall功能);缺點(diǎn)是在安裝完成后要做一些設(shè)置工作才能使編譯器工作正常。在本文中我采用這個(gè)方案安裝GCC 3.4.0,并且在安裝完成后,仍然能夠使用原來低版本的GCC編譯器,即一個(gè)系統(tǒng)上可以同時(shí)存在并使用多個(gè)版本的GCC編譯器。

      按照本文提供的步驟和設(shè)置選項(xiàng),即使以前沒有安裝過GCC,也可以在系統(tǒng)上安裝上一個(gè)可工作的新版本的GCC編譯器。

      1. 下載

      在GCC網(wǎng)站上(http://gcc.gnu.org/)或者通過網(wǎng)上搜索可以查找到下載資源。目前GCC的最新版本為 3.4.0。可供下載的文件一般有兩種形式:gcc-3.4.0.tar.gz和gcc-3.4.0.tar.bz2,只是壓縮格式不一樣,內(nèi)容完全一致,下載其中一種即可。

      2. 解壓縮

      根據(jù)壓縮格式,選擇下面相應(yīng)的一種方式解包(以下的“%”表示命令行提示符):

      % tar xzvf gcc-3.4.0.tar.gz
      或者
      % bzcat gcc-3.4.0.tar.bz2 | tar xvf -

      新生成的gcc-3.4.0這個(gè)目錄被稱為源目錄,用${srcdir}表示它。以后在出現(xiàn)${srcdir}的地方,應(yīng)該用真實(shí)的路徑來替換它。用pwd命令可以查看當(dāng)前路徑。

      在${srcdir}/INSTALL目錄下有詳細(xì)的GCC安裝說明,可用瀏覽器打開index.html閱讀。

      3. 建立目標(biāo)目錄
     
      目標(biāo)目錄(用${objdir}表示)是用來存放編譯結(jié)果的地方。GCC建議編譯后的文件不要放在源目錄${srcdir]中(雖然這樣做也可以),最好單獨(dú)存放在另外一個(gè)目錄中,而且不能是${srcdir}的子目錄。

      例如,可以這樣建立一個(gè)叫 gcc-build 的目標(biāo)目錄(與源目錄${srcdir}是同級(jí)目錄):

      % mkdir gcc-build
      % cd gcc-build

      以下的操作主要是在目標(biāo)目錄 ${objdir} 下進(jìn)行。

      4. 配置
     
      配置的目的是決定將GCC編譯器安裝到什么地方(${destdir}),支持什么語言以及指定其它一些選項(xiàng)等。其中,${destdir}不能與${objdir}或${srcdir}目錄相同。

      配置是通過執(zhí)行${srcdir}下的configure來完成的。其命令格式為(記得用你的真實(shí)路徑替換${destdir}):

      % ${srcdir}/configure --prefix=${destdir} [其它選項(xiàng)]

      例如,如果想將GCC 3.4.0安裝到/usr/local/gcc-3.4.0目錄下,則${destdir}就表示這個(gè)路徑。

      在我的機(jī)器上,我是這樣配置的:

      % ../gcc-3.4.0/configure --prefix=/usr/local/gcc-3.4.0 --enable-threads=posix --disable-checking --enable--long-long --host=i386-redhat-linux --with-system-zlib --enable-languages=c,c++,java

      將GCC安裝在/usr/local/gcc-3.4.0目錄下,支持C/C++和JAVA語言,其它選項(xiàng)參見GCC提供的幫助說明。

      5. 編譯

      % make

      這是一個(gè)漫長的過程。在我的機(jī)器上(P4-1.6),這個(gè)過程用了50多分鐘。

      6. 安裝

      執(zhí)行下面的命令將編譯好的庫文件等拷貝到${destdir}目錄中(根據(jù)你設(shè)定的路徑,可能需要管理員的權(quán)限):

      % make install

      至此,GCC 3.4.0安裝過程就完成了。

      6. 其它設(shè)置

      GCC 3.4.0的所有文件,包括命令文件(如gcc、g++)、庫文件等都在${destdir}目錄下分別存放,如命令文件放在bin目錄下、庫文件在lib下、頭文件在include下等。由于命令文件和庫文件所在的目錄還沒有包含在相應(yīng)的搜索路徑內(nèi),所以必須要作適當(dāng)?shù)脑O(shè)置之后編譯器才能順利地找到并使用它們。

      6.1 gcc、g++、gcj的設(shè)置

      要想使用GCC 3.4.0的gcc等命令,簡單的方法就是把它的路徑${destdir}/bin放在環(huán)境變量PATH中。我不用這種方式,而是用符號(hào)連接的方式實(shí)現(xiàn),這樣做的好處是我仍然可以使用系統(tǒng)上原來的舊版本的GCC編譯器。

      首先,查看原來的gcc所在的路徑:

      % which gcc

      在我的系統(tǒng)上,上述命令顯示:/usr/bin/gcc。因此,原來的gcc命令在/usr/bin目錄下。我們可以把GCC 3.4.0中的gcc、g++、gcj等命令在/usr/bin目錄下分別做一個(gè)符號(hào)連接:

      % cd /usr/bin
      % ln -s ${destdir}/bin/gcc gcc34
      % ln -s ${destdir}/bin/g++ g++34
      % ln -s ${destdir}/bin/gcj gcj34

      這樣,就可以分別使用gcc34、g++34、gcj34來調(diào)用GCC 3.4.0的gcc、g++、gcj完成對(duì)C、C++、JAVA程序的編譯了。同時(shí),仍然能夠使用舊版本的GCC編譯器中的gcc、g++等命令。

      6.2 庫路徑的設(shè)置

      將${destdir}/lib路徑添加到環(huán)境變量LD_LIBRARY_PATH中,最好添加到系統(tǒng)的配置文件中,這樣就不必要每次都設(shè)置這個(gè)環(huán)境變量了。

      例如,如果GCC 3.4.0安裝在/usr/local/gcc-3.4.0目錄下,在RH Linux下可以直接在命令行上執(zhí)行或者在文件/etc/profile中添加下面一句:

      setenv LD_LIBRARY_PATH /usr/local/gcc-3.4.0/lib:$LD_LIBRARY_PATH

      7. 測(cè)試
     
      用新的編譯命令(gcc34、g++34等)編譯你以前的C、C++程序,檢驗(yàn)新安裝的GCC編譯器是否能正常工作。

      8. 根據(jù)需要,可以刪除或者保留${srcdir}和${objdir}目錄。

    posted @ 2008-09-19 14:53 小馬歌 閱讀(286) | 評(píng)論 (0)編輯 收藏
     

    dwr.xml中的簽名(Signatures)
    signatures段使DWR能確定集合中存放的數(shù)據(jù)類型。例如下面的定義中我們無法知道list中存放的是什么類型。

    public class Check
    {
      public void setLotteryResults(List nos)
      {
          ...
      }
    }
    signatures段允許我們暗示DWR應(yīng)該用什么類型去處理。格式對(duì)以了解JDK5的泛型的人來說很容易理解。

    <signatures>
      <![CDATA[
      import java.util.List;
      import com.example.Check;
      Check.setLotteryResults(List<Integer> nos);
      ]]>
    </signatures>
    DWR中又一個(gè)解析器專門來做這件事,所以即便你的環(huán)境時(shí)JDK1.3 DWR也能正常工作。

    解析規(guī)則基本上會(huì)和你預(yù)想規(guī)則的一樣(有兩個(gè)例外),所以java.lang下面的類型會(huì)被默認(rèn)import。

    第一個(gè)是DWR1.0中解析器的bug,某些環(huán)境下不能返回正確類型。所以你也不用管它了。

    第二個(gè)是這個(gè)解析器時(shí)"陽光(sunny day)"解析器。就是說它非常寬松,不想編譯器那樣嚴(yán)格的保證你一定正確。所以有時(shí)它也會(huì)允許你丟失import:

    <signatures>
      <![CDATA[
      import java.util.List;
      Check.setLotteryResults(List<Integer>);
      ]]>
    </signatures>
    將來的DWR版本會(huì)使用一個(gè)更正式的解析器,這個(gè)編譯器會(huì)基于官方Java定義,所以你最好不要使用太多這個(gè)不嚴(yán)格的東西。

    signatures段只是用來確定泛型參數(shù)中的類型參數(shù)。DWR會(huì)自己使用反射機(jī)制或者運(yùn)行時(shí)類型確定類型,或者假設(shè)它是一個(gè)String類型。所以:

    不需要signatures - 沒有泛型參數(shù):

    public void method(String p);
    public void method(String[] p);
    需要signatures - DWR不能通過反射確定:

    public void method(List<Date> p);
    public void method(Map<String, WibbleBean> p);
    不需要signatures - DWR能正確的猜出:

    public void method(List<String> p);
    public void method(Map<String, String> p);
    不需要signatures - DWR可以通過運(yùn)行時(shí)類型確定:

    public List<Date> method(String p);
    沒有必要讓Javascript中的所有對(duì)象的key都是String類型 - 你可以使用其他類型作為key。但是他們?cè)谑褂弥皶?huì)被轉(zhuǎn)換成String類型。DWR1.x用Javascript的特性把key轉(zhuǎn)換成String。DWR2.0可能會(huì)用toString()方法,在服務(wù)段進(jìn)行這一轉(zhuǎn)換。


    Comments  (Hide)
    如果對(duì)于遠(yuǎn)程接口都包含public void method(Map<String, WibbleBean> p);和public void method(Map<String, String> p);的情況該怎么設(shè)置?DWR貌似會(huì)使用后面的設(shè)置,也就是好像不能夠兩種接口都成功設(shè)置。

    posted @ 2008-09-17 12:51 小馬歌 閱讀(456) | 評(píng)論 (1)編輯 收藏
     

    轉(zhuǎn)換器
    轉(zhuǎn)換器在客戶端和服務(wù)器之間轉(zhuǎn)換數(shù)據(jù).

    下面這些轉(zhuǎn)換器有單獨(dú)章節(jié)介紹

    Array Converter
    Bean and Object Converters
    Collection Converter
    Enum Converter
    DOM Objects
    Hibernate整合
    Servlet Objects (HttpServletRequest, HttpSession, etc)
    基礎(chǔ)的轉(zhuǎn)換器
    原生類型,String,像BigDecimal這樣的簡單對(duì)象的轉(zhuǎn)換器已經(jīng)有了。你不需要在dwr.xml中<allow>部分的<convert>中定義。它們默認(rèn)支持。

    默認(rèn)支持的類型包括: boolean, byte, short, int, long, float, double, char, java.lang.Boolean, java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Float, java.lang.Double, java.lang.Character, java.math.BigInteger, java.math.BigDecimal 和 java.lang.String

    Date轉(zhuǎn)換器
    Date轉(zhuǎn)換器負(fù)責(zé)在Javascript的Date類型與Java中的Date類型(java.util.Date, java.sql.Date, java.sql.Times or java.sql.Timestamp)之間進(jìn)行轉(zhuǎn)換。同基礎(chǔ)的轉(zhuǎn)換器一樣,DateConverter默認(rèn)是支持的。

    如果你有一個(gè)Javascript的字符串 (例如"01 Jan 2010") ,你想把它轉(zhuǎn)換成Java的Date類型有兩個(gè)辦法:在javascript中用Date.parse()把它解析成Date類型,然后用DWR的DateConverter傳遞給服務(wù)器;或者把它作為字符串傳遞給Server,再用Java中的SimpleDateFormat(或者類似的)來解析。

    同樣,如果你有個(gè)Java的Date類型并且希望在HTML使用它。你可以先用SimpleDateFormat把它轉(zhuǎn)換成字符串再使用。也可以直接傳Date給Javascript,然后用Javascript格式化。第一種方式簡單一些,盡管浪費(fèi)了你的轉(zhuǎn)換器,而且這樣做也會(huì)是瀏覽器上的顯示邏輯受到限制。其實(shí)后面的方法更好,也有一些工具可以幫你,例如:

    The Javascript Toolbox Date formatter
    Web Developers Notes on Date formatting
    其他對(duì)象
    其實(shí)創(chuàng)建自己的轉(zhuǎn)換器也很簡單。Converter接口的Javadoc包含了信息。其實(shí)這種需要很少出現(xiàn)。在你寫自己的Converter之前先看看BeanConverter,它有可能就是你要的。

    posted @ 2008-09-17 12:50 小馬歌 閱讀(169) | 評(píng)論 (0)編輯 收藏
     

    Bean 和 Object 轉(zhuǎn)換器
    兩個(gè)沒有默認(rèn)打開的轉(zhuǎn)換器是Bean 和 Object 轉(zhuǎn)換器。Bean轉(zhuǎn)換器可以把POJO轉(zhuǎn)換成Javascript的接合數(shù)組(類似與Java中的Map),或者反向轉(zhuǎn)換。這個(gè)轉(zhuǎn)換器默認(rèn)情況下是沒打開的,因?yàn)镈WR要獲得你的允許才能動(dòng)你的代碼。

    Object轉(zhuǎn)換器很相似,不同的是它直接應(yīng)用于對(duì)象的成員,而不是通過getter和setter方法。下面的例子都是可以用object來替換bean的來直接訪問對(duì)象成員。

    如果你有一個(gè)在 <create ...> 中聲明的遠(yuǎn)程調(diào)用Bean。它有個(gè)一參數(shù)也是一個(gè)bean,并且這個(gè)bean有一個(gè)setter存在一些安全隱患,那么攻擊者就可能利用這一點(diǎn)。

    你可以為某一個(gè)單獨(dú)的類打開轉(zhuǎn)換器:

    <convert converter="bean" match="your.full.package.BeanName"/>
    如果要允許轉(zhuǎn)換一個(gè)包或者子包下面的所有類,可以這樣寫:

    <convert converter="bean" match="your.full.package.*"/>
    顯而易見,這樣寫是允許轉(zhuǎn)換所有的JavaBean:

    <convert converter="bean" match="*"/>
    BeanConverter 和 JavaBeans 規(guī)范
    用于被BeanConverter轉(zhuǎn)換的Bean必須符合JavaBeans的規(guī)范,因?yàn)檗D(zhuǎn)換器用的是Introspection,而不是Reflection。這就是說屬性要符合一下條件:有g(shù)etter和setter,setter有一個(gè)參數(shù),并且這個(gè)參數(shù)的類型是getter的返回類型。setter應(yīng)該返回void,getter應(yīng)該沒有任何參數(shù)。setter沒有重載。以上這些屬于常識(shí)。如果你用的不是JavaBean,那么你應(yīng)該用ObjectConverter.

    設(shè)置Javascript變量
    DWR可以把Javascript對(duì)象(又名maps,或聯(lián)合數(shù)組)轉(zhuǎn)換成JavaBean或者Java對(duì)象。

    一個(gè)簡單的例子可以幫助你。假設(shè)你有下面的Java代碼:

    public class Remoted {
      public void setPerson(Person p) {
        // ...
      }
    }

    public class Person {
      public void setName(String name) { ... }
      public void setAge(int age) { ... }
      // ...
    }
    如果這個(gè)Remoted已經(jīng)被配置成Creator了,Persion類也定義了BeanConverter,那么你可以通過下面的方式調(diào)用Java代碼:

    var p = { name:"Fred", age:21 };
    Remoted.setPerson(p);
    限制屬性轉(zhuǎn)換
    就像你可以在creator的定義中剔出一些方法一樣,converter也有類似的定義。

    限制屬性轉(zhuǎn)換僅僅對(duì)于Bean有意義,很明顯原生類型是不要需要這個(gè)功能的,所以只有BeanConverter及其子類型(HibernateBeanConverter))有這個(gè)功能。

    語法是這樣的:

    <convert converter="bean" match="com.example.Fred">
      <param name="exclude" value="property1, property2"/>
    </convert>
    這就保證了DWR不會(huì)調(diào)用 fred.getProperty1() 和fred.getProperty2兩個(gè)方法。另外如果你喜歡"白名單"而不是"黑名單"的話:

    <convert converter="bean" match="com.example.Fred">
      <param name="include" value="property1, property2"/>
    </convert>
    安全上比較好的設(shè)計(jì)是使用"白名單"而不是"黑名單"。

    對(duì)象的私有成員
    通過'object'轉(zhuǎn)換器的參數(shù)的一個(gè)名為force的參數(shù),可以讓DWR通過反射來訪問對(duì)象私有成員。

    語法是這樣的:

    <convert converter="object" match="com.example.Fred">
      <param name="force" value="true"/>
    </convert>
    直到DWR1.1.3,這里有一個(gè)bug,public的field反而不能被發(fā)現(xiàn),所以你需要在public成員上設(shè)置force=true。

    posted @ 2008-09-17 12:47 小馬歌 閱讀(861) | 評(píng)論 (0)編輯 收藏
     
    util.js包含一些有用的函數(shù)function,用于在客戶端頁面調(diào)用.

    主要功能如下:

    代碼

    1、$() 獲得頁面參數(shù)值   
    2、addOptions and removeAllOptions 初始化下拉框   
    3、addRows and removeAllRows   填充表格   
    4、getText   取得text屬性值   
    5、getValue 取得form表單值   
    6、getValues 取得form多個(gè)值   
    7、onReturn     
    8、selectRange   
    9、setValue   
    10、setValues   
    11、toDescriptiveString   
    12、useLoadingMessage   
    13、Submission box  

    代碼

    1、$()函數(shù)   
       IE5.0 不支持   
       $ = document.getElementById   
       取得form表單值   
       var name = $("name");  

    代碼

    a、如果你想在更新select 時(shí),想保存原來的數(shù)據(jù),即在原來的select中添加新的option:   
         var sel = DWRUtil.getValue(id);   
         DWRUtil.removeAllOptions(id);   
         DWRUtil.addOptions(id,...);   
         DWRUtil.setValue(id,sel);   
         demo:比如你想添加一個(gè)option:“--請(qǐng)選擇--”   
    DWRUtil.addOptions(id,["--請(qǐng)選擇--"]);   
      
        DWRUtil.addOptions()有5中方式:  

    代碼

    @ Simple Array Example: 簡單數(shù)組   
         例如:   
         Array array = new Array[ 'Africa', 'America', 'Asia', 'Australasia', 'Europe' ];   
         DWRUtil.addOptions("demo1",array);  

    代碼

    @ Simple Object Array Example 簡單數(shù)組,元素為beans   
           這種情況下,你需要指定要顯示 beans 的 property 以及 對(duì)應(yīng)的 bean 值   
           例如:   
            public class Person {   
          private String name;   
          private Integer id;   
          pirvate String address;   
          public void set(){……}   
          public String get(){……}   
            }   
            DWRUtil.addOptions("demo2",array,'id','name');   
            其中id指向及bean的id屬性,在optiong中對(duì)應(yīng)value,name指向bean的name屬性,對(duì)應(yīng)下拉框中顯示的哪個(gè)值.  

    代碼

    @ Advanced Object Array Example 基本同上   
         DWRUtil.addOptions( "demo3",   
                     [{ name:'Africa', id:'AF' },   
                      { name:'America', id:'AM' },   
                      { name:'Asia', id:'AS' },   
                      { name:'Australasia', id:'AU' },   
                      { name:'Europe', id:'EU' }   
             ],'id','name');  

    代碼

    @ Map Example 用制定的map來填充 options:   
            如果 server 返回 Map,呢么這樣處理即可:   
            DWRUtil.addOptions( "demo3",map);   
            其中 value 對(duì)應(yīng) map keys,text 對(duì)應(yīng) map values;  

    代碼

    @ <ul> and <ol> list editing   
            
            DWRUtil.addOptions() 函數(shù)不但可以填出select,開可以填出<ul>和<ol>這樣的heml元素  

    3、addRows and removeAllRows 填充表格

    DWR 提供2個(gè)函數(shù)來操作 table;

    ----------------------------

    DWRUtil.addRows(); 添加行

    ----------------------------

    DWRUtil.removeAllRows(id); 刪除指定id的table

    ----------------------------

    下面著重看一下 addRows() 函數(shù):

    DWRUtil.addRows(id, array, cellfuncs, [options]);

    其中id 對(duì)應(yīng) table 的 id(更適合tbodye,推薦使用 tbodye)

    array 是server端服務(wù)器的返回值,比如list,map等等

    cellfuncs 及用返回值來天春表格

    [options] 用來設(shè)置表格樣式,它有2個(gè)內(nèi)部函數(shù)來設(shè)置單元格樣式(rowCreator、cellCreator)。

    比如: server端返回list,而list中存放的是下面這個(gè) bean:

    代碼

           public class Person {   
    private String name;   
    private Integer id;   
    pirvate String address;   
    public void set(){……}   
    public String get(){……}   
          }  

    下面用 DWRUtil.addRows();

    代碼

        function userList(data){   
         //var delButton = "<input type='button'/>";   
         //var editButton = "<input type='button'/>";   
         var cellfuncs = [   
             function(data){return data.id;},   
             function(data){return data.userName;},   
             function(data){return data.userTrueName;},   
             function(data){return data.birthday;},   
             function(data){   
             var idd = data.id;   
    var delButton = document.createElement("<INPUT TYPE='button' onclick='delPerson("+ idd +")'>");   
                 delButton.setAttribute("id","delete");   
                 delButton.setAttribute("value","delete");   
                 return delButton;   
             },   
             function(data){   
                 var idd = data.id;   
                 var editButton = document.createElement("<INPUT TYPE='button' onclick='editPerson("+ idd +")'>");   
                 editButton.setAttribute("name","edit");   
                 editButton.setAttribute("value","edit");               
                 return editButton;   
             }   
         ];   
         DWRUtil.removeAllRows('tabId');   
         DWRUtil.addRows('tabId', data,cellfuncs,{   
         rowCreator:function(options) {   
             var row = document.createElement("tr");   
             var index = options.rowIndex * 50;   
             row.setAttribute("id",options.rowData.id);   
             row.style.collapse = "separate";   
             row.style.color = "rgb(" + index + ",0,0)";   
             return row;   
         },   
         cellCreator:function(options) {   
             var td = document.createElement("td");   
             var index = 255 - (options.rowIndex * 50);   
             //td.style.backgroundColor = "rgb(" + index + ",255,255)";   
             td.style.backgroundColor = "menu";   
             td.style.fontWeight = "bold";   
             td.style.align = "center";   
             return td;   
         }          
         });   
         document.getElementById("bt").style.display = "none";   
          }  

    4、getText 取得text屬性值

    DWRUtil.getText(id): 用來獲得 option 中的文本

    比如:

    代碼

           <select id="select">  
    <option   value="1"> 蘋果 </option>  
    <option   value="2" select> 香蕉 </option>  
    <option   value="3"> 鴨梨 </option>  
           </select>  

    調(diào)用 DWRUtil.getText("select"); 將返回 "香蕉" 字段;

    DWRUtil.getText(id);僅僅是用來獲得 select 文本值,其他不適用。

    5、DWRUtil.getValue(id): 用來獲得 form 表單值

    有如下幾種情況:

    代碼

          Text area (id="textarea"): DWRUtil.getValue("textarea")將返回 Text area的值;   
    Selection list (id="select"): DWRUtil.getValue("select") 將返回 Selection list 的值;   
    Text input (id="text"): DWRUtil.getValue("text") 將返回 Text input 的值;   
    Password input (id="password"): DWRUtil.getValue("text") 將返回 Password input 的值;   
    Form button (id="formbutton"): DWRUtil.getValue("formbutton") 將返回 Form button 的值;   
    Fancy button (id="button"): DWRUtil.getValue("formbutton") 將返回 Fancy button 的值;  

    6、getValues 取得form多個(gè)值

    批量獲得頁面表單的值,組合成數(shù)組的形式,返回 name/value;

    例如: form():

    代碼

         <input type="textarea" id="textarea" value="1111"/>  
          <input type="text" id="text" value="2222"/>  
          <input type="password" id= "password" value="3333"/>  
          <select id="select">  
    <option   value="1"> 蘋果 </option>  
    <option   value="4444" select> 香蕉 </option>  
    <option   value="3"> 鴨梨 </option>  
           </select>  
          <input type="button" id="button" value="5555"/>  
            
          那么: DWRUtil.getValues({textarea:null,select:null,text:null,password:null,button:null})   
          將返回   ^^^^^^^^^^^^^^^^{textarea:1111,select:4444,text:2222,password:3333,button:5555}  

    7、DWRUtil.onReturn 防止當(dāng)在文本框中輸入后,直接按回車就提交表單。

    <input type="text" onkeypress="DWRUtil.onReturn(event, submitFunction)"/>

    <input type="button" onclick="submitFunction()"/>

    8、DWRUtil.selectRange(ele, start, end);

    在一個(gè)input box里選一個(gè)范圍

    代碼

    DWRUtil.selectRange("sel-test", $("start").value, $("end").value);   
      
    比如:<input type="text" id="sel-test" value="012345678901234567890">   
      
    DWRUtil.selectRange("sel-test", 2, 15);  

    9、DWRUtil.setValue(id,value);

    為指定的id元素,設(shè)置一個(gè)新值;

    10、DWRUtil.setValues({

    name: "fzfx88",

    password: "1234567890"

    }

    ); 同上,批量更新表單值.

    /***********************************************************************/

    11、DWRUtil.toDescriptiveString()

    帶debug信息的toString,第一個(gè)為將要debug的對(duì)象,第二個(gè)參數(shù)為處理等級(jí)。等級(jí)如下:

    0: Single line of debug 單行調(diào)試

    1: Multi-line debug that does not dig into child objects 不分析子元素的多行調(diào)試

    2: Multi-line debug that digs into the 2nd layer of child objects 最多分析到第二層子元素的多行調(diào)試

    <input type="text" id="text">

    DWRUtil。toDescriptiveString("text",0);

    /******************************************************************************/

    12、DWRUtil.useLoadingMessage();

    當(dāng)發(fā)出ajax請(qǐng)求后,頁面顯示的提示等待信息;

    代碼

        function searchUser(){   
    var loadinfo = "loading....."  
    try{   
         regUser.queryAllUser(userList);   
         DWRUtil.useLoadingMessage(loadinfo);           
    }catch(e){   
      
    }   
        }
    posted @ 2008-09-17 12:45 小馬歌 閱讀(855) | 評(píng)論 (0)編輯 收藏
    僅列出標(biāo)題
    共95頁: First 上一頁 80 81 82 83 84 85 86 87 88 下一頁 Last 
     
    主站蜘蛛池模板: 日本高清免费不卡在线| 手机看黄av免费网址| 久久香蕉国产线看免费| 久久精品无码专区免费青青| 日本免费网址大全在线观看| 免费高清av一区二区三区| 婷婷亚洲天堂影院| 婷婷亚洲久悠悠色悠在线播放| 亚洲午夜国产精品| 黄页网站在线免费观看| 国内少妇偷人精品视频免费| 久草免费在线观看视频| 免费看一级做a爰片久久| 久久精品亚洲中文字幕无码网站| 亚洲不卡视频在线观看| 色多多A级毛片免费看| 免费无遮挡无码永久视频| 嫩草视频在线免费观看| 亚洲精品蜜桃久久久久久| 国产精品亚洲精品观看不卡| 午夜在线免费视频| 亚洲网站在线免费观看| 国产成人一区二区三区免费视频 | 成人免费毛片内射美女APP| 免费a级毛片网站| 67pao强力打造67194在线午夜亚洲| 亚洲日本va一区二区三区 | 亚洲视频在线免费| 可以免费看的卡一卡二| 久久久久亚洲AV综合波多野结衣| 亚洲精品福利视频| 香港特级三A毛片免费观看| 免费A级毛片在线播放| 亚洲AⅤ永久无码精品AA| 亚洲第一精品电影网| 一级毛片aaaaaa视频免费看| 日本在线高清免费爱做网站| 伊伊人成亚洲综合人网7777| 一本天堂ⅴ无码亚洲道久久| 最近中文字幕大全免费版在线| 情侣视频精品免费的国产 |