玩转 Android MediaPlayer之视频预加载(优化)
================================================================
本文来自http://blog.csdn.net/hellogv/ ,引用必须注明出处!
文章源地址:http://blog.csdn.net/hellogv/article/details/7911293#comments
================================================================
本文是在《玩转 Android MediaPlayer之视频预加载》基础上做更进一步的优化,适应更多终端的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
本文的源码可以到这里下载:http://download.csdn.net/detail/hellogv/4528270
HttpGetProxy.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)); } } }