图片播放技术总结

1 概述

由于工作的项目上的需求,需要在浏览器上不间断的播放图片,即像播放视频一样播放图片。

后端支持采用Java实现,需要用Java编写一个Http服务器,并提供WebSocket服务。前后端通过Http链接或WebSocket提供图片浏览服务,前端采用JS轮询或WebSocket推送的方式获取图片,浏览器显示图片有两种方式:一种是采用连续切换图片源,实现播放效果;另一种采用将图片画在canvas上面,实现播放。

要完成这个功能涉及到以下技术:

  • Http服务器的实现
  • 高速的读文件
  • WebSocket原理及实现
  • 基于浏览器pull方式的http资源获取
  • 基于服务器端push方式的http资源获取
  • JS播放图片帧的性能

2 技术分析

2.1 Http服务器的实现

实现HTTP服务器比较容易,实现方式也有如下多种:

  • 基于jdk中com.sun包下面的HttpServer来实现。(不推荐,com.sun不在java规范内,jdk升级可能会不兼容)
  • 基于jetty或tomcat的嵌入式包来实现。此方式基于Servlet规范来实现的,较简单且易于理解。
  • 基于netty的方式实现。性能好,需要对NIO有了解,编程难度相对大一些。
  • 基于vert.x的实现。这种方式底层还是采用netty实现,较简单,但是也需要熟悉vert.x的编程模型。

我们采用jetty的方式实现,基于servlet3.0规范,可以支持异步请求方式。代码如下:

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 layer
        ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
        // Add WebSocket endpoint to javax.websocket layer
        wscontainer.addEndpoint(EventSocket.class);


        server.start();
        server.dump(System.err);
        server.join();
    } catch (Throwable t){
        t.printStackTrace(System.err);
    }
}

2.2 高速的读文件

关于Java高速读取文件可参考这篇文章:How to read files quickly
这篇文章得出四个结论:

  • 为了减少I/O操作,每次 应该 读一个byte数组,而不是一个byte字节,8K的byte数组就是一个好的选择。
  • 为了减少方法调用的开销,每次应该获取一个byte数组的数据,而不是一个byte字节。
  • 为了减少线程同步锁的开销,要么减少线程同步方法的调用,要么采用非线程安全的类,如:FileChannel 和MappedByteBuffer。
  • 为了减少在JVM/OS、 internal buffers和应用程序数组之间的数据拷贝,要么使用带有内存映射的FileChannel类,要么使用 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原理及实现

WebSocket的原理以及与Http区别可以参考:WebSocket与http的区别,以及它的原理,总体来说,原理及区别如下:

  • WebSocket和Http协议没有太大的关系,WS只是借助Http实现了第一次握手,之后从http协议upgrade为ws://协议。
  • WS是持久性连接(类似socket),而HTTP的短连接、长连接都不是持久的。
  • WS协议是支持全双工的,可以pull,亦可以push。

用Java实现WebSocket服务端:

@ClientEndpoint
@ServerEndpoint(value="/events/")
public class EventSocket{
private static int DEFAULT_BUFFER_SIZE = 128 * 1024;// 8192
private byte[] bytes;

    @OnOpen
    public void onWebSocketConnect(Session session, EndpointConfig config)  {
session.setMaxBinaryMessageBufferSize(DEFAULT_BUFFER_SIZE);
    }
    
    @OnMessage
    public void onWebSocketText(Session session, String message) throws Exception{
        System.out.println("Received TEXT message: " + message);
        bytes = ... // 读取图片文件字节
        // 发送图片文件
        session.getAsyncRemote().sendBinary( ByteBuffer.wrap( this.bytes ) );
    }
    
    @OnClose
    public void onWebSocketClose(Session session, CloseReason reason){
        System.out.println("Socket Closed: " + reason);
    }
    
    @OnError
    public void onWebSocketError(Session session, Throwable cause){
        cause.printStackTrace(System.err);
    }
}

2.4 浏览器并发请求与长连接

浏览器请求一般都是拉取服务器的资源,而请求方式分为短连接和长连接两种,这篇文章介绍很清楚:HTTP的长连接和短连接

浏览器对后端资源的请求都是并发的执行的,不同的浏览器并发连接数不同。现在大多数浏览器都支持http1.1协议,默认都会开启keep-alive,支持长连接。在浏览器对后端的资源发出请求,在开启keep-alive情况下,都会复用连接通道。如果是不间断的下载图片,应该使用的是长连接通道复用功能。

2.5 Web服务器Push技术

实现服务器端Push有以下几种方式:

  • Ajax轮询。采用setInterval方法不停的调用
  • Ajax长轮询。俗称Comet方式,不需要重复建立连接,没有响应就一直等,等到才关闭连接。
  • WebSocket
  • server-sent-events

Ajax轮询原理还是pull的方式,不算真正的push,但是对一些老版本的浏览器是适用的。WebSocket优点是支持全双工、可跨域。server-sent-server实现简单,但只支持server到client单向传输,且IE系列都不支持。 详细内容参考: web服务器端推送技术简介

除了上面一些方法外,还有一些其他方式,如:Flash XML Socket, Java Applet等非主流。

而在本案例中,如果采用WebSocket传送图片,可实现真正的服务器端不间断的推送图片数据,但是如果要实现并发传送,必须自己在浏览器端来实现,否则,仅仅单连接的情况下不一定比浏览器的并发连接快。

2.6 JS播放图片帧的性能

JS播放图片有多种方式,如:

  • 采用标签,不停改变img的src属性,实现播放。
  • 采用Html5的Canvas,将Image对象画在Canvas上,实现播放。
  • 将图片设置为Div的背景,不停的更换背景,实现播放。

采用标签方式实现如下:

(function() {
    var i = 0;
    var pics = [ "andy_white.jpg", "andy_black.jpg" ];
    var el = document.getElementById('img_to_flip');  // el doesn't change
    function toggle() {
        el.src = pics[i];           // set the image
        i = (i + 1) % pics.length;  // update the counter
    }
    setInterval(toggle, 2000);
})();

这种方式下浏览器CPU占用率非常高,在IE11和Chrome下,i3的CPU(T440P)占用都在60%左右,内存占用较少,大约在100M左右。CPU的消耗主要在浏览器对图片的渲染上。
用Canvas替代标签,CPU占用方面,IE11仍然占用那么高,Chrome能降一半。更换背景的方式没有实验。基于以上,采用Canvas的方式是一种比较好的选择。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gjhuai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值