Repository: doggycoder/AAVT Branch: master Commit: 8f7f1e2f1340 Files: 148 Total size: 336.4 KB Directory structure: gitextract_5x9hpeoq/ ├── .gitignore ├── .idea/ │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── inspectionProfiles/ │ │ └── Project_Default.xml │ ├── markdown-exported-files.xml │ ├── markdown-navigator/ │ │ └── profiles_settings.xml │ ├── markdown-navigator.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── LICENSE.txt ├── aavt/ │ ├── .gitignore │ ├── bintrayUpload.gradle │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── wuwang/ │ │ └── aavt/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── assets/ │ │ │ └── shader/ │ │ │ ├── base.frag │ │ │ ├── base.vert │ │ │ ├── beauty/ │ │ │ │ ├── beauty.frag │ │ │ │ └── beauty.vert │ │ │ ├── color/ │ │ │ │ └── gray.frag │ │ │ ├── convert/ │ │ │ │ ├── eo_yuv420p.frag │ │ │ │ ├── export_yuv.frag │ │ │ │ ├── export_yuv420p.frag │ │ │ │ └── export_yuv420sp.frag │ │ │ ├── effect/ │ │ │ │ ├── fluorescence.frag │ │ │ │ ├── water_color.frag │ │ │ │ └── water_color_step1.frag │ │ │ ├── func/ │ │ │ │ ├── candy.frag │ │ │ │ ├── faltung3x3.frag │ │ │ │ ├── gauss.frag │ │ │ │ ├── sobel.frag │ │ │ │ └── sobel2.frag │ │ │ ├── oes.frag │ │ │ └── oes.vert │ │ ├── java/ │ │ │ └── com/ │ │ │ └── wuwang/ │ │ │ └── aavt/ │ │ │ ├── av/ │ │ │ │ ├── CameraRecorder2.java │ │ │ │ ├── Mp4Processor.java │ │ │ │ ├── Mp4Processor2.java │ │ │ │ ├── SurfaceRecorder.java │ │ │ │ └── VideoCapture.java │ │ │ ├── core/ │ │ │ │ ├── IObservable.java │ │ │ │ ├── IObserver.java │ │ │ │ ├── Observable.java │ │ │ │ └── Renderer.java │ │ │ ├── egl/ │ │ │ │ ├── EGLConfigAttrs.java │ │ │ │ ├── EGLContextAttrs.java │ │ │ │ └── EglHelper.java │ │ │ ├── expend/ │ │ │ │ └── SluggardFilterTool.java │ │ │ ├── gl/ │ │ │ │ ├── BaseFilter.java │ │ │ │ ├── BaseFuncFilter.java │ │ │ │ ├── BeautyFilter.java │ │ │ │ ├── BlackMagicFilter.java │ │ │ │ ├── CandyFilter.java │ │ │ │ ├── Faltung3x3Filter.java │ │ │ │ ├── FluorescenceFilter.java │ │ │ │ ├── FrameBuffer.java │ │ │ │ ├── GrayFilter.java │ │ │ │ ├── GroupFilter.java │ │ │ │ ├── LazyFilter.java │ │ │ │ ├── OesFilter.java │ │ │ │ ├── ProxyFilter.java │ │ │ │ ├── RollFilter.java │ │ │ │ ├── StickFigureFilter.java │ │ │ │ ├── WaterColorFilter.java │ │ │ │ ├── WaterMarkFilter.java │ │ │ │ └── YuvOutputFilter.java │ │ │ ├── log/ │ │ │ │ ├── AvLog.java │ │ │ │ ├── ConsoleLogger.java │ │ │ │ ├── EmptyLogger.java │ │ │ │ └── ILogger.java │ │ │ ├── media/ │ │ │ │ ├── CodecUtil.java │ │ │ │ ├── MediaConfig.java │ │ │ │ ├── RenderBean.java │ │ │ │ ├── SoundRecorder.java │ │ │ │ ├── SurfaceEncoder.java │ │ │ │ ├── SurfaceShower.java │ │ │ │ ├── VideoSurfaceProcessor.java │ │ │ │ ├── WrapRenderer.java │ │ │ │ ├── audio/ │ │ │ │ │ ├── FileAudioProvider.java │ │ │ │ │ ├── ISoundProvider.java │ │ │ │ │ └── MicAudioProvider.java │ │ │ │ ├── av/ │ │ │ │ │ ├── AvException.java │ │ │ │ │ ├── ICloseable.java │ │ │ │ │ └── IStore.java │ │ │ │ ├── hard/ │ │ │ │ │ ├── HardMediaData.java │ │ │ │ │ ├── IHardStore.java │ │ │ │ │ ├── MediaMuxerWraper.java │ │ │ │ │ ├── Mp4MuxStore.java │ │ │ │ │ ├── Recycler.java │ │ │ │ │ └── StrengthenMp4MuxStore.java │ │ │ │ ├── player/ │ │ │ │ │ ├── AudioDecoder.java │ │ │ │ │ ├── AudioPlayer.java │ │ │ │ │ ├── BaseAudioDecoder.java │ │ │ │ │ ├── BaseVideoDecoder.java │ │ │ │ │ ├── ByteBufferData.java │ │ │ │ │ ├── EPlayerException.java │ │ │ │ │ ├── EffectMediaPlayer.java │ │ │ │ │ ├── EffectMediaView.java │ │ │ │ │ ├── IAudioProcessor.java │ │ │ │ │ ├── IDecoder.java │ │ │ │ │ ├── ITextureProcessor.java │ │ │ │ │ ├── ITimeObserver.java │ │ │ │ │ ├── NormalAudioDecoder.java │ │ │ │ │ ├── NormalVideoDecoder.java │ │ │ │ │ └── VideoPlayer.java │ │ │ │ └── video/ │ │ │ │ ├── Camera2Provider.java │ │ │ │ ├── CameraProvider.java │ │ │ │ ├── ITextureProvider.java │ │ │ │ └── Mp4Provider.java │ │ │ └── utils/ │ │ │ ├── GpuUtils.java │ │ │ └── MatrixUtils.java │ │ └── res/ │ │ └── values/ │ │ ├── attr.xml │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── wuwang/ │ └── aavt/ │ └── ExampleUnitTest.java ├── build.gradle ├── examples/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── wuwang/ │ │ └── aavt/ │ │ └── examples/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── wuwang/ │ │ │ └── aavt/ │ │ │ └── examples/ │ │ │ ├── CameraRecorderActivity.java │ │ │ ├── ExampleMp4ProcessActivity.java │ │ │ ├── GetPathFromUri4kitkat.java │ │ │ ├── MainActivity.java │ │ │ ├── PermissionAsker.java │ │ │ ├── VideoPlayerActivity.java │ │ │ ├── VideoUtils.java │ │ │ └── YuvExportActivity.java │ │ └── res/ │ │ ├── drawable/ │ │ │ └── tv_start_bg.xml │ │ ├── layout/ │ │ │ ├── activity__export_yuv.xml │ │ │ ├── activity_camera_record.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_mp4.xml │ │ │ └── activity_player.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── wuwang/ │ └── aavt/ │ └── examples/ │ └── ExampleUnitTest.java ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── readme.md └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries /.idea/misc.xml .DS_Store /build /captures .externalNativeBuild ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/inspectionProfiles/Project_Default.xml ================================================ ================================================ FILE: .idea/markdown-exported-files.xml ================================================ ================================================ FILE: .idea/markdown-navigator/profiles_settings.xml ================================================ ================================================ FILE: .idea/markdown-navigator.xml ================================================ ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: aavt/.gitignore ================================================ /build ================================================ FILE: aavt/bintrayUpload.gradle ================================================ apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' // 配置版本 version = rootProject.ext.vName // 定义相关网站 def siteUrl = 'https://github.com/doggycoder/AAVT' // 项目的主页 def gitUrl = 'https://github.com/doggycoder/AAVT.git' group = "com.wuwang.aavt" install { repositories.mavenInstaller { // This generates POM.xml with proper parameters pom { project { packaging 'aar' // Add your description here name 'Aavt' url siteUrl // Set your license licenses { license { name 'The Apache Software License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id 'doggycoder' //填写的一些基本信息 name 'doggycoder' email '158183202@qq.com' // 填写邮箱 } } scm { connection gitUrl developerConnection gitUrl url siteUrl } } } } } // 打包 javadocjar 和 sourcejar task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { archives javadocJar archives sourcesJar } def propertiesFile = project.rootProject.file('local.properties') if(propertiesFile.exists()){ Properties properties = new Properties() properties.load(propertiesFile.newDataInputStream()) bintray { user = properties.getProperty("bintray.user") key = properties.getProperty("bintray.apikey") configurations = ['archives'] pkg { repo = "maven" name = "Aavt" //发布到JCenter上的项目名字 websiteUrl = siteUrl vcsUrl = gitUrl licenses = ["Apache-2.0"] publish = true } } } javadoc { //jav doc采用utf-8编码否则会报“GBK的不可映射字符”错误 options{ encoding "UTF-8" charSet 'UTF-8' links "http://docs.oracle.com/javase/7/docs/api" } } ================================================ FILE: aavt/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' group='com.wuwang.aavt' android { compileSdkVersion 25 defaultConfig { minSdkVersion 14 targetSdkVersion 25 versionCode rootProject.ext.vCode versionName rootProject.ext.vName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } task buildMyJar(type: Jar, dependsOn: ['build']) { //导出的jar文件名称 archiveName = 'Aavt.jar' //从哪个目录打包jar from('build/intermediates/classes/release/') destinationDir = file('output') //去掉不要的类 exclude('**/BuildConfig.class') exclude('**/BuildConfig\$*.class') exclude('**/R.class') exclude('**/R\$*.class') include('com/wuwang/aavt/**') // doLast{ // copy { // from('libs/','build/intermediates/cmake/release/obj/') // into('output/') // } // } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) implementation 'com.android.support:appcompat-v7:25.3.1' testImplementation 'junit:junit:4.12' } apply from: "bintrayUpload.gradle" ================================================ FILE: aavt/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in D:\Android\android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: aavt/src/androidTest/java/com/wuwang/aavt/ExampleInstrumentedTest.java ================================================ package com.wuwang.aavt; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.wuwang.aavt.test", appContext.getPackageName()); } } ================================================ FILE: aavt/src/main/AndroidManifest.xml ================================================ ================================================ FILE: aavt/src/main/assets/shader/base.frag ================================================ precision mediump float; varying vec2 vTextureCo; uniform sampler2D uTexture; void main() { gl_FragColor = texture2D( uTexture, vTextureCo); } ================================================ FILE: aavt/src/main/assets/shader/base.vert ================================================ precision highp float; precision highp int; attribute vec4 aVertexCo; attribute vec2 aTextureCo; uniform mat4 uVertexMatrix; uniform mat4 uTextureMatrix; varying vec2 vTextureCo; void main(){ gl_Position = uVertexMatrix*aVertexCo; vTextureCo = (uTextureMatrix*vec4(aTextureCo,0,1)).xy; } ================================================ FILE: aavt/src/main/assets/shader/beauty/beauty.frag ================================================ precision highp float; precision highp int; uniform sampler2D uTexture; uniform int uIternum; uniform float uACoef; //参数 uniform float uMixCoef; //混合系数 varying highp vec2 vTextureCo; varying highp vec2 vBlurCoord1s[14]; const float distanceNormalizationFactor = 4.0; const mat3 saturateMatrix = mat3(1.1102,-0.0598,-0.061,-0.0774,1.0826,-0.1186,-0.0228,-0.0228,1.1772); void main() { vec3 centralColor; float central; float gaussianWeightTotal; float sum; float sampleColor; float distanceFromCentralColor; float gaussianWeight; central = texture2D( uTexture, vTextureCo ).g; gaussianWeightTotal = 0.2; sum = central * 0.2; for (int i = 0; i < 6; i++) { sampleColor = texture2D( uTexture, vBlurCoord1s[i] ).g; distanceFromCentralColor = min( abs( central - sampleColor ) * distanceNormalizationFactor, 1.0 ); gaussianWeight = 0.05 * (1.0 - distanceFromCentralColor); gaussianWeightTotal += gaussianWeight; sum += sampleColor * gaussianWeight; } for (int i = 6; i < 14; i++) { sampleColor = texture2D( uTexture, vBlurCoord1s[i] ).g; distanceFromCentralColor = min( abs( central - sampleColor ) * distanceNormalizationFactor, 1.0 ); gaussianWeight = 0.1 * (1.0 - distanceFromCentralColor); gaussianWeightTotal += gaussianWeight; sum += sampleColor * gaussianWeight; } sum = sum / gaussianWeightTotal; centralColor = texture2D( uTexture, vTextureCo ).rgb; sampleColor = centralColor.g - sum + 0.5; for (int i = 0; i < 100; i++) { if(i>=uIternum){ break; } if (sampleColor <= 0.5) { sampleColor = sampleColor * sampleColor * 2.0; } else { sampleColor = 1.0 - ((1.0 - sampleColor)*(1.0 - sampleColor) * 2.0); } } float aa = 1.0 + pow( centralColor.g, 0.3 )*uACoef; vec3 smoothColor = centralColor*aa - vec3( sampleColor )*(aa - 1.0); smoothColor = clamp( smoothColor, vec3( 0.0 ), vec3( 1.0 ) ); smoothColor = mix( centralColor, smoothColor, pow( centralColor.g, 0.33 ) ); smoothColor = mix( centralColor, smoothColor, pow( centralColor.g, uMixCoef ) ); gl_FragColor = vec4( pow( smoothColor, vec3( 0.96 ) ), 1.0 ); vec3 satcolor = gl_FragColor.rgb * saturateMatrix; gl_FragColor.rgb = mix( gl_FragColor.rgb, satcolor, 0.23 ); } ================================================ FILE: aavt/src/main/assets/shader/beauty/beauty.vert ================================================ attribute vec4 aVertexCo; attribute vec2 aTextureCo; varying vec2 vTextureCo; varying vec2 vBlurCoord1s[14]; uniform float uWidth; uniform float uHeight; uniform mat4 uVertexMatrix; uniform mat4 uTextureMatrix; void main() { gl_Position = uVertexMatrix*aVertexCo; vTextureCo = (uTextureMatrix*vec4(aTextureCo,0,1)).xy; highp float mul_x = 2.0 / uWidth; highp float mul_y = 2.0 / uHeight; vBlurCoord1s[0] = vTextureCo + vec2( 0.0 * mul_x, -10.0 * mul_y ); vBlurCoord1s[1] = vTextureCo + vec2( 8.0 * mul_x, -5.0 * mul_y ); vBlurCoord1s[2] = vTextureCo + vec2( 8.0 * mul_x, 5.0 * mul_y ); vBlurCoord1s[3] = aTextureCo + vec2( 0.0 * mul_x, 10.0 * mul_y ); vBlurCoord1s[4] = aTextureCo + vec2( -8.0 * mul_x, 5.0 * mul_y ); vBlurCoord1s[5] = aTextureCo + vec2( -8.0 * mul_x, -5.0 * mul_y ); mul_x = 1.2 / uWidth; mul_y = 1.2 / uHeight; vBlurCoord1s[6] = aTextureCo + vec2( 0.0 * mul_x, -6.0 * mul_y ); vBlurCoord1s[7] = aTextureCo + vec2( -4.0 * mul_x, -4.0 * mul_y ); vBlurCoord1s[8] = aTextureCo + vec2( -6.0 * mul_x, 0.0 * mul_y ); vBlurCoord1s[9] = aTextureCo + vec2( -4.0 * mul_x, 4.0 * mul_y ); vBlurCoord1s[10] = aTextureCo + vec2( 0.0 * mul_x, 6.0 * mul_y ); vBlurCoord1s[11] = aTextureCo + vec2( 4.0 * mul_x, 4.0 * mul_y ); vBlurCoord1s[12] = aTextureCo + vec2( 6.0 * mul_x, 0.0 * mul_y ); vBlurCoord1s[13] = aTextureCo + vec2( 4.0 * mul_x, -4.0 * mul_y ); } ================================================ FILE: aavt/src/main/assets/shader/color/gray.frag ================================================ precision mediump float; varying vec2 vTextureCo; uniform sampler2D uTexture; const highp vec3 CO = vec3(0.2125, 0.7154, 0.0721); void main() { gl_FragColor=vec4(vec3(dot(texture2D( uTexture, vTextureCo).rgb,CO)),1.0); } ================================================ FILE: aavt/src/main/assets/shader/convert/eo_yuv420p.frag ================================================ precision highp float; precision highp int; varying vec2 vTextureCo; uniform sampler2D uTexture; //为了简化计算,宽高都必须为8的倍数 uniform float uWidth; // 纹理宽 uniform float uHeight; // 纹理高 //转换公式 //Y’= 0.299*R’ + 0.587*G’ + 0.114*B’ //U’= -0.147*R’ - 0.289*G’ + 0.436*B’ = 0.492*(B’- Y’) //V’= 0.615*R’ - 0.515*G’ - 0.100*B’ = 0.877*(R’- Y’) //导出原理:采样坐标只作为确定输出位置使用,通过输出纹理计算实际采样位置,进行采样和并转换, //然后将转换的结果填充到输出位置 float cY(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return c.r*0.2990+c.g*0.5870+c.b*0.1140; } float cU(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return -0.1471*c.r - 0.2889*c.g + 0.4360*c.b+0.5000; } float cV(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return 0.6150*c.r - 0.5150*c.g - 0.1000*c.b+0.5000; } vec2 cPos(float t,float shiftx,float gy){ vec2 pos=vec2(floor(uWidth*vTextureCo.x),floor(uHeight*gy)); return vec2(mod(pos.x*shiftx,uWidth),(pos.y*shiftx+floor(pos.x*shiftx/uWidth))*t); } //Y分量的计算 vec4 calculateY(){ vec2 pos=cPos(1.,4.,vTextureCo.y); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]=cY(pos.x/uWidth,textureYPos); oColor[1]=cY((pos.x+1.)/uWidth,textureYPos); oColor[2]=cY((pos.x+2.)/uWidth,textureYPos); oColor[3]=cY((pos.x+3.)/uWidth,textureYPos); return oColor; } //U分量的计算 vec4 calculateU(){ vec2 pos=cPos(2.,8.,vTextureCo.y-0.2500); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]= cU(pos.x/uWidth,textureYPos); oColor[1]= cU((pos.x+2.)/uWidth,textureYPos); oColor[2]= cU((pos.x+4.)/uWidth,textureYPos); oColor[3]= cU((pos.x+6.)/uWidth,textureYPos); return oColor; } //V分量计算 vec4 calculateV(){ vec2 pos=cPos(2.,8.,vTextureCo.y-0.3125); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]=cV(pos.x/uWidth,textureYPos); oColor[1]=cV((pos.x+2.)/uWidth,textureYPos); oColor[2]=cV((pos.x+4.)/uWidth,textureYPos); oColor[3]=cV((pos.x+6.)/uWidth,textureYPos); return oColor; } //UV的计算,YUV420SP用,test vec4 calculateUV(){ vec2 pos=cPos(2.,4.,vTextureCo.y-0.2500); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]= cU(pos.x/uWidth,textureYPos); oColor[1]= cV(pos.x/uWidth,textureYPos); oColor[2]= cU((pos.x+2.)/uWidth,textureYPos); oColor[3]= cV((pos.x+2.)/uWidth,textureYPos); return oColor; } void main() { if(vTextureCo.y<0.2500){ gl_FragColor=calculateY(); }else if(vTextureCo.y<0.3125){ gl_FragColor=calculateU(); }else if(vTextureCo.y<0.3750){ gl_FragColor=calculateV(); }else{ gl_FragColor=vec4(0,0,0,0); } //gl_FragColor=vec4(rPosX/uWidth,rPosY/uHeight,rPosX/uWidth,rPosY/uHeight); } ================================================ FILE: aavt/src/main/assets/shader/convert/export_yuv.frag ================================================ precision highp float; precision highp int; varying vec2 vTextureCo; uniform sampler2D uTexture; uniform float uWidth; uniform float uHeight; float cY(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return c.r*0.2126+c.g*0.7152+c.b*0.0722; } vec4 cC(float x,float y,float dx,float dy){ vec4 c0=texture2D(uTexture,vec2(x,y)); vec4 c1=texture2D(uTexture,vec2(x+dx,y)); vec4 c2=texture2D(uTexture,vec2(x,y+dy)); vec4 c3=texture2D(uTexture,vec2(x+dx,y+dy)); return (c0+c1+c2+c3)/4.; } float cU(float x,float y,float dx,float dy){ vec4 c=cC(x,y,dx,dy); return -0.09991*c.r - 0.33609*c.g + 0.43600*c.b+0.5000; } float cV(float x,float y,float dx,float dy){ vec4 c=cC(x,y,dx,dy); return 0.61500*c.r - 0.55861*c.g - 0.05639*c.b+0.5000; } vec2 cPos(float t,float shiftx,float gy){ vec2 pos=vec2(floor(uWidth*vTextureCo.x),floor(uHeight*gy)); return vec2(mod(pos.x*shiftx,uWidth),(pos.y*shiftx+floor(pos.x*shiftx/uWidth))*t); } vec4 calculateY(){ vec2 pos=cPos(1.,4.,vTextureCo.y); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]=cY(pos.x/uWidth,textureYPos); oColor[1]=cY((pos.x+1.)/uWidth,textureYPos); oColor[2]=cY((pos.x+2.)/uWidth,textureYPos); oColor[3]=cY((pos.x+3.)/uWidth,textureYPos); return oColor; } vec4 calculateU(float gy,float dx,float dy){ vec2 pos=cPos(2.,8.,vTextureCo.y-gy); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]= cU(pos.x/uWidth,textureYPos,dx,dy); oColor[1]= cU((pos.x+2.)/uWidth,textureYPos,dx,dy); oColor[2]= cU((pos.x+4.)/uWidth,textureYPos,dx,dy); oColor[3]= cU((pos.x+6.)/uWidth,textureYPos,dx,dy); return oColor; } vec4 calculateV(float gy,float dx,float dy){ vec2 pos=cPos(2.,8.,vTextureCo.y-gy); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]=cV(pos.x/uWidth,textureYPos,dx,dy); oColor[1]=cV((pos.x+2.)/uWidth,textureYPos,dx,dy); oColor[2]=cV((pos.x+4.)/uWidth,textureYPos,dx,dy); oColor[3]=cV((pos.x+6.)/uWidth,textureYPos,dx,dy); return oColor; } vec4 calculateUV(float dx,float dy){ vec2 pos=cPos(2.,4.,vTextureCo.y-0.2500); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]= cU(pos.x/uWidth,textureYPos,dx,dy); oColor[1]= cV(pos.x/uWidth,textureYPos,dx,dy); oColor[2]= cU((pos.x+2.)/uWidth,textureYPos,dx,dy); oColor[3]= cV((pos.x+2.)/uWidth,textureYPos,dx,dy); return oColor; } vec4 calculateVU(float dx,float dy){ vec2 pos=cPos(2.,4.,vTextureCo.y-0.2500); vec4 oColor=vec4(0); float textureYPos=pos.y/uHeight; oColor[0]= cV(pos.x/uWidth,textureYPos,dx,dy); oColor[1]= cU(pos.x/uWidth,textureYPos,dx,dy); oColor[2]= cV((pos.x+2.)/uWidth,textureYPos,dx,dy); oColor[3]= cU((pos.x+2.)/uWidth,textureYPos,dx,dy); return oColor; } ================================================ FILE: aavt/src/main/assets/shader/convert/export_yuv420p.frag ================================================ precision highp float; precision highp int; varying vec2 vTextureCo; uniform sampler2D uTexture; //为了简化计算,宽高都必须为8的倍数 uniform float uWidth; // 纹理宽 uniform float uHeight; // 纹理高 //转换公式 //Y’= 0.299*R’ + 0.587*G’ + 0.114*B’ //U’= -0.147*R’ - 0.289*G’ + 0.436*B’ = 0.492*(B’- Y’) //V’= 0.615*R’ - 0.515*G’ - 0.100*B’ = 0.877*(R’- Y’) //导出原理:采样坐标只作为确定输出位置使用,通过输出纹理计算实际采样位置,进行采样和并转换, //然后将转换的结果填充到输出位置 float cY(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return c.r*0.2990+c.g*0.5870+c.b*0.1140; } float cU(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return -0.1471*c.r - 0.2889*c.g + 0.4360*c.b+0.5000; } float cV(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return 0.6150*c.r - 0.5150*c.g - 0.1000*c.b+0.5000; } vec2 cPos(float t,float shiftx,float shifty){ vec2 pos=vec2(uWidth*vTextureCo.x,uHeight*(vTextureCo-shifty)); return vec2(mod(pos.x*shiftx,uWidth),(pos.y*shiftx+floor(pos.x*shiftx/uWidth))*t); } //Y分量的计算 vec4 calculateY(){ //填充点对应图片的位置 float posX=floor(uWidth*vTextureCo.x); float posY=floor(uHeight*vTextureCo.y); //实际采样起始点对应图片的位置 float rPosX=mod(posX*4.,uWidth); float rPosY=posY*4.+floor(posX*4./uWidth); vec4 oColor=vec4(0); float textureYPos=rPosY/uHeight; oColor[0]=cY(rPosX/uWidth,textureYPos); oColor[1]=cY((rPosX+1.)/uWidth,textureYPos); oColor[2]=cY((rPosX+2.)/uWidth,textureYPos); oColor[3]=cY((rPosX+3.)/uWidth,textureYPos); return oColor; } //U分量的计算 vec4 calculateU(){ //U的采样,宽度是1:8,高度是1:2,U的位置高度偏移了1/4,一个点是4个U,采样区域是宽高位8*2 float posX=floor(uWidth*vTextureCo.x); float posY=floor(uHeight*(vTextureCo.y-0.2500)); //实际采样起始点对应图片的位置 float rPosX=mod(posX*8.,uWidth); float rPosY=posY*16.+floor(posX*8./uWidth)*2.; vec4 oColor=vec4(0); oColor[0]= cU(rPosX/uWidth,rPosY/uHeight); oColor[1]= cU((rPosX+2.)/uWidth,rPosY/uHeight); oColor[2]= cU((rPosX+4.)/uWidth,rPosY/uHeight); oColor[3]= cU((rPosX+6.)/uWidth,rPosY/uHeight); return oColor; } //V分量计算 vec4 calculateV(){ //V的采样,宽度是1:8,高度是1:2,U的位置高度偏移了1/4,一个点是4个V,采样区域是宽高位8*2 float posX=floor(uWidth*vTextureCo.x); float posY=floor(uHeight*(vTextureCo.y-0.3125)); //实际采样起始点对应图片的位置 float rPosX=mod(posX*8.,uWidth); float rPosY=posY*16.+floor(posX*8./uWidth)*2.; vec4 oColor=vec4(0); oColor[0]=cV(rPosX/uWidth,rPosY/uHeight); oColor[1]=cV((rPosX+2.)/uWidth,rPosY/uHeight); oColor[2]=cV((rPosX+4.)/uWidth,rPosY/uHeight); oColor[3]=cV((rPosX+6.)/uWidth,rPosY/uHeight); return oColor; } //UV的计算,YUV420SP用,test vec4 calculateUV(){ float posX=floor(uWidth*vTextureCo.x); float posY=floor(uHeight*(vTextureCo.y-0.2500)); //实际采样起始点对应图片的位置 float rPosX=mod(posX*4.,uWidth); float rPosY=posY*8.+floor(posX*4./uWidth)*2.; vec4 oColor=vec4(0); oColor[0]= cU((rPosX+1.)/uWidth,(rPosY+1.)/uHeight); oColor[1]= cV((rPosX+1.)/uWidth,(rPosY+1.)/uHeight); oColor[2]= cU((rPosX+3.)/uWidth,(rPosY+1.)/uHeight); oColor[3]= cV((rPosX+3.)/uWidth,(rPosY+1.)/uHeight); return oColor; } void main() { if(vTextureCo.y<0.2500){ gl_FragColor=calculateY(); }else if(vTextureCo.y<0.3125){ gl_FragColor=calculateU(); }else if(vTextureCo.y<0.3750){ gl_FragColor=calculateV(); }else{ gl_FragColor=vec4(0,0,0,0); } //gl_FragColor=vec4(rPosX/uWidth,rPosY/uHeight,rPosX/uWidth,rPosY/uHeight); } ================================================ FILE: aavt/src/main/assets/shader/convert/export_yuv420sp.frag ================================================ precision highp float; precision highp int; varying vec2 vTextureCo; uniform sampler2D uTexture; //为了简化计算,宽高都必须为8的倍数 uniform float uWidth; // 纹理宽 uniform float uHeight; // 纹理高 //转换公式 //Y’= 0.299*R’ + 0.587*G’ + 0.114*B’ //U’= -0.147*R’ - 0.289*G’ + 0.436*B’ = 0.492*(B’- Y’) //V’= 0.615*R’ - 0.515*G’ - 0.100*B’ = 0.877*(R’- Y’) //导出原理:采样坐标只作为确定输出位置使用,通过输出纹理计算实际采样位置,进行采样和并转换, //然后将转换的结果填充到输出位置 float cY(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return c.r*0.2990+c.g*0.5870+c.b*0.1140; } float cU(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return -0.1471*c.r - 0.2889*c.g + 0.4360*c.b+0.5000; } float cV(float x,float y){ vec4 c=texture2D(uTexture,vec2(x,y)); return 0.6150*c.r - 0.5150*c.g - 0.1000*c.b+0.5000; } vec2 cPos(float t,float shiftx,float shifty){ vec2 pos=vec2(uWidth*vTextureCo.x,uHeight*(vTextureCo-shifty)); return vec2(mod(pos.x*shiftx,uWidth),(pos.y*shiftx+floor(pos.x*shiftx/uWidth))*t); } //Y分量的计算 vec4 calculateY(){ //填充点对应图片的位置 float posX=floor(uWidth*vTextureCo.x); float posY=floor(uHeight*vTextureCo.y); //实际采样起始点对应图片的位置 float rPosX=mod(posX*4.,uWidth); float rPosY=posY*4.+floor(posX*4./uWidth); vec4 oColor=vec4(0); float textureYPos=rPosY/uHeight; oColor[0]=cY(rPosX/uWidth,textureYPos); oColor[1]=cY((rPosX+1.)/uWidth,textureYPos); oColor[2]=cY((rPosX+2.)/uWidth,textureYPos); oColor[3]=cY((rPosX+3.)/uWidth,textureYPos); return oColor; } gi git void main() { if(vTextureCo.y<0.2500){ gl_FragColor=calculateY(); }else if(vTextureCo.y<0.3750){ gl_FragColor=calculateUV(); }else{ gl_FragColor=vec4(0,0,0,0); } } ================================================ FILE: aavt/src/main/assets/shader/effect/fluorescence.frag ================================================ precision highp float; uniform sampler2D uTexture; uniform sampler2D uTexture2; uniform float uWidth; uniform float uHeight; varying vec2 vTextureCo; uniform vec4 uBorderColor; uniform float uStep; void main(){ vec4 baseColor=texture2D(uTexture,vTextureCo); float sobelColor=texture2D(uTexture2,vTextureCo).r; gl_FragColor=(1.-sobelColor*uStep)*baseColor+sobelColor*uBorderColor*uStep; } ================================================ FILE: aavt/src/main/assets/shader/effect/water_color.frag ================================================ precision mediump float; uniform sampler2D uTexture; uniform sampler2D uNoiseTexture; uniform float uWidth; uniform float uHeight; varying vec2 vTextureCo; vec4 valueAdd(vec2 pos,float shiftX,float shiftY,float p){ vec2 newPos=vec2((pos.x+shiftX)/uWidth,(pos.y+shiftY)/uHeight); return texture2D(uTexture,newPos)/p; } void main(){ float step=floor(uWidth/128.); vec2 xy = vec2(vTextureCo.x * uWidth, vTextureCo.y * uHeight); vec4 color=valueAdd(xy,0.,0.,4.); color+=valueAdd(xy,-step,-step,16.); color+=valueAdd(xy,-step,step,8.); color+=valueAdd(xy,-step,step,16.); color+=valueAdd(xy,step,-step,8.); color+=valueAdd(xy,step,step,8.); color+=valueAdd(xy,step,-step,16.); color+=valueAdd(xy,step,step,8.); color+=valueAdd(xy,step,step,16.); gl_FragColor = color; } ================================================ FILE: aavt/src/main/assets/shader/effect/water_color_step1.frag ================================================ precision mediump float; uniform sampler2D uTexture; uniform sampler2D uNoiseTexture; uniform float uWidth; uniform float uHeight; varying vec2 vTextureCo; vec4 quant(vec4 cl, float n) { cl.x = floor(cl.x * 255./n)*n/255.; cl.y = floor(cl.y * 255./n)*n/255.; cl.z = floor(cl.z * 255./n)*n/255.; return cl; } void main(void){ vec4 noiseColor = texture2D(uNoiseTexture, vTextureCo); vec2 newUV = vec2(vTextureCo.x + noiseColor.x / uWidth, vTextureCo.y + noiseColor.y / uHeight); vec4 fColor = texture2D(uTexture, newUV); vec4 color = quant(fColor, 255./pow(2., 4.)); //vec4 color = vec4(1., 1., .5, 1.); gl_FragColor = color; } ================================================ FILE: aavt/src/main/assets/shader/func/candy.frag ================================================ precision highp float; uniform sampler2D uTexture; uniform float uWidth; uniform float uHeight; varying vec2 vTextureCo; const float step=1.; const mat3 GX=mat3(-1.,0., +1., -2., 0., +2., -1., 0., +1.); const mat3 GY=mat3(-1., -2., -1., 0., 0., 0., +1., +2., +1.); //sobel 算子有两个滤波矩阵Gx和Gy,注意: //边缘检测时Gx为检测纵向边缘,Gy为检测横向边缘 //计算法线时Gx为计算法线的横向偏移,Gy为计算法线的纵向偏移 //Gx为[-1 0 +1 -2 0 +2 -1 0 +1] 3*3矩阵 //Gy为[-1 -2 -1 0 0 0 +1 +2 +1] 3*3矩阵 //candy 最主要的是在sobel的基础上做非最大值抑制 //非最大值抑制前,先要更具Gx和Gy得到强度梯度 float colorR(vec2 center,float shiftX,float shiftY){ return texture2D(uTexture,vec2(vTextureCo.x+shiftX/uWidth,vTextureCo.y+shiftY/uHeight)).r; } void main(){ vec2 center=vec2(vTextureCo.x*uWidth,vTextureCo.y*uHeight); float leftTop=colorR(center,-step,-step); float centerTop=colorR(center,0.,-step); float rightTop=colorR(center,step,-step); float leftCenter=colorR(center,-step,0.); float rightCenter=colorR(center,step,0.); float leftBottom=colorR(center,-step,step); float centerBottom=colorR(center,0.,step); float rightBottom=colorR(center,step,step); mat3 d=mat3(colorR(center,-step,-step),colorR(center,0.,-step),colorR(center,step,-step), colorR(center,-step,0.),colorR(center,0.,0.),colorR(center,step,0.), colorR(center,-step,step),colorR(center,0.,step),colorR(center,step,step)); //计算Gx Gy float x = d[0][0]*GX[0][0]+d[1][0]*GX[1][0]+d[2][0]*GX[2][0]+ d[0][1]*GX[0][1]+d[1][1]*GX[1][1]+d[2][1]*GX[2][1]+ d[0][2]*GX[0][2]+d[1][2]*GX[1][2]+d[2][2]*GX[2][2]; float y = d[0][0]*GY[0][0]+d[1][0]*GY[1][0]+d[2][0]*GY[2][0]+ d[0][1]*GY[0][1]+d[1][1]*GY[1][1]+d[2][1]*GY[2][1]+ d[0][2]*GY[0][2]+d[1][2]*GY[1][2]+d[2][2]*GY[2][2]; //计算强度梯度 float G=length(vec2(x,y)); float thita=atan(y,x); gl_FragColor=vec4(vec3(G),1.); } ================================================ FILE: aavt/src/main/assets/shader/func/faltung3x3.frag ================================================ precision mediump float; varying vec2 vTextureCo; uniform sampler2D uTexture; uniform mat3 uFaltung; uniform float uWidth; uniform float uHeight; vec4 getColor(float x,float y,float p){ return p*texture2D(uTexture,vec2(x/uWidth,y/uHeight)+vTextureCo); } void main() { vec4 color; color+=getColor(-1.,-1.,uFaltung[0][0]); color+=getColor(0.,-1.,uFaltung[1][0]); color+=getColor(1.,-1.,uFaltung[2][0]); color+=getColor(-1.,0.,uFaltung[0][1]); color+=getColor(0.,0.,uFaltung[1][1]); color+=getColor(1.,0.,uFaltung[2][1]); color+=getColor(-1.,1.,uFaltung[0][2]); color+=getColor(0.,1.,uFaltung[1][2]); color+=getColor(1.,1.,uFaltung[2][2]); gl_FragColor = color; } ================================================ FILE: aavt/src/main/assets/shader/func/gauss.frag ================================================ precision mediump float; varying vec2 vTextureCo; uniform sampler2D uTexture; uniform float uWidth; uniform float uHeight; vec4 getColor(float x,float y,float p){ return p*texture2D(uTexture,vec2(x/uWidth,y/uHeight)+vTextureCo); } void main() { vec4 color; color+=getColor(-2.,-2.,2.); color+=getColor(-1.,-2.,4.); color+=getColor(0.,-2.,5.); color+=getColor(1.,-2.,4.); color+=getColor(2.,-2.,2.); color+=getColor(-2.,-1.,4.); color+=getColor(-1.,-1.,9.); color+=getColor(0.,-1.,12.); color+=getColor(1.,-1.,9.); color+=getColor(2.,-1.,4.); color+=getColor(-2.,1.,4.); color+=getColor(-1.,1.,9.); color+=getColor(0.,1.,12.); color+=getColor(1.,1.,9.); color+=getColor(2.,1.,4.); color+=getColor(-2.,0.,9.); color+=getColor(-1.,0.,12.); color+=getColor(0.,0.,15.); color+=getColor(1.,0.,12.); color+=getColor(2.,0.,9.); color+=getColor(-2.,2.,2.); color+=getColor(-1.,2.,4.); color+=getColor(0.,2.,5.); color+=getColor(1.,2.,4.); color+=getColor(2.,2.,2.); gl_FragColor = color/159.; } ================================================ FILE: aavt/src/main/assets/shader/func/sobel.frag ================================================ precision highp float; uniform sampler2D uTexture; uniform float uWidth; uniform float uHeight; varying vec2 vTextureCo; const float step=1.; const mat3 GX=mat3(-1.,0., +1., -2., 0., +2., -1., 0., +1.); const mat3 GY=mat3(-1., -2., -1., 0., 0., 0., +1., +2., +1.); //sobel 算子有两个滤波矩阵Gx和Gy,注意: //边缘检测时Gx为检测纵向边缘,Gy为检测横向边缘 //计算法线时Gx为计算法线的横向偏移,Gy为计算法线的纵向偏移 //Gx为[-1 0 +1 -2 0 +2 -1 0 +1] 3*3矩阵 //Gy为[-1 -2 -1 0 0 0 +1 +2 +1] 3*3矩阵 float colorR(vec2 center,float shiftX,float shiftY){ return texture2D(uTexture,vec2(vTextureCo.x+shiftX/uWidth,vTextureCo.y+shiftY/uHeight)).r; } void main(){ vec2 center=vec2(vTextureCo.x*uWidth,vTextureCo.y*uHeight); float leftTop=colorR(center,-step,-step); float centerTop=colorR(center,0.,-step); float rightTop=colorR(center,step,-step); float leftCenter=colorR(center,-step,0.); float rightCenter=colorR(center,step,0.); float leftBottom=colorR(center,-step,step); float centerBottom=colorR(center,0.,step); float rightBottom=colorR(center,step,step); mat3 d=mat3(colorR(center,-step,-step),colorR(center,0.,-step),colorR(center,step,-step), colorR(center,-step,0.),colorR(center,0.,0.),colorR(center,step,0.), colorR(center,-step,step),colorR(center,0.,step),colorR(center,step,step)); float x = d[0][0]*GX[0][0]+d[1][0]*GX[1][0]+d[2][0]*GX[2][0]+ d[0][1]*GX[0][1]+d[1][1]*GX[1][1]+d[2][1]*GX[2][1]+ d[0][2]*GX[0][2]+d[1][2]*GX[1][2]+d[2][2]*GX[2][2]; float y = d[0][0]*GY[0][0]+d[1][0]*GY[1][0]+d[2][0]*GY[2][0]+ d[0][1]*GY[0][1]+d[1][1]*GY[1][1]+d[2][1]*GY[2][1]+ d[0][2]*GY[0][2]+d[1][2]*GY[1][2]+d[2][2]*GY[2][2]; gl_FragColor=vec4(vec3(length(vec2(x,y))),1.); } ================================================ FILE: aavt/src/main/assets/shader/func/sobel2.frag ================================================ precision highp float; uniform sampler2D uTexture; uniform float uWidth; uniform float uHeight; varying vec2 vTextureCo; const float step=1.; const mat3 GX=mat3(-1.,0., +1., -1., 0., +1., -1., 0., +1.); const mat3 GY=mat3(-1., -1., -1., 0., 0., 0., +1., +1., +1.); //sobel 算子有两个滤波矩阵Gx和Gy,注意: //边缘检测时Gx为检测纵向边缘,Gy为检测横向边缘 //计算法线时Gx为计算法线的横向偏移,Gy为计算法线的纵向偏移 //Gx为[-1 0 +1 -2 0 +2 -1 0 +1] 3*3矩阵 //Gy为[-1 -2 -1 0 0 0 +1 +2 +1] 3*3矩阵 float colorR(vec2 center,float shiftX,float shiftY){ return texture2D(uTexture,vec2(vTextureCo.x+shiftX/uWidth,vTextureCo.y+shiftY/uHeight)).r; } void main(){ vec2 center=vec2(vTextureCo.x*uWidth,vTextureCo.y*uHeight); float leftTop=colorR(center,-step,-step); float centerTop=colorR(center,0.,-step); float rightTop=colorR(center,step,-step); float leftCenter=colorR(center,-step,0.); float rightCenter=colorR(center,step,0.); float leftBottom=colorR(center,-step,step); float centerBottom=colorR(center,0.,step); float rightBottom=colorR(center,step,step); mat3 d=mat3(colorR(center,-step,-step),colorR(center,0.,-step),colorR(center,step,-step), colorR(center,-step,0.),colorR(center,0.,0.),colorR(center,step,0.), colorR(center,-step,step),colorR(center,0.,step),colorR(center,step,step)); float x = d[0][0]*GX[0][0]+d[1][0]*GX[1][0]+d[2][0]*GX[2][0]+ d[0][1]*GX[0][1]+d[1][1]*GX[1][1]+d[2][1]*GX[2][1]+ d[0][2]*GX[0][2]+d[1][2]*GX[1][2]+d[2][2]*GX[2][2]; float y = d[0][0]*GY[0][0]+d[1][0]*GY[1][0]+d[2][0]*GY[2][0]+ d[0][1]*GY[0][1]+d[1][1]*GY[1][1]+d[2][1]*GY[2][1]+ d[0][2]*GY[0][2]+d[1][2]*GY[1][2]+d[2][2]*GY[2][2]; gl_FragColor=vec4(vec3(1.)-vec3(length(vec2(x,y))),1.); } ================================================ FILE: aavt/src/main/assets/shader/oes.frag ================================================ #extension GL_OES_EGL_image_external : require precision mediump float; varying vec2 vTextureCo; uniform samplerExternalOES uTexture; void main() { gl_FragColor = texture2D( uTexture, vTextureCo); } ================================================ FILE: aavt/src/main/assets/shader/oes.vert ================================================ attribute vec4 aVertexCo; attribute vec2 aTextureCo; uniform mat4 uVertexMatrix; uniform mat4 uTextureMatrix; varying vec2 vTextureCo; void main(){ gl_Position = uVertexMatrix*aVertexCo; vTextureCo = (uTextureMatrix*vec4(aTextureCo,0,1)).xy; } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/av/CameraRecorder2.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.av; import android.content.Context; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.media.video.Camera2Provider; import com.wuwang.aavt.media.video.ITextureProvider; import com.wuwang.aavt.media.SurfaceEncoder; import com.wuwang.aavt.media.SoundRecorder; import com.wuwang.aavt.media.SurfaceShower; import com.wuwang.aavt.media.VideoSurfaceProcessor; import com.wuwang.aavt.media.av.AvException; import com.wuwang.aavt.media.hard.IHardStore; import com.wuwang.aavt.media.hard.StrengthenMp4MuxStore; /** * CameraRecorder2 相机预览及录制工具类 * * @author wuwang * @version v1.0 2017:10:26 18:35 */ public class CameraRecorder2 { private VideoSurfaceProcessor mTextureProcessor; private ITextureProvider mCameraProvider; private SurfaceShower mShower; private SurfaceEncoder mSurfaceStore; private IHardStore mMuxer; private SoundRecorder mSoundRecord; public CameraRecorder2(Context context){ //用于视频混流和存储 mMuxer=new StrengthenMp4MuxStore(true); //用于预览图像 mShower=new SurfaceShower(); mShower.setOutputSize(720,1280); //用于编码图像 mSurfaceStore=new SurfaceEncoder(); mSurfaceStore.setStore(mMuxer); //用于音频 mSoundRecord=new SoundRecorder(mMuxer); //用于处理视频图像 mTextureProcessor=new VideoSurfaceProcessor(); mTextureProcessor.setTextureProvider(mCameraProvider=new Camera2Provider(context)); mTextureProcessor.addObserver(mShower); mTextureProcessor.addObserver(mSurfaceStore); } public void setRenderer(Renderer renderer){ mTextureProcessor.setRenderer(renderer); } /** * 设置预览对象,必须是{@link android.view.Surface}、{@link android.graphics.SurfaceTexture}或者 * {@link android.view.TextureView} * @param surface 预览对象 */ public void setSurface(Object surface){ mShower.setSurface(surface); } /** * 设置录制的输出路径 * @param path 输出路径 */ public void setOutputPath(String path){ mMuxer.setOutputPath(path); } /** * 设置预览大小 * @param width 预览区域宽度 * @param height 预览区域高度 */ public void setPreviewSize(int width,int height){ mShower.setOutputSize(width,height); } /** * 打开数据源 */ public void open(){ mTextureProcessor.start(); } /** * 关闭数据源 */ public void close(){ mTextureProcessor.stop(); stopRecord(); } /** * 打开预览 */ public void startPreview(){ mShower.open(); } /** * 关闭预览 */ public void stopPreview(){ mShower.close(); } /** * 开始录制 */ public void startRecord(){ mSurfaceStore.open(); mSoundRecord.start(); } /** * 关闭录制 */ public void stopRecord(){ mSoundRecord.stop(); mSurfaceStore.close(); mMuxer.close(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/av/Mp4Processor.java ================================================ package com.wuwang.aavt.av; import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaExtractor; import android.media.MediaFormat; import android.media.MediaMetadataRetriever; import android.media.MediaMuxer; import android.os.Build; import android.os.Bundle; import android.view.Surface; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.egl.EGLConfigAttrs; import com.wuwang.aavt.egl.EGLContextAttrs; import com.wuwang.aavt.egl.EglHelper; import com.wuwang.aavt.log.AvLog; import com.wuwang.aavt.media.WrapRenderer; import com.wuwang.aavt.utils.GpuUtils; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.Semaphore; /** * MP4处理工具,暂时只用于处理图像。 * 4.4的手机不支持video/mp4v-es格式的视频流,MediaMuxer混合无法stop,5.0以上可以 * */ @Deprecated @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class Mp4Processor { private final int TIME_OUT=1000; private String mInputPath; //输入路径 private String mOutputPath; //输出路径 private MediaCodec mVideoDecoder; //视频解码器 private MediaCodec mVideoEncoder; //视频编码器 //private MediaCodec mAudioDecoder; //音频解码器 //private MediaCodec mAudioEncoder; //音频编码器 private MediaExtractor mExtractor; //音视频分离器 private MediaMuxer mMuxer; //音视频混合器 private EglHelper mEGLHelper; //GL环境创建的帮助类 private MediaCodec.BufferInfo mVideoDecoderBufferInfo; //用于存储当前帧的视频解码信息 //private MediaCodec.BufferInfo mAudioDecoderBufferInfo; //用于存储当前帧的音频解码信息 private MediaCodec.BufferInfo mVideoEncoderBufferInfo; //用于存储当前帧的视频编码信息 private MediaCodec.BufferInfo mAudioEncoderBufferInfo; //用于纯粹当前帧的音频编码信息 private int mAudioEncoderTrack=-1; //解码音轨 private int mVideoEncoderTrack=-1; //解码视轨 private int mAudioDecoderTrack=-1; //编码音轨 private int mVideoDecoderTrack=-1; //编码视轨 //private String mAudioMime; //private String mVideoMime; private int mInputVideoWidth=0; //输入视频的宽度 private int mInputVideoHeight=0; //输入视频的高度 private int mOutputVideoWidth=0; //输出视频的宽度 private int mOutputVideoHeight=0; //输出视频的高度 private int mVideoTextureId; //原始视频图像的纹理 private SurfaceTexture mVideoSurfaceTexture; //用于接收原始视频的解码的图像流 private boolean isRenderToWindowSurface; //是否渲染到用户设置的WindowBuffer上,用于测试 private Surface mOutputSurface; //视频输出的Surface private Thread mDecodeThread; private Thread mGLThread; private boolean mCodecFlag=false; private boolean isVideoExtractorEnd=false; private boolean isAudioExtractorEnd=false; private boolean isStarted=false; private WrapRenderer mRenderer; private boolean mGLThreadFlag=false; private Semaphore mSem; private Semaphore mDecodeSem; private final Object Extractor_LOCK=new Object(); private final Object MUX_LOCK=new Object(); private final Object PROCESS_LOCK=new Object(); private OnProgressListener mProgressListener; private boolean isUserWantToStop=false; private long mVideoStopTimeStamp=0; //视频停止时的时间戳,用于外部主动停止处理时,音频截取 private long mTotalVideoTime=0; //视频的总时长 public Mp4Processor(){ mEGLHelper=new EglHelper(); mVideoDecoderBufferInfo=new MediaCodec.BufferInfo(); //mAudioDecoderBufferInfo=new MediaCodec.BufferInfo(); mVideoEncoderBufferInfo=new MediaCodec.BufferInfo(); mAudioEncoderBufferInfo=new MediaCodec.BufferInfo(); } /** * 设置用于处理的MP4文件 * @param path 文件路径 */ public void setInputPath(String path){ this.mInputPath=path; } /** * 设置处理后的mp4存储的位置 * @param path 文件路径 */ public void setOutputPath(String path){ this.mOutputPath=path; } /** * 设置直接渲染到指定的Surface上,测试用 * @param surface 渲染的位置 */ public void setOutputSurface(Surface surface){ this.mOutputSurface=surface; this.isRenderToWindowSurface=surface!=null; } /** * 设置用户处理接口 * @param renderer 处理接口 */ public void setRenderer(Renderer renderer){ mRenderer=new WrapRenderer(renderer); } public int getVideoSurfaceTextureId(){ return mVideoTextureId; } public SurfaceTexture getVideoSurfaceTexture(){ return mVideoSurfaceTexture; } /** * 设置输出Mp4的图像大小,默认为输出大小 * @param width 视频图像宽度 * @param height 视频图像高度 */ public void setOutputSize(int width,int height){ this.mOutputVideoWidth=width; this.mOutputVideoHeight=height; } public void setOnCompleteListener(OnProgressListener listener){ this.mProgressListener=listener; } private boolean prepare() throws IOException { //todo 获取视频旋转信息,并做出相应处理 synchronized (PROCESS_LOCK){ int videoRotation=0; MediaMetadataRetriever mMetRet=new MediaMetadataRetriever(); mMetRet.setDataSource(mInputPath); mExtractor=new MediaExtractor(); mExtractor.setDataSource(mInputPath); int count=mExtractor.getTrackCount(); //解析Mp4 for (int i=0;i"+mExtractor.getTrackFormat(i)); if(mime.startsWith("audio")){ mAudioDecoderTrack=i; //todo 暂时不对音频处理,后续需要对音频处理时再修改这个 /*mAudioDecoder=MediaCodec.createDecoderByType(mime); mAudioDecoder.configure(format,null,null,0); if(!isRenderToWindowSurface){ Log.e("wuwang", format.toString()); MediaFormat audioFormat=MediaFormat.createAudioFormat(mime, format.getInteger(MediaFormat.KEY_SAMPLE_RATE), format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, format.getInteger(MediaFormat.KEY_AAC_PROFILE)); audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, Integer.valueOf(mMetRet.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE))); mAudioEncoder=MediaCodec.createEncoderByType(mime); mAudioEncoder.configure(audioFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); }*/ }else if(mime.startsWith("video")){ //5.0以下,不能解析mp4v-es //todo 5.0以上也可能存在问题,目前还不知道原因 // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP&&mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { // return false; // } mVideoDecoderTrack=i; MediaFormat originFormat=mExtractor.getTrackFormat(mVideoDecoderTrack); int frameRate=originFormat.getInteger(MediaFormat.KEY_FRAME_RATE); frameRate=frameRate==0?24:frameRate; mTotalVideoTime=Long.valueOf(mMetRet.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); String rotation=mMetRet.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); if(rotation!=null){ videoRotation=Integer.valueOf(rotation); } if(videoRotation==90||videoRotation==270){ mInputVideoHeight=format.getInteger(MediaFormat.KEY_WIDTH); mInputVideoWidth=format.getInteger(MediaFormat.KEY_HEIGHT); }else{ mInputVideoWidth=format.getInteger(MediaFormat.KEY_WIDTH); mInputVideoHeight=format.getInteger(MediaFormat.KEY_HEIGHT); } AvLog.d("createDecoder"); mVideoDecoder= MediaCodec.createDecoderByType(mime); AvLog.d("createDecoder end"); mVideoTextureId= GpuUtils.createTextureID(true); mVideoSurfaceTexture=new SurfaceTexture(mVideoTextureId); mVideoDecoder.configure(format,new Surface(mVideoSurfaceTexture),null,0); if(!isRenderToWindowSurface){ if(mOutputVideoWidth==0||mOutputVideoHeight==0){ mOutputVideoWidth=mInputVideoWidth; mOutputVideoHeight=mInputVideoHeight; } MediaFormat videoFormat= MediaFormat.createVideoFormat(/*mime*/"video/avc",mOutputVideoWidth,mOutputVideoHeight); videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); videoFormat.setInteger(MediaFormat.KEY_BIT_RATE,mOutputVideoHeight*mOutputVideoWidth*5); videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, frameRate*10); mVideoEncoder= MediaCodec.createEncoderByType(/*mime*/"video/avc"); mVideoEncoder.configure(videoFormat,null,null, MediaCodec.CONFIGURE_FLAG_ENCODE); mOutputSurface=mVideoEncoder.createInputSurface(); Bundle bundle=new Bundle(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { bundle.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE,mOutputVideoHeight*mOutputVideoWidth*5); mVideoEncoder.setParameters(bundle); } } } } if(!isRenderToWindowSurface){ //如果用户没有设置渲染到指定Surface,就需要导出视频,暂时不对音频做处理 mMuxer=new MediaMuxer(mOutputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); // mMuxer.setOrientationHint(videoRotation); AvLog.d("video rotation:"+videoRotation); //如果mp4中有音轨 if(mAudioDecoderTrack>=0){ MediaFormat format=mExtractor.getTrackFormat(mAudioDecoderTrack); AvLog.d("audio track-->"+format.toString()); mAudioEncoderTrack=mMuxer.addTrack(format); } } } return true; } public boolean start() throws IOException { synchronized (PROCESS_LOCK){ if(!isStarted){ if(!prepare()){ AvLog.d("prepare failed"); return false; } isUserWantToStop=false; isVideoExtractorEnd=false; isVideoExtractorEnd=false; mGLThreadFlag=true; mVideoDecoder.start(); //mAudioDecoder.start(); if(!isRenderToWindowSurface){ //mAudioEncoder.start(); mVideoEncoder.start(); } mGLThread=new Thread(new Runnable() { @Override public void run() { glRunnable(); } }); mGLThread.start(); mCodecFlag=true; mDecodeThread=new Thread(new Runnable() { @Override public void run() { //视频处理 if(mVideoDecoderTrack>=0){ AvLog.d("videoDecodeStep start"); codecNum=0; while (mCodecFlag&&!videoDecodeStep()){}; AvLog.d("videoDecodeStep end--FrameNum="+codecNum); mGLThreadFlag=false; try { mSem.release(); mGLThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } //将原视频中的音频复制到新视频中 if(mAudioDecoderTrack>=0&&mVideoEncoderTrack>=0){ ByteBuffer buffer= ByteBuffer.allocate(1024*32); while (mCodecFlag&&!audioDecodeStep(buffer)){}; buffer.clear(); } AvLog.d("codec thread_finish"); mCodecFlag=false; avStop(); //todo 判断是用户取消了的情况 if(mProgressListener!=null){ mProgressListener.onComplete(mOutputPath); } } }); mDecodeThread.start(); isStarted=true; } } return true; } /** * 等待解码线程执行完毕,异步线程同步等待 */ public void waitProcessFinish() throws InterruptedException { if(mDecodeThread!=null&&mDecodeThread.isAlive()){ mDecodeThread.join(); } } private boolean audioDecodeStep(ByteBuffer buffer){ boolean isTimeEnd=false; buffer.clear(); synchronized (Extractor_LOCK){ mExtractor.selectTrack(mAudioDecoderTrack); int length=mExtractor.readSampleData(buffer,0); if(length!=-1){ int flags=mExtractor.getSampleFlags(); mAudioEncoderBufferInfo.size=length; mAudioEncoderBufferInfo.flags=flags; mAudioEncoderBufferInfo.presentationTimeUs=mExtractor.getSampleTime(); mAudioEncoderBufferInfo.offset=0; AvLog.d("audio sampleTime="+mAudioEncoderBufferInfo.presentationTimeUs); isTimeEnd=mExtractor.getSampleTime()>=mVideoStopTimeStamp; mMuxer.writeSampleData(mAudioEncoderTrack,buffer,mAudioEncoderBufferInfo); } isAudioExtractorEnd=!mExtractor.advance(); } return isAudioExtractorEnd||isTimeEnd; } //视频解码到SurfaceTexture上,以供后续处理。返回值为是否是最后一帧视频 private int codecNum=0; private boolean videoDecodeStep(){ int mInputIndex=mVideoDecoder.dequeueInputBuffer(TIME_OUT); if(mInputIndex>=0){ ByteBuffer buffer=getInputBuffer(mVideoDecoder,mInputIndex); buffer.clear(); synchronized (Extractor_LOCK) { mExtractor.selectTrack(mVideoDecoderTrack); int ret = mExtractor.readSampleData(buffer, 0); if (ret != -1) { mVideoStopTimeStamp=mExtractor.getSampleTime(); AvLog.d("mVideoStopTimeStamp:"+mVideoStopTimeStamp); mVideoDecoder.queueInputBuffer(mInputIndex, 0, ret, mVideoStopTimeStamp, mExtractor.getSampleFlags()); } isVideoExtractorEnd = !mExtractor.advance(); } } while (true){ int mOutputIndex=mVideoDecoder.dequeueOutputBuffer(mVideoDecoderBufferInfo,TIME_OUT); if(mOutputIndex>=0){ try { AvLog.d(" mDecodeSem.acquire "); if(!isUserWantToStop){ mDecodeSem.acquire(); } AvLog.d(" mDecodeSem.acquire end "); } catch (InterruptedException e) { e.printStackTrace(); } codecNum++; mVideoDecoder.releaseOutputBuffer(mOutputIndex,true); mSem.release(); }else if(mOutputIndex== MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ //MediaFormat format=mVideoDecoder.getOutputFormat(); }else if(mOutputIndex== MediaCodec.INFO_TRY_AGAIN_LATER){ break; } } return isVideoExtractorEnd||isUserWantToStop; } private boolean videoEncodeStep(boolean isEnd){ if(isEnd){ mVideoEncoder.signalEndOfInputStream(); } while (true){ int mOutputIndex=mVideoEncoder.dequeueOutputBuffer(mVideoEncoderBufferInfo,TIME_OUT); AvLog.d("videoEncodeStep-------------------mOutputIndex="+mOutputIndex+"/"+mVideoEncoderBufferInfo.presentationTimeUs); if(mOutputIndex>=0){ ByteBuffer buffer=getOutputBuffer(mVideoEncoder,mOutputIndex); if(mVideoEncoderBufferInfo.size>0){ mMuxer.writeSampleData(mVideoEncoderTrack,buffer,mVideoEncoderBufferInfo); } mVideoEncoder.releaseOutputBuffer(mOutputIndex,false); }else if(mOutputIndex== MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ MediaFormat format=mVideoEncoder.getOutputFormat(); AvLog.d("video format -->"+format.toString()); mVideoEncoderTrack=mMuxer.addTrack(format); mMuxer.start(); synchronized (MUX_LOCK){ MUX_LOCK.notifyAll(); } }else if(mOutputIndex== MediaCodec.INFO_TRY_AGAIN_LATER){ break; } } return false; } private void glRunnable(){ mSem=new Semaphore(0); mDecodeSem=new Semaphore(1); boolean ret=mEGLHelper.createGLESWithSurface(new EGLConfigAttrs(),new EGLContextAttrs(),mOutputSurface); if(!ret){ return; } if(mRenderer==null){ mRenderer=new WrapRenderer(null); } mRenderer.create(); mRenderer.sizeChanged(mOutputVideoWidth,mOutputVideoHeight); int frameNum=0; while (mGLThreadFlag){ try { AvLog.d(" mSem.acquire "); mSem.acquire(); AvLog.d(" mSem.acquire end"); } catch (InterruptedException e) { e.printStackTrace(); } if(mGLThreadFlag){ mVideoSurfaceTexture.updateTexImage(); //todo 带有rotation的视频,还需要处理 mVideoSurfaceTexture.getTransformMatrix(mRenderer.getTextureMatrix()); mRenderer.draw(mVideoTextureId); mEGLHelper.setPresentationTime(mEGLHelper.getDefaultSurface(),mVideoDecoderBufferInfo.presentationTimeUs*1000); if(!isRenderToWindowSurface){ frameNum++; videoEncodeStep(false); } mEGLHelper.swapBuffers(mEGLHelper.getDefaultSurface()); } if(mProgressListener!=null){ mProgressListener.onProgress(getTotalVideoTime()*1000L,mVideoDecoderBufferInfo.presentationTimeUs); } mDecodeSem.release(); } AvLog.d("Encode Frame num-----:"+frameNum); if(!isRenderToWindowSurface){ videoEncodeStep(true); } mRenderer.destroy(); mEGLHelper.destroyGLES(mEGLHelper.getDefaultSurface(),mEGLHelper.getDefaultContext()); } public long getPresentationTime(){ return mVideoDecoderBufferInfo.presentationTimeUs*1000; } public long getTotalVideoTime(){ return mTotalVideoTime; } private void avStop(){ if(isStarted){ if(mVideoDecoder!=null){ mVideoDecoder.stop(); mVideoDecoder.release(); mVideoDecoder=null; } if(!isRenderToWindowSurface&&mVideoEncoder!=null){ mVideoEncoder.stop(); mVideoEncoder.release(); mVideoEncoder=null; } if(!isRenderToWindowSurface){ if(mMuxer!=null&&mVideoEncoderTrack>=0){ try { mMuxer.stop(); }catch (IllegalStateException e){ e.printStackTrace(); } } if(mMuxer!=null){ try { mMuxer.release(); }catch (IllegalStateException e){ e.printStackTrace(); } mMuxer=null; } } if(mExtractor!=null){ mExtractor.release(); } isStarted=false; mVideoEncoderTrack=-1; mVideoDecoderTrack=-1; mAudioEncoderTrack=-1; mAudioDecoderTrack=-1; } } public boolean stop() throws InterruptedException { synchronized (PROCESS_LOCK){ if(isStarted){ if(mCodecFlag){ mDecodeSem.release(); isUserWantToStop=true; if(mDecodeThread!=null&&mDecodeThread.isAlive()){ AvLog.d("try to stop decode thread"); mDecodeThread.join(); AvLog.d("decode thread stoped"); } isUserWantToStop=false; } } } return true; } public boolean release() throws InterruptedException { synchronized (PROCESS_LOCK){ if(mCodecFlag){ stop(); } } return true; } private ByteBuffer getInputBuffer(MediaCodec codec, int index){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return codec.getInputBuffer(index); }else{ return codec.getInputBuffers()[index]; } } private ByteBuffer getOutputBuffer(MediaCodec codec, int index){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return codec.getOutputBuffer(index); }else{ return codec.getOutputBuffers()[index]; } } public interface OnProgressListener{ void onProgress(long max, long current); void onComplete(String path); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/av/Mp4Processor2.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.av; import com.wuwang.aavt.media.video.Mp4Provider; import com.wuwang.aavt.media.SurfaceEncoder; import com.wuwang.aavt.media.SurfaceShower; import com.wuwang.aavt.media.VideoSurfaceProcessor; import com.wuwang.aavt.media.hard.IHardStore; import com.wuwang.aavt.media.hard.StrengthenMp4MuxStore; /** * Mp4Processor2 用于处理Mp4文件 * * @author wuwang * @version v1.0 2017:10:26 18:48 */ public class Mp4Processor2 { private VideoSurfaceProcessor mTextureProcessor; private Mp4Provider mMp4Provider; private SurfaceShower mShower; private SurfaceEncoder mSurfaceStore; private IHardStore mMuxer; public Mp4Processor2(){ //用于视频混流和存储 mMuxer=new StrengthenMp4MuxStore(true); //用于预览图像 mShower=new SurfaceShower(); mShower.setOutputSize(720,1280); //用于编码图像 mSurfaceStore=new SurfaceEncoder(); mSurfaceStore.setStore(mMuxer); //用于音频 // mSoundRecord=new SoundRecorder(mMuxer); mMp4Provider=new Mp4Provider(); mMp4Provider.setStore(mMuxer); //用于处理视频图像 mTextureProcessor=new VideoSurfaceProcessor(); mTextureProcessor.setTextureProvider(mMp4Provider); mTextureProcessor.addObserver(mShower); mTextureProcessor.addObserver(mSurfaceStore); } public void setSurface(Object surface){ mShower.setSurface(surface); } public void setInputPath(String path){ mMp4Provider.setInputPath(path); } public void setOutputPath(String path){ mMuxer.setOutputPath(path); } public void setPreviewSize(int width,int height){ mShower.setOutputSize(width,height); } public void open(){ mTextureProcessor.start(); } public void close(){ mTextureProcessor.stop(); } public void startPreview(){ mShower.open(); } public void stopPreview(){ mShower.close(); } public void startRecord(){ mSurfaceStore.open(); // mSoundRecord.start(); } public void stopRecord(){ mSurfaceStore.close(); // mSoundRecord.stop(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/av/SurfaceRecorder.java ================================================ package com.wuwang.aavt.av; import android.content.Context; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.media.video.Camera2Provider; import com.wuwang.aavt.media.video.ITextureProvider; import com.wuwang.aavt.media.SoundRecorder; import com.wuwang.aavt.media.SurfaceEncoder; import com.wuwang.aavt.media.SurfaceShower; import com.wuwang.aavt.media.VideoSurfaceProcessor; import com.wuwang.aavt.media.av.AvException; import com.wuwang.aavt.media.hard.IHardStore; import com.wuwang.aavt.media.hard.StrengthenMp4MuxStore; /** * @author wuwang * @version 1.00 , 2018/11/14 */ public class SurfaceRecorder { private Context context; private VideoSurfaceProcessor mTextureProcessor; private SurfaceShower mShower; private SurfaceEncoder mSurfaceStore; private IHardStore mMuxer; private ITextureProvider mProvider; private SoundRecorder mSoundRecord; private int mPreviewWidth = 720; private int mPreviewHeight = 1280; private int mRecordWidth = 720; private int mRecordHeight = 1280; private String path; private Renderer mRender; private Object mShowSurface; public SurfaceRecorder(Context context){ this.context = context; } public void setTextureProvider(ITextureProvider provider){ this.mProvider = provider; } public void setPreviewSize(int width,int height){ this.mPreviewWidth = width; this.mPreviewHeight = height; } public void setRecordSize(int width,int height){ this.mRecordWidth = width; this.mRecordHeight = height; } public void setSurface(Object surface){ this.mShowSurface = surface; } /** * 设置录制的输出路径 * @param path 输出路径 */ public void setOutputPath(String path){ this.path = path; } public void setRenderer(Renderer renderer){ this.mRender = renderer; } public void open(){ if(mMuxer == null){ mMuxer=new StrengthenMp4MuxStore(true); mMuxer.setOutputPath(path); } if(mProvider == null){ mProvider=new Camera2Provider(context); } if(mShower == null){ //用于预览图像 mShower=new SurfaceShower(); mShower.setOutputSize(mPreviewWidth,mPreviewHeight); mShower.setSurface(mShowSurface); } if(mSurfaceStore == null){ //用于编码图像 mSurfaceStore=new SurfaceEncoder(); mSurfaceStore.setOutputSize(mRecordWidth,mRecordHeight); mSurfaceStore.setStore(mMuxer); } if(mSoundRecord == null){ //用于音频 mSoundRecord=new SoundRecorder(mMuxer); } if(mTextureProcessor == null){ //用于处理视频图像 mTextureProcessor=new VideoSurfaceProcessor(); mTextureProcessor.setTextureProvider(mProvider); mTextureProcessor.addObserver(mShower); mTextureProcessor.addObserver(mSurfaceStore); mTextureProcessor.setRenderer(mRender); } mTextureProcessor.start(); } public void close(){ if(mTextureProcessor != null){ mTextureProcessor.stop(); } } public void startPreview(){ if(mShower != null){ mShower.open(); } } public void stopPreview(){ if(mShower != null){ mShower.close(); } } /** * 开始录制 */ public void startRecord(){ if(mSurfaceStore != null && mSoundRecord !=null){ mSurfaceStore.open(); mSoundRecord.start(); } } /** * 关闭录制 */ public void stopRecord(){ if(mSoundRecord != null && mSurfaceStore != null && mMuxer != null){ mSoundRecord.stop(); mSurfaceStore.close(); mMuxer.close(); } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/av/VideoCapture.java ================================================ package com.wuwang.aavt.av; import android.annotation.SuppressLint; import android.content.Context; import android.util.SparseArray; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.media.video.CameraProvider; import com.wuwang.aavt.media.video.ITextureProvider; import com.wuwang.aavt.media.video.Mp4Provider; import com.wuwang.aavt.media.SurfaceEncoder; import com.wuwang.aavt.media.SurfaceShower; import com.wuwang.aavt.media.VideoSurfaceProcessor; import com.wuwang.aavt.media.hard.IHardStore; import com.wuwang.aavt.media.hard.StrengthenMp4MuxStore; /** * @author wuwang * @version 1.00 , 2019/03/13 */ public class VideoCapture { public static final int KEY_CAMERA_MIN_WIDTH = 1; public static final int KEY_CAMERA_RATE = 2; public static final int KEY_OUTPUT_WIDTH = 3; public static final int KEY_OUTPUT_HEIGHT = 4; public static final int KEY_OUTPUT_PATH = 5; public static final int KEY_PREVIEW_WIDTH = 6; public static final int KEY_PREVIEW_HEIGHT = 7; private Context context; private ITextureProvider provider; private SurfaceShower shower; private SurfaceEncoder encoder; private VideoSurfaceProcessor processor; private IHardStore muxer; private SparseArray propFloat; private SparseArray propStr; private Renderer renderer; private Object showSurface; @SuppressLint("SdCardPath") private static final String DEFAULT_OUTPUT_PATH = "/mnt/sdcard/test.mp4"; public VideoCapture(Context context){ this.context = context; propFloat = new SparseArray<>(); propStr = new SparseArray<>(); } public void setRenderer(Renderer renderer){ this.renderer = renderer; if(processor != null){ processor.setRenderer(renderer); } } public void setProperty(int key,float value){ propFloat.put(key,value); } public void setProperty(int key,String value){ propStr.put(key,value); } @SuppressLint("SdCardPath") public void open(int id){ provider = new CameraProvider(); ((CameraProvider) provider).setDefaultCamera(id); ((CameraProvider) provider).setCameraSize((int)(float)(propFloat.get(KEY_CAMERA_MIN_WIDTH,720f)),propFloat.get(KEY_CAMERA_RATE,1.7f)); shower = new SurfaceShower(); shower.setOutputSize((int)(float)(propFloat.get(KEY_PREVIEW_WIDTH,720.0f)), (int)(float)(propFloat.get(KEY_PREVIEW_HEIGHT,1280.0f))); muxer = new StrengthenMp4MuxStore(false); muxer.setOutputPath(propStr.get(KEY_OUTPUT_PATH,DEFAULT_OUTPUT_PATH)); encoder = new SurfaceEncoder(); encoder.setStore(muxer); encoder.setOutputSize((int)(float)(propFloat.get(KEY_OUTPUT_WIDTH,368.0f)), (int)(float)(propFloat.get(KEY_OUTPUT_HEIGHT,640.f))); processor = new VideoSurfaceProcessor(); processor.setTextureProvider(provider); processor.addObserver(shower); processor.addObserver(encoder); setRenderer(renderer); processor.start(); } public void open(String path){ muxer = new StrengthenMp4MuxStore(false); muxer.setOutputPath(propStr.get(KEY_OUTPUT_PATH,DEFAULT_OUTPUT_PATH)); provider = new Mp4Provider(); ((Mp4Provider) provider).setInputPath(path); ((Mp4Provider) provider).setStore(muxer); shower = new SurfaceShower(); shower.setOutputSize((int)(float)(propFloat.get(KEY_OUTPUT_WIDTH,720.0f)), (int)(float)(propFloat.get(KEY_OUTPUT_HEIGHT,1280.0f))); encoder = new SurfaceEncoder(); encoder.setStore(muxer); encoder.setOutputSize((int)(float)(propFloat.get(KEY_OUTPUT_WIDTH,368.0f)), (int)(float)(propFloat.get(KEY_OUTPUT_HEIGHT,640.f))); processor = new VideoSurfaceProcessor(); processor.setTextureProvider(provider); processor.addObserver(shower); processor.addObserver(encoder); setRenderer(renderer); processor.start(); } public void setPreviewSurface(Object surface){ this.showSurface = surface; if(shower != null){ shower.setSurface(surface); } } public void startPreview(){ setPreviewSurface(showSurface); shower.open(); } public void stopPreview(){ shower.close(); } public void startRecord(){ encoder.open(); } public void stopRecord(){ encoder.close(); muxer.close(); } public void close(){ processor.stop(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/core/IObservable.java ================================================ package com.wuwang.aavt.core; /** * Created by wuwang on 2017/10/20. */ public interface IObservable { void addObserver(IObserver observer); void notify(Type type); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/core/IObserver.java ================================================ package com.wuwang.aavt.core; /** * Created by wuwang on 2017/10/20. */ public interface IObserver { void onCall(Type type); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/core/Observable.java ================================================ package com.wuwang.aavt.core; import java.util.ArrayList; import java.util.Observer; /* * Created by Wuwang on 2017/10/23 */ public class Observable implements IObservable { private ArrayList> temp; @Override public void addObserver(IObserver observer) { if(temp==null){ temp=new ArrayList<>(); } temp.add(observer); } public void clear(){ if(temp!=null){ temp.clear(); temp=null; } } @Override public void notify(Type type) { for (IObserver t:temp){ t.onCall(type); } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/core/Renderer.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.core; /** * Renderer 渲染接口,渲染的四个接口应该在同一个GL线程中调用 * * @author wuwang * @version v1.0 2017:10:31 11:40 */ public interface Renderer { /** * 创建 */ void create(); /** * 大小改变 * @param width 宽度 * @param height 高度 */ void sizeChanged(int width, int height); /** * 渲染 * @param texture 输入纹理 */ void draw(int texture); /** * 销毁 */ void destroy(); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/egl/EGLConfigAttrs.java ================================================ package com.wuwang.aavt.egl; import android.opengl.EGL14; import java.util.Arrays; import javax.microedition.khronos.egl.EGL10; /* * Created by Wuwang on 2017/10/18 */ public class EGLConfigAttrs { private int red=8; private int green=8; private int blue=8; private int alpha=8; private int depth=8; private int renderType=4; private int surfaceType= EGL10.EGL_WINDOW_BIT; private boolean makeDefault=false; public EGLConfigAttrs red(int red){ this.red=red; return this; } public EGLConfigAttrs green(int green){ this.green=green; return this; } public EGLConfigAttrs blue(int blue){ this.blue=blue; return this; } public EGLConfigAttrs alpha(int alpha){ this.alpha=alpha; return this; } public EGLConfigAttrs depth(int depth){ this.depth=depth; return this; } public EGLConfigAttrs renderType(int type){ this.renderType=type; return this; } public EGLConfigAttrs surfaceType(int type){ this.surfaceType=type; return this; } public EGLConfigAttrs makeDefault(boolean def){ this.makeDefault=def; return this; } public boolean isDefault(){ return makeDefault; } int[] build(){ return new int[] { EGL10.EGL_SURFACE_TYPE, surfaceType, //渲染类型 EGL10.EGL_RED_SIZE, red, //指定RGB中的R大小(bits) EGL10.EGL_GREEN_SIZE, green, //指定G大小 EGL10.EGL_BLUE_SIZE, blue, //指定B大小 EGL10.EGL_ALPHA_SIZE, alpha, //指定Alpha大小,以上四项实际上指定了像素格式 EGL10.EGL_DEPTH_SIZE, depth, //指定深度缓存(Z Buffer)大小 EGL10.EGL_RENDERABLE_TYPE, renderType, //指定渲染api类别, 如上一小节描述,这里或者是硬编码的4(EGL14.EGL_OPENGL_ES2_BIT) EGL10.EGL_NONE }; //总是以EGL14.EGL_NONE结尾 } @Override public String toString() { return Arrays.toString(build()); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/egl/EGLContextAttrs.java ================================================ package com.wuwang.aavt.egl; import javax.microedition.khronos.egl.EGL10; /* * Created by Wuwang on 2017/10/18 */ public class EGLContextAttrs { private int version=2; private boolean isDefault; public EGLContextAttrs version(int v){ this.version=v; return this; } public EGLContextAttrs makeDefault(boolean def){ this.isDefault=def; return this; } public boolean isDefault(){ return isDefault; } int[] build(){ return new int[]{0x3098,version, EGL10.EGL_NONE}; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/egl/EglHelper.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.egl; import android.annotation.TargetApi; import android.opengl.EGL14; import android.opengl.EGLConfig; import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLExt; import android.opengl.EGLSurface; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.os.Build; import android.util.Log; import javax.microedition.khronos.opengles.GL10; /** * EGLHelper * * @author wuwang * @version v1.0 2017:11:01 11:41 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class EglHelper { private boolean isDebug=true; private EGLDisplay mEGLDisplay; private EGLConfig mEGLConfig; private EGLContext mEGLContext; private EGLSurface mEGLSurface; public EglHelper(int display){ changeDisplay(display); } public EglHelper(){ this(EGL14.EGL_DEFAULT_DISPLAY); } public void changeDisplay(int key){ mEGLDisplay=EGL14.eglGetDisplay(key); //获取版本号,[0]为版本号,[1]为子版本号 int[] versions=new int[2]; EGL14.eglInitialize(mEGLDisplay,versions,0,versions,1); log(EGL14.eglQueryString(mEGLDisplay, EGL14.EGL_VENDOR)); log(EGL14.eglQueryString(mEGLDisplay, EGL14.EGL_VERSION)); log(EGL14.eglQueryString(mEGLDisplay, EGL14.EGL_EXTENSIONS)); } public EGLConfig getConfig(EGLConfigAttrs attrs){ EGLConfig[] configs = new EGLConfig[1]; int[] configNum = new int[1]; EGL14.eglChooseConfig(mEGLDisplay,attrs.build(),0,configs,0,1,configNum,0); //选择的过程可能出现多个,也可能一个都没有,这里只用一个 if(configNum[0]>0){ if(attrs.isDefault()){ mEGLConfig=configs[0]; } return configs[0]; } return null; } public EGLConfig getDefaultConfig(){ return mEGLConfig; } public EGLSurface getDefaultSurface(){ return mEGLSurface; } public EGLContext getDefaultContext(){ return mEGLContext; } public EGLContext createContext(EGLConfig config,EGLContext share,EGLContextAttrs attrs){ EGLContext context= EGL14.eglCreateContext(mEGLDisplay,config,share,attrs.build(),0); if(attrs.isDefault()){ mEGLContext=context; } return context; } public EGLSurface createWindowSurface(EGLConfig config,Object surface){ return EGL14.eglCreateWindowSurface(mEGLDisplay,config,surface,new int[]{EGL14.EGL_NONE},0); } public EGLSurface createWindowSurface(Object surface){ mEGLSurface=EGL14.eglCreateWindowSurface(mEGLDisplay,mEGLConfig,surface,new int[]{EGL14.EGL_NONE},0); return mEGLSurface; } public EGLSurface createPBufferSurface(EGLConfig config,int width,int height){ return EGL14.eglCreatePbufferSurface(mEGLDisplay,config,new int[]{EGL14.EGL_WIDTH,width,EGL14.EGL_HEIGHT,height,EGL14.EGL_NONE},0); } public boolean createGLESWithSurface(EGLConfigAttrs attrs,EGLContextAttrs ctxAttrs,Object surface){ EGLConfig config=getConfig(attrs.surfaceType(EGL14.EGL_WINDOW_BIT).makeDefault(true)); if(config==null){ log("getConfig failed : "+EGL14.eglGetError()); return false; } mEGLContext=createContext(config,EGL14.EGL_NO_CONTEXT,ctxAttrs.makeDefault(true)); if(mEGLContext==EGL14.EGL_NO_CONTEXT){ log("createContext failed : "+EGL14.eglGetError()); return false; } mEGLSurface=createWindowSurface(surface); if(mEGLSurface==EGL14.EGL_NO_SURFACE){ log("createWindowSurface failed : "+EGL14.eglGetError()); return false; } if(!EGL14.eglMakeCurrent(mEGLDisplay,mEGLSurface,mEGLSurface,mEGLContext)){ log("eglMakeCurrent failed : "+EGL14.eglGetError()); return false; } return true; } public boolean makeCurrent(EGLSurface draw,EGLSurface read,EGLContext context){ if(!EGL14.eglMakeCurrent(mEGLDisplay,draw,read,context)){ log("eglMakeCurrent failed : "+EGL14.eglGetError()); } return true; } public boolean makeCurrent(EGLSurface surface,EGLContext context){ return makeCurrent(surface,surface,context); } public boolean makeCurrent(EGLSurface surface){ return makeCurrent(surface,mEGLContext); } public boolean makeCurrent(){ return makeCurrent(mEGLSurface,mEGLContext); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void setPresentationTime(EGLSurface surface, long time){ EGLExt.eglPresentationTimeANDROID(mEGLDisplay,surface,time); } public EGLSurface createGLESWithPBuffer(EGLConfigAttrs attrs,EGLContextAttrs ctxAttrs,int width,int height){ EGLConfig config=getConfig(attrs.surfaceType(EGL14.EGL_PBUFFER_BIT)); if(config==null){ log("getConfig failed : "+EGL14.eglGetError()); return null; } EGLContext eglContext=createContext(config,EGL14.EGL_NO_CONTEXT,ctxAttrs); if(eglContext==EGL14.EGL_NO_CONTEXT){ log("createContext failed : "+EGL14.eglGetError()); return null; } EGLSurface eglSurface=createPBufferSurface(config,width,height); if(eglSurface==EGL14.EGL_NO_SURFACE){ log("createWindowSurface failed : "+EGL14.eglGetError()); return null; } if(!EGL14.eglMakeCurrent(mEGLDisplay,eglSurface,eglSurface,eglContext)){ log("eglMakeCurrent failed : "+EGL14.eglGetError()); return null; } return eglSurface; } public void swapBuffers(EGLSurface surface){ EGL14.eglSwapBuffers(mEGLDisplay,surface); } public boolean destroyGLES(EGLSurface surface,EGLContext context){ EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); if(surface!=null){ EGL14.eglDestroySurface(mEGLDisplay,surface); } if(context!=null){ EGL14.eglDestroyContext(mEGLDisplay,context); } EGL14.eglTerminate(mEGLDisplay); log("gl destroy gles"); return true; } public void destroySurface(EGLSurface surface){ EGL14.eglDestroySurface(mEGLDisplay,surface); } public EGLDisplay getDisplay(){ return mEGLDisplay; } //创建视频数据流的OES TEXTURE private void log(String log){ if(isDebug){ Log.e("EGLHelper",log); } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/expend/SluggardFilterTool.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.expend; import com.wuwang.aavt.gl.BaseFilter; import java.util.HashMap; import java.util.Map; /** * SluggardFilterTool 懒汉Filter工具,该工具用于快速使用{@link BaseFilter}及其子类来处理纹理,而无需关注框架中 * 的滤镜处理流程。通常情况下,不推荐使用此工具类,推荐尽可能熟悉框架中的滤镜处理流程。然后自行根据业务逻辑, * 参照此类进行封装。 * * @author wuwang * @version v1.0 2017:11:11 15:41 */ public class SluggardFilterTool { private long threadId=-1; private HashMap filters=new HashMap<>(); /** * 处理一个纹理,并输出处理后的纹理 * @param texture 输入纹理 * @param width 输出纹理宽度 * @param height 输出纹理高度 * @param clazz 滤镜类型 * @return 输出纹理 */ public int processTexture(int texture, int width, int height, Class clazz){ long nowThreadId=Thread.currentThread().getId(); if(nowThreadId!=threadId){ filters.clear(); threadId=nowThreadId; } BaseFilter filter=filters.get(clazz); if(filter==null){ try { filter=clazz.newInstance(); filter.create(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } filters.put(clazz,filter); } if(filter!=null){ filter.sizeChanged(width, height); return filter.drawToTexture(texture); } return -1; } /** * 处理一个纹理,并输出处理后的纹理 * @param texture 输入纹理 * @param width 输出纹理宽度 * @param height 输出纹理高度 * @param filter 滤镜实体 * @return 输出纹理 */ public int processTexture(int texture,int width,int height,BaseFilter filter){ long nowThreadId=Thread.currentThread().getId(); if(nowThreadId!=threadId){ filters.clear(); threadId=nowThreadId; } if(filter!=null){ Class clazz=filter.getClass(); if(filters.get(clazz)==null){ filters.put(clazz,filter); filter.create(); } filter.sizeChanged(width, height); return filter.drawToTexture(texture); } return -1; } public void onGlDestroy(){ for (Map.Entry classBaseFilterEntry : filters.entrySet()) { BaseFilter filter = classBaseFilterEntry.getValue(); filter.destroy(); } filters.clear(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/BaseFilter.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.content.res.Resources; import android.opengl.GLES20; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.utils.GpuUtils; import com.wuwang.aavt.utils.MatrixUtils; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.LinkedList; /** * BaseFilter 滤镜的基类。对于滤镜而言,要求使用者外部调用的时候必须调用的方法为 * {@link #create()}、{@link #sizeChanged(int, int)}以及{@link #draw(int)}或者 * {@link #drawToTexture(int)}。 * 在实现Filter子类时,通常需要自行编写shader,shader中的变量应同assets下base.frag及 * base.vert中变量一致,可以增加。增加的变量需要重写{@link #onCreate()}方法,在其中 * 获取变量用于传参。 * @author wuwang * @version v1.0 2017:10:31 10:48 */ public abstract class BaseFilter implements Renderer { public static final String BASE_VERT="attribute vec4 aVertexCo;\n" + "attribute vec2 aTextureCo;\n" + "\n" + "uniform mat4 uVertexMatrix;\n" + "uniform mat4 uTextureMatrix;\n" + "\n" + "varying vec2 vTextureCo;\n" + "\n" + "void main(){\n" + " gl_Position = uVertexMatrix*aVertexCo;\n" + " vTextureCo = (uTextureMatrix*vec4(aTextureCo,0,1)).xy;\n" + "}"; private float[] mVertexMatrix= MatrixUtils.getOriginalMatrix(); private float[] mTextureMatrix=MatrixUtils.getOriginalMatrix(); protected FloatBuffer mVertexBuffer; protected FloatBuffer mTextureBuffer; protected int mWidth; protected int mHeight; protected Resources mRes; private String mVertex; private String mFragment; protected int mGLProgram; protected int mGLVertexCo; protected int mGLTextureCo; protected int mGLVertexMatrix; protected int mGLTextureMatrix; protected int mGLTexture; private int mGLWidth; private int mGLHeight; private boolean isUseSize=false; private FrameBuffer mFrameTemp; private final LinkedList mTasks=new LinkedList<>(); private final Object Lock = new Object(); protected BaseFilter(Resources resource,String vertex,String fragment){ this.mRes=resource; this.mVertex=vertex; this.mFragment=fragment; mFrameTemp=new FrameBuffer(); initBuffer(); } protected void initBuffer(){ ByteBuffer vertex=ByteBuffer.allocateDirect(32); vertex.order(ByteOrder.nativeOrder()); mVertexBuffer=vertex.asFloatBuffer(); mVertexBuffer.put(MatrixUtils.getOriginalVertexCo()); mVertexBuffer.position(0); ByteBuffer texture=ByteBuffer.allocateDirect(32); texture.order(ByteOrder.nativeOrder()); mTextureBuffer=texture.asFloatBuffer(); mTextureBuffer.put(MatrixUtils.getOriginalTextureCo()); mTextureBuffer.position(0); } public void setVertexCo(float[] vertexCo){ mVertexBuffer.clear(); mVertexBuffer.put(vertexCo); mVertexBuffer.position(0); } public void setTextureCo(float[] textureCo){ mTextureBuffer.clear(); mTextureBuffer.put(textureCo); mTextureBuffer.position(0); } public void setVertexBuffer(FloatBuffer vertexBuffer){ this.mVertexBuffer=vertexBuffer; } public void setTextureBuffer(FloatBuffer textureBuffer){ this.mTextureBuffer=textureBuffer; } public void setVertexMatrix(float[] matrix){ this.mVertexMatrix=matrix; } public void setTextureMatrix(float[] matrix){ this.mTextureMatrix=matrix; } public float[] getVertexMatrix(){ return mVertexMatrix; } public float[] getTextureMatrix(){ return mTextureMatrix; } protected void shaderNeedTextureSize(boolean need){ this.isUseSize=need; } protected void onCreate(){ if(mRes!=null){ mGLProgram= GpuUtils.createGLProgramByAssetsFile(mRes,mVertex,mFragment); }else{ mGLProgram= GpuUtils.createGLProgram(mVertex,mFragment); } mGLVertexCo= GLES20.glGetAttribLocation(mGLProgram,"aVertexCo"); mGLTextureCo=GLES20.glGetAttribLocation(mGLProgram,"aTextureCo"); mGLVertexMatrix=GLES20.glGetUniformLocation(mGLProgram,"uVertexMatrix"); mGLTextureMatrix=GLES20.glGetUniformLocation(mGLProgram,"uTextureMatrix"); mGLTexture=GLES20.glGetUniformLocation(mGLProgram,"uTexture"); if(isUseSize){ mGLWidth=GLES20.glGetUniformLocation(mGLProgram,"uWidth"); mGLHeight=GLES20.glGetUniformLocation(mGLProgram,"uHeight"); } } protected void onSizeChanged(int width,int height){ } @Override public final void create() { if(mVertex!=null&&mFragment!=null){ onCreate(); } } @Override public void sizeChanged(int width, int height) { this.mWidth=width; this.mHeight=height; onSizeChanged(width, height); mFrameTemp.destroyFrameBuffer(); } @Override public void draw(int texture) { onClear(); onUseProgram(); onSetExpandData(); onBindTexture(texture); onDraw(); } /** * 绘制内容到纹理上 * @param texture 输入纹理ID * @return 输出纹理ID */ public int drawToTexture(int texture){ mFrameTemp.bindFrameBuffer(mWidth,mHeight); draw(texture); mFrameTemp.unBindFrameBuffer(); return mFrameTemp.getCacheTextureId(); } @Override public void destroy() { mFrameTemp.destroyFrameBuffer(); GLES20.glDeleteProgram(mGLProgram); } protected void onTaskExec(){ synchronized (Lock) { while (!mTasks.isEmpty()){ mTasks.removeFirst().run(); } } } protected void onUseProgram(){ GLES20.glUseProgram(mGLProgram); onTaskExec(); } protected void onDraw(){ GLES20.glEnableVertexAttribArray(mGLVertexCo); GLES20.glVertexAttribPointer(mGLVertexCo,2, GLES20.GL_FLOAT, false, 0,mVertexBuffer); GLES20.glEnableVertexAttribArray(mGLTextureCo); GLES20.glVertexAttribPointer(mGLTextureCo, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4); GLES20.glDisableVertexAttribArray(mGLVertexCo); GLES20.glDisableVertexAttribArray(mGLTextureCo); } protected void onClear(){ GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); } public void runOnGLThread(Runnable runnable){ synchronized (Lock) { mTasks.addLast(runnable); } } /** * 设置其他扩展数据 */ protected void onSetExpandData(){ GLES20.glUniformMatrix4fv(mGLVertexMatrix,1,false,mVertexMatrix,0); GLES20.glUniformMatrix4fv(mGLTextureMatrix,1,false,mTextureMatrix,0); if(isUseSize){ GLES20.glUniform1f(mGLWidth,mWidth); GLES20.glUniform1f(mGLHeight,mHeight); } } /** * 绑定默认纹理 */ protected void onBindTexture(int textureId){ GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId); GLES20.glUniform1i(mGLTexture,0); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/BaseFuncFilter.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.content.res.Resources; /** * BaseFuncFilter 基础功能滤镜 * * @author wuwang * @version v1.0 2017:10:31 11:47 */ class BaseFuncFilter extends BaseFilter { static final String FILTER_SOBEL="shader/func/sobel.frag"; static final String FILTER_SOBEL_REVERSE="shader/func/sobel2.frag"; static final String FILTER_GAUSS="shader/func/gauss.frag"; BaseFuncFilter(Resources resource, String fragment) { super(resource, "shader/base.vert", fragment); shaderNeedTextureSize(true); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/BeautyFilter.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.content.res.Resources; import android.opengl.GLES20; /** * BeautyFilter 美颜滤镜 * * @author wuwang * @version v1.0 2017:10:31 11:46 */ public class BeautyFilter extends BaseFilter { private int mGLaaCoef; private int mGLmixCoef; private int mGLiternum; private float aaCoef; private float mixCoef; private int iternum; public BeautyFilter(Resources resource) { super(resource,"shader/beauty/beauty.vert", "shader/beauty/beauty.frag"); shaderNeedTextureSize(true); setBeautyLevel(0); } @Override protected void onCreate() { super.onCreate(); mGLaaCoef= GLES20.glGetUniformLocation(mGLProgram,"uACoef"); mGLmixCoef=GLES20.glGetUniformLocation(mGLProgram,"uMixCoef"); mGLiternum=GLES20.glGetUniformLocation(mGLProgram,"uIternum"); } public BaseFilter setBeautyLevel(int level){ switch (level){ case 1: a(1,0.19f,0.54f); break; case 2: a(2,0.29f,0.54f); break; case 3: a(3,0.17f,0.39f); break; case 4: a(3,0.25f,0.54f); break; case 5: a(4,0.13f,0.54f); break; case 6: a(4,0.19f,0.69f); break; default: a(0,0f,0f); break; } return this; } private void a(final int a, final float b, final float c){ runOnGLThread(new Runnable() { @Override public void run() { GLES20.glUniform1f(mGLaaCoef,b); GLES20.glUniform1f(mGLmixCoef,c); GLES20.glUniform1i(mGLiternum,a); } }); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/BlackMagicFilter.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.content.res.Resources; /** * BlackMagicFilter 黑魔法滤镜,sobel算法实现 * * @author wuwang * @version v1.0 2017:10:31 11:47 */ public class BlackMagicFilter extends GroupFilter { public BlackMagicFilter(Resources resources){ super(resources); } @Override protected void initBuffer() { super.initBuffer(); addFilter(new GrayFilter(mRes)); addFilter(new BaseFuncFilter(mRes,BaseFuncFilter.FILTER_SOBEL)); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/CandyFilter.java ================================================ package com.wuwang.aavt.gl; import android.content.res.Resources; /** * Created by 15581 on 2017/9/30. */ public class CandyFilter extends GroupFilter { public CandyFilter(Resources resource) { super(resource); } @Override protected void initBuffer() { super.initBuffer(); addFilter(new GrayFilter(mRes)); addFilter(new BaseFuncFilter(mRes,BaseFuncFilter.FILTER_GAUSS)); addFilter(new BaseFuncFilter(mRes,BaseFuncFilter.FILTER_SOBEL)); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/Faltung3x3Filter.java ================================================ package com.wuwang.aavt.gl; import android.content.res.Resources; import android.opengl.GLES20; /** * Created by wuwang on 2017/10/1. */ public class Faltung3x3Filter extends BaseFilter { private float[] mFaltung; private int mGLFaltung; public static final float[] FILTER_SHARPEN=new float[]{0,-1,0,-1,5,-1,0,-1,0}; public static final float[] FILTER_BORDER=new float[]{0,1,0,1,-4,1,0,1,0}; public static final float[] FILTER_CAMEO=new float[]{2,0,2,0,0,0,3,0,-6}; public Faltung3x3Filter(Resources res,float[] faltung){ super(res,"shader/base.vert","shader/func/faltung3x3.frag"); shaderNeedTextureSize(true); this.mFaltung=faltung; } @Override protected void onCreate() { super.onCreate(); mGLFaltung= GLES20.glGetUniformLocation(mGLProgram,"uFaltung"); } @Override protected void onSetExpandData() { super.onSetExpandData(); GLES20.glUniformMatrix3fv(mGLFaltung,1,false,mFaltung,0); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/FluorescenceFilter.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.content.res.Resources; import android.opengl.GLES20; /** * FluorescenceFilter 荧光滤镜 * * @author wuwang * @version v1.0 2017:10:31 11:50 */ public class FluorescenceFilter extends BaseFilter { private int mGLTexture2; private int mGLBorderColor; private int mGLStep; private BlackMagicFilter mBlackFilter; private int mTempTexture; private float[] mBorderColor=new float[]{0f,1f,1f,1}; private float mStep=1.0f; private boolean isAdd=true; public FluorescenceFilter(Resources resource) { super(resource, "shader/base.vert", "shader/effect/fluorescence.frag"); shaderNeedTextureSize(true); mBlackFilter=new BlackMagicFilter(resource); } @Override protected void onCreate() { mBlackFilter.create(); super.onCreate(); mGLTexture2= GLES20.glGetUniformLocation(mGLProgram,"uTexture2"); mGLBorderColor= GLES20.glGetUniformLocation(mGLProgram,"uBorderColor"); mGLStep=GLES20.glGetUniformLocation(mGLProgram,"uStep"); } @Override protected void onSizeChanged(int width, int height) { super.onSizeChanged(width, height); mBlackFilter.sizeChanged(width, height); } @Override public void draw(int texture) { mTempTexture=mBlackFilter.drawToTexture(texture); super.draw(texture); } @Override protected void onBindTexture(int textureId) { super.onBindTexture(textureId); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,mTempTexture); GLES20.glUniform1i(mGLTexture2,1); } @Override protected void onSetExpandData() { //todo 根据时间修改 if(isAdd){ mStep+=0.08f; }else{ mStep-=0.08f; } if(mStep>=1.0f){ isAdd=false; mStep=1.0f; }else if(mStep<=0.0f){ isAdd=true; mStep=0.0f; } super.onSetExpandData(); GLES20.glUniform4fv(mGLBorderColor,1,mBorderColor,0); GLES20.glUniform1f(mGLStep,mStep); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/FrameBuffer.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.opengl.GLES20; /** * FrameBuffer 工具类 * * @author wuwang * @version v1.0 2017:10:31 10:31 */ public class FrameBuffer { private int[] mFrameTemp; private int lastWidth=0,lastHeight=0; /** * 绑定到FrameBuffer,不使用RenderBuffer。{@link #bindFrameBuffer(int, int, boolean)} * @param width 宽度 * @param height 高度 * @return 绑定结果,0表示成功,其他值为GL错误 */ public int bindFrameBuffer(int width,int height){ return bindFrameBuffer(width, height,false); } /** * 绑定到FrameBuffer * @param width 宽度 * @param height 高度 * @param hasRenderBuffer 是否使用renderBuffer * @return 绑定结果,0表示成功,其他值为GL错误 */ public int bindFrameBuffer(int width,int height,boolean hasRenderBuffer){ if(lastWidth!=width||lastHeight!=height){ destroyFrameBuffer(); this.lastWidth=width; this.lastHeight=height; } if(mFrameTemp==null){ return createFrameBuffer(hasRenderBuffer,width,height, GLES20.GL_TEXTURE_2D,GLES20.GL_RGBA, GLES20.GL_LINEAR,GLES20.GL_LINEAR,GLES20.GL_CLAMP_TO_EDGE,GLES20.GL_CLAMP_TO_EDGE); }else{ return bindFrameBuffer(); } } /** * 创建FrameBuffer * @param hasRenderBuffer 是否启用RenderBuffer * @param width 宽度 * @param height 高度 * @param texType 类型,一般为{@link GLES20#GL_TEXTURE_2D} * @param texFormat 纹理格式,一般为{@link GLES20#GL_RGBA}、{@link GLES20#GL_RGB}等 * @param minParams 纹理的缩小过滤参数 * @param maxParams 纹理的放大过滤参数 * @param wrapS 纹理的S环绕参数 * @param wrapT 纹理的W环绕参数 * @return 创建结果,0表示成功,其他值为GL错误 */ public int createFrameBuffer(boolean hasRenderBuffer,int width,int height,int texType,int texFormat, int minParams,int maxParams,int wrapS,int wrapT){ mFrameTemp=new int[4]; GLES20.glGenFramebuffers(1,mFrameTemp,0); GLES20.glGenTextures(1,mFrameTemp,1); GLES20.glBindTexture(texType,mFrameTemp[1]); GLES20.glTexImage2D(texType, 0,texFormat, width, height, 0, texFormat, GLES20.GL_UNSIGNED_BYTE, null); //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色 GLES20.glTexParameteri(texType, GLES20.GL_TEXTURE_MIN_FILTER,minParams); //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色 GLES20.glTexParameteri(texType, GLES20.GL_TEXTURE_MAG_FILTER,maxParams); //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合 GLES20.glTexParameteri(texType, GLES20.GL_TEXTURE_WRAP_S,wrapS); //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合 GLES20.glTexParameteri(texType, GLES20.GL_TEXTURE_WRAP_T,wrapT); GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING,mFrameTemp,3); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,mFrameTemp[0]); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, texType, mFrameTemp[1], 0); if(hasRenderBuffer){ GLES20.glGenRenderbuffers(1,mFrameTemp,2); GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER,mFrameTemp[2]); GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER,GLES20.GL_DEPTH_COMPONENT16,width,height); GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER,GLES20.GL_DEPTH_ATTACHMENT,GLES20.GL_RENDERBUFFER,mFrameTemp[2]); } return GLES20.glGetError(); } /** * 绑定FrameBuffer,只有之前创建过FrameBuffer,才能调用此方法进行绑定 * @return 绑定结果 */ public int bindFrameBuffer(){ if(mFrameTemp==null){ return -1; } GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING,mFrameTemp,3); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,mFrameTemp[0]); return GLES20.glGetError(); } /** * 取消FrameBuffer绑定 */ public void unBindFrameBuffer(){ if(mFrameTemp!=null){ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,mFrameTemp[3]); } } /** * 获取绘制再FrameBuffer中的内容 * @return FrameBuffer绘制内容的纹理ID */ public int getCacheTextureId(){ return mFrameTemp!=null?mFrameTemp[1]:-1; } /** * 销毁FrameBuffer */ public void destroyFrameBuffer(){ if(mFrameTemp!=null){ GLES20.glDeleteFramebuffers(1,mFrameTemp,0); GLES20.glDeleteTextures(1,mFrameTemp,1); if(mFrameTemp[2]>0){ GLES20.glDeleteRenderbuffers(1,mFrameTemp,2); } mFrameTemp=null; } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/GrayFilter.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.content.res.Resources; /** * GrayFilter 灰度滤镜 * * @author wuwang * @version v1.0 2017:10:31 11:52 */ public class GrayFilter extends BaseFilter { public GrayFilter(Resources resource) { super(resource, "shader/base.vert", "shader/color/gray.frag"); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/gl/GroupFilter.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.gl; import android.content.res.Resources; import java.util.ArrayList; import java.util.Iterator; import java.util.Vector; /** * GroupFilter 滤镜组,将多个滤镜串联起来,合并成一个滤镜 * * @author wuwang * @version v1.0 2017:10:31 11:53 */ public class GroupFilter extends LazyFilter { private ArrayList mGroup; public GroupFilter(Resources resource) { super(resource); } public GroupFilter(){ super(); } @Override protected void initBuffer() { super.initBuffer(); mGroup=new ArrayList<>(); } public void addFilter(final BaseFilter filter){ runOnGLThread(new Runnable() { @Override public void run() { filter.create(); filter.sizeChanged(mWidth,mHeight); mGroup.add(filter); } }); } public void addFilter(final int index,final BaseFilter filter){ runOnGLThread(new Runnable() { @Override public void run() { filter.create(); filter.sizeChanged(mWidth,mHeight); mGroup.add(index, filter); } }); } public BaseFilter removeFilter(final int index){ BaseFilter filter=mGroup.get(index); runOnGLThread(new Runnable() { @Override public void run() { BaseFilter filter=mGroup.remove(index); if(filter!=null){ filter.destroy(); } } }); return filter; } public void removeFilter(final BaseFilter filter){ runOnGLThread(new Runnable() { @Override public void run() { mGroup.remove(filter); } }); } public BaseFilter get(int index){ return mGroup.get(index); } public Iterator iterator(){ return mGroup.iterator(); } public boolean isEmpty(){ return mGroup.isEmpty(); } @Override public void draw(int texture) { int tempTextureId=texture; for (int i=0;i=(mXRollTime+mYRollTime)){ mFrameCount=0; } if(mFrameCount= Build.VERSION_CODES.LOLLIPOP) { return codec.getInputBuffer(index); }else{ return codec.getInputBuffers()[index]; } } public static ByteBuffer getOutputBuffer(MediaCodec codec, int index){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return codec.getOutputBuffer(index); }else{ return codec.getOutputBuffers()[index]; } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/MediaConfig.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media; /** * MediaConfig 音视频编码信息设置 * * @author wuwang * @version v1.0 2017:10:26 18:28 */ public class MediaConfig { public Video mVideo=new Video(); public Audio mAudio=new Audio(); public class Video{ public String mime="video/avc"; public int width=368; public int height=640; public int frameRate=24; public int iframe=1; public int bitrate=1177600; public int colorFormat; } public class Audio{ public String mime="audio/mp4a-latm"; public int sampleRate=48000; public int channelCount=2; public int bitrate=128000; public int profile; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/RenderBean.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media; import com.wuwang.aavt.egl.EglHelper; /** * RenderBean * * @author wuwang * @version v1.0 2017:10:27 15:02 */ public class RenderBean { public EglHelper egl; public int sourceWidth; public int sourceHeight; public int textureId; public boolean endFlag; public long timeStamp; public long textureTime; public long threadId; } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/SoundRecorder.java ================================================ package com.wuwang.aavt.media; import android.annotation.TargetApi; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaRecorder; import android.os.Build; import android.os.SystemClock; import android.util.Log; import com.wuwang.aavt.log.AvLog; import com.wuwang.aavt.media.hard.HardMediaData; import com.wuwang.aavt.media.hard.IHardStore; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /* * Created by Wuwang on 2017/10/26 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class SoundRecorder { private AudioRecord mRecord; private int mRecordBufferSize=0; private int mRecordSampleRate=48000; //音频采样率 private int mRecordChannelConfig= AudioFormat.CHANNEL_IN_STEREO; //音频录制通道,默认为立体声 private int mRecordAudioFormat=AudioFormat.ENCODING_PCM_16BIT; //音频录制格式,默认为PCM16Bit private MediaCodec mAudioEncoder; private MediaConfig mConfig=new MediaConfig(); private boolean isStarted=false; private IHardStore mStore; private static final int TIME_OUT=1000; private int mAudioTrack=-1; private long startTime=0; private boolean stopFlag=false; private Executors mExec; public SoundRecorder(IHardStore store){ this.mStore=store; } public void configure(){ } public void start(){ if(!isStarted){ stopFlag=false; mRecordBufferSize = AudioRecord.getMinBufferSize(mRecordSampleRate, mRecordChannelConfig, mRecordAudioFormat)*2; mRecord=new AudioRecord(MediaRecorder.AudioSource.MIC,mRecordSampleRate,mRecordChannelConfig, mRecordAudioFormat,mRecordBufferSize); mRecord.startRecording(); try { MediaFormat format=convertAudioConfigToFormat(mConfig.mAudio); mAudioEncoder=MediaCodec.createEncoderByType(format.getString(MediaFormat.KEY_MIME)); mAudioEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); mAudioEncoder.start(); } catch (IOException e) { e.printStackTrace(); } Thread thread=new Thread(new Runnable() { @Override public void run() { while (!stopFlag&&!audioEncodeStep(false)){}; audioEncodeStep(true); Log.e("wuwang","audio stop"); if(isStarted){ mRecord.stop(); mRecord.release(); mRecord=null; } if(mAudioEncoder!=null){ mAudioEncoder.stop(); mAudioEncoder.release(); mAudioEncoder=null; } isStarted=false; } }); thread.start(); startTime=SystemClock.elapsedRealtimeNanos(); isStarted=true; } } private synchronized boolean audioEncodeStep(boolean isEnd){ if(isStarted){ AvLog.d("audioEncodeStep"); int inputIndex=mAudioEncoder.dequeueInputBuffer(TIME_OUT); if(inputIndex>=0){ ByteBuffer buffer= CodecUtil.getInputBuffer(mAudioEncoder,inputIndex); buffer.clear(); long time= (SystemClock.elapsedRealtimeNanos()-startTime)/1000; int length=mRecord.read(buffer,mRecordBufferSize); if(length>=0){ mAudioEncoder.queueInputBuffer(inputIndex,0,length,time, isEnd?MediaCodec.BUFFER_FLAG_END_OF_STREAM:0); } } MediaCodec.BufferInfo info=new MediaCodec.BufferInfo(); while (true){ int outputIndex=mAudioEncoder.dequeueOutputBuffer(info,TIME_OUT); if(outputIndex>=0){ if(mStore!=null){ mStore.addData(mAudioTrack,new HardMediaData(CodecUtil.getOutputBuffer(mAudioEncoder,outputIndex),info)); } mAudioEncoder.releaseOutputBuffer(outputIndex,false); if(info.flags==MediaCodec.BUFFER_FLAG_END_OF_STREAM){ AvLog.d("CameraRecorder get audio encode end of stream"); stop(); return true; } }else if(outputIndex==MediaCodec.INFO_TRY_AGAIN_LATER){ break; }else if(outputIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ AvLog.d("get audio output format changed ->"+mAudioEncoder.getOutputFormat().toString()); mAudioTrack=mStore.addTrack(mAudioEncoder.getOutputFormat()); } } } return false; } public void stop(){ stopFlag=true; } public void setConfig(MediaConfig config){ this.mConfig=config; } protected MediaFormat convertAudioConfigToFormat(MediaConfig.Audio config){ MediaFormat format=MediaFormat.createAudioFormat(config.mime,config.sampleRate,config.channelCount); format.setInteger(MediaFormat.KEY_BIT_RATE,config.bitrate); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); return format; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/SurfaceEncoder.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.opengl.EGLSurface; import android.os.Build; import com.wuwang.aavt.log.AvLog; import com.wuwang.aavt.media.hard.HardMediaData; import com.wuwang.aavt.media.hard.IHardStore; import java.io.IOException; import java.nio.ByteBuffer; /** * SurfaceEncoder 从surface上进行硬编码,通过{@link #setStore(IHardStore)}来设置存储器进行存储 * * @author wuwang * @version v1.0 2017:10:27 08:29 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class SurfaceEncoder extends SurfaceShower{ private final String TAG="SurfaceEncoder"; private MediaConfig mConfig=new MediaConfig(); private MediaCodec mVideoEncoder; private boolean isEncodeStarted=false; private static final int TIME_OUT=1000; private IHardStore mStore; private int mVideoTrack=-1; private OnDrawEndListener mListener; private long startTime=-1; public SurfaceEncoder(){ super.setOnDrawEndListener(new OnDrawEndListener() { @Override public void onDrawEnd(EGLSurface surface, RenderBean bean) { AvLog.d(TAG,"onDrawEnd start-->"); if(bean.timeStamp!=-1){ bean.egl.setPresentationTime(surface,bean.timeStamp*1000); }else{ if(startTime==-1){ startTime=bean.textureTime; } bean.egl.setPresentationTime(surface,bean.textureTime-startTime); } videoEncodeStep(false); AvLog.e(TAG,"onDrawEnd end-->"); if(mListener!=null){ mListener.onDrawEnd(surface,bean); } } }); } @Override public void onCall(RenderBean rb) { if (rb.endFlag){ videoEncodeStep(true); } super.onCall(rb); } public void setConfig(MediaConfig config){ this.mConfig=config; } public void setStore(IHardStore store){ this.mStore=store; } @Override public void setOutputSize(int width, int height) { super.setOutputSize(width, height); mConfig.mVideo.width=width; mConfig.mVideo.height=height; } protected MediaFormat convertVideoConfigToFormat(MediaConfig.Video config){ MediaFormat format=MediaFormat.createVideoFormat(config.mime,config.width,config.height); format.setInteger(MediaFormat.KEY_BIT_RATE,config.bitrate); format.setInteger(MediaFormat.KEY_FRAME_RATE,config.frameRate); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,config.iframe); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); return format; } private void openVideoEncoder(){ AvLog.d(TAG,"openVideoEncoder startTime-->"); if(mVideoEncoder==null){ try { MediaFormat format=convertVideoConfigToFormat(mConfig.mVideo); mVideoEncoder= MediaCodec.createEncoderByType(mConfig.mVideo.mime); mVideoEncoder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); super.setSurface(mVideoEncoder.createInputSurface()); super.setOutputSize(mConfig.mVideo.width,mConfig.mVideo.height); mVideoEncoder.start(); isEncodeStarted=true; } catch (IOException e) { e.printStackTrace(); } } AvLog.d(TAG,"openVideoEncoder endTime-->"); } private void closeVideoEncoder(){ AvLog.d(TAG,"closeEncoder"); if(mVideoEncoder!=null){ mVideoEncoder.stop(); mVideoEncoder.release(); mVideoEncoder=null; } } private synchronized boolean videoEncodeStep(boolean isEnd){ AvLog.d(TAG,"videoEncodeStep:"+isEncodeStarted+"/"+isEnd); if(isEncodeStarted){ if(isEnd){ mVideoEncoder.signalEndOfInputStream(); } MediaCodec.BufferInfo info=new MediaCodec.BufferInfo(); while (true){ int mOutputIndex=mVideoEncoder.dequeueOutputBuffer(info,TIME_OUT); AvLog.i(TAG,"videoEncodeStep:mOutputIndex="+mOutputIndex); if(mOutputIndex>=0){ if((info.flags&MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!=0){ info.size=0; } ByteBuffer buffer= CodecUtil.getOutputBuffer(mVideoEncoder,mOutputIndex); if(mStore!=null){ mStore.addData(mVideoTrack,new HardMediaData(buffer,info)); } mVideoEncoder.releaseOutputBuffer(mOutputIndex,false); if((info.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){ closeVideoEncoder(); isEncodeStarted=false; AvLog.i(TAG,"videoEncodeStep: MediaCodec.BUFFER_FLAG_END_OF_STREAM "); break; } }else if(mOutputIndex== MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ MediaFormat format=mVideoEncoder.getOutputFormat(); if(mStore!=null){ mVideoTrack=mStore.addTrack(format); } }else if(mOutputIndex== MediaCodec.INFO_TRY_AGAIN_LATER&&!isEnd){ break; } } } return false; } @Override public void open() { openVideoEncoder(); super.open(); } @Override public void close() { super.close(); videoEncodeStep(true); startTime=-1; } @Override public void setOnDrawEndListener(OnDrawEndListener listener) { this.mListener=listener; } @Override public void setSurface(Object surface) {} } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/SurfaceShower.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media; import android.opengl.EGLSurface; import android.opengl.GLES20; import com.wuwang.aavt.core.IObserver; import com.wuwang.aavt.gl.BaseFilter; import com.wuwang.aavt.gl.LazyFilter; import com.wuwang.aavt.utils.MatrixUtils; /** * SurfaceShower 用于将RenderBean展示到指定的Surface上 * * @author wuwang * @version v1.0 2017:10:27 08:53 */ public class SurfaceShower implements IObserver { private EGLSurface mShowSurface; private boolean isShow=false; private BaseFilter mFilter; private Object mSurface; private int mWidth; private int mHeight; private int mMatrixType= MatrixUtils.TYPE_CENTERCROP; private OnDrawEndListener mListener; private RenderBean mBean; public void setOutputSize(int width,int height){ this.mWidth=width; this.mHeight=height; } private void clearSurface() { if(mShowSurface!=null){ mBean.egl.destroySurface(mShowSurface); mShowSurface = null; } } /** * 设置输出的Surface * @param surface {@link android.view.Surface}、{@link android.graphics.SurfaceTexture}或{@link android.view.TextureView} */ public void setSurface(Object surface){ this.mSurface=surface; clearSurface(); } /** * 设置矩阵变换类型 * @param type 变换类型,{@link MatrixUtils#TYPE_FITXY},{@link MatrixUtils#TYPE_FITSTART},{@link MatrixUtils#TYPE_CENTERCROP},{@link MatrixUtils#TYPE_CENTERINSIDE}或{@link MatrixUtils#TYPE_FITEND} */ public void setMatrixType(int type){ this.mMatrixType=type; } public void open(){ isShow=true; } public void close(){ isShow=false; } @Override public void onCall(RenderBean rb) { if(rb.endFlag){ clearSurface(); }else if(isShow&&mSurface!=null){ if(mShowSurface==null){ mBean = rb; mShowSurface=rb.egl.createWindowSurface(mSurface); mFilter=new LazyFilter(); mFilter.create(); mFilter.sizeChanged(rb.sourceWidth, rb.sourceHeight); MatrixUtils.getMatrix(mFilter.getVertexMatrix(),mMatrixType,rb.sourceWidth,rb.sourceHeight, mWidth,mHeight); MatrixUtils.flip(mFilter.getVertexMatrix(),false,true); } rb.egl.makeCurrent(mShowSurface); GLES20.glViewport(0,0,mWidth,mHeight); mFilter.draw(rb.textureId); if(mListener!=null){ mListener.onDrawEnd(mShowSurface,rb); } rb.egl.swapBuffers(mShowSurface); } } /** * 设置单帧渲染完成监听器 * @param listener 监听器 */ public void setOnDrawEndListener(OnDrawEndListener listener){ this.mListener=listener; } public interface OnDrawEndListener{ /** * 渲染完成通知 * @param surface 渲染的目标EGLSurface * @param bean 渲染用的资源 */ void onDrawEnd(EGLSurface surface,RenderBean bean); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/VideoSurfaceProcessor.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media; import android.annotation.TargetApi; import android.graphics.Point; import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.opengl.GLES20; import android.os.Build; import com.wuwang.aavt.core.IObserver; import com.wuwang.aavt.core.Observable; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.egl.EGLConfigAttrs; import com.wuwang.aavt.egl.EGLContextAttrs; import com.wuwang.aavt.egl.EglHelper; import com.wuwang.aavt.gl.FrameBuffer; import com.wuwang.aavt.log.AvLog; import com.wuwang.aavt.media.video.ITextureProvider; import com.wuwang.aavt.utils.GpuUtils; /** * VideoSurfaceProcessor 视频流图像处理类,以{@link ITextureProvider}作为视频流图像输入。通过设置{@link IObserver} * 来接收处理完毕的{@link RenderBean},并做相应处理,诸如展示、编码等。 * * @author wuwang * @version v1.0 2017:10:27 08:37 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class VideoSurfaceProcessor{ private String TAG=getClass().getSimpleName(); private boolean mGLThreadFlag=false; private Thread mGLThread; private Renderer renderer; private boolean isRendererChanged = false; private Observable observable; private final Object LOCK=new Object(); private ITextureProvider mProvider; public VideoSurfaceProcessor(){ observable=new Observable<>(); } public void setTextureProvider(ITextureProvider provider){ this.mProvider=provider; } public void start(){ synchronized (LOCK){ if(!mGLThreadFlag){ if(mProvider==null){ return; } mGLThreadFlag=true; mGLThread=new Thread(new Runnable() { @Override public void run() { glRun(); } }); mGLThread.start(); try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void stop(){ synchronized (LOCK){ if(mGLThreadFlag){ mGLThreadFlag=false; mProvider.close(); try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } private WrapRenderer checkWrapRenderer(WrapRenderer renderer,int width,int height){ if(isRendererChanged){ isRendererChanged = false; renderer.destroy(); renderer = new WrapRenderer(this.renderer); renderer.create(); renderer.sizeChanged(width, height); renderer.setFlag(mProvider.isLandscape()?WrapRenderer.TYPE_CAMERA:WrapRenderer.TYPE_MOVE); } return renderer; } public void setRenderer(Renderer renderer){ this.renderer = renderer; this.isRendererChanged = true; } private void glRun(){ EglHelper egl=new EglHelper(); boolean ret=egl.createGLESWithSurface(new EGLConfigAttrs(),new EGLContextAttrs(),new SurfaceTexture(1)); if(!ret){ //todo 错误处理 return; } int mInputSurfaceTextureId = GpuUtils.createTextureID(true); SurfaceTexture mInputSurfaceTexture = new SurfaceTexture(mInputSurfaceTextureId); Point size=mProvider.open(mInputSurfaceTexture); AvLog.d(TAG,"Provider Opened . data size (x,y)="+size.x+"/"+size.y); if(size.x<=0||size.y<=0){ //todo 错误处理 destroyGL(egl); synchronized (LOCK){ LOCK.notifyAll(); } return; } int mSourceWidth = size.x; int mSourceHeight = size.y; synchronized (LOCK){ LOCK.notifyAll(); } //要求数据源提供者必须同步返回数据大小 if(mSourceWidth <=0|| mSourceHeight <=0){ error(1,"video source return inaccurate size to SurfaceTextureActuator"); return; } WrapRenderer wrapRenderer=new WrapRenderer(null); wrapRenderer.create(); wrapRenderer.sizeChanged(mSourceWidth,mSourceHeight); wrapRenderer.setFlag(mProvider.isLandscape()?WrapRenderer.TYPE_CAMERA:WrapRenderer.TYPE_MOVE); FrameBuffer sourceFrame=new FrameBuffer(); //用于其他的回调 RenderBean rb=new RenderBean(); rb.egl=egl; rb.sourceWidth= mSourceWidth; rb.sourceHeight= mSourceHeight; rb.endFlag=false; rb.threadId=Thread.currentThread().getId(); AvLog.d(TAG,"Processor While Loop Entry"); //要求数据源必须同步填充SurfaceTexture,填充完成前等待 while (!mProvider.frame()&&mGLThreadFlag){ wrapRenderer = checkWrapRenderer(wrapRenderer,mSourceWidth,mSourceHeight); mInputSurfaceTexture.updateTexImage(); mInputSurfaceTexture.getTransformMatrix(wrapRenderer.getTextureMatrix()); AvLog.d(TAG,"timestamp:"+ mInputSurfaceTexture.getTimestamp()); sourceFrame.bindFrameBuffer(mSourceWidth, mSourceHeight); GLES20.glViewport(0,0, mSourceWidth, mSourceHeight); wrapRenderer.draw(mInputSurfaceTextureId); sourceFrame.unBindFrameBuffer(); rb.textureId=sourceFrame.getCacheTextureId(); //接收数据源传入的时间戳 rb.timeStamp=mProvider.getTimeStamp(); rb.textureTime= mInputSurfaceTexture.getTimestamp(); observable.notify(rb); } AvLog.d(TAG,"out of gl thread loop"); synchronized (LOCK){ rb.endFlag=true; observable.notify(rb); wrapRenderer.destroy(); destroyGL(egl); LOCK.notifyAll(); AvLog.d(TAG,"gl thread exit"); } } private void destroyGL(EglHelper egl){ mGLThreadFlag=false; EGL14.eglMakeCurrent(egl.getDisplay(), EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); EGL14.eglDestroyContext(egl.getDisplay(),egl.getDefaultContext()); EGL14.eglTerminate(egl.getDisplay()); } public void addObserver(IObserver observer) { observable.addObserver(observer); } protected void error(int id,String msg) { } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/WrapRenderer.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.gl.OesFilter; import com.wuwang.aavt.utils.MatrixUtils; /** * WrapRenderer 用于包装其他Filter渲染OES纹理 * * @author wuwang * @version v1.0 2017:10:27 08:53 */ public class WrapRenderer implements Renderer { private Renderer mRenderer; private OesFilter mFilter; public static final int TYPE_MOVE=0; public static final int TYPE_CAMERA=1; public WrapRenderer(Renderer renderer){ this.mRenderer=renderer; mFilter=new OesFilter(); setFlag(TYPE_MOVE); } public void setFlag(int flag){ if(flag==TYPE_MOVE){ mFilter.setVertexCo(new float[]{ -1.0f,1.0f, -1.0f,-1.0f, 1.0f,1.0f, 1.0f,-1.0f, }); }else if(flag==TYPE_CAMERA){ mFilter.setVertexCo(new float[]{ -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }); } } public float[] getTextureMatrix(){ return mFilter.getTextureMatrix(); } @Override public void create() { mFilter.create(); if(mRenderer!=null){ mRenderer.create(); } } @Override public void sizeChanged(int width, int height) { mFilter.sizeChanged(width, height); if(mRenderer!=null){ mRenderer.sizeChanged(width, height); } } @Override public void draw(int texture) { if(mRenderer!=null){ mRenderer.draw(mFilter.drawToTexture(texture)); }else{ mFilter.draw(texture); } } @Override public void destroy() { if(mRenderer!=null){ mRenderer.destroy(); } mFilter.destroy(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/audio/FileAudioProvider.java ================================================ package com.wuwang.aavt.media.audio; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import com.wuwang.aavt.media.CodecUtil; import java.io.IOException; import java.nio.ByteBuffer; /** * @author wuwang * @version 1.00 , 2019/03/13 */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public class FileAudioProvider extends MediaCodec.Callback implements ISoundProvider { private MediaExtractor extractor; private MediaCodec codec; private int audioTrack = -1; private MediaFormat format; private final long TIME_US = 1000; private FileAudioProvider(){} @Override public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) { ByteBuffer buffer = CodecUtil.getInputBuffer(codec,index); buffer.clear(); int size = extractor.readSampleData(buffer,0); codec.queueInputBuffer(index,0,size ,extractor.getSampleTime(),extractor.getSampleFlags()); } @Override public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) { ByteBuffer buffer = CodecUtil.getOutputBuffer(codec,index); codec.releaseOutputBuffer(index,false); } @Override public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) { } @Override public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) { } private boolean setPath(String path){ try { extractor = new MediaExtractor(); extractor.setDataSource(path); int count = extractor.getTrackCount(); if(count <= 0){ return false; } for (int i=0;i=0){ ByteBuffer buffer = CodecUtil.getInputBuffer(codec,inputId); buffer.clear(); } } @Override public void close() { codec.stop(); codec.release(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/audio/ISoundProvider.java ================================================ package com.wuwang.aavt.media.audio; import com.wuwang.aavt.media.av.ICloseable; /** * @author wuwang * @version 1.00 , 2019/03/13 */ public interface ISoundProvider extends ICloseable { void open(); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/audio/MicAudioProvider.java ================================================ package com.wuwang.aavt.media.audio; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import com.wuwang.aavt.media.audio.ISoundProvider; import com.wuwang.aavt.media.av.AvException; /** * @author wuwang * @version 1.00 , 2019/03/13 */ public class MicAudioProvider implements ISoundProvider { private AudioRecord record; private int recordBufferSize=0; private int sampleRateInHz=48000; //音频采样率 private int channelConfig= AudioFormat.CHANNEL_IN_STEREO; //音频录制通道,默认为立体声 private int audioFormat=AudioFormat.ENCODING_PCM_16BIT; //音频录制格式,默认为PCM16Bit public MicAudioProvider(){ setProperty(sampleRateInHz,channelConfig,audioFormat); } public MicAudioProvider(int sampleRateInHz, int channelConfig, int audioFormat){ setProperty(sampleRateInHz, channelConfig, audioFormat); } public void setProperty(int sampleRateInHz, int channelConfig, int audioFormat){ this.sampleRateInHz = sampleRateInHz; this.channelConfig = channelConfig; this.audioFormat = audioFormat; this.recordBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat) * 2; } @Override public void open() { close(); record = new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRateInHz,channelConfig,audioFormat,recordBufferSize); record.startRecording(); } @Override public void close() { if(record != null){ record.stop(); record.release(); record = null; } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/av/AvException.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.av; /** * AVException * * @author wuwang * @version v1.0 2017:10:28 17:27 */ public class AvException extends Exception { public AvException(){ super(); } public AvException(String msg){ super("AVException:"+msg); } public AvException(String message, Throwable cause) { super("AVException:"+message, cause); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/av/ICloseable.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.av; /** * ICloseable * * @author wuwang * @version v1.0 2017:10:28 17:25 */ public interface ICloseable { void close(); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/av/IStore.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.av; /** * IStore 文件存储接口 * * @author wuwang * @version v1.0 2017:10:28 16:39 */ public interface IStore extends ICloseable{ /** * 增加存储轨道 * @param track 待存储的内容信息 * @return 轨道索引 */ int addTrack(Track track); /** * 写入内容到存储中 * @param track 轨道索引 * @param data 存储内容,包括内容信息 * @return 写入结果 */ int addData(int track,Data data); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/hard/HardMediaData.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.hard; import android.media.MediaCodec; import android.os.Build; import android.support.annotation.RequiresApi; import java.nio.ByteBuffer; /** * HardMediaData * * @author wuwang * @version v1.0 2017:10:28 16:53 */ public class HardMediaData { public int index=-1; public ByteBuffer data; public MediaCodec.BufferInfo info; public HardMediaData(ByteBuffer buffer, MediaCodec.BufferInfo info){ this.data=buffer; this.info=info; } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public void copyTo(HardMediaData data){ data.index=index; data.data.position(0); data.data.put(this.data); data.info.set(info.offset,info.size,info.presentationTimeUs,info.flags); } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public HardMediaData copy(){ ByteBuffer buffer=ByteBuffer.allocate(data.capacity()); MediaCodec.BufferInfo info=new MediaCodec.BufferInfo(); HardMediaData data=new HardMediaData(buffer,info); copyTo(data); return data; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/hard/IHardStore.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.hard; import android.media.MediaFormat; import com.wuwang.aavt.media.av.IStore; /** * IHardStore 硬编码的媒体文件存储器 * * @author wuwang * @version v1.0 2017:10:28 16:52 */ public interface IHardStore extends IStore { /** * 设置存储路径 * @param path 路径 */ void setOutputPath(String path); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/hard/MediaMuxerWraper.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.hard; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaMuxer; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import com.wuwang.aavt.log.AvLog; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * MediaMuxerWraper * * @author wuwang * @version v1.0 2017:11:08 11:07 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class MediaMuxerWraper{ private final String tag=getClass().getSimpleName(); private MediaMuxer mMuxer; private BlockingQueue datas; private Recycler recycler; private boolean isStarted=false; private int indexCount=0; private final Object Lock=new Object(); private ExecutorService mExec; public MediaMuxerWraper(String path, int format) throws IOException { mMuxer=new MediaMuxer(path,format); datas=new LinkedBlockingQueue<>(30); recycler=new Recycler<>(); ThreadFactory factory= Executors.defaultThreadFactory(); mExec=new ThreadPoolExecutor(1,1,1,TimeUnit.MINUTES,new LinkedBlockingQueue(16),factory); } private void muxRun(){ while (isStarted){ try { HardMediaData data=datas.poll(1, TimeUnit.SECONDS); AvLog.d(tag,"get HardMediaData from the queue"); synchronized (Lock){ if(isStarted){ mMuxer.writeSampleData(data.index, data.data, data.info); recycler.put(data.index,data); } } } catch (InterruptedException e) { e.printStackTrace(); } } AvLog.d(tag,"end -->"); mMuxer.stop(); mMuxer.release(); datas.clear(); recycler.clear(); } public void release(){ stop(); } public void start(){ synchronized (Lock){ if(!isStarted){ mMuxer.start(); isStarted=true; mExec.execute(new Runnable() { @Override public void run() { muxRun(); } }); } } } public void stop(){ synchronized (Lock){ indexCount=0; isStarted=false; } } @RequiresApi(api = Build.VERSION_CODES.KITKAT) public void setLocation(float latitude, float longitude){ mMuxer.setLocation(latitude, longitude); } public void writeSampleData(int trackIndex, @NonNull ByteBuffer byteBuf, @NonNull MediaCodec.BufferInfo bufferInfo){ HardMediaData hmd=recycler.poll(trackIndex); if(hmd==null){ ByteBuffer buffer=ByteBuffer.allocate(byteBuf.capacity()); MediaCodec.BufferInfo info=new MediaCodec.BufferInfo(); hmd=new HardMediaData(buffer,info); AvLog.d(tag,"buffer Size->"+buffer.capacity()); } AvLog.i(tag,"buffer Size->"+hmd.data.capacity()+"/data size:"+bufferInfo.size); hmd.data.position(0); hmd.data.put(byteBuf); hmd.info.set(bufferInfo.offset,bufferInfo.size,bufferInfo.presentationTimeUs,bufferInfo.flags); hmd.index=trackIndex; datas.offer(hmd); } public int addTrack(@NonNull MediaFormat format){ indexCount++; AvLog.d(tag,"addTrack -->"+indexCount); return mMuxer.addTrack(format); } public void setOrientationHint(int degrees){ mMuxer.setOrientationHint(degrees); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/hard/Mp4MuxStore.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.hard; import android.annotation.TargetApi; import android.media.MediaFormat; import android.media.MediaMuxer; import android.os.Build; import android.util.Log; import com.wuwang.aavt.media.av.AvException; import java.io.IOException; /** * Mp4MuxStore * * @author wuwang * @version v1.0 2017:10:28 17:48 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class Mp4MuxStore implements IHardStore { private MediaMuxer mMuxer; private final Object LOCK=new Object(); private String mPath; private int mAudioTrack=-1; private int mVideoTrack=-1; private boolean isMuxStart=false; private boolean waiAudio=true; public Mp4MuxStore(boolean waitAudio){ this.waiAudio=waitAudio; } @Override public int addTrack(MediaFormat mediaFormat) { synchronized (LOCK){ if(!isMuxStart){ if(mAudioTrack==-1&&mVideoTrack==-1){ try { mMuxer=new MediaMuxer(mPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (IOException e) { e.printStackTrace(); } } if(mMuxer!=null){ int ret=-1; String mime=mediaFormat.getString(MediaFormat.KEY_MIME); if(mime.startsWith("audio")){ ret = mMuxer.addTrack(mediaFormat); mAudioTrack=ret; }else if(mime.startsWith("video")){ ret = mMuxer.addTrack(mediaFormat); mVideoTrack=ret; if(mAudioTrack<0&&waiAudio){ try { LOCK.wait(2000); } catch (InterruptedException e) { e.printStackTrace(); } } mMuxer.start(); isMuxStart=true; Log.e("wuwang","add video track:"+ret); } return ret; } } } return -1; } @Override public int addData(int track, HardMediaData hardMediaData) { boolean canMux=isMuxStart&&(track==mAudioTrack||track==mVideoTrack); if(canMux){ mMuxer.writeSampleData(track,hardMediaData.data,hardMediaData.info); return 0; } return -1; } @Override public void setOutputPath(String path) { this.mPath=path; } @Override public void close() { synchronized (LOCK){ try { if(isMuxStart){ isMuxStart=false; mAudioTrack=-1; mVideoTrack=-1; mMuxer.stop(); mMuxer.release(); mMuxer=null; } }catch (IllegalStateException e){ e.printStackTrace(); } } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/hard/Recycler.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.hard; import android.util.SparseArray; import java.util.concurrent.LinkedBlockingQueue; /** * Recycler * * @author wuwang * @version v1.0 2017:11:08 16:16 */ class Recycler { private SparseArray> datas=new SparseArray<>(); public void put(int index,T t){ if(datas.indexOfKey(index)<0){ datas.append(index,new LinkedBlockingQueue()); } datas.get(index).add(t); } public T poll(int index){ if(datas.indexOfKey(index)>=0){ return datas.get(index).poll(); } return null; } public void clear(){ datas.clear(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/hard/StrengthenMp4MuxStore.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.hard; import android.annotation.TargetApi; import android.media.MediaFormat; import android.media.MediaMuxer; import android.os.Build; import android.util.Log; import com.wuwang.aavt.log.AvLog; import com.wuwang.aavt.media.av.AvException; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * StrengthenMp4MuxStore * * @author wuwang * @version v1.0 2017:11:08 17:15 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class StrengthenMp4MuxStore implements IHardStore { private final String tag=getClass().getSimpleName(); private MediaMuxer mMuxer; private final boolean av; private String path; private int audioTrack=-1; private int videoTrack=-1; private final Object Lock=new Object(); private boolean muxStarted=false; private LinkedBlockingQueue cache; private Recycler recycler; private ExecutorService exec; public StrengthenMp4MuxStore(boolean av){ this.av=av; cache=new LinkedBlockingQueue<>(30); recycler=new Recycler<>(); exec=new ThreadPoolExecutor(1,1,1, TimeUnit.MINUTES, new LinkedBlockingQueue(16), Executors.defaultThreadFactory() ); } @Override public void close(){ synchronized (Lock){ if(muxStarted){ audioTrack=-1; videoTrack=-1; muxStarted=false; } } } private void muxRun(){ AvLog.d(tag,"enter mux loop"); while (muxStarted){ try { HardMediaData data=cache.poll(50, TimeUnit.MILLISECONDS); synchronized (Lock){ AvLog.d(tag,"data is null?"+(data==null)); if(muxStarted&&data!=null){ mMuxer.writeSampleData(data.index, data.data, data.info); recycler.put(data.index,data); } } } catch (InterruptedException e) { e.printStackTrace(); break; } } try { mMuxer.stop(); AvLog.d(tag,"muxer stoped success"); mMuxer.release(); }catch (IllegalStateException e){ e.printStackTrace(); AvLog.e("stop muxer failed!!!"); } mMuxer=null; cache.clear(); recycler.clear(); } @Override public int addTrack(MediaFormat mediaFormat) { int ret=-1; synchronized (Lock){ if(!muxStarted){ if(audioTrack==-1&&videoTrack==-1){ try { mMuxer=new MediaMuxer(path,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (IOException e) { e.printStackTrace(); AvLog.e("create MediaMuxer failed:"+e.getMessage()); } } String mime=mediaFormat.getString(MediaFormat.KEY_MIME); if(mime.startsWith("audio")){ audioTrack=mMuxer.addTrack(mediaFormat); ret=audioTrack; }else if(mime.startsWith("video")){ videoTrack=mMuxer.addTrack(mediaFormat); ret=videoTrack; } startMux(); } } return ret; } private void startMux(){ boolean canMux=!av||(audioTrack!=-1&&videoTrack!=-1); if(canMux){ mMuxer.start(); muxStarted=true; exec.execute(new Runnable() { @Override public void run() { muxRun(); } }); } } @Override public int addData(int track, HardMediaData hardMediaData) { if(track>=0){ AvLog.d(tag,"addData->"+track+"/"+audioTrack+"/"+videoTrack); hardMediaData.index=track; if(track==audioTrack||track==videoTrack){ HardMediaData d=recycler.poll(track); if(d==null){ d=hardMediaData.copy(); }else{ hardMediaData.copyTo(d); } while (!cache.offer(d)){ AvLog.d(tag,"put data to the cache : poll"); HardMediaData c=cache.poll(); recycler.put(c.index,c); } } } return 0; } @Override public void setOutputPath(String path) { this.path=path; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/AudioDecoder.java ================================================ package com.wuwang.aavt.media.player; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Build; import java.nio.ByteBuffer; /** * @author wuwang * @version 1.00 , 2018/09/27 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) class AudioDecoder implements Runnable { private MediaExtractor extractor; private MediaCodec codec; private boolean threadFlag = false; private Thread thread; private long seekTime; private final Object SEEK_LOCK = new Object(); private int timeout = 1000; private MediaCodec.BufferInfo info; private IAudioProcessor processor; AudioDecoder(MediaExtractor extractor, MediaCodec codec){ this.extractor = extractor; this.codec = codec; this.info = new MediaCodec.BufferInfo(); } public void setAudioFormatChanged(IAudioProcessor processor){ this.processor = processor; } public void init(){ threadFlag = true; thread = new Thread(this); thread.start(); } public void seekTo(long time){ synchronized (SEEK_LOCK){ this.seekTime = time; } } private void checkAndDoSeek(){ if(seekTime >= 0){ synchronized (SEEK_LOCK){ if(seekTime >=0 ){ extractor.seekTo(seekTime,MediaExtractor.SEEK_TO_PREVIOUS_SYNC); } } } } private ByteBuffer getInputBuffer(MediaCodec codec, int index){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return codec.getInputBuffer(index); }else{ return codec.getInputBuffers()[index]; } } private ByteBuffer getOutputBuffer(MediaCodec codec, int index){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return codec.getOutputBuffer(index); }else{ return codec.getOutputBuffers()[index]; } } public void release(){ threadFlag = false; } @Override public void run() { while (threadFlag){ checkAndDoSeek(); int inputIndex = codec.dequeueInputBuffer(timeout); if(inputIndex >= 0){ ByteBuffer inputBuffer = getInputBuffer(codec,inputIndex); inputBuffer.position(0); int size = extractor.readSampleData(inputBuffer,0); if(size == -1){ //todo 最后一帧处理 codec.queueInputBuffer(inputIndex,0,0,extractor.getSampleTime(),MediaCodec.BUFFER_FLAG_END_OF_STREAM); }else{ codec.queueInputBuffer(inputIndex,0,size,extractor.getSampleTime(),extractor.getSampleFlags()); } } while(true){ int outputIndex = codec.dequeueOutputBuffer(info,timeout); if (outputIndex >= 0) { if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { ByteBuffer outputBuffer = getOutputBuffer(codec,outputIndex); if(processor != null){ ByteBufferData data = new ByteBufferData(); data.data = outputBuffer; data.length = info.size; data.offset = info.offset; data.timeUs = info.presentationTimeUs; data.flag = info.flags; processor.processAudioData(data); } codec.releaseOutputBuffer(outputIndex, false); if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { //todo 最后一帧处理 break; } } else { codec.releaseOutputBuffer(outputIndex, false); } } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat format = codec.getOutputFormat(); if(processor != null){ int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); processor.onAudioFormatChanged(sampleRate,channelCount); } } else if(outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER){ break; } } } } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/AudioPlayer.java ================================================ package com.wuwang.aavt.media.player; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.os.Build; /** * @author wuwang * @version 1.00 , 2018/09/28 */ public class AudioPlayer implements IAudioProcessor,ITimeObserver{ private AudioTrack track; private float volume = 1.0f; private int channelCount; private int sampleRate; private float tempo = 1.0f; private float pitchSemi = 1.0f; private float rate = 1.0f; private int audioTrackMinBuffer; private int writtenPcmByteCount; private int currentTimeUs; // private SoundTouch soundTouch; private byte[] soundTouchBuffer; private ITimeObserver observer; private boolean resetTimeWhenRepeat = true; private long lastTimePts; public void setVolume(float volume){ if(track != null){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { track.setVolume(volume); }else{ track.setStereoVolume(volume,volume); } } this.volume = volume; } public void setTimeObserver(ITimeObserver observer){ this.observer = observer; } public void init(int sampleRate, int channelCount){ int channelFormat = channelCount == 1 ? AudioFormat.CHANNEL_OUT_MONO:AudioFormat.CHANNEL_OUT_STEREO; this.channelCount = channelCount; this.sampleRate = sampleRate; this.audioTrackMinBuffer = AudioTrack.getMinBufferSize(sampleRate, channelFormat, AudioFormat.ENCODING_PCM_16BIT); if (audioTrackMinBuffer <= 0) { audioTrackMinBuffer = sampleRate * channelCount * 2 * 100 / 1000; } track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelFormat, AudioFormat.ENCODING_PCM_16BIT, audioTrackMinBuffer, AudioTrack.MODE_STREAM); setVolume(volume); track.play(); } private long pcmBufferSizeToDurationUs(long bufferSize) { final int frameSize = 2 * channelCount; return (long) (bufferSize / frameSize * 1000000 * tempo / sampleRate); } private void writePcmToAudioTrack(ByteBufferData data) { if (pitchSemi == 1.0f && rate == 1.0f && tempo == 1.0f) { int offset = 0; int leftSize = data.length; long dataPlayTime = pcmBufferSizeToDurationUs(data.length); while (true) { int currentSize = Math.min(leftSize,audioTrackMinBuffer); update(data.timeUs + dataPlayTime - (long)(leftSize/(float)data.length * dataPlayTime)); if (track != null && track.getPlayState() != AudioTrack.PLAYSTATE_STOPPED) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { track.write(data.data,currentSize,AudioTrack.WRITE_BLOCKING); }else{ track.write(data.data.array(),data.offset+offset,currentSize); } writtenPcmByteCount += currentSize; currentTimeUs += pcmBufferSizeToDurationUs(currentSize); } offset += currentSize; leftSize -= currentSize; if (leftSize <= 0) { break; } } } // else { // if(soundTouch == null){ // soundTouch = new SoundTouch(0, channelCount, sampleRate, // 2, tempo, pitchSemi); // soundTouch.setRate(rate); // soundTouch.setup(); // soundTouch.setTempo(tempo); // } // byte[] t = new byte[data.length]; // data.data.get(t,data.offset,data.length); // soundTouch.putBytes(t); // if (soundTouchBuffer == null) { // soundTouchBuffer = new byte[audioTrackMinBuffer]; // } // if (soundTouch != null) { // int bytesReceived = soundTouch.getBytes(soundTouchBuffer); // while (bytesReceived > 0) { // if (track != null) { // track.write(soundTouchBuffer, 0, bytesReceived); // writtenPcmByteCount += bytesReceived; // currentTimeUs += pcmBufferSizeToDurationUs(bytesReceived); // //todo 计算精准时间 // update(data.timeUs); // } // bytesReceived = soundTouch.getBytes(soundTouchBuffer); // } // } // } } public long getCurrentTime(){ return currentTimeUs; } @Override public void onAudioFormatChanged(int sampleRate, int channelCount) { init(sampleRate,channelCount); } @Override public ByteBufferData processAudioData(ByteBufferData data) { if(resetTimeWhenRepeat && data.timeUs < lastTimePts){ currentTimeUs = 0; } lastTimePts = data.timeUs; writePcmToAudioTrack(data); return data; } @Override public void release() { if(track != null){ track.stop(); track.release(); track = null; } } @Override public boolean update(long time) { return observer == null || observer.update(time); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/BaseAudioDecoder.java ================================================ package com.wuwang.aavt.media.player; import android.media.MediaExtractor; import android.media.MediaFormat; /** * @author wuwang * @version 1.00 , 2018/09/28 */ public abstract class BaseAudioDecoder implements IDecoder,ITimeObserver { protected IAudioProcessor processor; protected MediaExtractor extractor; protected MediaFormat format; protected long seekToTimeUs = -1; protected ITimeObserver observer; public boolean loop = false; public void setAudioProcessor(IAudioProcessor processor){ this.processor = processor; } public void setTimeObserver(ITimeObserver observer){ this.observer = observer; } public void init(MediaExtractor extractor, MediaFormat codec) { this.extractor = extractor; this.format = codec; } @Override public void seekTo(long timeUs) { this.seekToTimeUs = timeUs; } public void setLoop(boolean loop){ this.loop = loop; } @Override public boolean update(long time) { if(observer != null){ observer.update(time); } return true; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/BaseVideoDecoder.java ================================================ package com.wuwang.aavt.media.player; import android.graphics.SurfaceTexture; import android.media.MediaExtractor; import android.media.MediaFormat; import android.view.Surface; /** * @author wuwang * @version 1.00 , 2018/09/28 */ public abstract class BaseVideoDecoder implements IDecoder,ITimeObserver { protected ITextureProcessor processor; protected MediaExtractor extractor; protected MediaFormat format; protected long seekToTimeUs = -1; protected Surface surface; public BaseVideoDecoder(){ } public void init(MediaExtractor extractor, MediaFormat codec, SurfaceTexture texture){ this.extractor = extractor; this.format = codec; this.surface = new Surface(texture); } public void setTextureProcessor(ITextureProcessor processor){ this.processor = processor; } @Override public void seekTo(long timeUs) { this.seekToTimeUs = timeUs; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/ByteBufferData.java ================================================ package com.wuwang.aavt.media.player; import java.nio.ByteBuffer; /** * @author wuwang * @version 1.00 , 2018/09/28 */ public class ByteBufferData{ public ByteBuffer data; public int offset; public int length; public long timeUs; public int flag; } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/EPlayerException.java ================================================ package com.wuwang.aavt.media.player; /** * @author wuwang * @version 1.00 , 2018/10/05 */ public class EPlayerException extends RuntimeException { public EPlayerException(String message){ super(message); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/EffectMediaPlayer.java ================================================ package com.wuwang.aavt.media.player; import android.annotation.TargetApi; import android.media.MediaExtractor; import android.media.MediaFormat; import android.media.MediaMetadataRetriever; import android.os.Build; import android.text.TextUtils; import com.wuwang.aavt.core.Renderer; import java.io.IOException; /** * @author wuwang * @version 1.00 , 2018/09/27 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class EffectMediaPlayer { private static final String YES = "yes"; private String path; private Object surface; private BaseAudioDecoder audioDecoder; private BaseVideoDecoder videoDecoder; private MediaExtractor audioExtractor; private MediaExtractor videoExtractor; private MediaFormat videoFormat; private MediaFormat audioFormat; private long durationUs = 0; private int videoWidth = -1; private int videoHeight = -1; private AudioPlayer audioPlayer; private VideoPlayer videoPlayer; public EffectMediaPlayer(){ audioPlayer = new AudioPlayer(); audioDecoder = new NormalAudioDecoder(); audioDecoder.setLoop(true); audioDecoder.setAudioProcessor(audioPlayer); videoPlayer = new VideoPlayer(); videoDecoder = new NormalVideoDecoder(); videoDecoder.setTextureProcessor(videoPlayer); //时钟同步 audioPlayer.setTimeObserver(videoDecoder); } public void setDataSource(String filePath) throws IOException { this.path = filePath; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(filePath); String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); if(!TextUtils.isEmpty(duration) && TextUtils.isDigitsOnly(duration)){ durationUs = Long.parseLong(duration); } MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(filePath); videoWidth = 0; videoHeight = 0; for (int i=0;i= Build.VERSION_CODES.M && format.containsKey(MediaFormat.KEY_ROTATION)) { degrees = format.getInteger(MediaFormat.KEY_ROTATION); } if(degrees%180 == 0){ videoWidth = format.getInteger(MediaFormat.KEY_WIDTH); videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT); }else{ videoHeight = format.getInteger(MediaFormat.KEY_WIDTH); videoWidth = format.getInteger(MediaFormat.KEY_HEIGHT); } }else if(mime.startsWith("audio")){ audioExtractor = new MediaExtractor(); audioExtractor.setDataSource(path); audioExtractor.selectTrack(i); audioFormat = format; if(audioFormat.containsKey(MediaFormat.KEY_DURATION)){ durationUs = audioFormat.getLong(MediaFormat.KEY_DURATION); } } } } public int getVideoWidth(){ return videoWidth; } public int getVideoHeight(){ return videoHeight; } public void setSurface(Object surface){ this.surface = surface; videoPlayer.setSurface(surface); videoPlayer.requestRender(); } public void destroySurface(){ videoPlayer.destroySurface(); } public void setSurfaceSize(int width,int height){ videoPlayer.setSurfaceSize(width, height); } public void prepare(){ videoPlayer.init(); audioDecoder.init(audioExtractor,audioFormat); videoDecoder.init(videoExtractor,videoFormat,videoPlayer.getInputSurfaceTexture()); } public void setRenderer(Renderer renderer){ videoPlayer.setRenderer(renderer); } public void pause() { if(audioDecoder != null){ audioDecoder.pause(); } if(videoDecoder != null){ videoDecoder.pause(); } } public void start() { if(audioDecoder != null){ audioDecoder.start(); } if(videoDecoder != null){ videoDecoder.start(); } } public void stop() { if(audioDecoder != null){ audioDecoder.stop(); } if(videoDecoder != null){ videoDecoder.stop(); } } public void release(){ videoPlayer.release(); } public void seekTo(long timeUs) { if(audioDecoder != null){ audioDecoder.seekTo(timeUs); } if(videoDecoder != null){ videoDecoder.seekTo(timeUs); } } public long getDuration(){ return durationUs; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/EffectMediaView.java ================================================ package com.wuwang.aavt.media.player; import android.content.Context; import android.content.res.TypedArray; import android.graphics.SurfaceTexture; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.RelativeLayout; import com.wuwang.aavt.R; import com.wuwang.aavt.core.Renderer; import java.io.IOException; /** * @author wuwang * @version 1.00 , 2018/09/30 */ public class EffectMediaView extends RelativeLayout { public static int TYPE_SURFACE = 0; public static int TYPE_TEXTURE = 1; private int type = TYPE_TEXTURE; private ImageView.ScaleType scaleType = ImageView.ScaleType.CENTER_INSIDE; private String path; private boolean isResume; private EffectMediaPlayer player; private LayoutParams screenLayoutParams; private View screenView; private boolean isUserWantStart = false; public EffectMediaView(Context context) { this(context,null); } public EffectMediaView(Context context, AttributeSet attrs) { this(context, attrs,0); } public EffectMediaView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context,attrs); } private void init(Context context, AttributeSet attrs){ if(attrs != null){ TypedArray tArray = context.obtainStyledAttributes(attrs, R.styleable.EffectMediaView); type = tArray.getInt(R.styleable.EffectMediaView_aavt_media_surface_type,0); tArray.recycle(); } player = new EffectMediaPlayer(); screenLayoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); screenLayoutParams.addRule(CENTER_IN_PARENT); } private void invalidatePlayerScreen(){ if(!isResume || player.getVideoWidth() <= 0 || player.getVideoHeight() <= 0){ return; } int viewWidth = getWidth(); int viewHeight = getHeight(); if(scaleType == ImageView.ScaleType.CENTER_INSIDE && viewWidth != 0 && viewHeight != 0){ float viewRatio = viewWidth/(float)viewHeight; float videoRatio = player.getVideoWidth()/(float)player.getVideoHeight(); screenLayoutParams.width = viewWidth; screenLayoutParams.height = (int) (viewWidth/videoRatio); } if(screenView != null){ screenView.setLayoutParams(screenLayoutParams); screenView.invalidate(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); invalidatePlayerScreen(); } private void useTextureViewAsScreen(){ TextureView texture = new TextureView(getContext()); texture.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { player.setSurface(surface); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { player.setSurface(surface); player.setSurfaceSize(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { player.destroySurface(); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }); texture.setBackgroundColor(0xFF880088); addView(texture,screenLayoutParams); screenView = texture; } private void useSurfaceViewAsScreen(){ SurfaceView surface = new SurfaceView(getContext()); surface.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { player.setSurface(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { player.setSurfaceSize(width, height); } @Override public void surfaceDestroyed(SurfaceHolder holder) { player.destroySurface(); } }); addView(surface,screenLayoutParams); screenView = surface; } public void setRenderer(Renderer renderer){ player.setRenderer(renderer); } private void tryToInit(){ if(player.getVideoWidth() < 0 || player.getVideoHeight() < 0){ return; } if(TYPE_SURFACE == type){ useSurfaceViewAsScreen(); }else if(TYPE_TEXTURE == type){ useTextureViewAsScreen(); } player.prepare(); } public void setDataSource(String path){ this.path = path; try { player.setDataSource(path); } catch (IOException e) { e.printStackTrace(); } tryToInit(); invalidatePlayerScreen(); } public void start(){ player.start(); isUserWantStart = true; } public void stop(){ player.stop(); isUserWantStart = false; } public void pause(){ player.pause(); isUserWantStart = false; } public void seekTo(long timeMs){ player.seekTo(timeMs * 1000); } public long getDuration(){ return player.getDuration(); } public void onResume(){ isResume = true; invalidatePlayerScreen(); if(isUserWantStart){ player.start(); } } public void onPause(){ isResume = false; player.pause(); } public void onDestroy(){ player.stop(); player.release(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/IAudioProcessor.java ================================================ package com.wuwang.aavt.media.player; /** * 处理音频数据接口 * @author wuwang * @version 1.00 , 2018/09/28 */ public interface IAudioProcessor { /** * 音频信息发生改变 * @param sampleRate 音频采样率 * @param channelCount 音频通道数 */ void onAudioFormatChanged(int sampleRate, int channelCount); /** * 处理音频数据 * @param data 输入的音频数据 * @return 处理后的数据 */ ByteBufferData processAudioData(ByteBufferData data); /** * 销毁音频处理相关资源 */ void release(); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/IDecoder.java ================================================ package com.wuwang.aavt.media.player; /** * @author wuwang * @version 1.00 , 2018/09/28 */ public interface IDecoder { /** * seek到指定位置再解码 * @param timeUs 时间us */ void seekTo(long timeUs); /** * 开始解码 */ void start(); /** * 暂停解码 */ void pause(); /** * 停止解码 */ void stop(); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/ITextureProcessor.java ================================================ package com.wuwang.aavt.media.player; /** * @author wuwang * @version 1.00 , 2018/10/05 */ public interface ITextureProcessor { int process(int textureId); void release(); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/ITimeObserver.java ================================================ package com.wuwang.aavt.media.player; /** * @author wuwang * @version 1.00 , 2018/10/10 */ public interface ITimeObserver { /** * 通知观察者当前时间 * @param time 时间戳 * @return 观察者是否处理完成 */ boolean update(long time); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/NormalAudioDecoder.java ================================================ package com.wuwang.aavt.media.player; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Build; import com.wuwang.aavt.media.CodecUtil; import java.io.IOException; import java.nio.ByteBuffer; /** * 正常流程的音频解码 * @author wuwang * @version 1.00 , 2018/09/28 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class NormalAudioDecoder extends BaseAudioDecoder implements Runnable { private boolean threadFlag = false; private Thread thread; private int timeout = 1000; private MediaCodec codec; private MediaCodec.BufferInfo info; private boolean pause; private final Object LOCK = new Object(); public NormalAudioDecoder(){ info = new MediaCodec.BufferInfo(); } @Override public void init(MediaExtractor extractor, MediaFormat codec) { super.init(extractor, codec); if(seekToTimeUs == -1){ seekTo(0); } pause = true; threadFlag = true; thread = new Thread(this); thread.start(); } @Override public void start() { if(threadFlag){ synchronized (LOCK){ pause = false; LOCK.notifyAll(); } }else{ if(seekToTimeUs == -1){ seekTo(0); } pause = false; threadFlag = true; thread = new Thread(this); thread.start(); } } @Override public void pause() { synchronized (LOCK){ this.pause = true; } } @Override public void stop() { threadFlag = false; synchronized (LOCK){ pause = false; LOCK.notifyAll(); } if(thread != null){ try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void seekTo(long timeUs) { synchronized (LOCK){ super.seekTo(timeUs); //当前如果是暂停状态需要跳过暂停 LOCK.notifyAll(); } } private void checkAndDoSeek(){ if(seekToTimeUs >= 0){ synchronized (LOCK){ extractor.seekTo(seekToTimeUs,MediaExtractor.SEEK_TO_PREVIOUS_SYNC); seekToTimeUs = -1; } } } private boolean checkAndDoPause(){ if(pause){ synchronized (LOCK){ try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return true; } return false; } @Override public void run() { try { codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)); } catch (IOException e) { e.printStackTrace(); return; } codec.configure(format,null,null,0); codec.start(); while (threadFlag){ checkAndDoSeek(); if(checkAndDoPause()){ continue; } int inputIndex = codec.dequeueInputBuffer(timeout); if(inputIndex >= 0){ ByteBuffer inputBuffer = CodecUtil.getInputBuffer(codec,inputIndex); inputBuffer.position(0); int size = extractor.readSampleData(inputBuffer,0); if(size == -1){ //todo 最后一帧处理 codec.queueInputBuffer(inputIndex,0,0,extractor.getSampleTime(),MediaCodec.BUFFER_FLAG_END_OF_STREAM); }else{ codec.queueInputBuffer(inputIndex,0,size,extractor.getSampleTime(),extractor.getSampleFlags()); } extractor.advance(); } while(true){ int outputIndex = codec.dequeueOutputBuffer(info,timeout); if (outputIndex >= 0) { if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { ByteBuffer outputBuffer = CodecUtil.getOutputBuffer(codec,outputIndex); if(processor != null){ ByteBufferData data = new ByteBufferData(); data.data = outputBuffer; data.length = info.size; data.offset = info.offset; data.timeUs = info.presentationTimeUs; data.flag = info.flags; processor.processAudioData(data); update(data.timeUs); } codec.releaseOutputBuffer(outputIndex, false); if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { //todo 最后一帧处理 if(loop){ seekTo(0); codec.flush(); }else{ threadFlag = false; } break; } } else { codec.releaseOutputBuffer(outputIndex, false); } } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat format = codec.getOutputFormat(); if(processor != null){ int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); processor.onAudioFormatChanged(sampleRate,channelCount); } } else if(outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER){ break; } } } codec.stop(); codec.release(); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/NormalVideoDecoder.java ================================================ package com.wuwang.aavt.media.player; import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Build; import com.wuwang.aavt.media.CodecUtil; import java.io.IOException; import java.nio.ByteBuffer; /** * @author wuwang * @version 1.00 , 2018/09/28 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class NormalVideoDecoder extends BaseVideoDecoder implements Runnable { private boolean threadFlag = false; private Thread thread; private int timeout = 1000; private boolean pause; private long currentPtsTime; private long currentReferenceTime; private final Object CONTROL_LOCK = new Object(); private final Object DECODE_LOCK = new Object(); private final Object SEEK_LOCK = new Object(); private boolean loop = true; private boolean isFirstFrameEmpty = true; private final long SEEK_LIMIT_TIME = 400000; private long tryToSeekTo = -1; public NormalVideoDecoder(){ } @Override public void init(MediaExtractor extractor, MediaFormat codec, SurfaceTexture texture) { super.init(extractor, codec, texture); if(seekToTimeUs == -1){ seekTo(0); } pause = true; threadFlag = true; thread = new Thread(this); thread.start(); } @Override public void start() { if(threadFlag){ synchronized (CONTROL_LOCK){ pause = false; CONTROL_LOCK.notifyAll(); } }else{ if(seekToTimeUs == -1){ seekTo(0); } pause = false; threadFlag = true; thread = new Thread(this); thread.start(); } } @Override public void pause() { synchronized (CONTROL_LOCK){ this.pause = true; } } @Override public void stop() { threadFlag = false; synchronized (CONTROL_LOCK){ pause = false; CONTROL_LOCK.notifyAll(); } synchronized (DECODE_LOCK){ DECODE_LOCK.notifyAll(); } try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } currentPtsTime = 0; currentReferenceTime = 0; } @Override public void seekTo(long timeUs) { synchronized (CONTROL_LOCK){ super.seekTo(timeUs); //当前如果是暂停状态需要跳过暂停 CONTROL_LOCK.notifyAll(); } //当前如果是等待解码状态需要跳过等待,seek后更新图像 synchronized (DECODE_LOCK){ DECODE_LOCK.notifyAll(); } isFirstFrameEmpty = true; } private void checkAndDoSeek(MediaCodec codec){ if(seekToTimeUs >= 0 && tryToSeekTo == -1){ synchronized (CONTROL_LOCK){ codec.flush(); extractor.seekTo(seekToTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); tryToSeekTo = seekToTimeUs; seekToTimeUs = -1; } } } private boolean checkAndDoPause(){ if(pause && !isFirstFrameEmpty){ synchronized (CONTROL_LOCK){ try { CONTROL_LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return true; } return false; } private void codecInput(MediaCodec codec){ int inputIndex = codec.dequeueInputBuffer(timeout); if(inputIndex >= 0){ ByteBuffer inputBuffer = CodecUtil.getInputBuffer(codec,inputIndex); inputBuffer.position(0); int size = extractor.readSampleData(inputBuffer,0); if(size == -1){ //todo 最后一帧处理 codec.queueInputBuffer(inputIndex,0,0,extractor.getSampleTime(),MediaCodec.BUFFER_FLAG_END_OF_STREAM); }else{ codec.queueInputBuffer(inputIndex,0,size,extractor.getSampleTime(),extractor.getSampleFlags()); } extractor.advance(); } } @Override public void run() { MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); MediaCodec codec; try { codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)); } catch (IOException e) { e.printStackTrace(); return; } codec.configure(format,surface,null,0); codec.start(); //todo 处理帧率非常高,图像处理又比较复杂比较耗时的情况。 while (threadFlag){ checkAndDoSeek(codec); if(checkAndDoPause()){ continue; } codecInput(codec); while(true){ int outputIndex = codec.dequeueOutputBuffer(info,timeout); if (outputIndex >= 0) { currentPtsTime = info.presentationTimeUs; //处理为精准seek,如果seek到的位置解码还未到目标位置,不更新texture,继续下一帧解码 if(tryToSeekTo != -1){ synchronized (CONTROL_LOCK){ if(currentPtsTime < tryToSeekTo){ codec.releaseOutputBuffer(outputIndex, false); continue; } } synchronized (SEEK_LOCK){ tryToSeekTo = -1; SEEK_LOCK.notifyAll(); } } //精准seek完成,或者是正常的音频同步解码,需要更新输出纹理 codec.releaseOutputBuffer(outputIndex, true); if(isFirstFrameEmpty){ //如果是seek完成的第一帧,需要立即处理 if(processor != null){ processor.process(0); } isFirstFrameEmpty = false; }else{ //音频同步解码流程中,如果视频解码到了音频前面,则需要等待音频解码 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 0 && currentPtsTime > currentReferenceTime) { synchronized (DECODE_LOCK){ if(threadFlag){ try { DECODE_LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } if(processor != null && currentReferenceTime - currentPtsTime < SEEK_LIMIT_TIME){ processor.process(0); } } if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { if(loop) { seekTo(0); }else{ threadFlag = false; } break; } } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat format = codec.getOutputFormat(); } else if(outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER){ break; } } } codec.flush(); codec.stop(); codec.release(); } @Override public boolean update(long time) { if(tryToSeekTo != -1){ synchronized (SEEK_LOCK){ try { SEEK_LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } //ITimeObserver的接口,通常由音频解码器或者播放器调用,以达到音视频同步的目的 currentReferenceTime = time; long interval = currentPtsTime - currentReferenceTime; if(interval > SEEK_LIMIT_TIME){ if(seekToTimeUs == -1 && tryToSeekTo == -1){ //视频解码太前时,seek回音频播放处,主要用于处理视频总长度大于音频的情况 seekTo(currentReferenceTime); synchronized (DECODE_LOCK){ DECODE_LOCK.notifyAll(); } } }else if(interval < 0){ //视频时间戳小于音频时间戳时,通知处理并继续解码下一帧 synchronized (DECODE_LOCK){ DECODE_LOCK.notifyAll(); } } return true; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/player/VideoPlayer.java ================================================ package com.wuwang.aavt.media.player; import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.opengl.EGLSurface; import android.opengl.GLES20; import android.os.Build; import android.view.Surface; import android.view.SurfaceHolder; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.egl.EGLConfigAttrs; import com.wuwang.aavt.egl.EGLContextAttrs; import com.wuwang.aavt.egl.EglHelper; import com.wuwang.aavt.gl.FrameBuffer; import com.wuwang.aavt.gl.LazyFilter; import com.wuwang.aavt.media.WrapRenderer; import com.wuwang.aavt.utils.GpuUtils; import com.wuwang.aavt.utils.MatrixUtils; /** * @author wuwang * @version 1.00 , 2018/09/29 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class VideoPlayer implements Runnable,ITextureProcessor{ private boolean threadFlag = false; private Thread thread; private SurfaceTexture surfaceTexture; private int surfaceTextureId; private Object surface; private boolean surfaceChanged = false; private boolean textureUpdate = false; private final Object SURFACE_LOCK = new Object(); private final Object THREAD_LOCK = new Object(); private final Object RENDER_LOCK = new Object(); private int width; private int height; private Renderer renderer; private boolean isRendererChanged = false; private boolean isSurfaceSizeChanged = false; public VideoPlayer(){ } public void setSurface(Object surface){ if(surface instanceof Surface || surface instanceof SurfaceTexture || surface instanceof SurfaceHolder){ synchronized (SURFACE_LOCK){ if(this.surface != surface){ this.surface = surface; surfaceChanged = true; SURFACE_LOCK.notifyAll(); } } } } public void destroySurface(){ synchronized (SURFACE_LOCK){ if(this.surface != null){ surface = null; surfaceChanged = true; } } } public void setSurfaceSize(int width,int height){ this.width = width; this.height = height; this.isSurfaceSizeChanged = true; } public void init(){ synchronized (THREAD_LOCK){ threadFlag = true; thread = new Thread(this); thread.start(); try { THREAD_LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void setRenderer(Renderer renderer){ this.renderer = renderer; this.isRendererChanged = true; } public SurfaceTexture getInputSurfaceTexture(){ return surfaceTexture; } @Override public int process(int textureId) { synchronized (RENDER_LOCK){ textureUpdate = true; RENDER_LOCK.notifyAll(); } return 0; } public void requestRender(){ synchronized (RENDER_LOCK){ RENDER_LOCK.notifyAll(); } } @Override public void release(){ threadFlag = false; synchronized (SURFACE_LOCK){ SURFACE_LOCK.notifyAll(); } synchronized (RENDER_LOCK){ RENDER_LOCK.notifyAll(); } } private WrapRenderer checkWrapRenderer(WrapRenderer renderer){ if(isRendererChanged){ isRendererChanged = false; renderer.destroy(); renderer = new WrapRenderer(this.renderer); renderer.create(); renderer.sizeChanged(width,height); } return renderer; } @Override public void run() { SurfaceTexture temp = new SurfaceTexture(0); EglHelper helper = new EglHelper(); helper.createGLESWithSurface(new EGLConfigAttrs(),new EGLContextAttrs(),temp); surfaceTextureId = GpuUtils.createTextureID(true); surfaceTexture = new SurfaceTexture(surfaceTextureId); synchronized (THREAD_LOCK){ THREAD_LOCK.notifyAll(); } synchronized (SURFACE_LOCK){ if(surface == null || !surfaceChanged){ try { SURFACE_LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } surfaceChanged = false; } } if(!threadFlag){ return; } EGLSurface showSurface = helper.createWindowSurface(surface); if(showSurface == EGL14.EGL_NO_SURFACE){ //todo report error return; } FrameBuffer buffer = new FrameBuffer(); LazyFilter matrixFilter = new LazyFilter(); MatrixUtils.flip(matrixFilter.getVertexMatrix(),false,true); matrixFilter.create(); matrixFilter.sizeChanged(width,height); WrapRenderer renderer = new WrapRenderer(null); renderer.create(); while (threadFlag){ synchronized (RENDER_LOCK){ try { RENDER_LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } if(!threadFlag){ break; } if(textureUpdate){ surfaceTexture.updateTexImage(); textureUpdate = false; } } renderer = checkWrapRenderer(renderer); if(surfaceChanged && surface!=null){ helper.destroySurface(showSurface); showSurface = helper.createWindowSurface(surface); surfaceChanged = false; } if(isSurfaceSizeChanged){ isSurfaceSizeChanged = false; renderer.sizeChanged(width,height); } helper.makeCurrent(showSurface); GLES20.glViewport(0,0,width,height); GLES20.glClearColor(0,1,0,1); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); surfaceTexture.getTransformMatrix(renderer.getTextureMatrix()); buffer.bindFrameBuffer(width,height); renderer.draw(surfaceTextureId); buffer.unBindFrameBuffer(); matrixFilter.draw(buffer.getCacheTextureId()); helper.swapBuffers(showSurface); } renderer.destroy(); surfaceTexture.release(); temp.release(); helper.destroyGLES(helper.getDefaultSurface(),helper.getDefaultContext()); } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/video/Camera2Provider.java ================================================ package com.wuwang.aavt.media.video; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Point; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.support.annotation.NonNull; import android.util.Size; import android.view.Surface; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Semaphore; /** * @author wuwang * @version 1.00 , 2018/11/14 */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class Camera2Provider extends CameraDevice.StateCallback implements ITextureProvider { private CameraDevice camera; private CameraManager manager; private String[] ids; private String[] cameraId; private Size[] previewSize; private int minWidth = 720; private float rate = 1.67f; private int nowCamera = 1; private SurfaceTexture surface; private HandlerThread thread; private Handler cameraHandler; private CaptureRequest.Builder captureRequestBuilder; private CameraCaptureSession captureSession; private Semaphore mFrameSem; public Camera2Provider(Context context){ manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); cameraId = new String[2]; previewSize = new Size[2]; thread = new HandlerThread("camera handler thread"); thread.start(); cameraHandler = new Handler(thread.getLooper()); } public void switchCamera(){ this.nowCamera ^= 1; close(); open(surface); } @SuppressLint("MissingPermission") @Override public Point open(SurfaceTexture surface) { try { mFrameSem=new Semaphore(0); ids = manager.getCameraIdList(); for (String id:ids){ CameraCharacteristics characteristics = manager.getCameraCharacteristics(id); Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); int cameraIndex = -1; if(facing != null){ cameraIndex = facing; } if(cameraIndex != 0 && cameraIndex != 1){ return null; } cameraId[cameraIndex] = id; //选择合适的预览尺寸 StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if(map == null){ return null; } Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class); List sizes = Arrays.asList(previewSizes); Collections.sort(sizes,new Comparator() { @Override public int compare(Size o1, Size o2) { return o1.getWidth() - o2.getWidth(); } }); for (Size s : sizes) { if (s.getHeight() >= minWidth) { boolean isSuit = (rate >1.4f && s.getWidth() / (float) s.getHeight() > 1.4) || (rate < 1.4f &&s.getWidth() / (float) s.getHeight() < 1.4); if (isSuit) { previewSize[cameraIndex] = s; break; } } } if(previewSize[cameraIndex] == null){ previewSize[cameraIndex] = previewSizes[0]; } } if(cameraId[0] == null && cameraId[1] == null){ return new Point(0,0); }else if(cameraId[0] == null){ cameraId[0] = cameraId[1]; }else if(cameraId[1] == null){ cameraId[1] = cameraId[0]; } surface.setDefaultBufferSize(previewSize[nowCamera].getWidth(),previewSize[nowCamera].getHeight()); manager.openCamera(cameraId[nowCamera],this,cameraHandler); this.surface = surface; surface.setOnFrameAvailableListener(frameListener); return new Point(previewSize[nowCamera].getHeight(),previewSize[nowCamera].getWidth()); } catch (CameraAccessException e) { e.printStackTrace(); } return null; } @Override public void close() { mFrameSem.drainPermits(); mFrameSem.release(); if (captureSession != null){ captureSession.close(); captureSession = null; } if (camera != null){ camera.close(); camera = null; } if(cameraHandler != null && cameraHandler.getLooper() != null){ cameraHandler.getLooper().quitSafely(); cameraHandler = null; } } @Override public boolean frame() { try { mFrameSem.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } return false; } @Override public long getTimeStamp() { return -1; } @Override public boolean isLandscape() { return false; } private SurfaceTexture.OnFrameAvailableListener frameListener=new SurfaceTexture.OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { mFrameSem.drainPermits(); mFrameSem.release(); } }; @Override public void onOpened(@NonNull CameraDevice camera) { this.camera = camera; try { Surface target = new Surface(surface); captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); captureRequestBuilder.addTarget(target); /* //获取设备方向 int rotation = getWindowManager().getDefaultDisplay().getRotation(); // 根据设备方向计算设置照片的方向 builder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));*/ camera.createCaptureSession(Collections.singletonList(target),captureSessionStateCallback,cameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onDisconnected(@NonNull CameraDevice camera) { camera.close(); } @Override public void onError(@NonNull CameraDevice camera, int error) { camera.close(); } private CameraCaptureSession.StateCallback captureSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { captureSession = session; try { session.setRepeatingRequest(captureRequestBuilder.build(),captureCallback,cameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { session.close(); } }; private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { super.onCaptureStarted(session, request, timestamp, frameNumber); } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { super.onCaptureProgressed(session, request, partialResult); } }; } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/video/CameraProvider.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.video; import android.graphics.Point; import android.graphics.SurfaceTexture; import android.hardware.Camera; import com.wuwang.aavt.log.AvLog; import java.io.IOException; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Semaphore; /** * CameraProvider 相机数据 * * @author wuwang * @version v1.0 2017:10:26 18:09 */ public class CameraProvider implements ITextureProvider { private Camera mCamera; private int cameraId=0; private int minWidth = 720; private float rate = 1.67f; private Semaphore mFrameSem; private String tag=getClass().getSimpleName(); @Override public Point open(final SurfaceTexture surface) { final Point size=new Point(); try { mFrameSem=new Semaphore(0); mCamera=Camera.open(cameraId); onCameraOpened(mCamera); mCamera.setPreviewTexture(surface); surface.setOnFrameAvailableListener(frameListener); Camera.Size s=mCamera.getParameters().getPreviewSize(); mCamera.startPreview(); size.x=s.height; size.y=s.width; AvLog.i(tag,"Camera Opened"); } catch (IOException e) { e.printStackTrace(); } return size; } /** * Camera Open后,Preview前被调用的函数 * @param camera 打开的Camera */ protected void onCameraOpened(Camera camera){ //小米5、5s这类奇葩手机不调用setParameters这句会导致图像预览方向错误 Camera.Parameters param = camera.getParameters(); List sizes = param.getSupportedPreviewSizes(); Collections.sort(sizes, new Comparator() { @Override public int compare(android.hardware.Camera.Size o1, android.hardware.Camera.Size o2) { return o1.width - o2.width; } }); for (android.hardware.Camera.Size s : sizes) { if (s.width >= minWidth) { if (rate > 1.4) { if (s.width / (float) s.height > 1.4) { param.setPreviewSize(s.width, s.height); param.set("video-size", s.width + "x" + s.height); break; } } else { if (s.width / (float) s.height < 1.4) { param.setPreviewSize(s.width, s.height); param.set("video-size", s.width + "x" + s.height); break; } } } } List mode = param.getSupportedFocusModes(); if(mode.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)){ param.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); } mCamera.setParameters(param); } public void setCameraSize(int minWidth,float rate){ this.minWidth = minWidth; this.rate = rate; } public void switchCamera(){ cameraId^=1; } public void setDefaultCamera(int id){ cameraId = id; } @Override public void close() { mFrameSem.drainPermits(); mFrameSem.release(); mCamera.stopPreview(); mCamera.release(); mCamera=null; } @Override public boolean frame() { try { mFrameSem.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } return false; } @Override public long getTimeStamp() { return -1; } @Override public boolean isLandscape() { return true; } private SurfaceTexture.OnFrameAvailableListener frameListener=new SurfaceTexture.OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { AvLog.d(tag,"onFrameAvailable"); mFrameSem.drainPermits(); mFrameSem.release(); } }; } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/video/ITextureProvider.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.media.video; import android.graphics.Point; import android.graphics.SurfaceTexture; import com.wuwang.aavt.media.av.ICloseable; /** * ITextureProvider * * @author wuwang * @version v1.0 2017:10:26 18:18 */ public interface ITextureProvider extends ICloseable { /** * 打开视频流数据源 * @param surface 数据流输出到此 * @return 视频流的宽高 */ Point open(SurfaceTexture surface); /** * 获取一帧数据 * @return 是否最后一帧 */ boolean frame(); /** * 获取当前帧时间戳 * @return 时间戳 */ long getTimeStamp(); /** * 视频流是否是横向的 * @return true or false */ boolean isLandscape(); } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/media/video/Mp4Provider.java ================================================ package com.wuwang.aavt.media.video; import android.annotation.TargetApi; import android.graphics.Point; import android.graphics.SurfaceTexture; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.media.MediaMetadataRetriever; import android.os.Build; import android.view.Surface; import com.wuwang.aavt.log.AvLog; import com.wuwang.aavt.media.CodecUtil; import com.wuwang.aavt.media.av.AvException; import com.wuwang.aavt.media.hard.HardMediaData; import com.wuwang.aavt.media.hard.IHardStore; import com.wuwang.aavt.media.video.ITextureProvider; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.Semaphore; /** * @author wuwang */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class Mp4Provider implements ITextureProvider { private final String tag=getClass().getSimpleName(); private String mPath; private MediaExtractor mExtractor; private MediaCodec mVideoDecoder; private int mVideoDecodeTrack=-1; private int mAudioDecodeTrack=-1; private Point mVideoSize=new Point(); private Semaphore mFrameSem; private static final int TIME_OUT=1000; private final Object Extractor_LOCK=new Object(); private long mVideoStopTimeStamp; private boolean isVideoExtractorEnd=false; private boolean isUserWantToStop=false; private Semaphore mDecodeSem; private boolean videoProvideEndFlag=false; private IHardStore mStore; private long nowTimeStamp=-1; private MediaCodec.BufferInfo videoDecodeBufferInfo=new MediaCodec.BufferInfo(); private int mAudioEncodeTrack=-1; private long mVideoTotalTime=-1; public Mp4Provider(){ } public void setInputPath(String path){ this.mPath=path; } private boolean extractMedia(){ if(mPath==null||!new File(mPath).exists()){ //文件不存在 return false; } try { MediaMetadataRetriever mMetRet=new MediaMetadataRetriever(); mMetRet.setDataSource(mPath); mVideoTotalTime=Long.valueOf(mMetRet.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); mExtractor=new MediaExtractor(); mExtractor.setDataSource(mPath); int trackCount=mExtractor.getTrackCount(); for (int i=0;i=0){ ByteBuffer buffer= CodecUtil.getInputBuffer(mVideoDecoder,mInputIndex); buffer.clear(); synchronized (Extractor_LOCK) { mExtractor.selectTrack(mVideoDecodeTrack); int ret = mExtractor.readSampleData(buffer, 0); if (ret != -1) { mVideoStopTimeStamp=mExtractor.getSampleTime(); mVideoDecoder.queueInputBuffer(mInputIndex, 0, ret, mVideoStopTimeStamp, mExtractor.getSampleFlags()); isVideoExtractorEnd = false; }else{ //可以用!mExtractor.advance,但是貌似会延迟一帧。readSampleData 返回 -1 也表示没有更多数据了 isVideoExtractorEnd = true; } mExtractor.advance(); } } while (true){ int mOutputIndex=mVideoDecoder.dequeueOutputBuffer(videoDecodeBufferInfo,TIME_OUT); if(mOutputIndex>=0){ try { if(!isUserWantToStop){ mDecodeSem.acquire(); } } catch (InterruptedException e) { e.printStackTrace(); } nowTimeStamp=videoDecodeBufferInfo.presentationTimeUs; mVideoDecoder.releaseOutputBuffer(mOutputIndex,true); mFrameSem.release(); }else if(mOutputIndex== MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ }else if(mOutputIndex== MediaCodec.INFO_TRY_AGAIN_LATER){ break; } } return isVideoExtractorEnd||isUserWantToStop; } public long getMediaDuration(){ return mVideoTotalTime; } private void startDecodeThread(){ Thread mDecodeThread=new Thread(new Runnable() { @Override public void run() { while (!videoDecodeStep()){} if(videoDecodeBufferInfo.flags!=MediaCodec.BUFFER_FLAG_END_OF_STREAM){ AvLog.d(tag,"video ------------------ end"); videoProvideEndFlag=true; // try { // mDecodeSem.acquire(); // } catch (InterruptedException e) { // e.printStackTrace(); // } //释放最后一帧的信号 videoDecodeBufferInfo.flags=MediaCodec.BUFFER_FLAG_END_OF_STREAM; mFrameSem.release(); } mVideoDecoder.stop(); mVideoDecoder.release(); mVideoDecoder=null; AvLog.d(tag,"audioStart"); audioDecodeStep(); AvLog.d(tag,"audioStop"); mExtractor.release(); mExtractor=null; mStore.close(); } }); mDecodeThread.start(); } private boolean isOpenAudio=true; private boolean audioDecodeStep(){ ByteBuffer buffer=ByteBuffer.allocate(1024*64); boolean isTimeEnd=false; if(isOpenAudio){ buffer.clear(); mExtractor.selectTrack(mAudioDecodeTrack); MediaCodec.BufferInfo info=new MediaCodec.BufferInfo(); while (true){ int length=mExtractor.readSampleData(buffer,0); if(length!=-1){ int flags=mExtractor.getSampleFlags(); boolean isAudioEnd=mExtractor.getSampleTime()>mVideoStopTimeStamp; info.size=length; info.flags=isAudioEnd?MediaCodec.BUFFER_FLAG_END_OF_STREAM:flags; info.presentationTimeUs=mExtractor.getSampleTime(); info.offset=0; AvLog.d(tag,"audio sampleTime= "+info.presentationTimeUs+"/"+mVideoStopTimeStamp); isTimeEnd=mExtractor.getSampleTime()>mVideoStopTimeStamp; AvLog.d(tag,"is End= "+isAudioEnd ); mStore.addData(mAudioEncodeTrack,new HardMediaData(buffer,info)); if(isAudioEnd){ break; } }else{ AvLog.d(tag,"is End= "+true ); info.size=0; info.flags=MediaCodec.BUFFER_FLAG_END_OF_STREAM; mStore.addData(mAudioEncodeTrack,new HardMediaData(buffer,info)); isTimeEnd=true; break; } mExtractor.advance(); } } return isTimeEnd; } @Override public Point open(SurfaceTexture surface) { try { if(!extractMedia()){ return new Point(0,0); } mFrameSem=new Semaphore(0); mDecodeSem=new Semaphore(1); videoProvideEndFlag=false; isUserWantToStop=false; mAudioEncodeTrack=mStore.addTrack(mExtractor.getTrackFormat(mAudioDecodeTrack)); MediaFormat format=mExtractor.getTrackFormat(mVideoDecodeTrack); mVideoDecoder = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)); mVideoDecoder.configure(format,new Surface(surface),null,0); mVideoDecoder.start(); startDecodeThread(); } catch (IOException e) { e.printStackTrace(); } return mVideoSize; } @Override public void close() { isUserWantToStop=true; } @Override public boolean frame() { try { mFrameSem.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } mDecodeSem.release(); return videoProvideEndFlag; } @Override public long getTimeStamp() { return nowTimeStamp; } @Override public boolean isLandscape() { return false; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/utils/GpuUtils.java ================================================ package com.wuwang.aavt.utils; import android.content.res.Resources; import android.opengl.GLES11Ext; import android.opengl.GLES20; import com.wuwang.aavt.log.AvLog; import java.io.InputStream; import javax.microedition.khronos.opengles.GL10; public enum GpuUtils { ; /** * 读取Assets中的文本文件 * @param mRes res * @param path 文件路径 * @return 文本内容 */ public static String readText(Resources mRes,String path){ StringBuilder result=new StringBuilder(); try{ InputStream is=mRes.getAssets().open(path); int ch; byte[] buffer=new byte[1024]; while (-1!=(ch=is.read(buffer))){ result.append(new String(buffer,0,ch)); } }catch (Exception e){ return null; } return result.toString().replaceAll("\\r\\n","\n"); } /** * 加载Shader * @param shaderType Shader类型 * @param source Shader代码 * @return shaderId */ public static int loadShader(int shaderType,String source){ if(source==null){ glError(1,"Shader source ==null : shaderType ="+shaderType); return 0; } int shader= GLES20.glCreateShader(shaderType); if(0!=shader){ GLES20.glShaderSource(shader,source); GLES20.glCompileShader(shader); int[] compiled=new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,compiled,0); if(compiled[0]==0){ glError(1,"Could not compile shader:"+shaderType); glError(1,"GLES20 Error:"+ GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader=0; } } return shader; } /** * 通过字符串创建GL程序 * @param vertexSource 顶点着色器 * @param fragmentSource 片元着色器 * @return programId */ public static int createGLProgram(String vertexSource, String fragmentSource){ int vertex=loadShader(GLES20.GL_VERTEX_SHADER,vertexSource); if(vertex==0){ return 0; } int fragment=loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentSource); if(fragment==0){ return 0; } int program= GLES20.glCreateProgram(); if(program!=0){ GLES20.glAttachShader(program,vertex); GLES20.glAttachShader(program,fragment); GLES20.glLinkProgram(program); int[] linkStatus=new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS,linkStatus,0); if(linkStatus[0]!= GLES20.GL_TRUE){ glError(1,"Could not link program:"+ GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); program=0; } } return program; } /** * 通过assets中的文件创建GL程序 * @param res res * @param vertex 顶点作色器路径 * @param fragment 片元着色器路径 * @return programId */ public static int createGLProgramByAssetsFile(Resources res,String vertex,String fragment){ return createGLProgram(readText(res,vertex),readText(res,fragment)); } private static void glError(int code,Object index){ AvLog.e("glError:"+code+"---"+index); } public static int createTextureID(boolean isOes) { int target= GLES20.GL_TEXTURE_2D; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { target = isOes? GLES11Ext.GL_TEXTURE_EXTERNAL_OES: GLES20.GL_TEXTURE_2D; } int[] texture = new int[1]; GLES20.glGenTextures(1, texture, 0); GLES20.glBindTexture(target, texture[0]); GLES20.glTexParameterf(target, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); GLES20.glTexParameterf(target, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); GLES20.glTexParameteri(target, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(target, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); return texture[0]; } } ================================================ FILE: aavt/src/main/java/com/wuwang/aavt/utils/MatrixUtils.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.utils; import android.opengl.Matrix; import android.widget.ImageView; /** * MatrixUtils * * @author wuwang * @version v1.0 2017:11:01 10:41 */ public enum MatrixUtils { ; public static final int TYPE_FITXY=0; public static final int TYPE_CENTERCROP=1; public static final int TYPE_CENTERINSIDE=2; public static final int TYPE_FITSTART=3; public static final int TYPE_FITEND=4; /** * 获取一个新的原始纹理坐标,每次调用,都会重新创建 * @return 坐标数组 */ public static float[] getOriginalTextureCo(){ return new float[]{ 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f, 1.0f,1.0f }; } /** * 获取一个新的原始顶点坐标,每次调用,都会重新创建 * @return 坐标数组 */ public static float[] getOriginalVertexCo(){ return new float[]{ -1.0f,-1.0f, -1.0f,1.0f, 1.0f,-1.0f, 1.0f,1.0f }; } /** * 获取一个新的4*4单位矩阵 * @return 矩阵数组 */ public static float[] getOriginalMatrix(){ return new float[]{ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; } /** * 根据预览的大小和图像的大小,计算合适的变换矩阵 * @param matrix 接收变换矩阵的数组 * @param type 变换的类型,参考{@link #TYPE_CENTERCROP}、{@link #TYPE_FITEND}、{@link #TYPE_CENTERINSIDE}、{@link #TYPE_FITSTART}、{@link #TYPE_FITXY},对应{@link android.widget.ImageView}的{@link android.widget.ImageView#setScaleType(ImageView.ScaleType)} * @param imgWidth 图像的宽度 * @param imgHeight 图像的高度 * @param viewWidth 视图的宽度 * @param viewHeight 视图的高度 */ public static void getMatrix(float[] matrix,int type,int imgWidth,int imgHeight,int viewWidth, int viewHeight){ if(imgHeight>0&&imgWidth>0&&viewWidth>0&&viewHeight>0){ float[] projection=new float[16]; float[] camera=new float[16]; if(type==TYPE_FITXY){ Matrix.orthoM(projection,0,-1,1,-1,1,1,3); Matrix.setLookAtM(camera,0,0,0,1,0,0,0,0,1,0); Matrix.multiplyMM(matrix,0,projection,0,camera,0); return; } float sWhView=(float)viewWidth/viewHeight; float sWhImg=(float)imgWidth/imgHeight; if(sWhImg>sWhView){ switch (type){ case TYPE_CENTERCROP: Matrix.orthoM(projection,0,-sWhView/sWhImg,sWhView/sWhImg,-1,1,1,3); break; case TYPE_CENTERINSIDE: Matrix.orthoM(projection,0,-1,1,-sWhImg/sWhView,sWhImg/sWhView,1,3); break; case TYPE_FITSTART: Matrix.orthoM(projection,0,-1,1,1-2*sWhImg/sWhView,1,1,3); break; case TYPE_FITEND: Matrix.orthoM(projection,0,-1,1,-1,2*sWhImg/sWhView-1,1,3); break; default: break; } }else{ switch (type){ case TYPE_CENTERCROP: Matrix.orthoM(projection,0,-1,1,-sWhImg/sWhView,sWhImg/sWhView,1,3); break; case TYPE_CENTERINSIDE: Matrix.orthoM(projection,0,-sWhView/sWhImg,sWhView/sWhImg,-1,1,1,3); break; case TYPE_FITSTART: Matrix.orthoM(projection,0,-1,2*sWhView/sWhImg-1,-1,1,1,3); break; case TYPE_FITEND: Matrix.orthoM(projection,0,1-2*sWhView/sWhImg,1,-1,1,1,3); break; default: break; } } Matrix.setLookAtM(camera,0,0,0,1,0,0,0,0,1,0); Matrix.multiplyMM(matrix,0,projection,0,camera,0); } } /** * 翻转矩阵 * @param m 需要被翻转的矩阵 * @param x 是否x轴左右翻转 * @param y 是否y轴左右翻转 * @return 传入的矩阵 */ public static float[] flip(float[] m,boolean x,boolean y){ if(x||y){ Matrix.scaleM(m,0,x?-1:1,y?-1:1,1); } return m; } } ================================================ FILE: aavt/src/main/res/values/attr.xml ================================================ ================================================ FILE: aavt/src/main/res/values/strings.xml ================================================ AAVT ================================================ FILE: aavt/src/test/java/com/wuwang/aavt/ExampleUnitTest.java ================================================ package com.wuwang.aavt; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() google() } } ext{ vCode=25 vName="a0.2.5" } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: examples/.gitignore ================================================ /build ================================================ FILE: examples/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig { applicationId "com.wuwang.aavt.examples" minSdkVersion 14 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' compile project(':aavt') } ================================================ FILE: examples/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in D:\Android\android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: examples/src/androidTest/java/com/wuwang/aavt/examples/ExampleInstrumentedTest.java ================================================ package com.wuwang.aavt.examples; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.wuwang.aavt", appContext.getPackageName()); } } ================================================ FILE: examples/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/CameraRecorderActivity.java ================================================ package com.wuwang.aavt.examples; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.wuwang.aavt.av.VideoCapture; public class CameraRecorderActivity extends AppCompatActivity{ private SurfaceView mSurfaceView; private TextView mTvPreview,mTvRecord; private boolean isPreviewOpen=false; private boolean isRecordOpen=false; private int mCameraWidth,mCameraHeight; private VideoCapture mCamera; private String tempPath= Environment.getExternalStorageDirectory().getAbsolutePath()+"/test.mp4"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera_record); mSurfaceView= (SurfaceView) findViewById(R.id.mSurfaceView); mTvRecord= (TextView) findViewById(R.id.mTvRec); mTvPreview= (TextView) findViewById(R.id.mTvShow); mCamera =new VideoCapture(getApplicationContext()); mCamera.setProperty(VideoCapture.KEY_OUTPUT_WIDTH, 368); mCamera.setProperty(VideoCapture.KEY_OUTPUT_HEIGHT,640); mCamera.setProperty(VideoCapture.KEY_OUTPUT_PATH,tempPath); mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { // GroupFilter filter=new GroupFilter(getResources()); // mCamera.setRenderer(filter); // filter.addFilter(new StickFigureFilter(getResources())); // filter.addFilter(new BeautyFilter(getResources()).setBeautyLevel(4)); // filter.addFilter(new WaterMarkFilter().setMarkPosition(30,10,100,76).setMark(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mCamera.setProperty(VideoCapture.KEY_PREVIEW_WIDTH,width); mCamera.setProperty(VideoCapture.KEY_PREVIEW_HEIGHT,height); mCamera.open(0); mCamera.setPreviewSurface(holder.getSurface()); mCamera.startPreview(); isPreviewOpen=true; } @Override public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.close(); } }); } public void onClick(View view){ switch (view.getId()){ case R.id.mTvShow: isPreviewOpen=!isPreviewOpen; mTvPreview.setText(isPreviewOpen?"关预览":"开预览"); if(isPreviewOpen){ mCamera.startPreview(); }else{ mCamera.stopPreview(); } break; case R.id.mTvRec: isRecordOpen=!isRecordOpen; mTvRecord.setText(isRecordOpen?"关录制":"开录制"); if(isRecordOpen){ mCamera.startRecord(); }else{ mCamera.stopRecord(); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { Intent v=new Intent(Intent.ACTION_VIEW); v.setDataAndType(Uri.parse(tempPath),"video/mp4"); if(v.resolveActivity(getPackageManager()) != null){ //startActivity(v); }else{ Toast.makeText(CameraRecorderActivity.this, "无法找到默认媒体软件打开:"+tempPath, Toast.LENGTH_SHORT).show(); } } },1000); } break; default: break; } } } ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/ExampleMp4ProcessActivity.java ================================================ /* * Created by Wuwang on 2017/9/11 * Copyright © 2017年 深圳哎吖科技. All rights reserved. */ package com.wuwang.aavt.examples; import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import com.wuwang.aavt.av.Mp4Processor2; public class ExampleMp4ProcessActivity extends AppCompatActivity { private SurfaceView mSurfaceView; private Mp4Processor2 mMp4Processor; private String tempPath= Environment.getExternalStorageDirectory().getAbsolutePath()+"/test.mp4"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mp4); mMp4Processor=new Mp4Processor2(); mMp4Processor.setInputPath(Environment.getExternalStorageDirectory().getAbsolutePath()+"/a.mp4"); mMp4Processor.setOutputPath(tempPath); mSurfaceView= (SurfaceView) findViewById(R.id.mSurfaceView); mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mMp4Processor.setSurface(holder); mMp4Processor.setPreviewSize(width, height); mMp4Processor.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { mMp4Processor.stopPreview(); } }); } public void onClick(View view){ switch (view.getId()){ case R.id.mOpen: Intent intent = new Intent(Intent.ACTION_GET_CONTENT); //intent.setType(“image/*”);//选择图片 //intent.setType(“audio/*”); //选择音频 intent.setType("video/mp4"); //选择视频 (mp4 3gp 是android支持的视频格式) //intent.setType(“video/*;image/*”);//同时选择视频和图片 //intent.setType("*/*");//无类型限制 intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(intent, 1); break; case R.id.mProcess: mMp4Processor.startRecord(); mMp4Processor.open(); break; case R.id.mStop: mMp4Processor.stopRecord(); mMp4Processor.close(); break; case R.id.mPlay: Intent v=new Intent(Intent.ACTION_VIEW); v.setDataAndType(Uri.parse(tempPath),"video/mp4"); startActivity(v); break; default: break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { String path = getRealFilePath(data.getData()); if (path != null) { mMp4Processor.setInputPath(path); } } } public String getRealFilePath(final Uri uri) { if (null == uri) return null; final String scheme = uri.getScheme(); String data = null; if (scheme == null) { Log.e("wuwang", "scheme is null"); data = uri.getPath(); } else if (ContentResolver.SCHEME_FILE.equals(scheme)) { data = uri.getPath(); Log.e("wuwang", "SCHEME_FILE"); } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { data = GetPathFromUri4kitkat.getPath(getApplicationContext(), uri); } return data; } } ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/GetPathFromUri4kitkat.java ================================================ package com.wuwang.aavt.examples; import android.annotation.SuppressLint; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; public class GetPathFromUri4kitkat { /** * 专为Android4.4设计的从Uri获取文件绝对路径,以前的方法已不好使 */ @SuppressLint("NewApi") public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } // TODO handle non-primary volumes } // DownloadsProvider else if (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(context, contentUri, null, null); } // MediaProvider else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } final String selection = "_id=?"; final String[] selectionArgs = new String[] { split[1] }; return getDataColumn(context, contentUri, selection, selectionArgs); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { return getDataColumn(context, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. * * @param context * The context. * @param uri * The Uri to query. * @param selection * (Optional) Filter used in the query. * @param selectionArgs * (Optional) Selection arguments used in the query. * @return The value of the _data column, which is typically a file path. */ public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } finally { if (cursor != null) cursor.close(); } return null; } /** * @param uri * The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ public static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri * The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ public static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri * The Uri to check. * @return Whether the Uri authority is MediaProvider. */ public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } } ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/MainActivity.java ================================================ package com.wuwang.aavt.examples; import android.Manifest; import android.content.Intent; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private PermissionAsker mAsker; private final int REQUEST_CODE_VIDEO_PLAY = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAsker=new PermissionAsker(10,new Runnable() { @Override public void run() { setContentView(R.layout.activity_main); } }, new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "必要权限被拒绝,应用退出", Toast.LENGTH_SHORT).show(); finish(); } }).askPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); mAsker.onRequestPermissionsResult(grantResults); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(data != null && data.getData() != null){ if(REQUEST_CODE_VIDEO_PLAY == requestCode){ String path = GetPathFromUri4kitkat.getPath(this,data.getData()); Intent intent = new Intent(this,VideoPlayerActivity.class); intent.putExtra("videoPath",path); startActivity(intent); } } } public void onClick(View view){ switch (view.getId()){ case R.id.mMp4Process: startActivity(new Intent(this,ExampleMp4ProcessActivity.class)); break; case R.id.mCameraRecord: startActivity(new Intent(this,CameraRecorderActivity.class)); break; case R.id.mYuvExport: startActivity(new Intent(this,YuvExportActivity.class)); break; case R.id.mPlayVideo: Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("video/*"); startActivityForResult(intent,REQUEST_CODE_VIDEO_PLAY); break; default:break; } } } ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/PermissionAsker.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.examples; import android.app.Activity; import android.content.pm.PackageManager; import android.support.v4.app.ActivityCompat; /** * PermissionUtils * * @author wuwang * @version v1.0 2017:11:16 16:47 */ public class PermissionAsker { private Runnable mOkRun=RUN; private Runnable mDeniRun=RUN; private int mReqCode=1; public PermissionAsker(){} public PermissionAsker(int code,Runnable ok,Runnable deni){ this.mReqCode=code; this.mOkRun=ok; this.mDeniRun=deni; } public void setReqCode(int code){ this.mReqCode=code; } public void setSuccedCallback(Runnable run){ this.mOkRun=run; } public void setFailedCallback(Runnable run){ this.mDeniRun=run; } public PermissionAsker askPermission(Activity context,String ... permission){ int result=0; for (String p:permission){ result+=ActivityCompat.checkSelfPermission(context,p); } if(result==0){ mOkRun.run(); }else{ ActivityCompat.requestPermissions(context,permission,mReqCode); } return this; } public void onRequestPermissionsResult(int[] grantResults){ boolean b=true; for (int a:grantResults){ b&=(a== PackageManager.PERMISSION_GRANTED); } if (grantResults.length > 0&&b) { mOkRun.run(); } else { mDeniRun.run(); } } private static final Runnable RUN=new Runnable() { @Override public void run() { } }; } ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/VideoPlayerActivity.java ================================================ package com.wuwang.aavt.examples; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.View; import com.wuwang.aavt.gl.CandyFilter; import com.wuwang.aavt.gl.GrayFilter; import com.wuwang.aavt.media.player.EffectMediaView; /** * @author wuwang * @version 1.00 , 2019/03/13 */ public class VideoPlayerActivity extends AppCompatActivity { private EffectMediaView playerView; private String videoPath; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_player); playerView = (EffectMediaView) findViewById(R.id.playerView); videoPath = getIntent().getStringExtra("videoPath"); playerView.setRenderer(new CandyFilter(getResources())); playerView.setDataSource(videoPath); playerView.start(); } @Override protected void onResume() { super.onResume(); playerView.onResume(); } @Override protected void onPause() { super.onPause(); playerView.onPause(); } @Override protected void onDestroy() { super.onDestroy(); playerView.onDestroy(); } public void onPlay(View view){ playerView.start(); } public void onStop(View view){ playerView.stop(); } public void onPause(View view){ playerView.pause(); } } ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/VideoUtils.java ================================================ /* * Created by Wuwang on 2017/10/11 * Copyright © 2017年 深圳哎吖科技. All rights reserved. */ package com.wuwang.aavt.examples; import android.util.Log; import com.wuwang.aavt.av.Mp4Processor; import com.wuwang.aavt.core.Renderer; import com.wuwang.aavt.gl.BaseFilter; import com.wuwang.aavt.gl.LazyFilter; import java.io.IOException; public class VideoUtils { public static void transcodeVideoFile(String srcVideoFile, String dstVideoFile, int dstWidth, int dstHeight, int durationUS, OnProgress onProgress) throws IOException { final Mp4Processor processor=new Mp4Processor(); processor.setOutputPath(dstVideoFile); processor.setInputPath(srcVideoFile); processor.setOutputSize(dstWidth,dstHeight); processor.setOnCompleteListener(new Mp4Processor.OnProgressListener() { @Override public void onProgress(long max, long current) { } @Override public void onComplete(String path) { Log.e("wuwang","end:::::"+path); } }); processor.setRenderer(new Renderer() { BaseFilter mFilter; @Override public void create() { mFilter=new LazyFilter(); mFilter.create(); } @Override public void sizeChanged(int width, int height) { mFilter.sizeChanged(width, height); } @Override public void draw(int texture) { mFilter.draw(texture); Log.e("wuwang","getPresentationTime:"+processor.getPresentationTime()); } @Override public void destroy() { mFilter.destroy(); } }); processor.start(); } interface OnProgress{ void process(long time); } } ================================================ FILE: examples/src/main/java/com/wuwang/aavt/examples/YuvExportActivity.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wuwang.aavt.examples; import android.graphics.Bitmap; import android.opengl.EGLSurface; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.ImageView; import com.wuwang.aavt.gl.FrameBuffer; import com.wuwang.aavt.gl.LazyFilter; import com.wuwang.aavt.gl.YuvOutputFilter; import com.wuwang.aavt.media.video.CameraProvider; import com.wuwang.aavt.media.RenderBean; import com.wuwang.aavt.media.SurfaceShower; import com.wuwang.aavt.media.VideoSurfaceProcessor; /** * YuvExportActivity * * @author wuwang * @version v1.0 2017:10:28 10:27 */ public class YuvExportActivity extends AppCompatActivity { private VideoSurfaceProcessor mProcessor; private CameraProvider mProvider; private SurfaceShower mShower; private FrameBuffer mFb; private YuvOutputFilter mOutputFilter; private LazyFilter mCutScene; private byte[] tempBuffer; private boolean exportFlag=false; private ImageView mImage; private Bitmap mBitmap; private int picX=368; private int picY=640; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity__export_yuv); cameraInit(); mImage= (ImageView) findViewById(R.id.mImage); SurfaceView view= (SurfaceView) findViewById(R.id.mSurfaceView); view.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mShower.open(); mShower.setSurface(holder.getSurface()); mShower.setOutputSize(width, height); mProcessor.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { mProcessor.stop(); mShower.close(); } }); } private void cameraInit(){ mShower=new SurfaceShower(); mProvider=new CameraProvider(); mProcessor=new VideoSurfaceProcessor(); mProcessor.setTextureProvider(mProvider); mProcessor.addObserver(mShower); mFb=new FrameBuffer(); mShower.setOnDrawEndListener(new SurfaceShower.OnDrawEndListener() { @Override public void onDrawEnd(EGLSurface surface, RenderBean bean) { if(exportFlag){ if(mOutputFilter==null){ mOutputFilter=new YuvOutputFilter(YuvOutputFilter.EXPORT_TYPE_NV21); mOutputFilter.create(); mOutputFilter.sizeChanged(picX,picY); mOutputFilter.setInputTextureSize(bean.sourceWidth,bean.sourceHeight); tempBuffer=new byte[picX*picY*3/2]; } mOutputFilter.drawToTexture(bean.textureId); mOutputFilter.getOutput(tempBuffer,0,picX*picY*3/2); runOnUiThread(new Runnable() { @Override public void run() { if(mBitmap!=null){ mBitmap.recycle(); mBitmap=null; } mBitmap=rawByteArray2RGBABitmap2(tempBuffer,picX,picY); mImage.setImageBitmap(mBitmap); mImage.setVisibility(View.VISIBLE); } }); exportFlag=false; } } }); } public void onClick(View view){ switch (view.getId()){ case R.id.mTvCap: exportFlag=true; break; case R.id.mImage: mImage.setVisibility(View.GONE); break; default: break; } } public Bitmap rawByteArray2RGBABitmap2(byte[] data, int width, int height) { int frameSize = width * height; int[] rgba = new int[frameSize]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int y = (0xff & ((int) data[i * width + j])); int u = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 0])); int v = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 1])); y = y < 16 ? 16 : y; int r = Math.round(1.164f * (y - 16) + 1.596f * (v - 128)); int g = Math.round(1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128)); int b = Math.round(1.164f * (y - 16) + 2.018f * (u - 128)); r = r < 0 ? 0 : (r > 255 ? 255 : r); g = g < 0 ? 0 : (g > 255 ? 255 : g); b = b < 0 ? 0 : (b > 255 ? 255 : b); rgba[i * width + j] = 0xff000000 + (b << 16) + (g << 8) + r; } } Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bmp.setPixels(rgba, 0 , width, 0, 0, width, height); return bmp; } } ================================================ FILE: examples/src/main/res/drawable/tv_start_bg.xml ================================================ ================================================ FILE: examples/src/main/res/layout/activity__export_yuv.xml ================================================ ================================================ FILE: examples/src/main/res/layout/activity_camera_record.xml ================================================ ================================================ FILE: examples/src/main/res/layout/activity_main.xml ================================================