在这一系列的第一部分中我描述了在JMF(JAVA媒体帧工作器 下同)的帮助下怎样将一部电影片断插入JAVA D场景中这个执行过程使用ModelViewController设计模式
动画屏幕是由JMFMovieScreen类表示的视觉元素
动画模型部分由JMFSnapper类控制
java D行为类TimeBehavior是动画中引起帧周期性恢复的控制类
在这篇文章中我将使用QTJ(QuickTime for Java 下同)再次解析动画成份QTJ提供一个覆盖QuickTime API的依赖对象的java使之可以展示编辑和创建QuickTime动画捕获视频 音频展示D和D动画QuickTime可用于Mac和Windows系统关于QTJ的安装文档和实例细节可在/quicktime/qtjava查询
由设计模式作出的推论只在动画类JMFSnapper被QTSnapper取代时QTJ取代JMF在应用中有微小的作用
图两幅QTJ情况下的D动画截屏右边图片采取显示屏背面视角
从图大致看出QTJbased 和JMF成像效果没有明显区别
然而通过更仔细得比较可以看出有两个变化QTJ动画有轻微的被像素化播放的更慢像素化(像素化是对内部像素对于观看者易见的数字图像的显示当一些用于普通的计算机显示的低分辨率图像被投射到一个大的显示器上每一个像素都会变得单独可见这种不常发生的现象就叫做像素化)是由于原始动画从MPEG转换为QuickTimes MOV格式引起的它可由更好的转换方法矫正速度问题更基础它与QTSnapper的潜在执行有关
这篇文章的重点为?
执行QTSnapper的两种主要方法的讨论一种方法是将动画里的每一帧都提出来显示在屏幕上另一种方法依靠当前的时间提出帧第二种方法意味着可能将会遗漏部分帧画面颤抖但遗漏 可以使播放更快
一些简单的FPS(framepersecond)度量器的介绍我将用它们这两种方法的相对速度探测遗漏帧数
此山非彼山
与第一部分一样编码将利用两个大型的API在这里没有时间介绍利用的细节了我将再次使用java D但API媒体将由JMF转变为QTJ
在我的OReilly book Killer Game Programming in Java (KGPJ)中有大量关于java D的信息还有图的原码
我将不会解释动画屏幕和动画更新行为因为它们与第一部分相同
在QTJ技术中我将会使用QTSnapper从动画中提取帧
应用的两种看法
图应用流程图此图表与第一篇文章中的几乎相同
QuickTime动画由QTSnapper类加载动画屏幕由QTMovieScreen创建每毫秒TimeBehavior对象调用QTMovieScreen中的nextFrame()方法然后调用QTSnapper中的getFrame()方法获取动画中的一个帧依次循环
JMFSnapper与QTSnapper之间有一个很重要的不同JMFSnapper返回一个帧这个帧是动画播放时的当前帧而QTSnapper返回动画中的帧根据递增的索引
例如当getFrame()方法在JMFSnapper被反复调用时也许会重新得到帧等等它是由方法何时被调用与动画播放速度决定的当getFrame()方法在QTSnapper被调用它将会返回帧等等
图UML类的应用图表仅列出公共方法此图表除了动画屏幕名和动画类名(QTMovieScreen 和 QTSnapper)外与第一篇文章的相同事实上只有Snapper类的内部执行被改变
JMF MovieD应用程序和QTJbased版本之间的改动需要Snapper被重写
// global variableprivate QTSnapper snapper;// was JMFSnapper// in the constructor load the movie in fnmsnapper = new QTSnapper(fnm);
这两处改动是由于须将JMFMovieScreen重命名为QTMovieScreen
这个例子中的所有代码和文章的早期版本可以在KGPJ website查询到
一帧一帧的动画
理解QTSnapper的内在工作机理可以帮助我们对QuickTime动画构造有一个大致的认识每一幅动画可以理解为视频轨迹和音频轨迹在相同时间上的重叠图是这种思想的图示
图QuickTime动画的内部构造机理每个轨迹控制着其自身数据例如它包含的媒体类型和媒体本身媒体容器(media container)有它自己的数据结构包括它的持续时间和播放率(每秒播放抽样数)媒体是由一组抽样(或帧)组成第一个抽样时间为(与媒体时间有关)抽样是被变址的第一个抽样在位置(非)
图大致描述了QuickTime的轨迹和媒体结构
图QuickTime轨迹和媒体的内在构造机理想要得到更多信息请查询QuickTime指南的movie section
打开动画视频媒体
QTSnapper构造器打开动画
// globalsprivate boolean isSessionOpen = false;private OpenMovieFile movieFile;private Movie movie;// in the constructor// start a QuickTime sessionQTSessionopen();isSessionOpen = true;// open the moviemovieFile =OpenMovieFileasRead( new QTFile(fnm) );movie = MoviefromFile(movieFile);
在QuickTime使用之前调用QTSessionopen()方法将其初始化在终止时相应的调用QTSessionclose()方法
轨迹定位和媒体访问
// more globalsprivate Track videoTrack;private Media vidMedia;// in the constructor // extract the video track from the movievideoTrack = moviegetIndTrackType( StdQTConstantsvideoMediaTypeStdQTConstantsmovieTrackMediaType);if (videoTrack == null) {Systemoutprintln(Sorry not a video);Systemexit();}// get the media used by the video trackvidMedia = videoTrackgetMedia();
一旦媒体打开从中提取各种信息
// more globalsprivate MediaSample mediaSample;private int numSamples;// number of samplesprivate int sampIdx; // current sample indexprivate int width; // frame widthprivate int height;// frame height// in the constructornumSamples = vidMediagetSampleCount();sampIdx = ; // get first sample in the trackmediaSample = vidMediagetSample( vidMediasampleNumToMediaTime(sampIdx)time);// store width and height of image in the sampleImageDescription imgDesc = ImageDescription) mediaSampledescription;width = imgDescgetWidth();height = imgDescgetHeight();
sampIdx作为计数器将在抽样中被重复调用(抽样于位置开始)
动画图像的宽度和高度由第一个抽样获得接下来所有的抽样都是同样的尺寸
测算 FPS
由QTSnapper返回的 帧数/秒 将在稍后被用作类的不同使用方法的比较参数构造器中必要的参数已被初始化
// frame rate globalsprivate long startTime;private long numFramesMade;// initialize them in the constructorstartTime = SystemcurrentTimeMillis(); numFramesMade = ;
结束
将应用程序结束时QTSnapper类中的stopMovie()方法将被调用它报告FPS关闭QuickTime
// globalsprivate DecimalFormat frameDf = new DecimalFormat(#); // dpsynchronized public void stopMovie(){if (isSessionOpen) {// report frame ratelong duration = SystemcurrentTimeMillis() startTime;double frameRate =((double) numFramesMade*)/duration;Systemoutprintln(FPS: + frameDfformat(frameRate));QTSessionclose();// close down QuickTimeisSessionOpen = false;}}
由于stopMovie()和getFrame()是同步的所以从动画中提取帧和QuickTime关闭在时间上是不可能同时进行
缓存帧
getFrame()返回一次抽样样品称作BufferedImage对象被选择的帧利用索引指数存贮在sampIdx
// globalsprivate BufferedImage img formatImg;synchronized public BufferedImage getFrame(){if (!isSessionOpen)return null;if (sampIdx > numSamples) // start back with the first samplesampIdx = ;try {/* Get the sample starting at thespecified index time */TimeInfo ti =vidMediasampleNumToMediaTime(sampIdx);mediaSample=vidMediagetSample(titime);sampIdx++;writeToBufferedImage(mediaSample img);// resize img writing it to formatImgGraphics g = formatImggetGraphics();gdrawImage(img FORMAT_SIZE FORMAT_SIZE null); // Overlay current t