前言
在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;
}
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" />