文章詳情頁
JAVA 3D世界的動畫展示(Part2,使用QuickTime for Java)
瀏覽:118日期:2024-06-12 11:38:25
內(nèi)容: JAVA 3D世界的動畫展示(Part2,使用QuickTime for Java)作者: Andrew Davison 翻譯 Caesh版權(quán)聲明:可以任意轉(zhuǎn)載,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明英文原文地址:http://www.onjava.com/pub/a/onjava/2005/06/01/kgpjava_part2.html中文地址:http://www.matrix.org.cn/resource/article/43/43676_JAVA_3D.html關(guān)鍵詞: Java 3D QuickTime在這一系列的第一部分中,我描述了在JMF(JAVA媒體幀工作器 下同)的幫助下,怎樣將一部電影片斷插入JAVA 3D場景中。這個(gè)執(zhí)行過程使用Model-View-Controller設(shè)計(jì)模式。。動畫屏幕是由JMFMovieScreen類表示的視覺元素。動畫模型部分由JMFSnapper類控制。java 3D行為類,TimeBehavior,是動畫中引起幀周期性恢復(fù)的控制類在這篇文章中,我將使用QTJ(QuickTime for Java 下同)再次解析動畫成份。QTJ提供一個(gè)覆蓋QuickTime API的依賴對象的java,使之可以展示,編輯和創(chuàng)建QuickTime動畫;捕獲視頻 音頻;展示2D和3D動畫。QuickTime可用于Mac和Windows系統(tǒng)。關(guān)于QTJ的安裝.文檔和實(shí)例細(xì)節(jié)可在developer.apple.com/quicktime/qtjava查詢。由設(shè)計(jì)模式作出的推論,只在動畫類JMFSnapper被QTSnapper取代時(shí),QTJ取代JMF在應(yīng)用中有微小的作用。圖1:兩幅QTJ情況下的3D動畫截屏,右邊圖片采取顯示屏背面視角 從圖1大致看出,QTJ-based 和JMF成像效果沒有明顯區(qū)別。然而,通過更仔細(xì)得比較可以看出有兩個(gè)變化:QTJ動畫有輕微的被像素化,播放的更慢。像素化(像素化是對內(nèi)部像素對于觀看者易見的數(shù)字圖像的顯示。當(dāng)一些用于普通的計(jì)算機(jī)顯示的低分辨率圖像被投射到一個(gè)大的顯示器上,每一個(gè)像素都會變得單獨(dú)可見,這種不常發(fā)生的現(xiàn)象就叫做像素化。)是由于原始動畫從MPEG轉(zhuǎn)換為QuickTime's MOV格式引起的,它可由更好的轉(zhuǎn)換方法矯正。速度問題更基礎(chǔ):它與QTSnapper的潛在執(zhí)行有關(guān)。這篇文章的重點(diǎn)為?。執(zhí)行QTSnapper的兩種主要方法的討論.一種方法是將動畫里的每一幀都提出來顯示在屏幕上。另一種方法依靠當(dāng)前的時(shí)間提出幀.第二種方法意味著可能將會遺漏部分幀,畫面顫抖,但遺漏 可以使播放更快。一些簡單的FPS(frame-per-second)度量器的介紹.我將用它們這兩種方法的相對速度,探測遺漏幀數(shù)1.. 此山非彼山與第一部分一樣,編碼將利用兩個(gè)大型的API,在這里沒有時(shí)間介紹利用的細(xì)節(jié)了。我將再次使用java 3D,但API媒體將由JMF轉(zhuǎn)變?yōu)镼TJ。在我的O'Reilly book, Killer Game Programming in Java (KGPJ)中有大量關(guān)于java 3D的信息,還有圖1的原碼。我將不會解釋動畫屏幕和動畫更新行為,因?yàn)樗鼈兣c第一部分相同。在QTJ技術(shù)中我將會使用QTSnapper從動畫中提取幀2.應(yīng)用的兩種看法圖2:應(yīng)用流程圖 此圖表與第一篇文章中的幾乎相同QuickTime動畫由QTSnapper類加載,動畫屏幕由QTMovieScreen創(chuàng)建.每40毫秒,TimeBehavior對象調(diào)用QTMovieScreen中的nextFrame()方法.然后調(diào)用QTSnapper中的getFrame()方法獲取動畫中的一個(gè)幀.依次循環(huán).JMFSnapper與QTSnapper之間有一個(gè)很重要的不同.JMFSnapper返回一個(gè)幀,這個(gè)幀是動畫播放時(shí)的當(dāng)前幀.而QTSnapper返回動畫中的幀根據(jù)遞增的索引.例如.當(dāng)getFrame()方法在JMFSnapper被反復(fù)調(diào)用時(shí),也許會重新得到幀1,3,6,9等等,它是由方法何時(shí)被調(diào)用與動畫播放速度決定的.當(dāng)getFrame()方法在QTSnapper被調(diào)用,它將會返回幀1,2,3,4等等.圖3:UML類的應(yīng)用圖表,僅列出公共方法. 此圖表除了動畫屏幕名和動畫類名(QTMovieScreen 和 QTSnapper)外,與第一篇文章的相同.事實(shí)上,只有Snapper類的內(nèi)部執(zhí)行被改變.JMF Movie3D應(yīng)用程序和QTJ-based版本之間的改動需要Snapper被重寫. // global variableprivate QTSnapper snapper; // was JMFSnapper// in the constructor, load the movie in fnmsnapper = new QTSnapper(fnm);這兩處改動是由于須將JMFMovieScreen重命名為QTMovieScreen. 這個(gè)例子中的所有代碼,和文章的早期版本,可以在KGPJ website查詢到.3.一幀一幀的動畫理解QTSnapper的內(nèi)在工作機(jī)理,可以幫助我們對QuickTime動畫構(gòu)造有一個(gè)大致的認(rèn)識.每一幅動畫可以理解為視頻軌跡和音頻軌跡在相同時(shí)間上的重疊.圖4是這種思想的圖示 圖4:QuickTime動畫的內(nèi)部構(gòu)造機(jī)理每個(gè)軌跡控制著其自身數(shù)據(jù),例如它包含的媒體類型和媒體本身.媒體容器(media container)有它自己的數(shù)據(jù)結(jié)構(gòu),包括它的持續(xù)時(shí)間和播放率(每秒播放抽樣數(shù)).媒體是由一組抽樣(或幀)組成,第一個(gè)抽樣時(shí)間為0(與媒體時(shí)間有關(guān)).抽樣是被變址的,第一個(gè)抽樣在1位置(非0).圖5大致描述了QuickTime的軌跡和媒體結(jié)構(gòu) 圖5:QuickTime軌跡和媒體的內(nèi)在構(gòu)造機(jī)理想要得到更多信息,請查詢QuickTime指南的movie section打開動畫視頻媒體QTSnapper構(gòu)造器打開動畫:// globalsprivate boolean isSessionOpen = false;private OpenMovieFile movieFile;private Movie movie;// in the constructor,// start a QuickTime sessionQTSession.open();isSessionOpen = true;// open the moviemovieFile = OpenMovieFile.asRead( new QTFile(fnm) );movie = Movie.fromFile(movieFile);在QuickTime使用之前調(diào)用QTSession.open()方法將其初始化.在終止時(shí)相應(yīng)的調(diào)用QTSession.close()方法.軌跡定位和媒體訪問// more globalsprivate Track videoTrack;private Media vidMedia;// in the constructor, // extract the video track from the movievideoTrack = movie.getIndTrackType(1, StdQTConstants.videoMediaType, StdQTConstants.movieTrackMediaType);if (videoTrack == null) { System.out.println('Sorry, not a video'); System.exit(0);}// get the media used by the video trackvidMedia = videoTrack.getMedia();一旦媒體打開,從中提取各種信息// 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 = vidMedia.getSampleCount();sampIdx = 1; // get first sample in the trackmediaSample = vidMedia.getSample(0, vidMedia.sampleNumToMediaTime(sampIdx).time,1);// store width and height of image in the sampleImageDescription imgDesc = ImageDescription) mediaSample.description;width = imgDesc.getWidth();height = imgDesc.getHeight();sampIdx作為計(jì)數(shù)器將在抽樣中被重復(fù)調(diào)用(抽樣于位置1開始).動畫圖像的寬度和高度由第一個(gè)抽樣獲得,接下來所有的抽樣都是同樣的尺寸.測算 FPS由QTSnapper返回的 幀數(shù)/秒 將在稍后被用作類的不同使用方法的比較參數(shù).構(gòu)造器中必要的參數(shù)已被初始化. // frame rate globalsprivate long startTime;private long numFramesMade;// initialize them in the constructorstartTime = System.currentTimeMillis(); numFramesMade = 0;結(jié)束將應(yīng)用程序結(jié)束時(shí),QTSnapper類中的stopMovie()方法將被調(diào)用.它報(bào)告FPS,關(guān)閉QuickTime.// globalsprivate DecimalFormat frameDf = new DecimalFormat('0.#'); // 1 dpsynchronized public void stopMovie(){ if (isSessionOpen) { // report frame rate long duration = System.currentTimeMillis() - startTime; double frameRate = ((double) numFramesMade*1000.0)/duration; System.out.println('FPS: ' + frameDf.format(frameRate)); QTSession.close(); // close down QuickTime isSessionOpen = false; }}由于stopMovie()和getFrame()是同步的,所以從動畫中提取幀和QuickTime關(guān)閉在時(shí)間上是不可能同時(shí)進(jìn)行.緩存幀getFrame()返回一次抽樣樣品,稱作BufferedImage對象.被選擇的幀利用索引指數(shù)存貯在sampIdx.// globalsprivate BufferedImage img, formatImg;synchronized public BufferedImage getFrame(){ if (!isSessionOpen) return null; if (sampIdx> numSamples) // start back with the first sample sampIdx = 1; try { /* Get the sample starting at the specified index time */ TimeInfo ti = vidMedia.sampleNumToMediaTime(sampIdx); mediaSample=vidMedia.getSample(0,ti.time,1); sampIdx++; writeToBufferedImage(mediaSample, img); // resize img, writing it to formatImg Graphics g = formatImg.getGraphics(); g.drawImage(img, 0, 0, FORMAT_SIZE, FORMAT_SIZE, null); // Overlay current time on image g.setColor(Color.RED); g.setFont( new Font('Helvetica', Font.BOLD, 12)); g.drawString(timeNow(), 5, 14); g.dispose(); numFramesMade++; // count frame } catch (Exception e) { System.out.println(e); formatImg = null; } return formatImg;} // end of getFrame()從QTJ的媒體類中調(diào)用getSample()方法可以容易的獲得抽樣.不幸的是,將抽樣轉(zhuǎn)化為BufferedImage仍然是個(gè)棘手的問題.豐富的細(xì)節(jié)和注釋,可以在編碼中研究.從抽樣種萃取一個(gè)“原始圖像,然后將其減壓寫成一個(gè)QuickTime版本的Graphics對象.Graphics對象中的無壓縮數(shù)據(jù)被拷貝成為另一個(gè)“原始圖像,然后成為一個(gè)像素?cái)?shù)組.最后,這個(gè)數(shù)組被寫入空BufferedImage的DataBuffer.程序能工作嗎?能順利工作嗎? 是的,Movie3D顯示動畫,但是比較大的動畫播放的比較慢.這是由于getFrame()方法在幀補(bǔ)給上的緩慢,它可以通過FPS數(shù)量進(jìn)行量化.對于圖1的動畫,在Windows 98系統(tǒng)FPS之大概在15-17幀/秒.然而,TimeBehavior對象要求每40毫秒更新,轉(zhuǎn)化為幀數(shù)大概在25FPS.getFrame()方法之所以慢是由于抽樣轉(zhuǎn)化為BufferedImage的時(shí)間消耗.由于當(dāng)前調(diào)用的getFrame()方法在轉(zhuǎn)化幀時(shí)停頓,更多的請求將被延遲直到當(dāng)前轉(zhuǎn)化完成.我將考慮兩種解決這一問題的方法:允許getFrame()方法在處理請求時(shí)遺漏幀,和在getFrame()方法中使用不同的轉(zhuǎn)化方法.我將輪流考慮這兩種方法,以幀遺漏開始.4. 遺漏幀的動畫新的Snapper類,QTSnapper1,仍然返回一個(gè)幀當(dāng)getFrame()方法被調(diào)用時(shí).與QTSnapper的不同在于它提供的類相應(yīng)于當(dāng)前動畫的執(zhí)行時(shí)間.例如,getFrame()也許重新獲得幀1,2,5,8,14等等,依賴于方法的調(diào)用時(shí)間.因此,動畫以一個(gè)很好的速度播放,但是由于幀的遺漏可能導(dǎo)致畫面顫抖.對比的看,QTSnapper將會返回所有的幀數(shù)(1,2,3,4等等),但是由于調(diào)用getFrame()方法的延遲可能會導(dǎo)致動畫播放緩慢.然而,畫面將不會出現(xiàn)顫抖,由于沒有幀被遺漏.QTSnapper1種的關(guān)鍵部分是對于動畫的“當(dāng)前執(zhí)行時(shí)間理念.我的方法是當(dāng)getFrame()方法被調(diào)用估計(jì)QTSnapper的當(dāng)前執(zhí)行時(shí)間,將它轉(zhuǎn)變?yōu)閯赢媹?zhí)行時(shí)間,然后作為樣本給定值.QTSnapper1有與QTSnapper相同的公共方法,所以它只需作出微小的改變就可用于QTMovieScreen.只有在動畫播放時(shí)差別才變得明顯,在以很好的速度播放時(shí)會發(fā)出喳喳聲.詳細(xì)的測量,以圖1的“明顯幀率比較,31FPS對比QTSnapper的16FPS.打開動畫視頻媒體QTSnapper1訪問動畫視頻的過程與QTSnapper相同.一旦視頻可被利用,個(gè)別的媒體值將被儲存并稍后被getFrame()方法調(diào)用:// globalsprivate Media vidMedia;private int numSamples;private int timeScale; // media's time scaleprivate int duration; // duration of the media// in the constructor,// get the media used by the video trackvidMedia = videoTrack.getMedia();// store media details for laternumSamples = vidMedia.getSampleCount();timeScale = vidMedia.getTimeScale();duration = vidMedia.getDuration();獲取一幀getFrame()方法中的新要素是它怎樣計(jì)算被用于訪問詳悉抽樣的給定值.方法的其他部分,writeToBufferedImage()的調(diào)用和當(dāng)前圖像的編碼與QTSnapper相同. // globalsprivate MediaSample mediaSample;private BufferedImage img, formatImg;private int prevSampNum;private int sampNum = 0;private int numCycles = 0;private int numSkips = 0;// inside getFrame(),// get the time in secs since start of QTSnapper1double currTime = ((double)(System.currentTimeMillis() - startTime))/1000.0;// use the video's time scaleint videoCurrTime = ((int)(currTime*timeScale)) % duration;try { // backup the previous sample number prevSampNum = sampNum; // calculate the new sample number sampNum = vidMedia.timeToSampleNum( videoCurrTime).sampleNum; // if no sample change, then don't generate // a new image if (sampNum == prevSampNum) return formatImg; if (sampNum < prevSampNum) numCycles++; // movie has just started over // record the number of frames skipped int skipSize = sampNum - (prevSampNum+1); if (skipSize> 0) // skipped frame(s) numSkips += skipSize; // get a single sample starting at the // sample number's time TimeInfo ti = vidMedia.sampleNumToMediaTime(sampNum); mediaSample = vidMedia.getSample(0,ti.time,1);getFrame()在很短的時(shí)間內(nèi)計(jì)算當(dāng)前時(shí)間,從QTSnapper1開始時(shí)測量:double currTime = ((double)(System.currentTimeMillis() - startTime))/1000.0;每一個(gè)QuickTime的媒體片斷都有它自己的時(shí)間刻度,ts,例如一個(gè)個(gè)體的時(shí)間是1/ts 秒.恒定的時(shí)間刻度必須由currTime方法增加以獲得當(dāng)前動畫時(shí)間.int videoCurrTime = ((int)(currTime*timeScale)) % duration;以媒體持續(xù)時(shí)間為模校正刻度時(shí)間,允許動畫在當(dāng)前時(shí)間已超過動畫結(jié)束時(shí)間重復(fù).調(diào)用Media's timeToSampleNum()方法將抽樣序列數(shù)以刻度時(shí)間顯示:sampNum = vidMedia.timeToSampleNum( videoCurrTime).sampleNum;上次抽樣序列號存儲在prevSampNum,以便允許實(shí)現(xiàn)大量的檢測和計(jì)算.如果新的抽樣序列號與上次取樣的序列號相同,就不需要檢查將抽樣轉(zhuǎn)化為BufferedImage的過程;getFrame()可以返回現(xiàn)有的formatImg接口.如果新的抽樣序列號小于上次取樣的序列號,這就意味著動畫開始循環(huán),動畫起始幀將被顯示.這就是被注冊的numCycles增加.如果新的抽樣序列號大于上次取樣的序列號+1,意味著被遺漏的幀序列被記錄上.結(jié)束stopMovie()打印出FPS,關(guān)閉QuickTime進(jìn)程,與QTSnapper類中的stopMovie()方法相同.同時(shí)它也報(bào)告附加信息: long totalFrames = (numCycles * numSamples) + sampNum;// report percentage of skipped framesdouble skipPerCent = (double)(numSkips * 100) / totalFrames;System.out.println('Percentage frames skipped: '+ frameDf.format(skipPerCent) + '%');// 'apparent' FPS (AFPS)double appFrameRate = ((double) totalFrames * 1000.0) / duration;System.out.println('AFPS: ' + frameDf.format(appFrameRate)); // 1 dpappFrameRate方法描述“顯性幀頻,就是從QTSnapper1開始使用時(shí)的抽樣總量.感覺上的“顯性 是因?yàn)椴皇撬械某闃佣加斜伙@示出來的必要.程序能工作嗎?能順利工作嗎?QTSnapper被QTSnapper1取代后,緩慢的動畫(圖1所示)將會播放的更快.結(jié)束時(shí)間時(shí),據(jù)報(bào)告的明顯幀頻是31FPS,實(shí)際上的幀頻大概在16FPS,遺漏頻大概占總數(shù)的50%.驚奇的是,大量遺漏頻的并沒有顯示在屏幕上.對與另外一些比較小的動畫,速度的增加幾乎是察覺不到的;遺漏頻率大概在5%-10%.不幸的是,仍然還有兩個(gè)問題:幀遺漏產(chǎn)生的雜亂像素和對遺漏幀的數(shù)量的控制.雜亂像素?zé)o論何時(shí),只要QTSnapper1遺漏一個(gè)動畫幀,下一個(gè)幀將會包含一些雜亂像素.圖6是效果顯示圖.從一段早期視頻截得的錯(cuò)誤像素使用值. 圖6 雜亂圖像截屏問題是我所有的視頻事例都是使用temporal compression,它是一種利用連續(xù)視頻幀之間類似處的一種壓縮方法.假如兩個(gè)連續(xù)視頻幀有相同的背景,就不用再次存儲背景.只有兩個(gè)幀之間的不同才會被存儲.這項(xiàng)技術(shù),被用在幾乎所有的流行視頻格式,意味著從一個(gè)幀中抽取圖像依賴這個(gè)幀和此前的幾個(gè)幀.暫時(shí)解壓由在writeToBufferedImage()方法中的QuickTime DSequence對象處理.DSequence構(gòu)造器詳細(xì)說明了QuickTime在解壓過程中應(yīng)該使用的一種屏幕外圖像緩沖器.幀圖像被寫入緩沖器,在那里與早期的幀數(shù)據(jù)結(jié)合.結(jié)合后圖像被傳給轉(zhuǎn)化的下一過程.QTSnapper1順次解壓時(shí)工作的很好,沒有遺漏幀,但是如果產(chǎn)生遺漏會導(dǎo)致錯(cuò)誤.例如,當(dāng)QTSnapper1遺漏幀5和6,然后解壓幀7時(shí)會發(fā)生什莫?幀被寫入QuickTime圖像緩沖器,與此前幀的數(shù)據(jù)結(jié)合.然而,幀5和6的數(shù)據(jù)丟失,所以結(jié)合后的圖像會有錯(cuò)誤.簡單的講,圖像中的雜亂像素是由動畫的暫時(shí)壓縮引起的.一個(gè)可選擇的辦法是使用空間壓縮技術(shù),既獨(dú)立的壓縮每個(gè)幀.這就意味著解壓時(shí)幀里所有的信息都會從幀本身被釋放出來,不需要檢查早期的幀.QuickTime MOV支持名為Motion-JPEG(M-JPEG)的空間壓縮方案.我使用QuickTime 6 Pro 中的工具將圖1以M-JPEG A編碼形式存儲為MOV文件.當(dāng)這個(gè)動畫用Movie3D播放時(shí),沒有發(fā)生畫面顫抖.限制幀遺漏QTSnapper1的另一個(gè)問題時(shí)getFrame()沒有對可能遺漏幀的數(shù)量進(jìn)行限制.在我的測試中,遺漏幀的數(shù)量被限制在3個(gè)以下.然而,如果getFrame()用于一個(gè)很大抽樣的轉(zhuǎn)化,那末它的緩慢增加將會導(dǎo)致更多幀的遺漏.動畫質(zhì)量將會發(fā)生明顯的惡化.5. 試著使圖像更快在QTSnapper和QTSnapper1中使用的sample-to-BufferedImage轉(zhuǎn)換方法(writeToBufferedImage())是由Chris W. Johnson在實(shí)例中獲得的.有沒有一種更快的從抽樣中隨取圖像的方法呢?QTJ方面的權(quán)威書籍:QuickTime for Java: A Developer's Notebook,作者:Chris Adamson, O'Reilly,2005.1月出版.卷五,covering QuickDraw,中的ConvertToJavaImageBetter.java實(shí)例,展示了怎樣獲得一個(gè)PICT圖像的抽樣并將其轉(zhuǎn)化為Java圖像對象.這個(gè)例子也可在quicktime-java mailing list中找到.我使用Adamson碼作為另一個(gè)Snapper類的編碼基礎(chǔ),稱之為QTSnapper2.它可以無遺漏的返回幀,與QTSnapper的方法一樣,但是使用PICT-to-Image轉(zhuǎn)化.在一部小動畫中,QTSnapper2與QTSnapper的表現(xiàn)沒有區(qū)別,但對于比較大的動畫例如例1,它的平局幀頻大概為9FPS,對比與QTSnapper的16FPS.換句話說,PICT-based轉(zhuǎn)化慢于Johnson技術(shù)--------------------------------------------------------------------------------------Andrew Davison 是個(gè)教育者,研究員,及作家。以前曾在墨爾本大學(xué)的計(jì)算機(jī)科學(xué)部門工作,現(xiàn)居住在泰國,并在Prince of Songkla大學(xué)任教。 Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd JAVA 3D世界的動畫展示(Part2,使用QuickTime for Java)作者: Andrew Davison 翻譯 Caesh版權(quán)聲明:可以任意轉(zhuǎn)載,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲
標(biāo)簽:
Java
相關(guān)文章:
1. Python使用urlretrieve實(shí)現(xiàn)直接遠(yuǎn)程下載圖片的示例代碼2. Python環(huán)境使用OpenCV檢測人臉實(shí)現(xiàn)教程3. Python:UserWarning:此模式具有匹配組。要實(shí)際獲得組,請使用str.extract4. 如何使用ASP.NET Core 配置文件5. python 使用多線程創(chuàng)建一個(gè)Buffer緩存器的實(shí)現(xiàn)思路6. ASP.NET MVC使用Boostrap實(shí)現(xiàn)產(chǎn)品展示、查詢、排序、分頁7. 在Dreamhost上使用virtualenv更新新的Django和Python 2.7。*(with passenger)8. php使用正則驗(yàn)證密碼字段的復(fù)雜強(qiáng)度原理詳細(xì)講解 原創(chuàng)9. python 使用elasticsearch 實(shí)現(xiàn)翻頁的三種方式10. Java使用Tesseract-Ocr識別數(shù)字
排行榜
