• 9eb36870023caf684d60d34d73293e71.jpg

    最近因為 mysql 的效能有點頭痛。剛好有朋友介紹一套 mysql 校調工具,tuning-primer.sh。
    其實還滿簡單易用的,只是從他的建議裡面還是要從經驗判斷一下,不然可能不一定是對的喔!
     
    Database | 7989 觀看 | 2013-09-18 | 標籤: mysql, performance, cat_applications | Kuann Hung 上傳
  • 7f3c8ec626c5bef165fa82ed20959647.gif
    今天遇到用戶反應線上系統出問題,但是測試都正常!最後用跟他一模一樣的方式測試才發現,問題出在報表。因為該用戶的資料量特大!所以跑報表就出問題了。
     
    一開始看到的錯誤是
    Err Code: 3
    Error writing file /tmp/MY915 (Errcode: 28)
    仔細查了一下文件才發現是硬碟空間不足。但是太神奇了!因為那台硬碟空間很大,查詢一下 /tmp 空間有 2G ,應該綽綽有餘才對。
     
    把他的 SQL 語法抓出來實際執行,還真的會錯誤!再執行一次,一邊觀察 /tmp 空間的變化... 驚人的事情是... mysql 在 join 等資料交換時,會把資料放在 /tmp 作處理,如果同時有多人一起做的時候,資料真的會衝破 2G!真是暈倒了!
     
    最後處理方式就是,在 /etc/my.cnf 中設定,把 tmpdir 改成另一個大一點的空間。
    # my.cnf 
    tmpdir = /mysql_tmp
    真是,不經一事,不長一智啊!
     
    別問我為何只切 2G 給 /tmp,那是跟國外租的 dedicated server,設定就是那樣,除非要他重灌,但是得付費!所以還是自己解決囉!
     
    BTW,有人知道如何直接加大 /tmp 大小嗎?(現在是切一個 partition,別跟我說用 Partition Magic 之類的,機器遠在美國,我只能用 ssh 進去而已)
    Database | 10370 觀看 | 2013-09-18 | 標籤: mysql, errcode:28 | Kuann Hung 上傳
  • 91f88f2804344161afc6a62d5886f974.jpg
     
    最近因為一些安全的問題,要把原本的程式升級到 PHP 5.3。原本想說可以無痛升級的,但是試了才知道,如果資料庫是使用 SQL Server 的話,就會很麻煩了!
     
    在 PHP 5.3 以後就不再支持 SQL Server 了,因為微軟自己跳下來做了!(詳情請看:http://msdn.microsoft.com/en-us/sqlserver/ff657782.aspx)
     
    除此之外,原本的 mssql_xxx 的 function 也不再支援,全部換成 sqlsrv_xxx 的Function。
     
    以下說明一下我的更新方式:
    Database | 9345 觀看 | 2013-09-18 | 標籤: php, mssql, 2008, 5.3 | Kuann Hung 上傳
  • 689eb576083c69ef73b8e0c97d5b5444.png
     
    嗯,研究 Vorbis 一段時間了, 需要整理一下。這次主要調整的跟 block type 有關。主要動到的是 envelope.c 這隻程式。
    下面介紹一下 block type 的意義!
     
    Vorbis 在壓縮的時候會將取得的聲音拆成兩種不同長度的 block (short 跟 long blocks)。這在前一篇 (Vorbis - audio codec) 中有介紹到,而每一個 block 除了長度不同外,還會被分類成四種不同的 block type。分別是:
     
    LONG、IMPULSE (short)、TRANSITION (between long and short)、PADDING (在 IMPULSE 之前出現的 short )。
     
    在我的案例中,為了處理 voice 與 video sync 與 editor 的需求。所以我調整了 block type 的判斷方式。從原本根據聲音內容判斷 聲音 (tonal) 或 雜音 (noise) 改成一律回覆 LONG 的 block type。原本抱著姑且一試的方式,想不到竟然就成功了!
     
    雖然只是一點的小研究,不過也找到一個專門討論語音壓縮的論壇。裡面很多高手喔!如果你做語音壓縮的話,可以去那邊多討論研究喔!
     
    Vorbis | 7111 觀看 | 2013-09-18 | 標籤: vorbis, ogg, block type | Kuann Hung 上傳
  • a13baed16fde7c1ec53de30a8d691358.png
    最近的案子使我又回老本行-處理聲音壓縮。以前都是用 G 系列的,而最近要接觸的是一個 open、free royalty 的 audio codec,Vorbis。
    跟 G 系列的不同是,它不是 speech codec 而是 audio codec。所以可以應用的範圍就更為廣泛,除了語音壓縮之外,還可以用在音樂等其他的壓縮。
    為了怕自己把看過的文件忘光光(年過三十之後就開始有記憶力退化的狀況),必須做點筆記,把這當作 "外部儲存設備" ,避免腦袋老化的情形!
    主要說明文件在此:Vorbis Specification,有興趣的人可以 認真看!看不懂請不要問我,因為我也看不懂!
    以下只做簡單、重要部分的筆記。
    Vorbis 提供兩種聲音壓縮 Mode,以下這段話相當重要,也因為這樣的彈性,使的我必須去深究 Vorbis 的內部,然後調整到可以在我的專案中使用。
     
    Window shape decode (long windows only) Vorbis frames may be one of two PCM sample sizes specified during codec setup. In Vorbis I, legal frame sizes are powers of two from 64 to 8192 samples. Aside from coupling, Vorbis handles channels as independent vectors and these frame sizes are in samples per channel.
     
    1. 第一種模式,是相同 size 的 window 處理方式。
    011e1990229b4c9d7a7104d5aa0a3c1d.png
     
    2. 第二種模式,是不同 size 的 window 處理方式。
    我個人認為,這樣不同的 size 是為了 internet streaming 的需要,因為可能因為網路 jitter 的關係,聲音來源信號不穩定,所以有這樣的模式可以處理 "有多少資料就壓多少資料" 的特性。
    只是實務上會把程式架構搞複雜,通常對這種不穩定的狀況,我們都會 maintain 一個 jitter buffer 做處理,不過有 jitter buffer 又會影響到 real time 的要求。不過,網路不穩定,又要兼顧 real-time 以及 jitter buffer,那就變得很複雜了!
    所以需要一個 adaptive jitter buffer 來處理。那正是小弟研究所論文的題目!請自行到國家圖書館查閱。呵呵,扯遠了。
     
    9a0f67e5902a51d2a006933640523c46.png
     
    簡單提一下原理後,就要進入程式的深處了。先講一下 Vorbis 的壓縮現象以及我的需求。
     
    在 Vorbis 中,如果每次丟進同樣長度的 sample (比如說 32k 個 samples) ,在前兩次,Initial 階段,都不會有資料輸出。要到了第三組 sample 後才會開始輸出資料。可是問題就出在這!每次輸出資料的 "長度" 是不同的。這件事情應用在 streaming 上問題還不大,可是要應用在結合 video 後 editing 的需求問題就很大了!
     
    試想,如果今天在一段影片 中,video frame 與 voice frame 一起存放,但是因為輸出的長度不同,編輯時就很有可能剛好 "切錯" 地方,這樣你的 editing 就會爛掉了!所以,必須調整他的機制變成每次輸入固定的 samples 就要輸出相同的 encoded frame。這樣在編輯時才不需要為 video 與 audio maintain 兩組 timecode ,造成 sync 上的複雜度。
     
    ( code tracing 中....)
     
    經過一段時間的 tracing 後,以下跳過中間研究的過程,直接跳到結論。
     
    由於有動態 block size encoding 的特性,所以造成我的困擾,所以,先抓幾種不同的聲音來測試看看反應。
     
    測試後發現,輸出的行為跟 "聲音本身" 有關,也就是說,Vorbis 跟大多數的 voice/speech codec  一樣,會去看聲音的特性來決定壓縮的方式以及 codebook。特別用 single-tone / dual-tone 測試時特別明顯,會在聲音波形的 Boundry 的地方產生異動。所以,暫時先放棄去追如何判斷聲音的部分,因為較複雜,改從 window block size 的方向下手。
     
    研究 source code 之後發現,只要修改 window size ,限制最大和最小容許值就可以了。以下為修改的部分:
    在 Vorbis\libvorbis\lib\modes\ 下有 setup_8.h, setup_11.h, setup_16.h, setup_22.h, setup_32.h, setup_44.h, setup_44p51.h, setup_44u.h, setup_X.h。
     
    分別是不同的 sample rate 會使用不同的 setup 。在我的專案中,我需要修改的是 setup_44.h。
     
    在這個檔案中,有定義了 blocksize_short_44 以及 blocksize_long_44。事實上,這兩個常數陣列就是前面所提到的 window mode。
     
    blocksize_short_44 表示壓縮所需 "最小" 的 block size,blocksize_long_44 則表示 "最大" 的。
    原本的內容是:
    static const int blocksize_short_44[11]={
      512,256,256,256,256,256,256,256,256,256,256
    };
     
    static const int blocksize_long_44[11]={
      4096,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048
    };
    只要修改以下即可:
    static const int blocksize_short_44[11]={
        512,2048,256,256,256,256,256,256,256,256,256
    };
     
    static const int blocksize_long_44[11]={
        4096,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048
    };
    眼尖的你應該會發現。我只有調整 blocksize_short_44 的第二個元素為 2048。
     
    因為在我的專案中,我所選用的 quality = 0 。( range : -0.1 ~ 1.0 )
     
    所以我只調整第二組,如果你用的 quality 不同,則要調整相對應的元素。對應表可參考以下陣列:
    static const double quality_mapping_44[12]={
      -.1,.0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1.0
    };

    這樣修改實測後發現還是有些奇怪的現象。一般情況下輸出兩組 samples 會吐出兩組 encoded packets。但是某些情況會輸入兩組,吐出三組,但下一次就一定是輸入兩組,吐出一組,平均來說還是輸入兩組吐出兩組。符合正規的情形,所以這樣把壓縮後的資料存到 video frame 裡面是不會有問題的!接下來就是要把這些情形封裝到一個物件中,對上層 AP 來說,應該就是丟一組吐一組,一個很規律的情形。這部分只要做一個 buffer 處理就可以了,並不是太困難,等寫好再來分享經驗!
     
    順帶一提,網路上關於 Vobis 的討論真的很少,大家似乎都只拿來用,很少去改動裡面的東西。所以只能像瞎子摸象,憑感覺+經驗去查找,測試。所以我也不敢講這是 100% 正確的,不過就我對 voice codec 的了解,這樣邏輯上應該是對的,實際丟不同的聲音類型 ( speech / slience / noise / singal-tone / dual-tone ) 進去都是對的。接下來就是要實戰了!
    若有朋友也在苦戰 vorbis 的話,歡迎留言討論喔!
    Vorbis | 7691 觀看 | 2013-09-18 | 標籤: vorbis, audio codec | Kuann Hung 上傳
  • A video tutorial to help you get started with AngularJS. You can play around with the final result in the following jsfiddle:

    http://jsfiddle.net/johnlindquist/U3c2Q/

    Please take any technical questions about AngularJS to the very active and helpful AngularJS mailing list:
    https://groups.google.com/forum/?fromgroups#!forum/angular
    JavaScript | 6209 觀看 | 2013-09-18 | Kuann Hung 上傳
  • 5b579083a7f80181310b550c505b5ecb.jpg
     
    這篇其實是花了不少時間研究出來的,台灣好像不太多人在作這方面的工作,查到的資料都是國外的,但是舊的、錯誤的居多!
    需要前情提要一下,如果你需要實作一個 Facebook AP,並且使用 facebook chat api 的話,那你就需要準備一個 XMPP Server,然後藉由 XMPP over BOSH  的方式讓你的 javascript 可以直接與 facebook 傳送/接收訊息。
    以下介紹如何用 punjab 架設 XMPP over  BOSH。
    我的環境是 CentOS 5.2,算舊的機器了,所以 Python 還在 2.4 版,若需要使用 punjab 則需要升級到 2.5 以上喔!
    * 以下所使用的函式庫,建議直接上官網尋找最新版。
    JavaScript | 6991 觀看 | 2013-09-18 | 標籤: xmpp, punjab, bosh, chat.facebook.com | Kuann Hung 上傳
  • 6788fdb0026244e87e8251e39fb4fca5.jpg
    今天介紹一下 Facebook Chat API。
    這東西搞了我好久,因為 Facebook 的文件是有名的寫得不清不楚又變來變去的。所以最後還用 sniffer 去觀察封包,畫流程圖,最後才把搞定!希望這個文件對開發 Facebook (以下簡稱 FB) 的人有幫助!
    先提一下 FB Chat API 的作用好了。現在看到大多數的 FB 應用程式,特別是遊戲。總是喜歡有許多邀請朋友、貼塗鴉牆等行為,但是這些行為都 "不即時"。所以如果您需要做一個 "即時" 的應用程式,也就是可以即時找到正在線上的使用者,並且發訊息給他的話,那你就需要用到 FB Chat API 了。
    FB 的 SDK 說明在這邊:https://developers.facebook.com/docs/chat/ 如果你把它看完,而且就可以把程式寫好,那你應該是 XMPP 的高手中的高手了!這樣請跟我聯絡,因為我還有很多雖然作出來,但是不太懂的地方需要請教了!
    在這個案子中我 google 了很多文件,但是有用的不多,而且很多都是過時的文件,所以需要去改程式。以下會說明所用到的相關函式。
     
    XMPP 及 strophe.js
    在開始使用 FB chat api 前不得不先說一下,FB 的聊天功能是走標準的 XMPP,但是 "認證" 的部分則是 FB 修改過的 tag ,叫做 X-FACEBOOK-PLATFORM 。就是這個搞了很久。
    網路上找到關於 XMPP 的 javascript 作最好的應該就是 strophe.js 了!不過只提供 XMPP 一般的認證,也就是輸入帳號及密碼來做認證。有興趣的人可以到這裡 (profxmpp) 下載程式,把它傳到你自己的網站上就可以測試喔!
    測試的方式就是輸入你在 facebook 的帳號密碼,ex: your-name@chat.facebook.com,記得,網域是 chat.facebook.com,而 @ 前面的名稱就是要看你自己的設定了。以下就是畫面,請自行測試。
     
    fac7374c7c67a5923870984c4c95fcb9.png
     
    strophe.js plugin for Facebook.
     
    現在要進入重點了。我找到曾經有人寫過 strophe.js 的 plugin,可以用在 FB 上面。很高興地抓下來測試,卻發現不能用... 查了很久,google 了很久也沒有答案!所以只好拿出以前研究 protocol 的精神,慢慢研究 code 跟 sniffer 了!
    (code tracing + sniffer 中...)
     
    叮~~~
     
    最後發現,原來 Facebook 改掉了抓取 access token 的參數。所以只要小改就可以了!
    在 facebook.js 中大約 Line: 102~109 改成以下:
    var responseText = "";
    responseText += 'api_key=' + this.apiKey;
    responseText += '&call_id=0';
    responseText += '&method=' + method;
    responseText += '&nonce=' + nonce;
    responseText += '&access_token=' + this.sessionKey;
    responseText += '&v=' + '1.0';
    等這邊改好之後,就是要進入主程式了。以下分享重要的幾個部分,相信熟悉 javascript 的人都可以搞定的!

    Initial Strophe.js
     
    <script>
    FB.init({ 
        appId: '<?php echo $app_id;?>',
        status: true, 
        cookie: true,
        xfbml: true,
        oauth: true});
     
    var g_accessToken;
     
    FB.getLoginStatus(function(response)
    {
        if(response.status=='connected')
        {
            if (response.authResponse) {
                syslog("User is connected to the application.");
                g_accessToken = response.authResponse.accessToken;
              }
            CreateStrophe('<?php echo $app_id;?>', '<?php echo $app_secret;?>');
        }
    });
    </script>
    相關 Function​
    <script>
    var g_app_id,g_app_secret;
     
    function on_roster(iq) {
     
        $(iq).find('item').each(function () {
            var jid = $(this).attr('jid');
            var name = $(this).attr('name') || jid;
             
            $('#contacts').append(new Option(name , jid, true, true));
            $("#contacts option[value='"+jid+"']").attr("disabled","");
        });
         
        syslog('add ' + $('#contacts option').length + ' contacts');
         
        // set up presence handler and send initial presence
        connectionFB.addHandler(on_presence, null, "presence");
        connectionFB.addHandler(on_message, null, "message", "chat");
    }
     
    function on_message(message) {
        var from = $(message).attr('from');
         
        if($('#contacts option[value='+from+']').length = 0)
            var name = from;
        else
            var name = $('#contacts option[value='+from+']').text();
             
        var body = $(message).find("html > body");
         
         if (body.length === 0) {
            body = $(message).find('body');
            if (body.length > 0) {
                body = body.text()
            } else {
                body = null;
            }
        } else {
            body = body.contents();
        }
          
        if(body!=null)
        {
            syslog( '<font color="blue">' + name + ' says:' + body + '</font>');
        }
         
        return true;
    }
     
    function on_presence(presence) {
         
        var ptype = $(presence).attr('type');
        var from = $(presence).attr('from');
        if($('#contacts option[value='+from+']').length = 0)
            return;
             
         var name = $('#contacts option[value='+from+']').text();
     
        if (ptype === 'subscribe') {
            syslog('jid: ' + from + ' (' + name + ') has subscribed.');
             
        } else if (ptype !== 'error') {
             
            if (ptype === 'unavailable') {
                // set offline
                syslog('jid: ' + from + ' ( ' + name + ' ) is offline');
            } else {
                var show = $(presence).find("show").text();
                $("#contacts option[value='"+from+"']").attr("disabled","");
                if (show === "" || show === "chat") {
                    // set online
                    syslog('jid: ' + from + ' ( ' + name + ' ) is online');
                } else {
                    // set away
                    syslog('jid: ' + from + ' ( ' + name + ' ) is away');
                }
            }
     
        }
     
        return true;
    }
     
    function syslog(msg)
    {
        console.log(msg);
        $('#syslog').append(msg+'<br />');
     
    }
     
     
    function CreateStrophe(app_id, app_secret)
    {
        g_app_id = app_id;
        g_app_secret = app_secret;
        connectionFB = new Strophe.Connection("http://bosh.metajack.im:5280/xmpp-httpbind");
        connectionFB.facebookConnect("your-name@chat.facebook.com", onConnectFacebook, 300, 1, app_id, app_secret, g_accessToken);
    }
     
    function reconnect()
    {
        connectionFB.facebookConnect("your-name@chat.facebook.com", onConnectFacebook, 300, 1, g_app_id, g_app_secret, g_accessToken);
    }
     
     
    function onConnectFacebook(resp)
    {
        // syslog('onConnectFacebook (' + resp + ')');
         
        switch(resp)
        {
            case Strophe.Status.CONNECTED:
                $('#btnSend').show('slow');
                 
                var iq = $iq({type: 'get'}).c('query', {xmlns: 'jabber:iq:roster'});
                connectionFB.sendIQ(iq, on_roster);
     
                syslog('You can send the messages now!');
                 
                syslog('Retriving contacts...');
            break;
             
            case Strophe.Status.ERROR:
                syslog('onConnect: ERROR!');
            break;
     
            case Strophe.Status.CONNECTING:
                syslog('onConnect: CONNECTING!');
            break;
     
            case Strophe.Status.CONNFAIL:
                syslog('onConnect: CONNFAIL!');
                reconnect();
            break;
     
            case Strophe.Status.AUTHENTICATING:
                syslog('onConnect: AUTHENTICATING!');
            break;
     
            case Strophe.Status.AUTHFAIL:
                syslog('onConnect: AUTHFAIL!');
                reconnect();
            break;
     
            case Strophe.Status.DISCONNECTED:
                syslog('onConnect: DISCONNECTED!');
                reconnect();
            break;
             
            case Strophe.Status.DISCONNECTING:
                syslog('onConnect: DISCONNECTING!');
            break;
             
            case Strophe.Status.ATTACHED:
                syslog('onConnect: ATTACHED!');
            break;
             
        }
    }
    function sendMsg(jid, text)
    {
            var message = $msg({to: jid,
                                "type": "chat"})
                .c('body').t(text).up()
                .c('active', {xmlns: "http://jabber.org/protocol/chatstates"});
     
            connectionFB.send(message);
     
    }
    </script>
    關於 email 如何取得,這部分只要是有開發 FB App 經驗的應該都不是問題,所以就不細說了。而權限部分則記得增加以下幾個權限!
     
    user_online_presence, friends_online_presence, xmpp_login, read_friendlists
    這邊有一個要注意的事情就是,需要自己架一個 XMPP Server 然後啟用 BOSH 掛在 apache proxy 裡面,這部分我還沒有完成,暫時先用別人的。(http://bosh.metajack.im:5280/xmpp-httpbind)
    最好還是自己架,別人的有時候怪怪的,有時通有時不通,等我架好測試好在跟大家分享了!
    JavaScript | 8454 觀看 | 2013-09-18 | 標籤: facebook, chat, api, gap, xmpp | Kuann Hung 上傳
  • 4feb0746055798626cf742bb6fe66606.png
     

    開發 Web 越來越需要用到 JavaScript 了,有的時候自己寫,有的時候要看別人寫的 code。通常寫程式的習慣不同,到後來看得都很痛苦!
    現在有個網站可以幫助你 "美化" 你的 JavaScript 喔!
     
     
    這個網站用法很簡單,只要把你的 JavaScript 貼進去,他就會幫你直接美化喔!
    希望大家都能把程式寫好看一點,這樣維護容易,接手也容易!
     
    JavaScript | 4817 觀看 | 2013-09-18 | 標籤: javascript, beautifier | Kuann Hung 上傳
  • 67a35e63003675f6b0446cbb828de783.jpg
    這是 FB Developer Blog 在 2012/5/8 發表的。用來判斷當 User 已經登入 FB 的判斷,先註記起來,以後一定用得上的!
     
    <!DOCTYPE html>
     
    <html>
      <head>
        <title>Example</title>
      </head>
      <body>
        <!-- Load the Facebook JavaScript SDK -->
        <div id="fb-root"></div>
        <script src="//connect.facebook.net/en_US/all.js"></script>
         
        <script type="text/javascript">
           
          // Initialize the Facebook JavaScript SDK
          FB.init({
            appId: 'APP_ID',
            xfbml: true,
            status: true,
            cookie: true,
          });
           
          // Check if the current user is logged in and has authorized the app
          FB.getLoginStatus(checkLoginStatus);
           
          // Login in the current user via Facebook and ask for email permission
          function authUser() {
            FB.login(checkLoginStatus, {scope:'email'});
          }
           
          // Check the result of the user status and display login button if necessary
          function checkLoginStatus(response) {
            if(response && response.status == 'connected') {
              alert('User is authorized');
               
              // Hide the login button
              document.getElementById('loginButton').style.display = 'none';
               
              // Now Personalize the User Experience
              console.log(‘Access Token: ‘ + response.authResponse.accessToken);
            } else {
              alert('User is not authorized');
               
              // Display the login button
              document.getElementById('loginButton').style.display = 'block';
            }
          }
        </script>
         
        <input id="loginButton" type="button" value="Login!" onclick="authUser();" />
      </body>
    </html>
    JavaScript | 5337 觀看 | 2013-09-18 | 標籤: facebook, login, user | Kuann Hung 上傳