Android,SurfaceView播放视频源码




      
android

android

SurfaceView 

  先来介绍一下大部分软件如何解析一段视频流。首先它需要先确定视频的格式,这个和解码相关,不同的格式视频编码不同,不是这里的重点。知道了视频的编码格式后,再通过编码格式进行解码,最后得到一帧一帧的图像,并把这些图像快速的显示在界面上,即为播放一段视频。SurfaceView在Android中就是完成这个功能的。

  既然SurfaceView是配合MediaPlayer使用的,MediaPlayer也提供了相应的方法设置SurfaceView显示图片,只需要为MediaPlayer指定SurfaceView显示图像即可。它的完整签名如下:

    void setDisplay(SurfaceHolder sh)

  它需要传递一个SurfaceHolder对象,SurfaceHolder可以理解为SurfaceView装载需要显示的一帧帧图像的容器,它可以通过SurfaceHolder.getHolder()方法获得。

  使用MediaPlayer配合SurfaceView播放视频的步骤与播放使用MediaPlayer播放MP3大体一致,只需要额外设置显示的SurfaceView即可。

SurfaceView双缓冲

  上面有提到,SurfaceView和大部分视频应用一样,把视频流解析成一帧帧的图像进行显示,但是如果把这个解析的过程放到一个线程中完成,可能在上一帧图像已经显示过后,下一帧图像还没有来得及解析,这样会导致画面的不流畅或者声音和视频不同步的问题。所以SurfaceView和大部分视频应用一样,通过双缓冲的机制来显示帧图像。那么什么是双缓冲呢?双缓冲可以理解为有两个线程轮番去解析视频流的帧图像,当一个线程解析完帧图像后,把图像渲染到界面中,同时另一线程开始解析下一帧图像,使得两个线程轮番配合去解析视频流,以达到流畅播放的效果。

SurfaceHolder

SurfaceView内部实现了双缓冲的机制,但是实现这个功能是非常消耗系统内存的。因为移动设备的局限性,Android在设计的时候规定,SurfaceView如果为用户可见的时候,创建SurfaceView的SurfaceHolder用于显示视频流解析的帧图片,如果发现SurfaceView变为用户不可见的时候,则立即销毁SurfaceView的SurfaceHolder,以达到节约系统资源的目的。

如果开发人员不对SurfaceHolder进行维护,会出现最小化程序后,再打开应用的时候,视频的声音在继续播放,但是不显示画面了的情况,这就是因为当SurfaceView不被用户可见的时候,之前的SurfaceHolder已经被销毁了,再次进入的时候,界面上的SurfaceHolder已经是新的SurfaceHolder了。所以SurfaceHolder需要我们开发人员去编码维护,维护SurfaceHolder需要用到它的一个回调,SurfaceHolder.Callback(),它需要实现三个如下三个方法:

  • void surfaceDestroyed(SurfaceHolder holder):当SurfaceHolder被销毁的时候回调。
  • void surfaceCreated(SurfaceHolder holder):当SurfaceHolder被创建的时候回调。
  • void surfaceChange(SurfaceHolder holder):当SurfaceHolder的尺寸发生变化的时候被回调。

以下是这三个方法的调用的过程,在应用中分别为SurfaceHolder实现了这三个方法,先进入应用,SurfaceHolder被创建,创建好之后会改变SurfaceHolder的大小,然后按Home键回退到桌面销毁SurfaceHolder,最后再进入应用,重新SurfaceHolder并改变其大小。

SurfaceView的Demo示例

  上面讲了那么多关于SurfaceView的内容,下面通过一个Demo简单演示一下SurfaceView如何播放视频,加了一个滚动条,用于显示进度,还可以拖动滚动条选择播放位置,Demo的注释比较完整,这里不再累述,视频是在网上随便找的,朋友们运行的时候保证/sdcard/ykzzldx.mp4,这个目录下有这个文件。

  布局文件:activity_main.xml

 
 
  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8. activity_main.xml

实现代码:

 
 
  1. package cn.bgxt.surfaceviewdemo;
  2.  
  3. import java.io.File;
  4.  
  5. import android.media.AudioManager;
  6. import android.media.MediaPlayer;
  7. import android.media.MediaPlayer.OnCompletionListener;
  8. import android.media.MediaPlayer.OnErrorListener;
  9. import android.media.MediaPlayer.OnPreparedListener;
  10. import android.os.Bundle;
  11. import android.app.Activity;
  12. import android.util.Log;
  13. import android.view.SurfaceHolder;
  14. import android.view.SurfaceHolder.Callback;
  15. import android.view.SurfaceView;
  16. import android.view.View;
  17. import android.widget.Button;
  18. import android.widget.EditText;
  19. import android.widget.SeekBar;
  20. import android.widget.SeekBar.OnSeekBarChangeListener;
  21. import android.widget.Toast;
  22.  
  23. public class MainActivity extends Activity {
  24. private final String TAG = "main";
  25. private EditText et_path;
  26. private SurfaceView sv;
  27. private Button btn_play, btn_pause, btn_replay, btn_stop;
  28. private MediaPlayer mediaPlayer;
  29. private SeekBar seekBar;
  30. private int currentPosition = 0;
  31. private boolean isPlaying;
  32.  
  33. @Override
  34. protected void onCreate(Bundle savedInstanceState) {
  35. super.onCreate(savedInstanceState);
  36. setContentView(R.layout.activity_main);
  37.  
  38. seekBar = (SeekBar) findViewById(R.id.seekBar);
  39. sv = (SurfaceView) findViewById(R.id.sv);
  40. et_path = (EditText) findViewById(R.id.et_path);
  41.  
  42. btn_play = (Button) findViewById(R.id.btn_play);
  43. btn_pause = (Button) findViewById(R.id.btn_pause);
  44. btn_replay = (Button) findViewById(R.id.btn_replay);
  45. btn_stop = (Button) findViewById(R.id.btn_stop);
  46.  
  47. btn_play.setOnClickListener(click);
  48. btn_pause.setOnClickListener(click);
  49. btn_replay.setOnClickListener(click);
  50. btn_stop.setOnClickListener(click);
  51.  
  52. // 为SurfaceHolder添加回调
  53. sv.getHolder().addCallback(callback);
  54. // 4.0版本之下需要设置的属性
  55. // 设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面
  56. // sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  57. // 为进度条添加进度更改事件
  58. seekBar.setOnSeekBarChangeListener(change);
  59. }
  60.  
  61. private Callback callback = new Callback() {
  62. // SurfaceHolder被修改的时候回调
  63. @Override
  64. public void surfaceDestroyed(SurfaceHolder holder) {
  65. Log.i(TAG, "SurfaceHolder 被销毁");
  66. // 销毁SurfaceHolder的时候记录当前的播放位置并停止播放
  67. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  68. currentPosition = mediaPlayer.getCurrentPosition();
  69. mediaPlayer.stop();
  70. }
  71. }
  72.  
  73. @Override
  74. public void surfaceCreated(SurfaceHolder holder) {
  75. Log.i(TAG, "SurfaceHolder 被创建");
  76. if (currentPosition > 0) {
  77. // 创建SurfaceHolder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放
  78. play(currentPosition);
  79. currentPosition = 0;
  80. }
  81. }
  82.  
  83. @Override
  84. public void surfaceChanged(SurfaceHolder holder, int format, int width,
  85. int height) {
  86. Log.i(TAG, "SurfaceHolder 大小被改变");
  87. }
  88.  
  89. };
  90.  
  91. private OnSeekBarChangeListener change = new OnSeekBarChangeListener() {
  92.  
  93. @Override
  94. public void onStopTrackingTouch(SeekBar seekBar) {
  95. // 当进度条停止修改的时候触发
  96. // 取得当前进度条的刻度
  97. int progress = seekBar.getProgress();
  98. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  99. // 设置当前播放的位置
  100. mediaPlayer.seekTo(progress);
  101. }
  102. }
  103.  
  104. @Override
  105. public void onStartTrackingTouch(SeekBar seekBar) {
  106.  
  107. }
  108.  
  109. @Override
  110. public void onProgressChanged(SeekBar seekBar, int progress,
  111. boolean fromUser) {
  112.  
  113. }
  114. };
  115.  
  116. private View.OnClickListener click = new View.OnClickListener() {
  117.  
  118. @Override
  119. public void onClick(View v) {
  120.  
  121. switch (v.getId()) {
  122. case R.id.btn_play:
  123. play(0);
  124. break;
  125. case R.id.btn_pause:
  126. pause();
  127. break;
  128. case R.id.btn_replay:
  129. replay();
  130. break;
  131. case R.id.btn_stop:
  132. stop();
  133. break;
  134. default:
  135. break;
  136. }
  137. }
  138. };
  139.  
  140.  
  141. /*
  142. * 停止播放
  143. */
  144. protected void stop() {
  145. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  146. mediaPlayer.stop();
  147. mediaPlayer.release();
  148. mediaPlayer = null;
  149. btn_play.setEnabled(true);
  150. isPlaying = false;
  151. }
  152. }
  153.  
  154. /**
  155. * 开始播放
  156. *
  157. * @param msec 播放初始位置
  158. */
  159. protected void play(final int msec) {
  160. // 获取视频文件地址
  161. String path = et_path.getText().toString().trim();
  162. File file = new File(path);
  163. if (!file.exists()) {
  164. Toast.makeText(this, "视频文件路径错误", 0).show();
  165. return;
  166. }
  167. try {
  168. mediaPlayer = new MediaPlayer();
  169. mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  170. // 设置播放的视频源
  171. mediaPlayer.setDataSource(file.getAbsolutePath());
  172. // 设置显示视频的SurfaceHolder
  173. mediaPlayer.setDisplay(sv.getHolder());
  174. Log.i(TAG, "开始装载");
  175. mediaPlayer.prepareAsync();
  176. mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
  177.  
  178. @Override
  179. public void onPrepared(MediaPlayer mp) {
  180. Log.i(TAG, "装载完成");
  181. mediaPlayer.start();
  182. // 按照初始位置播放
  183. mediaPlayer.seekTo(msec);
  184. // 设置进度条的最大进度为视频流的最大播放时长
  185. seekBar.setMax(mediaPlayer.getDuration());
  186. // 开始线程,更新进度条的刻度
  187. new Thread() {
  188.  
  189. @Override
  190. public void run() {
  191. try {
  192. isPlaying = true;
  193. while (isPlaying) {
  194. int current = mediaPlayer
  195. .getCurrentPosition();
  196. seekBar.setProgress(current);
  197. sleep(500);
  198. }
  199. } catch (Exception e) {
  200. e.printStackTrace();
  201. }
  202. }
  203. }.start();
  204.  
  205. btn_play.setEnabled(false);
  206. }
  207. });
  208. mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
  209.  
  210. @Override
  211. public void onCompletion(MediaPlayer mp) {
  212. // 在播放完毕被回调
  213. btn_play.setEnabled(true);
  214. }
  215. });
  216.  
  217. mediaPlayer.setOnErrorListener(new OnErrorListener() {
  218.  
  219. @Override
  220. public boolean onError(MediaPlayer mp, int what, int extra) {
  221. // 发生错误重新播放
  222. play(0);
  223. isPlaying = false;
  224. return false;
  225. }
  226. });
  227. } catch (Exception e) {
  228. e.printStackTrace();
  229. }
  230.  
  231. }
  232.  
  233. /**
  234. * 重新开始播放
  235. */
  236. protected void replay() {
  237. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  238. mediaPlayer.seekTo(0);
  239. Toast.makeText(this, "重新播放", 0).show();
  240. btn_pause.setText("暂停");
  241. return;
  242. }
  243. isPlaying = false;
  244. play(0);
  245.  
  246. }
  247.  
  248. /**
  249. * 暂停或继续
  250. */
  251. protected void pause() {
  252. if (btn_pause.getText().toString().trim().equals("继续")) {
  253. btn_pause.setText("暂停");
  254. mediaPlayer.start();
  255. Toast.makeText(this, "继续播放", 0).show();
  256. return;
  257. }
  258. if (mediaPlayer != null && mediaPlayer.isPlaying()) {
  259. mediaPlayer.pause();
  260. btn_pause.setText("继续");
  261. Toast.makeText(this, "暂停播放", 0).show();
  262. }
  263.  
  264. }
  265.  
  266. }

 

源码下载地址 :http://pan.baidu.com/s/1lgKLS

 

转载请注明:安度博客 » Android,SurfaceView播放视频源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值