├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── linkin │ │ └── camera2examples │ │ ├── activity │ │ ├── CameraShowActivity.java │ │ └── MainActivity.java │ │ ├── base │ │ ├── AspectRatio.java │ │ ├── Constants.java │ │ ├── Size.java │ │ └── SizeMap.java │ │ ├── camera │ │ ├── Camera1Preview.java │ │ ├── Camera2Preview.java │ │ └── CameraView.java │ │ └── util │ │ ├── DeviceUtils.java │ │ └── FileUtils.java │ └── res │ ├── drawable-hdpi │ ├── camera.png │ └── switch_camera.png │ ├── drawable-xxxhdpi │ ├── camera1.jpg │ └── camera2.jpg │ ├── layout │ ├── activity_camera_show.xml │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | <<<<<<< HEAD 56 | # Freeline 57 | freeline.py 58 | freeline/ 59 | freeline_project_description.json 60 | 61 | # fastlane 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/readme.md 67 | ======= 68 | >>>>>>> first commit 69 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Camera2Examples 2 | Android Camera1 API 和Camera2 API的封装与使用 3 |
4 | 此项目是通过Camer1和Camera2分别封装到不同的View中来实现相机的适配。 5 | 项目的分析地址可跳转到此查看:https://blog.csdn.net/Android_Technology/article/details/81538259 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.2" 6 | defaultConfig { 7 | applicationId "com.linkin.camera2examples" 8 | minSdkVersion 14 9 | targetSdkVersion 22 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | } 14 | 15 | dependencies { 16 | compile fileTree(include: ['*.jar'], dir: 'libs') 17 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 18 | exclude group: 'com.android.support', module: 'support-annotations' 19 | }) 20 | compile 'com.android.support:appcompat-v7:26.0.0-alpha1' 21 | } 22 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/activity/CameraShowActivity.java: -------------------------------------------------------------------------------- 1 | package com.linkin.camera2examples.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.View; 10 | import android.view.WindowManager; 11 | import android.widget.FrameLayout; 12 | import android.widget.Toast; 13 | 14 | import com.linkin.camera2examples.R; 15 | import com.linkin.camera2examples.camera.Camera1Preview; 16 | import com.linkin.camera2examples.camera.Camera2Preview; 17 | import com.linkin.camera2examples.camera.CameraView; 18 | import com.linkin.camera2examples.util.DeviceUtils; 19 | 20 | /** 21 | * Created by Linkin on 2018/8/9. 22 | * 作者: 刘忠俊 23 | * 日期: 2018年08月09日 24 | * 描述: 首页面选择 25 | */ 26 | 27 | public class CameraShowActivity extends AppCompatActivity { 28 | 29 | 30 | public static final String OPEN_CAMERA2 = "openCamera2"; 31 | private boolean isOpenCamera2; 32 | 33 | 34 | private CameraView mCameraPreview; 35 | private FrameLayout parentView; 36 | 37 | public static void startActivity(Activity activity, boolean openCamera2) { 38 | Intent intent = new Intent(activity, CameraShowActivity.class); 39 | intent.putExtra(OPEN_CAMERA2, openCamera2); 40 | activity.startActivity(intent); 41 | } 42 | 43 | @Override 44 | protected void onCreate(@Nullable Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_camera_show); 47 | isOpenCamera2 = getIntent().getBooleanExtra(OPEN_CAMERA2, false); 48 | if (!DeviceUtils.checkCameraHardware(this)) { 49 | Toast.makeText(this, "当前设备无法不支持相机!", Toast.LENGTH_SHORT).show(); 50 | return; 51 | } 52 | 53 | parentView = (FrameLayout) findViewById(R.id.camera_preview); 54 | 55 | 56 | if (isOpenCamera2) { 57 | if (Build.VERSION.SDK_INT < 21) { 58 | Toast.makeText(this, "必须在Android 5.0及其以上才能使用 Camear2 API!", Toast.LENGTH_SHORT).show(); 59 | return; 60 | } 61 | mCameraPreview = new Camera2Preview(this); 62 | } else { 63 | mCameraPreview = new Camera1Preview(this); 64 | } 65 | 66 | parentView.addView((View) mCameraPreview); 67 | 68 | 69 | findViewById(R.id.button_capture).setOnClickListener(new View.OnClickListener() { 70 | @Override 71 | public void onClick(View v) { 72 | mCameraPreview.takePicture(new CameraView.TakePictureCallback() { 73 | @Override 74 | public void success(String picturePath) { 75 | Toast.makeText(CameraShowActivity.this, "图片保存地址:" + picturePath, Toast.LENGTH_SHORT).show(); 76 | } 77 | 78 | @Override 79 | public void error(String error) { 80 | Toast.makeText(CameraShowActivity.this, "错误信息:" + error, Toast.LENGTH_SHORT).show(); 81 | 82 | } 83 | }); 84 | } 85 | }); 86 | 87 | findViewById(R.id.button_switch).setOnClickListener(new View.OnClickListener() { 88 | @Override 89 | public void onClick(View v) { 90 | mCameraPreview.switchCameraFacing(); 91 | } 92 | }); 93 | } 94 | 95 | @Override 96 | protected void onResume() { 97 | super.onResume(); 98 | if (mCameraPreview != null) 99 | mCameraPreview.onResume(); 100 | } 101 | 102 | @Override 103 | protected void onPause() { 104 | super.onPause(); 105 | if (mCameraPreview != null) 106 | mCameraPreview.onPause(); 107 | } 108 | 109 | /** 110 | * 隐藏虚拟按键,并且全屏 111 | */ 112 | protected void hideBottomUIMenu() { 113 | //隐藏虚拟按键,并且全屏 114 | if (Build.VERSION.SDK_INT < 19) { // lower api 115 | View v = this.getWindow().getDecorView(); 116 | v.setSystemUiVisibility(View.GONE); 117 | } else if (Build.VERSION.SDK_INT >= 19) { 118 | //for new api versions. 119 | View decorView = getWindow().getDecorView(); 120 | int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE 121 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 122 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 123 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar 124 | // | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar 125 | | View.SYSTEM_UI_FLAG_IMMERSIVE; 126 | decorView.setSystemUiVisibility(uiOptions); 127 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 128 | } 129 | } 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.linkin.camera2examples.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | 7 | import com.linkin.camera2examples.R; 8 | 9 | 10 | /** 11 | * @author Linkin 12 | */ 13 | public class MainActivity extends AppCompatActivity { 14 | 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | 21 | findViewById(R.id.btn_open_camera).setOnClickListener(new View.OnClickListener() { 22 | @Override 23 | public void onClick(View v) { 24 | CameraShowActivity.startActivity(MainActivity.this, false); 25 | } 26 | }); 27 | 28 | findViewById(R.id.btn_open_camera2).setOnClickListener(new View.OnClickListener() { 29 | @Override 30 | public void onClick(View v) { 31 | CameraShowActivity.startActivity(MainActivity.this, true); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/base/AspectRatio.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkin.camera2examples.base; 18 | 19 | import android.os.Parcel; 20 | import android.os.Parcelable; 21 | import android.support.annotation.NonNull; 22 | import android.support.v4.util.SparseArrayCompat; 23 | 24 | /** 25 | * Immutable class for describing proportional relationship between width and height. 26 | */ 27 | public class AspectRatio implements Comparable, Parcelable { 28 | 29 | private final static SparseArrayCompat> sCache 30 | = new SparseArrayCompat<>(16); 31 | 32 | private final int mX; 33 | private final int mY; 34 | 35 | /** 36 | * Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values. 37 | * The values {@code x} and {@code} will be reduced by their greatest common divider. 38 | * 39 | * @param x The width 40 | * @param y The height 41 | * @return An instance of {@link AspectRatio} 42 | */ 43 | public static AspectRatio of(int x, int y) { 44 | int gcd = gcd(x, y); 45 | x /= gcd; 46 | y /= gcd; 47 | SparseArrayCompat arrayX = sCache.get(x); 48 | if (arrayX == null) { 49 | AspectRatio ratio = new AspectRatio(x, y); 50 | arrayX = new SparseArrayCompat<>(); 51 | arrayX.put(y, ratio); 52 | sCache.put(x, arrayX); 53 | return ratio; 54 | } else { 55 | AspectRatio ratio = arrayX.get(y); 56 | if (ratio == null) { 57 | ratio = new AspectRatio(x, y); 58 | arrayX.put(y, ratio); 59 | } 60 | return ratio; 61 | } 62 | } 63 | 64 | /** 65 | * Parse an {@link AspectRatio} from a {@link String} formatted like "4:3". 66 | * 67 | * @param s The string representation of the aspect ratio 68 | * @return The aspect ratio 69 | * @throws IllegalArgumentException when the format is incorrect. 70 | */ 71 | public static AspectRatio parse(String s) { 72 | int position = s.indexOf(':'); 73 | if (position == -1) { 74 | throw new IllegalArgumentException("Malformed aspect ratio: " + s); 75 | } 76 | try { 77 | int x = Integer.parseInt(s.substring(0, position)); 78 | int y = Integer.parseInt(s.substring(position + 1)); 79 | return AspectRatio.of(x, y); 80 | } catch (NumberFormatException e) { 81 | throw new IllegalArgumentException("Malformed aspect ratio: " + s, e); 82 | } 83 | } 84 | 85 | private AspectRatio(int x, int y) { 86 | mX = x; 87 | mY = y; 88 | } 89 | 90 | public int getX() { 91 | return mX; 92 | } 93 | 94 | public int getY() { 95 | return mY; 96 | } 97 | 98 | public boolean matches(Size size) { 99 | int gcd = gcd(size.getWidth(), size.getHeight()); 100 | int x = size.getWidth() / gcd; 101 | int y = size.getHeight() / gcd; 102 | return mX == x && mY == y; 103 | } 104 | 105 | @Override 106 | public boolean equals(Object o) { 107 | if (o == null) { 108 | return false; 109 | } 110 | if (this == o) { 111 | return true; 112 | } 113 | if (o instanceof AspectRatio) { 114 | AspectRatio ratio = (AspectRatio) o; 115 | return mX == ratio.mX && mY == ratio.mY; 116 | } 117 | return false; 118 | } 119 | 120 | @Override 121 | public String toString() { 122 | return mX + ":" + mY; 123 | } 124 | 125 | public float toFloat() { 126 | return (float) mX / mY; 127 | } 128 | 129 | @Override 130 | public int hashCode() { 131 | // assuming most sizes are <2^16, doing a rotate will give us perfect hashing 132 | return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2))); 133 | } 134 | 135 | @Override 136 | public int compareTo(@NonNull AspectRatio another) { 137 | if (equals(another)) { 138 | return 0; 139 | } else if (toFloat() - another.toFloat() > 0) { 140 | return 1; 141 | } 142 | return -1; 143 | } 144 | 145 | /** 146 | * @return The inverse of this {@link AspectRatio}. 147 | */ 148 | public AspectRatio inverse() { 149 | //noinspection SuspiciousNameCombination 150 | return AspectRatio.of(mY, mX); 151 | } 152 | 153 | private static int gcd(int a, int b) { 154 | while (b != 0) { 155 | int c = b; 156 | b = a % b; 157 | a = c; 158 | } 159 | return a; 160 | } 161 | 162 | @Override 163 | public int describeContents() { 164 | return 0; 165 | } 166 | 167 | @Override 168 | public void writeToParcel(Parcel dest, int flags) { 169 | dest.writeInt(mX); 170 | dest.writeInt(mY); 171 | } 172 | 173 | public static final Creator CREATOR 174 | = new Creator() { 175 | 176 | @Override 177 | public AspectRatio createFromParcel(Parcel source) { 178 | int x = source.readInt(); 179 | int y = source.readInt(); 180 | return AspectRatio.of(x, y); 181 | } 182 | 183 | @Override 184 | public AspectRatio[] newArray(int size) { 185 | return new AspectRatio[size]; 186 | } 187 | }; 188 | 189 | } 190 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/base/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkin.camera2examples.base; 18 | 19 | 20 | public interface Constants { 21 | 22 | AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3); 23 | AspectRatio FULL_ASPECT_RATIO = AspectRatio.of(16, 9); 24 | 25 | int FACING_BACK = 0; 26 | int FACING_FRONT = 1; 27 | 28 | int FLASH_OFF = 0; 29 | int FLASH_ON = 1; 30 | int FLASH_TORCH = 2; 31 | int FLASH_AUTO = 3; 32 | int FLASH_RED_EYE = 4; 33 | 34 | int LANDSCAPE_90 = 90; 35 | int LANDSCAPE_270 = 270; 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/base/Size.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkin.camera2examples.base; 18 | 19 | import android.support.annotation.NonNull; 20 | 21 | /** 22 | * Immutable class for describing width and height dimensions in pixels. 23 | */ 24 | public class Size implements Comparable { 25 | 26 | private final int mWidth; 27 | private final int mHeight; 28 | 29 | /** 30 | * Create a new immutable Size instance. 31 | * 32 | * @param width The width of the size, in pixels 33 | * @param height The height of the size, in pixels 34 | */ 35 | public Size(int width, int height) { 36 | mWidth = width; 37 | mHeight = height; 38 | } 39 | 40 | public int getWidth() { 41 | return mWidth; 42 | } 43 | 44 | public int getHeight() { 45 | return mHeight; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (o == null) { 51 | return false; 52 | } 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o instanceof Size) { 57 | Size size = (Size) o; 58 | return mWidth == size.mWidth && mHeight == size.mHeight; 59 | } 60 | return false; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return mWidth + "x" + mHeight; 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | // assuming most sizes are <2^16, doing a rotate will give us perfect hashing 71 | return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); 72 | } 73 | 74 | @Override 75 | public int compareTo(@NonNull Size another) { 76 | return mWidth * mHeight - another.mWidth * another.mHeight; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/base/SizeMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.linkin.camera2examples.base; 18 | 19 | import android.support.v4.util.ArrayMap; 20 | 21 | import java.util.Set; 22 | import java.util.SortedSet; 23 | import java.util.TreeSet; 24 | 25 | /** 26 | * A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s. 27 | */ 28 | 29 | public class SizeMap { 30 | 31 | private final ArrayMap> mRatios = new ArrayMap<>(); 32 | 33 | /** 34 | * Add a new {@link Size} to this collection. 35 | * 36 | * @param size The size to add. 37 | * @return {@code true} if it is added, {@code false} if it already exists and is not added. 38 | */ 39 | public boolean add(Size size) { 40 | for (AspectRatio ratio : mRatios.keySet()) { 41 | if (ratio.matches(size)) { 42 | final SortedSet sizes = mRatios.get(ratio); 43 | if (sizes.contains(size)) { 44 | return false; 45 | } else { 46 | sizes.add(size); 47 | return true; 48 | } 49 | } 50 | } 51 | // None of the existing ratio matches the provided size; add a new key 52 | SortedSet sizes = new TreeSet<>(); 53 | sizes.add(size); 54 | mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes); 55 | return true; 56 | } 57 | 58 | /** 59 | * Removes the specified aspect ratio and all sizes associated with it. 60 | * 61 | * @param ratio The aspect ratio to be removed. 62 | */ 63 | public void remove(AspectRatio ratio) { 64 | mRatios.remove(ratio); 65 | } 66 | 67 | public Set ratios() { 68 | return mRatios.keySet(); 69 | } 70 | 71 | public SortedSet sizes(AspectRatio ratio) { 72 | return mRatios.get(ratio); 73 | } 74 | 75 | public void clear() { 76 | mRatios.clear(); 77 | } 78 | 79 | boolean isEmpty() { 80 | return mRatios.isEmpty(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/camera/Camera1Preview.java: -------------------------------------------------------------------------------- 1 | package com.linkin.camera2examples.camera; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.Configuration; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.graphics.ImageFormat; 9 | import android.graphics.Matrix; 10 | import android.hardware.Camera; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.Surface; 14 | import android.view.SurfaceHolder; 15 | import android.view.SurfaceView; 16 | import android.view.View; 17 | 18 | import com.linkin.camera2examples.base.AspectRatio; 19 | import com.linkin.camera2examples.base.Constants; 20 | import com.linkin.camera2examples.base.Size; 21 | import com.linkin.camera2examples.base.SizeMap; 22 | import com.linkin.camera2examples.util.FileUtils; 23 | 24 | import java.io.File; 25 | import java.io.FileOutputStream; 26 | import java.io.IOException; 27 | import java.util.SortedSet; 28 | import java.util.concurrent.atomic.AtomicBoolean; 29 | 30 | public class Camera1Preview extends SurfaceView implements CameraView, SurfaceHolder.Callback { 31 | 32 | private static final String TAG = "Camera1Preview"; 33 | 34 | private Camera mCamera; 35 | private Context mContext; 36 | private int mCameraCount; 37 | private int mCurrentCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; 38 | 39 | private SurfaceHolder mSurfaceHolder; 40 | 41 | /** 42 | * 标识相机是否正在拍照过程中 43 | */ 44 | private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false); 45 | 46 | 47 | private int mRatioWidth = 0; 48 | private int mRatioHeight = 0; 49 | 50 | public Camera1Preview(Context context) { 51 | this(context, null); 52 | } 53 | 54 | public Camera1Preview(Context context, AttributeSet attrs) { 55 | this(context, attrs, 0); 56 | } 57 | 58 | public Camera1Preview(Context context, AttributeSet attrs, int defStyleAttr) { 59 | super(context, attrs, defStyleAttr); 60 | mContext = context; 61 | initCamera(); 62 | } 63 | 64 | 65 | private void initCamera() { 66 | mCamera = getCameraInstance(); 67 | if (mCamera == null) 68 | return; 69 | 70 | //得到摄像头数量 71 | mCameraCount = Camera.getNumberOfCameras(); 72 | mSurfaceHolder = getHolder(); 73 | // 设置SurfaceHolder.Callback回调,这样我们可以在创建或销毁Surface时处理相应的逻辑 74 | mSurfaceHolder.addCallback(this); 75 | //设置屏幕常亮 76 | mSurfaceHolder.setKeepScreenOn(true); 77 | //点击自动对焦 78 | setOnClickListener(new OnClickListener() { 79 | @Override 80 | public void onClick(View v) { 81 | if (mCamera != null) { 82 | mCamera.autoFocus(null); 83 | } 84 | } 85 | }); 86 | } 87 | 88 | 89 | @Override 90 | public void onResume() { 91 | if (mCamera == null) 92 | mCamera = getCameraInstance(); 93 | } 94 | 95 | 96 | @Override 97 | public void onPause() { 98 | releaseCamera(); 99 | } 100 | 101 | /** 102 | * 拍照方法(无回调,默认保存到文件中) 103 | */ 104 | @Override 105 | public void takePicture() { 106 | if (mCamera == null) { 107 | throw new IllegalStateException( 108 | "Camera is not ready. Call start() before takePicture()."); 109 | } 110 | takePictureInternal(); 111 | } 112 | 113 | /** 114 | * 拍照方法(有回调) 115 | * 116 | * @param callback 117 | */ 118 | @Override 119 | public void takePicture(TakePictureCallback callback) { 120 | if (mCamera == null) { 121 | throw new IllegalStateException( 122 | "Camera is not ready. Call start() before takePicture()."); 123 | } 124 | takePictureCallback = callback; 125 | takePictureInternal(); 126 | } 127 | 128 | private void takePictureInternal() { 129 | //如果正在拍照处理中,则不能调用takePicture方法,否则应用会崩溃 130 | if (!isPictureCaptureInProgress.get()) { 131 | isPictureCaptureInProgress.set(true); 132 | mCamera.takePicture(null, null, mPictureCallback); 133 | } 134 | } 135 | 136 | 137 | /** 138 | * 设置图片的保存路径 139 | * 140 | * @param pictureSavePath 141 | */ 142 | @Override 143 | public void setPictureSavePath(String pictureSavePath) { 144 | mPictureSaveDir = pictureSavePath; 145 | } 146 | 147 | /*** 148 | * 切换相机摄像头 149 | */ 150 | @Override 151 | public void switchCameraFacing() { 152 | if (mCameraCount > 1) { 153 | mCurrentCameraFacing = (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) ? 154 | Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK; 155 | releaseCamera(); 156 | startPreview(mSurfaceHolder); 157 | } else { 158 | //手机不支持前置摄像头 159 | } 160 | } 161 | 162 | 163 | /** 164 | * 设置此视图的宽高比。 165 | * 视图的大小将基于从参数计算的比率来测量。 166 | * 请注意,参数的实际大小并不重要,也就是说,setAspectRatio(2, 3)setAspectRatio(4, 6)会得到相同的结果。 167 | * 168 | * @param width Relative horizontal size 169 | * @param height Relative vertical size 170 | */ 171 | public void setAspectRatio(int width, int height) { 172 | if (width < 0 || height < 0) { 173 | throw new IllegalArgumentException("Size cannot be negative."); 174 | } 175 | mRatioWidth = width; 176 | mRatioHeight = height; 177 | requestLayout(); 178 | } 179 | 180 | @Override 181 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 182 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 183 | int width = MeasureSpec.getSize(widthMeasureSpec); 184 | int height = MeasureSpec.getSize(heightMeasureSpec); 185 | if (0 == mRatioWidth || 0 == mRatioHeight) { 186 | setMeasuredDimension(width, height); 187 | } else { 188 | if (width < height * mRatioWidth / mRatioHeight) { 189 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); 190 | } else { 191 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); 192 | } 193 | } 194 | } 195 | 196 | 197 | /** 198 | * 获取相机实例 199 | */ 200 | private Camera getCameraInstance() { 201 | Camera camera = null; 202 | try { 203 | // 获取相机实例, 注意:某些设备厂商可能需要用 Camera.open() 方法才能打开相机。 204 | camera = Camera.open(mCurrentCameraFacing); 205 | } catch (Exception e) { 206 | // 相机不可用或不存在 207 | Log.e(TAG, "error open(int cameraId) camera1 : " + e.getMessage()); 208 | } 209 | 210 | try { 211 | if (null == camera) 212 | camera = Camera.open(); 213 | } catch (Exception e) { 214 | Log.e(TAG, "error open camera1() : " + e.getMessage()); 215 | } 216 | 217 | return camera; 218 | } 219 | 220 | /** 221 | * 释放相机 222 | */ 223 | private void releaseCamera() { 224 | if (mCamera != null) { 225 | mCamera.stopPreview(); 226 | mCamera.release(); // release the camera1 for other applications 227 | mCamera = null; 228 | } 229 | } 230 | 231 | 232 | @Override 233 | public void surfaceCreated(SurfaceHolder holder) { 234 | Log.i(TAG, "surfaceCreated"); 235 | // Surface创建完成, 现在即可设置相机预览 236 | startPreview(holder); 237 | } 238 | 239 | @Override 240 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 241 | 242 | Log.i(TAG, "surfaceChanged format" + format + ", width =" + width + " | height=" + height); 243 | // surface在改变大小或旋转时触发此时间 244 | // 确保在调整或重新格式化之前停止视频预览 245 | if (mSurfaceHolder.getSurface() == null) { 246 | // 预览Surface不存在 247 | return; 248 | } 249 | // 改变前先停止预览 250 | try { 251 | mCamera.stopPreview(); 252 | startPreview(holder); 253 | setCameraParameters(width, height); 254 | } catch (Exception e) { 255 | Log.d(TAG, "error starting camera1 preview: " + e.getMessage()); 256 | } 257 | 258 | } 259 | 260 | @Override 261 | public void surfaceDestroyed(SurfaceHolder holder) { 262 | Log.i(TAG, "surfaceDestroyed"); 263 | //释放相机 264 | releaseCamera(); 265 | } 266 | 267 | 268 | private void startPreview(SurfaceHolder holder) { 269 | if (mCamera == null) 270 | mCamera = getCameraInstance(); 271 | try { 272 | mCamera.setPreviewDisplay(holder); 273 | //设置预览的旋转角度 274 | mCamera.setDisplayOrientation(calcDisplayOrientation(mCurrentCameraFacing)); 275 | mCamera.startPreview(); 276 | } catch (IOException e) { 277 | Log.e(TAG, "error setting camera1 preview:" + e.getMessage()); 278 | } 279 | } 280 | 281 | 282 | private int calcDisplayOrientation(int cameraId) { 283 | Camera.CameraInfo info = 284 | new Camera.CameraInfo(); 285 | Camera.getCameraInfo(cameraId, info); 286 | int rotation = ((Activity) mContext).getWindowManager().getDefaultDisplay() 287 | .getRotation(); 288 | int degrees = 0; 289 | switch (rotation) { 290 | case Surface.ROTATION_0: 291 | degrees = 0; 292 | break; 293 | case Surface.ROTATION_90: 294 | degrees = 90; 295 | break; 296 | case Surface.ROTATION_180: 297 | degrees = 180; 298 | break; 299 | case Surface.ROTATION_270: 300 | degrees = 270; 301 | break; 302 | } 303 | mDisplayOrientation = degrees; 304 | int result; 305 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 306 | result = (info.orientation + degrees) % 360; 307 | result = (360 - result) % 360; // compensate the mirror 308 | } else { // back-facing 309 | result = (info.orientation - degrees + 360) % 360; 310 | } 311 | return result; 312 | } 313 | 314 | 315 | private Camera.Parameters mCameraParameters; 316 | private final SizeMap mPreviewSizes = new SizeMap(); 317 | private final SizeMap mPictureSizes = new SizeMap(); 318 | private AspectRatio mAspectRatio; 319 | 320 | private int mDisplayOrientation; 321 | 322 | private AspectRatio chooseAspectRatio() { 323 | AspectRatio r = null; 324 | for (AspectRatio ratio : mPreviewSizes.ratios()) { 325 | r = ratio; 326 | if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) { 327 | return ratio; 328 | } 329 | } 330 | return r; 331 | } 332 | 333 | private Size chooseOptimalSize(SortedSet sizes, int surfaceWidth, int surfaceHeight) { 334 | 335 | int desiredWidth; 336 | int desiredHeight; 337 | if (isLandscape(mDisplayOrientation)) { 338 | desiredWidth = surfaceHeight; 339 | desiredHeight = surfaceWidth; 340 | } else { 341 | desiredWidth = surfaceWidth; 342 | desiredHeight = surfaceHeight; 343 | } 344 | Size result = null; 345 | for (Size size : sizes) { // Iterate from small to large 346 | if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) { 347 | return size; 348 | 349 | } 350 | result = size; 351 | } 352 | return result; 353 | } 354 | 355 | /** 356 | * Test if the supplied orientation is in landscape. 357 | * 358 | * @param orientationDegrees Orientation in degrees (0,90,180,270) 359 | * @return True if in landscape, false if portrait 360 | */ 361 | private boolean isLandscape(int orientationDegrees) { 362 | return (orientationDegrees == Constants.LANDSCAPE_90 || 363 | orientationDegrees == Constants.LANDSCAPE_270); 364 | } 365 | 366 | /*** 367 | * 设置相机参数 368 | * 369 | * @param width 370 | * @param height 371 | */ 372 | private void setCameraParameters(int width, int height) { 373 | if (mCamera == null) 374 | return; 375 | 376 | mCameraParameters = mCamera.getParameters(); 377 | 378 | 379 | // 相机预览支持的大小 380 | mPreviewSizes.clear(); 381 | for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) { 382 | mPreviewSizes.add(new Size(size.width, size.height)); 383 | } 384 | // 相机照片支持的大小 385 | mPictureSizes.clear(); 386 | for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) { 387 | mPictureSizes.add(new Size(size.width, size.height)); 388 | } 389 | // AspectRatio 默认长宽比为4:3 390 | if (mAspectRatio == null) { 391 | mAspectRatio = Constants.DEFAULT_ASPECT_RATIO; 392 | } 393 | 394 | SortedSet sizes = mPreviewSizes.sizes(mAspectRatio); 395 | if (sizes == null) { // Not supported 396 | mAspectRatio = chooseAspectRatio(); 397 | sizes = mPreviewSizes.sizes(mAspectRatio); 398 | } 399 | Size previewSize = chooseOptimalSize(sizes, width, height); 400 | 401 | // Always re-apply camera1 parameters 402 | // Largest picture size in this ratio 403 | final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last(); 404 | 405 | mCameraParameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); 406 | mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); 407 | 408 | 409 | mCameraParameters.setPictureFormat(ImageFormat.JPEG); // 设置图片格式 410 | mCameraParameters.setJpegQuality(100); // 设置照片质量 411 | mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//自动对焦 412 | //parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//连续对焦 413 | //camera1.cancelAutoFocus();//如果要实现连续的自动对焦,这一句必须加上 414 | 415 | mCamera.setParameters(mCameraParameters); 416 | 417 | 418 | //根据我们选中的预览相机大小的宽高比调整View的大小 419 | int orientation = getResources().getConfiguration().orientation; 420 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 421 | setAspectRatio( 422 | previewSize.getWidth(), previewSize.getHeight()); 423 | } else { 424 | setAspectRatio( 425 | previewSize.getHeight(), previewSize.getWidth()); 426 | } 427 | 428 | } 429 | 430 | 431 | private String mPictureSaveDir; 432 | private TakePictureCallback takePictureCallback; 433 | 434 | private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() { 435 | 436 | @Override 437 | public void onPictureTaken(final byte[] data, Camera camera) { 438 | Log.d(TAG, "onPictureTaken start timestemp :" + System.currentTimeMillis()); 439 | savePictureToSDCard(data); 440 | startPreview(mSurfaceHolder); 441 | isPictureCaptureInProgress.set(false); 442 | Log.d(TAG, "onPictureTaken end timestemp :" + System.currentTimeMillis()); 443 | } 444 | }; 445 | 446 | 447 | /** 448 | * 将拍下来的照片存放在SD卡中 449 | * 450 | * @param data 451 | */ 452 | 453 | private void savePictureToSDCard(byte[] data) { 454 | File pictureFile; 455 | //检测外部存储是否存在 456 | if (FileUtils.checkSDCard()) { 457 | if (mPictureSaveDir == null) { 458 | pictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); 459 | } else { 460 | pictureFile = FileUtils.getTimeStampMediaFile(mPictureSaveDir, FileUtils.MEDIA_TYPE_IMAGE); 461 | } 462 | if (pictureFile == null) { 463 | Log.e(TAG, "error creating media file, check storage permissions"); 464 | if (takePictureCallback != null) { 465 | takePictureCallback.error("error creating media file, check storage permissions"); 466 | } 467 | return; 468 | } 469 | } else { 470 | pictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); 471 | } 472 | 473 | try { 474 | FileOutputStream outputStream = new FileOutputStream(pictureFile); 475 | //由于在预览的时候,我们调整了预览的方向,所以在保存的时候我们要旋转回来,不然保存的图片方向是不正确的 476 | Matrix matrix = new Matrix(); 477 | if (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) { 478 | matrix.setRotate(90); 479 | } else { 480 | matrix.setRotate(-90); 481 | } 482 | Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); 483 | bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); 484 | bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream); 485 | outputStream.write(data); 486 | outputStream.close(); 487 | } catch (Exception e) { 488 | Log.e(TAG, "savePictureToSDCard error :" + e.getMessage()); 489 | if (takePictureCallback != null) { 490 | takePictureCallback.error(e.getMessage()); 491 | } 492 | return; 493 | } 494 | 495 | if (takePictureCallback != null) 496 | takePictureCallback.success(pictureFile.getAbsolutePath()); 497 | 498 | //这个的作用是让系统去扫描刚拍下的这个图片文件,以利于在MediaSore中能及时更新, 499 | // 可能会存在部分手机不用使用的情况(众所周知,现在国内的Rom厂商已把原生Rom改的面目全非) 500 | //mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + mPictureSavePath))); 501 | // MediaScannerConnection.scanFile(mContext, new String[]{ 502 | // pictureFile.getAbsolutePath()}, 503 | // null, new MediaScannerConnection.OnScanCompletedListener() { 504 | // @Override 505 | // public void onScanCompleted(String path, Uri uri) { 506 | //// Log.e(TAG, "扫描完成"); 507 | // } 508 | // }); 509 | } 510 | 511 | 512 | } 513 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/camera/Camera2Preview.java: -------------------------------------------------------------------------------- 1 | package com.linkin.camera2examples.camera; 2 | 3 | import android.Manifest; 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.content.pm.PackageManager; 7 | import android.content.res.Configuration; 8 | import android.graphics.ImageFormat; 9 | import android.graphics.Matrix; 10 | import android.graphics.Point; 11 | import android.graphics.RectF; 12 | import android.graphics.SurfaceTexture; 13 | import android.hardware.camera2.CameraAccessException; 14 | import android.hardware.camera2.CameraCaptureSession; 15 | import android.hardware.camera2.CameraCharacteristics; 16 | import android.hardware.camera2.CameraDevice; 17 | import android.hardware.camera2.CameraManager; 18 | import android.hardware.camera2.CameraMetadata; 19 | import android.hardware.camera2.CaptureRequest; 20 | import android.hardware.camera2.CaptureResult; 21 | import android.hardware.camera2.TotalCaptureResult; 22 | import android.hardware.camera2.params.StreamConfigurationMap; 23 | import android.media.Image; 24 | import android.media.ImageReader; 25 | import android.os.Build; 26 | import android.os.Handler; 27 | import android.os.HandlerThread; 28 | import android.support.annotation.NonNull; 29 | import android.support.v4.app.ActivityCompat; 30 | import android.util.AttributeSet; 31 | import android.util.Log; 32 | import android.util.Size; 33 | import android.util.SparseIntArray; 34 | import android.view.Surface; 35 | import android.view.TextureView; 36 | import android.view.WindowManager; 37 | 38 | import com.linkin.camera2examples.util.FileUtils; 39 | 40 | import java.io.File; 41 | import java.io.FileOutputStream; 42 | import java.io.IOException; 43 | import java.nio.ByteBuffer; 44 | import java.util.ArrayList; 45 | import java.util.Arrays; 46 | import java.util.Collections; 47 | import java.util.Comparator; 48 | import java.util.List; 49 | import java.util.concurrent.Semaphore; 50 | import java.util.concurrent.TimeUnit; 51 | 52 | /** 53 | * Author: Linkin 54 | * Time:2018/8/27 55 | * Email:liuzhongjun@novel-supertv.com 56 | * Blog:https://blog.csdn.net/Android_Technology 57 | * Desc: TODO 58 | */ 59 | 60 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 61 | public class Camera2Preview extends TextureView implements CameraView { 62 | 63 | 64 | private static final String TAG = "Camera2Preview"; 65 | private Context mContext; 66 | private WindowManager mWindowManager; 67 | 68 | 69 | private int mRatioWidth = 0; 70 | private int mRatioHeight = 0; 71 | 72 | private int mCameraCount; 73 | private int mCurrentCameraFacing = CameraCharacteristics.LENS_FACING_BACK; 74 | 75 | 76 | public Camera2Preview(Context context) { 77 | this(context, null); 78 | } 79 | 80 | public Camera2Preview(Context context, AttributeSet attrs) { 81 | this(context, attrs, 0); 82 | } 83 | 84 | public Camera2Preview(Context context, AttributeSet attrs, int defStyle) { 85 | super(context, attrs, defStyle); 86 | mContext = context; 87 | init(); 88 | } 89 | 90 | private void init() { 91 | mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 92 | } 93 | 94 | 95 | /** 96 | * 同生命周期 onResume 97 | */ 98 | @Override 99 | public void onResume() { 100 | startBackgroundThread(); 101 | // 当关闭并打开屏幕时,SurfaceTexture已经可用,并且不会调用“onSurfaceTextureAvailable”。 102 | // 在这种情况下,我们可以打开相机并从这里开始预览(否则,我们将等待,直到Surface在SurfaceTextureListener中准备好)。 103 | if (isAvailable()) { 104 | openCamera(getWidth(), getHeight()); 105 | } else { 106 | setSurfaceTextureListener(mSurfaceTextureListener); 107 | } 108 | } 109 | 110 | /** 111 | * 同生命周期 onPause 112 | */ 113 | @Override 114 | public void onPause() { 115 | closeCamera(); 116 | stopBackgroundThread(); 117 | } 118 | 119 | 120 | /** 121 | * 启动拍照的方法 122 | */ 123 | 124 | @Override 125 | public void takePicture() { 126 | takePictureInternal(); 127 | } 128 | 129 | private TakePictureCallback mTakePictureCallback; 130 | 131 | @Override 132 | public void takePicture(TakePictureCallback takePictureCallback) { 133 | mTakePictureCallback = takePictureCallback; 134 | takePictureInternal(); 135 | } 136 | 137 | private void takePictureInternal() { 138 | if (mCurrentCameraFacing == CameraCharacteristics.LENS_FACING_BACK) { 139 | lockFocus(); 140 | } else { 141 | runPrecaptureSequence(); 142 | } 143 | } 144 | 145 | 146 | /** 147 | * 设置图片的保存路径(文件夹) 148 | * 149 | * @param pictureSavePath 150 | */ 151 | private String mPictureSaveDir; 152 | 153 | @Override 154 | public void setPictureSavePath(String pictureSavePath) { 155 | mPictureSaveDir = pictureSavePath; 156 | } 157 | 158 | @Override 159 | public void switchCameraFacing() { 160 | if (mCameraCount > 1) { 161 | mCurrentCameraFacing = mCurrentCameraFacing == CameraCharacteristics.LENS_FACING_BACK ? 162 | CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK; 163 | closeCamera(); 164 | openCamera(getWidth(), getHeight()); 165 | } 166 | } 167 | 168 | 169 | /** 170 | * 设置此视图的宽高比。 171 | * 视图的大小将基于从参数计算的比率来测量。 172 | * 请注意,参数的实际大小并不重要,也就是说,setAspectRatio(2, 3)setAspectRatio(4, 6)会得到相同的结果。 173 | * 174 | * @param width Relative horizontal size 175 | * @param height Relative vertical size 176 | */ 177 | public void setAspectRatio(int width, int height) { 178 | if (width < 0 || height < 0) { 179 | throw new IllegalArgumentException("Size cannot be negative."); 180 | } 181 | mRatioWidth = width; 182 | mRatioHeight = height; 183 | requestLayout(); 184 | } 185 | 186 | @Override 187 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 188 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 189 | int width = MeasureSpec.getSize(widthMeasureSpec); 190 | int height = MeasureSpec.getSize(heightMeasureSpec); 191 | if (0 == mRatioWidth || 0 == mRatioHeight) { 192 | setMeasuredDimension(width, height); 193 | } else { 194 | if (width < height * mRatioWidth / mRatioHeight) { 195 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); 196 | } else { 197 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); 198 | } 199 | } 200 | } 201 | 202 | 203 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); 204 | 205 | static { 206 | ORIENTATIONS.append(Surface.ROTATION_0, 90); 207 | ORIENTATIONS.append(Surface.ROTATION_90, 0); 208 | ORIENTATIONS.append(Surface.ROTATION_180, 270); 209 | ORIENTATIONS.append(Surface.ROTATION_270, 180); 210 | } 211 | 212 | 213 | /** 214 | * 相机状态: 显示相机预览 215 | */ 216 | private static final int STATE_PREVIEW = 0; 217 | /** 218 | * 相机状态: 等待焦点被锁定 219 | */ 220 | private static final int STATE_WAITING_LOCK = 1; 221 | /** 222 | * 相机状态: 等待曝光前的状态。 223 | */ 224 | private static final int STATE_WAITING_PRECAPTURE = 2; 225 | /** 226 | * 相机状态: 等待曝光状态非预先捕获的东西. 227 | */ 228 | private static final int STATE_WAITING_NON_PRECAPTURE = 3; 229 | /** 230 | * 相机状态: 照片拍摄 231 | */ 232 | private static final int STATE_PICTURE_TAKEN = 4; 233 | 234 | /** 235 | * 最大的预览宽度 236 | */ 237 | private static final int MAX_PREVIEW_WIDTH = 1920; 238 | 239 | /** 240 | * 最大的预览高度 241 | */ 242 | private static final int MAX_PREVIEW_HEIGHT = 1080; 243 | 244 | /** 245 | * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a 246 | * {@link TextureView}. 247 | */ 248 | private final TextureView.SurfaceTextureListener mSurfaceTextureListener 249 | = new TextureView.SurfaceTextureListener() { 250 | 251 | @Override 252 | public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 253 | openCamera(width, height); 254 | } 255 | 256 | @Override 257 | public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 258 | configureTransform(width, height); 259 | } 260 | 261 | @Override 262 | public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 263 | return true; 264 | } 265 | 266 | @Override 267 | public void onSurfaceTextureUpdated(SurfaceTexture texture) { 268 | } 269 | 270 | }; 271 | 272 | /** 273 | * ID of the current {@link CameraDevice}. 274 | */ 275 | private String mCameraId; 276 | 277 | /** 278 | * 相机预览要用到的{@link CameraCaptureSession } . 279 | */ 280 | private CameraCaptureSession mCaptureSession; 281 | 282 | /** 283 | * A reference to the opened {@link CameraDevice}. 284 | */ 285 | private CameraDevice mCameraDevice; 286 | 287 | /** 288 | * 相机预览的 {@link android.util.Size} 289 | */ 290 | private Size mPreviewSize; 291 | 292 | /** 293 | * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state. 294 | */ 295 | private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { 296 | 297 | @Override 298 | public void onOpened(@NonNull CameraDevice cameraDevice) { 299 | // 当相机打开时调用此方法。我们在这里开始相机预览。 300 | mCameraOpenCloseLock.release(); 301 | mCameraDevice = cameraDevice; 302 | createCameraPreviewSession(); 303 | } 304 | 305 | @Override 306 | public void onDisconnected(@NonNull CameraDevice cameraDevice) { 307 | mCameraOpenCloseLock.release(); 308 | cameraDevice.close(); 309 | mCameraDevice = null; 310 | } 311 | 312 | @Override 313 | public void onError(@NonNull CameraDevice cameraDevice, int error) { 314 | mCameraOpenCloseLock.release(); 315 | cameraDevice.close(); 316 | mCameraDevice = null; 317 | Log.e(TAG, "CameraDevice.StateCallback onError errorCode= " + error); 318 | } 319 | 320 | }; 321 | 322 | /** 323 | * 用于运行不应该阻止UI的任务的附加线程。 324 | */ 325 | private HandlerThread mBackgroundThread; 326 | 327 | /** 328 | * 在后台运行任务的Handler。 329 | */ 330 | private Handler mBackgroundHandler; 331 | 332 | /** 333 | * An {@link ImageReader} 用于处理图像捕获(抓拍). 334 | */ 335 | private ImageReader mImageReader; 336 | 337 | 338 | /** 339 | * 图片文件 340 | */ 341 | private File mPictureFile; 342 | /** 343 | * 这是{@link ImageReader}的回调对象. 当静态图像准备好保存时,将回调"onImageAvailable" 344 | */ 345 | private final ImageReader.OnImageAvailableListener mOnImageAvailableListener 346 | = new ImageReader.OnImageAvailableListener() { 347 | 348 | @Override 349 | public void onImageAvailable(ImageReader reader) { 350 | //检测外部存储是否存在 351 | //根据时间戳生成图片名称 352 | if (FileUtils.checkSDCard()) { 353 | if (mPictureSaveDir == null) { 354 | mPictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); 355 | } else { 356 | mPictureFile = FileUtils.getTimeStampMediaFile(mPictureSaveDir, FileUtils.MEDIA_TYPE_IMAGE); 357 | } 358 | if (mPictureFile == null) { 359 | Log.e(TAG, "error creating media file, check storage permissions"); 360 | if (mTakePictureCallback != null) { 361 | mTakePictureCallback.error("error creating media file, check storage permissions"); 362 | } 363 | return; 364 | } 365 | } else { 366 | mPictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); 367 | } 368 | mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mPictureFile)); 369 | } 370 | 371 | }; 372 | 373 | /** 374 | * {@link CaptureRequest.Builder} for the camera1 preview 375 | */ 376 | private CaptureRequest.Builder mPreviewRequestBuilder; 377 | 378 | /** 379 | * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} 380 | */ 381 | private CaptureRequest mPreviewRequest; 382 | 383 | /** 384 | * 拍照相机的当前状态 385 | * 386 | * @see #mCaptureCallback 387 | */ 388 | private int mState = STATE_PREVIEW; 389 | 390 | /** 391 | * A {@link Semaphore} 防止相机关闭前退出应用程序。 392 | */ 393 | private Semaphore mCameraOpenCloseLock = new Semaphore(1); 394 | 395 | /** 396 | * 当前的相机设备是否支持Flash。 397 | */ 398 | private boolean mFlashSupported; 399 | 400 | /** 401 | * 相机传感器的方向 402 | */ 403 | private int mSensorOrientation; 404 | 405 | /** 406 | * A {@link CameraCaptureSession.CaptureCallback} 处理JPEG捕获的相关事件 407 | */ 408 | private CameraCaptureSession.CaptureCallback mCaptureCallback 409 | = new CameraCaptureSession.CaptureCallback() { 410 | 411 | private void process(CaptureResult result) { 412 | switch (mState) { 413 | case STATE_PREVIEW: { 414 | // We have nothing to do when the camera1 preview is working normally. 415 | break; 416 | } 417 | case STATE_WAITING_LOCK: { 418 | Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); 419 | if (afState == null) { 420 | captureStillPicture(); 421 | } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || 422 | CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { 423 | // CONTROL_AE_STATE can be null on some devices 424 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 425 | if (aeState == null || 426 | aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 427 | mState = STATE_PICTURE_TAKEN; 428 | captureStillPicture(); 429 | } else { 430 | runPrecaptureSequence(); 431 | } 432 | } 433 | break; 434 | } 435 | case STATE_WAITING_PRECAPTURE: { 436 | // CONTROL_AE_STATE can be null on some devices 437 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 438 | if (aeState == null || 439 | aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || 440 | aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { 441 | mState = STATE_WAITING_NON_PRECAPTURE; 442 | } 443 | break; 444 | } 445 | case STATE_WAITING_NON_PRECAPTURE: { 446 | // CONTROL_AE_STATE can be null on some devices 447 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 448 | if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { 449 | mState = STATE_PICTURE_TAKEN; 450 | captureStillPicture(); 451 | } 452 | break; 453 | } 454 | } 455 | } 456 | 457 | @Override 458 | public void onCaptureProgressed(@NonNull CameraCaptureSession session, 459 | @NonNull CaptureRequest request, 460 | @NonNull CaptureResult partialResult) { 461 | process(partialResult); 462 | } 463 | 464 | @Override 465 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 466 | @NonNull CaptureRequest request, 467 | @NonNull TotalCaptureResult result) { 468 | process(result); 469 | } 470 | 471 | }; 472 | 473 | 474 | /** 475 | * 给定相机支持的{@code Size}的{@code choices}, 476 | * 选择至少与相应TextureView大小一样大、最多与相应最大大小一样大、且纵横比与指定值匹配的最小一个。 477 | * 如果不存在这样的尺寸,则选择最大尺寸与相应的最大尺寸一样大,并且其纵横比与指定值匹配的最大尺寸。 478 | * 479 | * @param choices 摄像机支持预期输出类的大小列表。 480 | * @param textureViewWidth TextureView相对于传感器坐标的宽度 481 | * @param textureViewHeight TextureView相对于传感器坐标的高度 482 | * @param maxWidth 可选择的最大宽度 483 | * @param maxHeight 可选择的最大高度 484 | * @param aspectRatio 宽高比 485 | * @return 最佳的 {@code Size}, 或任意一个,如果没有足够大的话 486 | */ 487 | private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, 488 | int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { 489 | 490 | // 收集至少与预览表面一样大的支持分辨率。 491 | List bigEnough = new ArrayList<>(); 492 | // 收集小于预览表面的支持的分辨率 493 | List notBigEnough = new ArrayList<>(); 494 | int w = aspectRatio.getWidth(); 495 | int h = aspectRatio.getHeight(); 496 | for (Size option : choices) { 497 | if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && 498 | option.getHeight() == option.getWidth() * h / w) { 499 | if (option.getWidth() >= textureViewWidth && 500 | option.getHeight() >= textureViewHeight) { 501 | bigEnough.add(option); 502 | } else { 503 | notBigEnough.add(option); 504 | } 505 | } 506 | } 507 | 508 | // 挑那些最小中的足够大的。如果没有足够大的,选择最大的那些不够大的。 509 | if (bigEnough.size() > 0) { 510 | return Collections.min(bigEnough, new CompareSizesByArea()); 511 | } else if (notBigEnough.size() > 0) { 512 | return Collections.max(notBigEnough, new CompareSizesByArea()); 513 | } else { 514 | Log.e(TAG, "Couldn't find any suitable preview size"); 515 | return choices[0]; 516 | } 517 | } 518 | 519 | 520 | /** 521 | * 设置与摄像机相关的成员变量。 522 | * 523 | * @param width 相机预览可用尺寸的宽度 524 | * @param height 相机预览可用尺寸的高度 525 | */ 526 | @SuppressWarnings("SuspiciousNameCombination") 527 | private void setUpCameraOutputs(int width, int height) { 528 | CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 529 | try { 530 | String[] cameraIdList = manager.getCameraIdList(); 531 | mCameraCount = cameraIdList.length; 532 | for (String cameraId : cameraIdList) { 533 | CameraCharacteristics characteristics 534 | = manager.getCameraCharacteristics(cameraId); 535 | 536 | //判断当前摄像头是前置还是后置摄像头 537 | Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); 538 | if (facing != null && facing != mCurrentCameraFacing) { 539 | continue; 540 | } 541 | 542 | StreamConfigurationMap map = characteristics.get( 543 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 544 | if (map == null) { 545 | continue; 546 | } 547 | 548 | // 对于静态图像捕获,我们使用最大可用的大小。 549 | Size largest = Collections.max( 550 | Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 551 | new CompareSizesByArea()); 552 | mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), 553 | ImageFormat.JPEG, /*maxImages*/2); 554 | mImageReader.setOnImageAvailableListener( 555 | mOnImageAvailableListener, mBackgroundHandler); 556 | 557 | // 找出是否需要交换尺寸以获得相对与传感器坐标的预览大小 558 | int displayRotation = mWindowManager.getDefaultDisplay().getRotation(); 559 | //检验条件 560 | mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 561 | boolean swappedDimensions = false; 562 | switch (displayRotation) { 563 | case Surface.ROTATION_0: 564 | case Surface.ROTATION_180: 565 | if (mSensorOrientation == 90 || mSensorOrientation == 270) { 566 | swappedDimensions = true; 567 | } 568 | break; 569 | case Surface.ROTATION_90: 570 | case Surface.ROTATION_270: 571 | if (mSensorOrientation == 0 || mSensorOrientation == 180) { 572 | swappedDimensions = true; 573 | } 574 | break; 575 | default: 576 | Log.e(TAG, "Display rotation is invalid: " + displayRotation); 577 | } 578 | 579 | Point displaySize = new Point(); 580 | mWindowManager.getDefaultDisplay().getSize(displaySize); 581 | int rotatedPreviewWidth = width; 582 | int rotatedPreviewHeight = height; 583 | int maxPreviewWidth = displaySize.x; 584 | int maxPreviewHeight = displaySize.y; 585 | 586 | if (swappedDimensions) { 587 | rotatedPreviewWidth = height; 588 | rotatedPreviewHeight = width; 589 | maxPreviewWidth = displaySize.y; 590 | maxPreviewHeight = displaySize.x; 591 | } 592 | 593 | if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { 594 | maxPreviewWidth = MAX_PREVIEW_WIDTH; 595 | } 596 | 597 | if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { 598 | maxPreviewHeight = MAX_PREVIEW_HEIGHT; 599 | } 600 | 601 | // 危险,W.R.!尝试使用太大的预览大小可能超过相机总线的带宽限制,导致高清的预览,但存储垃圾捕获数据。 602 | mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 603 | rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, 604 | maxPreviewHeight, largest); 605 | 606 | // 我们将TextureView的宽高比与我们选择的预览大小相匹配。 607 | int orientation = getResources().getConfiguration().orientation; 608 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 609 | setAspectRatio( 610 | mPreviewSize.getWidth(), mPreviewSize.getHeight()); 611 | } else { 612 | setAspectRatio( 613 | mPreviewSize.getHeight(), mPreviewSize.getWidth()); 614 | } 615 | 616 | //检验是否支持flash 617 | Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 618 | mFlashSupported = available == null ? false : available; 619 | 620 | mCameraId = cameraId; 621 | return; 622 | } 623 | } catch (CameraAccessException e) { 624 | e.printStackTrace(); 625 | } catch (NullPointerException e) { 626 | //抛出空指针一般代表当前设备不支持Camera2API 627 | Log.e(TAG, "This device doesn't support Camera2 API."); 628 | 629 | } 630 | } 631 | 632 | /** 633 | * 打开指定的相机(mCameraId) 634 | */ 635 | private void openCamera(int width, int height) { 636 | 637 | setUpCameraOutputs(width, height); 638 | configureTransform(width, height); 639 | 640 | CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 641 | try { 642 | if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 643 | throw new RuntimeException("Time out waiting to lock camera1 opening."); 644 | } 645 | if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 646 | return; 647 | } 648 | manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); 649 | } catch (CameraAccessException e) { 650 | e.printStackTrace(); 651 | } catch (InterruptedException e) { 652 | throw new RuntimeException("Interrupted while trying to lock camera1 opening.", e); 653 | } 654 | } 655 | 656 | /** 657 | * 关闭相机 658 | */ 659 | private void closeCamera() { 660 | try { 661 | mCameraOpenCloseLock.acquire(); 662 | if (null != mCaptureSession) { 663 | mCaptureSession.close(); 664 | mCaptureSession = null; 665 | } 666 | if (null != mCameraDevice) { 667 | mCameraDevice.close(); 668 | mCameraDevice = null; 669 | } 670 | if (null != mImageReader) { 671 | mImageReader.close(); 672 | mImageReader = null; 673 | } 674 | } catch (InterruptedException e) { 675 | throw new RuntimeException("Interrupted while trying to lock camera1 closing.", e); 676 | } finally { 677 | mCameraOpenCloseLock.release(); 678 | } 679 | } 680 | 681 | /** 682 | * 启动后台线程和Handler. 683 | */ 684 | private void startBackgroundThread() { 685 | mBackgroundThread = new HandlerThread("CameraBackground"); 686 | mBackgroundThread.start(); 687 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 688 | } 689 | 690 | /** 691 | * 停止后台线程和Handler. 692 | */ 693 | private void stopBackgroundThread() { 694 | mBackgroundThread.quitSafely(); 695 | try { 696 | mBackgroundThread.join(); 697 | mBackgroundThread = null; 698 | mBackgroundHandler = null; 699 | } catch (InterruptedException e) { 700 | e.printStackTrace(); 701 | } 702 | } 703 | 704 | /** 705 | * 创建一个新的 {@link CameraCaptureSession} 用于相机预览. 706 | */ 707 | private void createCameraPreviewSession() { 708 | try { 709 | SurfaceTexture texture = getSurfaceTexture(); 710 | assert texture != null; 711 | 712 | // 我们将默认缓冲区的大小设置为我们想要的相机预览的大小。 713 | texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 714 | 715 | // 我们需要开始预览输出Surface 716 | Surface surface = new Surface(texture); 717 | 718 | // 我们建立了一个具有输出Surface的捕获器。 719 | mPreviewRequestBuilder 720 | = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 721 | mPreviewRequestBuilder.addTarget(surface); 722 | 723 | // 这里,我们创建了一个用于相机预览的CameraCaptureSession 724 | mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), 725 | new CameraCaptureSession.StateCallback() { 726 | 727 | @Override 728 | public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { 729 | // 相机已经关闭 730 | if (null == mCameraDevice) { 731 | return; 732 | } 733 | 734 | // 当session准备好后,我们开始显示预览 735 | mCaptureSession = cameraCaptureSession; 736 | try { 737 | // 相机预览时应连续自动对焦 738 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, 739 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 740 | // 设置闪光灯在必要时自动打开 741 | setAutoFlash(mPreviewRequestBuilder); 742 | 743 | // 最终,显示相机预览 744 | mPreviewRequest = mPreviewRequestBuilder.build(); 745 | mCaptureSession.setRepeatingRequest(mPreviewRequest, 746 | mCaptureCallback, mBackgroundHandler); 747 | } catch (CameraAccessException e) { 748 | e.printStackTrace(); 749 | } 750 | } 751 | 752 | @Override 753 | public void onConfigureFailed( 754 | @NonNull CameraCaptureSession cameraCaptureSession) { 755 | Log.e(TAG, "CameraCaptureSession.StateCallback onConfigureFailed"); 756 | } 757 | }, null 758 | ); 759 | } catch (CameraAccessException e) { 760 | e.printStackTrace(); 761 | } 762 | } 763 | 764 | /** 765 | * 配置必要的 {@link android.graphics.Matrix} 转换为 `mTextureView`. 766 | *

767 | * 该方法应该在setUpCameraOutputs中确定相机预览大小以及“mTextureView”的大小固定之后调用。 768 | * 769 | * @param viewWidth The width of `mTextureView` 770 | * @param viewHeight The height of `mTextureView` 771 | */ 772 | private void configureTransform(int viewWidth, int viewHeight) { 773 | if (null == mPreviewSize || null == mContext) { 774 | return; 775 | } 776 | 777 | int rotation = mWindowManager.getDefaultDisplay().getRotation(); 778 | Matrix matrix = new Matrix(); 779 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 780 | RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); 781 | float centerX = viewRect.centerX(); 782 | float centerY = viewRect.centerY(); 783 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 784 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 785 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 786 | float scale = Math.max( 787 | (float) viewHeight / mPreviewSize.getHeight(), 788 | (float) viewWidth / mPreviewSize.getWidth()); 789 | matrix.postScale(scale, scale, centerX, centerY); 790 | matrix.postRotate(90 * (rotation - 2), centerX, centerY); 791 | } else if (Surface.ROTATION_180 == rotation) { 792 | matrix.postRotate(180, centerX, centerY); 793 | } 794 | setTransform(matrix); 795 | } 796 | 797 | 798 | /** 799 | * 锁定焦点作为静态图像捕获的第一步 800 | */ 801 | private void lockFocus() { 802 | try { 803 | // 这里是让相机锁定焦点 804 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 805 | CameraMetadata.CONTROL_AF_TRIGGER_START); 806 | // 告知 #mCaptureCallback 等待锁 807 | mState = STATE_WAITING_LOCK; 808 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 809 | mBackgroundHandler); 810 | } catch (CameraAccessException e) { 811 | e.printStackTrace(); 812 | } 813 | } 814 | 815 | /** 816 | * 运行预捕获序列捕获一张静态图片。 817 | *

818 | * 这个方法应该在我们从得到mCaptureCallback的响应后调用 819 | */ 820 | private void runPrecaptureSequence() { 821 | try { 822 | // 这就是如何告诉相机触发。 823 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 824 | CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); 825 | // 告知 #mCaptureCallback 等待设置预捕获序列。 826 | mState = STATE_WAITING_PRECAPTURE; 827 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 828 | mBackgroundHandler); 829 | } catch (CameraAccessException e) { 830 | e.printStackTrace(); 831 | } 832 | } 833 | 834 | /** 835 | * 捕获一张静态图片 836 | * 这个方法应该在我们从得到mCaptureCallback的响应后调用 837 | */ 838 | private void captureStillPicture() { 839 | try { 840 | if (null == mCameraDevice) { 841 | return; 842 | } 843 | // 这是 CaptureRequest.Builder ,我们用它来进行拍照 844 | final CaptureRequest.Builder captureBuilder = 845 | mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 846 | captureBuilder.addTarget(mImageReader.getSurface()); 847 | 848 | // 使用相同的AE和AF模式作为预览。 849 | captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, 850 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 851 | setAutoFlash(captureBuilder); 852 | 853 | // 方向 854 | int rotation = mWindowManager.getDefaultDisplay().getRotation(); 855 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); 856 | 857 | CameraCaptureSession.CaptureCallback CaptureCallback 858 | = new CameraCaptureSession.CaptureCallback() { 859 | 860 | @Override 861 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 862 | @NonNull CaptureRequest request, 863 | @NonNull TotalCaptureResult result) { 864 | Log.d(TAG, "CameraCaptureSession.CaptureCallback onCaptureCompleted 图片保存地址为:" + mPictureFile.toString()); 865 | if (mTakePictureCallback != null) { 866 | mTakePictureCallback.success(mPictureFile.getAbsolutePath()); 867 | } 868 | unlockFocus(); 869 | } 870 | }; 871 | 872 | mCaptureSession.stopRepeating(); 873 | mCaptureSession.abortCaptures(); 874 | mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); 875 | } catch (CameraAccessException e) { 876 | e.printStackTrace(); 877 | } 878 | } 879 | 880 | /** 881 | * 从指定的屏幕旋转中检索JPEG方向。 882 | * 883 | * @param rotation 图片旋转 884 | * @return The JPEG orientation (one of 0, 90, 270, and 360) 885 | */ 886 | private int getOrientation(int rotation) { 887 | // 对于大多数设备,传感器定向是90,对于某些设备(例如Nexus 5X)是270。 888 | //我们必须考虑到这一点,并适当的旋转JPEG。 889 | //对于取向为90的设备,我们只需从方向返回映射即可。 890 | //对于方向为270的设备,我们需要旋转JPEG 180度。 891 | return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; 892 | } 893 | 894 | /** 895 | * 解锁焦点. 896 | *

897 | * 此方法应该在静态图片捕获序列结束后调用 898 | */ 899 | private void unlockFocus() { 900 | try { 901 | // 重置自动对焦触发 902 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 903 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 904 | setAutoFlash(mPreviewRequestBuilder); 905 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 906 | mBackgroundHandler); 907 | // 在此之后,相机将回到正常的预览状态。 908 | mState = STATE_PREVIEW; 909 | mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, 910 | mBackgroundHandler); 911 | } catch (CameraAccessException e) { 912 | e.printStackTrace(); 913 | } 914 | } 915 | 916 | 917 | private void setAutoFlash(CaptureRequest.Builder requestBuilder) { 918 | if (mFlashSupported) { 919 | requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, 920 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 921 | } 922 | } 923 | 924 | /** 925 | * 将JPEG{@link Image}保存并放到指定的文件中 926 | */ 927 | private static class ImageSaver implements Runnable { 928 | 929 | /** 930 | * The JPEG image 931 | */ 932 | private final Image mImage; 933 | /** 934 | * The file we save the image into. 935 | */ 936 | private final File mFile; 937 | 938 | ImageSaver(Image image, File file) { 939 | mImage = image; 940 | mFile = file; 941 | } 942 | 943 | @Override 944 | public void run() { 945 | ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); 946 | byte[] bytes = new byte[buffer.remaining()]; 947 | buffer.get(bytes); 948 | FileOutputStream output = null; 949 | try { 950 | output = new FileOutputStream(mFile); 951 | output.write(bytes); 952 | } catch (IOException e) { 953 | e.printStackTrace(); 954 | } finally { 955 | mImage.close(); 956 | if (null != output) { 957 | try { 958 | output.close(); 959 | } catch (IOException e) { 960 | e.printStackTrace(); 961 | } 962 | } 963 | } 964 | } 965 | 966 | } 967 | 968 | /** 969 | * 根据它们的区域比较两个的大小 {@code Size}。 970 | */ 971 | static class CompareSizesByArea implements Comparator { 972 | 973 | @Override 974 | public int compare(Size lhs, Size rhs) { 975 | // We cast here to ensure the multiplications won't overflow 976 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() - 977 | (long) rhs.getWidth() * rhs.getHeight()); 978 | } 979 | 980 | } 981 | 982 | 983 | } 984 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/camera/CameraView.java: -------------------------------------------------------------------------------- 1 | package com.linkin.camera2examples.camera; 2 | 3 | /** 4 | * Author: Linkin 5 | * Time:2018/8/29 6 | * Email:liuzhongjun@novel-supertv.com 7 | * Blog:https://blog.csdn.net/Android_Technology 8 | * Desc: TODO 9 | */ 10 | 11 | public interface CameraView { 12 | 13 | /** 14 | * 与生命周期onResume调用 15 | */ 16 | void onResume(); 17 | 18 | /** 19 | * 与生命周期onPause调用 20 | */ 21 | void onPause(); 22 | 23 | /** 24 | * 拍照 25 | */ 26 | void takePicture(); 27 | 28 | /** 29 | * 拍照(有回调) 30 | */ 31 | void takePicture(TakePictureCallback takePictureCallback); 32 | 33 | /** 34 | * 设置保存的图片文件 35 | * 36 | * @param pictureSavePath 拍摄的图片返回的绝对路径 37 | */ 38 | void setPictureSavePath(String pictureSavePath); 39 | 40 | /** 41 | * 切换相机摄像头 42 | */ 43 | void switchCameraFacing(); 44 | 45 | 46 | interface TakePictureCallback { 47 | 48 | void success(String picturePath); 49 | 50 | void error(final String error); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/util/DeviceUtils.java: -------------------------------------------------------------------------------- 1 | package com.linkin.camera2examples.util; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | 6 | /** 7 | * Author: Linkin 8 | * Time:2018/8/27 9 | * Email:liuzhongjun@novel-supertv.com 10 | * Blog:https://blog.csdn.net/Android_Technology 11 | * Desc: 设备相关工具类 12 | */ 13 | 14 | public final class DeviceUtils { 15 | 16 | /** 17 | * 检验设备是否有摄像头 18 | */ 19 | public static boolean checkCameraHardware(Context context) { 20 | if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { 21 | // this device has a camera1 22 | return true; 23 | } else { 24 | // no camera1 on this device 25 | return false; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/linkin/camera2examples/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.linkin.camera2examples.util; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | import android.util.Log; 6 | 7 | import java.io.File; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Date; 10 | 11 | /** 12 | * Author: Linkin 13 | * Time:2018/8/27 14 | * Email:liuzhongjun@novel-supertv.com 15 | * Blog:https://blog.csdn.net/Android_Technology 16 | * Desc: TODO 17 | */ 18 | 19 | public class FileUtils { 20 | 21 | private static final String TAG = "FileUtils"; 22 | 23 | /** 24 | * 检测外部存储是否存在 25 | */ 26 | public static boolean checkSDCard() { 27 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 28 | } 29 | 30 | 31 | public static final int MEDIA_TYPE_IMAGE = 1; 32 | public static final int MEDIA_TYPE_VIDEO = 2; 33 | 34 | /** 35 | * 创建一个文件来保存图片或者视频 36 | */ 37 | public static File getOutputMediaFile(Context mContext, int type) { 38 | 39 | File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( 40 | Environment.DIRECTORY_PICTURES), "Camera2Examples"); 41 | 42 | // This location works best if you want the created images to be shared 43 | // between applications and persist after your app has been uninstalled. 44 | 45 | // Create the storage directory if it does not exist 46 | if (!mediaStorageDir.exists()) { 47 | if (!mediaStorageDir.mkdirs()) { 48 | Log.d(TAG, "failed to create directory"); 49 | return null; 50 | } 51 | } 52 | 53 | // Create a media file name 54 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 55 | File mediaFile; 56 | if (type == MEDIA_TYPE_IMAGE) { 57 | mediaFile = new File(mediaStorageDir.getPath() + File.separator + 58 | "IMG_" + timeStamp + ".jpg"); 59 | } else if (type == MEDIA_TYPE_VIDEO) { 60 | mediaFile = new File(mediaStorageDir.getPath() + File.separator + 61 | "VID_" + timeStamp + ".mp4"); 62 | } else { 63 | return null; 64 | } 65 | return mediaFile; 66 | } 67 | 68 | 69 | public static File getTimeStampMediaFile(String parentPath, int type) { 70 | // Create a media file name 71 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 72 | File mediaFile; 73 | if (type == MEDIA_TYPE_IMAGE) { 74 | mediaFile = new File(parentPath + File.separator + 75 | "IMG_" + timeStamp + ".jpg"); 76 | } else if (type == MEDIA_TYPE_VIDEO) { 77 | mediaFile = new File(parentPath + File.separator + 78 | "VID_" + timeStamp + ".mp4"); 79 | } else { 80 | return null; 81 | } 82 | return mediaFile; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/drawable-hdpi/camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/drawable-hdpi/switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/camera1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/drawable-xxxhdpi/camera1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/camera2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/drawable-xxxhdpi/camera2.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_camera_show.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 26 | 27 | 38 | 39 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #47ac94 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Camera2Examples 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gentleman-jun/Camera2Examples/0c6875c6fc37b271f43b14010db30ddccd97f01b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Aug 09 15:26:40 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------