Mjpeg視頻流加載

由于Mjpeg流是通過一幀一幀的圖片發(fā)送來達(dá)到視頻顯示的效果,所以我們用類似網(wǎng)絡(luò)加載圖片方式加載,但是由于這個流是一直在發(fā)送的,我們需要知道這一幀的圖片流的開始位置和結(jié)束位置喉钢,才能顯示這一幀的圖片,原理就是這樣的良姆,其他就直接上代碼了

這個類是網(wǎng)上找到的肠虽,搜android加載mjpeg流基本可以搜到,基本都有說明

package com.stereo.video.utils.mjpeg;

/**
 * Created by Administrator on 2018/8/22 0022.
 */

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Properties;

/**
 * 該類繼承了DataInputStream實現(xiàn)了Serializable接口
 * 1. 實例化流,獲取初始化流和關(guān)閉實例流的方法
 * 2. 一個構(gòu)造函數(shù)
 * 3. 一個根據(jù)幀數(shù)據(jù)大小獲得位圖方法
 */
public class MjpegInputStream extends DataInputStream implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    /**
     * 用UE打開發(fā)現(xiàn) 每一個jpg格式的圖片 開始兩字節(jié)都是 0xFF,0xD8
     */
    private final byte[] SOI_MARKER = {(byte) 0xFF, (byte) 0xD8};
    private final byte[] EOF_MARKER = { (byte) 0xFF, (byte) 0xD9 };
    /**
     * 表示服務(wù)器發(fā)給客戶端的一幀數(shù)據(jù)的長度
     */
    private final String CONTENT_LENGTH = "Content-length";
    private final static int HEADER_MAX_LENGTH = 100;
    private final static int FRAME_MAX_LENGTH = 40000 + HEADER_MAX_LENGTH;
    private int mContentLength = -1;
    private static MjpegInputStream mis = null;

    /**
     * 調(diào)用該類的構(gòu)造方法 創(chuàng)建MjpegInputStream流
     *
     * @param is
     */
    public static void initInstance(InputStream is) {
        if (mis == null)
            mis = new MjpegInputStream(is);

    }

    /**
     * 獲得創(chuàng)建的mjpegInputsteam流
     *
     * @return
     */
    public static MjpegInputStream getInstance() {
        if (mis != null)
            return mis;

        return null;
    }

    /**
     * 因為mpjeginputstream繼承了datainputstream
     * 所以可以調(diào)用mpjeginputstream的關(guān)閉流方法
     */
    public static void closeInstance() {
        try {
            mis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mis = null;
    }

    private MjpegInputStream(InputStream in) {
        super(new BufferedInputStream(in, FRAME_MAX_LENGTH));
    }

    /**
     * 在數(shù)據(jù)流里面找SOI_MARKER={(byte)0xFF,(byte) 0xD8}
     * 所有對IO流的操作都會拋出異常
     *
     * @param in
     * @param sequence
     * @return
     * @throws IOException
     */
    private int getEndOfSeqeunce(DataInputStream in, byte[] sequence)
            throws IOException {
        int seqIndex = 0;
        byte c;
        for (int i = 0; i < FRAME_MAX_LENGTH; i++) {// 0 1 2 3
            c = (byte) in.readUnsignedByte();
            if (c == sequence[seqIndex]) {
                seqIndex++;
                if (seqIndex == sequence.length)//2
                    return i + 1;//3
            } else
                seqIndex = 0;
        }
        return -1;
    }

    /**
     * 此方法功能是找到索引0xFF,0XD8在字符流的位置
     * 整個數(shù)據(jù)流形式:http頭信息 幀頭(0xFF 0xD8) 幀數(shù)據(jù) 幀尾(0xFF 0xD9)
     * 1、首先通過0xFF 0xD8找到幀頭位置
     * 2玛追、幀頭位置前的數(shù)據(jù)就是http頭税课,里面包含Content-Length闲延,這個字段指示了整個幀數(shù)據(jù)的長度
     * 3、幀頭位置后面的數(shù)據(jù)就是幀圖像的開始位置
     *
     * @param in
     * @param sequence
     * @return
     * @throws IOException
     */
    private int getStartOfSequence(DataInputStream in, byte[] sequence)
            throws IOException {
        int end = getEndOfSeqeunce(in, sequence);
        return (end < 0) ? (-1) : (end - sequence.length);
    }

    /**
     * 從http的頭信息中獲取Content-Length韩玩,知道一幀數(shù)據(jù)的長度
     *
     * @param headerBytes
     * @return
     * @throws IOException
     * @throws NumberFormatException
     */
    private int parseContentLength(byte[] headerBytes) throws IOException,
            NumberFormatException {
        /**
         * 根據(jù)字節(jié)流創(chuàng)建ByteArrayInputStream流
         * Properties是java.util包里的一個類垒玲,它有帶參數(shù)和不帶參數(shù)的構(gòu)造方法,表示創(chuàng)建無默認(rèn)值和有默認(rèn)值的屬性列表
         * 根據(jù)流中的http頭信息生成屬性文件啸如,然后找到屬性文件CONTENT_LENGTH的value侍匙,這就找到了要獲得的幀數(shù)據(jù)大小
         * 創(chuàng)建一個 ByteArrayInputStream,使用 headerBytes作為其緩沖區(qū)數(shù)組
         */
        ByteArrayInputStream headerIn = new ByteArrayInputStream(headerBytes);
        Properties props = new Properties();/*創(chuàng)建一個無默認(rèn)值的空屬性列表*/
        props.load(headerIn);/*從輸入流中生成屬性列表(鍵和元素對)叮雳。*/
        String parse = props.getProperty(CONTENT_LENGTH);   //這個位置如果錯誤記得看一下想暗,注意大小寫對比
        return Integer.parseInt(parse);/*用指定的鍵在此屬性列表中搜索屬性。*/
    }

    /**
     * @return
     * @throws IOException
     */
    public Bitmap readMjpegFrame() throws IOException {
        mark(FRAME_MAX_LENGTH);/*流中當(dāng)前的標(biāo)記位置*/
        int headerLen = getStartOfSequence(this, SOI_MARKER);
        reset();/*將緩沖區(qū)的位置重置為標(biāo)記位置*/
        byte[] header = new byte[headerLen];

        readFully(header);/*會一直阻塞等待帘不,直到數(shù)據(jù)全部到達(dá)(數(shù)據(jù)緩沖區(qū)裝滿)*/
            mContentLength = parseContentLength(header);// ?
        /**
         * 根據(jù)幀數(shù)據(jù)的大小創(chuàng)建字節(jié)數(shù)組
         */
        byte[] frameData = new byte[mContentLength];
        readFully(frameData);
        /**
         * 根據(jù)不同的源(file说莫,stream,byte-arrays)創(chuàng)建位圖
         * 把輸入字節(jié)流流轉(zhuǎn)為位圖
         */
        Bitmap bitmap =
                BitmapFactory.decodeStream(new ByteArrayInputStream(frameData));/*方便查看圖片是否解析出來*/

        return bitmap;
    }

}

這個是調(diào)用后臺接口地址寞焙,url就是 mjpeg的http地址储狭,記得子線程調(diào)用

    private Bitmap getImageBitmap(String url) {
        URL imgUrl = null;
        Bitmap bitmap = null;
        try {
            Log.v("bitmapfactory", "URL");
            imgUrl = new URL(url);
            conn = (HttpURLConnection) imgUrl.openConnection();
            conn.setDoInput(true);
            conn.connect();
            Log.v("bitmapfactory", "connect");
            InputStream inputStream = conn.getInputStream();
            Log.v("bitmapfactory", "getInputStream:");
            BufferedInputStream bis = new BufferedInputStream(inputStream);
            DataInputStream dis= new DataInputStream(bis);
            MjpegInputStream.initInstance(dis);
            MjpegInputStream mjpegInputStream = MjpegInputStream.getInstance();
//            Bitmap bmp = mjpegInputStream.readMjpegFrame();
            Message msg = new Message();
            msg.what = 1;
            msg.obj = mjpegInputStream;
            handler.sendMessage(msg);

        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            Log.v("bitmapfactory:", "MalformedURLException");
            e.printStackTrace();
        } catch (IOException e) {
            Log.v("bitmapfactory:", "IOException");
            if (conn != null) {
                conn.disconnect();
            }
            e.printStackTrace();
        }
        return bitmap;

    }

然后通過handler接收到流然后傳入流,然后開始就可以了

 mjpegView.setSource((MjpegInputStream) msg.obj);
                    mjpegView.startPlay();

MjpegView捣郊,這個也是網(wǎng)上的代碼辽狈,如果需要自己拿到Bitamap可以直接在調(diào)用網(wǎng)絡(luò)接口位置調(diào)用流解析代碼,就可以拿到bitmap呛牲,然后bitmap設(shè)置到其他位置刮萌,MjpegView也是調(diào)用解析流的工具類拿到bitmap然后繪制

package com.stereo.video.utils.mjpeg;

/**
 * Created by Administrator on 2018/8/22 0022.
 */

import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 此類繼承了SurfaceView實現(xiàn)了SurfaceHolder.Callback接口
 * SurfaceView是視圖類(view)的繼承類,這個視圖里內(nèi)嵌入了一個專門用于繪制的Surface    娘扩,可以控制這個Surface的格式和尺寸
 * SurfaceView控制這個Surface的繪制位置
 * surface是縱深排序(Z-ordered)的着茸,這表明它總在自己所在窗口的后面。surfaceview提供了一個可見區(qū)域
 * 只有在這個可見區(qū)域內(nèi) 的surface部分內(nèi)容才可見琐旁,可見區(qū)域外的部分不可見涮阔。surface的排版顯示受到視圖層級關(guān)系的影響
 * 它的兄弟視圖結(jié)點會在頂端顯示,這意味者 surface的內(nèi)容會被它的兄弟視圖遮擋灰殴,這一特性可以用來放置遮蓋物(overlays)(例如敬特,文本和按鈕等控件)
 * 可以通過SurfaceHolder接口訪問這個surface,getHolder()方法可以得到這個接口
 * surfaceview變得可見時    牺陶,surface被創(chuàng)建擅羞;surfaceview隱藏前,surface被銷毀义图;這樣能節(jié)省資源减俏。如果你要查看 surface被創(chuàng)建和銷毀的時機(jī)
 * 可以重載surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)
 * surfaceview的核心在于提供了兩個線程:UI線程和渲染線程,這里應(yīng)注意:
 * 1> 所有SurfaceView和SurfaceHolder.Callback的方法都應(yīng)該在UI線程里調(diào)用碱工,一般來說就是應(yīng)用程序主線程娃承,渲染線程所要訪問的各種變量應(yīng)該作同步處理奏夫。
 * 2> 由于surface可能被銷毀,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之間有效历筝,
 * 所以要確保渲染線程訪問的是合法有效的surface
 * 整個過程:繼承SurfaceView并實現(xiàn)SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()獲得SurfaceHolder對象(Surface控制器)
 * ---->SurfaceHolder.addCallback(callback)添加回調(diào)函數(shù)---->SurfaceHolder.lockCanvas()獲得Canvas對象并鎖定畫布
 * ----> Canvas繪畫 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)結(jié)束鎖定畫圖酗昼,并提交改變,將圖形顯示梳猪。
 */
public class MjpegView extends SurfaceView implements SurfaceHolder.Callback {
    /*fps顯示位置*/
    public final static int POSITION_UPPER_LEFT = 9;
    public final static int POSITION_UPPER_RIGHT = 3;
    public final static int POSITION_LOWER_LEFT = 12;
    public final static int POSITION_LOWER_RIGHT = 6;
    /*圖像顯示模式*/
    public final static int STANDARD_MODE = 1;//標(biāo)準(zhǔn)尺寸
    public final static int KEEP_SCALE_MODE = 4;//保持寬高比例
    public final static int FULLSCREEN_MODE = 8;//全屏

    private Context mContext = null;
    private MjpegViewThread mvThread = null;
    private MjpegInputStream mIs = null;
    private Paint overlayPaint = null;//用于fps涂層繪畫筆
    private boolean bIsShowFps = true;
    private boolean bRun = false;
    private boolean bsurfaceIsCreate = false;
    private int overlayTextColor;
    private int overlayBackgroundColor;
    private int ovlPos;
    private int dispWidth;//MjpegView的寬度
    private int dispHeight;//MjpegView的高度
    private int displayMode;//覆蓋模式

    public MjpegView(Context context) {
        super(context);
        init(context);
    }

    /**
     * 因為在res/layout目錄下的main.xml中作為自定義的控件使用了這個類麻削,所以需要給此類提供帶有屬性形參的構(gòu)造函數(shù)
     * 當(dāng)在MainActivity通過ID找到這自定義的控件時,該構(gòu)造函數(shù)將被調(diào)用春弥,所以將該構(gòu)造函數(shù)設(shè)為public
     *
     * @param context
     * @param attrs
     */
    public MjpegView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     * 類的私有方法
     * 1.獲得Surface控制器呛哟,為Surface控制器添加回調(diào)接口
     * 2.新建渲染線程MjpegViewThread
     * 3.新建覆蓋畫筆,設(shè)置文本的對齊方式匿沛、文本長度扫责、字體劳跃、畫筆文本顏色舅逸、畫筆背景
     * 4.設(shè)置覆蓋動態(tài)文本的覆蓋位置 //如果你只需要實現(xiàn)監(jiān)控畫面的功能膨桥,3和4步可以省略
     * 5.設(shè)置MjpegView顯示模式
     *
     * @param context
     */
    private void init(Context context) {
        mContext = context;
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        mvThread = new MjpegViewThread(holder, context);
        setFocusable(true);
        overlayPaint = new Paint();
        overlayPaint.setTextAlign(Paint.Align.LEFT);
        overlayPaint.setTextSize(12);
        overlayPaint.setTypeface(Typeface.DEFAULT);

        overlayTextColor = Color.RED;
        overlayBackgroundColor = Color.TRANSPARENT;
        ovlPos = MjpegView.POSITION_UPPER_RIGHT;
        displayMode = MjpegView.KEEP_SCALE_MODE;

    }

    /**
     * Surface的任何結(jié)構(gòu)性結(jié)構(gòu)性的改變(如格式沸枯,大小)將激發(fā)此方法
     * 主要調(diào)用渲染線程的setSurfaceSize來設(shè)置Surface的寬和高
     */
    public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
        mvThread.setSurfaceSize(w, h);
    }

    /**
     * Surface被銷毀之前將激發(fā)此方法,這里只設(shè)置標(biāo)記位畔柔,表示Surface“被銷毀了”
     */
    public void surfaceDestroyed(SurfaceHolder holder) {
        bsurfaceIsCreate = false;
    }

    /**
     * Surface被第一次創(chuàng)建后將激發(fā)此方法仔戈,這里只設(shè)置標(biāo)記位柠横,表示Surface“被創(chuàng)建了”
     */
    public void surfaceCreated(SurfaceHolder holder) {
        bsurfaceIsCreate = true;
    }

    /**
     * setFps推姻,getFps腿准,set source都在MaiActivity使用
     *
     * @param b
     */
    public void setFps(boolean b) {
        bIsShowFps = b;
    }

    public boolean getFps() {
        return bIsShowFps;
    }

    public void setSource(MjpegInputStream source) {
        mIs = source;
    }

    /**
     * 開始播放線程
     * 設(shè)置標(biāo)記,表示“Surface被創(chuàng)建了”拾碌,然后調(diào)用渲染線程的的run方法啟動渲染
     */
    public void startPlay() {
        if (mIs != null) {
            bRun = true;
            mvThread.start();
        }
    }

    /**
     * 停止播放線程
     * 1.先設(shè)置標(biāo)記,表示"停止播放"
     * 2.等待播放線程的退出
     * 3.關(guān)閉輸入流
     */
    public void stopPlay() {
        bRun = false;
        boolean retry = true;
        while (retry) {
            try {
                mvThread.join();
                retry = false;
            } catch (InterruptedException e) {
            }
        }

        //線程停止后關(guān)閉Mjpeg流(很重要)
        mIs.closeInstance();
    }

    /**
     * mjpegview的獲取位圖方法街望,調(diào)用渲染線程的獲取位圖方法
     *
     * @return
     */
    public Bitmap getBitmap() {
        return mvThread.getBitmap();
    }

    /**
     * 設(shè)置顯示模式校翔,在MainActivity的initview調(diào)用
     *
     * @param s
     */
    public void setDisplayMode(int s) {
        displayMode = s;
    }

    /**
     * 既然有設(shè)置顯示模式,就應(yīng)該也有獲得顯示模式灾前,這是java在設(shè)置方法方面的風(fēng)格
     *
     * @return
     */
    public int getDisplayMode() {
        return displayMode;
    }

    /**
     * 此渲染線程類在主類上是重點防症,應(yīng)該重點掌握
     *
     * @author Administrator
     */
    public class MjpegViewThread extends Thread {
        private SurfaceHolder mSurfaceHolder = null;
        private int frameCounter = 0;
        private long start = 0;
        private Canvas c = null;
        private Bitmap overlayBitmap = null;
        private Bitmap mjpegBitmap = null;
        private PorterDuffXfermode mode = null;

        /**
         * 用一個變量來保存?zhèn)鬟M(jìn)來的surfaceHolder
         * 新建一個目的圖層和覆蓋圖層的相交模式,mjpegview為目的圖層哎甲,覆蓋圖層為右上角的動態(tài)"文本"
         * mode在calculateFps方法里使用
         *
         * @param surfaceHolder:Surfaceview控制器
         * @param context                      : 上下文環(huán)境
         */
        public MjpegViewThread(SurfaceHolder surfaceHolder, Context context) {
            mSurfaceHolder = surfaceHolder;
            mode = new PorterDuffXfermode(PorterDuff.Mode.DST_OVER);/*相交時動態(tài)文本覆蓋mjpegview*/
        }

        public Bitmap getBitmap() {
            return mjpegBitmap;
        }

        /**
         * 計算圖像尺寸
         *
         * @param bmw bitmap寬
         * @param bmh bitmap高
         * @return 圖像矩陣
         */
        private Rect destRect(int bmw, int bmh) {
            int tempx;
            int tempy;
            /**
             * 顯示模式只會在全屏和半屏模式之間切換蔫敲,根本不會進(jìn)入STANDARD_MODE模式,故下面的if分支可以去掉
             */
            if (displayMode == MjpegView.STANDARD_MODE) {
                tempx = (dispWidth / 2) - (bmw / 2);
                tempy = (dispHeight / 2) - (bmh / 2);
                return new Rect(tempx, tempy, bmw + tempx, bmh + tempy);
            }
            /**
             * 一開始炭玫,程序處于KEEP_SCALE_MODE模式奈嘿,表示半屏顯示畫面
             */
            if (displayMode == MjpegView.KEEP_SCALE_MODE) {
                float bmasp = (float) bmw / (float) bmh;
                bmw = dispWidth;
                bmh = (int) (dispWidth / bmasp);/*寬是手機(jī)屏幕的一半*/
                if (bmh > dispHeight) {
                    bmh = dispHeight;
                    bmw = (int) (dispHeight * bmasp);
                }
                tempx = (dispWidth / 2) - (bmw / 2);
                tempy = (dispHeight / 2) - (bmh / 2);
                /**
                 * Rect(左邊,頂邊吞加,右邊裙犹,下邊)尽狠,功能是繪制一個特定坐標(biāo)的矩形
                 * 簡單說就是左上角坐標(biāo)為(0,0),右下角坐標(biāo)為(bmw叶圃,bmh)
                 */
                return new Rect(0, 0, bmw + 0, bmh + 0);
            }
            /**
             * 如果顯示模式為全屏袄膏,則全屏顯示畫面
             * dispWidth和dispHeight在下面的setSurfaceSize方法使用,它們表示mjpegview的寬和高
             */
            if (displayMode == MjpegView.FULLSCREEN_MODE)
                return new Rect(0, 0, dispWidth, dispHeight);
            return null;
        }

        /**
         * 當(dāng)mjpegview發(fā)生任何結(jié)構(gòu)性的改變時掺冠,將激發(fā)此方法沉馆,前面也提到,渲染線程使用的各種變量需做同步處理
         * synchronized內(nèi)的就是同步代碼塊德崭,為了防止線程之間對臨界資源的競爭
         *
         * @param width
         * @param height
         */
        public void setSurfaceSize(int width, int height) {
            synchronized (mSurfaceHolder) {
                dispWidth = width;
                dispHeight = height;
            }
        }

        /**
         * 此方法被calculateFps使用斥黑,calculateFps又被渲染線程的run方法使用
         * 功能是返回一個位圖
         *
         * @param p:覆蓋"文本"用的畫筆
         * @param text:要繪制的字符  如:幀
         * @return bm
         */
        private Bitmap makeFpsOverlay(Paint p, String text) {
            int nWidth, nHeight;

            Rect b = new Rect();
            //int  a = b.left ;
            /**
             * 功能是獲得從原點開始,字符圍繞的最小的矩形
             * text:字符
             * 0:表示第一個字符
             * text.length:測量的最后一個字符
             * b:用于存放獲得的字符矩形
             * 獲得了text的邊界后就可以得到矩形的寬和高
             */
            p.getTextBounds(text, 0, text.length(), b);
            nWidth = b.width() + 2;
            nHeight = b.height() + 2;
            /**
             * 每一個像素4字節(jié)接癌,根據(jù)上面獲得的寬和高返回一個位圖
             */
            Bitmap bm = Bitmap.createBitmap(nWidth, nHeight,
                    Bitmap.Config.ARGB_8888);
            /**
             * Canvas :畫布心赶,這是圖像處理的基本單元
             * 畫圖時,需要4個重要的元素:
             * 1.操作像素的位圖
             * 2.繪圖到位圖的畫布
             * 3.矩形
             * 4. 描述顏色和繪制風(fēng)格的畫筆
             * Canvas(bm):構(gòu)造出一個要繪制到位圖的畫布
             */
            Canvas c = new Canvas(bm);
            /**
             * Paint類介紹
             * Paint即畫筆缺猛,在繪圖過程中起到了極其重要的作用缨叫,畫筆主要保存了顏色,
             * 樣式等繪制信息荔燎,指定了如何繪制文本和圖形耻姥,畫筆對象有很多設(shè)置方法,
             * 大體上可以分為兩類有咨,一類與圖形繪制相關(guān)琐簇,一類與文本繪制相關(guān)。
             *
             * 1.圖形繪制
             * setColor(int color);
             * 設(shè)置繪制的顏色座享,使用顏色值來表示婉商,該顏色值包括透明度和RGB顏色。
             * setDither(boolean dither);
             * setXfermode(Xfermode xfermode);
             * 設(shè)置圖形重疊時的處理方式渣叛,如合并丈秩,取交集或并集,經(jīng)常用來制作橡皮的擦除效果
             *
             * 2.文本繪制
             * setFakeBoldText(boolean fakeBoldText);
             * 模擬實現(xiàn)粗體文字淳衙,設(shè)置在小字體上效果會非常差
             * setSubpixelText(boolean subpixelText);
             * 設(shè)置該項為true蘑秽,將有助于文本在LCD屏幕上的顯示效果
             *
             * setTextAlign(Paint.Align align);
             * 設(shè)置繪制文字的對齊方向
             * setTextSize(float textSize);
             * 設(shè)置繪制文字的字號大小
             * setTypeface(Typeface typeface);
             * 設(shè)置Typeface對象,即字體風(fēng)格箫攀,包括粗體肠牲,斜體以及襯線體,非襯線體等
             */

            p.setColor(overlayBackgroundColor);// 背景顏色
            c.drawRect(0, 0, nWidth, nHeight, p);/*繪制矩形*/
            p.setColor(overlayTextColor);// 文字顏色
            /**
             * 畫布的繪制文字方法
             * test:要繪制的字符
             * -b.left:字符起始位置的x坐標(biāo)靴跛,這里是矩形的左邊
             * (nHeight / 2) - ((p.ascent() + p.descent()) / 2) + 1:字符起始位置的y坐標(biāo)
             * p:用到的畫筆
             * 關(guān)于涉及的矩形屬性可看博客  http://mikewang.blog.51cto.com/3826268/871765
             */
            c.drawText(text, -b.left + 1,
                    (nHeight / 2) - ((p.ascent() + p.descent()) / 2) + 1, p);

            return bm;
        }

        /**
         * 重頭戲
         * 如果線程是運行的缀雳,SurfaceView也創(chuàng)建了的
         * 則鎖定畫布com/mjpeg/io/MjpegInputStream.java中的readMjpegFrame方法獲得mjpeg視頻流的內(nèi)容
         * mjpeg視頻的內(nèi)容就是類位圖,然后根據(jù)類位圖繪制矩形梢睛,再繪制相應(yīng)的位圖俏险,這個位圖才是我們需要的
         * 如果設(shè)置了幀率文本严拒,就在mjpegview上覆蓋,最后解鎖畫布
         */
        public void run() {
            start = System.currentTimeMillis();
            Rect destRect;
            Paint p = new Paint();
            //        String fps = "";
            while (bRun) {
                if (bsurfaceIsCreate) {
                    c = mSurfaceHolder.lockCanvas();
                    try {
                        mjpegBitmap = mIs.readMjpegFrame();/*調(diào)用Inputstrean的方法*/
                        if (mjpegBitmap == null) {
                            Log.v("mjpegBitmap","mjpegBitmap is null");
                            continue;
                        }
                        /*同步圖像的寬高設(shè)置*/
                        synchronized (mSurfaceHolder) {
                            destRect = destRect(mjpegBitmap.getWidth(),
                                    mjpegBitmap.getHeight());
                        }
                        /**
                         * 當(dāng)主activity點擊相冊和設(shè)置跳轉(zhuǎn)時竖独,Surfaceview被銷毀裤唠,此時c將為空
                         */
                        if (c != null) {
                            c.drawPaint(new Paint());
                            c.drawBitmap(mjpegBitmap, null, destRect, p);
                            if (bIsShowFps)
                                calculateFps(destRect, c, p);
                            mSurfaceHolder.unlockCanvasAndPost(c);
                        }
                    } catch (IOException e) {
                    }
                } else {
                    try {
                        Thread.sleep(500);//線程休眠,讓出調(diào)度
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        /**
         * 使用前面的方法莹痢,繪制出“顯示幀率”文本种蘸,效果為"i幀",i自增
         *
         * @param destRect
         * @param c
         * @param p
         */
        public void calculateFps(Rect destRect, Canvas c, Paint p) {
            int width;
            int height;
            String fps;

            p.setXfermode(mode);/* 設(shè)置兩個畫面相交時的模式*/
            if (overlayBitmap != null) {
                /**
                 * 計算好文本的寬和高
                 * 然后調(diào)用畫布的繪制位圖方法繪圖
                 */
                height = ((ovlPos & 1) == 1) ? destRect.top
                        : destRect.bottom - overlayBitmap.getHeight();
                width = ((ovlPos & 8) == 8) ? destRect.left
                        : destRect.right - overlayBitmap.getWidth();
                c.drawBitmap(overlayBitmap, width, height, null);
            }
            p.setXfermode(null);
            frameCounter++;
            /**
             * currentTimeMillis表示系統(tǒng)從January 1, 1970 00:00:00.0 UTC開始的毫秒數(shù)
             * start在前面已經(jīng)設(shè)置好竞膳,表示渲染線程開始的系統(tǒng)時間
             */
            if ((System.currentTimeMillis() - start) >= 1000) {
                fps = frameCounter + "fps";
                start = System.currentTimeMillis();
                overlayBitmap = makeFpsOverlay(overlayPaint, fps);/*真正的繪制這個"文本"*/
                frameCounter = 0;
            }
        }


    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末航瞭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坦辟,更是在濱河造成了極大的恐慌刊侯,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锉走,死亡現(xiàn)場離奇詭異滨彻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挪蹭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門亭饵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梁厉,你說我怎么就攤上這事辜羊。” “怎么了词顾?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵八秃,是天一觀的道長。 經(jīng)常有香客問我肉盹,道長昔驱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任垮媒,我火速辦了婚禮,結(jié)果婚禮上航棱,老公的妹妹穿的比我還像新娘睡雇。我一直安慰自己,他們只是感情好饮醇,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布它抱。 她就那樣靜靜地躺著,像睡著了一般朴艰。 火紅的嫁衣襯著肌膚如雪观蓄。 梳的紋絲不亂的頭發(fā)上混移,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音侮穿,去河邊找鬼歌径。 笑死,一個胖子當(dāng)著我的面吹牛亲茅,可吹牛的內(nèi)容都是我干的回铛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼克锣,長吁一口氣:“原來是場噩夢啊……” “哼茵肃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袭祟,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤验残,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后巾乳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體您没,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年想鹰,在試婚紗的時候發(fā)現(xiàn)自己被綠了紊婉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辑舷,死狀恐怖喻犁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情何缓,我是刑警寧澤肢础,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站碌廓,受9級特大地震影響传轰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谷婆,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一慨蛙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纪挎,春花似錦期贫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春封孙,著一層夾襖步出監(jiān)牢的瞬間迹冤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工虎忌, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留泡徙,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓呐籽,卻偏偏與公主長得像锋勺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子狡蝶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容