玩轉 Android MediaPlayer之視頻預加載(優化)

 
       MediaPlayer由廠傢定制,不同終端的MediaPlayer略有差異,例如:有些MediaPlayer首次播放從頭buffer,有些MdiaPlayer首次播放會多次Request,Range到網絡媒體文件的頭部、中間和文件尾,再從指定位置buffer…本文所做的優化就是適應播放前多次Request的MediaPlayer。
       決定預加載效果好壞由三因素決定:
網速
緩沖文件大小
視頻碼率
碼率低、網速快的情況沒必要使用預加載,碼率中等、網速一般的情況合適使用。另外,緩沖文件也不能設置太大:過大的緩沖區會刷爆MediaPlayer內置的緩沖區,影響正常播放;再者,讀取緩沖文件也耗時。
 
先看看本文程序的運行結果,以下是不使用預加載的運行LOG:
 
08-27 10:34:55.222: E//mnt/sdcard/ProxyBuffer/files(12949): ——–共有0個緩存文件
08-27 10:34:55.327: E/MediaPlayer(12949): MediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:34:55.327: E/(12949): IMediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:34:55.367: E/MediaPlayer(12949): MediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:34:55.367: E/(12949): IMediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:35:01.152: E/MediaPlayer(12949): MediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:35:01.152: E/(12949): IMediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:35:01.402: E/MediaPlayer(12949): MediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:35:01.402: E/(12949): IMediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:35:02.382: E/testVideoPlayer(12949): 預加載開關:false,等待緩沖時間:8000,首次緩沖時間:7152
 
 
以下是使用預加載的運行LOG,內容有點多,這個MediaPlayer就是首次播放前多次Request:
 
08-27 10:40:02.627: E//mnt/sdcard/ProxyBuffer/files(13769): ——–共有0個緩存文件
08-27 10:40:02.777: E/testVideoPlayer(13769): 預加載文件:/mnt/sdcard/ProxyBuffer/files/videorzx201208151345010952759.mp4
08-27 10:40:02.972: E/DownloadThread(13769): /mnt/sdcard/ProxyBuffer/files/videorzx201208151345010952759.mp4
08-27 10:40:10.782: E/MediaPlayer(13769): MediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:40:10.782: E/(13769): IMediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:40:10.787: E/HttpGetProxy(13769): ——————————————————————
08-27 10:40:10.787: E/HttpGetProxy(13769): java.lang.NullPointerException
08-27 10:40:10.787: E/HttpGetProxy(13769): com.proxy.HttpGetProxy.startProxy  171line
08-27 10:40:10.787: E/HttpGetProxy(13769): com.proxy.HttpGetProxy.access$0  134line
08-27 10:40:10.787: E/HttpGetProxy(13769): com.proxy.HttpGetProxy$1.run  129line
08-27 10:40:10.787: E/HttpGetProxy(13769): ——————————————————————
08-27 10:40:10.792: E/HttpParser(13769): GET /video/rzx/201208/15/1345010952759.mp4 HTTP/1.1
08-27 10:40:10.792: E/HttpParser(13769): Range: bytes=0-
08-27 10:40:10.792: E/HttpParser(13769): Host: video.cztv.com
08-27 10:40:10.792: E/HttpParser(13769): Accept: */*
08-27 10:40:10.792: E/HttpParser(13769): Pragma: no-cache
08-27 10:40:10.792: E/HttpParser(13769):
08-27 10:40:10.792: E/HttpParser(13769): _prebufferFilePath:/mnt/sdcard/ProxyBuffer/files/videorzx201208151345010952759.mp4
08-27 10:40:10.792: E/HttpParser(13769): ——->rangePosition:0
08-27 10:40:10.792: E/HttpGetProxy(13769): prebuffer size:999296
08-27 10:40:10.797: E/DownloadThread(13769): mTotalSize:8311866,mTargetSize:3145728
08-27 10:40:11.762: E/HttpGetProxy<—(13769): HTTP/1.1 206 Partial Content
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Date: Mon, 27 Aug 2012 02:33:24 GMT
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Server: Apache
08-27 10:40:11.762: E/HttpGetProxy<—(13769): X-Mod-H264-Streaming: version=2.2.7
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Last-Modified: Wed, 15 Aug 2012 06:24:38 GMT
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Accept-Ranges: bytes
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Cache-Control: max-age=315360000
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Expires: Thu, 25 Aug 2022 02:33:24 GMT
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Content-Type: video/mp4
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Powered-By-ChinaCache: HIT from 01006613Y4
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Content-Range: bytes 0-8311865/8311866
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Content-Length: 8311866
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Age: 407
08-27 10:40:11.762: E/HttpGetProxy<—(13769): Powered-By-ChinaCache: HIT from 01075913r4
08-27 10:40:11.762: E/HttpGetProxy<—(13769):
08-27 10:40:11.777: E/HttpGetProxy(13769): ………over……….
08-27 10:40:11.777: E/HttpGetProxy(13769): ——————————————————————
08-27 10:40:11.782: E/HttpParser(13769): GET /video/rzx/201208/15/1345010952759.mp4 HTTP/1.1
08-27 10:40:11.782: E/HttpParser(13769): Range: bytes=101901-
08-27 10:40:11.782: E/HttpParser(13769): Host: video.cztv.com
08-27 10:40:11.782: E/HttpParser(13769): Accept: */*
08-27 10:40:11.782: E/HttpParser(13769): Pragma: no-cache
08-27 10:40:11.782: E/HttpParser(13769):
08-27 10:40:11.782: E/HttpParser(13769): _prebufferFilePath:/mnt/sdcard/ProxyBuffer/files/videorzx201208151345010952759.mp4
08-27 10:40:11.782: E/HttpParser(13769): ——->rangePosition:101901
08-27 10:40:11.782: E/HttpGetProxy(13769): prebuffer size:1000320
08-27 10:40:12.167: E/HttpGetProxy<—(13769): HTTP/1.1 206 Partial Content
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Date: Mon, 27 Aug 2012 02:33:24 GMT
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Server: Apache
08-27 10:40:12.167: E/HttpGetProxy<—(13769): X-Mod-H264-Streaming: version=2.2.7
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Last-Modified: Wed, 15 Aug 2012 06:24:38 GMT
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Accept-Ranges: bytes
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Cache-Control: max-age=315360000
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Expires: Thu, 25 Aug 2022 02:33:24 GMT
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Content-Type: video/mp4
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Powered-By-ChinaCache: HIT from 01006613Y4
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Content-Range: bytes 101901-8311865/8311866
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Content-Length: 8209965
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Age: 408
08-27 10:40:12.167: E/HttpGetProxy<—(13769): Powered-By-ChinaCache: HIT from 01075913r4
08-27 10:40:12.167: E/HttpGetProxy<—(13769):
08-27 10:40:12.172: E/HttpGetProxy(13769): >>>skip:101901
08-27 10:40:12.182: E/HttpGetProxy(13769): ………over……….
08-27 10:40:12.182: E/HttpGetProxy(13769): ——————————————————————
08-27 10:40:12.187: E/HttpParser(13769): GET /video/rzx/201208/15/1345010952759.mp4 HTTP/1.1
08-27 10:40:12.187: E/HttpParser(13769): Range: bytes=74-
08-27 10:40:12.187: E/HttpParser(13769): Host: video.cztv.com
08-27 10:40:12.187: E/HttpParser(13769): Accept: */*
08-27 10:40:12.187: E/HttpParser(13769): Pragma: no-cache
08-27 10:40:12.187: E/HttpParser(13769):
08-27 10:40:12.187: E/HttpParser(13769): _prebufferFilePath:/mnt/sdcard/ProxyBuffer/files/videorzx201208151345010952759.mp4
08-27 10:40:12.187: E/HttpParser(13769): ——->rangePosition:74
08-27 10:40:12.187: E/HttpGetProxy(13769): prebuffer size:1000320
08-27 10:40:12.372: E/HttpGetProxy<—(13769): HTTP/1.1 206 Partial Content
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Date: Mon, 27 Aug 2012 02:33:24 GMT
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Server: Apache
08-27 10:40:12.372: E/HttpGetProxy<—(13769): X-Mod-H264-Streaming: version=2.2.7
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Last-Modified: Wed, 15 Aug 2012 06:24:38 GMT
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Accept-Ranges: bytes
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Cache-Control: max-age=315360000
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Expires: Thu, 25 Aug 2022 02:33:24 GMT
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Content-Type: video/mp4
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Powered-By-ChinaCache: HIT from 01006613Y4
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Content-Range: bytes 74-8311865/8311866
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Content-Length: 8311792
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Age: 408
08-27 10:40:12.372: E/HttpGetProxy<—(13769): Powered-By-ChinaCache: HIT from 01075913r4
08-27 10:40:12.372: E/HttpGetProxy<—(13769):
08-27 10:40:12.377: E/HttpGetProxy(13769): >>>skip:74
08-27 10:40:12.397: E/MediaPlayer(13769): MediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:40:12.397: E/(13769): IMediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:40:12.537: E/HttpGetProxy(13769): >>>讀取預加載耗時:164
08-27 10:40:12.537: E/HttpGetProxy(13769): >>>讀取完畢…下載:1000320,讀取:1000246
08-27 10:40:12.537: E/HttpGetProxy-pre->(13769): GET /video/rzx/201208/15/1345010952759.mp4 HTTP/1.1
08-27 10:40:12.537: E/HttpGetProxy-pre->(13769): Range: bytes=1000320-
08-27 10:40:12.537: E/HttpGetProxy-pre->(13769): Host: video.cztv.com
08-27 10:40:12.537: E/HttpGetProxy-pre->(13769): Accept: */*
08-27 10:40:12.537: E/HttpGetProxy-pre->(13769): Pragma: no-cache
08-27 10:40:12.537: E/HttpGetProxy-pre->(13769):
08-27 10:40:12.647: E/MediaPlayer(13769): MediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:40:12.647: E/(13769): IMediaPlayer::setVideoRect(0,25, 1280, 720)
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): HTTP/1.1 206 Partial Content
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Date: Mon, 27 Aug 2012 02:33:24 GMT
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Server: Apache
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): X-Mod-H264-Streaming: version=2.2.7
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Last-Modified: Wed, 15 Aug 2012 06:24:38 GMT
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Accept-Ranges: bytes
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Cache-Control: max-age=315360000
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Expires: Thu, 25 Aug 2022 02:33:24 GMT
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Content-Type: video/mp4
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Powered-By-ChinaCache: HIT from 01006613Y4
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Content-Range: bytes 1000320-8311865/8311866
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Content-Length: 7311546
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Age: 409
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769): Powered-By-ChinaCache: HIT from 01075913r4
08-27 10:40:12.962: E/HttpGetProxy ~~~~(13769):
08-27 10:40:14.037: E/testVideoPlayer(13769): 預加載開關:true,等待緩沖時間:8000,首次緩沖時間:3261
   HttpGetProxy.java源碼如下,可以讀出大概的運行流程:
[java] 
public class HttpGetProxy{ 
    final static public int SIZE =  (int) (3 * 1024 * 1024); 
    final static public String TAG = "HttpGetProxy"; 
    /** 鏈接帶的端口 */ 
    private int remotePort=-1; 
    /** 遠程服務器地址 */ 
    private String remoteHost; 
    /** 代理服務器使用的端口 */ 
    private int localPort; 
    /** 本地服務器地址 */ 
    private String localHost; 
    private ServerSocket localServer = null; 
    /** 收發Media Player請求的Socket */ 
    private Socket sckPlayer = null; 
    /** 收發Media Server請求的Socket */ 
    private Socket sckServer = null; 
    /**服務器的Address*/ 
    private SocketAddress serverAddress; 
     
    /**下載線程*/ 
    private DownloadThread download = null; 
     
    /**
     * 初始化代理服務器
     * @param localport 代理服務器監聽的端口
     */ 
    public HttpGetProxy(int localport) { 
        try { 
            localPort = localport; 
            localHost = C.LOCAL_IP_ADDRESS; 
            localServer = new ServerSocket(localport, 1,InetAddress.getByName(localHost)); 
        } catch (Exception e) { 
            System.exit(0); 
        } 
    } 
 
    /**
     * 把URL提前下載在SD卡,實現預加載
     * @param urlString
     * @return 返回預加載文件名
     * @throws Exception
     */ 
    public String prebuffer(String urlString,int size) throws Exception{ 
        if(download!=null && download.isDownloading()) 
            download.stopThread(true); 
         
        URI tmpURI=new URI(urlString); 
        String fileName=Utils.urlToFileName(tmpURI.getPath()); 
        String filePath=C.getBufferDir()+"/"+fileName; 
         
        download=new DownloadThread(urlString,filePath,size); 
        download.startThread(); 
         
        return filePath; 
    } 
     
    /**
     * 把網絡URL轉為本地URL,127.0.0.1替換網絡域名
     * 
     * @param url網絡URL
     * @return [0]:重定向後MP4真正URL,[1]:本地URL
     */ 
    public String[] getLocalURL(String urlString) { 
         
        // —-排除HTTP特殊—-// 
        String targetUrl = Utils.getRedirectUrl(urlString); 
        // —-獲取對應本地代理服務器的鏈接—-// 
        String localUrl = null; 
        URI originalURI = URI.create(targetUrl); 
        remoteHost = originalURI.getHost(); 
        if (originalURI.getPort() != -1) {// URL帶Port 
            serverAddress = new InetSocketAddress(remoteHost, originalURI.getPort());// 使用默認端口 
            remotePort = originalURI.getPort();// 保存端口,中轉時替換 
            localUrl = targetUrl.replace( 
                    remoteHost + ":" + originalURI.getPort(), localHost + ":" 
                            + localPort); 
        } else {// URL不帶Port 
            serverAddress = new InetSocketAddress(remoteHost, C.HTTP_PORT);// 使用80端口 
            remotePort = -1; 
            localUrl = targetUrl.replace(remoteHost, localHost + ":" 
                    + localPort); 
        } 
         
        String[] result= new String[]{targetUrl,localUrl}; 
        return result; 
    } 
 
    /**
     * 異步啟動代理服務器
     * 
     * @throws IOException
     */ 
    public void asynStartProxy() { 
        new Thread() { 
            public void run() { 
                startProxy(); 
            } 
        }.start(); 
    } 
 
    private void startProxy() { 
        HttpParser httpParser =null; 
        HttpGetProxyUtils utils=null; 
        int bytes_read; 
     
        byte[] local_request = new byte[1024]; 
        byte[] remote_reply = new byte[1024]; 
 
        while (true) { 
            boolean sentResponseHeader = false; 
            try {// 開始新的request之前關閉過去的Socket 
                if (sckPlayer != null) 
                    sckPlayer.close(); 
                if (sckServer != null) 
                    sckServer.close(); 
            } catch (IOException e1) {} 
            try { 
                // ————————————– 
                // 監聽MediaPlayer的請求,MediaPlayer->代理服務器 
                // ————————————– 
                sckPlayer = localServer.accept(); 
                Log.e(TAG,"——————————————————————"); 
                if(download!=null && download.isDownloading()) 
                    download.stopThread(false); 
                 
                httpParser=new HttpParser(remoteHost,remotePort,localHost,localPort); 
                utils = new HttpGetProxyUtils(sckPlayer,sckServer,serverAddress); 
                 
                ProxyRequest request = null; 
                while ((bytes_read = sckPlayer.getInputStream().read(local_request)) != -1) { 
                    byte[] buffer=httpParser.getRequestBody(local_request,bytes_read); 
                    if(buffer!=null){ 
                        request=httpParser.getProxyRequest(buffer); 
                        break; 
                    } 
                } 
                 
                boolean isExists=new File(request._prebufferFilePath).exists(); 
                if(isExists) 
                    Log.e(TAG,"prebuffer size:"+download.getDownloadedSize()); 
                 
                sckServer = utils.sentToServer(request._body); 
                // —————————————————— 
                // 把網絡服務器的反饋發到MediaPlayer,網絡服務器->代理服務器->MediaPlayer 
                // —————————————————— 
                while ((bytes_read = sckServer.getInputStream().read(remote_reply)) != -1) { 
                    if(sentResponseHeader){ 
                        try{//拖動進度條時,容易在此異常,斷開重連 
                            utils.sendToMP(remote_reply,bytes_read); 
                        }catch (Exception e) { 
                            break;//發送異常直接退出while 
                        } 
                        continue;//退出本次while 
                    } 
 
                    List<byte[]> httpResponse = httpParser.getResponseBody(remote_reply, bytes_read); 
                    if (httpResponse.size() == 0) 
                        continue;//沒Header則退出本次循環 
 
                    sentResponseHeader = true; 
                    String responseStr = new String(httpResponse.get(0)); 
                    Log.e(TAG + "<—", responseStr); 
                    //send http header to mediaplayer 
                    utils.sendToMP(httpResponse.get(0)); 
                     
                    if (isExists) {//需要發送預加載到MediaPlayer 
                        isExists = false; 
                        int sentBufferSize = 0; 
                        try{ 
                            sentBufferSize = utils.sendPrebufferToMP( 
                                request._prebufferFilePath, 
                                request._rangePosition); 
                        }catch(Exception ex){ 
                            break; 
                        } 
                        if (sentBufferSize > 0) {// 成功發送預加載,重新發送請求到服務器 
                            int newRange=(int) (sentBufferSize + request._rangePosition); 
                            String newRequestStr = httpParser.modifyRequestRange(request._body,newRange); 
                            Log.e(TAG + "-pre->", newRequestStr); 
                            //修改Range後的Request發送給服務器 
                            sckServer = utils.sentToServer(newRequestStr); 
                            //把服務器的Response的Header去掉 
                            utils.removeResponseHeader(httpParser); 
                            continue; 
                        } 
                    } 
 
                    // 發送剩餘數據 
                    if (httpResponse.size() == 2) { 
                        utils.sendToMP(httpResponse.get(1)); 
                    } 
                } 
 
                Log.e(TAG, "………over………."); 
 
                // 關閉 2個SOCKET 
                sckPlayer.close(); 
                sckServer.close(); 
            } catch (Exception e) { 
                Log.e(TAG,e.toString()); 
                Log.e(TAG,Utils.getExceptionMessage(e)); 
            } 
        } 
    } 

發佈留言