Unity视频播放的各种实现方式汇总
Unity提供了多种视频播放的实现方式,开发者可根据具体需求进行选择。以下是对这些实现方式的详细总结:
1. MovieTexture
简介
MovieTexture是Unity的标准接口,支持的视频格式包括.mov、.mpg、.mpeg、.mp4、.avi和.asf。不过,它仅支持PC端的本地视频播放。
在游戏对象中播放
在游戏世界里,可通过创建一个Plane面对象来播放视频,就像让摄像机直直地照射在这个面上。具体操作如下:
- 新建一个plane平面,将其纹理绑定为电影纹理。示例代码如下:// 设置当前对象的主纹理为电影纹理 renderer.material.mainTexture = movTexture; // 设置电影纹理播放模式为循环 movTexture.loop = true;
- 播放控制:可以使用以下方法对视频进行播放控制。movTexture.Play(); movTexture.Pause(); movTexture.Stop();
- 缩放视频:可直接缩放plane平面来实现视频的缩放。
- 赋值问题:在4.6x/5.0x版本上,无法通过将视频拖入Project视图来自动生成纹理,而之前的老版本是可行的。
在GUI层面播放
由于播放视频用到的MovieTexture属于贴图Texture的子类,所以它和贴图非常相像。示例代码如下:
// 绘制电影纹理
GUI.DrawTexture (new Rect (0, 0, Screen.width, Screen.height), movTexture, ScaleMode.StretchToFill);
播放视频的大小默认为屏幕的宽高,若想动态修改视频的宽或高,直接修改new Rect()中的视频显示区域即可。
2. Handheld.PlayFullScreenMovie
简介
这是Unity标准的视频播放接口,支持的视频格式有.mov、.mpg、.mpeg、.mp4、.avi和.asf。它支持PC端和移动端播放,也支持本地和在线播放。
使用示例
string url_movie = "http://dl.nbrom.cn/17051/c3e408229342723fbdf62d0bcf1d549c.mp4?fsname=Criminal_Minds_S01E01.mp4";
Handheld.PlayFullScreenMovie(url_movie, Color.black, FullScreenMovieControlMode.Full);
Handheld.PlayFullScreenMovie("test.mp4", Color.black, FullScreenMovieControlMode.CancelOnInput);
需将视频文件放置在Assets/StreamingAssets/路径下。在移动端,该方法可边下载边播放网络视频,属于在线播放。不过,再次观看时需要再次加载。可以在播放时判断视频是否已下载到本地,若在本地则播放本地视频,若没有则从网上下载到本地。
3. MobileMovieTexture
简介
这是Unity移动端的第三方插件视频播放工具,分为免费版和收费版。免费版的视频会添加水印,收费版则无水印。但该插件本身存在很多bug,其中最重要的是没有音频,并且它不支持在线视频播放。
4. EasyMovieTexture
简介
这是Unity移动端的第三方视频播放插件,支持视频本地播放,也支持RTSP。
实现步骤
- 初始化加载:在Unity中将播放视频的地址(本地/URL)传送到Android,并完成MediaPlayer的初始化。
- Android创建Surface:Android创建一个Surface,并将其与之前创建的MediaPlayer绑定。
- 计算图像拉伸比:结合视频绘制载体计算图像拉伸比。
- 创建VideoTexture:根据视频宽高比创建VideoTexture并传到Android。示例代码如下:m_VideoTexture = new Texture2D(Call_GetVideoWidth(), Call_GetVideoHeight(), TextureFormat.RGB565, false); Call_SetUnityTexture(m_VideoTexture.GetNativeTextureID());
- 设置视频窗口:完成TextureId与surface的绑定。SetWindowSize(GetVideoWidth(), GetVideoHeight(), m_iUnityTextureID, m_bRockchip);
- 更新纹理:Call_UpdateVideoTexture(); m_SurfaceTexture.updateTexImage();
- 播放视频:使用MediaPlayer播放视频。
5. MediaPlayer + SurfaceTexture
简介
播放组件上层使用MediaPlayer来处理,在成功创建并设置好setDataSource后,需要创建GL_TEXTURE_EXTERNAL_OES格式的纹理ID来与MediaPlayer建立联系。
SurfaceTexture的作用
使用SurfaceTexture的原因在于,它能代替SurfaceHolder。当指定图像流的输出目标为照相机预览或视频解码时,每一帧中得到的所有数据不需要直接用于显示在设备上,而是可以先输出到SurfaceTexture上,在上屏之前进行一些自定义扩展。当调用updateTexImage()时,用来创建SurfaceTexture的纹理对象内容会被更新为包含图像流中最近的图片。
注意事项
- SurfaceTexture对象可以在任何线程里创建,但updateTexImage()只能在包含纹理对象的OpenGL ES上下文所在的线程里调用。可以得到帧信息的回调可以在任何线程被调用。若上下文不一致,视频将无法上屏。
- 创建纹理时,需要使用GL_TEXTURE_EXTERNAL_OES作为纹理目标,它是OpenGL ES扩展GL_OES_EGL_image_external定义的。这种纹理目标会对纹理的使用方式造成一些限制:每次纹理绑定的时候,都要绑定到GL_TEXTURE_EXTERNAL_OES,而不是GL_TEXTURE_2D。而且,任何需要从纹理中采样的OpenGL ES 2.0 shader都需要声明其对此扩展的使用,例如使用指令#extension GL_OES_EGL_image_external:require,这些shader也必须使用samplerExternalOES采样方式来访问纹理。
重要技术点
Shader采样 YUV->RGB
片元着色器针对GL_TEXTURE_EXTERNAL_OES进行扩展,完成YUV到RGB的转换。采样出来的数据需要绘制到Unity纹理上,这需要借助FBO和EGLImage来操作。该片元着色器的写法在使用SurfaceTexture时会有提及。
EGLImage
EGLImage代表一种由EGL客户API(如OpenGL,OpenVG)创建的共享资源类型,其本意是共享2D图像数据。在这里使用它的目的是,经过EGLImage创建的2D纹理能够绑定到任意的载体GameObject上面。
创建EGLImage的标准接口
EGLImageKHR eglCreateImageKHR(
EGLDisplay dpy,
EGLContext ctx,
EGLenum target,
EGLClientBuffer buffer,
const EGLint *attrib_list
);
target决定了创建EGLImage的方式,例如在Android系统中专门定义了一个称为EGL_NATIVE_BUFFER_ANDROID的Target,支持通过ANativeWindowBuffer创建EGLImage对象,而Buffer则对应创建EGLImage对象时的数据来源。
创建步骤
- 获取ANativeWindowBuffer:该buffer可以通过ANativeWindow的接口dequeueBuffer来获取。在获取buffer之前要创建一个ANativeWindow对象。这部分操作可参考这篇文章。
- 创建EGLImage:通过获取到的ANativeWindowBuffer来创建EGLImage。
- 创建2D纹理:成功创建EGLImage后,可通过它来创建一个2D纹理,这个2D纹理在后面创建FBO的时候会用到。
FBO - Frame Buffer Object
FBO即帧缓存对象,它是OpenGL管线的最终渲染目的地。在OpenGL渲染管线中,几何数据和纹理在FBO内部经过多次转化和多次测试,最后以二维像素的形式显示在屏幕上。它是一些二维数组和OpenGL所使用的存储区的集合,包括颜色缓存、深度缓存、模板缓存和累计缓存。默认情况下,OpenGL将帧缓冲区作为渲染最终目的地,此帧缓冲区完全由window系统生成和管理,这个默认的帧缓存被称作“window系统生成”(window-system-provided)的帧缓冲区。
FBO有两种类型的“帧缓存关联图像”:纹理图像(texture images)和渲染缓存图像(renderbuffer images)。如果纹理对象的图像数据关联到帧缓存,OpenGL执行的是“渲染到纹理”(render to texture)操作;如果渲染缓存的图像数据关联到帧缓存,OpenGL执行的是离线渲染(offscreen rendering)。
具体操作时,使用通过上一步EGLImage生成的2D纹理mTex,后续就可以通过操作FBO对象来获取MediaPlayer中SurfaceTexture中的每一帧数据。关于FBO的详细接口说明,可参考此链接。
SurfaceTexture.updateTexImage
当MediaPlayer中有新的帧流可用时,调用updateTexImage从图像流中提取最近一帧到纹理图像中。由于GL_TEXTURE_EXTERNAL_OES的特性,此时需要先执行一次FBO绑定操作,实际上这个操作是为了下一帧做准备。然后将shader采样出来的数据帧跟Unity纹理ID绑定后就可上屏。
PS:这里没有给出代码的详细逻辑,只是阐述原理,可能存在理解不到位的地方,欢迎指正。
