2011年1月23日 星期日

買了!Uptech TV7130電視卡

IMG_0109


前幾個禮拜,常常跟博班 Adios 大神閒聊電視的事情,沒想到我也買了電視卡了。宿舍本來就有提供第四台,但住了一年多並沒有吸引我去買電視卡/電視盒的念頭,但最後我還是敗了。買電視卡的主因是因為前陣子申請了壹電視的網樂通,原先以為他可以當電視盒來用,沒想到他只輸出 AV 端子 Orz 盡管東西非常慢才來,在等待的過程中,我也回信說要退掉,但耐心的客服始終說服我,最後也就等到網樂通啦!算是比第一批申請的人,卻慢了快三個禮拜吧!


TV7130 產品應用
來源:TV7130產品型錄


接著來說說 TV7130 電視卡吧!這是我第一次使用電視卡,更早之前算是使用過電視盒。電視卡的缺點是需要插在主機上面,並佔用一個插槽,並且要看電視就必須開機。我買的這張是使用 PCI 插槽,在高階一點的電視卡是用 PCI-E 的。再加上現在主機板大多設計為 M-ATX 的板子,為了縮小面積,所提供的 PCI 插槽就少了不少,像我這張主機板是 GA-MA785GPMT-UD2H 的,就只有兩個 PCI 插槽。一個用在網路卡,另一個就用在這張電視卡,剛好都插滿了。而使用電視卡的優點,那就是可以邊用電腦邊看電視,換句話說,可以把看電視弄成小視窗擺在螢幕上,而不用像電視盒那樣佔了一個螢幕資源,除此之外,也可以錄製電視節目喔。但真正吸引得我的主因,就是可以把邊寫程式邊看電視啦 cc 以前常用廣播當作背景音樂,現在大概可以用電視當背景音樂吧 XD


TV7130 配備
來源:TV7130 繁體中文使用手冊第二版


安裝這張電視卡並非難事,插進主機板,安裝一下驅動程式重開機後,接上 Cable 線就算搞定。然後我一直搞不懂為啥他多了一條線,也就是上圖左下角的那條 Phone Jack to Phone Jack ,最後,我才發現原來裝了電視卡後,使用時螢幕上雖然可以看到影像(第一次使用別忘了要去電台掃描),但始終沒有聲音,原來這就是那條線的目的,他就是用來把電視卡輸出的聲音導入音效卡的輸入端,當然,也可以另外接喇叭吧。我使用的就是導回音效卡的輸入端,並且在電視卡軟體->[設定]->[裝置]->[播放控制]->[Rear Blue In]


這樣的使用缺點就是電視的聲音大小聲,跟主機上使用的聲音大小聲會綁再一起,例如我想要把電視的聲音關小一點,把 Skype 的聲音放大一點,那這樣是不太行的!整體上但勉強都還堪用囉!


對於電視卡算是多了點了解,最驚訝的是音效的輸出設計,只提供從電視卡輸出,這大概也跟自身產品有關吧,未來可以在找一個喇叭接著用,就可以改善這種困擾吧。


USB 無線網卡 - SAPIDO AU-4912 超迷你無線N

usb網卡


上週無意間發現這款 USB 無線網卡,就在 Plurk 上詢問有無使用經驗,倒是讓我大學同學也敗了這款,他還比我早買 XD 同學買的主因是自己筆電網卡很不穩定,所以需要新購一張網卡,最後則是被這款體積小的給吸引了,只是筆電 USB 孔很少,要留意啊。這是一款跟 10 元硬幣大小差不多大的 USB 無線網卡,我買他的主因有兩個,一個是讓主機可以透過無線網路上網,第二個則是它可當 Soft AP 囉。其中 Soft AP 就是透過軟體讓無線網卡變成無線基地台啦,雖然說原理都差不多,但並不是每張無線網卡的製造商都有提供 Soft AP 的程式。


我在新竹 NOVA 邊逛了會,發現還不少家在賣 USB 無線網卡,體積跟隨身碟差不多大的比較多,跟這張一樣大的比較少,另外,價格也都不便宜,都要 500 上下,我在順發有看到一款體積稍大的,但賣 369 元的無線網卡,但最後還是花多一點點錢試試這款小體積的網卡。可以多留一下 spec ,像這張網卡是 150 Mbps ,我看到有得賣得比較貴是 300 Mbps 囉,只是速度快慢是否影響很大,那就很難說了!我覺得 150 Mbps 在某些使用情境已經非常夠用了。


usage


使用上很簡便,若只是當一般無線網卡,那可能插上主機後,在 Win 7 x64 甚至連驅動程式都不用安裝也可以用。但如果要當 Soft AP 的話,那必須安裝軟體,安裝完後,請將 [模式] 調成 [基地台] ,接著再選擇要分享的網路以及加設定密連線的密碼,拉到此頁面下面,按下 OK 後即可完工!我用 iPod Touch 4 測試可以正常使用網路。


逛了一下內附的 CD ,發現裡頭也有 Linux 跟 Mac 的 driver ,看起來還不錯吧。有興趣的可以再去官網看看,至少短時間我還測不到啥問題。未來應該會把它當成旅行用的隨身 3C 產品 XD


2011年1月18日 星期二

心的方向!Go~








昨天到台北參加聚餐,往高鐵站的路上,被後照鏡上火紅的夕陽閃了許久,讓我哼起了這首歌。有點久沒騎車到高鐵站,除了騎過頭外,沒想到連高鐵前面常常一排排違規停車的機車群,忽然都沒看到,害我一時不知要停在哪。


最近還是有非技術類的心情想要記下來,但每每寫到一半就被我 cancel 掉,實在是三五句提到公司福利不然就政治八卦等,這樣的行為讓我對自己感到失落。去年底開始警惕自己不要談論這類話題,希望 2011 年有好的開始。盡管背負的壓力還沒減輕,但開始比較不會只為錢工作了。今年的工作壓力理當比去年大,計畫趕不上變化,但還好,除了每週唸不熟領域的 paper 外,剩下的應該都還能應付吧?


對於 2010 的感觸,除了提不得的暗黑八卦外,大概是對資源管控有所感觸。例如一天 24 小時,某件事做多了,另一件事就必然做少了。有時也不得不跟別人搶資源,例如宿舍樓下的烘乾機啊,或是火車坐位上永遠只有 3 個扶手。也曾跟大神吃飯,當時探問他對 Android Market 或 iOS App store 有無興趣投入,得到的回應當然是有,但有更重要的事要擺在前頭 -- 陪孩子成長,這時我也才比較會回過頭思考一些事情,投入熱門的產業追求的目標是什麼?有沒有比這些更重要的目標呢?很佩服一些人,像青蛙學弟,可以大膽地嘗試一些新東西,不要只為錢工作,也讓我反省一下,該抽空弄弄點更有意義的東西。


最近對未來有些新的方向了,願能持之以恆,離目標越來越近。


2011年1月6日 星期四

Android 開發筆記 - 模擬與繪製 GPS 移動路線

mygpspathwithmarker
上頭紅色的是繪製的路徑,藍色點則是模擬時,顯示的 GPS 座標位置,將隨著時間變動。這是根據此篇 Android 開發教學筆記 - 透過 Google Maps API 畫出 GPS 路徑Android 開發教學筆記 - 調整 GPS 座標個數,以提昇路徑繪圖效率 的筆記所做的延伸練習。


給定一段 GPS 紀錄的路徑,經過個數的縮減壓縮並在地圖上完成路徑的繪製,接著想要了解原始 GPS 移動與繪製的路徑差異有多大,所以就有了這個練習。依照原始 GPS 座標資訊,畫製一個 marker 隨著時間擺放到指定的 GPS 位置,這樣的效果除了能確認原始 GPS 路線與壓縮後的路徑位置的差異,也可以了解實際收集到的 GPS 座標是否出現在 Google Maps 繪製的地圖道路。


實做上在 class MyGPSPath 裡,新增並且改變一些 member 宣告方式:


Handler jobs;
final double srcLogs[]={ 48.138050017878413, 16.481179967522621 /* ,  ... */ };
double logs[] = {};
int gps_at;


其中 jobs 用來更新畫面,srcLogs用來紀錄原始的不重複的 GPS 資訊,而 logs 會是壓縮過後的 GPS 個數,而 gps_at 則是等會模擬時,用來紀錄走到那個 GPS 點。


接著在 onCreate 裡,初始化 gps_at, jobs, logs 變數,以及調整 overlay :


gps_at = 0;
jobs = new Handler();

ArrayList<Double> data = new ArrayList<Double>();
for( int i=0; i<srcLogs.length ; ++i )
    data.add( new Double( srcLogs[i] ) );
data = rebuildGSPLogsByAngleCheck( data , 0 );
        
logs = new double[data.size()];
for( int i=0; i<data.size() ; ++i)
    logs[i] = data.get(i).floatValue();

List<com.google.android.maps.Overlay> ol = mapView.getOverlays();
ol.clear();
ol.add(new MyPathOverlay());

MapController mapController = mapView.getController();
if( mapController != null )
{
    mapController.setCenter(new GeoPoint( (int) (srcLogs[0]* 1000000) , (int)(srcLogs[1]* 1000000) ) );
    mapController.setZoom(15);
}

new Thread( new Runnable(){
    public void run()
    {
        gps_at = 2;
        while( gps_at < srcLogs.length )
        {
            try{
                jobs.post( new Runnable(){
                    public void run(){
                        List<com.google.android.maps.Overlay> ol = mapView.getOverlays();
                        while( ol.size() > 1 )
                            ol.remove(1);
                        ol.add(new MyMarkerOverlay( srcLogs[gps_at] , srcLogs[gps_at+1]));
                        
                        GeoPoint in = new GeoPoint((int) (srcLogs[gps_at] * 1000000) , (int) (srcLogs[gps_at+1] * 1000000) );
                        Point out = new Point();
                        mapView.getProjection().toPixels(in, out);
                        int diff = 100;
                        if( out.x < diff || out.y < diff || out.x > mapView.getWidth() - diff || out.y > mapView.getHeight() - diff )
                        {
                            MapController mc = mapView.getController();
                            mc.animateTo(new GeoPoint( (int)(srcLogs[gps_at]*1000000) , (int)(srcLogs[gps_at+1]*1000000 )) );
                        }
                    }
                });
                Thread.sleep(500);
            }
            catch(Exception e)
            {
            }
            gps_at +=2 ;
        }
    }
}).start();


上頭的 MyPathOverlay 則是之前例子中的 MyMapOverlay,在此僅改變成較有意義的名稱,而 onCreate 最後面使用到的 Thread,就是用來模擬 GPS 變化的過程,每 0.5 秒更動 gps_at 時,則重新擺放 MyMarkerOverlay 的位置,並且稍微計算一下,當 Marker 太靠近可視地圖的邊框時,自動調整地圖位置。


另外,還需實做 MyMarkerOverlay,用來標記 GPS 移動:


class MyMarkerOverlay extends com.google.android.maps.Overlay
{
    double at_lat,at_lng;
    MyMarkerOverlay( double _lat, double _lng )
    {
        super();
        at_lat = _lat;
        at_lng = _lng;
    }
    @Override
    public boolean draw(Canvas canvas, MapView mapView,boolean shadow, long when)
    {
        super.draw(canvas, mapView, shadow);
            
        if( !shadow )
        {
            GeoPoint in = new GeoPoint((int) (at_lat * 1000000) , (int) (at_lng * 1000000) );
            Point out = new Point();
            mapView.getProjection().toPixels(in, out);
                
            int r = 6;
            Paint p = new Paint();
                
            p.setColor(Color.BLUE);
            p.setAntiAlias(true);
            RectF oval=new RectF( out.x - r, out.y - r, out.x + r, out.y + r);

            canvas.drawOval(oval, p);
        }
        return true;
    }
}


也可以實做 ItemizedOverlay<OverlayItem> 的方式,好處是用法接近 Web API 的使用,把你想要新增的點,加到下面的 list 即可:


class MyItemMarkerOverlay extends com.google.android.maps.ItemizedOverlay<OverlayItem>
{
    private ArrayList<OverlayItem> myOverlays = new ArrayList<OverlayItem>();
    private Context myContext;
    public MyItemMarkerOverlay(Drawable defaultMarker, Context context)
    {
        super(boundCenterBottom(defaultMarker));
        myContext = context;
    }
    @Override
    protected OverlayItem createItem(int i)
    {
        return myOverlays.get(i);
    }
    @Override
    public int size()
    {
        return myOverlays.size();
    }
    @Override
    protected boolean onTap(int index)
    {
        OverlayItem item = myOverlays.get(index);
        //AlertDialog dialog = new AlertDialog.Builder(MyGPSPath.this).create();
        AlertDialog dialog = new AlertDialog.Builder(myContext).create();
        dialog.setTitle(item.getTitle());
        dialog.setMessage(item.getSnippet());
        dialog.setButton("OK", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which)
            {
                dialog.cancel();
            }
        } );
        dialog.show();
        return true;
    }
    public void addOverlay(OverlayItem overlay)
    {
        myOverlays.add(overlay);
        populate();
    }
}


用法:


List<com.google.android.maps.Overlay> olay = mapView.getOverlays();
Drawable drawable = this.getResources().getDrawable(R.drawable.icon);
MyItemMarkerOverlay markersOverlay = new MyItemMarkerOverlay( drawable, this ); // new MyItemMarkerOverlay( drawable, MyPGSPath.this );

markersOverlay.addOverlay(
    new OverlayItem(
        new GeoPoint((int) (srcLogs[gps_at] * 1000000) , (int) (srcLogs[gps_at+1] * 1000000)),
        "Title",
        "Snippet"
    )
);

olay.add(markersOverlay);


此練習的 GPS 變化,也可以透過 DDMS 來處理,例如定時 send 一些 GPS 座標等,但我還沒嘗試,看來下個練習方向可以用用看 DDMS 囉。


Android 開發筆記 - 調整 GPS 座標個數,以提昇路徑繪圖效率

Android 開發教學筆記 - 透過 Google Maps API 畫出 GPS 路徑 提到關於繪製 GPS 路徑的方式之一,然而,有時路徑長度過長,如兩三千個 GPS 座標,則可能造成路徑繪圖的負擔。


在此嘗試降低 GPS 個數的方式:



  • 以連續兩點為單位,計算差距,差距小於某數值則刪去

  • 以連續三個點為單位,所建立的兩段直線的角度,若角度大過某個數值,則把中間點去除


其中前者會存在一些哪些點可刪或不可刪問題,並且去除個數有限,後者則是計算量大,刪除的效果不錯,在 2011_01_03_Schwechat_Brno.gpx 測資中,可以從不重複的一千多點縮小到兩、三百點,但仍會有畫出的路徑與道路偏移的問題,例外則是角度選擇的部份


簡單的角度實做,就是計算兩向量的 cosine 數值,將越靠近 -1 的進行刪除的動作,也就是兩向量越接近 180 度的直線。


片段程式碼:


public static void main(String a[] )
{
    double logs[] = { 25.051981, 121.522751, /* ... , */ 25.064867, 121.526154 };

    ArrayList<Double> data = new ArrayList<Double>();
    for( int i=0; i<logs.length ; ++i )
        data.add( new Double( logs[i] ) );

    System.out.println( data.size() );
    data = rebuildGSPLogs( data , 0 );
    System.out.println( data.size() );
}

//
// list = { lat1, lng1, lat2, lng2, ... };
//
private ArrayList<Double> rebuildGSPLogsByAngleCheck( ArrayList<Double> list , int at )
{
    if( at < 2 )    // begin at second point
        at = 2;
//    if( list.size() > at + 6 && at >= 0 )
    while( list.size() > at + 6 && at >= 0 )
    {
        double x1 = list.get(at).floatValue() , y1 = list.get(at+1).floatValue() , x2 = list.get(at+2).floatValue(), y2 = list.get(at+3).floatValue() , x3 = list.get(at+4).floatValue() , y3 = list.get(at+5).floatValue();
        double nx1 = x1-x2 ,nx2 = x3 - x2, ny1 = y1-y2 , ny2 = y3-y2;
        double len1 = Math.sqrt( nx1*nx1 + ny1*ny1 ) , len2 = Math.sqrt( nx2*nx2 + ny2*ny2 );
            
        if( len1 == 0 )
        {
            list.remove(at+2);    // lat
            list.remove(at+2);    // lng
        }
        else if( len2 == 0 )
        {
            list.remove(at+4);    // lat
            list.remove(at+4);    // lng
        }
        else
        {
            double checkCos = (nx1*nx2 + ny1*ny2 )/len1/len2 ;
            if( checkCos > -0.986025 )  //  >= -1 : keep all
            {
                at+=2;
            }
            else
            {
                list.remove(at+2);    // lat
                list.remove(at+2);    // lng
            }
        }
//        return rebuildGSPLogsByAngleCheck( list , at );
    }
    return list;
}


有些需要留意的點,原先把 rebuildGSPLogsByAngleCheck 寫成遞迴,在桌機上跑很正常,但移植到 Android 模擬器上,大概沒跑多久就掛了,查了錯誤訊息才得知 out of stack,所幸程式碼只需改個 2 行就改成非遞迴式。


除此之外,在繪圖方面,也可以採用一些方式提昇效率:



  • 扣除重複位置的點

  • 僅繪出可視地圖範圍內的路徑


對於前者部分,是因為就算 GPS 座標不同,在進行繪製的過程中,會先轉成對應的繪圖座標,在這樣的情境下,有些非常相近的 GPS 座標,其實是會被對應成同一個繪圖點,解法很簡單,就只要用個 Hash 就可以解掉;後者問題是在於一條路徑很長,但顯是在地圖上只是很小的一段路程,因此可以只挑出目前地圖看得見的部分進行繪製,也就是將 GPS 座標轉成繪圖座標,此時去判斷該繪圖座標的位置是不是落在目前螢幕顯示的區域,如判斷 X >= 0 && Y >= 0 && X < width && Y < height 。


然而,處理可見路徑的繪圖部分,有以下的問題:


由於兩點構成一條線,因此有可能下一個點已經超出目前顯示地圖的範圍,但剛剛畫的點離 MapView 邊框還有段距離,依據上述的條件則不會畫出連接線,導致看起來像路徑已經斷掉,這種情況也會發生在移動地圖時,前一個點已超出範圍,則會看到之前的路線斷掉。


解決方式有兩種:



  • 使用一個 diff 數值來判斷,例如原先是判斷畫點得座標必須在 0 ~ mapView.getWidth() 且 0 ~ mapView.getHeight() 範圍才顯示,現在再加上一點數值來增大範圍

    • x + diff >= 0 && x <= mapView.getWidth() && y + diff >= 0 && y <= mapView.getHeight()



  • 使用兩個 list 來處理,假設整條路徑共有 n 個點,且第 x 點代表第一個被畫在螢幕上的點,那使用一個 stack 記錄 1~ x - 1 個點,第二個 list 記錄 x+1 ~ n 個點,以輔助繪出路徑 x-1 與 x 的路徑,以及 x 與 x+1 的路徑,以此解決路線斷掉的問題。如果並沒有一個 x 點會被畫出,那只好去找尋在可視地圖中,有沒有哪條線比較接近目前的位置,有的話則把它會出來,在此使用 cosine 的計算,挑選數值最接近 -1 的,代表夾角接近 180 度


第一種解法:


int diff = 100;
boolean useMoveTo = true;
for( int i=0; i<logs.length ; i+=2 )
{
    out = getPixelXYFromGeoValue( p, logs[i], logs[i+1] );

    if( out.x + diff < 0 || out.y + diff < 0 || out.x > mapView.getWidth() + diff || out.y > mapView.getHeight() + diff )
    {
        useMoveTo = true;
    }
    else if( useMoveTo )  // i = 0
    {
        myPath.moveTo(out.x, out.y);
        useMoveTo = false;
    }
    else if( i+2 != logs.length )
    {
        myPath.lineTo(out.x, out.y);
    }
    else
    {
        myPath.setLastPoint(out.x, out.y);
    }
}


第二種:


boolean useMoveTo = true;
int pCnt = 0;
HashMap<Point,String> hashMap = new HashMap<Point,String>();
Point check = getPixelXYFromGeoValue( p, srcLogs[gps_at], srcLogs[gps_at+1]);
ArrayList<Point> UseFF = new ArrayList<Point>();
ArrayList<Point> UsePP = new ArrayList<Point>();
                
for( int i=0; i<logs.length ; i+=2 )
{
    out = getPixelXYFromGeoValue( p, logs[i], logs[i+1] );

    if( hashMap.containsKey(out) )
        continue;
    hashMap.put(out, out.x+"-"+out.y);
                    
    if( out.x < 0 || out.y < 0 || out.x > mapView.getWidth() || out.y > mapView.getHeight() )
    {
        if( pCnt < 1 )
            UsePP.add( out );
        else
            UseFF.add( out );
        continue;
    }
    else if ( pCnt < 1 )
    {
        int size = UsePP.size();
        if( size > 0 )
        {
            myPath.moveTo( UsePP.get(size-1).x, UsePP.get(size-1).y);
            useMoveTo = false;
            UsePP.clear();
        }
    }
    else
    {
        int size = UseFF.size();
        if( size > 0 )
        {
            myPath.lineTo( UseFF.get(0).x, UseFF.get(0).y);
            if( size > 1 )
            {
                myPath.moveTo( UseFF.get(size-1).x, UseFF.get(size-1).y);
            }
            UseFF.clear();
            useMoveTo = false;
        }
    }

    pCnt++;
                    
    if( useMoveTo )
    {
        myPath.moveTo(out.x, out.y);
        useMoveTo = false;
    }
    else if( i+2 != logs.length )
    {
        myPath.lineTo(out.x, out.y);
    }
    else
    {
        myPath.setLastPoint(out.x, out.y);
    }
}

if ( pCnt < 1 )
{
    int size = UsePP.size();
    if( size > 1 )
    {
        double diff = 1;
        int checkAt = -1;
        for( int i=0 ; i+1<size; ++i )
        {
            double x1 = UsePP.get(i).x - check.x ;
            double y1 = UsePP.get(i).y - check.y ;
            double x2 = UsePP.get(i+1).x - check.x ;
            double y2 = UsePP.get(i+1).y - check.y ;
            double l1 = Math.sqrt( x1*x1 + y1*y1 );
            double l2 = Math.sqrt( x2*x2 + y2*y2 );
            double checkCos = (x1*x2 + y1*y2 )/l1/l2 ;
            if( checkCos < diff )
            {
                diff = checkCos;
                checkAt = i;
            }
        }

        if( checkAt > -1 )
        {
            myPath.moveTo( UsePP.get(checkAt).x, UsePP.get(checkAt).y);
            myPath.lineTo( UsePP.get(checkAt+1).x, UsePP.get(checkAt+1).y);
        }
        UsePP.clear();
    }
}
else
{
    int size = UseFF.size();
    if( size > 0 )
    {
        myPath.lineTo( UseFF.get(0).x, UseFF.get(0).y);
        UseFF.clear();
    }
}


雖然降低 GPS 個數可以提升路徑繪圖的效率,但也因此失去精準度,特別是 Zoom Level 越高時,會看到路徑偏移很嚴重,或許還可以搭配 Zoom Level 來決定是否要做 GPS 個數的刪減囉