Android实现调用摄像头进行拍照功能
作者:灵思迈Leansmall 发布时间:2021-07-16 20:26:07
标签:Android,摄像头,拍照
现在Android智能手机的像素都会提供照相的功能,大部分的手机的摄像头的像素都在1000万以上的像素,有的甚至会更高。它们大多都会支持光学变焦、曝光以及快门等等。
下面的程序Demo实例示范了使用Camera v2来进行拍照,当用户按下拍照键时,该应用会自动对焦,当对焦成功时拍下照片。
layout/activity_main.xml界面布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fukaimei.camerav2test">
<!-- 授予该程序使用摄像头的权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<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>
上面的程序的界面提供了一个自定义TextureView来显示预览取景,十分简单。该自定义TextureView类的代码如下:
AutoFitTextureView.java逻辑代码如下:
package com.fukaimei.camerav2test;
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
/**
* Created by FuKaimei on 2017/9/29.
*/
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setAspectRatio(int width, int height) {
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
接来了的MainActivity.java程序将会使用CameraManager来打开CameraDevice,并通过CameraDevice创建CameraCaptureSession,然后即可通过CameraCaptureSession进行预览或拍照了。
MainActivity.java逻辑代码如下:
package com.fukaimei.camerav2test;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class MainActivity extends Activity implements View.OnClickListener {
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final String TAG = "MainActivity";
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
private AutoFitTextureView textureView;
// 摄像头ID(通常0代表后置摄像头,1代表前置摄像头)
private String mCameraId = "0";
// 定义代表摄像头的成员变量
private CameraDevice cameraDevice;
// 预览尺寸
private Size previewSize;
private CaptureRequest.Builder previewRequestBuilder;
// 定义用于预览照片的捕获请求
private CaptureRequest previewRequest;
// 定义CameraCaptureSession成员变量
private CameraCaptureSession captureSession;
private ImageReader imageReader;
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture
, int width, int height) {
// 当TextureView可用时,打开摄像头
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture
, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
// 摄像头被打开时激发该方法
@Override
public void onOpened(CameraDevice cameraDevice) {
MainActivity.this.cameraDevice = cameraDevice;
// 开始预览
createCameraPreviewSession(); // ②
}
// 摄像头断开连接时激发该方法
@Override
public void onDisconnected(CameraDevice cameraDevice) {
cameraDevice.close();
MainActivity.this.cameraDevice = null;
}
// 打开摄像头出现错误时激发该方法
@Override
public void onError(CameraDevice cameraDevice, int error) {
cameraDevice.close();
MainActivity.this.cameraDevice = null;
MainActivity.this.finish();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureView = (AutoFitTextureView) findViewById(R.id.texture);
// 为该组件设置 *
textureView.setSurfaceTextureListener(mSurfaceTextureListener);
findViewById(R.id.capture).setOnClickListener(this);
}
@Override
public void onClick(View view) {
captureStillPicture();
}
private void captureStillPicture() {
try {
if (cameraDevice == null) {
return;
}
// 创建作为拍照的CaptureRequest.Builder
final CaptureRequest.Builder captureRequestBuilder =
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 将imageReader的surface作为CaptureRequest.Builder的目标
captureRequestBuilder.addTarget(imageReader.getSurface());
// 设置自动对焦模式
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 获取设备方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
// 根据设备方向计算设置照片的方向
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION
, ORIENTATIONS.get(rotation));
// 停止连续取景
captureSession.stopRepeating();
// 捕获静态图像
captureSession.capture(captureRequestBuilder.build()
, new CameraCaptureSession.CaptureCallback() // ⑤
{
// 拍照完成时激发该方法
@Override
public void onCaptureCompleted(CameraCaptureSession session
, CaptureRequest request, TotalCaptureResult result) {
try {
// 重设自动对焦模式
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
// 设置自动曝光模式
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 打开连续取景模式
captureSession.setRepeatingRequest(previewRequest, null,
null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 打开摄像头
private void openCamera(int width, int height) {
setUpCameraOutputs(width, height);
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
// 打开摄像头
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
manager.openCamera(mCameraId, stateCallback, null); // ①
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = textureView.getSurfaceTexture();
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
Surface surface = new Surface(texture);
// 创建作为预览的CaptureRequest.Builder
previewRequestBuilder = cameraDevice
.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 将textureView的surface作为CaptureRequest.Builder的目标
previewRequestBuilder.addTarget(new Surface(texture));
// 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求
cameraDevice.createCaptureSession(Arrays.asList(surface
, imageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③
{
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
// 如果摄像头为null,直接结束方法
if (null == cameraDevice) {
return;
}
// 当摄像头已经准备好时,开始显示预览
captureSession = cameraCaptureSession;
try {
// 设置自动对焦模式
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光模式
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 开始显示相机预览
previewRequest = previewRequestBuilder.build();
// 设置预览时连续捕获图像数据
captureSession.setRepeatingRequest(previewRequest,
null, null); // ④
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Toast.makeText(MainActivity.this, "配置失败!"
, Toast.LENGTH_SHORT).show();
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void setUpCameraOutputs(int width, int height) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
// 获取指定摄像头的特性
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(mCameraId);
// 获取摄像头支持的配置属性
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// 获取摄像头支持的最大尺寸
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea());
// 创建一个ImageReader对象,用于获取摄像头的图像数据
imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, 2);
imageReader.setOnImageAvailableListener(
new ImageReader.OnImageAvailableListener() {
// 当照片数据可用时激发该方法
@Override
public void onImageAvailable(ImageReader reader) {
// 获取捕获的照片数据
Image image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
// 使用IO流将照片写入指定文件
File file = new File(getExternalFilesDir(null), "pic.jpg");
buffer.get(bytes);
try (
FileOutputStream output = new FileOutputStream(file)) {
output.write(bytes);
Toast.makeText(MainActivity.this, "保存: " + file, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
} finally {
image.close();
}
}
}, null);
// 获取最佳的预览尺寸
previewSize = chooseOptimalSize(map.getOutputSizes(
SurfaceTexture.class), width, height, largest);
// 根据选中的预览尺寸来调整预览组件(TextureView的)的长宽比
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
textureView.setAspectRatio(
previewSize.getWidth(), previewSize.getHeight());
} else {
textureView.setAspectRatio(
previewSize.getHeight(), previewSize.getWidth());
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
Log.d(TAG, "出现错误");
}
}
private static Size chooseOptimalSize(Size[] choices
, int width, int height, Size aspectRatio) {
// 收集摄像头支持的打过预览Surface的分辨率
List<Size> bigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getHeight() == option.getWidth() * h / w &&
option.getWidth() >= width && option.getHeight() >= height) {
bigEnough.add(option);
}
}
// 如果找到多个预览尺寸,获取其中面积最小的。
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else {
System.out.println("找不到合适的预览尺寸!!!");
return choices[0];
}
}
// 为Size定义一个比较器Comparator
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
// 强转为long保证不会发生溢出
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
}
上面的程序中序号①的代码是用于打开系统摄像头,openCamera()方法的第一个参数代表请求打开的摄像头ID,此处传入的摄像头ID为“0”,这代表打开设备后置摄像头;如果需要打开设备指定摄像头(比如前置摄像头),可以在调用openCamera()方法时传入相应的摄像头ID。
注意:由于该程序需要使用手机的摄像头,因此还需要在清单文件AndroidManifest.xml文件中授权相应的权限:
<!-- 授予该程序使用摄像头的权限 -->
<uses-permission android:name="android.permission.CAMERA" />
Demo程序运行效果界面截图如下:
Demo程序源码下载地址
来源:https://blog.csdn.net/leansmall/article/details/80093760
0
投稿
猜你喜欢
- 由于项目上的需要侧滑条目展示收藏按钮,记得之前代码家有写过一个厉害的开源控件 AndroidSwipeLayout 本来准备直接拿来使用,但
- 以下内容通过1、实现目标注入程序,2、实现主程序,3、实现注入函数,4、thumb指令集实现等4个方面详细分析了android中inline
- 1 前言在项目开发中,异步化处理是非常常见的解决问题的手段,异步化处理除了使用线程池之外,还可以使用 CompletableFut
- 1. 增强for概述增强for循环,也叫Foreach循环,用于数组和容器(集合类)的遍历。使用foreach循环遍历数组和集合元素时,无需
- 微服务的特点决定了功能模块的部署是分布式的,大部分功能模块都是运行在不同的机器上,彼此通过服务调用进行交互,前后台的业务流会经过很多个微服务
- 背景项目中经常会用到消息推送功能,关于推送技术的实现,我们通常会联想到轮询、comet长连接技术,虽然这些技术能够实现,但是需要反复连接,对
- java 中基本算法之希尔排序的实例详解希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的
- 在上篇文章给大家介绍了Android Bluetooth蓝牙技术初体验相关内容,感兴趣的朋友可以点击了解详情。一:蓝牙设备之间的通信主要包括
- 1、在Android studio中进行打开一个项目的文件之后,然后进行点击Android stuio中菜单中的“tools”的选项。在弹出
- 前言:来这家公司上班后,开始使用Git作为项目版本控制系统,由于以前用的是SVN,所以对Git也就简单学习了一下。但是,实践出真知,当开始使
- 前言Spring Cloud默认为Zuul编写并启用了一些过滤器,这些过滤器有什么作用呢?我们不妨按照@EnableZuulServer、@
- 引言对于Nacos大家应该都不太陌生,出身阿里名声在外,能做动态服务发现、配置管理,非常好用的一个工具。然而这样的技术用的人越多面试被问的概
- 采用继承Thead类实现多线程:优势:编写简单,如果需要访问当前线程,只需使用this即可,无需使用Thead.currentThread(
- 代码很简单,功能也很简单,这里就不多废话了#include<stdio.h>int main(){ char ku[16]={&
- 同时使用and和or的查询UserServiceImpl 类,service实现类import org.springframework.be
- 前言1.因为涉及到对象锁,Wait、Notify一定要在synchronized里面进行使用。2.Wait必须暂定当前正在执行的线程,并释放
- 概述java.lang.String 类代表字符串。Java程序中所有的字符串文字(例如"abc" )都可以被看作是实现
- 先引用using System.Runtime.InteropServices; 的命名空间, 然后在合适的位置加上如下代码就OK。。注意:
- 这是我们用得比较多的一种设计模式,也是23种标准设计模式之一,使用前面讲的简单工厂设计模式,遇到具体产品经常变换时就不太适合了,违反了开闭设
- 当遇到以下场景:其他人写的单元测试影响统计结果一些需要调用外部接口的测试暂不运行需要在非本机环境上运行一些不回滚的单元测试则有必要选择以下方