在 Android Camera进行视频的采集,分别使用 SurfaceView、TextureView 来预览 Camera 数据,取到 NV21 的数据回调

做视频开发就需要用到Camera进行采集数据,关于Camera的用法就不多说了,如有兴趣的可以到Camera官网学习。
更加详细的SurfaceView,可以到Android音视频技术入门之绘制一张图片中了解。
TextureView是Android 4.0之后加入的。TextureView必须工作在开启硬件加速的环境中,也即配置文件里Activity的设置项里:android:hardwareAccelerated="true" 默认的这个属性就是true,因此不用再写了。但如果写成false,onSurfaceTextureAvailable()这个回调就进不来了。

Camera采集数据

  • 打开摄像头

      mCamera = Camera.open();
    
  • 设置摄像头的预览数据界面

    预览一般有两种方式,一种是调用setPreviewDisplay方法设置SurfaceHolder,也就是和SurfaceView进行绑定了,还有一种就是调用setPreviewTexture方法设置SurfaceTexture的,这个就和GLSurfaceView以及TextureView绑定了

  • 获取到Camera.Parameters参数信息

    Camera.Parameters parameters = mCamera.getParameters(); //获取摄像头参数 可以根据情况设置参数
    
  • 在把添加好的参数信息设置回去,调用startPreview开始预览效果了

    mCamera.startPreview();
    
  • 释放摄像头

      mCamera.release();
    

注意 Camera用完了之后一定要释放掉,不然别的地方调用不到相机的。

用SurfaceView预览Camera的数据

    public class SurfaceViewActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private SurfaceView mSurfaceView;
    private Camera mCamera;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// 设置全屏

        setContentView(R.layout.activity_surface_view);

        mSurfaceView = findViewById(R.id.surface_view);
        mSurfaceView.getHolder().addCallback(this);
        mSurfaceView.setKeepScreenOn(true);

        // 打开摄像头并将展示方向旋转90度
        camera = Camera.open();
        camera.setDisplayOrientation(90);
   
    }

   //------ Surface 预览 -------
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
            //在控件创建的时候,进行相应的初始化工作
           try {
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();//开始预览

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
        //变化时,可以做相应操作
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        mCamera.release();
    }
}

用 TextureView 来预览 Camera 数据

public class TextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener{

    TextureView textureView;
    Camera camera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_texture_view);
        textureView = findViewById(R.id.texture_view);
        textureView.setSurfaceTextureListener(this);

        // 打开摄像头并将展示方向旋转90度
        camera = Camera.open();
        camera.setDisplayOrientation(90);
    }

   //------ Texture 预览 -------
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        try {
            camera.setPreviewTexture(surfaceTexture);
            camera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        camera.release();//释放相机
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

    }
}

取到 NV21 的数据回调

Android 中Google支持的 Camera Preview Callback的YUV常用格式有两种:一个是NV21,一个是YV12。Android一般默认使用YCbCr_420_SP的格式(NV21)。可以配置数据回调的格式。如下:

Camera.Parameters parameters = camera.getParameters();
    parameters.setPreviewFormat(ImageFormat.NV21);
    camera.setParameters(parameters);
    通过setPreviewCallback方法监听预览的回调:
    camera.setPreviewCallback(new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] bytes, Camera camera) {
            //这里面的Bytes的数据就是NV21格式的数据。
        }
    });
    

下面就用ImageView来显示取到 NV21 的数据。

mCamera.setPreviewCallback(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    //处理data
                    mPreviewSize = camera.getParameters().getPreviewSize();//获取尺寸,格式转换的时候要用到
                    //取发YUVIMAGE
                    YuvImage yuvimage = new YuvImage(
                            data,
                            ImageFormat.NV21,
                            mPreviewSize.width,
                            mPreviewSize.height,
                            null);
                    mBaos = new ByteArrayOutputStream();
                    //yuvimage 转换成jpg格式
                    yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);// 80--JPG图片的质量[0-100],100最高
                    mImageBytes = mBaos.toByteArray();

                    //将mImageBytes转换成bitmap
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inPreferredConfig = Bitmap.Config.RGB_565;

                    mBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length, options);
                    icon.setImageBitmap(mBitmap);
//                    icon.setImageBitmap(rotateBitmap(mBitmap,getDegree()));
                }
            });

完整代码

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class SurfaceViewActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private SurfaceView mSurfaceView;
    private Camera mCamera;

    private ImageView icon;
    private ByteArrayOutputStream mBaos;
    private byte[] mImageBytes;
    private Bitmap mBitmap;
    private Camera.Size mPreviewSize;//预览尺寸大小

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// 设置全屏

        setContentView(R.layout.activity_surface_view);

        mSurfaceView = findViewById(R.id.surface_view);
        mSurfaceView.getHolder().addCallback(this);
        mSurfaceView.setKeepScreenOn(true);
        icon = findViewById(R.id.icon);

    }

//    //------ Surface 预览 -------
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
            //在控件创建的时候,进行相应的初始化工作
        mCamera = Camera.open();
    }


    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int w, int h) {
        doChange(surfaceHolder);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();
        mCamera.release();
    }
    //当我们的程序开始运行,即使我们没有开始录制视频,我们的surFaceView中也要显示当前摄像头显示的内容
    private void doChange(SurfaceHolder holder) {
        try {
            mCamera.setPreviewDisplay(holder);//设置摄像机的预览界面  一般都是与SurfaceView#SurfaceHolder进行绑定
            //设置surfaceView旋转的角度,系统默认的录制是横向的画面
            mCamera.setDisplayOrientation(getDegree());

            if (mCamera != null ){
                try{
                    Camera.Parameters parameters = mCamera.getParameters(); //获取摄像头参数 
//                    可以根据情况设置参数
//                    parameters.setZoom();  //镜头缩放
                    // 设置预览照片的大小
//                    parameters.setPreviewSize(200, 200);
                    // 设置预览照片时每秒显示多少帧的最小值和最大值
//                    parameters.setPreviewFpsRange(4, 10);
                    // 设置图片格式
//                    parameters.setPictureFormat(ImageFormat.JPEG);
                    // 设置JPG照片的质量  图片的质量[0-100],100最高
//                    parameters.set("jpeg-quality", 85);
                    // 设置照片的大小
//                    parameters.setPictureSize(200, 200);
                    mCamera.setParameters(parameters);
                }
                catch (Exception e) {
                    e.printStackTrace();
                    Log.e("lu","Camera设置的参数错误:"+e.getMessage());
                }
            }

            mCamera.setPreviewCallback(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    //处理data
                    mPreviewSize = camera.getParameters().getPreviewSize();//获取尺寸,格式转换的时候要用到
                    //取发YUVIMAGE
                    YuvImage yuvimage = new YuvImage(
                            data,
                            ImageFormat.NV21,
                            mPreviewSize.width,
                            mPreviewSize.height,
                            null);
                    mBaos = new ByteArrayOutputStream();
                    //yuvimage 转换成jpg格式
                    yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);// 80--JPG图片的质量[0-100],100最高
                    mImageBytes = mBaos.toByteArray();

                    //将mImageBytes转换成bitmap
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inPreferredConfig = Bitmap.Config.RGB_565;

                    mBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length, options);
//                    icon.setImageBitmap(mBitmap);
                    icon.setImageBitmap(rotateBitmap(mBitmap,getDegree()));
                }
            });
            mCamera.startPreview();//开始预览

        } catch (IOException e) {
            e.printStackTrace();
            Log.e("lu","Camera预览变化错误:"+e.getMessage());
        }
    }

    private int getDegree() {
        //获取当前屏幕旋转的角度
        int rotating = this.getWindowManager().getDefaultDisplay().getRotation();
        int degree = 0;//度数
        //根据手机旋转的角度,来设置surfaceView的显示的角度
        switch (rotating) {
            case Surface.ROTATION_0:
                degree = 90;
                break;
            case Surface.ROTATION_90:
                degree = 0;
                break;
            case Surface.ROTATION_180:
                degree = 270;
                break;
            case Surface.ROTATION_270:
                degree = 180;
                break;
        }
        return degree;
    }


    /**
     * 选择变换
     * @param origin 原图
     * @param degree  旋转角度,可正可负
     * @return 旋转后的图片
     */
    private Bitmap rotateBitmap(Bitmap origin, float degree) {
        if (origin == null) {
            return null;
        }
        int width = origin.getWidth();
        int height = origin.getHeight();
        Matrix matrix = new Matrix();
        matrix.setRotate(degree);
        // 围绕原地进行旋转
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        if (newBM.equals(origin)) {
            return newBM;
        }
        origin.recycle();
        return newBM;
    }

}

总结

  • 因为系统默认只能同时开启一个摄像头不管是前置摄像头还是后置摄像头,所以不用的时候一定要释放 。
  • Android中的摄像头Camera是区分前置和后置的,所以这里就要做一个前置和后置摄像头的切换功能了(上面是没有写切换功能的)
  • 注意预览画布旋角度问题。
  • Camera在执行startPreview时必须保证TextureView的SurfaceTexture上来了,如果因为一些性能原因onSurfaceTextureAvailable()这个回调上不来就开预览,就开不了的。如果发生这种情况,就在onSurfaceTextureAvailable()回调里执行open和startPreview操作,保证万无一失。
  • 视频画面帧的展示控件SurfaceView及TextureView对比
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容