Android音视频开发(三) : 在Android平台使用Camera API进行视频的采集,并且预览Camera数据,得到NV21数据进行回调

前言

在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》。本文是Android音视频任务列表的第三篇, 对应的要学习的内容是:在Android平台使用Camera API进行视频的采集,分别使用SurfaceView、TextureView来预览Camera数据,取到NV21的数据回调(例如将获取的NV21数据进行处理之后显示在ImageView控件上面)。


音视频任务列表

音视频任务列表: 点击此处跳转查看


一、目录

在这里插入图片描述


(一)Camera采集数据步骤

(1)打开摄像头

 mCamera = Camera.open();

(2)设置摄像头的预览数据界面

预览一般有两种方式:
SurfaceView:是调用setPreviewDisplay方法设置SurfaceHolder,也就是和SurfaceView进行绑定
TextureView:是调用setPreviewTexture方法设置SurfaceTexture,就是和TextureView绑定了

(3)获取到Camera.Parameters参数信息(如果是简单的预览,就不用设置参数信息了)

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);

(4)在把添加好的参数信息设置回去,调用startPreview开始预览效果了

mCamera.startPreview();

(5)释放摄像头

  mCamera.release();

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


(二)SurfaceView来预览Camera数据

package com.lzacking.cameraapidemo;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;

import java.io.IOException;

public class CameraSurfaceViewActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private SurfaceView mSurfaceView;
    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_surface_view);

        mSurfaceView = findViewById(R.id.surfaceView);
        mSurfaceView.getHolder().addCallback(this);

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


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

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 释放摄像头
        mCamera.release();
    }

}

(三)TextureView来预览Camera数据

package com.lzacking.cameraapidemo;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.TextureView;

import java.io.IOException;

public class CameraTextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {

    private TextureView mTextureView;
    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_texture_view);

        mTextureView = findViewById(R.id.textureView);
        mTextureView.setSurfaceTextureListener(this);

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


    //------ TextureView预览 -------
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        // 在控件创建的时候,进行相应的初始化工作
        try {
            mCamera.setPreviewTexture(surface);
            // 开始预览
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        // 变化时,可以做相应操作
    }

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

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

}

(四)得到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,这里面的data数据就是NV21格式的数据,将数据显示在ImageView控件上面
        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);
        mImageView.setImageBitmap(rotateBitmap(mBitmap, getDegree()));
    }
});

(五)完整代码

(1)布局

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/btn_surfaceview"
        android:text="SurfaceView预览camera数据"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn_textureview"
        android:text="TextureView预览camera数据"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"/>

    <Button
        android:id="@+id/btn_nv21data_callback"
        android:text="获取的NV21数据进行回调"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"/>

</LinearLayout>

activity_camera_surface_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CameraSurfaceViewActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </SurfaceView>

</LinearLayout>

activity_camera_texture_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CameraTextureViewActivity">

    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

activity_nv21data_callback.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".NV21DataCallbackActivity"
    android:orientation="vertical">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="400dp" />

    <ImageView
        android:id="@+id/iv_imageview"
        android:layout_width="match_parent"
        android:layout_height="200dp">
    </ImageView>

</LinearLayout>

(2)代码

MainActivity.java

package com.lzacking.cameraapidemo;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mSurfaceView;
    private Button mTextureView;
    private Button mNv21DataCallback;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // SurfaceView预览camera数据
        mSurfaceView = findViewById(R.id.btn_surfaceview);
        mSurfaceView.setOnClickListener(this);

        // TextureView预览camera数据
        mTextureView = findViewById(R.id.btn_textureview);
        mTextureView.setOnClickListener(this);

        // 获取的NV21数据进行回调
        mNv21DataCallback = findViewById(R.id.btn_nv21data_callback);
        mNv21DataCallback.setOnClickListener(this);

        // 申请权限
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, 1);
        }

    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.i("info", "onRequestPermissionsResult: " + "权限已经申请");
                } else {
                    Toast.makeText(this, "你需要打开相机权限", Toast.LENGTH_LONG).show();
                }
                break;
            default:
        }
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_surfaceview:
                Intent intentSurfaceView = new Intent(this, CameraSurfaceViewActivity.class);
                startActivity(intentSurfaceView);
                break;

            case R.id.btn_textureview:
                Intent intentTextureView = new Intent(this, CameraTextureViewActivity.class);
                startActivity(intentTextureView);
                break;

            case R.id.btn_nv21data_callback:
                Intent intentNV21DataCallback = new Intent(this, NV21DataCallbackActivity.class);
                startActivity(intentNV21DataCallback);
                break;

            default:
                break;

        }
    }

}

CameraSurfaceViewActivity.java

package com.lzacking.cameraapidemo;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;

import java.io.IOException;

public class CameraSurfaceViewActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private SurfaceView mSurfaceView;
    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_surface_view);

        mSurfaceView = findViewById(R.id.surfaceView);
        mSurfaceView.getHolder().addCallback(this);

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

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

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 释放摄像头
        mCamera.release();
    }

}

CameraTextureViewActivity.java

package com.lzacking.cameraapidemo;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.TextureView;

import java.io.IOException;

public class CameraTextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {

    private TextureView mTextureView;
    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_texture_view);

        mTextureView = findViewById(R.id.textureView);
        mTextureView.setSurfaceTextureListener(this);

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

    //------ TextureView预览 -------
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        // 在控件创建的时候,进行相应的初始化工作
        try {
            mCamera.setPreviewTexture(surface);
            // 开始预览
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        // 变化时,可以做相应操作
    }

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

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

}

NV21DataCallbackActivity

package com.lzacking.cameraapidemo;

import androidx.appcompat.app.AppCompatActivity;

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.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.ImageView;

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

public class NV21DataCallbackActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private Camera mCamera;
    private SurfaceView mSurfaceView;

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_nv21data_callback);
        mImageView = findViewById(R.id.iv_imageview);

        mSurfaceView = findViewById(R.id.surfaceView);
        mSurfaceView.getHolder().addCallback(this);

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


    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }


    // 窗口改变
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        doChange(holder);
    }


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

    // 当我们的程序开始运行,surFaceView显示当前摄像头获取的内容,获取的NV21数据显示在ImageView控件上
    private void doChange(SurfaceHolder holder) {
        try {
            mCamera.setPreviewDisplay(holder);// 设置摄像机的预览界面
            // 设置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);

                    // 设置预览图片的图像格式
                    parameters.setPreviewFormat(ImageFormat.NV21);

                    mCamera.setParameters(parameters);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            mCamera.setPreviewCallback(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    // 处理data,这里面的data数据就是NV21格式的数据,将数据显示在ImageView控件上面
                    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);
                    mImageView.setImageBitmap(rotateBitmap(mBitmap, getDegree()));
                }
            });

            mCamera.startPreview();// 开始预览

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

    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;
    }

}

(3)权限

<uses-permission android:name="android.permission.CAMERA" />

猜你喜欢

转载自blog.csdn.net/a13027629517/article/details/108430175
今日推荐