水壶里的Android Camera2 API



不久前,我忙于使用机器人机器,试图在其上安装ANDROID智能手机。 我的任务就是使购物车在进化上进步。 这样,可以说,她可以用他的感应器感知世界,用她的眼睛(相机)看着他,用麦克风听见,并在免提电话上发誓。 当然,AVR资源还不够用,因此推车上的微控制器移到了较低的位置,位于脊髓区域的某个地方,以控制电机和各种无条件的反射。

但是奇怪的是,当我开始编写智能手机应用程序时,不良的IDE ANDROID STUDIO开始不断删除我的代码,并将其称为过时的代码。

相机= 相机。 打开();

如您所见,尤其是在我尝试使用相机的那些部分。 因为我在互联网上阅读并在这里这里这里甚至这里学习了许多使用android和相机的经验,真是令人失望。 那里什么都没有划掉。 它被称为Camera API。 那里的一切都很简单和合乎逻辑。 但是Google固执地将我推向了某种Camera2 API

我看了一下,然后就在演示代码的每一行中遇到了许多不同的回调,构建器,处理程序和循环程序的麻烦。 如果您是普通的业余爱好者,而不是android开发人员,那么采用哪种方法完全无法理解。 而且,尽管此更新似乎像四年前一样,但今天在网络上甚至还有几篇有关Camera2 API的文章。 但是我所发现的只是2016年在Hacker上的一篇文章,同年乌克兰兄弟的三部分文章,2017年在Habré上的两篇文章以及日本扩音器今井智昭的一篇文章《了解Camera2》。 我的意思是一些结构化和形式化的信息,而不是散布在互联网上的代码片段,如“看看我能做到”和样式表(“看代码顶住,对我没有用”)。

现在,如果您仍然想知道为什么我需要删减关于该主题的帖子
已经在2019年了,欢迎光临。

实际上,我喜欢Hacker上的文章,因为它至少使我大致了解了为什么引入了使用相机的新范例。 同样清楚的是,为什么需要一些以前的API中没有的新方法。 但是为这篇文章编写一个工作程序是完全不可能的,因为显然,像真正的黑客一样,这些作者仅引用了方法的名称,也许还有几行其他代码。 但这对我来说还不够。

我不认为关于Habré的帖子很好,甚至我都理解了前几段,但这是重点,因为该帖子的作者使用RxJava2打败了相机,这自动将我排除在其他读者群体之外。 我通常会处理OOP,但这是某种反应式编程。

即使他不是用母语写的,日语文章也更好。 但是我也不知道Kotlin,尽管我当然为国内开发人员感到高兴,并赞赏其语法的简洁性,与Google开发人员页面上的JAVA-Vermicelli相比(顺便说一句,在此页面上,我也很清楚显然不多)。

上一篇使我受益匪浅的文章是来自兄弟的乌克兰的三部分文章。 我什至在那发射了东西,在那里看到了东西。 但是不幸的是,在第三部分中,本文的作者非常疲倦,并且还开始以片段形式发布代码,这些代码并没有构成我的整体工作。 此外,最后,作者完全不高兴,因为他得到了错误的调色板图像,这是他要为其计数的。



说,这是因为Lollipop的版本是5.0,并且存在一个错误。 您需要升级到Lollipop 5.1,然后一切都会好起来。 但是不知何故。 另外,有害的乌克兰人也将注意力集中在JAVA SCRIPT文章上,并且在复制代码时,在程序文本中注入了大量的垃圾。 伙计们,好吧,你做不到……我不得不在Firefox中安装一个特殊的插件。

因此,我不得不拿起堕落的旗帜,并完成工作直到最后,这当然需要巨大的精神努力。 而且,由于对获得工作结果的认识感到不满足,因此我想与业余茶壶分享。 而且,这还远没有结束。 我仍然需要学习如何将实时视频从android摄像机传输到计算机(机器人必须发展,这是进化的规律),而不是像我那样盲目但平稳地进行。 诸如媒体编解码器之类的勃朗峰障碍也只是马口铁。

但另一方面,整个事情可能会以完全失望而告终。 并不是因为他们将无法攀登勃朗峰。

而且由于现在智能手机制造商正在开发全新的相机制造方法。
一个月前,有传言称诺基亚拥有五个主要摄像头的智能手机。 如何与此相关? 有趣且充满希望的谣言或其他奇怪的事情? 不管这样的设计看起来有多么独特,诺基亚无疑都无法成为在紧凑型设备中引入不同数量的镜头和传感器的先驱。 Light L16相机在2015年已经配备了16个镜头,而且该公司显然拥有一个正在运行的新原型。 在上方,您可以看到它的外观。

在华为P20 Pro中出现了三摄相机之后,过渡到具有五个摄像头的智能手机听起来不再像几年前那样可笑了。 但是,主要问题仍然存在-重点是什么?
这么多镜片怎么办?

首先想到的是现代智能手机市场上可用的各种类型的相机传感器,以及添加更多功能。 如果您可以在一台设备上获得所有这些功能,为什么还要在广角,远摄,带散景的肖像或单色之间进行选择?

从理论上讲,这样的设计使用起来很尴尬。 该软件将必须在模式之间自动切换或向用户提供一组复杂的选项。 而且,对于这种解决方案的所有可疑优点,这种设计的开发将是非常昂贵的。 每个摄像头在很大程度上都可以独立运行,并且购买者不太可能使用大量模式。 目前尚不清楚他们愿意为这种功能支付多少费用。 因此,具有多个模块的相机应该能够做更多的事情来吸引用户。

华为P20 Pro已提供其多个相机模块如何协同工作以提供有趣结果的版本。 我们正在谈论华为的技术,例如单色和混合变焦。 第一种通过将常规RGB数据与感光的黑白传感器结合使用来提高标准帧的动态范围。 混合变焦的前景更加广阔:它将来自多个摄像机的数据组合在一起,以提高图像的分辨率,从而实现更好的变焦。 因此,在P20 Pro 8 MP中,远摄镜头传感器可让您以10MP分辨率以3倍和5倍变焦拍摄。
更高的分辨率-更高的灵活性

第一台Light L16相机的工作原理类似,使用潜望镜将相机模块安装到紧凑的机身中。 相机从28、70和150毫米的多个模块中获取数据,具体取决于变焦级别。 结果是从10个略有不同的角度以52倍的放大倍率拍摄了52 MP大镜头,放大倍数高达5倍。 专为智能手机开发的新模型概念可与5-9镜头配合使用。 这样的相机模块能够拍摄64兆像素的大图片。

在弱光和具有多个光圈的HDR中拍摄时,多次拍摄的想法也增加了好处。 由于同时进行软件仿真和使用多个焦距,因此还可以获得画框深度的高质量效果。

轻型L16令人失望,但这个想法本身是有希望的。 而成功的下一代可能被证明是值得的。 该公司声称,智能手机将在今年年底发布,届时将安装其最新的多镜头解决方案。

考虑到2013年诺基亚在Pelican Imaging上进行投资的悠久历史,这一想法可以追溯到诺基亚在部署多台相机方面的经验。 与光不同,此处的传感器要小得多。 即使如此,该技术也有望带来非常相似的优势,包括软件焦点改变,深度测量和增加最终图像的大小。 不幸的是,Tessera在2016年收购了这家公司,但是这个想法本身可能并没有引起诺基亚工程师的注意。

诺基亚目前的照片合作伙伴蔡司(Zeiss)拥有可切换变焦的专利,但是我们还没有从他们那里得到关于多镜头设计的任何消息。 也许诺基亚的另一个合作伙伴FIH Mobile看起来更有前途。 该公司归富士康所有,推出诺基亚手机,并于2015年投资了Light,获得了使用该技术的许可。

而且,如果您认为诺基亚泄密和Light原型有一些共同点,那就不是巧合了。 连接两家富士康公司。 那么诺基亚智能手机会成为第一个使用Light技术的人吗?
那是未来吗?

超高分辨率并不是什么新概念,2014年,Oppo Find 7使用了类似的原理,而华为的Hybrid Zoom允许该技术与多台相机一起使用。 从历史上看,该技术的主要问题一直是高性能要求,算法质量和功耗。 但是在现代智能手机方面,功能更强大的信号处理处理器,节能的DSP芯片甚至神经网络的功能得到了改进,从而逐渐减少了问题。

高细节,光学变焦功能和自定义散景效果是现代智能手机对相机的首要要求,而多相机技术可以帮助实现这一目标。 移动摄影的未来不是代替单独的相机执行不同的功能,而是将多个相机组合在一起以提供更高级和更灵活的功能。

仍然存在关于光技术的问题,特别是关于胶合具有不同焦距的图像的问题。 我们只能等待-我们将看到第二代技术会发生什么变化。



在程序中带有手柄的摄像机上配置这样的组件并不容易。 也许圆圈会闭合并且要拍照,我们将不得不再次写一个明确的意图,其内容为:“相机,请尽可能自己动手拍摄照片,但精美,并在我的活动中将其还给我”。

但就目前而言,尽管我们还有一点。 因此,我们立即进行。

为什么所有这些Google需要?

似乎整个过程都是安全且适当的多线程处理(也可以直接进行各种效果和滤镜处理)。 现在,如果需要使用香草JAVA,则需要在任何地方有效地推送互斥体,同步和信号,那么Google几乎可以接管一切。 您只需要在程序文本中注册回调,必要时将调用该回调。 也就是说,例如,您提交了打开相机的请​​求:

mCameraManager.openCamera() 

但这不是团队,这是一个要求。 您无法立即开始使用相机。 首先,她需要时间才能打开,其次,android有很多重要的事情要做,而您的愿望却排在了相当大的队列中。 但是,然后我们不必等待相机在主线程中循环打开,而挂起了整个应用程序(我们仍然记得在UI流中可以做什么,而不能记住)。 因此,我们发送我们的愿望,并在开展业务时打开视图,编写“ hello world”,配置按钮处理程序等。

同时,经过几十和几百毫秒,操作系统终于到达了相机的手并对其进行了初始化。 相机准备好后,就会触发相同的回调(除非您事先注册了它)

  private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; createCameraPreviewSession(); ….. } 

也就是说,相机现在处于打开状态,并且可以在其中执行某些操作:在视图中显示相机中的图像,将其进一步转发以进行保存,等等。

结果,实际上所有与相机兼容的工作都归结为规定了各种回调。 但是,在他们的崇高愿望下,Google的开发人员稍微走了一步,正如日本同志正确指出的那样:
造成Camera2困惑的原因之一是拍摄一张照片需要使用多少个回调。

但这还不够。 相机的完整方案看起来很棒



但幸运的是,对于初学者来说,它可以简化为更具吸引力的图片。



虽然那里的一切都是英文的,但正如他们所说的那样,很明显没有单词。 首先,我们有足够的这种设计。 我们将打开相机,在智能手机屏幕上显示图像,拍照,并准备好(再次进行回调),此事件的侦听器将起作用并将图片写入文件。

开始创建代码

我们将在Android Studio IDE中创建一个新项目,选择最低版本的SDK 22,以避免出现绿色图片,并订购空白的Activity(甚至更好,选择版本23,否则权限可能会出现问题)。 足够一个开始。 即使清单中的权限也不需要完成。

我们首先创建CameraManager类的实例。 这是系统服务管理器,它使您可以找到可用的相机,获取工作所需的特性并设置相机的拍摄设置。

我们将看到以下特征:

摄影机识别码(0、1、2 ....)
相机的方向(向前,向后)
相机分辨率

首先,我们以字符串数组的形式获取摄像机列表,然后循环打印所需的特征并将其写入日志。

 package com.example.camera; import android.content.Context; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import android.graphics.ImageFormat; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.util.Size; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; String[] myCameras = null; private CameraManager mCameraManager = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new String[mCameraManager.getCameraIdList().length]; //     for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); // e   CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(cameraID); //    ,    StreamConfigurationMap configurationMap = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); //      int Faceing = cc.get(CameraCharacteristics.LENS_FACING); if (Faceing == CameraCharacteristics.LENS_FACING_FRONT) { Log.i(LOG_TAG,"Camera with ID: "+cameraID + " is FRONT CAMERA "); } if (Faceing == CameraCharacteristics.LENS_FACING_BACK) { Log.i(LOG_TAG,"Camera with: ID "+cameraID + " is BACK CAMERA "); } //        jpeg Size[] sizesJPEG = configurationMap.getOutputSizes(ImageFormat.JPEG); if (sizesJPEG != null) { for (Size item:sizesJPEG) { Log.i(LOG_TAG, "w:"+item.getWidth()+" h:"+item.getHeight()); } } else { Log.i(LOG_TAG, "camera don`t support JPEG"); } } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } } 

日志将显示以下内容:
 2019-09-13 10:56:31.489 11130-11130/? I/myLogs: cameraID: 0 2019-09-13 10:56:31.504 11130-11130/? I/myLogs: Camera with: ID 0 is BACK CAMERA 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:4000 h:3000 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:4000 h:2250 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:3840 h:2160 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2592 h:1944 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2592 h:1940 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2048 h:1536 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1920 h:1080 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1600 h:1200 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1440 h:1080 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1440 h:720 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:960 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:768 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:720 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:400 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:800 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:720 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:640 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:640 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:360 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:320 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:352 h:288 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:320 h:240 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:240 h:320 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: w:176 h:144 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: w:144 h:176 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: cameraID: 1 2019-09-13 10:56:31.514 11130-11130/? I/myLogs: Camera with ID: 1 is FRONT CAMERA 2019-09-13 10:56:31.515 11130-11130/? I/myLogs: w:4224 h:3136 2019-09-13 10:56:31.515 11130-11130/? I/myLogs: w:4224 h:2376 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4160 h:3120 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4160 h:2340 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4000 h:3000 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4000 h:2250 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:3840 h:2160 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2592 h:1944 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2592 h:1940 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2048 h:1536 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1920 h:1080 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1600 h:1200 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1440 h:1080 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1440 h:720 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:960 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:768 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:720 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:400 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:800 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:720 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:640 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:640 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:360 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:320 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:352 h:288 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:320 h:240 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:240 h:320 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:176 h:144 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:144 h:176 


从原则上讲,我们实际上并不需要此信息,即使是以前的API,任何茶壶都已经知道相机具有从零到零的标识符,对于1920 x 1080分辨率甚至JPEG格式的分辨率,您都不会感到惊讶。 实际上,准备用于生产的“成人”应用程序已经需要此数据,并且该数据将在此基础上为用户提供菜单选择等。 通常,在最简单的情况下,一切都清楚了。 但是由于所有文章都是以此开头的,所以我们将开始。

确保前置摄像头的标识号为“ 1”,后摄像头的标识号为“ 0”(由于某种原因,它们以String格式指定),并且我们可以使用1920 x 1080的分辨率并保存JPG文件后,我们将继续进行攻击。

我们获得了必要的权限

最初,我们需要担心许多权限。 为此,您必须在清单中编写以下内容:

  <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 

第一个是相机可以理解的,第二个是用于将图片写入文件的(这不是外部存储卡,从单词EXTERNAL的含义看,它似乎是智能手机的内存)

但是Android也关心我们,因此,从Lollipop版本开始,清单中指定的权限将不再足够。 现在需要用户的笔批准打开相机并将数据写入内存的同意。

为此,在最简单的情况下,您需要添加以下内容:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ……... if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } ….. 

为什么最简单? 因为没有必要在用户界面流中执行此类操作。 首先,当用户用笨拙的手指戳屏幕时,线程会挂起;其次,如果您进一步初始化了相机,则应用程序通常可能会崩溃。 在此演示案例中,一切都很好,但通常会规定在这种情况下使用所需的回调类型:

 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == MY_REQUEST_CODE_FOR_CAMERA) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCameraActivity(); //     (  ) } } } 

尽管我以前并不了解所有这些信息,但我通过AsyncTask启动了所需的Activity,甚至更早之前,我也像Java中一样,雕刻了一个新的Thread。

烹饪相机

为方便起见,我们将根据聪明人的建议在单独的类中删除与相机相关的所有内容,并创建CameraService类。 在那里,我们将初始化摄像机,然后记下所有需要的回调。

 ….. CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ….. } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,null); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } 

在主线程中,创建mCameraManager的实例,并使用它来填充myCameras对象的数组。 在这种情况下,只有两个-前置和自拍相机。

  mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } 

在public void openCamera()方法中,您可以看到以下行:

  mCameraManager.openCamera(mCameraID,mCameraCallback,null); 

正是从这里,路径导致CameraDevice相机状态的第一个回调 StateCallback。 他会告诉我们摄像头是打开,关闭还是根本没有任何东西,并且会出错。 我们将在CameraService类的方法中编写它。

  private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; 

因此,如果摄像机可供操作(公用无效的onOpened(CameraDevice camera){}方法已起作用),则我们在此处编写进一步的操作,例如,调用createCameraPreviewSession()方法。 这将有助于将摄像机的图像带到视图上并进一步处理。

CreateCameraPreviewSession

在这里,我们尝试在mImageView纹理上显示图像(数据流),该纹理已在布局中定义。 您甚至可以确定像素的分辨率。

  private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); // texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, null ); } catch (CameraAccessException e) { e.printStackTrace(); } } 

当同一会话准备就绪时,上述回调将被调用,我们首先使用googloders的表达式:“显示摄像机预览”。 在这里,那些想要的人可以调整自动对焦和闪光灯设置,但是现在我们将使用默认设置。

创建布局

可以这么说,现在我们需要在画布上绘制颜色,并以
“三个屏幕按钮和一个视图。”
 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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"> <TextureView android:id="@+id/textureView" android:layout_width="356dp" android:layout_height="410dp" android:layout_marginTop="32dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.49" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:layout_width="292dp" android:layout_height="145dp" android:layout_marginStart="16dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textureView" app:layout_constraintVertical_bias="0.537"> <Button android:id="@+id/button4" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button5" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button6" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> </LinearLayout> </androidx.constr 



这个过程非常简单,任何人都可以根据需要在这里转身。 但是直接在布局中写按钮的名称也是一种不好的举止,因此在工作版本中没有必要这样做。

因此,在活动本身中,我们创建了侦听器,即按钮和视图的侦听器。

  private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ……... mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].closeCamera(); if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].closeCamera(); if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //    } }); …….. 

按钮的名称清晰明了,我们将保留第三个按钮以用于将来的图片。

如果现在将所有代码段放在一起,

得到以下内容:
 package com.example.camera; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) {myCameras[CAMERA2].closeCamera();} if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) {myCameras[CAMERA1].closeCamera();} if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); // if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, null ); } catch (CameraAccessException e) { e.printStackTrace(); } } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,null); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } @Override public void onPause() { if(myCameras[CAMERA1].isOpen()){myCameras[CAMERA1].closeCamera();} if(myCameras[CAMERA2].isOpen()){myCameras[CAMERA2].closeCamera();} super.onPause(); } } 


我们加载,开始,工作!

通常,如果这还不足以满足您的需要,则拍摄过程本身可以大大地多样化。 我们的日语Toomoaki展示并解释了如何制作精美的图表。



首先,相机需要对焦。 通常这不会引起问题,但有时需要进行多次尝试,这些尝试也可以通过回调实现。

 CameraCaptureSession.StateCallback(). 

然后,相机进入PRECAPTURE预览模式。 在这个阶段,相机会计算曝光,白平衡和光圈(我曾经知道小时候的样子,但是现在这些知识已经消失了)。 有时回调可能会返回CONTROL_AE_STATE_FLASH_REQUIRED请求,这意味着“最好打开闪光灯。” 它可以通过setAutoFlash(mPreviewRequestBuilder)自动打开。

定义了所有必要的拍摄参数后,回调函数会向我们返回状态CONTROL_AE_STATE_CONVERGED信号,表明相机已准备好拍照。

在googloid页面上,所有这些已经在示例中了,如果您有耐心突破这些雷区和铁丝网,那么荣誉和称赞就对您了。

拍摄照片并将照片保存到文件

这正是我开始遇到问题的地方。不,根据上述框图(不是日语,而是上一个),一切似乎都不太复杂。我们正在等待相机捕获图像。在使用CamerCaptureSession类进行处理之后,它将作为Surface对象可用,而该对象又可以使用ImageReader类进行处理。

事实是,再次创建ImageReader对象需要时间。我们正在下一个名为OnImageAvailableListener的侦听器中等待该时间。最后,在最后一个ImageSaver类的实例的帮助下,我们将图像保存到文件中,当然我们也异步地进行处理,因为ImageSaver在这里是可运行的。

问题是我无法在任何地方附加此ImageReader,因为CameraCaptureSession.StateCallback()的回调已经在忙于将视频广播到智能手机的视图中。而且,如果我进行了新的会话,那么Android可能会被诅咒并导致应用程序崩溃。结果(不要问我如何),我设法通过一种createCameraPreviewSession()方法来越过马匹和颤抖的母鹿,该方法过去只在视图上显示摄像机图像。

这是之前的这段代码:

 private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() ……. 

他在这里:

  private ImageReader mImageReader; private void createCameraPreviewSession() { mImageReader = ImageReader.newInstance(1920,1080,ImageFormat.JPEG,1); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); SurfaceTexture texture = mImageView.getSurfaceTexture(); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() …… 

除了顶部的ImageReader实例定义之外,其他区别几乎是难以捉摸的。只需添加到表面,用逗号分隔mImageReader.getSurface()即可。但是直到您开始使用……

从那时起,事情变得更加有趣,您可以使用第三个屏幕按钮“拍照”。当按下它时,将调用makePhoto()方法(好吧,谁会想到的):

  mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); …… public class CameraService { public void makePhoto (){ try { // This is the CaptureRequest.Builder that we use to take a picture. final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } 

然后紧接着我们编写OnImageAvailableListener侦听器:

  private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { { Toast.makeText(MainActivity.this,"   ", Toast.LENGTH_SHORT).show();} } }; 

当他什么都不做时,他只是举杯示意他们说一切都井井有条,您可以将图片保存到文件中。

为此,我们需要文件本身:

 public class CameraService { private File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test1.jpg");; 

还有ImageSaver类的特殊对象,它可以将数据从图像快速传输到字节缓冲区,再从那里传输到二进制文件。

此类是静态和可运行的。因此,我们将其放置在MainActivity本身中:

  private static class ImageSaver implements Runnable { private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 

为了使它起作用,我们另外在OnImageAvailableListener侦听器中编写:

 mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); 

等等!哦,该死!

mBackgroundHandler还有什么?但是,没有他,它仍然可以正常工作。

但实际上,正确的问题是-没有它,它怎么能工作?因为正如Google的示例所述,BackgroundHandler提供了BackgroundThread,而BackgroundThread则是一个在后台工作并实际上负责摄像头的线程。实际上,我们应该在活动开始时进行注册:

  private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } 

不仅如此,您还需要在此处添加BackgroundThread的开始和停止位置:

  public void onPause() { super.onPause(); stopBackgroundThread(); } @Override public void onResume() { super.onResume(); startBackgroundThread(); } 

至于mBackgroundHandler,必须将其添加到我们所有需要处理程序的回调中,而我们不必担心,在其中编写了null。

最有趣的是,在打开应用程序时不要启动此后台线程,因为从程序文本中很容易看到。也就是说,它是在没有我们帮助的情况下隐式启动的。但是我们必须停止它并以onPause()和onResume()模式运行它。这里出现了某种矛盾。

但是现在图片已成功保存到文件中。这很容易通过运行应用程序进行验证。俗话说,劳动之冠是最重要的。



的确,情况以某种方式存在,但是解决这一问题是后代的任务。

程序的完整清单
 package com.example.camera; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.media.Image; import android.media.ImageReader; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) {myCameras[CAMERA2].closeCamera();} if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) {myCameras[CAMERA1].closeCamera();} if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } public class CameraService { private File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test1.jpg");; private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; private ImageReader mImageReader; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } public void makePhoto (){ try { // This is the CaptureRequest.Builder that we use to take a picture. final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); } }; private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; private void createCameraPreviewSession() { mImageReader = ImageReader.newInstance(1920,1080, ImageFormat.JPEG,1); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface,mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,mBackgroundHandler); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } @Override public void onPause() { if(myCameras[CAMERA1].isOpen()){myCameras[CAMERA1].closeCamera();} if(myCameras[CAMERA2].isOpen()){myCameras[CAMERA2].closeCamera();} stopBackgroundThread(); super.onPause(); } @Override public void onResume() { super.onResume(); startBackgroundThread(); } private static class ImageSaver implements Runnable { /** * The JPEG image */ private final Image mImage; /** * The file we save the image into. */ private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } 

Source: https://habr.com/ru/post/zh-CN468083/


All Articles