Android视频播放 (一)——TextureView和SurfaceView区别 + 视频播放

来源:互联网 发布:i started a joke 知乎 编辑:程序博客网 时间:2024/05/18 19:19

转载注明出处:

文章出自 我不只是看客/NotLooker


  好久不更新博客了,视频播放相关技术继续积累。最近整理了下前端时间实现的瀑布流播放等,现在放出博客,接下来几篇 会陆续讲解 瀑布流跳转详情页的缓冲播放,以及多段视频资源的无缝切换;
  短视频的资本浪潮导致很多公司都要分一杯羹,然后就苦了我们这群写代码的,要支持各种播放需求。。。
  下面正文开始:
android开发中播放视频大概有3中模式,videoview+mediaplayer是最灵活强大的定制化解决办法,videoview继承于SurfaceView;简单介绍下SurfaceView:

SurfaceView从Android 1.0(API level 1)时就有 。它继承自类View,因此它本质上是一个View。但与普通View不同的是,它有自己的Surface。我们知道,一般的Activity包含的多个View会组成View hierachy的树形结构,只有最顶层的DecorView,也就是根结点视图,才是对WMS可见的。这个DecorView在WMS中有一个对应的WindowState。相应地,在SF中对应的Layer。而SurfaceView自带一个Surface,这个Surface在WMS中有自己对应的WindowState,在SF中也会有自己的Layer。如下图所示:
图1
也就是说,虽然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,渲染时可以有自己的GL context。这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影响主线程对事件的响应。但它也有缺点,因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。

既然SurfaceView并不是一个真正的view,既它无法平移,那么自然不能把它放到listview中,查看API发现在 3.0后引入了个SurfaceTexture的东西

SurfaceTexture从Android 3.0(API level 11)加入。和SurfaceView不同的是,它对图像流的处理并不直接显示,而是转为GL外部纹理,因此可用于图像流数据的二次处理(如Camera滤镜,桌面特效等)。比如Camera的预览数据,变成纹理后可以交给GLSurfaceView直接显示,也可以通过SurfaceTexture交给TextureView作为View heirachy中的一个硬件加速层来显示。首先,SurfaceTexture从图像流(来自Camera预览,视频解码,GL绘制场景等)中获得帧数据,当调用updateTexImage()时,根据内容流中最近的图像更新SurfaceTexture对应的GL纹理对象,接下来,就可以像操作普通GL纹理一样操作它了。从下面的类图中可以看出,它核心管理着一个BufferQueue的Consumer和Producer两端。Producer端用于内容流的源输出数据,Consumer端用于拿GraphicBuffer并生成纹理。SurfaceTexture.OnFrameAvailableListener用于让SurfaceTexture的使用者知道有新数据到来。JNISurfaceTextureContext是OnFrameAvailableListener从Native到Java的JNI跳板。其中SurfaceTexture中的attachToGLContext()和detachToGLContext()可以让多个GL context共享同一个内容源。

于是继续搜索SurfaceTexture,发现在(API level 14)中 google进入了一个新的组件TextureView。

它可以将内容流直接投影到View中,可以用于实现Live preview等功能。和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中。它显示的内容流数据可以来自App进程或是远端进程。TextureView继承自View,它与其它的View一样在View hierachy中管理与绘制。TextureView重载了draw()方法,其中主要把SurfaceTexture中收到的图像数据作为纹理更新到对应的HardwareLayer中。

SurfaceTexture就是mediaplayer输出视频流的窗口 Bingo!新的View找到了;
接下来就是使用TextureView+Meidaplayer播放视频了;
节约篇幅,省略无关代码:

    textureView = (TextureView) findViewById(R.id.textureview1);    final MediaPlayer mediaPlayer = new MediaPlayer();    textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {        @Override        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {            mediaPlayer.setSurface(new Surface(surface));                try {                    mediaPlayer.setDataSource(url);                } catch (IOException e) {                    e.printStackTrace();                }                mediaPlayer.prepareAsync();                mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {                    @Override                    public void onPrepared(MediaPlayer mp) {                        mediaPlayer.start();                    }                });            }            @Override            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {            }            @Override            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {                return false;            }            @Override            public void onSurfaceTextureUpdated(SurfaceTexture surface) {            }        }    });

部分解释:

    textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener());

想要获取到TextureView的SurfaceTexture就需要确保TextureView正确的初始化, 而TextureView正好提供了一个监听TextureView.SurfaceTextureListener 它可以监听SurfaceTexture的生成、变化、销毁和更新,如果你确定TextureView已经生成完成的话 也可以直接用getSurfaceTexture来获取;

在onSurfaceTextureAvailable回调中,添加MediaPlayer的初始化操作。

public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {    mediaPlayer.setSurface(new Surface(surface));    try {        mediaPlayer.setDataSource(url);    } catch (IOException e) {        e.printStackTrace();    }    mediaPlayer.prepareAsync();    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {        @Override        public void onPrepared(MediaPlayer mp) {            mediaPlayer.start();        }    });}

获取了surfaceTexture后塞给mediaPlayer;然后设置url,并且将mediaplayer设置准备状态;设置准备播放监听,在onPrepared回调中 开始播放;

如果MediaPlayer报错极有可能是状态变更出现问题,相关文章给出:Android开发之MdiaPlayer详解

PS:本次测试的URL是http地址,mediaplayer可以识别并播放,读者需替换掉自己的可播放地址;MediaPlayer的setDataSource支持http/rtps地址,支持本地文件目录以及FileDescriptor的播放,但是android5.0及以上对读取本地文件做了权限增加,相关文献不做展开 ,这里只提供部分解决方案:在AndroidManifest.xml
中增加读取权限

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

然后在android6.0以上在app设置中开启应用文件读取权限,就可以播放了

0 0
原创粉丝点击