2019年11月29日 星期五

Test

 
public clas Test {
    public static void Main(String arg) {
        system.out.println("Hello\n");
    }
}

2019年1月14日 星期一

[心得]台灣普設大學的後果

錯誤的政策比貪官還恐怖
李遠哲的普設大學這個錯誤政策造成的後果,
1. 研發設計需要大量動腦的工作, 卻充滿了不動腦思考整天只在等待別人給她結果的人, 生產線型的人
2. 技職體系的退步, 良好的技術沒人傳承
3. 大量需要勞力或者辛苦不需要技術的工作沒有人要做
整個結果是 高階需要大量動腦的設計類型的工作 卻充滿只在等待結果, 不想動腦的人, 但這些人應該要待在生產線才對
但是辛苦不需要動腦, 卻找不到人
整個產業就完全失衡,
研發不能研發, 生產也沒有足夠人力生產,
然後國家競爭力就越來越退步

2017年12月24日 星期日

vim不用外掛就有的自動完成

當我曾經輸入一些又臭又長的字時

如果之後要再輸入

只需要輸入部份

然後搭配Ctrl + p或Ctrl + n來選完整字

2015年8月19日 星期三

一個簡單的 musicPlayer

Android software stack,

在開始介紹 RandomMusicPlayer 前, 首先先複習 android software stack,
軟體分層是大型軟體專案開發的重要觀念,
當軟體分層後才不會導致軟體過於龐大難以維護,
並且軟體分層後軟體的架構也會比較清晰易懂

Android 是屬於及大型的軟體專案,
在 Android 這個大專案底下又包含了 509 個子專案
因此如果不將軟體分層, 則系統會過於龐大難以維護
並且也使的系統難以開發

Google 基本上把軟體分層了, application, java framework, native framework, kernel
但是根據一般電腦學科的分法, application, java framework, native framework 屬於 user space, kernel 屬於 kernel space.

  • kernel : 是一些硬體相關, os 相關, 一般在 CPU 的 kernel mode 執行, 一般的 process 無法操作
  • native framework: 這一曾放的東西比較多元, 像是外部的專案 google 把他放入, 像是 skia, libc, 另外還有一些跟硬體相關像是 surface manager, 另外還有一些急需執行效率像是 media framework
  • java framework: Android application 是以 java 寫成的, 因此需要 java framework 作為和 android 系統溝通的介面, 並且所有和 android application 相關的 framework 都是在這一曾實作的 
  • application: 通常是用來做 ui 顯示, 和程式開發商自己專利技術實作的地方


如下圖所示:

1. RandomMusicPlayer:

  在開始介紹 android MediaPlayer前, 先借介紹如何撰寫一個簡單的 music player, 以免一開始就陷入結構極度複雜的 media player 中. 在 android 原生 source code 終有一個很簡單的 music player 的 sample code, 可以到 你的 codebase 中的 development/samples/RandomMusicPlayer 中 study 他的 source code.


在 RandomMusicPlayer 中大致上可以分成四大類的元件
l   MainActivity: 他是 RandomMusicPlayer 的 UI 介面, MainActivity 主要是接收使用者的 UI 操作, 並且把這些 UI 操作轉成命令並且送出 intent 讓 MusicIntentReceiver 接收,
l   MusicService: MusicIntentReceiver 收到命令後, 透過 MusicService 送 command 給 Android 的 MediaPlayer
l   MusicRetriever: MusicRetriever 是這個 application 會叫 RandomMusicPlayer 的主要原因, 他會有一個 default 的 http stream 的 uri 和在 external storage 的歌曲共同構成一個 list, 每次挑選播放的歌曲時, 都是 random 的在這張 list 中隨機挑選一首歌曲播放
l   RemoteControl: Remote control 適用在 lockscreen 上操作 musicplayer 的介面

2. MainActivity

以下是 RandomMusicPlayer UI的簡圖,
雖然 google 在 UI 上做了很多的 button, 然而因為這只是一個 sample code, 因此不是每個 UI 元件都有實作, google 實做的只有以下六個 button 而已(mPlayButton, mPauseButton, mSkipButton, mRewindButton, mStopButton, mEjectButton).

一個 application 的入口點是在 onCreate 這個 callback, onCreate callback 一般負責 init resource,
以下這段程式碼就是 RandomMusicPlayer 的 onCreate method, 他首先會把抓取他需要用到的 UI object 的 instance, 並且為他關心的 object 透過 setOnClickListener 註冊這些 ui 元件相對應的 callback function, mPlayButton.setOnClickListener(this); 是指當 UI 事件發生在 mPlayButton 上時, 到相同的 class 尋找他的 callbackfunction (onClick)



    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mPlayButton = (Button) findViewById(R.id.playbutton);
        mPauseButton = (Button) findViewById(R.id.pausebutton);
        mSkipButton = (Button) findViewById(R.id.skipbutton);
        mRewindButton = (Button) findViewById(R.id.rewindbutton);
        mStopButton = (Button) findViewById(R.id.stopbutton);
        mEjectButton = (Button) findViewById(R.id.ejectbutton);

        mPlayButton.setOnClickListener(this);
        mPauseButton.setOnClickListener(this);
        mSkipButton.setOnClickListener(this);
        mRewindButton.setOnClickListener(this);
        mStopButton.setOnClickListener(this);
        mEjectButton.setOnClickListener(this);
    }

當使用者觸碰 UI 上的 button 時 framework 會執行行相對應的 onClick function,
以下的程式碼就是 RandomMusicPlayer 的 執行 button press 的相對應 method

    public void onClick(View target) {
        // Send the correct intent to the MusicService, according to the button that was clicked
        if (target == mPlayButton)
            startService(new Intent(MusicService.ACTION_PLAY));
        else if (target == mPauseButton)
            startService(new Intent(MusicService.ACTION_PAUSE));
        else if (target == mSkipButton)
            startService(new Intent(MusicService.ACTION_SKIP));
        else if (target == mRewindButton)
            startService(new Intent(MusicService.ACTION_REWIND));
        else if (target == mStopButton)
            startService(new Intent(MusicService.ACTION_STOP));
        else if (target == mEjectButton) {
            showUrlDialog();
        }
    }
在這裡假設, 使用者按 mPlayButton, 那麼 framework 會執行 onClick 的 function,  並且 traget 是 mPlayButton, 如此 onClick 就知道哪個 button 被 click,  並且執行相對應的 method

3. MusicService

在開始 study MusicService 前先們探討一個最基本的問題,
為什麼 MusicService 必須獨立成另成一個 service, 而無法跟 MainActivity  合併? 
其實跟 Activity 的 life cycle 是相關的,

首先思考使用者行為,
一般使用者用手機聽音樂都會關掉螢幕,
但是當螢幕關閉時 Activity Life cycle 進入了 onPause 的 stage,
此時 activity 的所有資源都必須被釋放掉,
因此如果把 MediaPlayer 的操作放在 activity 時, 當使用者關閉螢幕,
則 android 會把 MediaPlayer 的 instance 釋放掉,
此時音樂就會自動關閉, 因此才會把 MediaPlayer 的操作放在一個 service 中
當進手機休眠時,  Service 還會在背景執行.

MusicService 是一個負責在背景執行, 專門接收外部命令的程式,
但別被他的名字騙了, 雖然他是 Service,
但是他還是在 main thread 中執行, 並不是另一條獨立的 thread,
因此他還是受到 life cycle 的限制, 只是沒那麼嚴謹而已

MusicService 在背景執行, 並且接收前景送來的命令, 並且轉成 MediaPlayer 相關的 api,
通知 media player 執行相對映的操作

以下的程式碼就是他接收前景送來命令的程式碼

    /**
     * Called when we receive an Intent. When we receive an intent sent to us via startService(),
     * this is the method that gets called. So here we react appropriately depending on the
     * Intent's action, which specifies what is being requested of us.
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();
        if (action.equals(ACTION_TOGGLE_PLAYBACK)) processTogglePlaybackRequest();
        else if (action.equals(ACTION_PLAY)) processPlayRequest();
        else if (action.equals(ACTION_PAUSE)) processPauseRequest();
        else if (action.equals(ACTION_SKIP)) processSkipRequest();
        else if (action.equals(ACTION_STOP)) processStopRequest();
        else if (action.equals(ACTION_REWIND)) processRewindRequest();
        else if (action.equals(ACTION_URL)) processAddRequest(intent);

        return START_NOT_STICKY; // Means we started the service, but don't want it to
                                 // restart in case it's killed.
    }
接著我們在看到他如何跟 MediaPlayer 溝通的程式碼, 當然以下的程式碼已經是 application 最底部了,
執行到 MediaPlayer 就屬於 java framework 的部份了.
void processPauseRequest() {
        if (mState == State.Retrieving) {
            // If we are still retrieving media, clear the flag that indicates we should start
            // playing when we're ready
            mStartPlayingAfterRetrieve = false;
            return;
        }

        if (mState == State.Playing) {
            // Pause media player and cancel the 'foreground service' state.
            mState = State.Paused;
            mPlayer.pause();
            relaxResources(false); // while paused, we always retain the MediaPlayer
            // do not give up audio focus
        }

        // Tell any remote controls that our playback state is 'paused'.
        if (mRemoteControlClientCompat != null) {
            mRemoteControlClientCompat
                    .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
        }
    }
在這段程式碼中首先判斷是否正在抓歌曲清單, 如果正在抓歌曲清單則先暫停,
接著判斷目前的播放狀態, if (mState == State.Playing) { 必須有這行是為了保護 MediaPlayer,
以避免在不屬於 MediaPlayer 接受的狀態做違法的操作, 
而造成 MediaPlayer 丟出 IllgelStateException
至於 MediaPlayer 有哪些狀態, 並且哪些狀態可做哪些事情,
可以查詢 MediaPlayer sdk 的官網的 MediaPlayer state machine


2015年8月18日 星期二

android media player 簡介

在這裡我們只簡介 MediaPlayer 的模型,
從一般的 player 模型開始簡介;
然後介紹 media player 的 state machine,
state machine 是 media application 必須遵守否則無法播放;
接著簡介 media framework 的各個子模組;
最後才是介紹 nuplayer, nuplayer 從 android 5.0 開始已經是 media framework 的預設 player, 他也是整個 media framework 的核心.

1. MEDIAPLAYER 一般模型
無論是使用 vlc, gstreamer, 或是其他的 media,
其實他還是使用以下的模型,就只是程式撰寫技巧和t程式組織上的差異而已.

一個 Media framework 可以分成以下四大類組件
  • Source:資料進來的 stream, 這裡只是一個很抽象的概念,可以是一個很簡單的 file source, 也可以是複雜一點的 http source, 或是更複雜的 rtsp, HLS或是 drm stream
  • Demux: 當一個 data stream 進來時, 他其實只是一個被包裝過的 container, decoder 無法直接讀取裡面的資訊, 因此 Demux 的功用主要是把這個 container 解開並且把裡面的 audio stream, video stream, 或是其他的 data 導引到對的位置
  • Decoder: 一般的 media file 如果未壓縮則會浪費掉大量的空間, 因此所有的 media file 都是壓縮過的, 然而 Video driver 和 audio driver 他們只能接受 raw data, 因此要把資料餵給他們前, 必須先經過 decoder 把資料先 decode 成 raw data 再餵給 driver, 然而因為在 arm 上 cpu 的能力較弱, 因此一般的晶片廠都還會再包一顆 dsp 來執行 decoder 的功用
  • Output:  把 decoder 解出來的 raw data 顯示出來, 如果是 video data 就交給 display driver 如果是 audio data 就交給 speaker 把 audio 播出來

2. Media application在介紹 Media framework 前先從 Media application 談起,  要寫 Media application 時先要了解 Media Player 的 state machine, Media application 所有的操作都必須遵循這個 state machine, 否則 Media  framework 就會跟你抱怨操作有問題, 並且 throw exception 讓你的 application crash.



3. Media framework
一般的 Media framework 分成以下四個部分


  • Media player java side: 會有這一層是因為所有的 application 都是用 java 寫成的,但是 media player 都是由 c++, 因此必須透過這一層再透過 java 的 jni 跟 native 的 code 溝通,另外這一層是 google open 出來的 api 介面, 因此盡量不要修改這一層, 以免造成 application 的執行錯誤
  • Media player native side: 這一層工作是透過 binder 跟 MediaPlayerService 溝通
  • Mediaplayer service: Media Player Service 有點類似大總管的角色, 就以 playback 的觀點來看media player service, 他主要的工作是選擇對的 player 和提供 audio sink, 把 application 提供的 View 交給 player
  • StagefrightPlayer/NuPlayer: StagefrightPlayer 是android 2.2至4.4.4時所用的 media player engine他主要負責撥放 local file 和 http streaming, 然而從 android 5.0 開始 google 已經捨棄不用了, NuPlayer 是 google 從4.0 開始引進的 player engine, 原本只負責 rtsp 和 HLS, 但是到了 android 5.0 開始他負責所有的播放.

4. NuPlayer
底下以 HLS 來解釋 NuPlayer, 對主要依靠著 Looper 和 Handler 來構成他的基礎


  • Looper/Handler:所有 application 層發出的 command 都是一個 Handler, 並且他會送到 ALooper 裡,再由 ALopper 把 Handler 拿出來執行;當底層有訊息要通知 application 時, 也是發出一個 Handler再把這個 Handler 取出來送給 Application.
  • Stream:再 NuPlayer 中引入了 Stream 的概念, 再 NuPlayer 中所講的 Stream 就很像我們最上面那張圖所講的 Source另外必須 NuPlayer 的 stream 只是抽象的概念, 他隱藏了各個 Streaming 的細節, 因此以 NuPlayer 的角度來講做 HLS 和 RTSP, local file, http 都是一樣的, 就是 read 檔案而已, 然而怎麼跟 server side 溝通的細節就隱藏在各自的子模組中, NuPlayer 不在意怎麼跟 Server 溝通, 只要各自的子模組都遵守 interface 就可以了
  • NuPlayer 實做了 render, 主要是用來將資料送給  Audio, display driver
  • NuPlayer 實做了 codec, 主要是定義一組介面好連接 HW codec 或是 software code, 並且將解碼出來的資料送給 render
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // get 需要的 UI 元件的 instance
        mPlayButton = (Button) findViewById(R.id.playbutton);
        mPauseButton = (Button) findViewById(R.id.pausebutton);
        mSkipButton = (Button) findViewById(R.id.skipbutton);
        mRewindButton = (Button) findViewById(R.id.rewindbutton);
        mStopButton = (Button) findViewById(R.id.stopbutton);
        mEjectButton = (Button) findViewById(R.id.ejectbutton);

        // 註冊相對應的 callback function, 當事件發生時, 由 framework 執行相對應的 callback function
        mPlayButton.setOnClickListener(this);
        mPauseButton.setOnClickListener(this);
        mSkipButton.setOnClickListener(this);
        mRewindButton.setOnClickListener(this);
        mStopButton.setOnClickListener(this);
        mEjectButton.setOnClickListener(this);
    }