Google自Android4.0出了TextureView,为什么推出呢?就是为了弥补Surfaceview的不足,另外一方面也是为了平衡GlSurfaceView,当然这是本人揣度的。关于TextureView、Surfaceview、SurfaceTexture、GLSurfaceView的关系,待咱家推出GLSurfaceview预览Camera后再专门分析。本文主要介绍使用TextureView预览Camera。
其实关于如何用TextureView预览Camera,官网已经给出了demo,参见这里。另外,链接1 链接2也给出了完整的预览Camera的demo,但都是一堆东西染在一块。本文就利用前文 搭建的一个轻量级的Camera框架来快速替换掉Surfaceview。因为用Surfaceview预览的话传一个SurfaceHolder进去,用Textureview预览的话需要传进去一个SurfaceTexture。其他的Camera流程不变。
一、新建CameraTextureView类继承TextureView,并实现TextureView.SurfaceTextureListener接口。实现这个接口就像实现SurfaceHolder.Callback,最主要的目的是在SurfaceTexture准备好后能够知道,也即onSurfaceTextureAvailable这个函数。
CameraTextureView.java
1 <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.camera.preview;
2
3 import org.yanzi.camera.CameraInterface;
4
5 import android.content.Context;
6 import android.graphics.PixelFormat;
7 import android.graphics.SurfaceTexture;
8 import android.util.AttributeSet;
9 import android.util.Log;
10 import android.view.SurfaceHolder;
11 import android.view.SurfaceView;
12 import android.view.TextureView;
13
14 public class CameraTextureView extends TextureView implements TextureView.SurfaceTextureListener {
15 private static final String TAG = "yanzi";
16 Context mContext;
17 SurfaceTexture mSurface;
18 public CameraTextureView(Context context, AttributeSet attrs) {
19 super(context, attrs);
20 // TODO Auto-generated constructor stub
21 mContext = context;
22 this.setSurfaceTextureListener(this);
23 }
24 @Override
25 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
26 int height) {
27 // TODO Auto-generated method stub
28 Log.i(TAG, "onSurfaceTextureAvailable...");
29 mSurface = surface;
30 // CameraInterface.getInstance().doStartPreview(surface, 1.33f);
31 }
32 @Override
33 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
34 // TODO Auto-generated method stub
35 Log.i(TAG, "onSurfaceTextureDestroyed...");
36 CameraInterface.getInstance().doStopCamera();
37 return true;
38 }
39 @Override
40 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
41 int height) {
42 // TODO Auto-generated method stub
43 Log.i(TAG, "onSurfaceTextureSizeChanged...");
44 }
45 @Override
46 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
47 // TODO Auto-generated method stub
48 Log.i(TAG, "onSurfaceTextureUpdated...");
49
50 }
51
52 /* 让Activity能得到TextureView的SurfaceTexture
53 * @see android.view.TextureView#getSurfaceTexture()
54 */
55 public SurfaceTexture _getSurfaceTexture(){
56 return mSurface;
57 }
58 }
59 </span>
二、在布局文件里把它加上就行了,因为他的父类就是View,当成一般的View就行
1 <span style="font-family:Comic Sans MS;font-size:18px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 tools:context=".CameraActivity" >
6 <FrameLayout
7 android:layout_width="wrap_content"
8 android:layout_height="wrap_content" >
9 <org.yanzi.camera.preview.CameraTextureView
10 android:id="@+id/camera_textureview"
11 android:layout_width="0dip"
12 android:layout_height="0dip" />
13 </FrameLayout>
14 <ImageButton
15 android:id="@+id/btn_shutter"
16 android:layout_width="wrap_content"
17 android:layout_height="wrap_content"
18 android:background="@drawable/btn_shutter_background"
19 android:layout_alignParentBottom="true"
20 android:layout_centerHorizontal="true"
21 android:layout_marginBottom="10dip"/>
22 </RelativeLayout>
23 </span>
三、在CameraInterface里,我封装了两个函数:
1 <span style="font-family:Comic Sans MS;font-size:18px;">/**使用Surfaceview开启预览
2 * @param holder
3 * @param previewRate
4 */
5 public void doStartPreview(SurfaceHolder holder, float previewRate){
6 Log.i(TAG, "doStartPreview...");
7 if(isPreviewing){
8 mCamera.stopPreview();
9 return;
10 }
11 if(mCamera != null){
12 try {
13 mCamera.setPreviewDisplay(holder);
14 } catch (IOException e) {
15 // TODO Auto-generated catch block
16 e.printStackTrace();
17 }
18 initCamera(previewRate);
19 }
20
21
22 }
23 /**使用TextureView预览Camera
24 * @param surface
25 * @param previewRate
26 */
27 public void doStartPreview(SurfaceTexture surface, float previewRate){
28 Log.i(TAG, "doStartPreview...");
29 if(isPreviewing){
30 mCamera.stopPreview();
31 return;
32 }
33 if(mCamera != null){
34 try {
35 mCamera.setPreviewTexture(surface);
36 } catch (IOException e) {
37 // TODO Auto-generated catch block
38 e.printStackTrace();
39 }
40 initCamera(previewRate);
41 }
42
43 }</span>
分别对应Surfaceview和TextureView预览。可以看到就是传进来的参数不一样,initCamera()的东西都一样。
1 <span style="font-family:Comic Sans MS;font-size:18px;"> private void initCamera(float previewRate){
2 if(mCamera != null){
3
4 mParams = mCamera.getParameters();
5 mParams.setPictureFormat(PixelFormat.JPEG);//设置拍照后存储的图片格式
6 // CamParaUtil.getInstance().printSupportPictureSize(mParams);
7 // CamParaUtil.getInstance().printSupportPreviewSize(mParams);
8 //设置PreviewSize和PictureSize
9 Size pictureSize = CamParaUtil.getInstance().getPropPictureSize(
10 mParams.getSupportedPictureSizes(),previewRate, 800);
11 mParams.setPictureSize(pictureSize.width, pictureSize.height);
12 Size previewSize = CamParaUtil.getInstance().getPropPreviewSize(
13 mParams.getSupportedPreviewSizes(), previewRate, 800);
14 mParams.setPreviewSize(previewSize.width, previewSize.height);
15
16 mCamera.setDisplayOrientation(90);
17
18 // CamParaUtil.getInstance().printSupportFocusMode(mParams);
19 List<String> focusModes = mParams.getSupportedFocusModes();
20 if(focusModes.contains("continuous-video")){
21 mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
22 }
23 mCamera.setParameters(mParams);
24 mCamera.startPreview();//开启预览
25
26
27
28 isPreviewing = true;
29 mPreviwRate = previewRate;
30
31 mParams = mCamera.getParameters(); //重新get一次
32 Log.i(TAG, "最终设置:PreviewSize--With = " + mParams.getPreviewSize().width
33 + "Height = " + mParams.getPreviewSize().height);
34 Log.i(TAG, "最终设置:PictureSize--With = " + mParams.getPictureSize().width
35 + "Height = " + mParams.getPictureSize().height);
36 }
37 }</span>
四、在Activity里,依旧开一个线程去open Camera:
1 <span style="font-family:Comic Sans MS;font-size:18px;"> Thread openThread = new Thread(){
2 @Override
3 public void run() {
4 // TODO Auto-generated method stub
5 CameraInterface.getInstance().doOpenCamera(CameraActivity.this);
6 }
7 };
8 openThread.start();</span>
在Camera Open完的回调里开预览:
1 <span style="font-family:Comic Sans MS;font-size:18px;"> @Override
2 public void cameraHasOpened() {
3 // TODO Auto-generated method stub
4 SurfaceTexture surface = textureView._getSurfaceTexture();
5 CameraInterface.getInstance().doStartPreview(surface, previewRate);
6 }</span>
之后就能正常运行了,可以看到与前文Surfaceview预览Camera 改动非常之小。
几个注意事项:
1、TextureView是Android 4.0之后加入的,低版本么这个类。TextureView必须工作在开启硬件加速的环境中,也即配置文件里Activity的设置项里:android:hardwareAccelerated="true" 默认的这个属性就是true,因此不用再写了。但如果写成false,可以看到onSurfaceTextureAvailable()这个回调就进不来了,TextureView没有了SurfaceTexture还玩个屁啊。
2、本文demo打开camera并预览的正常log是:
1 <span style="font-family:Comic Sans MS;font-size:18px;"> Line 417: 06-22 12:37:43.682 I/yanzi ( 4917): Camera open....
2 Line 489: 06-22 12:37:43.758 I/yanzi ( 4917): onSurfaceTextureAvailable...
3 Line 533: 06-22 12:37:43.819 I/yanzi ( 4917): Camera open over....
4 Line 535: 06-22 12:37:43.819 I/yanzi ( 4917): doStartPreview...
5 Line 537: 06-22 12:37:43.825 I/yanzi ( 4917): PictureSize : w = 1280h = 720
6 Line 539: 06-22 12:37:43.825 I/yanzi ( 4917): PreviewSize:w = 800h = 448
7 Line 555: 06-22 12:37:43.874 I/yanzi ( 4917): 最终设置:PreviewSize--With = 800Height = 448
8 Line 557: 06-22 12:37:43.874 I/yanzi ( 4917): 最终设置:PictureSize--With = 1280Height = 720
9 Line 577: 06-22 12:37:44.106 I/yanzi ( 4917): onSurfaceTextureUpdated...
10 Line 579: 06-22 12:37:44.138 I/yanzi ( 4917): onSurfaceTextureUpdated...
11 Line 583: 06-22 12:37:44.169 I/yanzi ( 4917): onSurfaceTextureUpdated...
12 Line 585: 06-22 12:37:44.220 I/yanzi ( 4917): onSurfaceTextureUpdated...
13 Line 587: 06-22 12:37:44.253 I/yanzi ( 4917): onSurfaceTextureUpdated...</span>
测试手机为中兴Geek,这个手机Camera还是很牛逼的,比手里的华为G700强,就是偶尔会连不上Camera Service,汗。从log可以看到,onSurfaceTextureAvailable这个回调需要一定时间。Camera.open()这句话用了130多ms。但有两点跟Surfaceview不同。第一,TextureView创建过程中没有进到onSurfaceTextureSizeChanged()这个函数里。而SurfaceView在创建过程中,从无到有的时候会进到大小发生变化回调里。第二,onSurfaceTextureUpdated()这个函数每上来一帧数据,这块就进来一次。这是跟Surfaceview相比,最伟大的一个地方。通过这个接口,可以将上来的SurfaceTexture送给OpenGL再去处理。这个回调是实时的,而非用Camera的PreviewCallback这种2次回调的方式。从时间看,基本上每32ms左右上来一帧数据,即每秒30帧,跟本手机的Camera的性能吻合。
3、Camera再执行startPreview时必须保证TextureView的SurfaceTexture上来了,如果因为一些性能原因onSurfaceTextureAvailable()这个回调上不来就开预览,就开不了的。如果发生这种情况,就在onSurfaceTextureAvailable()回调里执行open和startPreview操作,保证万无一失。
4、TextureView本身就有getSurfaceTexture()这个函数,我又封装了个:
1 <span style="font-family:Comic Sans MS;font-size:18px;"> /* 让Activity能得到TextureView的SurfaceTexture
2 * @see android.view.TextureView#getSurfaceTexture()
3 */
4 public SurfaceTexture _getSurfaceTexture(){
5 return mSurface;
6 }</span>
这里的mSurface就是onSurfaceTextureAvailable()回调里传上来的SurfaceTexture。测试证明,开预览时直接调
textureView.getSurfaceTexture(),把它传给Camera: mCamera.setPreviewTexture(surface);也是能正常预览的。但是推荐使用前者,原因见官方上的这段话:
A TextureView's SurfaceTexture can be obtained either by invoking getSurfaceTexture()
or by using a TextureView.SurfaceTextureListener
. It is important to know that a SurfaceTexture is available only after the TextureView is attached to a window (and onAttachedToWindow()
has been invoked.) It is therefore highly recommended you use a listener to be notified when the SurfaceTexture becomes available.
两种方式获得SurfaceTexture,推荐使用监听。因为只有在TextureView执行完onAttachedToWindow时,它的tSurfaceTexture才上来。
5、SurfaceTexture和TextureView的关系:
Using a TextureView is simple: all you need to do is get its SurfaceTexture
. The SurfaceTexture
can then be used to render content
如果说TextureView是一幅画的话,那SurfaceTexture就是画布,真正渲染的载体是SurfaceTexture。
6、TextureView可以像一般View执行各种变化,其中有个textureView.setAlpha(1.0f);默认不写这句话,它的alpha也是1.0f,即不透明。如果设成透明0.0f,可以看到啥都看不到了,这一点跟Surfaceview刚好相反。Surfaceview的SurfaceHolder一般要设一下Transparent即透明。但TextureView因为是个view,任何一个png的照片透明度设成0肯定啥都看不到。
7、如果认为预览个Camera这就是TextureView和SurfaceTexture的使命的话,就大错特错了,真正用意是和OpenGL无缝连接。