Android平台之不预览获取照相机预览数据帧及精确时间截

  在android平台上要获取预览数据帧是一件极其容易的事儿,但要获取每帧数据对应的时间截并不那么容易,网络上关于这方面的资料也比较少。之所以要获取时间截,是因为某些情况下需要加入精确时间轴才能解决问题,如果自己给获取到的时间截打上时间截,则必定引入很多误差,文档主要以理论为主,我想作为一名合格的程序员,有了一个想法,则一定会有办法去编码实现的。

  因为项目需要,查找了大量的资料,发现网络上关于获取预览数据的资料都是通过实现PreviewCallback接口来获取。这种方法能获取到照相机的预览数据,但是系统不提供时间截服务,自己打上时间截,可能会导致预览数据帧发生时间截偏移。具体分析来说,如果你实现了PreviewCallback接口,调用setPreviewDisplay使用一个SurfaceHolder来显示预览数据,并且在onPreviewFrame回调函数中获取到了帧数据,但是你不能获取该帧产生时的时间截。你需要为他手工编码打上时间截,或许你就是以程序运行到那行代码时刻的时间截,但在帧生成到调用回调onPreviewFrame,再到运行该行代码,已经消耗了一些时间,如果你的预览频率设置不当的话,会使得消耗是时间是你设定的预览帧间隔的几倍,这样误差可能就导致了错误的时间截。

  使用PreviewCallback和SurfaceView来获取预览数据的方法,还有很大的问题就是你必须把预览得到的数据显示出来,才能在onPreviewFrame回调函数中获取到数据。官方API是这么解释的,但这一点在2.3及以前版本的android中并不一定成立,因为我已经在2.3,2.2的系统中测试关闭输出,仍然能在onPreviewFrame中获取数据,但同样的方法在4.0以上的系统中则不可以。获取你可能会问,为什么要关闭预览输出?这个问题可能会有各种答案,但很明显的是它可以明显减少系统资源的消耗,从而可以使得照相机Camera能够以更大的预览频率输出。那么,怎么样才能使得高版本的android在不显示预览的情况下也能获得预览数据呢?这种情况下,一个叫SurfaceTexture的类登场了。

  SurfaceTexture是直接继承自Object类, 最低版本api 11,关于SurfaceTexture的介绍,官方是这么介绍的——"A Surface created from a SurfaceTexture can be used as an output destination for the android.hardware.camera2, MediaCodec, MediaPlayer, and Allocation APIs."和"A SurfaceTexture may also be used in place of a SurfaceHolder when specifying the output destination of the older Camera API. Doing so will cause all the frames from the image stream to be sent to the SurfaceTexture object rather than to the device's display."。也就是说SurfaceTexture可以作为视频或图像流的输出载体。说明一下,因为android5.0的推出,要废弃Camera类,使用Camera2来替代,所以说"older Camera API"。总之,如果你使用SurfaceTexure来作为Camera的输出载体(调用Camera的setPreviewTexture即可把生成的SurfaceTexture对象设置了输出载体),那么就可以把SurfaceTexture作为预览数据缓存地方,而不用再屏幕上显示出来,显然你要为设置一个足够大的缓存区域。有了SurfaceTexture,那么接下来的工作就变得容易多了,下面说说本文提到的另一个重点就是获取到精确的时间截。

  查阅SurfaceTexture类API就会发现它还提供了一个getTimeStamp()函数,官方介绍"Retrieve the timestamp associated with the texture image set by the most recent call to updateTexImage.",也就是说它可以获得SurfaceTexture最新数据帧的时间截,但在这之前需要调用updateTexImage()更新数据,另外getTimeStamp返回值的单位是纳秒。而updateTexImage()的调用对SurfaceTexture有要求,必须把SurfaceTexture设置为 GL_TEXTURE_EXTERNAL_OES texture 类型。可以这样编写代码:

surfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);

使用SurfaceTexture获取预览数据也是要实现PreviewCallback,方法同前文提到的PreviewCallback获取预览数据,不同的是在startPreview之前,不再调用setPreviewDisplay,而是使用Camera的setPreviewTexture。

 1 int version = android.os.Build.VERSION.SDK_INT;
 2 if (version >= OSSURPORTFORSURFACETEXTURE) {
 3     try {
 4         camera.setPreviewTexture(surfaceTexture);
 5         int buffersize = WIDTH_COLLECT * HEIGHT_COLLECT
 6                 * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8;
 7         previewBuffer = new byte[buffersize];
 8         camera.addCallbackBuffer(previewBuffer);
 9         camera.setPreviewCallbackWithBuffer(this);
10         camera.startPreview();
11         isView = true;
12     } catch (IOException e) {
13         camera.release();
14         camera = null;
15         e.printStackTrace();
16     }
17 } else {
18     ......
19 }

 另外还要主要的就是要记得onPreviewFrame回调函数中添加addCallbackBuffer调用,不然缓存不会自动更新,就不能获取到后续的数据帧;要获取精确时间截(这里说精确,是因为这个时间截是系统在数据发送到SurfaceTexture时设置的,非常接近预览数据生成的时间,要远比手工在onPreviewFrame中打上数据的时间截准确),还要调用updateTexImage()。可以像下面这样编写程序:

1 long timestampx;
2 if (osversion >= OSSURPORTFORSURFACETEXTURE) {
3     surfaceTexture.updateTexImage();
4     timestampx = surfaceTexture.getTimestamp()/1000000;
5     camera.addCallbackBuffer(previewBuffer);
6 }

 

上述代码是写在onPreviewFrame回调函数中的,有一个值得注意的地方是不要在onPreviewFrame中做耗时的工作,因为那么极可能会导致丢掉一些预览数据帧。

  通过上面的方法,已经可以在不显示预览的情况下获取到数据帧,并打上极为精确的生成时间截,这对于需要精确计算时间的程序来说是非常有用的。当然是用SurfaceTexture也可以将预览图像显示出来,你可以开一个线程专门来做这件事,而不是在onPreviewFrame中完成。下面提供一段显示预览图像的参考代码:

 1 try {
 2     YuvImage image = new YuvImage(data, ImageFormat.NV21,
 3             WIDTH_COLLECT, HEIGHT_COLLECT, null);
 4     if (image != null) {
 5         ByteArrayOutputStream stream = new ByteArrayOutputStream();
 6         image.compressToJpeg(new Rect(0, 0, WIDTH_COLLECT,
 7                 HEIGHT_COLLECT), 100, stream);
 8         Bitmap bm = BitmapFactory.decodeByteArray(
 9                 stream.toByteArray(), 0, stream.size());
10         stream.close();
11         Canvas canvas = previewHolder.lockCanvas();
12         canvas.drawBitmap(bm, 0, 0, null);
13         previewHolder.unlockCanvasAndPost(canvas);
14     }
15 } catch (Exception e) {
16     // TODO: handle exception
17 }

 

previewHolder就是要显示预览数据的SurfaceView的SurfaceHolder,当然你要可以加上synchronized同步机制。

  Demo就没有上传了,如果有什么问题可以直接留言讨论。虽然写得比较水,欢迎复制粘贴,转载请带上原文地址:http://www.cnblogs.com/HackingProgramer/p/4062119.html 

posted @ 2014-10-30 11:12  指尖的舞客  阅读(7187)  评论(1编辑  收藏  举报