2010年8月31日 星期二

GStreamer Debugging 參數說明

Standard
[GStreamer] 是一個優異的多媒體處理元件 Library,其架構了最先進的多媒體資料流機制,在目前主流的 Linux 系統中,應用極為廣範。GStreamer 支援以 Plugin 的形態擴充自身功能,可以是編解碼器(Encoder/Decoder),亦或是效果處理(Processing),因此,有許多生產硬體解碼晶片的廠商,都會透過提供 Gstreamer Plugin 的方式,支援 Linux 上的多媒體播放。說到多媒體架構,或許讀者也曾聽過 OpenCore/OpenMax,其實 Gstreamer 和 OpenCore 兩者是相類似的東西,只是前者主要被應用在桌面系統居多,後者則常見於嵌入式系統(Embedded System)應用,最廣為人知的便是 Android Multimedia Framework。在此,細節就不多談,本文將要探討 GStreamer Debugging 的一些手段。

如果曾開發過 GStreamer Application,對初始化 GStreamer Backend 應該很熟悉:
gst_init(&argc, &argv);

我們可以理解所有的 GStreamer Application,都會經過這段 Initialization 的動作,因此 gst_init() 會從 main() 得到程式被執行時所代入的後綴參數,換言之,理論上不管是什麼 Application ,我們可以透過這種方式去開啟 GStreamer 的 Debug 模式:
$ ./totem --gst-debug-level=3
0:00:00.003322140 13127  0x8c530a0 INFO                GST_INIT gstquery.c:105:_gst_query_initialize: init queries
0:00:00.004700946 13127  0x8c530a0 INFO                GST_INIT gstmessage.c:73:_gst_message_initialize: init messages
0:00:00.005529752 13127  0x8c530a0 INFO      GST_PLUGIN_LOADING gstplugin.c:348:_gst_plugin_initialize: registering 0 static plugins
0:00:00.005766514 13127  0x8c530a0 INFO      GST_PLUGIN_LOADING gstplugin.c:254:gst_plugin_register_static: registered static plugin "staticelements"
0:00:00.005795498 13127  0x8c530a0 INFO      GST_PLUGIN_LOADING gstplugin.c:256:gst_plugin_register_static: added static plugin "staticelements", result: 1
0:00:00.006600699 13127  0x8c530a0 INFO            GST_REGISTRY gstregistry.c:1586:ensure_current_registry: reading registry cache: /home/fred/.gstreamer-0.10/registry.i486.bin
0:00:00.029082746 13127  0x8c530a0 INFO            GST_REGISTRY gstregistrybinary.c:601:gst_registry_binary_read_cache: loaded /home/fred/.gstreamer-0.10/registry.i486.bin in 0.022431 seconds
0:00:00.029188137 13127  0x8c530a0 INFO            GST_REGISTRY gstregistry.c:1446:scan_and_update_registry: Validating plugins from registry cache: /home/fred/.gstreamer-0.10/registry.i486.bin
0:00:00.031259140 13127  0x8c530a0 INFO            GST_REGISTRY gstregistry.c:1548:scan_and_update_registry: Registry cache has not changed
0:00:00.031292035 13127  0x8c530a0 INFO            GST_REGISTRY gstregistry.c:1615:ensure_current_registry: registry reading and updating done, result = 1
0:00:00.031311032 13127  0x8c530a0 INFO                GST_INIT gst.c:786:init_post: GLib runtime version: 2.24.1

0:00:00.031331007 13127  0x8c530a0 INFO                GST_INIT gst.c:788:init_post: GLib headers version: 2.24.1
... 以下省略

參考 GStreamer 官方文件,會發現有數種參數可以使用:

  • --gst-debug-level=LEVEL
    設定 Debug 級別,LEVEL 範圍從 0 (不輸出訊息) 到 5 (輸出所有訊息)
  • --gst-debug=LIST
    要求特定的 Plugin 所 Debug 訊息輸出,亦可以分別設定不同 plugin 有的不同 Debug 級別,格式範例:
    --gst-debug=GST_AUTOPLUG:5,avidemux:3
  • --gst-plugin-spew
    輸出 Plugin 載入的錯誤訊息

References

GStreamer Application Development Manual:
http://www.gstreamer.net/data/doc/gstreamer/head/manual/html/section-checklist-debug.html

2010年8月29日 星期日

親手打造 Window Manager - Transient 和 OverrideRedirect

Standard
若嘗試去讀各個 Window Manager 的程式碼,最大的挫折就是會遭遇其中了龐大數量的專有名詞和屬性,假設沒有對 XWindow 和 Window Manager 整體架構有清楚概念,必定會花不少時間在查詢 X11/Xlib 的官方文件和映證,不過,查證過程雖繁瑣,對瞭解整個實作很有幫助。筆者將在本文記錄幾個比較特別的視窗(window)屬性,以便讀者懂得如何處理一些特殊的 window。

WM_TRANSIENT_FOR

此 Hint 屬性記載著短暫視窗的父視窗的 Window ID,若不是短暫視窗,將回傳沒有父視窗。短暫視窗有與一般視窗不同之特性,其將不會在工作列上被顯示,只是用來做短時間顯示使用,如對話視窗(Dialog Window) 就屬此類。想取得該屬性的設定值,可以藉由此 XGetTransientForHint() 得到:

XGetTransientForHint(Display *, Window, Window*)

OverrideRedirect

此 Window Attributes 參數表明忽略重新定向,若此參數為真(True),視窗管理員(Window Manager) 將不會為該視窗畫上標題框和邊框,如選單視窗(Menu Window)就屬此種會忽略重新定向的視窗。我們可以透過 XGetWindowAttributes() 去取得該屬性,簡單的範例如下:

XWindowAttributes attr;
XGetWindowAttributes(display, win, &attr);
if (attr.overrideRedirect)
    ....
else
    ....

暫且撇開一般 X Application 開發者熟知的視窗形態(Type),如:Dock 、Desktop、Toolbar 等等,Window Manager 在畫視窗時,首先要處理的是 WM_TRANSIENT_FOR 和 overrideRedirect 這類的屬性,而視窗形態應該是考量於 Window Manager 自身的應用後,才有不同的設計和呈現。

2010年8月27日 星期五

親手打造 Window Manager - 監聽 Screen 上的 XEvent

Standard
在 Linux 等 Unix's like 的系統上,擁有數十載歷史的 X11 通常為主流的圖形介面支援方案,雖架構看似老舊,卻在世界上眾高手的努力下,成就了現在的各種極絢麗的桌面環境,而最為眾人所知的,便是 Compiz 這類『視窗管理器(Window Manager)』,其運用 3D 技術帶來震憾的桌面體驗,因此在目前主流的 Linux 發行版中,無一不預設搭載。而許多人在此時,才真正了解 Window Manager 的厲害和重要性,新興的系統中,無論是 Moblin 還是 Google Chrome OS,也都特地為自家系統打造專屬的 Window Manager。

然而,Window Manager 實作細節雖看起來複雜,但實際上沒有想像中這麼艱澀,單純透過 X Protocol 和 API 去控制 window 的行為,這類工作極為簡單,而真正令人感到棘手的,是在事件(event)處理和 ICCCM/EWMH 的部份。ICCCM/EWMH 現在是由 [FreeDesktop.org] 所維護,其定義了一系列的屬性,用來記錄視窗環境的狀態,並可供所有 X Client 存取,而 Window Manager 要做的就是成為一個常註程式(Daemon),並隨時去提供和更新這些狀態。不過由於 ICCCM/EWMH 這部份細節繁雜,暫不在本文討論範疇(有興趣可自行參閱 FreeDesktop.org EWMH Spec),我們主要是討論如何監聽畫面上視窗的 event。

一般 window 監聽自己的 XEvent 是家常便飯,只要有以 Xlib 寫過純 X Application 的人應該都多少有些了解,會比較不瞭解的部份,就是該如何去監聽整個 screen 上的 event,但在這之前,我們有必要先大致瞭解 X window 的架構。通常在 X 上所呈現的桌面環境,是一層層 window 疊起來而成,舉例來說,桌面上常見的工具列(Panel),上面的狀態 Icon ,如:網路管理、Pidgin Icon 等,都是一個個 window 放在 Panel 的 window 之上,而在 Panel 之後,到最底層便是 screen 的 Root Window,Root Window 在所有 window 之下。

所以,若要做到監聽整個 screen 下所有 window 的 event,想當然爾就是對 screen 的 Root Window 做監聽,但單單只是監聽 Root Window 並無法取得所有 Sub Window 的 event,必須利用 X 定義的 SubstructureRedirectMask 以及 SubstructureNotifyMask 此類 event mask 達成目的。在程式中,我們只需使用 XChangeWindowAttributes() 去設定 Root Window 的 Attributes,將 event mask 代入就可以。

這是簡單的範例:
XSetWindowAttributes attr;

attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask |
        ColormapChangeMask | ButtonPressMask | PropertyChangeMask |
        EnterWindowMask;

XChangeWindowAttributes(display, screen->root, CWEventMask, &attr);
XSync(display, False);

當 Root Window 的 Attributes 被設定後,我們的程式便可以去監聽 Screen 下所有 Window 的 XEvent,當有程式被執行要顯示在畫面上時,就會收到 ConfigureRequest 和 MapRequest Event。

2010年8月25日 星期三

Reverse SSH Tunnel 反向打洞實錄

Standard
近來經手了幾個案子,其目標都是設計一台做特定用途的系統,但往往這台機器都會被鎖在防火牆後面,甚至不會連上網際網路,是完全封閉的環境。每當接近結案,將系統交到客戶手中以做最後的壓力測試時,便是痛苦的開始。怎麼說呢?因為當面對最後的大量測試,各種大大小小的臭蟲(Bugs)就會原形畢露,一一浮上臺面。而每次問題發生,便要千里迢迢趕到客戶那了解情況、解決問題。如果問題不大倒還好,一旦問題嚴重,被客戶關到三更半夜也是常有的事,更誇張的還必須每天都去報到。所以,為避免車程來回的時間和人力浪費,是否有可以遠端連線做處理呢?便思考起這問題。

雖然這些機器通常都不會有真實 IP,客戶也不會為了我們特別請 MIS 去開啟 NAT 或防火牆,但還好 SSH 提供了反向(Reverse)的機制讓我們可以連進去。其做法就是讓在客戶那的系統,透過 SSH 先連回我們自家有真實 IP 的主機,然後建立反向的通道即可。如此,我們便可從自家的主機,從 SSH 連回放在客戶那的系統。

實際的操作步驟:
# 首先,在客戶那理的機器下指令連回我們自己的 Server,並設定自己 Server 上的 12345 port 會對應到幾器上的 SSH port
ssh -NfR 12345:localhost:22 fred@myhost.com

# 然後在 myhost 的機器上連自己的 12345 port,就可以連回在客戶那的機器
ssh localhost -p 12345

後記

可以將建立 Reverse SSH tunnel 的命令設定在開機後執行,並且透過設定 ssh config 讓斷線時自動重連,如此就可以確保 SSH 連線不會中斷。此外,若是客戶端那的機器是處於完全無法對外連線的環境,則可以考慮使用 3G 網卡,並將網卡暫時借放於客戶那,然後等結案後再取回,雖然 3G 通訊月租不便宜,但和經常來回的通勤費和時間花費相比,可滑算多了。

2010年8月22日 星期日

善用 DRI 加速貼圖

Standard
近日以來,筆者都在著手處理 Linux 下圖形運算的議題,不久前也曾發表一篇『善用 XShm 加速貼圖』以探討 X 架構之下的貼圖機制,舊文最後也提出範例,使用共享記憶體(Shared Memory)的方式,達到效能的改善。不過,透過減少資料複製,雖然能有效提升貼圖的效率,但都還只是停留在彌補 X 架構先天上的缺陷,令人不禁去想,是否有更直接的解決方案?尋思,現今硬體能力不可同日而語,幾乎每台電腦都具備著基本能力的影像晶片,若運用 DRI 直接對硬體貼圖應該是種可行的手段。

欲在 X 上透過 DRI 繪圖,最簡單的方法是使用 GLX Extension,藉由建立 OpenGL Window 開啟直接和硬體溝通的管道。我們可以修改與舊文相同的程式碼,並改用 OpenGL 來達成相同的效果:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>

int main(void)
{
    Window win;
    Display display = XOpenDisplay(getenv("DISPLAY"));
    unsigned char *buffer;
    GLint glxattr[] = { GLX_RGBA, GLX_DEPTH_SIZE, 32, GLX_DOUBLEBUFFER, None };

    /* Initialize GLX */
    xvinfo = glXChooseVisual(display, 0, glxattr);
    if (xvinfo == NULL)
        exit(1);

    /* Initial Window */
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)),
                                                        0, 0, 100, 100, 0,
                                                        BlackPixel(display, DefaultScreen(display)),
                                                        BlackPixel(display, DefaultScreen(display)));

    XMapWindow(display, win);

    /* Create context */
    glcontext = glXCreateContext(display, vinfo, NULL, GL_TRUE);
    if (glcontext == NULL)
        exit(0);

    glXMakeCurrent(display, win, glcontext);
    glEnable(GL_DEPTH_TEST);

    XSync(display, False);

    /* Allocate image memory for 100x100x32bits */
    buffer = (unsigned char *)malloc(sizeof(char) * 100 * 100 * 4);

    /* Create texture from framebuffer */
    glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &texture_id);
    glBindTexture(GL_TEXTURE_2D, texture_id);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 100, 100, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)buffer);

    /* Loop */
    while(1) {
        glViewport(0, 0, 100, 100);
        glClearColor(0., 0., 0., 1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-1., 1., -1., 1., -1., 1.);

        glColor3f(1.0, 1.0, 1.0);
        glBegin(GL_QUADS);
            glTexCoord2f(0.0, 0.0); glVertex3f(-1.0,  1.0, 0.0);
            glTexCoord2f(1.0, 0.0); glVertex3f( 1.0,  1.0, 0.0);
            glTexCoord2f(1.0, 1.0); glVertex3f( 1.0, -1.0, 0.0);
            glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, -1.0, 0.0);
        glEnd(); 

        glXSwapBuffers(display, win);
        usleep(1000);
    };

    /* Release */
    glXMakeCurrent(display, None, NULL);
    glXDestroyContext(display, glcontext);
    XCloseDisplay(display);
}

編譯 GLX 程式:
gcc -o glximage glximage.c -lX11 -lGL -lGLU

2010年8月19日 星期四

Android is Working on X

Standard
As you know, Android was designed for mobile device, so there is no large value from Android for desktop environment. But there is no denying that Android has extreme applications resources. If we can combine Android and desktop environment, it will make great experience on Linux. By hacking Android framework, we made Android work on X with success, it is a "real" X application right now. You can see these screenshots:




From screenshots, Android is running on Chromium OS, based on Android-x86 project. Currently, we can use chromium browser to surf internet and use Android application in the same time. In first screenshot, using "ps ax" to show all process on system to prove Android is running on X Server rather than emulator.

2010年8月13日 星期五

善用 XShm Extension 加速貼圖

Standard
如今的 Xorg 千變萬化,發展出各種(DRI/DRI2/EAA/EXA/UXA/AIGLX)等加速機制,更有極絢麗的 3D 視窗技術,一點也不讓 Mac OS 和 Windows 上的 3D 視窗特效專美於前。話雖如此,很多人們早已遺忘 X Window System 的歷史原由,我們反觀當年的 XFree86 就有如醜小鴨般,其為 Networking 考量的 Client/Server 架構,就是舊 Unix 終端機時代所留下的東西。到目前為止,隨著硬體速度越來越快,Client/Server 的界限雖被 DRI/DRI2/AIGLX 等技術逐漸打破,但無論如何,Xorg 大底上還是維持著 Client/Server 的本質。只不過應用程式層的軟體,因為經過各種 GTK/GDK/Qt 等 Graphical Toolkits 包裝後,多數開發者和使用者已經不會再看到 X 底層的運作模式。

這裡記載著一個簡單的範例程式,用來說明 X 如何利用 XImage 做貼圖運算,其主要是將 buffer(100x100x32bits 的影像)畫在視窗上,該 buffer 可以是讀取圖檔或是使用各種資料來源,為一塊記憶體。此程式將會用 XCreateImage() 建立一組 Structure,然後使用 XPutImage() 將該 Structure 和影像資料吃從 XClient 透過 X Protocol(Socket)傳送到 X Server,X Server 再依 XImage 的敘述將圖形畫在視窗上。

XImage 範例程式:
void main(void)
{
    Window win;
    Display display = XOpenDisplay(getenv("DISPLAY"));    XImage *ximage;
    unsigned char *buffer;

    /* Initial Window */
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)),
                                                        0, 0, 100, 100, 0,
                                                        BlackPixel(display, DefaultScreen(display)),
                                                        BlackPixel(display, DefaultScreen(display)));

    XMapWindow(display, win);
    XSync(display, False);

    /* Allocate image memory for 100x100x32bits */
    buffer = (unsigned char *)malloc(sizeof(char) * 100 * 100 * 4);

    /* Create XImage structure and map image memory on it */
    ximage = XCreateImage(display, DefaultVisual(display, 0), DefaultDepth(display, 0), ZPixmap, 0, buffer, 100, 100, 32, 0);

    /* Put XImage into X Server to display */
    XPutImage(display, window, DefaultGC(display, 0), ximage, 0, 0, 0, 0, 100, 100);

    /* Loop */
    while(1) {};

    /* Release */
    XDestroyImage(ximage);
    XCloseDisplay(display);
}

不過,眼尖的人或許已經注意到,此程式完全曝露出 X Client/Server 架構所存在的缺陷,無論要貼什麼圖,都必需先透過 X Protocol 傳送至 X Server,如此大量的資料複製,當然取而代之的就是效能低落。一種改善效能的方法就是使用 Shm(Shared Memory),使 X Client 和 X Server 共享一塊記憶體,讓原先的大量複製資料,改由傳記憶體位置取代,為此,X 就支援了 XShm Extension 來實作此種方法。

XShm Extension 所提供能函式與原生的 XImage 相關函式相差不大,這邊用另一個範例程式做說明:
void main(void)
{
    Window win;
    Display display = XOpenDisplay(getenv("DISPLAY"));    XImage *ximage;
    unsigned char *buffer;
    XShmSegmentInfo shminfo;

    /* XShm */
    XShmQueryExtension(display);

    /* Initial Window */
    win = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)),
                                                        0, 0, 100, 100, 0,
                                                        BlackPixel(display, DefaultScreen(display)),
                                                        BlackPixel(display, DefaultScreen(display)));

    XMapWindow(display, win);
    XSync(display, False);

    /* Allocate image memory for 100x100x32bits */
    buffer = (unsigned char *)malloc(sizeof(char) * 100 * 100 * 4);

    /* Create XImage structure and map image memory on it */
    ximage = XShmCreateImage(display, DefaultVisual(display, 0), DefaultDepth(display, 0), ZPixmap, 0, &shminfo, 100, 100);

    /* Setting SHM */
    shminfo.shmid = shmget(IPC_PRIVATE, 100 * 100 * 4, IPC_CREAT | 0777);
    if (shminfo.shmid < 0)
        exit(-1);

    shminfo.shmaddr = ximage->data = (unsigned char *)shmat(shminfo.shmid, 0, 0);
    if(shminfo.shmaddr == (char *) -1)
        exit(-1);

    shminfo.readOnly = False;
    XShmAttach(display, &shminfo);

    /* Put XImage into X Server to display */
    XShmPutImage(display, win, DefaultGC(display, 0), ximage, 0, 0, 0, 0, 100, 100, False);
    XFlush(display);
    /* Loop */
    while(1) {};

    /* Release */
    XDestroyImage(ximage);
    XCloseDisplay(display);
}

大體上,主要就是 XCreateImage 換成 XShmPutImage,XPutImage 換成 XShmPutImage,更多資料可以參考 XFree86 MIT-SHM 文件:
http://www.xfree86.org/current/mit-shm.html