JMonkeyEngine3——播放视频
概况
有时候我们需要在游戏中播放一些视频,比如恐怖游戏中玩家看到一台电视,我们希望在电视里播放一些片段;或者游戏过程显示一段CG之类的;无论如何,我们都有在游戏内播放视频的需求,JME3官方没有提供内置的功能,但是有一些人制作了相关库,参考:
SimpleMediaPlayerVideoPlayer
后者需要你下载javafx库,并添加相关jar包,但是允许你直接播放mp4,avi等视频格式,不需要任何转换,非常强大。
SimpleMediaPlayer
这里我先简单介绍下如何使用SimpleMediaPlayer这个库(可以直接在JME3商店下载这个库),这个库与其说是播放视频,倒不如说是播放帧序列+音频轨迹,从而还原视频内容。
从商店打开下载这个库之后,实际上是有问题的,将库里的SimpleMediaPlayer.frag替换内容如下:
1 #import "Common/ShaderLib/GLSLCompat.glsllib" 2 3 #ifdef DISCARD_ALPHA 4 uniform float m_AlphaDiscardThreshold; 5 #endif 6 uniform float m_Alpha; 7 uniform vec4 m_Color; 8 uniform sampler2D m_ColorMap; 9 varying vec2 texCoord; 10 varying vec4 vertColor; 11 12 uniform float g_Time; 13 uniform vec2 g_Resolution; 14 15 uniform bool m_EnabledVHS; 16 uniform bool m_EnabledLine; 17 uniform bool m_EnabledGrain; 18 uniform bool m_EnabledScanline; 19 uniform bool m_EnabledVignette; 20 21 float lineHeight = 5.; 22 float lineSpeed = 5.0; 23 float lineOverflow = 1.4; 24 float noise = .70; 25 float pixelDensity = 450.; 26 27 float rand(vec2 co){ 28 return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); 29 } 30 31 32 float rand2(vec2 co){ 33 return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453) * 2.0 - 1.0; 34 } 35 36 float offset(float blocks, vec2 uv) { 37 return rand2(vec2(g_Time, floor(uv.y * blocks))); 38 } 39 vec3 lum = vec3(0.299, 0.587, 0.114); 40 void main(){ 41 vec4 color = vec4(1.0); 42 vec2 uv = texCoord; 43 #ifdef HAS_COLORMAP 44 color = texture2D(m_ColorMap, texCoord); 45 #endif 46 47 #ifdef HAS_COLOR 48 color = m_Color; 49 #endif 50 51 52 //VHS dirt 53 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_VHS) 54 vec2 pos=vec2(0.5+0.5*sin(g_Time),uv.y); 55 vec3 col2=vec3(texture2D(m_ColorMap,pos))*0.2; 56 color.rgb+=col2; 57 #endif 58 59 60 // Moving strip effect 61 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_LINE) 62 float blurLine = clamp(sin(uv.y * lineHeight + g_Time * lineSpeed) + 1.22, 0., 1.); 63 float line = clamp(floor(sin(uv.y * lineHeight + g_Time * lineSpeed) + 1.90), 0., lineOverflow); 64 color.rgb = mix(color.rgb - noise * vec3(.08), color.rgb, line); 65 color.rgb = mix(color.rgb - noise * vec3(.25), color.rgb, blurLine); 66 #endif 67 68 //Grain 69 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_GRAIN) 70 color.rgb *= vec3(clamp(rand(vec2(floor(uv.x * pixelDensity ), floor(uv.y * pixelDensity)) *g_Time / 1000.) + 1. - noise, 0., 1.)); 71 #endif 72 73 //Scanlines 74 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_SCANLINE) 75 float d = length(uv - vec2(0.5,0.5)); 76 float scanline = sin(uv.y* g_Resolution.y )*0.04; 77 color -= vec4(scanline); 78 #endif 79 80 // Vignette 81 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_VIGNETTE) 82 color.rgb *= vec3(1.0 - pow(distance(uv, vec2(0.5, 0.5)), 3.0) * 3.0); 83 #endif 84 85 86 // LCD 87 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_LCD) 88 float scanline2 = clamp( 0.95 + 0.05 * cos( 3.14 * ( uv.y + 0.008 * g_Time ) * 240.0 * 1.0 ), 0.0, 1.0 ); 89 float lins = 0.85 + 0.15 * clamp( 1.5 * cos( 3.14 * uv.x * 640.0 * 1.0 ), 0.0, 1.0 ); 90 color *= vec4(scanline2 * lins * 1.2); 91 gl_FragColor = color*gl_FragColor ; 92 #endif 93 94 // CRT 95 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_CRT) 96 if (mod(gl_FragCoord.x, 3.0 ) <1.0) { 97 color.rgb += vec3(color.r, 0, 0); 98 } else if (mod(gl_FragCoord.x, 3.0 ) <2.0) { 99 color.rgb += vec3(0, color.g, 0); 100 } else if (mod(gl_FragCoord.x, 3.0 ) <3.0) { 101 color.rgb += vec3(0, 0, color.b); 102 } else { 103 color.rgb += vec3(.1); 104 } 105 // 106 if ( mod(gl_FragCoord.y, 3.0 ) < 1.0) { 107 color.rgb += vec3(.1); 108 } 109 #endif 110 111 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_GLITCH) 112 color.r *= texture2D(m_ColorMap, uv + vec2(offset(16.0, uv)*0.1 , 0.0)).r; 113 color.g *= texture2D(m_ColorMap, uv + vec2(offset(8.0, uv)*0.1 * 0.16666666, 0.0)).g; 114 color.b *= texture2D(m_ColorMap, uv + vec2(offset(8.0, uv)*0.1 , 0.0)).b; 115 #endif 116 117 #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_BAW) 118 color = vec4(vec3( color.r * lum.r+color.g * lum.g+color.b * lum.b),1.0); 119 #endif 120 121 122 123 124 #ifdef DISCARD_ALPHA 125 if(color.a < m_AlphaDiscardThreshold){ 126 discard; 127 } 128 #endif 129 130 #ifdef HAS_ALPHA 131 color.a = m_Alpha; 132 #endif 133 134 gl_FragColor = color; 135 }
我猜作者没有做好兼容性测试就提交了代码。
然后我们将库中Media文件夹中的随便一个测试视频(比如960_540.mjpg),对应的音轨audio.ogg,还有库文件SimpleMediaPlayer.java以及相关的材质定义着色器文件复制到我们的工程测试,如下:

注意必须将SimpleMediaPlayer.frag、vert、j3md放到MatDefs/SimpleMediaPlayer目录下,或者你修改SimpleMediaPlayer.java的加载以及j3md相对于frag、vert的读取位置,简单起见你就按照我的截图来放就行了。
我们添加一个java文件,内容如下:
1 import com.jme3.app.SimpleApplication; 2 import de.lessvoid.nifty.Nifty; 3 import de.lessvoid.nifty.NiftyEventSubscriber; 4 import de.lessvoid.nifty.controls.ButtonClickedEvent; 5 import de.lessvoid.nifty.screen.Screen; 6 import de.lessvoid.nifty.screen.ScreenController; 7 8 /** 9 * 测试音视频播放 10 * @author JohnKkk 18402012144@163.com 11 */ 12 public class HelloMedia extends SimpleApplication implements ScreenController{ 13 14 @Override 15 public void simpleInitApp() { 16 } 17 18 @NiftyEventSubscriber(id = "Play") 19 public final void playMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 20 21 } 22 23 @NiftyEventSubscriber(id = "Pause") 24 public final void pauseMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 25 26 } 27 28 @NiftyEventSubscriber(id = "Reset") 29 public final void resetMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 30 31 } 32 33 @Override 34 public void bind(Nifty nifty, Screen screen) { 35 } 36 37 @Override 38 public void onStartScreen() { 39 } 40 41 @Override 42 public void onEndScreen() { 43 } 44 45 public static void main(String[] args) { 46 HelloMedia helloMedia = new HelloMedia(); 47 helloMedia.start(); 48 } 49 50 }
然后创建一个NiftyGui xml,在设计器上调整页面如下:

切换到xml,内容如下:
1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 <nifty xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://nifty-gui.lessvoid.com/nifty-gui" xsi:schemaLocation="https://gitee.com/JoyClm/nifty-gui/raw/1.4/nifty-core/src/main/resources/nifty.xsd https://gitee.com/JoyClm/nifty-gui/raw/1.4/nifty-core/src/main/resources/nifty.xsd"> 3 <useControls filename="nifty-default-controls.xml"/> 4 <useStyles filename="nifty-default-styles.xml"/> 5 <screen id="GScreen0" controller="mygame.HelloMedia"> 6 <layer id="GLayer0" childLayout="absolute"> 7 <control name="button" id="Play" childLayout="center" x="61" y="401" label="Play"/> 8 <control name="button" id="Pause" childLayout="center" x="265" y="402" label="Pause"/> 9 <control name="button" id="Reset" childLayout="center" x="454" y="401" label="Reset"/> 10 </layer> 11 </screen> 12 </nifty>
这里我们让三个按钮分别调用对应的EventBus执行方法,回到java代码,添加如下内容:
1 public class HelloMedia extends SimpleApplication implements ScreenController{ 2 private Nifty m_Nifty; 3 4 @Override 5 public void simpleInitApp() { 6 setDisplayStatView(false); 7 // 初始化Nifty 8 NiftyJmeDisplay niftyDisplay = NiftyJmeDisplay.newNiftyJmeDisplay( 9 assetManager, 10 inputManager, 11 audioRenderer, 12 guiViewPort); 13 m_Nifty = niftyDisplay.getNifty(); 14 // 将NiftyGUI显示对象添加到JME3中 15 guiViewPort.addProcessor(niftyDisplay); 16 17 m_Nifty.fromXml("Interface/IntroInfo.xml", "GScreen0"); 18 19 // 禁用flyCam并显示鼠标 20 flyCam.setEnabled(false); 21 inputManager.setCursorVisible(true); 22 } 23 24 @NiftyEventSubscriber(id = "Play") 25 public final void playMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 26 System.out.println("mygame.HelloMedia.playMedia()"); 27 } 28 29 @NiftyEventSubscriber(id = "Pause") 30 public final void pauseMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 31 System.out.println("mygame.HelloMedia.pauseMedia()"); 32 } 33 34 @NiftyEventSubscriber(id = "Reset") 35 public final void resetMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 36 System.out.println("mygame.HelloMedia.resetMedia()"); 37 } 38 39 @Override 40 public void bind(Nifty nifty, Screen screen) { 41 } 42 43 @Override 44 public void onStartScreen() { 45 } 46 47 @Override 48 public void onEndScreen() { 49 } 50 51 public static void main(String[] args) { 52 HelloMedia helloMedia = new HelloMedia(); 53 helloMedia.start(); 54 } 55 56 }
此时启动JME3程序,点击对应按钮可看到函数调用:

接着我们编写播放视频的逻辑,我们首先需要初始化SimpleMediaPlayer对象,然后进行一些配置,内容如下:
1 /** 2 * 初始化mediaPlayer.<br/> 3 */ 4 private void initMediaPlayer(){ 5 // Init player 6 m_MediaPlayer = new SimpleMediaPlayer(this); 7 // 原视频宽高比 8 int movieWidth = 960; 9 int movieHeight = 540; 10 // 是否保持宽高比 11 boolean keepAspect = true; 12 float scaleRatio = 1.0f; 13 int width = cam.getWidth(); 14 int height = cam.getHeight(); 15 if (keepAspect) { 16 scaleRatio = ((float) width) / ((float) movieWidth); 17 height = (int) (height * scaleRatio); 18 movieWidth = width; 19 movieHeight = height; 20 } 21 // Unique name 22 String screenName = "Intro"; 23 // 播放器空闲时显示的图像。如果为空则使用屏幕颜色 24 String idleImageAssetPath = "Media/idleImageAssetPath.jpg"; 25 // 播放器加载时显示的图像。如果为 Null,则使用 screenColor 26 String loadingImageAssetPath = "Media/loadingImageAssetPath.jpg"; 27 // 播放器暂停时显示的图像。最后一帧为空 28 String pausedImageAssetPath = "Media/pausedImageAssetPath.jpg"; 29 // 如果未提供以上图片则使用的颜色。 30 ColorRGBA screenColor = ColorRGBA.Black; 31 // 需要播放的视频 32 String videoAssetPath = "Media/960_540.mjpg"; 33 // 需要播放的视频音轨(不需要则为null) 34 String audioAssetPath = "Media/audio.ogg"; 35 // 源视频帧率。应与原始帧率一致。大多数情况下为 25 或 30 36 int framesPerSec = 30; 37 // 播放模式。播放一次或循环播放 38 int playBackMode = SimpleMediaPlayer.PB_MODE_ONCE; 39 // 屏幕透明度, 1用于intro, material and menu geometries. 小于1用于HUDgeometries 40 float alpha = 1f; 41 42 Geometry menuGeometry1 = m_MediaPlayer.genGeometry(screenName, movieWidth, movieHeight, idleImageAssetPath, loadingImageAssetPath, pausedImageAssetPath, screenColor, videoAssetPath, audioAssetPath, framesPerSec, playBackMode, alpha); 43 // 保持宽高比的适合,需要调整位置 44 if(keepAspect){ 45 menuGeometry1.setLocalTranslation(0, (cam.getHeight() - height) / 2 , 0); 46 } 47 // 加载视频和音轨资源 48 m_MediaPlayer.loadMedia(); 49 // Add to gui 50 guiNode.attachChild(menuGeometry1); 51 }
该说明的几乎都在注释里了,唯一需要注意的是,SimpleMediaPlayer提供了几个方式,第42行我们可以通过genGeometry()方法返回一个Quad(Geometry),然后将其添加到guiNode进行显示,我们也可以将Quad添加到3D场景中任何地方进行显示;我们还可以调用genState()方法返回一个BaseAppState对象,一旦stateManager.attach(state);方法,就会立即播放,通过stateManager.detach(state);方法移除可停止播放,该方式适合那些过程动画播放CG之类的需求(参考库代码IntroStateTest.java);我们还可以通过genMaterial()返回一个Material对象,然后将其设置给3D场景中的某个物体,这样就可以在某个物体上显示视频,比如恐怖游戏中的电视机(参考库代码ModelMaterialTest.java)。
第48行我们可以在其他地方加载资源,我们还可以调用loadAndPlayMedia()加载完就立即播放,但这个例子我们希望点击按钮进行视频播放、暂停和重置,继续完善EventBus执行函数,如下:
1 @NiftyEventSubscriber(id = "Play") 2 public final void playMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 3 if(m_MediaPlayer.isLoaded()){ 4 if(!m_MediaPlayer.isPlaying()){ 5 m_MediaPlayer.playMedia(); 6 } 7 else if(m_MediaPlayer.isPaused()){ 8 m_MediaPlayer.unpauseMedia(); 9 } 10 } 11 } 12 13 @NiftyEventSubscriber(id = "Pause") 14 public final void pauseMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 15 if(m_MediaPlayer.isLoaded()){ 16 if(!m_MediaPlayer.isPaused()){ 17 m_MediaPlayer.pauseMedia(); 18 } 19 else{ 20 m_MediaPlayer.unpauseMedia(); 21 } 22 } 23 } 24 25 @NiftyEventSubscriber(id = "Reset") 26 public final void resetMedia(final String id, ButtonClickedEvent buttonClickedEvent){ 27 if(m_MediaPlayer.isLoaded()){ 28 m_MediaPlayer.stopMedia(); 29 m_MediaPlayer.loadMedia(); 30 } 31 }
这些代码也是不必多解释的,基本就是字面意思。启动JME3程序,点击play按钮,播放视频和音轨:

库代码的演示案例截图:


视频和音轨转换制作
直接看文档吧,写的已经足够详细了,后续再补充。

浙公网安备 33010602011771号