ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-01-28 18:02:05  阅读:155  来源: 互联网

标签:预览 void mCamera Camera import Android 音视频 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对比

标签:预览,void,mCamera,Camera,import,Android,音视频,android
来源: https://blog.csdn.net/xfb1989/article/details/113353740

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有