之前博文《Android学习笔记之——将程序运行到手机上 》其实已经尝试过调用手机的摄像头了哈~但是那属于启动相机的程序,而本博文是需要捕获相机的视频流~
目录
相机API中关键类的解析(SurfaceView)
通过相机API实现拍摄功能涉及以下几个关键类和接口:
Camera:最主要的类,用于管理和操作camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法有以下这些:
- open():获取camera实例。
- setPreviewDisplay(SurfaceHolder):绑定绘制预览图像的surface。surface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。通过surfaceHolder可以将Camera和surface连接起来,当camera和surface连接后,camera获得的预览帧数据就可以通过surface显示在屏幕上了。
- setPrameters设置相机参数,包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。
- startPreview():开始预览,将camera底层硬件传来的预览帧数据显示在绑定的surface上。
- stopPreview():停止预览,关闭camra底层的帧数据 递以及surface上的绘制。
- release():释放Camera实例
- takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):这个是实现相机拍照的主要方法,包含了三个回调参数。shutter是快门按下时的回调,raw是获取拍照原始数据的回调,jpeg是获取经过压缩成jpg格式的图像数据的回调。
TextureView
(https://blog.csdn.net/afei__/article/details/100023701(浅谈 SurfaceView、TextureView、GLSurfaceView、SurfaceTexture))
Android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次,因此效率相对较低。
视频或者opengl内容往往是显示在SurfaceView中的,SurfaceView的工作方式是:创建一个置于应用窗口之后的新窗口
。因为SurfaceView窗口刷新的时候不需要重绘应用程序的窗口,所以这种方式的效率非常高。
但是SurfaceView也有一些非常不便的限制,因为SurfaceView的内容不在应用窗口上,所以不能使用平移、缩放、旋转
等变换操作,也难以放在ListView或者ScrollView中,同样不能使用UI控件的一些特性,比如View.setAlpha()。
为了解决这个问题,Android 4.0 中引入了TextureView,与SurfaceView相比,TextureView并没有创建一个单独的 Surface 用来绘制,这使得它可以像一般的View一样执行一些变换操作,设置透明度等
。
TextureView的使用非常简单,你唯一要做的就是获取用于渲染内容的SurfaceTexture。(下面的代码以此为主)
TextureView 是一个由于显示数据流的UI控件。从这里我们不难得知TextureView的应用场景应该主要分为两种:
- 播放视频
- 显示相机预览
TextureView既然作为一个控件,那么可以像普通的控件一样进行布局、动画等设置。
调用后置摄像头
首先创建一个新的工程命名为CameraTest
定义UI界面
显示启动后置摄像头的按钮以及把照片拍下来
<?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"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/take_photo_BackCamera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BackCamera"
android:textAllCaps="false"
/>
<Button
android:id="@+id/take_photo_FrontCamera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="FrontCamera"
android:textAllCaps="false"
/>
<RelativeLayout
android:layout_width="200dp"
android:layout_height="200dp">
<TextureView
android:id="@+id/texture_view_back"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="TextureView(摄像头预览)"
android:textColor="#f00"
android:textSize="10sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="200dp"
android:layout_height="200dp">
<ImageView
android:id="@+id/iv_pic_back"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="right"
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="ImageView(视频帧数据预览)"
android:textColor="#f00"
android:textSize="10sp" />
</RelativeLayout>
</LinearLayout>
Mainactivity
package com.example.cameratest;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
public class MainActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener{
private ImageView picture;//(视频帧数据预览)
private TextureView textureView;//摄像头预览
private Camera mCamera;权限【android.permission.CAMERA】
private Button GetVideoStreamBack,GetVideoStreamFront;//定义两个按钮
//权限
private static String[] PERMISSIONS_STORAGE = {
// Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,//写权限
Manifest.permission.CAMERA//照相权限
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//定义后置摄像头的按钮
GetVideoStreamBack=(Button) findViewById(R.id.take_photo_BackCamera);
GetVideoStreamFront=(Button) findViewById(R.id.take_photo_FrontCamera);
//定义照片实例用于显示图片(视频帧数据预览)
picture =(ImageView) findViewById(R.id.iv_pic_back);
picture.setRotation(90);//设置角度
//摄像头预览
textureView=(TextureView) findViewById(R.id.texture_view_back);
textureView.setRotation(90); // // 设置预览角度,并不改变获取到的原始数据方向(与Camera.setDisplayOrientation(0)
如果提示【Fail to connect to camera service】很可能是没申请权限,或申请权限了但用户没有给你权限
//华为手机摄像头权限申请
//用于判断SDK版本是否大于23
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
//检查权限
int i = ContextCompat.checkSelfPermission(this,PERMISSIONS_STORAGE[0]);
//如果权限申请失败,则重新申请权限
if(i!= PackageManager.PERMISSION_GRANTED){
//重新申请权限函数
startRequestPermission();
Log.e("这里","权限请求成功");
}
}
//查看摄像头的个数
initData();
//定义按钮点击事件
GetVideoStreamBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// // 打开相机 0后置 1前置
// mCamera = Camera.open(0);
//定义捕获相机的视频流
addCallBack();
}
});
// GetVideoStreamFront.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
//
// // 打开相机 0后置 1前置
// mCamera = Camera.open(1);
// addCallBack();
// }
// });
textureView.setSurfaceTextureListener(this);
}
//****************定义一系列函数***************///
private void initData() {
int numberOfCameras = Camera.getNumberOfCameras();// 获取摄像头个数
if(numberOfCameras<1){
Toast.makeText(this, "没有相机", Toast.LENGTH_SHORT).show();
finish();
return;
}
}
//调用后置摄像头函数
private void addCallBack() {
if (mCamera!=null){
mCamera.setPreviewCallback(new Camera.PreviewCallback(){
//预览
//通过Android Camera拍摄预览中设置setPreviewCallback实现onPreviewFrame接口,实时截取每一帧视频流数据
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Size size=camera.getParameters().getPreviewSize();
try{
YuvImage image=new YuvImage(data, ImageFormat.NV21,size.width,size.height,null);
if(image!=null){
ByteArrayOutputStream stream =new ByteArrayOutputStream();
//将摄像头预览回调的每一帧Nv21数据通过jpeg压缩
image.compressToJpeg(new Rect(0,0,size.width,size.height),80,stream);
Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());//解码
picture.setImageBitmap(bmp);
stream.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
});
}
}
//用TextureView预览Camera
//调用textureview的draw方法时会调用这个
//SurfaceTexture准备就绪后调用这个方法
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mCamera = Camera.open(0);// 打开相机 0后置 1前置
if (mCamera != null) {
// 设置相机预览宽高,此处设置为TextureView宽高
Camera.Parameters params = mCamera.getParameters();
// params.setPreviewSize(width, height);//不能设置显示的图像的大小。有些相机尺寸不对,则会闪退
// 设置自动对焦模式
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//设置自动对焦
mCamera.setParameters(params);//设置相机参数,包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。
}
try {
mCamera.setDisplayOrientation(0);// 设置预览角度,并不改变获取到的原始数据方向
// 绑定相机和预览的View
mCamera.setPreviewTexture(surface);//绑定绘制预览图像的surface。
//surface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,
// 通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。
// 开始预览
mCamera.startPreview();//开始预览,将camera底层硬件传来的预览帧数据显示在绑定的surface上。
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void startRequestPermission(){
//321为请求码
ActivityCompat.requestPermissions(this,PERMISSIONS_STORAGE,321);
}
//在surface发生format或size变化时调用。
//SurfaceTexture缓冲大小变化
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
// SurfaceTexture即将被销毁
//在此处回调做一些释放资源的操作
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
mCamera.stopPreview();//停止预览,关闭camra底层的帧数据传递以及surface上的绘制。
mCamera.release();//释放Camera实例
return false;
}
//SurfaceTexture通过updateImage更新
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
}
权限申请
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cameratest">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
调用前置摄像头
前置摄像头的话其实就是把上面的camera.open()对应的id改一下就好~
参考资料
https://blog.csdn.net/Andreaw/article/details/88761062(TextureView)
https://github.com/android/camera-samples(官方示例)
https://blog.csdn.net/qq_17441227/article/details/82877161(主要参考示例)
https://www.cnblogs.com/baiqiantao/p/299570dc16f00c0ad12de306613e449f.html(TextureView)