├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── lkl │ │ └── opengl │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── lkl │ │ │ └── opengl │ │ │ ├── BaseActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MyGLProgram.kt │ │ │ ├── MyGLRenderer.kt │ │ │ ├── MyGLSurfaceView.kt │ │ │ └── Shader.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── lkl │ └── opengl │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | *.iml 5 | local.properties 6 | 7 | # Compiled class file 8 | *.class 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | -------------------------------------------------------------------------------- /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 | # OpenGL-YUV 2 | Android OpenGL 针对YUV格式(I420,NV12,NV21)数据渲染 3 | 4 | [简要说明](https://blog.csdn.net/lkl22/article/details/88824888) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.lkl.opengl" 11 | minSdkVersion 18 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'androidx.appcompat:appcompat:1.0.0-beta01' 29 | implementation 'androidx.core:core-ktx:1.0.0-rc02' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test:runner:1.1.0-alpha4' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' 34 | 35 | // For developers using AndroidX in their applications 36 | implementation 'pub.devrel:easypermissions:3.0.0' 37 | } 38 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/lkl/opengl/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.lkl.opengl 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.lkl.opengl", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/lkl/opengl/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.lkl.opengl 2 | 3 | import android.Manifest 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.util.Log 7 | import androidx.annotation.NonNull 8 | import androidx.appcompat.app.AppCompatActivity 9 | import pub.devrel.easypermissions.AfterPermissionGranted 10 | import pub.devrel.easypermissions.AppSettingsDialog 11 | import pub.devrel.easypermissions.EasyPermissions 12 | 13 | /** 14 | * 创建者 likunlun 15 | * 创建时间 2019/3/28 10:26 16 | * 描述 Activity基类 17 | */ 18 | open class BaseActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { 19 | companion object { 20 | private const val TAG = "BaseActivity" 21 | private const val RC_CAMERA_PERM = 100 22 | } 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | methodRequiresCameraPermission() 27 | } 28 | 29 | override fun onRequestPermissionsResult(requestCode: Int, @NonNull permissions: Array, @NonNull grantResults: IntArray) { 30 | 31 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 32 | 33 | // Forward results to EasyPermissions 34 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) 35 | } 36 | 37 | @AfterPermissionGranted(RC_CAMERA_PERM) 38 | private fun methodRequiresCameraPermission() { 39 | val perms = arrayOf(Manifest.permission.CAMERA) 40 | if (EasyPermissions.hasPermissions(this, *perms)) { 41 | // Already have permission, do the thing 42 | // ... 43 | Log.d(TAG, "Camera Permission have open") 44 | } else { 45 | // Do not have permissions, request them now 46 | EasyPermissions.requestPermissions( 47 | this, getString(R.string.rationale_camera), 48 | RC_CAMERA_PERM, *perms 49 | ) 50 | Log.d(TAG, "Camera Permission start request") 51 | } 52 | } 53 | 54 | override fun onPermissionsGranted(requestCode: Int, @NonNull perms: List) { 55 | Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size) 56 | restartApplication() 57 | } 58 | 59 | 60 | override fun onPermissionsDenied(requestCode: Int, @NonNull perms: List) { 61 | Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size) 62 | 63 | // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN." 64 | // This will display a dialog directing them to enable the permission in app settings. 65 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { 66 | AppSettingsDialog.Builder(this) 67 | .setTitle("权限申请") 68 | .setRationale("没有请求的权限,此应用可能无法正常工作。打开应用程序设置界面以修改应用程序权限。") 69 | .build() 70 | .show() 71 | } else { 72 | finish() 73 | } 74 | } 75 | 76 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 77 | super.onActivityResult(requestCode, resultCode, data) 78 | if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) { 79 | // Do something after user returned from app settings screen, like showing a Toast. 80 | methodRequiresCameraPermission() 81 | } 82 | } 83 | 84 | /** 85 | * 重启应用程序 86 | */ 87 | private fun restartApplication() { 88 | val intent = packageManager.getLaunchIntentForPackage(packageName) 89 | intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 90 | startActivity(intent) 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lkl/opengl/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.lkl.opengl 2 | 3 | import android.graphics.ImageFormat 4 | import android.hardware.Camera 5 | import android.os.Bundle 6 | import android.util.Log 7 | import android.view.SurfaceHolder 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | 10 | class MainActivity : BaseActivity(), Camera.PreviewCallback { 11 | companion object { 12 | private const val TAG = "MainActivity" 13 | } 14 | 15 | private var mCamera: Camera? = null 16 | // 设定默认的预览宽高 17 | private var mPreviewWidth = 1920 18 | private var mPreviewHeight = 1280 19 | 20 | private var mBuffer: ByteArray? = null 21 | 22 | private lateinit var mSurfaceHolder: SurfaceHolder 23 | 24 | private var mDegrees = 0 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.activity_main) 29 | 30 | cameraPreSurface.holder.addCallback(object : SurfaceHolder.Callback { 31 | override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { 32 | 33 | } 34 | 35 | override fun surfaceDestroyed(holder: SurfaceHolder) { 36 | releaseCamera() 37 | } 38 | 39 | override fun surfaceCreated(holder: SurfaceHolder) { 40 | mSurfaceHolder = holder 41 | initCamera() 42 | } 43 | }) 44 | 45 | rotateBtn.setOnClickListener { 46 | mDegrees += 90 47 | mDegrees %= 360 48 | openGlSurface?.setDisplayOrientation(mDegrees) 49 | } 50 | } 51 | 52 | private fun initCamera() { 53 | try { 54 | mCamera = openCamera() 55 | 56 | mCamera?.apply { 57 | var params: Camera.Parameters = parameters 58 | val sizes = params.supportedPreviewSizes 59 | if (sizes != null) { 60 | /* Select the size that fits surface considering maximum size allowed */ 61 | calculateCameraFrameSize(sizes, mPreviewWidth, mPreviewHeight) 62 | } 63 | params.previewFormat = ImageFormat.NV21 64 | params.setPreviewSize(mPreviewWidth, mPreviewHeight) 65 | Log.d(TAG, "Set preview size to $mPreviewWidth x $mPreviewHeight") 66 | 67 | parameters = params 68 | 69 | openGlSurface.setYuvDataSize(mPreviewWidth, mPreviewHeight) 70 | 71 | var size = mPreviewWidth * mPreviewHeight 72 | size = size * ImageFormat.getBitsPerPixel(params.previewFormat) / 8 73 | mBuffer = ByteArray(size) 74 | 75 | addCallbackBuffer(mBuffer) 76 | setPreviewCallbackWithBuffer(this@MainActivity) 77 | 78 | setPreviewDisplay(mSurfaceHolder) 79 | 80 | startPreview()//开始预览 81 | } 82 | } catch (e: Exception) { 83 | Log.w(TAG, e.message) 84 | } 85 | } 86 | 87 | override fun onPreviewFrame(data: ByteArray, camera: Camera?) { 88 | 89 | // mPreUtil.feedData(data) 90 | openGlSurface.feedData(data, 2) 91 | camera?.addCallbackBuffer(mBuffer) 92 | } 93 | 94 | /** 95 | * 打开Camera 96 | */ 97 | private fun openCamera(): Camera? { 98 | Log.d(TAG, "Trying to open camera with old open()") 99 | var camera: Camera? = null 100 | try { 101 | camera = Camera.open(1) 102 | } catch (e: Exception) { 103 | Log.w(TAG, "Camera is not available (in use or does not exist): ${e.message}") 104 | } 105 | 106 | if (camera == null) { 107 | var connected = false 108 | for (camIdx in 0 until Camera.getNumberOfCameras()) { 109 | Log.d(TAG, "Trying to open camera with new open(" + Integer.valueOf(camIdx) + ")") 110 | try { 111 | camera = Camera.open(camIdx) 112 | connected = true 113 | } catch (e: RuntimeException) { 114 | Log.w(TAG, "Camera #$camIdx failed to open: ${e.message}") 115 | } 116 | 117 | if (connected) break 118 | } 119 | } 120 | 121 | return camera 122 | } 123 | 124 | /** 125 | * 释放Camera资源 126 | */ 127 | private fun releaseCamera() { 128 | mCamera?.apply { 129 | stopPreview() 130 | setPreviewCallback(null) 131 | release() 132 | } 133 | mCamera = null 134 | } 135 | 136 | private fun calculateCameraFrameSize(supportedSizes: List<*>, maxAllowedWidth: Int, maxAllowedHeight: Int) { 137 | var calcWidth = 0 138 | var calcHeight = 0 139 | 140 | for (size in supportedSizes) { 141 | val cameraSize = size as Camera.Size 142 | val width = cameraSize.width 143 | val height = cameraSize.height 144 | 145 | if (width <= maxAllowedWidth && height <= maxAllowedHeight) { 146 | if (width >= calcWidth && height >= calcHeight) { 147 | // 找到临近的像素大小 148 | calcWidth = width 149 | calcHeight = height 150 | } 151 | } 152 | } 153 | 154 | mPreviewWidth = calcWidth 155 | mPreviewHeight = calcHeight 156 | } 157 | 158 | override fun onDestroy() { 159 | releaseCamera() 160 | super.onDestroy() 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /app/src/main/java/com/lkl/opengl/MyGLProgram.kt: -------------------------------------------------------------------------------- 1 | package com.lkl.opengl 2 | 3 | import android.opengl.GLES20 4 | import android.util.Log 5 | import java.nio.ByteBuffer 6 | import java.nio.ByteOrder 7 | import java.nio.FloatBuffer 8 | import java.nio.IntBuffer 9 | 10 | /** 11 | * 创建者 likunlun 12 | * 创建时间 2019/3/26 17:23 13 | * 描述 desc 14 | */ 15 | class MyGLProgram { 16 | companion object { 17 | private const val TAG = "MyGLProgram" 18 | var squareVertices = floatArrayOf(-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f) // fullscreen 19 | } 20 | 21 | private var mProgram: Int 22 | 23 | private var mPlanarTextureHandles = IntBuffer.wrap(IntArray(3)) 24 | private val mSampleHandle = IntArray(3) 25 | // handles 26 | private var mPositionHandle = -1 27 | private var mCoordHandle = -1 28 | private var mVPMatrixHandle: Int = -1 29 | 30 | // vertices buffer 31 | private var mVertexBuffer: FloatBuffer? = null 32 | private var mCoordBuffer: FloatBuffer? = null 33 | // whole-texture 34 | private val mCoordVertices = floatArrayOf(0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f) 35 | 36 | init { 37 | val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode) 38 | val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode) 39 | Log.d(TAG, "vertexShader = $vertexShader \n fragmentShader = $fragmentShader") 40 | 41 | // create empty OpenGL ES Program 42 | mProgram = GLES20.glCreateProgram().also { 43 | checkGlError("glCreateProgram") 44 | // add the vertex shader to program 45 | GLES20.glAttachShader(it, vertexShader) 46 | 47 | // add the fragment shader to program 48 | GLES20.glAttachShader(it, fragmentShader) 49 | 50 | // creates OpenGL ES program executables 51 | GLES20.glLinkProgram(it) 52 | } 53 | 54 | val linkStatus = IntArray(1) 55 | GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0) 56 | if (linkStatus[0] != GLES20.GL_TRUE) { 57 | Log.w(TAG, "Could not link program: ${GLES20.glGetProgramInfoLog(mProgram)}") 58 | GLES20.glDeleteProgram(mProgram) 59 | mProgram = 0 60 | } 61 | 62 | Log.d(TAG, "mProgram = $mProgram") 63 | 64 | checkGlError("glCreateProgram") 65 | 66 | // 生成纹理句柄 67 | GLES20.glGenTextures(3, mPlanarTextureHandles) 68 | 69 | checkGlError("glGenTextures") 70 | } 71 | 72 | /** 73 | * 绘制纹理贴图 74 | * @param mvpMatrix 顶点坐标变换矩阵 75 | * @param type YUV数据格式类型 76 | */ 77 | fun drawTexture(mvpMatrix: FloatArray, type: Int) { 78 | 79 | GLES20.glUseProgram(mProgram) 80 | checkGlError("glUseProgram") 81 | /* 82 | * get handle for "vPosition" and "a_texCoord" 83 | */ 84 | mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also { 85 | GLES20.glVertexAttribPointer(it, 2, GLES20.GL_FLOAT, false, 8, mVertexBuffer) 86 | GLES20.glEnableVertexAttribArray(it) 87 | } 88 | 89 | // 传纹理坐标给fragment shader 90 | mCoordHandle = GLES20.glGetAttribLocation(mProgram, "texCoord").also { 91 | GLES20.glVertexAttribPointer(it, 2, GLES20.GL_FLOAT, false, 8, mCoordBuffer) 92 | GLES20.glEnableVertexAttribArray(it) 93 | } 94 | 95 | // get handle to shape's transformation matrix 96 | mVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix") 97 | 98 | // Pass the projection and view transformation to the shader 99 | GLES20.glUniformMatrix4fv(mVPMatrixHandle, 1, false, mvpMatrix, 0) 100 | 101 | //传纹理的像素格式给fragment shader 102 | val yuvType = GLES20.glGetUniformLocation(mProgram, "yuvType") 103 | checkGlError("glGetUniformLocation yuvType") 104 | GLES20.glUniform1i(yuvType, type) 105 | 106 | //type: 0是I420, 1是NV12 107 | var planarCount = 0 108 | if (type == 0) { 109 | //I420有3个平面 110 | planarCount = 3 111 | mSampleHandle[0] = GLES20.glGetUniformLocation(mProgram, "samplerY") 112 | mSampleHandle[1] = GLES20.glGetUniformLocation(mProgram, "samplerU") 113 | mSampleHandle[2] = GLES20.glGetUniformLocation(mProgram, "samplerV") 114 | } else { 115 | //NV12、NV21有两个平面 116 | planarCount = 2 117 | mSampleHandle[0] = GLES20.glGetUniformLocation(mProgram, "samplerY") 118 | mSampleHandle[1] = GLES20.glGetUniformLocation(mProgram, "samplerUV") 119 | } 120 | (0 until planarCount).forEach { i -> 121 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i) 122 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mPlanarTextureHandles[i]) 123 | GLES20.glUniform1i(mSampleHandle[i], i) 124 | } 125 | 126 | // 调用这个函数后,vertex shader先在每个顶点执行一次,之后fragment shader在每个像素执行一次, 127 | // 绘制后的图像存储在render buffer中 128 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) 129 | GLES20.glFinish() 130 | 131 | GLES20.glDisableVertexAttribArray(mPositionHandle) 132 | GLES20.glDisableVertexAttribArray(mCoordHandle) 133 | } 134 | 135 | /** 136 | * 将图片数据绑定到纹理目标,适用于UV分量分开存储的(I420) 137 | * @param yPlane YUV数据的Y分量 138 | * @param uPlane YUV数据的U分量 139 | * @param vPlane YUV数据的V分量 140 | * @param width YUV图片宽度 141 | * @param height YUV图片高度 142 | */ 143 | fun feedTextureWithImageData(yPlane: ByteBuffer, uPlane: ByteBuffer, vPlane: ByteBuffer, width: Int, height: Int) { 144 | //根据YUV编码的特点,获得不同平面的基址 145 | textureYUV(yPlane, width, height, 0) 146 | textureYUV(uPlane, width / 2, height / 2, 1) 147 | textureYUV(vPlane, width / 2, height / 2, 2) 148 | } 149 | 150 | /** 151 | * 将图片数据绑定到纹理目标,适用于UV分量交叉存储的(NV12、NV21) 152 | * @param yPlane YUV数据的Y分量 153 | * @param uvPlane YUV数据的UV分量 154 | * @param width YUV图片宽度 155 | * @param height YUV图片高度 156 | */ 157 | fun feedTextureWithImageData(yPlane: ByteBuffer, uvPlane: ByteBuffer, width: Int, height: Int) { 158 | //根据YUV编码的特点,获得不同平面的基址 159 | textureYUV(yPlane, width, height, 0) 160 | textureNV12(uvPlane, width / 2, height / 2, 1) 161 | } 162 | 163 | /** 164 | * 将图片数据绑定到纹理目标,适用于UV分量分开存储的(I420) 165 | * @param imageData YUV数据的Y/U/V分量 166 | * @param width YUV图片宽度 167 | * @param height YUV图片高度 168 | */ 169 | private fun textureYUV(imageData: ByteBuffer, width: Int, height: Int, index: Int) { 170 | // 将纹理对象绑定到纹理目标 171 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mPlanarTextureHandles[index]) 172 | // 设置放大和缩小时,纹理的过滤选项为:线性过滤 173 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) 174 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) 175 | // 设置纹理X,Y轴的纹理环绕选项为:边缘像素延伸 176 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) 177 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) 178 | // 加载图像数据到纹理,GL_LUMINANCE指明了图像数据的像素格式为只有亮度,虽然第三个和第七个参数都使用了GL_LUMINANCE, 179 | // 但意义是不一样的,前者指明了纹理对象的颜色分量成分,后者指明了图像数据的像素格式 180 | // 获得纹理对象后,其每个像素的r,g,b,a值都为相同,为加载图像的像素亮度,在这里就是YUV某一平面的分量值 181 | GLES20.glTexImage2D( 182 | GLES20.GL_TEXTURE_2D, 0, 183 | GLES20.GL_LUMINANCE, width, height, 0, 184 | GLES20.GL_LUMINANCE, 185 | GLES20.GL_UNSIGNED_BYTE, imageData 186 | ) 187 | } 188 | 189 | /** 190 | * 将图片数据绑定到纹理目标,适用于UV分量交叉存储的(NV12、NV21) 191 | * @param imageData YUV数据的UV分量 192 | * @param width YUV图片宽度 193 | * @param height YUV图片高度 194 | */ 195 | private fun textureNV12(imageData: ByteBuffer, width: Int, height: Int, index: Int) { 196 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mPlanarTextureHandles[index]) 197 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) 198 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) 199 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) 200 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) 201 | GLES20.glTexImage2D( 202 | GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE_ALPHA, width, height, 0, 203 | GLES20.GL_LUMINANCE_ALPHA, GLES20.GL_UNSIGNED_BYTE, imageData 204 | ) 205 | } 206 | 207 | /** 208 | * 创建两个缓冲区用于保存顶点 -> 屏幕顶点和纹理顶点 209 | * @param vert 屏幕顶点数据 210 | */ 211 | fun createBuffers(vert: FloatArray) { 212 | mVertexBuffer = ByteBuffer.allocateDirect(vert.size * 4).run { 213 | // use the device hardware's native byte order 214 | order(ByteOrder.nativeOrder()) 215 | 216 | // create a floating point buffer from the ByteBuffer 217 | asFloatBuffer().apply { 218 | // add the coordinates to the FloatBuffer 219 | put(vert) 220 | // set the buffer to read the first coordinate 221 | position(0) 222 | } 223 | } 224 | 225 | if (mCoordBuffer == null) { 226 | mCoordBuffer = ByteBuffer.allocateDirect(mCoordVertices.size * 4).run { 227 | // use the device hardware's native byte order 228 | order(ByteOrder.nativeOrder()) 229 | 230 | // create a floating point buffer from the ByteBuffer 231 | asFloatBuffer().apply { 232 | // add the coordinates to the FloatBuffer 233 | put(mCoordVertices) 234 | // set the buffer to read the first coordinate 235 | position(0) 236 | } 237 | } 238 | } 239 | Log.d(TAG, "createBuffers vertice_buffer $mVertexBuffer coord_buffer $mCoordBuffer") 240 | } 241 | 242 | /** 243 | * 检查GL操作是否有error 244 | * @param op 检查当前所做的操作 245 | */ 246 | private fun checkGlError(op: String) { 247 | var error: Int = GLES20.glGetError() 248 | while (error != GLES20.GL_NO_ERROR) { 249 | Log.e(TAG, "***** $op: glError $error") 250 | error = GLES20.glGetError() 251 | } 252 | } 253 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lkl/opengl/MyGLRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.lkl.opengl 2 | 3 | import android.opengl.GLES20 4 | import android.opengl.GLSurfaceView 5 | import android.opengl.Matrix 6 | import android.util.Log 7 | import java.nio.ByteBuffer 8 | import javax.microedition.khronos.egl.EGLConfig 9 | import javax.microedition.khronos.opengles.GL10 10 | 11 | /** 12 | * 创建者 likunlun 13 | * 创建时间 2019/3/26 17:17 14 | * 描述 GLSurfaceView.Renderer 渲染类 15 | */ 16 | class MyGLRenderer : GLSurfaceView.Renderer { 17 | companion object { 18 | private const val TAG = "MyGLRenderer" 19 | } 20 | 21 | private lateinit var mProgram: MyGLProgram 22 | // GLSurfaceView宽度 23 | private var mScreenWidth: Int = 0 24 | // GLSurfaceView高度 25 | private var mScreenHeight: Int = 0 26 | // 预览YUV数据宽度 27 | private var mVideoWidth: Int = 0 28 | // 预览YUV数据高度 29 | private var mVideoHeight: Int = 0 30 | 31 | // vPMatrix is an abbreviation for "Model View Projection Matrix" 32 | private val vPMatrix = FloatArray(16) 33 | private val projectionMatrix = FloatArray(16) 34 | private val viewMatrix = FloatArray(16) 35 | 36 | // y分量数据 37 | private var y: ByteBuffer = ByteBuffer.allocate(0) 38 | // u分量数据 39 | private var u: ByteBuffer = ByteBuffer.allocate(0) 40 | // v分量数据 41 | private var v: ByteBuffer = ByteBuffer.allocate(0) 42 | // uv分量数据 43 | private var uv: ByteBuffer = ByteBuffer.allocate(0) 44 | 45 | // YUV数据格式 0 -> I420 1 -> NV12 2 -> NV21 46 | private var type: Int = 0 47 | // 标识GLSurfaceView是否准备好 48 | private var hasVisibility = false 49 | 50 | // Called once to set up the view's OpenGL ES environment. 51 | override fun onSurfaceCreated(unused: GL10, config: EGLConfig) { 52 | // Set the background frame color 53 | GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f) 54 | 55 | // 配置OpenGL ES 环境 56 | mProgram = MyGLProgram() 57 | } 58 | 59 | // Called if the geometry of the view changes, for example when the device's screen orientation changes. 60 | override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) { 61 | GLES20.glViewport(0, 0, width, height) 62 | 63 | mScreenWidth = width 64 | mScreenHeight = height 65 | 66 | mScreenWidth = width 67 | mScreenHeight = height 68 | val ratio: Float = width.toFloat() / height.toFloat() 69 | 70 | // this projection matrix is applied to object coordinates 71 | // in the onDrawFrame() method 72 | Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f) 73 | 74 | // Set the camera position (View matrix) 75 | Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 1.0f, 0.0f, 0.0f) 76 | 77 | if (mVideoWidth > 0 && mVideoHeight > 0) { 78 | createBuffers(mVideoWidth, mVideoHeight) 79 | } 80 | hasVisibility = true 81 | Log.d(TAG, "onSurfaceChanged width:$width * height:$height") 82 | } 83 | 84 | // Called for each redraw of the view. 85 | override fun onDrawFrame(unused: GL10) { 86 | synchronized(this) { 87 | if (y.capacity() > 0) { 88 | y.position(0) 89 | if (type == 0) { 90 | u.position(0) 91 | v.position(0) 92 | mProgram.feedTextureWithImageData(y, u, v, mVideoWidth, mVideoHeight) 93 | } else { 94 | uv.position(0) 95 | mProgram.feedTextureWithImageData(y, uv, mVideoWidth, mVideoHeight) 96 | } 97 | // Redraw background color 98 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 99 | 100 | // Calculate the projection and view transformation 101 | Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0) 102 | 103 | try { 104 | mProgram.drawTexture(vPMatrix, type) 105 | } catch (e: Exception) { 106 | Log.w(TAG, e.message) 107 | } 108 | } 109 | } 110 | } 111 | 112 | /** 113 | * 设置显示方向 114 | * @param degrees 显示旋转角度(逆时针),有效值是(0, 90, 180, and 270.) 115 | */ 116 | fun setDisplayOrientation(degrees: Int) { 117 | // Set the camera position (View matrix) 118 | if (degrees == 0) { 119 | Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 1.0f, 0.0f, 0.0f) 120 | } else if (degrees == 90) { 121 | Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 0.0f, 1.0f, 0.0f) 122 | } else if (degrees == 180) { 123 | Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, -1.0f, 0.0f, 0.0f) 124 | } else if (degrees == 270) { 125 | Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 0.0f, -1.0f, 0.0f) 126 | } else { 127 | Log.e(TAG, "degrees pram must be in (0, 90, 180, 270) ") 128 | } 129 | } 130 | 131 | /** 132 | * 设置渲染的YUV数据的宽高 133 | * @param width 宽度 134 | * @param height 高度 135 | */ 136 | fun setYuvDataSize(width: Int, height: Int) { 137 | if (width > 0 && height > 0) { 138 | // 调整比例 139 | createBuffers(width, height) 140 | 141 | // 初始化容器 142 | if (width != mVideoWidth && height != mVideoHeight) { 143 | this.mVideoWidth = width 144 | this.mVideoHeight = height 145 | val yarraySize = width * height 146 | val uvarraySize = yarraySize / 4 147 | synchronized(this) { 148 | y = ByteBuffer.allocate(yarraySize) 149 | u = ByteBuffer.allocate(uvarraySize) 150 | v = ByteBuffer.allocate(uvarraySize) 151 | uv = ByteBuffer.allocate(uvarraySize * 2) 152 | } 153 | } 154 | } 155 | } 156 | 157 | /** 158 | * 调整渲染纹理的缩放比例 159 | * @param width YUV数据宽度 160 | * @param height YUV数据高度 161 | */ 162 | private fun createBuffers(width: Int, height: Int) { 163 | if (mScreenWidth > 0 && mScreenHeight > 0) { 164 | val f1 = mScreenHeight.toFloat() / mScreenWidth.toFloat() 165 | val f2 = height.toFloat() / width.toFloat() 166 | if (f1 == f2) { 167 | mProgram.createBuffers(MyGLProgram.squareVertices) 168 | } else if (f1 < f2) { 169 | val widthScale = f1 / f2 170 | mProgram.createBuffers( 171 | floatArrayOf( 172 | -widthScale, 173 | -1.0f, 174 | widthScale, 175 | -1.0f, 176 | -widthScale, 177 | 1.0f, 178 | widthScale, 179 | 1.0f 180 | ) 181 | ) 182 | } else { 183 | val heightScale = f2 / f1 184 | mProgram.createBuffers( 185 | floatArrayOf( 186 | -1.0f, 187 | -heightScale, 188 | 1.0f, 189 | -heightScale, 190 | -1.0f, 191 | heightScale, 192 | 1.0f, 193 | heightScale 194 | ) 195 | ) 196 | } 197 | } 198 | } 199 | 200 | /** 201 | * 预览YUV格式数据 202 | * @param yuvdata yuv格式的数据 203 | * @param type YUV数据的格式 0 -> I420 1 -> NV12 2 -> NV21 204 | */ 205 | fun feedData(yuvdata: ByteArray, type: Int = 0) { 206 | synchronized(this) { 207 | if (hasVisibility) { 208 | this.type = type 209 | if (type == 0) { 210 | y.clear() 211 | u.clear() 212 | v.clear() 213 | y.put(yuvdata, 0, mVideoWidth * mVideoHeight) 214 | u.put(yuvdata, mVideoWidth * mVideoHeight, mVideoWidth * mVideoHeight / 4) 215 | v.put(yuvdata, mVideoWidth * mVideoHeight * 5 / 4, mVideoWidth * mVideoHeight / 4) 216 | } else { 217 | y.clear() 218 | uv.clear() 219 | y.put(yuvdata, 0, mVideoWidth * mVideoHeight) 220 | uv.put(yuvdata, mVideoWidth * mVideoHeight, mVideoWidth * mVideoHeight / 2) 221 | } 222 | } 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lkl/opengl/MyGLSurfaceView.kt: -------------------------------------------------------------------------------- 1 | package com.lkl.opengl 2 | 3 | import android.content.Context 4 | import android.opengl.GLSurfaceView 5 | import android.util.AttributeSet 6 | import android.util.Log 7 | 8 | /** 9 | * 创建者 likunlun 10 | * 创建时间 2019/3/26 17:16 11 | * 描述 自定义GLSurfaceView 12 | */ 13 | class MyGLSurfaceView(context: Context, attributeSet: AttributeSet?) : GLSurfaceView(context, attributeSet) { 14 | companion object { 15 | private const val TAG = "MyGLSurfaceView" 16 | } 17 | 18 | constructor(context: Context) : this(context, null) 19 | 20 | private val renderer: MyGLRenderer 21 | 22 | init { 23 | 24 | // Create an OpenGL ES 2.0 context 25 | setEGLContextClientVersion(2) 26 | 27 | renderer = MyGLRenderer() 28 | 29 | // Set the Renderer for drawing on the GLSurfaceView 30 | setRenderer(renderer) 31 | 32 | // Render the view only when there is a change in the drawing data 33 | renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY 34 | } 35 | 36 | /** 37 | * 设置显示方向 38 | * @param degrees 显示旋转角度(逆时针),有效值是(0, 90, 180, and 270.) 39 | */ 40 | fun setDisplayOrientation(degrees: Int) { 41 | renderer.setDisplayOrientation(degrees) 42 | } 43 | 44 | /** 45 | * 设置渲染的YUV数据的宽高 46 | * @param width 宽度 47 | * @param height 高度 48 | */ 49 | fun setYuvDataSize(width: Int, height: Int) { 50 | Log.d(TAG, "setYuvDataSize $width * $height") 51 | renderer.setYuvDataSize(width, height) 52 | } 53 | 54 | /** 55 | * 填充预览YUV格式数据 56 | * @param yuvData yuv格式的数据 57 | * @param type YUV数据的格式 0 -> I420 1 -> NV12 2 -> NV21 58 | */ 59 | fun feedData(yuvData: ByteArray?, type: Int = 0) { 60 | if (yuvData == null) { 61 | return 62 | } 63 | renderer.feedData(yuvData, type) 64 | // 请求渲染新的YUV数据 65 | requestRender() 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lkl/opengl/Shader.kt: -------------------------------------------------------------------------------- 1 | package com.lkl.opengl 2 | 3 | import android.opengl.GLES20 4 | 5 | /** 6 | * 加载着色器程序 7 | * @param type GLES20.GL_VERTEX_SHADER -> vertex shader 8 | * GLES20.GL_FRAGMENT_SHADER -> fragment shader 9 | * @param shaderCode 着色器程序代码 10 | */ 11 | fun loadShader(type: Int, shaderCode: String): Int { 12 | 13 | // create a vertex shader type (GLES20.GL_VERTEX_SHADER) 14 | // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) 15 | return GLES20.glCreateShader(type).also { shader -> 16 | 17 | // add the source code to the shader and compile it 18 | GLES20.glShaderSource(shader, shaderCode) 19 | GLES20.glCompileShader(shader) 20 | } 21 | } 22 | 23 | /** 24 | * 顶点着色器程序 25 | * vertex shader在每个顶点上都执行一次,通过不同世界的坐标系转化定位顶点的最终位置。 26 | * 它可以传递数据给fragment shader,如纹理坐标、顶点坐标,变换矩阵等 27 | */ 28 | const val vertexShaderCode = 29 | "uniform mat4 uMVPMatrix;" + 30 | "attribute vec4 vPosition;" + 31 | "attribute vec2 texCoord;" + 32 | "varying vec2 tc;" + 33 | "void main() {" + 34 | " gl_Position = uMVPMatrix * vPosition;" + 35 | " tc = texCoord;" + 36 | "}" 37 | 38 | /** 39 | * 片段着色器程序 40 | * fragment shader在每个像素上都会执行一次,通过插值确定像素的最终显示颜色 41 | */ 42 | const val fragmentShaderCode = 43 | "precision mediump float;" + 44 | "uniform sampler2D samplerY;" + 45 | "uniform sampler2D samplerU;" + 46 | "uniform sampler2D samplerV;" + 47 | "uniform sampler2D samplerUV;" + 48 | "uniform int yuvType;" + 49 | "varying vec2 tc;" + 50 | "void main() {" + 51 | " vec4 c = vec4((texture2D(samplerY, tc).r - 16./255.) * 1.164);" + 52 | " vec4 U; vec4 V;" + 53 | " if (yuvType == 0){" + 54 | " U = vec4(texture2D(samplerU, tc).r - 128./255.);" + 55 | " V = vec4(texture2D(samplerV, tc).r - 128./255.);" + 56 | " } else if (yuvType == 1){" + 57 | " U = vec4(texture2D(samplerUV, tc).r - 128./255.);" + 58 | " V = vec4(texture2D(samplerUV, tc).a - 128./255.);" + 59 | " } else {" + 60 | " U = vec4(texture2D(samplerUV, tc).a - 128./255.);" + 61 | " V = vec4(texture2D(samplerUV, tc).r - 128./255.);" + 62 | " } " + 63 | " c += V * vec4(1.596, -0.813, 0, 0);" + 64 | " c += U * vec4(0, -0.392, 2.017, 0);" + 65 | " c.a = 1.0;" + 66 | " gl_FragColor = c;" + 67 | "}" -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 19 | 20 |