剛才怎麼發到it168的blog去了,cu的跳轉有我呢? it168不能超過一萬字,發不了,分了兩篇
還是覺得這邊好,發這邊一篇。
kdrive的xvide的加速的實現。
首先xvideo本來也是在驅動裡面實現的,實際上我們先要做一個驅動。
kdrive硬體加速驅動的實現,其實就是kaa的實現,kaa分xserver這邊和driver這邊,xserver這邊會提供一種機制
這個機制就是當gc操作的時候會判斷一下回呼函數是否註冊,如果註冊就使用回呼函數,如果沒有註冊就使用軟體的實現,
而註冊這個回呼函數就是在kdrive的驅動裡面來實現的。因為kdrive不能動態裝載驅動,所以驅動都是直接編譯到Xserver
裡面的,也就是說編譯出來的Xfbdev是直接帶了驅動的。
我們先看看代碼的布局,之前是在xserver 1.5.x的基礎上來實現的,大概就以1.5.3來講吧
關於kdrive和xserver的關係之前說過,kdrive是xserver的一種,xserver在xorg的代碼樹裡面有好幾種實現,但是大體的代碼都是共用的
比如基本的事件處理,clip等等代碼都是共用的。hw目錄下面就是不同的xserver的實現。xorg只是其中一種,如前所說,kdrive和xorg有很多
代碼是共用的,所以xorg的代碼更新過快,kdrive更新過慢,導致kdrive的有很多事件處理有問題。
本來應該先說kaa的實現的,但是因為xivdeo的實現比較簡單,所以先說。
我們先看看xvideo的工作流程,就是說xvideo到底是怎麼發揮作用的。
xvideo的目的是為了把video或者frame輸出直接輸出到螢幕上面,盡量少的轉存,盡量多的利用硬體加速,而xorg是管理最後輸出的(目前是這樣)
所以需要在xserver裡面有一個擴充這個就是Xv的擴充了,
這裡需要一個圖片說明
我們看到最原始的資料無非是一個視頻檔案,我們先假定這個檔案是一個mpeg4的檔案。然後通過demux之後就有不同的資料流,比如音頻,視頻,還有其他的,
但是我們這裡先只關注視頻,視頻流出來之後就經過解碼,解碼完成之後就成了一幀一幀的圖片,和放電影的原理是一樣的。然後我們就要把這個圖片顯示
到螢幕上面,最終的輸出函數一般都是XPutImage,XvPutImage,XvShmPutImage類似這樣的函數。
當然使用shm的版本會比不使用shm的版本要快很多了,Xshm本來也是Xserver的一個擴充,這個主要是為了減少資料的copy
一般來說寫的好的播放器都會判斷一些xserver是否支援shm擴充,如果支援,就會使用shm的函數。比如mplayer,gstreamer(xvimagesink)
因為Xserver這個架構是C/S模式的,說這個很好理解,但是很多人還是知其然不知其所以然,我們從原始的架構來理解。
原始的X是可以通過網路來傳輸的,但是因為這種模式我們現在用的不是很多了,除了ssh -X abc@192.168.1.100這樣的用法之外,可能我們
很少會用到這樣的遠程傳輸,我們本機一般用的是unix socket。也就是說所有的請求,實際上是這樣的,
我們編寫應用程式裡面會有XPutImage的函數,實際上這個函數是由libX11來提供的(如果我沒有記錯的話),XvSHmPutImage這樣的函數是由libXext來實現的。
這個裡面的代碼實際上就是封裝一個X的請求,然後通過Xsend發送出去。然後Xserver那邊會dispatch這些訊息,由對應的擴充或者函數來處理,因為這些Xext初始化
的時候會告訴Xserver這類訊息由自己的擴充來處理。
所以想想如果是一個很大幀的資料,都走socket的話是很慢的,所以有了Xshm就好很多了,shm,和我們普通的shm是一樣的,就是共用記憶體了,就是使用者程式和
xserver共用一個記憶體空間,當使用者程式填充幀的時候實際上就直接寫到xserver的記憶體空間了(需要圖片說明),然後xserver可以直接顯示出去,這樣會節省很多空間
Ximage這個資料介面是有data指標的,但是如果是shm的版本就沒有,裡面會帶一個地址。XshmImage.這些內容都是憑記憶寫的,可能會有漏誤
話說回來,Xvideo在xserver這邊對應的處理流程。
xserver無非也會接收到XshmPutimage的請求,這個在Xserver裡面xorg-server-1.5.3/Xext/shm.c裡面實現的
static int
SProcShmPutImage(client)
ClientPtr client;
{
register int n;
REQUEST(xShmPutImageReq);
swaps(&stuff->length, n);
REQUEST_SIZE_MATCH(xShmPutImageReq);
swapl(&stuff->drawable, n);
swapl(&stuff->gc, n);
swaps(&stuff->totalWidth, n);
swaps(&stuff->totalHeight, n);
swaps(&stuff->srcX, n);
swaps(&stuff->srcY, n);
swaps(&stuff->srcWidth, n);
swaps(&stuff->srcHeight, n);
swaps(&stuff->dstX, n);
swaps(&stuff->dstY, n);
swapl(&stuff->shmseg, n);
swapl(&stuff->offset, n);
return ProcShmPutImage(client);
}
ProcShmPutImage這個函數會調用下面的函數下面調用的是XPutImage的註冊回呼函數了,
(*pGC->ops->PutImage) (pDraw, pGC, stuff->depth,
stuff->dstX, stuff->dstY,
stuff->totalWidth, stuff->srcHeight,
stuff->srcX, stuff->format,
shmdesc->addr + stuff->offset +
(stuff->srcY * length));
從dispatch開始看
Xext/xvdisp.c
#define XVCALL(name) Xv##name
ProcXvPutImage
最後調用的是這個,
return XVCALL(diPutImage)(client, pDraw, pPort, pGC,
stuff->src_x, stuff->src_y,
stuff->src_w, stuff->src_h,
stuff->drw_x, stuff->drw_y,
stuff->drw_w, stuff->drw_h,
pImage, (unsigned char*)(&stuff[1]), FALSE,
stuff->width, stuff->height);
實際上是下面的函數XvdiPutImage
Xext/xvmain.c這個函數是xvideo的具體實現,當然xorg和kdrive實際上都用這裡的代碼。
XvdiPutImage
調用下面的回呼函數了
return (* pPort->pAdaptor->ddPutImage)(client, pDraw, pPort, pGC,
src_x, src_y, src_w, src_h,
drw_x, drw_y, drw_w, drw_h,
image, data, sync, width, height);
我們要實現的自然就是ddPutImage了。
這個函數就是gc註冊的了,這裡我們使用kdrive的架構,自然就是kaa去註冊的了。
然後kaa的具體實現,也就是我們的驅動,會註冊kaa的函數。
所有kdrive的函數都在
xorg-server-1.5.3/hw/kdrive
ailantian@vax:/mnt/sdb1/ubd/soft/xorg/temp/xorg-server-1.5.3/hw/kdrive$ ls
ati ephyr fake i810 mach64 Makefile.in neomagic pm2 sdl smi vesa
chips epson fbdev linux Makefile.am mga nvidia r128 sis300 src via
ailantian@vax:/mnt/sdb1/ubd/soft/xorg/temp/xorg-server-1.5.3/hw/kdrive$ pwd
這下面基本每個目錄都是一個driver,比如有intel 810, ati,等等,很多驅動了。
kaa這個架構是在hw/kdrive/kaa.c裡面實現的,至於xvideo的架構的實現就是在kxv裡面來實現的了。
我們的驅動裡面通過一定的機制實際上會調用到下面這個函數,
KdXVInitAdaptors
pa->pScreen = pScreen;
pa->ddAllocatePort = KdXVAllocatePort;
pa->ddFreePort = KdXVFreePort;
pa->ddPutVideo = KdXVPutVideo;
pa->ddPutStill = KdXVPutStill;
pa->ddGetVideo = KdXVGetVideo;
pa->ddGetStill = KdXVGetStill;
pa->ddStopVideo = KdXVStopVideo;
pa->ddPutImage = KdXVPutImage;
pa->ddSetPortAttribute = KdXVSetPortAttribute;
pa->ddGetPortAttribute = KdXVGetPortAttribute;
pa->ddQueryBestSize = KdXVQueryBestSize;
pa->ddQueryImageAttributes = KdXVQueryImageAttributes;
實際上我們只關注
pa->ddPutImage = KdXVPutImage;
這個函數是在xserver的xvideo擴充裡面被調用的。
也就是說kxv實際上是一個架構,然後會有人來填充KdXVPutImage這樣的回呼函數的,這個就是我們kaa驅動裡面要做的事情了。
其實i810等等這些驅動可以直接參考,我們可以直接copy一下一個目錄,然後作為自己驅動開發的基礎,然後改改makefile就行了。
這裡還是以ati作為例子來講解吧,我自己的代碼寫的比較亂,可能還有其他一些問題,所以不便貼出來。
一般來說xvideo的實現都是在xxx_video.c裡面實現的,比如我們看ati的,就是在ati_video.c裡面了。
其實代碼量比較小。
其實輸出出去,最終還是這個函數,
ATIPutImage,然後這個函數一般會調用DisplayVideo這樣的函數,不過這個只是開發人員的習慣而已。
那麼先看看架構。
以ati為例,註冊回呼函數從哪裡開始,
grep一下代碼就知道了,實際上是這個。
ATISetupImageVideo
ATIInitVideo調用ATISetupImageVideo
看個人的喜好了,反正是要註冊這些回呼函數了,
下面是關鍵代碼,所謂的架構都在這裡了。
static KdVideoAdaptorPtr
ATISetupImageVideo(ScreenPtr pScreen)
{
KdScreenPriv(pScreen);
ATIScreenInfo(pScreenPriv);
KdVideoAdaptorPtr adapt;
ATIPortPrivPtr pPortPriv;
int i;
atis->num_texture_ports = 16;
adapt = xcalloc(1, sizeof(KdVideoAdaptorRec) + atis->num_texture_ports *
(sizeof(ATIPortPrivRec) + sizeof(DevUnion)));
if (adapt == NULL)
return NULL;
adapt->type = XvWindowMask | XvInputMask | XvImageMask;
adapt->flags = VIDEO_CLIP_TO_VIEWPORT;
adapt->name = "ATI Texture Video";
adapt->nEncodings = 1;
adapt->pEncodings = DummyEncoding;
adapt->nFormats = NUM_FORMATS;
adapt->pFormats = Formats;
adapt->nPorts = atis->num_texture_ports;
adapt->pPortPrivates = (DevUnion*)(&adapt[1]);
pPortPriv =
(ATIPortPrivPtr)(&adapt->pPortPrivates[atis->num_texture_ports]);
for (i = 0; i < atis->num_texture_ports; i++)
adapt->pPortPrivates[i].ptr = &pPortPriv[i];
adapt->nAttributes = NUM_ATTRIBUTES;
adapt->pAttributes = Attributes;
adapt->pImages = Images;
adapt->nImages = NUM_IMAGES;
adapt->PutVideo = NULL;
adapt->PutStill = NULL;
adapt->GetVideo = NULL;
adapt->GetStill = NULL;
adapt->StopVideo = ATIStopVideo;
adapt->SetPortAttribute = ATISetPortAttribute;
adapt->GetPortAttribute = ATIGetPortAttribute;
adapt->QueryBestSize = ATIQueryBestSize;
adapt->PutImage = ATIPutImage;
adapt->ReputImage = ATIReputImage;
adapt->QueryImageAttributes = ATIQueryImageAttributes;
/* gotta uninit this someplace */
REGION_INIT(pScreen, &pPortPriv->clip, NullBox, 0);
atis->pAdaptor = adapt;
xvBrightness = MAKE_ATOM("XV_BRIGHTNESS");
xvSaturation = MAKE_ATOM("XV_SATURATION");
return adapt;
}
看看上面最重要的函數就是註冊了 adapt->PutImage = ATIPutImage;
這個函數在最後就會被調用到。
大家可能會問這個adapt怎麼會和上面的KdXVPutImage打上關係,這個自然是要註冊的。
KdXVScreenInit->KdXVInitAdaptors
adaptorPriv->flags = adaptorPtr->flags;
adaptorPriv->PutVideo = adaptorPtr->PutVideo;
adaptorPriv->PutStill = adaptorPtr->PutStill;
adaptorPriv->GetVideo = adaptorPtr->GetVideo;
adaptorPriv->GetStill = adaptorPtr->GetStill;
adaptorPriv->StopVideo = adaptorPtr->StopVideo;
adaptorPriv->SetPortAttribute = adaptorPtr->SetPortAttribute;
adaptorPriv->GetPortAttribute = adaptorPtr->GetPortAttribute;
adaptorPriv->QueryBestSize = adaptorPtr->QueryBestSize;
adaptorPriv->QueryImageAttributes = adaptorPtr->QueryImageAttributes;
adaptorPriv->PutImage = adaptorPtr->PutImage;
adaptorPriv->ReputImage = adaptorPtr->ReputImage;
portPriv->AdaptorRec = adaptorPriv;
這個函數指標都放在了AdaptorRec這個裡面,而這個是在KdXVXXXX函數裡面被調用的。
比如
KdXVPutImage
到輸出的時候就會嘗試調用這個註冊的回呼函數了。
ret = (*portPriv->AdaptorRec->PutImage)(portPriv->screen, pDraw,
src_x, src_y, WinBox.x1, WinBox.y1,
src_w, src_h, drw_w, drw_h, format->id, data, width, height,
sync, &ClipRegion, portPriv->DevPriv.ptr);
而這個回呼函數就是我們之前ATISetupImageVideo裡面註冊的函數,通過一系列周轉到這裡來的。
回到正題,只看最重要的部分,如何硬體加速輸出的。其實看代碼grep一下,肯定是和寄存器相關,或者mmio相關的,因為老的架構都是使用mmio的。
ATIPutImage裡面加入了Xrandr的處理,這個我們可以先不管,主要是旋轉螢幕會造成問題,所以需要處理一下,不過我們如果不旋轉可以不關注這個代碼了。
最關鍵的代碼如下我們解碼出來的圖片是在記憶體裡面,但是實際上我們要顯示出去,必須放到顯存裡面,這裡我不想講kaa的記憶體管理了,偏離標題了,
if (pPortPriv->off_screen == NULL) {
pPortPriv->off_screen = KdOffscreenAlloc(screen->pScreen,
size * 2, 64, TRUE, ATIVideoSave, pPortPriv);
if (pPortPriv->off_screen == NULL)
return BadAlloc;
}
反正就是先我們要在顯存裡面劃撥一個地區來存放這個圖片才行。
下面有一段pixmap的判斷,這個先不說,
總之,我們必須保證這個東西是在顯存裡面,無論是不是offscreen的。
然後就是下面的代碼了,
switch(id) {
case FOURCC_YV12:
case FOURCC_I420:
top &= ~1;
nlines = ((((rot_y2 + 0xffff) >> 16) + 1) & ~1) - top;
KdXVCopyPlanarData(screen, buf, pPortPriv->src_addr, randr,
srcPitch, srcPitch2, dstPitch, rot_src_w, rot_src_h,
height, top, left, nlines, npixels, id);
break;
case FOURCC_UYVY:
case FOURCC_YUY2:
default:
nlines = ((rot_y2 + 0xffff) >> 16) - top;
KdXVCopyPackedData(screen, buf, pPortPriv->src_addr, randr,
srcPitch, dstPitch, rot_src_w, rot_src_h, top, left,
nlines, npixels);
break;
}
這個代碼實際上是用來進行資料格式的轉換的,也就是說,其實顯卡所支援的硬體加速的格式是有限的,如果這個格式不被硬體所支援的話,我們就需要進行軟體的
格式轉換,這裡xserver處理的有點死板,xserver到最後的輸出的時候只支援兩種格式,所以所有的格式都會轉換成那兩種格式,如果沒有記錯的話
應該是YVYU,VYUY,也就是說xserver目前的架構,是將資料封裝成這兩個格式,然後給硬體的,當然,這個驅動是自己實現的,一般來說,硬體支援很多種格式,
還有我們看到上面的代碼,從user application傳遞過來的之後四種格式,這個是因為我們的驅動只註冊了四種格式,也就是說我們只支援這四種格式,
類似mplayer會通過函數來查詢當前的驅動到底支援哪幾種格式,xvinfo這個x內建的工具也可以看。
註冊格式是在這個地方,如果硬體支援更多的比如NV12,NV21等可以加在這裡,好像一般的嵌入式的處理器如果有2d處理的話,都會支援。
static KdImageRec Images[NUM_IMAGES] =
{
XVIMAGE_YUY2,
XVIMAGE_YV12,
XVIMAGE_I420,
XVIMAGE_UYVY
};
xserver預設只支援這幾個fourcc,如果要更多,可以從別的驅動裡面抄這些定義,或者抄mplayer或者pixman裡面都有這些fourcc的定義。因為很多格式還沒有被
標準化。
資料pack完成之後自然就是發送給硬體顯示了。
這個就比較簡單了,因為我們有一個緩衝區,這個緩衝區已經在顯存裡面了,我們要顯示到螢幕的某個地方。
注意offscreen和online screen的區別,比如我們有一個顯卡,顯存500M,實際上只有很小一部分空間是對應在螢幕上面的。
一般來說,顯卡操作顯存,是比較快的,有各種不同的技術實現,各家也不一樣。但是比記憶體是要快的,或者至少也要和記憶體的速度差不多,大塊資料的搬運會比較快一些。
比如要從offscreen搬運到online screen,其實嵌入式來說,顯存也是記憶體,劃撥一塊而已,但是這個操作一般不需要cpu參與了。
好,我們看看顯示的過程,
一般驅動程式會單獨寫一個函數來處理這個過程。
這裡其實只有兩個需要注意的地方,一個就是clip的處理,一個就是composite的處理。
雖然說xvideo本身的設計是和composite背離的,不過xvideo也可以直接輸出到一個texure或者是一個pixmap裡面。因為無論是什麼方式,無非是一塊顯存。
R128DisplayVideo
我們看看這個實現。
BoxPtr pBox = REGION_RECTS(&pPortPriv->clip);
int nBox = REGION_NUM_RECTS(&pPortPriv->clip);
這裡是計算clip的,比如我們播放一個視頻的時候,這個時候有菜單蓋住螢幕,這樣我們的輸出地區就被剪下成多個地區了,2個或者說個等等。
我們就只能按小塊輸出了,每塊單獨輸出,但是還是硬體加速的,我們看看迴圈就知道了。
while (nBox--) {
int srcX, srcY, dstX, dstY, srcw, srch, dstw, dsth;
dstX = pBox->x1 + dstxoff;
dstY = pBox->y1 + dstyoff;
dstw = pBox->x2 - pBox->x1;
dsth = pBox->y2 - pBox->y1;
srcX = (pBox->x1 - pPortPriv->dst_x1) *
pPortPriv->src_w / pPortPriv->dst_w;
srcY = (pBox->y1 - pPortPriv->dst_y1) *
pPortPriv->src_h / pPortPriv->dst_h;
srcw = pPortPriv->src_w - srcX;
srch = pPortPriv->src_h - srcY;
BEGIN_DMA(6);
OUT_RING(DMA_PACKET0(R128_REG_SCALE_SRC_HEIGHT_WIDTH, 2));
OUT_RING_REG(R128_REG_SCALE_SRC_HEIGHT_WIDTH,
(srch << 16) | srcw);
OUT_RING_REG(R128_REG_SCALE_OFFSET_0, pPortPriv->src_offset +
srcY * pPortPriv->src_pitch + srcX * 2);
OUT_RING(DMA_PACKET0(R128_REG_SCALE_DST_X_Y, 2));
OUT_RING_REG(R128_REG_SCALE_DST_X_Y, (dstX << 16) | dstY);
OUT_RING_REG(R128_REG_SCALE_DST_HEIGHT_WIDTH,
(dsth << 16) | dstw);
END_DMA();
pBox++;
}
裡面的OUT_RING我們就不看了,簡單點理解,就是硬體輸出。
各家硬體不一樣,這裡的目的是一樣的,就是輸出地區。nBox就是我們地區的個數了。
我們要把buffer輸出到一個指定的地區,指定的位移就可以了。
至於composite,我們無非是要指定輸出的地區就行了,這個地區如果不在online screen,我們就輸出到pixmap了。
然後composite會管理輸出,可能是windowmanager做這個或者是xcompmgr類似這樣的程式。
DamageDamageRegion(pPortPriv->pDraw, &pPortPriv->clip);
這個是需要的。xv的架構輸出,我們需要彙報一下,我們寫了這個地區,如果有人也在使用這塊地區,就需要知道這個事情。
所以實際上xv的架構涉及到的擴充比較多
Xv,Xcomposite,Xdamage,Xfixes,
大體上就是這樣了,其他的函數都是一些輔助的函數,直接照抄就行了。
另外要說明的一點就是。各種不同的flag的作用。
VIDEO_CLIP_TO_VIEWPORT等等,這個是根據不同的顯示模式來設定的。
overlay和texture是不一樣的,overlay在現在好像用的很少了,不過因為是硬體的支援,可能比texture會快一些,畢竟一個單獨的layer操作
起來加上硬體的alpha blending會快的多,而texture的輸出,完全靠顯卡,感覺現在的顯卡雖然很強了,但是面對大量的資料的alpha blending
無論是從顯存大小,還是資料轉送還是計算能力,都還是不夠的。pc如此,嵌入式更如此。
想想現在都是大解析度1440x900這個是一般的小螢幕了,24bit depth,然後還要加上composite,需求加倍。記憶體佔用就很多了,還有就是所有的視窗都要先
offscreen render一下,然後再說alpha blending,很慢,我也不知道composite有什麼好,為了酷炫,就要付出更多的代價。
唉,用的是fcitx的全拼打字,還是有點慢的,上面的東西寫了一個半小時,其他事情也還比較多
kaa不知道什麼時候寫了。
kdriver+kaa+kxv
xorg+exa+xv
兩個架構其實是類似的,xorg擴充性更強一些,可以動態裝載。
xorg的input 自動探測也比較簡單,還像說說。evdev統一天下還需時日。
暈,輸入居然多餘的萬字了,我太能了:)
看來我寫小說還挺快,一個半小時能超過一萬字呢
漏掉一點就是為什麼要使用xvideo
這個之前的文章說過,就是我們從mpeg4或者h264等等檔案解碼出來的實際上是yuv的資料,比如可能是yuv422的
但是我們的螢幕實際上是rgb的,不同的系統不一樣了,比如我們的pc一般rgb888的,對於嵌入式來說可能會選擇rgb565這樣的,所以就需要轉換,
這個yuv轉rgb是需要很多計算量的,另外就是硬體縮放,比如解碼出來的映像是vga或者wvga的,我們要全螢幕顯示的時候就需要縮放,這個時候也非常佔用cpu。
沒記錯的話,應該都是浮點運算。雖然coretex-a8等等都有了vfp等等指令,但是實際上,這點效能還是遠遠不夠。還有就是xvideo的設計就是為了快速的輸出,
所以從架構上來說是很精簡的,為了快速顯示而設計的。
不過有一點要注意就是profile,我們一般會profile一下,看看這個xvideo的實現效能在什麼地方,因為現在輸出都是由硬體來做的,所以cpu佔用很少,
後來用oprofile看的時候發現其實cpu佔用最多的地方居然在資料封裝的地方,也就是我們把使用者程式傳遞過來的資料封裝成硬體支援的格式。
這個地方還是可以用彙編最佳化一下的。改善很明顯。畢竟是驅動裡面最"熱"的地方。
不過後來因為遷移到了xorg,所以kdrive這邊的實現,包括kaa的驅動和xvideo的實現都沒再繼續用了
xorg這邊有晶片供應商在開發xorg的dri+exa的driver,所以我就沒再做這個工作了。