在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);
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);
mCamera = Camera.open(0);
mCamera.setDisplayOrientation(90);
@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);
mCamera = Camera.open(0);
mCamera.setDisplayOrientation(90);
@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);
camera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
用ImageView来显示取到NV21的数据
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
mPreviewSize = camera.getParameters().getPreviewSize();
YuvImage yuvimage = new YuvImage(
data,
ImageFormat.NV21,
mPreviewSize.width,
mPreviewSize.height,
null);
mBaos = new ByteArrayOutputStream();
yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);
mImageBytes = mBaos.toByteArray();
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);
mSurfaceView = findViewById(R.id.btn_surfaceview);
mSurfaceView.setOnClickListener(this);
mTextureView = findViewById(R.id.btn_textureview);
mTextureView.setOnClickListener(this);
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);
mCamera = Camera.open(0);
mCamera.setDisplayOrientation(90);
@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);
mCamera = Camera.open(0);
mCamera.setDisplayOrientation(90);
@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);
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();
private void doChange(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
mCamera.setDisplayOrientation(getDegree());
if (mCamera != null ) {
try {
Camera.Parameters parameters = mCamera.getParameters();
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) {
mPreviewSize = camera.getParameters().getPreviewSize();
YuvImage yuvimage = new YuvImage(
data,
ImageFormat.NV21,
mPreviewSize.width,
mPreviewSize.height,
null);
mBaos = new ByteArrayOutputStream();
yuvimage.compressToJpeg(new Rect(0, 0, mPreviewSize.width, mPreviewSize.height), 100, mBaos);
mImageBytes = mBaos.toByteArray();
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;
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" />
Android音视频开发基础(三) : 在Android平台使用Camera API进行视频的采集,并且预览Camera数据,得到NV21数据进行回调