android 圆形相机预览拍照_Android Camera SurfaceView 预览拍照-程序员宅基地

技术标签: android 圆形相机预览拍照  

Android使用Camera API + SurfaceView 方式进行预览拍照。

1、创建一个SurfaceView,并实现SurfaceHolder的回调。由于Camera在SurfaceView中是通过SurfaceHolder 使得Surfaceview能够预览Camera返回的数据,因此我们需要实现SurfaceHolder 的回调,实现图如下:

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

private static final String TAG = CameraSurfaceView.class.getSimpleName();

private SurfaceHolder mSurfaceHolder;

public CameraSurfaceView(Context context) {

super(context);

init();

}

public CameraSurfaceView(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init();

}

private void init() {

mSurfaceHolder = getHolder();

mSurfaceHolder.addCallback(this);

}

@Override

public void surfaceCreated(SurfaceHolder holder) {

CameraUtils.openFrontalCamera(CameraUtils.DESIRED_PREVIEW_FPS);

}

@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

CameraUtils.startPreviewDisplay(holder);

}

@Override

public void surfaceDestroyed(SurfaceHolder holder) {

CameraUtils.releaseCamera();

}

}

2、CameraUtils 辅助类主要是Camera API 的一些操作,比如打开相机、开始预览、停止预览、切换相机、设置预览参数等操作,具体实现如下:

public class CameraUtils {

// 相机默认宽高,相机的宽度和高度跟屏幕坐标不一样,手机屏幕的宽度和高度是反过来的。

public static final int DEFAULT_WIDTH = 1280;

public static final int DEFAULT_HEIGHT = 720;

public static final int DESIRED_PREVIEW_FPS = 30;

private static int mCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT;

private static Camera mCamera;

private static int mCameraPreviewFps;

private static int mOrientation = 0;

/**

* 打开相机,默认打开前置相机

* @param expectFps

*/

public static void openFrontalCamera(int expectFps) {

if (mCamera != null) {

throw new RuntimeException("camera already initialized!");

}

Camera.CameraInfo info = new Camera.CameraInfo();

int numCameras = Camera.getNumberOfCameras();

for (int i = 0; i < numCameras; i++) {

Camera.getCameraInfo(i, info);

if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {

mCamera = Camera.open(i);

mCameraID = info.facing;

break;

}

}

// 如果没有前置摄像头,则打开默认的后置摄像头

if (mCamera == null) {

mCamera = Camera.open();

mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;

}

// 没有摄像头时,抛出异常

if (mCamera == null) {

throw new RuntimeException("Unable to open camera");

}

Camera.Parameters parameters = mCamera.getParameters();

mCameraPreviewFps = CameraUtils.chooseFixedPreviewFps(parameters, expectFps * 1000);

parameters.setRecordingHint(true);

mCamera.setParameters(parameters);

setPreviewSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);

setPictureSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);

mCamera.setDisplayOrientation(mOrientation);

}

/**

* 根据ID打开相机

* @param cameraID

* @param expectFps

*/

public static void openCamera(int cameraID, int expectFps) {

if (mCamera != null) {

throw new RuntimeException("camera already initialized!");

}

mCamera = Camera.open(cameraID);

if (mCamera == null) {

throw new RuntimeException("Unable to open camera");

}

mCameraID = cameraID;

Camera.Parameters parameters = mCamera.getParameters();

mCameraPreviewFps = CameraUtils.chooseFixedPreviewFps(parameters, expectFps * 1000);

parameters.setRecordingHint(true);

mCamera.setParameters(parameters);

setPreviewSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);

setPictureSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT);

mCamera.setDisplayOrientation(mOrientation);

}

/**

* 开始预览

* @param holder

*/

public static void startPreviewDisplay(SurfaceHolder holder) {

if (mCamera == null) {

throw new IllegalStateException("Camera must be set when start preview");

}

try {

mCamera.setPreviewDisplay(holder);

mCamera.startPreview();

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 切换相机

* @param cameraID

*/

public static void switchCamera(int cameraID, SurfaceHolder holder) {

if (mCameraID == cameraID) {

return;

}

mCameraID = cameraID;

// 释放原来的相机

releaseCamera();

// 打开相机

openCamera(cameraID, CameraUtils.DESIRED_PREVIEW_FPS);

// 打开预览

startPreviewDisplay(holder);

}

/**

* 释放相机

*/

public static void releaseCamera() {

if (mCamera != null) {

mCamera.stopPreview();

mCamera.release();

mCamera = null;

}

}

/**

* 开始预览

*/

public static void startPreview() {

if (mCamera != null) {

mCamera.startPreview();

}

}

/**

* 停止预览

*/

public static void stopPreview() {

if (mCamera != null) {

mCamera.stopPreview();

}

}

/**

* 拍照

*/

public static void takePicture(Camera.ShutterCallback shutterCallback,

Camera.PictureCallback rawCallback,

Camera.PictureCallback pictureCallback) {

if (mCamera != null) {

mCamera.takePicture(shutterCallback, rawCallback, pictureCallback);

}

}

/**

* 设置预览大小

* @param camera

* @param expectWidth

* @param expectHeight

*/

public static void setPreviewSize(Camera camera, int expectWidth, int expectHeight) {

Camera.Parameters parameters = camera.getParameters();

Camera.Size size = calculatePerfectSize(parameters.getSupportedPreviewSizes(),

expectWidth, expectHeight);

parameters.setPreviewSize(size.width, size.height);

camera.setParameters(parameters);

}

/**

* 获取预览大小

* @return

*/

public static Camera.Size getPreviewSize() {

if (mCamera != null) {

return mCamera.getParameters().getPreviewSize();

}

return null;

}

/**

* 设置拍摄的照片大小

* @param camera

* @param expectWidth

* @param expectHeight

*/

public static void setPictureSize(Camera camera, int expectWidth, int expectHeight) {

Camera.Parameters parameters = camera.getParameters();

Camera.Size size = calculatePerfectSize(parameters.getSupportedPictureSizes(),

expectWidth, expectHeight);

parameters.setPictureSize(size.width, size.height);

camera.setParameters(parameters);

}

/**

* 获取照片大小

* @return

*/

public static Camera.Size getPictureSize() {

if (mCamera != null) {

return mCamera.getParameters().getPictureSize();

}

return null;

}

/**

* 计算最完美的Size

* @param sizes

* @param expectWidth

* @param expectHeight

* @return

*/

public static Camera.Size calculatePerfectSize(List sizes, int expectWidth,

int expectHeight) {

sortList(sizes); // 根据宽度进行排序

Camera.Size result = sizes.get(0);

boolean widthOrHeight = false; // 判断存在宽或高相等的Size

// 辗转计算宽高最接近的值

for (Camera.Size size: sizes) {

// 如果宽高相等,则直接返回

if (size.width == expectWidth && size.height == expectHeight) {

result = size;

break;

}

// 仅仅是宽度相等,计算高度最接近的size

if (size.width == expectWidth) {

widthOrHeight = true;

if (Math.abs(result.height - expectHeight)

> Math.abs(size.height - expectHeight)) {

result = size;

}

}

// 高度相等,则计算宽度最接近的Size

else if (size.height == expectHeight) {

widthOrHeight = true;

if (Math.abs(result.width - expectWidth)

> Math.abs(size.width - expectWidth)) {

result = size;

}

}

// 如果之前的查找不存在宽或高相等的情况,则计算宽度和高度都最接近的期望值的Size

else if (!widthOrHeight) {

if (Math.abs(result.width - expectWidth)

> Math.abs(size.width - expectWidth)

&& Math.abs(result.height - expectHeight)

> Math.abs(size.height - expectHeight)) {

result = size;

}

}

}

return result;

}

/**

* 排序

* @param list

*/

private static void sortList(List list) {

Collections.sort(list, new Comparator() {

@Override

public int compare(Camera.Size pre, Camera.Size after) {

if (pre.width > after.width) {

return 1;

} else if (pre.width < after.width) {

return -1;

}

return 0;

}

});

}

/**

* 选择合适的FPS

* @param parameters

* @param expectedThoudandFps 期望的FPS

* @return

*/

public static int chooseFixedPreviewFps(Camera.Parameters parameters, int expectedThoudandFps) {

List supportedFps = parameters.getSupportedPreviewFpsRange();

for (int[] entry : supportedFps) {

if (entry[0] == entry[1] && entry[0] == expectedThoudandFps) {

parameters.setPreviewFpsRange(entry[0], entry[1]);

return entry[0];

}

}

int[] temp = new int[2];

int guess;

parameters.getPreviewFpsRange(temp);

if (temp[0] == temp[1]) {

guess = temp[0];

} else {

guess = temp[1] / 2;

}

return guess;

}

/**

* 设置预览角度,setDisplayOrientation本身只能改变预览的角度

* previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的

* 拍摄的照片需要自行处理

* 这里Nexus5X的相机简直没法吐槽,后置摄像头倒置了,切换摄像头之后就出现问题了。

* @param activity

*/

public static int calculateCameraPreviewOrientation(Activity activity) {

Camera.CameraInfo info = new Camera.CameraInfo();

Camera.getCameraInfo(mCameraID, info);

int rotation = activity.getWindowManager().getDefaultDisplay()

.getRotation();

int degrees = 0;

switch (rotation) {

case Surface.ROTATION_0:

degrees = 0;

break;

case Surface.ROTATION_90:

degrees = 90;

break;

case Surface.ROTATION_180:

degrees = 180;

break;

case Surface.ROTATION_270:

degrees = 270;

break;

}

int result;

if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {

result = (info.orientation + degrees) % 360;

result = (360 - result) % 360;

} else {

result = (info.orientation - degrees + 360) % 360;

}

mOrientation = result;

return result;

}

/**

* 获取当前的Camera ID

* @return

*/

public static int getCameraID() {

return mCameraID;

}

/**

* 获取当前预览的角度

* @return

*/

public static int getPreviewOrientation() {

return mOrientation;

}

/**

* 获取FPS(千秒值)

* @return

*/

public static int getCameraPreviewThousandFps() {

return mCameraPreviewFps;

}

}

3、在Activity中使用CameraSurfaceview,有Android6.0动态权限申请问题,需要我们判断相机和存储权限是否申请了:

public class CameraSurfaceViewActivity extends AppCompatActivity implements View.OnClickListener {

private static final int REQUEST_CAMERA = 0x01;

private CameraSurfaceView mCameraSurfaceView;

private Button mBtnTake;

private Button mBtnSwitch;

private int mOrientation;

// CameraSurfaceView 容器包装类

private FrameLayout mAspectLayout;

private boolean mCameraRequested;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

requestWindowFeature(Window.FEATURE_NO_TITLE);

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN);

setContentView(R.layout.activity_camera_surface);

// Android 6.0相机动态权限检查

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)

== PackageManager.PERMISSION_GRANTED) {

initView();

} else {

ActivityCompat.requestPermissions(this,

new String[]{

Manifest.permission.CAMERA,

Manifest.permission.WRITE_EXTERNAL_STORAGE

}, REQUEST_CAMERA);

}

}

/**

* 初始化View

*/

private void initView() {

mAspectLayout = (FrameLayout) findViewById(R.id.layout_aspect);;

mCameraSurfaceView = new CameraSurfaceView(this);

mAspectLayout.addView(mCameraSurfaceView);

mOrientation = CameraUtils.calculateCameraPreviewOrientation(CameraSurfaceViewActivity.this);

mBtnTake = (Button) findViewById(R.id.btn_take);

mBtnTake.setOnClickListener(this);

mBtnSwitch = (Button) findViewById(R.id.btn_switch);

mBtnSwitch.setOnClickListener(this);

}

@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

switch (requestCode) {

// 相机权限

case REQUEST_CAMERA:

if (grantResults.length > 0

&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {

mCameraRequested = true;

initView();

}

break;

}

}

@Override

protected void onResume() {

super.onResume();

if (mCameraRequested) {

CameraUtils.startPreview();

}

}

@Override

protected void onPause() {

super.onPause();

CameraUtils.stopPreview();

}

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.btn_take:

takePicture();

break;

case R.id.btn_switch:

switchCamera();

break;

}

}

/**

* 拍照

*/

private void takePicture() {

CameraUtils.takePicture(new Camera.ShutterCallback() {

@Override

public void onShutter() {

}

}, null, new Camera.PictureCallback() {

@Override

public void onPictureTaken(byte[] data, Camera camera) {

CameraUtils.startPreview();

Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

if (bitmap != null) {

bitmap = ImageUtils.getRotatedBitmap(bitmap, mOrientation);

String path = Environment.getExternalStorageDirectory() + "/DCIM/Camera/"

+ System.currentTimeMillis() + ".jpg";

try {

FileOutputStream fout = new FileOutputStream(path);

BufferedOutputStream bos = new BufferedOutputStream(fout);

bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);

bos.flush();

bos.close();

fout.close();

} catch (IOException e) {

e.printStackTrace();

}

}

CameraUtils.startPreview();

}

});

}

/**

* 切换相机

*/

private void switchCamera() {

if (mCameraSurfaceView != null) {

CameraUtils.switchCamera(1 - CameraUtils.getCameraID(), mCameraSurfaceView.getHolder());

// 切换相机后需要重新计算旋转角度

mOrientation = CameraUtils.calculateCameraPreviewOrientation(CameraSurfaceViewActivity.this);

}

}

}

由于用到了相机和存储权限,我们需要在manifest中注册相机和存储权限,这里要说明的是,manifest用use-permission只是声明了需要使用哪些权限,而我们实际项目中在使用到这两项权限时,需要你检查权限是否已经被授权,如果没授权,则需要请求授权:

另外,ImageUtils类的实现如下:

public class ImageUtils {

/**

* 旋转图片

* @param bitmap

* @param rotation

* @Return

*/

public static Bitmap getRotatedBitmap(Bitmap bitmap, int rotation) {

Matrix matrix = new Matrix();

matrix.postRotate(rotation);

return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),

bitmap.getHeight(), matrix, false);

}

/**

* 镜像翻转图片

* @param bitmap

* @Return

*/

public static Bitmap getFlipBitmap(Bitmap bitmap) {

Matrix matrix = new Matrix();

matrix.setScale(-1, 1);

matrix.postTranslate(bitmap.getWidth(), 0);

return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),

bitmap.getHeight(), matrix, false);

}

}

layout如下:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.cgfay.camerasample.CameraSurfaceViewActivity">

android:id="@+id/layout_aspect"

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="horizontal"

android:layout_gravity="bottom"

android:gravity="center">

android:id="@+id/btn_take"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="拍照" />

android:id="@+id/btn_switch"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="切换相机" />

至此,通过SurfaceView + Camera API 预览拍照功能已经实现。

备注: Camera API 在打开相机是在哪个线程,那么onPreviewFrame回调执行就在哪个线程。因此,如果要通过onPreviewFrame回调使用预览数据,则可以通过HandlerThread 异步调用Camera进行操作。

另外一个问题,onPreviewFrame方法中不要执行过于复杂的逻辑操作,这样会阻塞Camera,无法获取新的Frame,导致帧率下降。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39603132/article/details/111843180

智能推荐

基于opencv的人脸检测与识别(python)(1)-程序员宅基地

文章浏览阅读514次。基于opencv的人脸检测,再使用tensorflow的框架以及keras库通过卷积神经网络对获取的人脸数据进行训练,生成训练模型,并对实时图片进行人脸识别。

Numbers on Tree_k. numbers on tree-程序员宅基地

文章浏览阅读160次。D. Numbers on Treetime limit per test1 secondmemory limit per test256 megabytesinputstandard inputoutputstandard outputEvlampiy was gifted a rooted tree. The vertices of the tree are numbered..._k. numbers on tree

软件测试工程师面试题-测试概念篇_面试测试工程师概念性问题有哪些-程序员宅基地

文章浏览阅读901次。转载于:https://www.cnblogs.com/mrwuzs/p/7976534.html_面试测试工程师概念性问题有哪些

0910-12学习记录-OFDM细节描述_ofdm子载波流数-程序员宅基地

文章浏览阅读899次。PPDU编码过程总览编码过程包含了很多细节的步骤,在以下的细节条款有很详细的描述。接下来的总览主要是为了促进对于这些细节的理解。生成PLCP前导码字段,包含10个短训练序列(用来做AGC增益控制,分集选择,时间的同步获取以及在接收端的粗频偏估计)和两个重复的长训练序列(用来做信道估计以及接收端的精准频偏估计)前面有保护间隔(GI)。具体的细节描述在 17.3.3.17.3.3.描述PLCP..._ofdm子载波流数

OSChina 中秋节乱弹 ——加班比抢了我的小鱼干,更让我难过!-程序员宅基地

文章浏览阅读234次。2019独角兽企业重金招聘Python工程师标准>>> ...

SpringBoot引入MyBatis_springboot mybatis jar maven引入-程序员宅基地

文章浏览阅读128次。首先进入spring官网,添加依赖,然后生成项目。打开Idea,然后导入刚才生成的项目文件。测试不用集成组件,要不然下载会下载很长时间。找到maven插件,导入必要的jar包。_springboot mybatis jar maven引入

随便推点

Shell根据文本内容批量修改文件名(附完整代码)_linux shell while批量改名-程序员宅基地

文章浏览阅读3.1k次。Shell根据文本内容批量修改文件名_linux shell while批量改名

“计算机系统概述”学习笔记_程序计数器怎么计算下一条指令的地址-程序员宅基地

文章浏览阅读2.2k次。文章目录机器字长存储器组成运算器组成控制器组成计算机系统的层次结构指令执行过程源程序翻译成可执行文件的过程机器字长计算机进行一次整数运算所能处理的二进制数据的位数。存储器组成组件由大到小(大包含小)依次为:计算机系统=计算机硬件系统+计算机软件系统计算机硬件系统=存储器+控制器+运算器+输入设备+输出设备存储器=主存储器(内存储器)+辅助存储器(外存储器)主存储器=地址寄存器(MAR)+存储体+数据寄存器(MDR)+时序控制逻辑存储体=若干存储单元存储单元=若干存储元件每个存储元件存_程序计数器怎么计算下一条指令的地址

Scala编写JedisPoolUtils工具类_jedis scala-程序员宅基地

文章浏览阅读1.5k次。package com.cloudera.utilsimport redis.clients.jedis.{Jedis, JedisPool, JedisPoolConfig}object JedisPoolUtils extends Serializable { @transient private var pool: JedisPool = null def makePoo..._jedis scala

MySQL 使用AVG聚合函数时,保留两位小数的方法_mysql avg保留两位小数-程序员宅基地

文章浏览阅读3.1k次。SELECT ( CASE WHEN platform_parameter IS NULL THEN page ELSE platform_parameter END ) AS page, sum(pv) AS pv, sum(uv) AS uv, CAST(AVG(time) AS DECIMAL(10,2)) as timeFROM t_page_stat_daily tLEFT JOIN t_app_dictionary d ON t.app_id = d._mysql avg保留两位小数

QOS_qos双色分离-程序员宅基地

文章浏览阅读1k次,点赞3次,收藏9次。QOS按权重进行资源分配,进行资源协调(1) 甄别流量(哪些有用,哪些无用----重要程度)(2) 分析流量诉求(有哪些要求----带宽、时延)(3) 细化流量诉求(对哪些参数比较敏感)队列机制 拥塞避免一、 流量分类TOS位,位于包头为之后,8位二进制规定:数字越大,越容易从接口出去(优先级越高)不合理之处:当队列满时,若再有较重要流量要进行排队,将会被拒绝DSCP:插分..._qos双色分离

聚类算法系列---DBSCAN密度聚类算法_聚类公式 ci min y dbscan-程序员宅基地

文章浏览阅读430次。DBSCAN(Density-Based Spatial Clustering of Application with Noise)思想:用一个点的邻域内的邻居点数来衡量该店所在的空间密度,根据密度来判定将样本划分到哪个簇,对于同一个簇里面的样本是紧密相连的。在进行聚类的时候事先不知道cluster的数目。基本概念设数据集X={x1,x2,....,xn}Eps:定义密度时的邻域半径..._聚类公式 ci min y dbscan