图片播放技术总结
1 概述
由于工作的項(xiàng)目上的需求,需要在瀏覽器上不間斷的播放圖片,即像播放視頻一樣播放圖片。
后端支持采用Java實(shí)現(xiàn),需要用Java編寫(xiě)一個(gè)Http服務(wù)器,并提供WebSocket服務(wù)。前后端通過(guò)Http鏈接或WebSocket提供圖片瀏覽服務(wù),前端采用JS輪詢(xún)或WebSocket推送的方式獲取圖片,瀏覽器顯示圖片有兩種方式:一種是采用連續(xù)切換圖片源,實(shí)現(xiàn)播放效果;另一種采用將圖片畫(huà)在canvas上面,實(shí)現(xiàn)播放。
要完成這個(gè)功能涉及到以下技術(shù):
- Http服務(wù)器的實(shí)現(xiàn)
- 高速的讀文件
- WebSocket原理及實(shí)現(xiàn)
- 基于瀏覽器pull方式的http資源獲取
- 基于服務(wù)器端push方式的http資源獲取
- JS播放圖片幀的性能
2 技術(shù)分析
2.1 Http服務(wù)器的實(shí)現(xiàn)
實(shí)現(xiàn)HTTP服務(wù)器比較容易,實(shí)現(xiàn)方式也有如下多種:
- 基于jdk中com.sun包下面的HttpServer來(lái)實(shí)現(xiàn)。(不推薦,com.sun不在java規(guī)范內(nèi),jdk升級(jí)可能會(huì)不兼容)
- 基于jetty或tomcat的嵌入式包來(lái)實(shí)現(xiàn)。此方式基于Servlet規(guī)范來(lái)實(shí)現(xiàn)的,較簡(jiǎn)單且易于理解。
- 基于netty的方式實(shí)現(xiàn)。性能好,需要對(duì)NIO有了解,編程難度相對(duì)大一些。
- 基于vert.x的實(shí)現(xiàn)。這種方式底層還是采用netty實(shí)現(xiàn),較簡(jiǎn)單,但是也需要熟悉vert.x的編程模型。
我們采用jetty的方式實(shí)現(xiàn),基于servlet3.0規(guī)范,可以支持異步請(qǐng)求方式。代碼如下:
public static void main(String[] args){Server server = new Server();ServerConnector connector = new ServerConnector(server);connector.setPort(8080);server.addConnector(connector);ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);context.setContextPath("/");context.addServlet(new ServletHolder(new HelloServlet()), "/hello"); server.setHandler(context);try {// Initialize javax.websocket layerServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);// Add WebSocket endpoint to javax.websocket layerwscontainer.addEndpoint(EventSocket.class);server.start();server.dump(System.err);server.join();} catch (Throwable t){t.printStackTrace(System.err);} }2.2 高速的讀文件
關(guān)于Java高速讀取文件可參考這篇文章:How to read files quickly
這篇文章得出四個(gè)結(jié)論:
- 為了減少I(mǎi)/O操作,每次應(yīng)該讀一個(gè)byte數(shù)組,而不是一個(gè)byte字節(jié),8K的byte數(shù)組就是一個(gè)好的選擇。
- 為了減少方法調(diào)用的開(kāi)銷(xiāo),每次應(yīng)該獲取一個(gè)byte數(shù)組的數(shù)據(jù),而不是一個(gè)byte字節(jié)。
- 為了減少線(xiàn)程同步鎖的開(kāi)銷(xiāo),要么減少線(xiàn)程同步方法的調(diào)用,要么采用非線(xiàn)程安全的類(lèi),如:FileChannel 和MappedByteBuffer。
- 為了減少在JVM/OS、internal buffers和應(yīng)用程序數(shù)組之間的數(shù)據(jù)拷貝,要么使用帶有內(nèi)存映射的FileChannel類(lèi),要么使用a direct or wrapped array ByteBuffer.
下面提高兩種高速讀取文件方法:
for (String filePath : fileList){try(FileChannel ch = new RandomAccessFile(filePath, "r").getChannel()){int size = (int) ch.size();MappedByteBuffer buf = ch.map(MapMode.READ_ONLY, 0, size);// 處理buf....} catch (IOException e) {e.printStackTrace();} } for (String filePath : fileList){try (SeekableByteChannel sbc = Files.newByteChannel(Paths.get(filePath), StandardOpenOption.READ)) {ByteBuffer buf = ByteBuffer.allocate(10);// Read the bytes with the proper encoding for this platform. If// you skip this step, you might see something that looks like// Chinese characters when you expect Latin-style characters.//String encoding = System.getProperty("file.encoding");while (sbc.read(buf) > 0) {buf.rewind();// 處理buf...//System.out.print(Charset.forName(encoding).decode(buf));buf.flip();}} catch (IOException x) {System.out.println("caught exception: " + x);} }2.3 WebSocket原理及實(shí)現(xiàn)
WebSocket的原理以及與Http區(qū)別可以參考:WebSocket與http的區(qū)別,以及它的原理,總體來(lái)說(shuō),原理及區(qū)別如下:
- WebSocket和Http協(xié)議沒(méi)有太大的關(guān)系,WS只是借助Http實(shí)現(xiàn)了第一次握手,之后從http協(xié)議upgrade為ws://協(xié)議。
- WS是持久性連接(類(lèi)似socket),而HTTP的短連接、長(zhǎng)連接都不是持久的。
- WS協(xié)議是支持全雙工的,可以pull,亦可以push。
用Java實(shí)現(xiàn)WebSocket服務(wù)端:
@ClientEndpoint @ServerEndpoint(value="/events/") public class EventSocket{private static int DEFAULT_BUFFER_SIZE = 128 * 1024;// 8192private byte[] bytes;@OnOpenpublic void onWebSocketConnect(Session session, EndpointConfig config) {session.setMaxBinaryMessageBufferSize(DEFAULT_BUFFER_SIZE);}@OnMessagepublic void onWebSocketText(Session session, String message) throws Exception{System.out.println("Received TEXT message: " + message);bytes = ... // 讀取圖片文件字節(jié)// 發(fā)送圖片文件session.getAsyncRemote().sendBinary( ByteBuffer.wrap( this.bytes ) );}@OnClosepublic void onWebSocketClose(Session session, CloseReason reason){System.out.println("Socket Closed: " + reason);}@OnErrorpublic void onWebSocketError(Session session, Throwable cause){cause.printStackTrace(System.err);} }2.4 瀏覽器并發(fā)請(qǐng)求與長(zhǎng)連接
瀏覽器請(qǐng)求一般都是拉取服務(wù)器的資源,而請(qǐng)求方式分為短連接和長(zhǎng)連接兩種,這篇文章介紹很清楚:HTTP的長(zhǎng)連接和短連接
瀏覽器對(duì)后端資源的請(qǐng)求都是并發(fā)的執(zhí)行的,不同的瀏覽器并發(fā)連接數(shù)不同。現(xiàn)在大多數(shù)瀏覽器都支持http1.1協(xié)議,默認(rèn)都會(huì)開(kāi)啟keep-alive,支持長(zhǎng)連接。在瀏覽器對(duì)后端的資源發(fā)出請(qǐng)求,在開(kāi)啟keep-alive情況下,都會(huì)復(fù)用連接通道。如果是不間斷的下載圖片,應(yīng)該使用的是長(zhǎng)連接通道復(fù)用功能。
2.5 Web服務(wù)器Push技術(shù)
實(shí)現(xiàn)服務(wù)器端Push有以下幾種方式:
- Ajax輪詢(xún)。采用setInterval方法不停的調(diào)用
- Ajax長(zhǎng)輪詢(xún)。俗稱(chēng)Comet方式,不需要重復(fù)建立連接,沒(méi)有響應(yīng)就一直等,等到才關(guān)閉連接。
- WebSocket
- server-sent-events
Ajax輪詢(xún)?cè)磉€是pull的方式,不算真正的push,但是對(duì)一些老版本的瀏覽器是適用的。WebSocket優(yōu)點(diǎn)是支持全雙工、可跨域。server-sent-server實(shí)現(xiàn)簡(jiǎn)單,但只支持server到client單向傳輸,且IE系列都不支持。詳細(xì)內(nèi)容參考: web服務(wù)器端推送技術(shù)簡(jiǎn)介
除了上面一些方法外,還有一些其他方式,如:Flash XML Socket, Java Applet等非主流。
而在本案例中,如果采用WebSocket傳送圖片,可實(shí)現(xiàn)真正的服務(wù)器端不間斷的推送圖片數(shù)據(jù),但是如果要實(shí)現(xiàn)并發(fā)傳送,必須自己在瀏覽器端來(lái)實(shí)現(xiàn),否則,僅僅單連接的情況下不一定比瀏覽器的并發(fā)連接快。
2.6 JS播放圖片幀的性能
JS播放圖片有多種方式,如:
- 采用標(biāo)簽,不停改變img的src屬性,實(shí)現(xiàn)播放。
- 采用Html5的Canvas,將Image對(duì)象畫(huà)在Canvas上,實(shí)現(xiàn)播放。
- 將圖片設(shè)置為Div的背景,不停的更換背景,實(shí)現(xiàn)播放。
采用標(biāo)簽方式實(shí)現(xiàn)如下:
(function() {var i = 0;var pics = [ "andy_white.jpg", "andy_black.jpg" ];var el = document.getElementById('img_to_flip'); // el doesn't changefunction toggle() {el.src = pics[i]; // set the imagei = (i + 1) % pics.length; // update the counter}setInterval(toggle, 2000); })();這種方式下瀏覽器CPU占用率非常高,在IE11和Chrome下,i3的CPU(T440P)占用都在60%左右,內(nèi)存占用較少,大約在100M左右。CPU的消耗主要在瀏覽器對(duì)圖片的渲染上。
用Canvas替代標(biāo)簽,CPU占用方面,IE11仍然占用那么高,Chrome能降一半。更換背景的方式?jīng)]有實(shí)驗(yàn)。基于以上,采用Canvas的方式是一種比較好的選擇。
aaaa
bbbb
轉(zhuǎn)載于:https://www.cnblogs.com/gjhuai/p/6807492.html
總結(jié)