Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android多媒體開發

Android多媒體開發

編輯:關於Android編程

一、什麼是多媒體

多媒體(duō méi tǐ) 的英文單詞是Multimedia,它由media和multi兩部分組成。一般理解為多種媒體的綜合

多媒體是計算機和視頻技術的結合,實際上它是兩個媒體;聲音和圖像,或者用現在的術語:音響和電視。

多媒體(Multimedia),在計算機系統中,組合兩種或兩種以上媒體的一種人機交互式信息交流和傳播媒體

使用的媒體包括文字、圖片、照片、聲音 (包含音樂、語音旁白、特殊音效)、動畫和影片,以及程式所提供的互動功能

多媒體是超媒體(Hypermedia)系統中的一個子集,而超媒體系統是使用超鏈接 (Hyperlink)構成的全球信息系統

全球信息系統是因特網上使用 TCP/IP 協議和 UDP/IP 協議

二、音樂播放器

Android 官方提供了MediaPlayer 核心類,用於播放音樂,其狀態流程如下圖所示。MediaPlayer 必須嚴格按照狀態圖操作,否則就會出現錯誤,這些錯誤都是底層拋出,嚴格按照狀態圖操作的話一般就不會出問題。

MediaPlayer,原生的API,可以播放音視頻,但是支持的格式比較少,實際開發中用的比較少,但是還是很有必要學習,熟悉API,因為Vitamio框架的API大部分跟原生的API是一樣的

1、MediaPlayer使用流程圖

這裡寫圖片描述

2、MediaPlayer核心方法

方法 說明 create() 播放本地res/raw/目錄下的資源 reset() 重置為初始狀態 setAudioStreamType() 設置音樂格式,例如:AudioManager.STREAM_MUSIC setDataSource() 設置音頻源,本地網絡資源均可 prepare() 播放前的准備工作 prepareAsync() 異步進行准備工作,播放網絡音頻的時候使用 start() 開始或恢復播放 pause() 暫停播放 stop() 停止播放 release() 釋放資源 getDuration() 獲取音樂最大長度(毫秒單位) getCurrentPosition() 獲取當前的播放進度 seekTo() 拖拽進度 setDisplay() 設置輸出畫面 setOnPreparedListener() 設置准備監聽

 
為了演示MediaPlayer 的使用,我們需要提前准備一個mp3 文件放到sdcard 中

需求:制作一個播放器,能夠播放/暫停/停止音樂文件,並且添加一個SeekBar(可以拖拽的ProgressBar),當音樂播放時SeekBar 也會不斷的跟新當前的進度,當用戶拖動SeekBar 時可以更改播放的進度

這裡寫圖片描述

布局文件<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">

代碼實現

public class MainActivity extends Activity implements OnSeekBarChangeListener {

        private SeekBar     sb;
        private MediaPlayer player;
        private int         duration;
        // 播放器的幾個狀態
        private static final int PLAYING  = 1;// 播放狀態
        private static final int PAUSING  = 2;// 暫停狀態
        private static final int STOPPING = 3;// 停止狀態
        private volatile     int CURRENT  = 0;// 當前狀態
        private Timer timer;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            sb = (SeekBar) findViewById(R.id.sb);
            //設置拖動監聽
            sb.setOnSeekBarChangeListener(this);
        }

        //播放
        public void play(View view) {
            if (player != null) {
                if (CURRENT == PLAYING) {
                    Toast.makeText(this, "音樂已經在播放了", Toast.LENGTH_SHORT).show();
                    return;
                } else if (CURRENT == PAUSING) {
                    player.start();
                    CURRENT = PLAYING;
                    return;
                }
            }
            try {
                //創建一個播放器對象
                player = new MediaPlayer();
                //給播放器設置音樂路徑
                player.setDataSource("/mnt/sdcard/test.mp3");
                //設置音樂格式
                player.setAudioStreamType(AudioManager.STREAM_MUSIC);
                //准備
                player.prepare();
                //獲取音樂最大長度(毫秒單位)
                duration = player.getDuration();
                //給SeekBar 設置最大值
                sb.setMax(duration);
                //音樂開始播放
                player.start();
                //設置當前的狀態為播放
                CURRENT = PLAYING;
                if (timer == null) {
                    //創建定時器
                    timer = new Timer();
                }
                /**
                 * 參數1:匿名內部類,相當於Runnable 類
                 * 參數2:第一次延時多長時間(毫秒)後執行,0 則代表立即執行
                 * 參數3:每隔多長時間(毫秒)執行一次
                 */
                timer.schedule(new TimerTask() {

                    @Override
                    public void run() {//該方法每1 秒被調用一次
                        if (CURRENT == PLAYING) {
                            runOnUiThread(new Runnable() {

                                @Override
                                public void run() {
                                    //雙重判斷,盡可能避免線程問題,因為該段代碼時在主線程中的,
                                    //第一次判斷是在子線程中進行的
                                    if (player != null && CURRENT == PLAYING) {
                                        //獲取當前的播放進度
                                        int currentPosition = player.getCurrentPosition();
                                        //設置給SeekBar
                                        sb.setProgress(currentPosition);
                                    }
                                }
                            });
                        }

                    }
                }, 0, 1000);

            } catch (Exception e) {
                e.printStackTrace();
                Toast.makeText(this, "音樂播放失敗" + e, 0).show();
            }
        }

        /**
         * 暫停
         */
        public void pause(View view) {
            if (player != null && CURRENT == PLAYING) {
                player.pause();
                CURRENT = PAUSING;
            }
        }

        /**
         * 停止
         */
        public void stop(View view) {
            if (player != null) {
                if (CURRENT == PLAYING || CURRENT == PAUSING) {
                    CURRENT = STOPPING;
                    //取消定時器
                    timer.cancel();
                    timer = null;
                    player.stop();
                    player.reset();
                    player.release();
                    player = null;
                    sb.setProgress(0);
                }
            }
        }

        /*
        * 拖動過程中回調多次
        */
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (player == null) {
                sb.setProgress(0);
            } else {
                player.seekTo(progress);
            }
        }

        /*
        * 開始拖動前回調一次
        */
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            if (player == null) {
                Toast.makeText(this, "音樂播放器還未開始", Toast.LENGTH_SHORT).show();
            }
        }

        /*
        * 結束拖動後回調一次
        */
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
            stop(null);
        }

    }

3、播放本地res/raw/目錄下的資源

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you

4、播放本地URI資源

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

5、播放網絡資源

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

6、異步准備

String url = "http://........"; // your URL here
        MediaPlayer mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setDataSource(url);
        mediaPlayer.prepareAsync();
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mp.start();
            }
        });

7、在後台Service異步執行播放任務

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    MediaPlayer mMediaPlayer;
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mMediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mMediaPlayer = ... // initialize it here
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

 public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...

        mMediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }

8、在手機睡眠時使用喚醒鎖

mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

//wifi鎖
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

//當暫停,不再需要網絡時釋放鎖
wifiLock.release();

9、在前台服務運行播放任務

這裡寫圖片描述 這裡寫圖片描述

String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
                new Intent(getApplicationContext(), MainActivity.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
                "Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);

10、處理音頻焦點

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
    AudioManager.AUDIOFOCUS_GAIN);

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // could not get audio focus.
}

requestAudioFocus() 方法的第一個參數是一個 AudioManager.OnAudioFocusChangeListener,當任何時候音頻焦點發生變化的時候,會回調 OnAudioFocusChangeListener的onAudioFocusChange()方法

class MyService extends Service
                implements AudioManager.OnAudioFocusChangeListener {
    // ....
    public void onAudioFocusChange(int focusChange) {
        // Do something based on focus change...
    }
}

11、播放完畢時手動釋放資源

public class MyService extends Service {
   MediaPlayer mMediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       if (mMediaPlayer != null) mMediaPlayer.release();
   }
}

三、Mp3文件簡介

ID3

一般是位於一個mp3文件的開頭或末尾的若干字節內,附加了關於該mp3的歌手,標題,專輯名稱,年代,風格等信息,該信息就被稱為ID3信息,ID3信息分為兩個版本,v1和v2版。 其中:v1版的ID3在mp3文件的末尾128字節,以TAG三個字符開頭,後面跟上歌曲信息。 v2版一般位於mp3的開頭,可以存儲歌詞,該專輯的圖片等大容量的信息。

V1與V2:

ID3V1記錄在MP3文件的末尾,長度固定 ID3V2就記錄在MP3文件的首部。 ID3V2一共有4個版本,但流行的播放軟件一般只支持第3版,既ID3v2.3。 對ID3V2的操作比ID3V1要慢。而且ID3V2結構比ID3V1的結構要復雜得多,但比ID3V1全面且可以伸縮和擴展。

四、視頻播放器

1、SurfaceView

對畫面的實時更新要求較高,重量級組件,可見時才創建 雙緩沖技術:內存中有兩個畫布,A畫布顯示至屏幕,B畫布在內存中繪制下一幀畫面,繪制完畢後B顯示至屏幕,A在內存中繼續繪制下一幀畫面 播放視頻也是用MediaPlayer,不過跟音頻不同,要設置顯示在哪個SurfaceView

2、使用MediaPlayer+SurfaceView 播放視頻

在該節中,視頻播放依然使用MediaPlayer 類,為了方便演示,我們直接使用本文中創建的工程,只需在布局文件添加SurfaceView 控件即可

布局文件



        

    

實現代碼

public class MainActivity extends Activity {
        private MediaPlayer player;
        static int currentPosition;
        private SurfaceView sv;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            sv = (SurfaceView) findViewById(R.id.sv);
            //拿到surfaceview的控制器
            final SurfaceHolder sh = sv.getHolder();

            //      Thread t = new Thread(){
            //          @Override
            //          public void run() {
            //              try {
            //                  sleep(200);
            //              } catch (InterruptedException e) {
            //                  e.printStackTrace();
            //              }
            //              runOnUiThread(new Runnable() {
            //                  @Override
            //                  public void run() {
            //                      MediaPlayer player = new MediaPlayer();
            //                      player.reset();
            //                      try {
            //                          player.setDataSource("sdcard/2.3gp");
            //                          player.setDisplay(sh);
            //                          player.prepare();
            //                          player.start();
            //                      } catch (Exception e) {
            //                          e.printStackTrace();
            //                      } 
            //                      
            //                  }
            //              });
            //              
            //          }
            //      };
            //      t.start();

            sh.addCallback(new Callback() {

                //surfaceView銷毀時調用
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                    //每次surfaceview銷毀時,同時停止播放視頻
                    if(player != null){
                        currentPosition = player.getCurrentPosition();
                        player.stop();
                        player.release();
                        player = null;
                    }

                }
                //surfaceView創建時調用
                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    //每次surfaceView創建時才去播放視頻
                    if(player == null){
                        player = new MediaPlayer();
                        player.reset();
                        try {
                            player.setDataSource("sdcard/2.3gp");
                            player.setDisplay(sh);
                            player.prepare();
                            player.start();
                            player.seekTo(currentPosition);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                }
                //surfaceView結構改變時調用
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

                }
            });
        }

    }

使用VideoView 播放視頻

VideoView 跟MediaPlayer 相比播放視頻步驟要簡單的多,因為VideoView 原生提供了播放,暫停、快進、快退、進度條等方法。使用起來要方便的很多

1、設置布局文件,布局文件比較簡單,因此這裡只給你VideoView 標簽

 

2、設置VideoView 的播放文件路徑和媒體控制器,調用start 方法即可播放媒體文件

//實例化VideoView 對象
vv = (VideoView) findViewById(R.id.vv);
//從界面獲取播放路徑
et_path = (EditText) findViewById(R.id.et_path);

//給VideoView 設置視頻路徑
vv.setVideoPath(et_path.getText().toString());
//設置VideoView 控制器,我們當前類實現了MediaPlayerControl 接口
vv.setMediaController(new MediaController(this));
//開始播放
 vv.start();
 //設置當前播放器窗口設置為焦點
 vv.requestFocus();

3、覆寫MediaPlayerControl 接口中的抽象方法

 @Override
    public void start() {
    }

    @Override
    public void pause() {

    }

    @Override
    public int getDuration() {
        return 0;
    }

    @Override

    public int getCurrentPosition() {
        return 0;
    }

    @Override
    public void seekTo(int pos) {
    }

    @Override
    public boolean isPlaying() {
        return false;
    }

    @Override
    public int getBufferPercentage() {
        return 0;
    }

    @Override
    public boolean canPause() {
        return false;
    }

    @Override
    public boolean canSeekBackward() {
        return false;
    }

    @Override
    public boolean canSeekForward() {
        return false;
    }

    @Override
    public int getAudioSessionId() {
        return 0;
    }


    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
        return super.onKeyMultiple(keyCode, repeatCount, event);
    }

注意:上面的方法都是回調方法,我們可以在這些方法裡面實現我們的業務邏輯。只有當我們給VideoView設置setMediaController 後控制器才會出現

這裡寫圖片描述

收音機

播放協議: MMS MMS(Microsoft Media Server protocol)是一種串流媒體傳送協議 ,android並不支持這種流媒體協議

引入Vitamo框架進行播放

核心類:io.vov.vitamio.MediaPlayer 操作:同系統的MediaPlayer,代碼編寫與播放網絡音樂相近

視頻處理

電影文件有很多基本的組成部分。首先,文件本身被稱為容器Container,容器的類型決定了信息被存放在文件中的位置。AVI和Quicktime就是容器的例子。接著,你有一組流,例如,你經常有的是一個音頻流和一個視頻流。(一個流只是一種想像出來的詞語,用來表示一連串的通過時間來串連的數據元素)。在流中的數據元素被稱為幀Frame。每個流是由不同的編碼器來編碼生成的。編解碼器描述了實際的數據是如何被編碼Coded和解碼DECoded的,因此它的名字叫做CODEC。接著從流中被讀出來的叫做包Packets。包是一段數據,它包含了一段可以被解碼成方便我們最後在應用程序中操作的原始幀的數據。

七個模塊分別為:讀文件模塊,解復用模塊 ,視頻解碼模塊,音頻解碼音頻,顏色空間轉換模塊,視頻顯示模塊,音頻播放模塊

粗略的分為五類,分別是 Source filer, Demux flter, Decoder filter, Color Space converter filter,Render filter,各類 filter的功能與作用簡述如下

Source filter

Source filter 源過濾器的作用是為下級 demux filter 以包的形式源源不斷的提供數據流。在通常情況下,我們有多種方式可以獲得數據流,一種是從本地文件中讀取,一種是從網上獲取,Sourcefilter 另外一個作用就是屏蔽讀本地文件和獲取網絡數據的差別,在下一級的 demux filter 看來,本地文件和網絡數據是一樣的。

Demux filter

解復用過濾器的作用是識別文件類型,媒體類型,分離出各媒體原始數據流,打上時鐘信息後送給下級 decoder filter。為識別出不同的文件類型和媒體類型,常規的做法是讀取一部分數據,然後遍歷解復用過濾器支持的文件格式和媒體數據格式,做匹配來確定是哪種文件類型,哪種媒體類型;有些媒體類型的原始數據外面還有其他的信息,比如時間,包大小,是否完整包等等。demux filter 解析數據包後取出原始數據,有些類型的媒體不管是否是完整包都立即送往下級 decoder filter,有些類型的媒體要送完整數據包,此時可能有一些數據包拼接的動作;當然時鐘信息的計算也是 demux filter 的工作內容,這個時鐘用於各媒體之間的同步。在本例中,AVI Splitter 是 Demux filter。

Decoder filter

解碼過濾器的作用就是解碼數據包,並且把同步時鐘信息傳遞下去。對視頻媒體而言,通常是解碼成 YUV 數據,然後利用顯卡硬件直接支持 YUV 格式數據 Overlay 快速顯示的特性讓顯卡極速顯示。YUV格式是一個統稱,常見的有 YV12,YUY2,UYVY 等等。有些非常古老的顯卡和嵌入式系統不支持 YUV 數據顯示,那就要轉換成 RGB 格式的數據,每一幀的每一個像素點都要轉換,分別計算 RGB 分量,並且因為轉換是浮點運算,雖然有定點算法,還是要耗掉相當一部分 CPU,總體上效率底下;對音頻媒體而言,通常是解碼成 PCM 數據,然後送給聲卡直接輸出。在本例中,AVI Decompress 和 ACM Warper 是 decoder filter。

Color space converter filter

顏色空間轉換過濾器的作用是把視頻解碼器解碼出來的數據轉換成當前顯示系統支持的顏色格式。通常視頻解碼器解碼出來的是 YUV 數據,PC 系統是直接支持 YUV 格式的,也支持 RGB 格式,有些嵌入式系統只支持 RGB 格式的。在本例中,視頻解碼器解碼出來的是 RGB8 格式的數據,Color space converter filter 把 RGB8 轉換成 RGB32 顯示。

Render filter

渲染過濾器的作用就是在適當的時間渲染相應的媒體,對視頻媒體就是直接顯示圖像,對音頻就是播放聲音。視音頻同步的策略方法有好幾種,其中最簡單的一種就是默認視頻和音頻基准時間相同,這時音頻可以不打時鐘信息,通過計算音頻的采樣頻率,量化 bit 數,聲道數等基本參數就知道音頻 PCM 的數據速率,按照這個速率往前播放即可;視頻必須要使用同步時鐘信息來決定什麼時候顯示。DirectShow 采用一個有序鏈表 ,把接收到的數據包放進有序鏈表中,啟動一個定時器,每次定時器時間到就掃描鏈表,比較時鐘信息,或者顯示相應的幀,或者什麼也不做,每次接收到新的數據幀,首先判斷時鐘信息,如果是歷史數據幀就丟棄,如果是將來顯示數據幀就進有序鏈表,如果當前時間幀就直接顯示。如此這樣,保持視頻和音頻在人體感覺誤差范圍內相對的動態同步。在本例中 VideoRender 和 Default DirectSound Device 是 Render filter,同時也是 Sink filter

JetPlayer

JetPlayer jetPlayer = JetPlayer.getJetPlayer();
jetPlayer.loadJetFile("/sdcard/level1.jet");
byte segmentId = 0;

// queue segment 5, repeat once, use General MIDI, transpose by -1 octave
jetPlayer.queueJetSegment(5, -1, 1, -1, 0, segmentId++);
// queue segment 2
jetPlayer.queueJetSegment(2, -1, 0, 0, 0, segmentId++);

jetPlayer.play();
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved