HardBirch

玩转 Android MediaPlayer之视频预加载(优化)

时间:12-08-27 栏目:安卓入门与提高 作者:张飞不张,文采横飞 评论:13 点击: 5,450 次


       本文是在《玩转 Android MediaPlayer之视频预加载》基础上做更进一步的优化,适应更多终端的MediaPlayer,不再唠叨预加载的作用和基础,有兴趣的读者请看上回。

       MediaPlayer由厂家定制,不同终端的MediaPlayer略有差异,例如:有些MediaPlayer首次播放从头buffer,有些MdiaPlayer首次播放会多次Request,Range到网络媒体文件的头部、中间和文件尾,再从指定位置buffer...本文所做的优化就是适应播放前多次Request的MediaPlayer。

       决定预加载效果好坏由三因素决定:

  1. 网速
  2. 缓冲文件大小
  3. 视频码率

码率低、网速快的情况没必要使用预加载,码率中等、网速一般的情况合适使用。另外,缓冲文件也不能设置太大:过大的缓冲区会刷爆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));
			}
		}
	}

声明: 本文由( 张飞不张,文采横飞 )原创编译,转载请保留链接: 玩转 Android MediaPlayer之视频预加载(优化)

玩转 Android MediaPlayer之视频预加载(优化):目前有13 条留言

  1. 13楼
    j2mesoft:

    强啊。牛人

    2012-08-27 11:58 [回复]
  2. 12楼
    xuyan87101:

    顶虫哥!

    2012-08-27 12:00 [回复]
  3. 11楼
    hmc1985:

    不错!顶!

    2012-08-27 12:17 [回复]
  4. 10楼
    kf156:

    顶起

    2012-08-27 12:45 [回复]
  5. 9楼
    oWintersun:

    牛人 顶

    2012-08-27 15:46 [回复]
  6. 8楼
    lindir:

    强大!

    2012-08-27 19:11 [回复]
  7. 哇 新作品又出了!依旧深奥! 谢谢分享!

    2012-08-29 09:16 [回复]
  8. 6楼
    fensider:

    请问我在真机Android2.3上为什么不能播放?文件已加载完毕了,是不支持mp4吗?

    2012-08-30 16:22 [回复]
  9. 5楼
    dxpqxb:

    进来了就支持支持

    2012-08-31 10:45 [回复]
  10. 4楼
    hellogv:

    [reply]fensider[/reply]
    肯定支持MP4,你的手机是什么型号?

    2012-08-31 22:22 [回复]
  11. 地板
    dadoneo:

    顶楼主,转了再看

    2012-09-01 00:15 [回复]
  12. 板凳
    fensider:

    [reply]hellogv[/reply]
    不好意思,最近没上CSDN,我手机是三星S5838

    2012-09-10 08:37 [回复]
  13. 楼主太伟大了

    2012-09-19 19:06 [回复]

发表评论


QQ群互动

Linux系统与内核学习群:194051772

WP建站技术学习交流群:194062106

魔豆之路QR

魔豆的Linux内核之路

魔豆的Linux内核之路

优秀工程师当看优秀书籍

优秀程序员,要看优秀书!

赞助商广告

友荐云推荐