Android 學習筆記之SurfaceView的使用+如何實現視頻播放...


學習內容:

1.掌握Surface的使用...

2.Android中如何實現視頻播放...

 

1.SurfaceView類的使用

  在Android中,一般播放音頻時我們可以去使用Android提供的MediaPlayer類,但是想要播放視頻僅僅依靠MediaPlayer類是遠遠不夠的...這里還需要使用到一個SurfaceView這個組件來完成..為什么?因為像視頻和SD圖形等都需要迅速的更新...如果這個更新實在主線程內去完成,那么顯然是不合理的,因為一個視頻的播放,系統會首先確定視頻的格式,然后得到視頻的編碼..然后對編碼進行解碼,得到一幀一幀的圖像,最后在畫布上進行迅速更新...這就是視頻播放的機制...那么這個過程顯然我們需要在另外一個線程內部去完成...這樣主線程的其他內容,比如說其他的渲染操作,圖片加載什么的,就不會導致主線程阻塞...這樣我們就可以使用SurfaceView來完成...

  SurfaceView繼承了View類,也是屬於視圖的一部分...SurfaceView視圖內嵌了一個專門用於繪制的Surface...其實每一個Surface都在每一個窗口的后面,我們可以通過SurfaceView類來控制哪些Surface的內容可以顯示出來...其實說白了它的作用就是可以直接從內存或者是DMA硬件里獲取圖像數據...將這些圖像數據迅速的顯示出來,通過啟動另外一個線程可以迅速的完成畫面的更新操作,不會導致主線程阻塞...這個在游戲開發中也是起着非常重要的作用...

  SurfaceView使用雙緩沖機制,這個機制可以使SurfaceView同時對兩張圖片進行渲染操作...其實目的也是為了迅速更新圖片的顯示,第一個緩沖對這一幀進行解析,第二個會對下一幀進行解析...這樣就可以避免在上一幀的圖片顯示完成后,下一幀的圖片還沒有進行顯示的情況發生...這樣就可以流暢的播放一個視頻文件...

  SurfaceView的實現...

  首先需要一個類去繼承這個類,然后實現SurfaceHolder.Callback()接口...使用這個接口的目的就是在Android中,SurfaceView的雙緩沖機制是非常消耗資源的,因此Android規定,在SurfaceView可見的時候,SurfaceView會對文件進行解析並顯示,當其不可見時,要直接銷毀掉SurfaceView以節省資源...那么這個接口的目的就是形成這個過程...

需要重寫的方法

 (1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

     //在surface的大小發生改變時激發

 (2)public void surfaceCreated(SurfaceHolder holder){}

     //在創建時激發,一般在這里調用畫圖的線程。然后畫圖的工作開始...

 (3)public void surfaceDestroyed(SurfaceHolder holder) {}

     //銷毀時激發,一般在這里將畫圖的線程停止、釋放。

  這三個方法就形成了上述說的那種關系...在視頻的播放當中,它會結合MediaPlayer完成視頻的流暢播放..而在游戲的開發當中,它可以結合Canvas元素來完成游戲中的一些畫面迅速更新的操作...

  總而言之,SurfaceView就是一個繪圖的容器,它可以在不干擾主線程的情況下,調用另一個線程完成繪圖的操作,並迅速的更新圖像...以完成更好的顯示效果....

這里來兩個實例...一個是SurfaceView配合MediaPlayer完成視頻的播放,另一個是結合Canvas完成一個圖像在畫布上顯示出來...

1.結合Canvas完成圖像在畫布上的顯示...

  這里做了一個摩爾斯燈塔....什么是摩爾斯燈塔...這玩意其實在以前的戰爭中可以使用的到,這里有一個概念就是摩爾斯碼...這個碼就是將數字,字母等符號以"."和"-"的形式顯示出來就是對文字的一種加密操作...出了可以發報文的形式,還可以以信號燈的形式來發送密報,然后另一方根據對應的信息進行解析...扯遠了...

a .-
b -...
c -.-.
d -..
e .
f ..-.
g --.
h ....
i ..
j .---
k -.-
l .-..
m --
n -.
o ---
p .--.
q --.-
r .-.
s ...
t -
u ..-
v ...-
w .--
x -..-
y -.--
z --..
0 -----
1 .----
2 ..---
3 ...--
4 ....-
5 .....
6 -....
7 --...
8 ---..
9 ----.
. .-.-.-
- -....-
, ..--..
/ -..-
; -.-.-.
( -.--.
) -.--.
@ .--.-
* ...-.-
+ .-.-.
% .-...
! =---.
$ ...-..-
View Code

  這上面的就是摩爾斯碼...我寫在了txt文檔當中...為了方便...但是我們需要注意一個地方就是我們在讀取txt文檔的時候,一定要把txt文檔的對應內容拷貝到res/asset文件夾下面...或者是放在模擬器的內存卡中...千萬別放在本地文件...因為Android的資源加載全在res文件夾下面或者是內存卡中,本地文件在正常的java程序中是可以讀到的,但是在Android是一定讀不到的..這個我已經試過,被坑3小時...因此這點需要注意....

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_1"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
    <EditText 
         android:id="@+id/input"
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:hint=""/>
    <Button 
        android:id="@+id/translate"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/input"
        android:text="轉換"/>
    <TextView 
        android:id="@+id/show"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:layout_below="@id/translate"
        android:text="輸出"/>
    <EditText 
        android:id="@+id/output"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:layout_below="@id/show"
        android:enabled="false"/>
    <Button 
        android:id="@+id/send"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:layout_below="@id/output"
        android:text="發送"/>
</RelativeLayout>
View Code

  布局文件非常簡單..看看就行....然后就是如何使用SurfaceView和Canvas的結合使用....在沒有Canvas的基礎下,還是去腦補一下Canvas。。。

package com.example.mouse;

import java.util.HashMap;
import java.util.Scanner;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.EditText;
import android.widget.RelativeLayout;

public class MainActivity extends Activity implements View.OnClickListener {

    public static HashMap<String, String> map = new HashMap<String, String>();
    private EditText input;
    private EditText output;
    private char chars[];
    private RelativeLayout layout;
    private boolean flag = false;
    private boolean loop = false;
    int count;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        input = (EditText) findViewById(R.id.input);
        output = (EditText) findViewById(R.id.output);
        findViewById(R.id.translate).setOnClickListener(this);
        findViewById(R.id.send).setOnClickListener(this);
        layout= (RelativeLayout) findViewById(R.id.layout_1);//獲取布局的id...
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub

        switch (v.getId()) {
        case R.id.translate:
            try {
                Scanner in = new Scanner(getResources().getAssets().open(
                        "morce.txt"));//獲取資源文件morce.txt文件內部的內容...
                while (in.hasNextLine()) {
                    String str = in.nextLine();
                    String abc[] = str.trim().split("[\\p{Space}]+");
                    map.put(abc[0], abc[1]);//定義了一個HashMap<String,String>,以鍵值對的形式來保存內容...
                }
                map.put(" ", "/");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            String text = Mouse.morcecode(input.getText().toString(), map);//對我們輸入的字符進行匹配..來完成加密操作...
            output.setText(text);
            break;
        case R.id.send:
            if (output.getText() != null
                    && output.getText().toString().length() > 0) {
                chars = output.getText().toString().toCharArray();
                count = chars.length;
                LightView light = new LightView(MainActivity.this);
                layout.addView(light);//將畫布的繪畫內容加載到布局中....
            }
        }
    }

    class LightView extends SurfaceView {//定義一個類繼承SurfaceView類...

        SurfaceHolder holder;

        public LightView(Context context) {
            // TODO Auto-generated constructor stub
            super(context);
            holder = this.getHolder();//調用getHolder()來獲取SurfaceHolder對象..
            holder.addCallback(new SurfaceHolder.Callback() {//實現接口...

                class LightThread implements Runnable {//內部定義異步線程來完成繪畫操作...

                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        while (loop) {
                            if (count > 0) {
                                String s = String.valueOf(chars[chars.length
                                        - count]);
                                Canvas canvas = holder.lockCanvas(null);//鎖定畫布...
                                Paint paint = new Paint();
                                paint.setAntiAlias(true);//去掉鋸齒..
                                paint.setColor(Color.BLACK);//畫布的顏色為黑色...
                                canvas.drawRect(0, 0, 480, 480, paint);
                                //下面這個是對報文的每一個字節解析以信號燈的形式顯示出來...
                                if (flag) {
                                    paint.setColor(Color.BLACK);
                                } else {
                                    if (s.equalsIgnoreCase(".")) {
                                        sleep(2);
                                        paint.setColor(Color.YELLOW);
                                    } else if (s.equalsIgnoreCase("-")) {
                                        sleep(4);
                                        paint.setColor(Color.YELLOW);
                                    } else if (s.equalsIgnoreCase(" ")) {
                                        sleep(2);
                                        paint.setColor(Color.BLACK);
                                    } else if (s.equalsIgnoreCase("/")) {
                                        sleep(2);
                                        paint.setColor(Color.BLACK);
                                    }
                                    count--;
                                }
                                canvas.drawCircle(250.0f, 200.0f, 100, paint);//畫出一個圓用來顯示信號...
                                flag = !flag;
                                holder.unlockCanvasAndPost(canvas);//解除鎖定
                            }
                        }
                    }

                    public void sleep(int time) {
                        try {
                            Thread.sleep(time * 80);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }

                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                    // TODO Auto-generated method stub
                    loop = false;
                }

                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    // TODO Auto-generated method stub
                    new Thread(new LightThread()).start();
                }

                @Override
                public void surfaceChanged(SurfaceHolder holder, int format,
                        int width, int height) {
                    // TODO Auto-generated method stub

                }
            });
            loop = true;
        }
    }
}

這樣就完成了對輸入的文本以摩爾斯碼的形式進行加密操作....很簡單的一個小東西....

2.SurfaceView與MediaPlayer的結合操作....

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >
    <TextView 
        android:id="@+id/info"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:text="等待播放"/>
    <LinearLayout 
        android:orientation="horizontal"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content">
        <ImageButton 
            android:id="@+id/play"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:src="@drawable/play"/>
        <ImageButton 
            android:id="@+id/pause"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:src="@drawable/pause"/>
        <ImageButton 
            android:id="@+id/stop"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:src="@drawable/stop"/>
    </LinearLayout>
    <SeekBar 
        android:id="@+id/seekbar"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"/>
    <SurfaceView 
        android:id="@+id/surface_1"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"/>
</LinearLayout>

一個簡單的布局文件...三個圖片按鈕...一個進度條和一個表面視圖....

package com.example.exam7_2;



import java.io.IOException;

import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends Activity implements View.OnClickListener, SeekBar.OnSeekBarChangeListener{
    private MediaPlayer media;
    private boolean playflag=true;
    private boolean pauseflag=false;
    private SeekBar seekbar=null;
    private TextView tv;
    private SurfaceView suf=null;
    private SurfaceHolder sufh=null;
    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv=(TextView) findViewById(R.id.info);
        findViewById(R.id.play).setOnClickListener(this);
        findViewById(R.id.pause).setOnClickListener(this);
        findViewById(R.id.stop).setOnClickListener(this);
        suf=(SurfaceView) findViewById(R.id.surface_1);
        sufh=suf.getHolder(); //獲取SurfaceHolder。。。
        seekbar=(SeekBar) findViewById(R.id.seekbar);
        sufh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//設置SurfaceView的類型...
        media=new MediaPlayer();
        try {
            media.setDataSource("/sdcard/test.3gp");//這里需要把想要播放的文件拷貝到模擬器的sd卡中去...
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        
    }
    private class updateseekbar extends AsyncTask<Integer, Integer, String>{//AsyncTask的使用...

        protected void onPostExecute(String result){}
        protected void onProgressUpdate(Integer...progress){
            seekbar.setProgress(progress[0]);
        }
        @Override
        protected String doInBackground(Integer... params) {
            // TODO Auto-generated method stub
            while(MainActivity.this.playflag){
                try {
                    Thread.sleep(params[0]);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                this.publishProgress(MainActivity.this.media.getCurrentPosition());
            }
            return null;
        }
        
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch(v.getId()){
        case R.id.play:
        {
            MainActivity.this.media.setOnCompletionListener(new OnCompletionListener() {
                
                @Override
                public void onCompletion(MediaPlayer arg0) {
                    // TODO Auto-generated method stub
                    playflag=false;
                    media.release();
                }
            });
            seekbar.setMax(MainActivity.this.media.getDuration());
            updateseekbar update=new updateseekbar();
            //執行者execute
            update.execute(1000);
            seekbar.setOnSeekBarChangeListener(this);
            if(MainActivity.this.media!=null){
                MainActivity.this.media.stop();
            }
            try {
                MainActivity.this.media.prepare();
                MainActivity.this.media.start();
                tv.setText("正在播放文件...");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                tv.setText("文件出現異常...");
                e.printStackTrace();
            } 
            break;
        }
        case R.id.pause:
            if(media!=null){
                if(pauseflag){
                    media.start();
                    pauseflag=false;
                }else{
                    media.pause();
                    pauseflag=true;
                }
            }
            break;
        case R.id.stop:
            if(media!=null){
                media.stop();
                tv.setText("停止播放文件...");
            }
            break;
        }
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress,
            boolean fromUser) {
        // TODO Auto-generated method stub
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        // TODO Auto-generated method stub
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        // TODO Auto-generated method stub
        media.seekTo(seekbar.getProgress());
    }
   
}

這樣就完成了MediaPlayer和SurfaceView的配合使用...完成了視頻的播放...同時添加了一個進度條來控制播放的進度...

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM