├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── you │ │ └── chen │ │ └── media │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── ConvertNV.cpp │ │ ├── ConvertNV.h │ │ ├── Log.h │ │ ├── RotateNV_UV.cpp │ │ ├── RotateNV_UV.h │ │ ├── include │ │ │ ├── libyuv.h │ │ │ └── libyuv │ │ │ │ ├── basic_types.h │ │ │ │ ├── compare.h │ │ │ │ ├── compare_row.h │ │ │ │ ├── convert.h │ │ │ │ ├── convert_argb.h │ │ │ │ ├── convert_from.h │ │ │ │ ├── convert_from_argb.h │ │ │ │ ├── cpu_id.h │ │ │ │ ├── macros_msa.h │ │ │ │ ├── mjpeg_decoder.h │ │ │ │ ├── planar_functions.h │ │ │ │ ├── rotate.h │ │ │ │ ├── rotate_argb.h │ │ │ │ ├── rotate_row.h │ │ │ │ ├── row.h │ │ │ │ ├── scale.h │ │ │ │ ├── scale_argb.h │ │ │ │ ├── scale_row.h │ │ │ │ ├── version.h │ │ │ │ └── video_common.h │ │ ├── jniLibs │ │ │ ├── arm64-v8a │ │ │ │ └── libyuv.so │ │ │ ├── armeabi-v7a │ │ │ │ └── libyuv.so │ │ │ ├── x86 │ │ │ │ └── libyuv.so │ │ │ └── x86_64 │ │ │ │ └── libyuv.so │ │ └── native-lib.cpp │ ├── java │ │ └── you │ │ │ └── chen │ │ │ └── media │ │ │ ├── App.java │ │ │ ├── YuvTests.java │ │ │ ├── camera │ │ │ ├── CameraHelper.java │ │ │ ├── CameraUtils.java │ │ │ ├── OrientationHelper.java │ │ │ ├── SizeFilter.java │ │ │ └── impl │ │ │ │ ├── HightSizeFilter.java │ │ │ │ ├── PictureSizeFilter.java │ │ │ │ └── VideoSizeFilter.java │ │ │ ├── core │ │ │ ├── BytePool.java │ │ │ ├── Constant.java │ │ │ ├── MediaEncoder.java │ │ │ ├── MediaUtils.java │ │ │ ├── Orientation.java │ │ │ ├── Transform.java │ │ │ ├── YuvUtils.java │ │ │ ├── audio │ │ │ │ ├── AudioCallback.java │ │ │ │ ├── AudioMuxerCallback.java │ │ │ │ ├── AudioPresentationTime.java │ │ │ │ ├── AudioRecorder.java │ │ │ │ ├── AudioTaker.java │ │ │ │ └── AudioTransform.java │ │ │ ├── h264 │ │ │ │ ├── AvcTransform.java │ │ │ │ ├── ClipAvcTransform.java │ │ │ │ ├── H264Callback.java │ │ │ │ ├── H264MuxerCallback.java │ │ │ │ └── H264Utils.java │ │ │ ├── mp4 │ │ │ │ └── Mp4Recorder.java │ │ │ └── scan │ │ │ │ ├── DecodeThread.java │ │ │ │ ├── DecoderHandler.java │ │ │ │ ├── FormatDecoder.java │ │ │ │ └── SoundVibratorHelper.java │ │ │ ├── rx │ │ │ ├── NothingSubscribe.java │ │ │ ├── RequestRetry.java │ │ │ ├── RxUtils.java │ │ │ ├── ThrowableConsumer.java │ │ │ ├── perm │ │ │ │ ├── Permission.java │ │ │ │ ├── PermissionFragment.java │ │ │ │ ├── PermissionMustCallback.java │ │ │ │ └── RxPermissions.java │ │ │ └── viewbind │ │ │ │ ├── TextViewChangedOnSubscribe.java │ │ │ │ └── ViewClickOnSubscribe.java │ │ │ ├── ui │ │ │ ├── AacActivity.java │ │ │ ├── CameraActivity.java │ │ │ ├── H264Activity.java │ │ │ ├── MainActivity.java │ │ │ ├── Mp4Activity.java │ │ │ ├── ScanActivity.java │ │ │ ├── TestActivity.java │ │ │ └── ViewTestActivity.java │ │ │ ├── utils │ │ │ ├── BitmapUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── LogUtils.java │ │ │ ├── Utils.java │ │ │ └── ViewUtils.java │ │ │ └── widget │ │ │ ├── CameraView.java │ │ │ ├── FlashView.java │ │ │ ├── FocusView.java │ │ │ ├── ScanFormatView.java │ │ │ └── ToggleRadioButton.java │ └── res │ │ ├── color │ │ └── flash_camera.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xxhdpi │ │ ├── flash_camera_s.png │ │ ├── flash_camera_un.png │ │ ├── focus_camera.png │ │ ├── line_scan_camera.png │ │ ├── qrcode_scan_line.png │ │ ├── scan_success.png │ │ └── switch_camera.png │ │ ├── drawable │ │ ├── flash_camera.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── act_aac.xml │ │ ├── act_camera.xml │ │ ├── act_h264.xml │ │ ├── act_main.xml │ │ ├── act_mp4.xml │ │ ├── act_pcm.xml │ │ ├── act_scan.xml │ │ ├── act_test.xml │ │ ├── act_viewtest.xml │ │ └── include_flash.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ └── beep.ogg │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── you │ └── chen │ └── media │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.DS_Store 3 | /.gradle 4 | /.idea 5 | /build 6 | /gradle 7 | /gradlew 8 | /gradlew.bat 9 | /local.properties 10 | /captures 11 | .externalNativeBuild 12 | .cxx 13 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.2" 6 | defaultConfig { 7 | applicationId "you.chen.media" 8 | minSdkVersion 21 9 | targetSdkVersion 29 10 | versionCode 100 11 | versionName "1.0.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | 14 | ndk { 15 | abiFilters "arm64-v8a", "armeabi-v7a" 16 | } 17 | 18 | externalNativeBuild { 19 | cmake { 20 | cppFlags "" 21 | abiFilters "arm64-v8a", "armeabi-v7a" 22 | } 23 | } 24 | } 25 | 26 | sourceSets { 27 | main { 28 | jni.srcDirs = [] 29 | jniLibs.srcDirs = ['src/main/cpp/jniLibs'] 30 | } 31 | } 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | lintOptions { //忽略Manifest警告 39 | disable 'GoogleAppIndexingWarning' 40 | } 41 | 42 | buildTypes { 43 | release { 44 | minifyEnabled false 45 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 46 | } 47 | } 48 | 49 | externalNativeBuild { 50 | cmake { 51 | path "src/main/cpp/CMakeLists.txt" 52 | version "3.10.2" 53 | } 54 | } 55 | } 56 | 57 | dependencies { 58 | implementation fileTree(include: ['*.jar'], dir: 'libs') 59 | implementation 'androidx.appcompat:appcompat:1.0.2' 60 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 61 | testImplementation 'junit:junit:4.12' 62 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 63 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 64 | implementation 'io.reactivex.rxjava2:rxjava:2.2.16' 65 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 66 | implementation 'com.google.zxing:core:3.3.3' 67 | } 68 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/you/chen/media/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package you.chen.media; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("you.chen.media", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 42 | 44 | 45 | 47 | 48 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.4.1) 7 | 8 | #include头文件目录 9 | include_directories(${PROJECT_SOURCE_DIR}/include) 10 | 11 | # 指定链接库文件目录 12 | link_directories(${PROJECT_SOURCE_DIR}/jniLibs/${ANDROID_ABI}) 13 | 14 | #设置所有.cpp文件路径 15 | file(GLOB SRC_CPP_LIST ${PROJECT_SOURCE_DIR}/*.cpp) 16 | 17 | add_library( 18 | yuv-utils 19 | SHARED 20 | ${SRC_CPP_LIST}) 21 | 22 | target_link_libraries( 23 | yuv-utils 24 | # libyuv.so 25 | yuv 26 | log) -------------------------------------------------------------------------------- /app/src/main/cpp/ConvertNV.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by you on 2018-09-06. NV21,NV12之间的UV剪切并旋转操作 3 | * NV21与NV12区别只是UV与VU交差的区别,因此以下方法NV21,NV12可通用 4 | */ 5 | 6 | #include "ConvertNV.h" 7 | 8 | /** 9 | * 同时剪切NV21数据并旋转 10 | * @param sample 11 | * @param sample_size 12 | * @param dst_y 13 | * @param dst_stride_y 14 | * @param dst_u 15 | * @param dst_stride_u 16 | * @param dst_v 17 | * @param dst_stride_v 18 | * @param crop_x 19 | * @param crop_y 20 | * @param src_width 21 | * @param src_height 22 | * @param crop_width 23 | * @param crop_height 24 | * @param mode 25 | */ 26 | void ConvertNV21(const uint8_t* sample, 27 | uint8_t* dst_y, 28 | int dst_stride_y, 29 | uint8_t* dst_vu, 30 | int dst_stride_vu, 31 | int crop_x, 32 | int crop_y, 33 | int src_width, 34 | int src_height, 35 | int crop_width, 36 | int crop_height, 37 | libyuv::RotationMode mode) { 38 | const uint8_t *src = sample + (src_width * crop_y + crop_x); 39 | const uint8_t* src_vu = sample + (src_width * src_height) + 40 | ((crop_y / 2) * src_width) + ((crop_x / 2) * 2); 41 | NV21Rotate(src, src_width, src_vu, src_width, dst_y, dst_stride_y, 42 | dst_vu, dst_stride_vu, crop_width, crop_height, mode); 43 | } 44 | 45 | /** 46 | * 同时NV21转NV12并旋转 47 | * @param src_y 48 | * @param src_stride_y 49 | * @param src_uv 50 | * @param src_stride_uv 51 | * @param dst_y 52 | * @param dst_stride_y 53 | * @param dst_uv 54 | * @param dst_stride_uv 55 | * @param width 56 | * @param height 57 | * @param mode 58 | */ 59 | void NV21ToNV12Rotate(const uint8_t* src_y, 60 | int src_stride_y, 61 | const uint8_t* src_vu, 62 | int src_stride_vu, 63 | uint8_t* dst_y, 64 | int dst_stride_y, 65 | uint8_t* dst_uv, 66 | int dst_stride_uv, 67 | int width, 68 | int height, 69 | libyuv::RotationMode mode) { 70 | if (mode == libyuv::kRotate0) { 71 | libyuv::NV21ToNV12(src_y, src_stride_y, src_vu, src_stride_vu, 72 | dst_y, dst_stride_y, dst_uv, dst_stride_uv, width, height); 73 | return; 74 | } 75 | 76 | int uv16Width = (width + 1) >> 1; 77 | int uv16Height = (height + 1) >> 1; 78 | 79 | const uint16_t *src = reinterpret_cast(src_vu); 80 | int src_stride = (src_stride_vu + 1) >> 1; 81 | uint16_t *dst = reinterpret_cast(dst_uv); 82 | int dst_stride = (dst_stride_uv + 1) >> 1; 83 | 84 | switch (mode) { 85 | case libyuv::kRotate90: 86 | libyuv::RotatePlane90(src_y, src_stride_y, dst_y, dst_stride_y, width, height); 87 | RotateNV_UV90_X(src, src_stride, dst, dst_stride, uv16Width, uv16Height);// 88 | break; 89 | case libyuv::kRotate270: 90 | libyuv::RotatePlane270(src_y, src_stride_y, dst_y, dst_stride_y, width, height); 91 | RotateNV_UV270_X(src, src_stride, dst, dst_stride, uv16Width, uv16Height); 92 | break; 93 | case libyuv::kRotate180: 94 | libyuv::RotatePlane180(src_y, src_stride_y, dst_y, dst_stride_y, width, height); 95 | // RotateNV_UV180_X(src, dst, uv16Width * uv16Height); 96 | RotateNV_UV180_X(src_vu, src_stride_vu, dst_uv, dst_stride_uv, width, uv16Height); 97 | break; 98 | } 99 | } 100 | 101 | /** 102 | * 同时裁剪NV21转NV12并旋转 103 | * @param sample 104 | * @param dst_y 105 | * @param dst_stride_y 106 | * @param dst_uv 107 | * @param dst_stride_uv 108 | * @param crop_x 109 | * @param crop_y 110 | * @param src_width 111 | * @param src_height 112 | * @param crop_width 113 | * @param crop_height 114 | * @param mode 115 | */ 116 | void ConvertNV21ToNV12(const uint8_t* sample, 117 | uint8_t* dst_y, 118 | int dst_stride_y, 119 | uint8_t* dst_uv, 120 | int dst_stride_uv, 121 | int crop_x, 122 | int crop_y, 123 | int src_width, 124 | int src_height, 125 | int crop_width, 126 | int crop_height, 127 | libyuv::RotationMode mode) { 128 | 129 | const uint8_t *src = sample + (src_width * crop_y + crop_x); 130 | const uint8_t* src_vu = sample + (src_width * src_height) + 131 | ((crop_y / 2) * src_width) + ((crop_x / 2) * 2); 132 | NV21ToNV12Rotate(src, src_width, src_vu, src_width, dst_y, dst_stride_y, 133 | dst_uv, dst_stride_uv, crop_width, crop_height, mode); 134 | } 135 | -------------------------------------------------------------------------------- /app/src/main/cpp/ConvertNV.h: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Created by you on 2018-09-06. NV21,NV12之间的UV剪切并旋转操作 4 | * NV21与NV12区别只是UV与VU交差的区别,因此以下方法NV21,NV12可通用 5 | */ 6 | #include "RotateNV_UV.h" 7 | 8 | #ifndef CAMERAMEDIACODEC_CONVERTNV_H 9 | #define CAMERAMEDIACODEC_CONVERTNV_H 10 | 11 | /** 12 | * 同时剪切NV21数据并旋转 13 | * @param sample 14 | * @param sample_size 15 | * @param dst_y 16 | * @param dst_stride_y 17 | * @param dst_u 18 | * @param dst_stride_u 19 | * @param dst_v 20 | * @param dst_stride_v 21 | * @param crop_x 22 | * @param crop_y 23 | * @param src_width 24 | * @param src_height 25 | * @param crop_width 26 | * @param crop_height 27 | * @param mode 28 | */ 29 | void ConvertNV21(const uint8_t* sample, 30 | uint8_t* dst_y, 31 | int dst_stride_y, 32 | uint8_t* dst_vu, 33 | int dst_stride_vu, 34 | int crop_x, 35 | int crop_y, 36 | int src_width, 37 | int src_height, 38 | int crop_width, 39 | int crop_height, 40 | libyuv::RotationMode mode); 41 | 42 | /** 43 | * 同时NV21转NV12并旋转 44 | * @param src_y 45 | * @param src_stride_y 46 | * @param src_uv 47 | * @param src_stride_uv 48 | * @param dst_y 49 | * @param dst_stride_y 50 | * @param dst_uv 51 | * @param dst_stride_uv 52 | * @param width 53 | * @param height 54 | * @param mode 55 | */ 56 | void NV21ToNV12Rotate(const uint8_t* src_y, 57 | int src_stride_y, 58 | const uint8_t* src_uv, 59 | int src_stride_uv, 60 | uint8_t* dst_y, 61 | int dst_stride_y, 62 | uint8_t* dst_uv, 63 | int dst_stride_uv, 64 | int width, 65 | int height, 66 | libyuv::RotationMode mode); 67 | 68 | /** 69 | * 同时裁剪NV21转NV12并旋转 70 | * @param sample 71 | * @param dst_y 72 | * @param dst_stride_y 73 | * @param dst_uv 74 | * @param dst_stride_uv 75 | * @param crop_x 76 | * @param crop_y 77 | * @param src_width 78 | * @param src_height 79 | * @param crop_width 80 | * @param crop_height 81 | * @param mode 82 | */ 83 | void ConvertNV21ToNV12(const uint8_t* sample, 84 | uint8_t* dst_y, 85 | int dst_stride_y, 86 | uint8_t* dst_uv, 87 | int dst_stride_uv, 88 | int crop_x, 89 | int crop_y, 90 | int src_width, 91 | int src_height, 92 | int crop_width, 93 | int crop_height, 94 | libyuv::RotationMode mode); 95 | 96 | #endif //CAMERAMEDIACODEC_CONVERTNV_H 97 | -------------------------------------------------------------------------------- /app/src/main/cpp/Log.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by you on 2018/6/5. 3 | // 4 | #ifndef RSADEMO_LOG_H 5 | #define RSADEMO_LOG_H 6 | 7 | #include 8 | #define TAG "youchenNdk" // 这个是自定义的LOG的标识 9 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 10 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 11 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 12 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 13 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 14 | 15 | 16 | 17 | #endif //RSADEMO_LOG_H 18 | -------------------------------------------------------------------------------- /app/src/main/cpp/RotateNV_UV.h: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Created by you on 2018-03-06. NV21,NV12之间的UV旋转操作 4 | * NV21与NV12区别只是UV与VU交差的区别,因此以下方法NV21,NV12可通用 5 | * 所有_X的交叉即可用作NV12与NV21之间的UV转换 6 | */ 7 | 8 | #ifndef CAMERAMEDIACODEC_ROTATENV_UV_H 9 | #define CAMERAMEDIACODEC_ROTATENV_UV_H 10 | 11 | #include 12 | 13 | /** 14 | * 适合NV21或者NV12的UV数据的操作 15 | * @param src 16 | * @param src_stride 17 | * @param dst 18 | * @param dst_stride 19 | * @param width 20 | * @param height 21 | */ 22 | void TransposeWxH_C2(const uint16_t* src, 23 | int src_stride, 24 | uint16_t* dst, 25 | int dst_stride, 26 | int width, 27 | int height); 28 | 29 | /** 30 | * 适合NV21或者NV12的UV数据的操作,并转换UV顺序 31 | * @param src 32 | * @param src_stride 33 | * @param dst 34 | * @param dst_stride 35 | * @param width 36 | * @param height 37 | */ 38 | void TransposeWxH_C2_X(const uint16_t* src, 39 | int src_stride, 40 | uint16_t* dst, 41 | int dst_stride, 42 | int width, 43 | int height); 44 | 45 | /** 46 | * 镜面翻转uint8_t类型的数据,并交叉变换一个步长的顺序,适合UV与VU之间的镜面转换, 与源码稍作修改 47 | * @param src 48 | * @param dst 49 | * @param width 50 | */ 51 | void MirrorRow_C_X(const uint8_t* src, 52 | uint8_t* dst, 53 | int width); 54 | 55 | /** 56 | * 将NV21或者NV12的UV数据旋转90 57 | * @param src 58 | * @param src_stride 59 | * @param dst 60 | * @param dst_stride 61 | * @param width UV数据的宽 62 | * @param height UV数据的高 63 | */ 64 | void RotateNV_UV90(const uint16_t* src, 65 | int src_stride, 66 | uint16_t* dst, 67 | int dst_stride, 68 | int width, 69 | int height); 70 | 71 | /** 72 | * 将NV21或者NV12的UV数据旋转270 73 | * @param src 74 | * @param src_stride 75 | * @param dst 76 | * @param dst_stride 77 | * @param width UV数据的宽 78 | * @param height UV数据的高 79 | */ 80 | void RotateNV_UV270(const uint16_t* src, 81 | int src_stride, 82 | uint16_t* dst, 83 | int dst_stride, 84 | int width, 85 | int height); 86 | 87 | /** 88 | * 将NV21或者NV12的UV数据旋转180 89 | * @param src 90 | * @param src_stride 91 | * @param dst 92 | * @param dst_stride 93 | * @param width 94 | * @param height 95 | */ 96 | void RotateNV_UV180(const uint8_t* src, 97 | int src_stride, 98 | uint8_t* dst, 99 | int dst_stride, 100 | int width, 101 | int height); 102 | 103 | /** 104 | * 同时将NV21或者NV12的UV数据旋转90,并交叉转换UV顺序, 可用作NV12与NV21之间的UV互转 105 | * @param src 106 | * @param src_stride 107 | * @param dst 108 | * @param dst_stride 109 | * @param width UV数据的宽 110 | * @param height UV数据的高 111 | */ 112 | void RotateNV_UV90_X(const uint16_t* src, 113 | int src_stride, 114 | uint16_t* dst, 115 | int dst_stride, 116 | int width, 117 | int height); 118 | 119 | /** 120 | * 同时将NV21或者NV12的UV数据旋转270, 并交叉转换UV顺序, 可用作NV12与NV21之间的UV互转 121 | * @param src 122 | * @param src_stride 123 | * @param dst 124 | * @param dst_stride 125 | * @param width UV数据的宽 126 | * @param height UV数据的高 127 | */ 128 | void RotateNV_UV270_X(const uint16_t* src, 129 | int src_stride, 130 | uint16_t* dst, 131 | int dst_stride, 132 | int width, 133 | int height); 134 | 135 | /** 136 | * 同时将NV21或者NV12的UV数据旋转180, 并交叉转换UV顺序, 可用作NV12与NV21之间的UV互转 137 | * @param src 138 | * @param src_stride 139 | * @param dst 140 | * @param dst_stride 141 | * @param width 142 | * @param height 143 | */ 144 | void RotateNV_UV180_X(const uint8_t* src, 145 | int src_stride, 146 | uint8_t* dst, 147 | int dst_stride, 148 | int width, 149 | int height); 150 | 151 | /** 152 | * NV21的数据拷贝 153 | * @param src_y 154 | * @param src_stride_y 155 | * @param src_vu 156 | * @param src_stride_vu 157 | * @param dst_y 158 | * @param dst_stride_y 159 | * @param dst_vu 160 | * @param dst_stride_vu 161 | * @param width 162 | * @param height 163 | */ 164 | void NV21Copy(const uint8_t* src_y, 165 | int src_stride_y, 166 | const uint8_t* src_vu, 167 | int src_stride_vu, 168 | uint8_t* dst_y, 169 | int dst_stride_y, 170 | uint8_t* dst_vu, 171 | int dst_stride_vu, 172 | int width, 173 | int height); 174 | 175 | /** 176 | * NV21旋转 177 | * @param src_y 178 | * @param src_stride_y 179 | * @param src_vu 180 | * @param src_stride_vu 181 | * @param dst_y 182 | * @param dst_stride_y 183 | * @param dst_vu 184 | * @param dst_stride_vu 185 | * @param width 186 | * @param height 187 | * @param mode 188 | */ 189 | void NV21Rotate(const uint8_t* src_y, 190 | int src_stride_y, 191 | const uint8_t* src_vu, 192 | int src_stride_vu, 193 | uint8_t* dst_y, 194 | int dst_stride_y, 195 | uint8_t* dst_vu, 196 | int dst_stride_vu, 197 | int width, 198 | int height, 199 | enum libyuv::RotationMode mode); 200 | 201 | #endif //CAMERAMEDIACODEC_ROTATENV_UV_H -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_H_ 12 | #define INCLUDE_LIBYUV_H_ 13 | 14 | #include "libyuv/basic_types.h" 15 | #include "libyuv/compare.h" 16 | #include "libyuv/convert.h" 17 | #include "libyuv/convert_argb.h" 18 | #include "libyuv/convert_from.h" 19 | #include "libyuv/convert_from_argb.h" 20 | #include "libyuv/cpu_id.h" 21 | #include "libyuv/mjpeg_decoder.h" 22 | #include "libyuv/planar_functions.h" 23 | #include "libyuv/rotate.h" 24 | #include "libyuv/rotate_argb.h" 25 | #include "libyuv/row.h" 26 | #include "libyuv/scale.h" 27 | #include "libyuv/scale_argb.h" 28 | #include "libyuv/scale_row.h" 29 | #include "libyuv/version.h" 30 | #include "libyuv/video_common.h" 31 | 32 | #endif // INCLUDE_LIBYUV_H_ 33 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/basic_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_BASIC_TYPES_H_ 12 | #define INCLUDE_LIBYUV_BASIC_TYPES_H_ 13 | 14 | #include // For size_t and NULL 15 | 16 | #if !defined(INT_TYPES_DEFINED) && !defined(GG_LONGLONG) 17 | #define INT_TYPES_DEFINED 18 | 19 | #if defined(_MSC_VER) && (_MSC_VER < 1600) 20 | #include // for uintptr_t on x86 21 | typedef unsigned __int64 uint64_t; 22 | typedef __int64 int64_t; 23 | typedef unsigned int uint32_t; 24 | typedef int int32_t; 25 | typedef unsigned short uint16_t; 26 | typedef short int16_t; 27 | typedef unsigned char uint8_t; 28 | typedef signed char int8_t; 29 | #else 30 | #include // for uintptr_t and C99 types 31 | #endif // defined(_MSC_VER) && (_MSC_VER < 1600) 32 | // Types are deprecated. Enable this macro for legacy types. 33 | #ifdef LIBYUV_LEGACY_TYPES 34 | typedef uint64_t uint64; 35 | typedef int64_t int64; 36 | typedef uint32_t uint32; 37 | typedef int32_t int32; 38 | typedef uint16_t uint16; 39 | typedef int16_t int16; 40 | typedef uint8_t uint8; 41 | typedef int8_t int8; 42 | #endif // LIBYUV_LEGACY_TYPES 43 | #endif // INT_TYPES_DEFINED 44 | 45 | #if !defined(LIBYUV_API) 46 | #if defined(_WIN32) || defined(__CYGWIN__) 47 | #if defined(LIBYUV_BUILDING_SHARED_LIBRARY) 48 | #define LIBYUV_API __declspec(dllexport) 49 | #elif defined(LIBYUV_USING_SHARED_LIBRARY) 50 | #define LIBYUV_API __declspec(dllimport) 51 | #else 52 | #define LIBYUV_API 53 | #endif // LIBYUV_BUILDING_SHARED_LIBRARY 54 | #elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__APPLE__) && \ 55 | (defined(LIBYUV_BUILDING_SHARED_LIBRARY) || \ 56 | defined(LIBYUV_USING_SHARED_LIBRARY)) 57 | #define LIBYUV_API __attribute__((visibility("default"))) 58 | #else 59 | #define LIBYUV_API 60 | #endif // __GNUC__ 61 | #endif // LIBYUV_API 62 | 63 | // TODO(fbarchard): Remove bool macros. 64 | #define LIBYUV_BOOL int 65 | #define LIBYUV_FALSE 0 66 | #define LIBYUV_TRUE 1 67 | 68 | #endif // INCLUDE_LIBYUV_BASIC_TYPES_H_ 69 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/compare.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_COMPARE_H_ 12 | #define INCLUDE_LIBYUV_COMPARE_H_ 13 | 14 | #include "libyuv/basic_types.h" 15 | 16 | #ifdef __cplusplus 17 | namespace libyuv { 18 | extern "C" { 19 | #endif 20 | 21 | // Compute a hash for specified memory. Seed of 5381 recommended. 22 | LIBYUV_API 23 | uint32_t HashDjb2(const uint8_t* src, uint64_t count, uint32_t seed); 24 | 25 | // Hamming Distance 26 | LIBYUV_API 27 | uint64_t ComputeHammingDistance(const uint8_t* src_a, 28 | const uint8_t* src_b, 29 | int count); 30 | 31 | // Scan an opaque argb image and return fourcc based on alpha offset. 32 | // Returns FOURCC_ARGB, FOURCC_BGRA, or 0 if unknown. 33 | LIBYUV_API 34 | uint32_t ARGBDetect(const uint8_t* argb, 35 | int stride_argb, 36 | int width, 37 | int height); 38 | 39 | // Sum Square Error - used to compute Mean Square Error or PSNR. 40 | LIBYUV_API 41 | uint64_t ComputeSumSquareError(const uint8_t* src_a, 42 | const uint8_t* src_b, 43 | int count); 44 | 45 | LIBYUV_API 46 | uint64_t ComputeSumSquareErrorPlane(const uint8_t* src_a, 47 | int stride_a, 48 | const uint8_t* src_b, 49 | int stride_b, 50 | int width, 51 | int height); 52 | 53 | static const int kMaxPsnr = 128; 54 | 55 | LIBYUV_API 56 | double SumSquareErrorToPsnr(uint64_t sse, uint64_t count); 57 | 58 | LIBYUV_API 59 | double CalcFramePsnr(const uint8_t* src_a, 60 | int stride_a, 61 | const uint8_t* src_b, 62 | int stride_b, 63 | int width, 64 | int height); 65 | 66 | LIBYUV_API 67 | double I420Psnr(const uint8_t* src_y_a, 68 | int stride_y_a, 69 | const uint8_t* src_u_a, 70 | int stride_u_a, 71 | const uint8_t* src_v_a, 72 | int stride_v_a, 73 | const uint8_t* src_y_b, 74 | int stride_y_b, 75 | const uint8_t* src_u_b, 76 | int stride_u_b, 77 | const uint8_t* src_v_b, 78 | int stride_v_b, 79 | int width, 80 | int height); 81 | 82 | LIBYUV_API 83 | double CalcFrameSsim(const uint8_t* src_a, 84 | int stride_a, 85 | const uint8_t* src_b, 86 | int stride_b, 87 | int width, 88 | int height); 89 | 90 | LIBYUV_API 91 | double I420Ssim(const uint8_t* src_y_a, 92 | int stride_y_a, 93 | const uint8_t* src_u_a, 94 | int stride_u_a, 95 | const uint8_t* src_v_a, 96 | int stride_v_a, 97 | const uint8_t* src_y_b, 98 | int stride_y_b, 99 | const uint8_t* src_u_b, 100 | int stride_u_b, 101 | const uint8_t* src_v_b, 102 | int stride_v_b, 103 | int width, 104 | int height); 105 | 106 | #ifdef __cplusplus 107 | } // extern "C" 108 | } // namespace libyuv 109 | #endif 110 | 111 | #endif // INCLUDE_LIBYUV_COMPARE_H_ 112 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/compare_row.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_COMPARE_ROW_H_ 12 | #define INCLUDE_LIBYUV_COMPARE_ROW_H_ 13 | 14 | #include "libyuv/basic_types.h" 15 | 16 | #ifdef __cplusplus 17 | namespace libyuv { 18 | extern "C" { 19 | #endif 20 | 21 | #if defined(__pnacl__) || defined(__CLR_VER) || \ 22 | (defined(__native_client__) && defined(__x86_64__)) || \ 23 | (defined(__i386__) && !defined(__SSE__) && !defined(__clang__)) 24 | #define LIBYUV_DISABLE_X86 25 | #endif 26 | #if defined(__native_client__) 27 | #define LIBYUV_DISABLE_NEON 28 | #endif 29 | // MemorySanitizer does not support assembly code yet. http://crbug.com/344505 30 | #if defined(__has_feature) 31 | #if __has_feature(memory_sanitizer) 32 | #define LIBYUV_DISABLE_X86 33 | #endif 34 | #endif 35 | // Visual C 2012 required for AVX2. 36 | #if defined(_M_IX86) && !defined(__clang__) && defined(_MSC_VER) && \ 37 | _MSC_VER >= 1700 38 | #define VISUALC_HAS_AVX2 1 39 | #endif // VisualStudio >= 2012 40 | 41 | // clang >= 3.4.0 required for AVX2. 42 | #if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) 43 | #if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) 44 | #define CLANG_HAS_AVX2 1 45 | #endif // clang >= 3.4 46 | #endif // __clang__ 47 | 48 | // The following are available for Visual C and GCC: 49 | #if !defined(LIBYUV_DISABLE_X86) && \ 50 | (defined(__x86_64__) || defined(__i386__) || defined(_M_IX86)) 51 | #define HAS_HASHDJB2_SSE41 52 | #define HAS_SUMSQUAREERROR_SSE2 53 | #define HAS_HAMMINGDISTANCE_SSE42 54 | #endif 55 | 56 | // The following are available for Visual C and clangcl 32 bit: 57 | #if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) && defined(_MSC_VER) && \ 58 | (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) 59 | #define HAS_HASHDJB2_AVX2 60 | #define HAS_SUMSQUAREERROR_AVX2 61 | #endif 62 | 63 | // The following are available for GCC and clangcl 64 bit: 64 | #if !defined(LIBYUV_DISABLE_X86) && \ 65 | (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) 66 | #define HAS_HAMMINGDISTANCE_SSSE3 67 | #endif 68 | 69 | // The following are available for GCC and clangcl 64 bit: 70 | #if !defined(LIBYUV_DISABLE_X86) && defined(CLANG_HAS_AVX2) && \ 71 | (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) 72 | #define HAS_HAMMINGDISTANCE_AVX2 73 | #endif 74 | 75 | // The following are available for Neon: 76 | #if !defined(LIBYUV_DISABLE_NEON) && \ 77 | (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) 78 | #define HAS_SUMSQUAREERROR_NEON 79 | #define HAS_HAMMINGDISTANCE_NEON 80 | #endif 81 | 82 | #if !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) 83 | #define HAS_HAMMINGDISTANCE_MSA 84 | #define HAS_SUMSQUAREERROR_MSA 85 | #endif 86 | 87 | #if !defined(LIBYUV_DISABLE_MMI) && defined(_MIPS_ARCH_LOONGSON3A) 88 | #define HAS_HAMMINGDISTANCE_MMI 89 | #define HAS_SUMSQUAREERROR_MMI 90 | #endif 91 | 92 | uint32_t HammingDistance_C(const uint8_t* src_a, 93 | const uint8_t* src_b, 94 | int count); 95 | uint32_t HammingDistance_SSE42(const uint8_t* src_a, 96 | const uint8_t* src_b, 97 | int count); 98 | uint32_t HammingDistance_SSSE3(const uint8_t* src_a, 99 | const uint8_t* src_b, 100 | int count); 101 | uint32_t HammingDistance_AVX2(const uint8_t* src_a, 102 | const uint8_t* src_b, 103 | int count); 104 | uint32_t HammingDistance_NEON(const uint8_t* src_a, 105 | const uint8_t* src_b, 106 | int count); 107 | uint32_t HammingDistance_MSA(const uint8_t* src_a, 108 | const uint8_t* src_b, 109 | int count); 110 | uint32_t HammingDistance_MMI(const uint8_t* src_a, 111 | const uint8_t* src_b, 112 | int count); 113 | uint32_t SumSquareError_C(const uint8_t* src_a, 114 | const uint8_t* src_b, 115 | int count); 116 | uint32_t SumSquareError_SSE2(const uint8_t* src_a, 117 | const uint8_t* src_b, 118 | int count); 119 | uint32_t SumSquareError_AVX2(const uint8_t* src_a, 120 | const uint8_t* src_b, 121 | int count); 122 | uint32_t SumSquareError_NEON(const uint8_t* src_a, 123 | const uint8_t* src_b, 124 | int count); 125 | uint32_t SumSquareError_MSA(const uint8_t* src_a, 126 | const uint8_t* src_b, 127 | int count); 128 | uint32_t SumSquareError_MMI(const uint8_t* src_a, 129 | const uint8_t* src_b, 130 | int count); 131 | 132 | uint32_t HashDjb2_C(const uint8_t* src, int count, uint32_t seed); 133 | uint32_t HashDjb2_SSE41(const uint8_t* src, int count, uint32_t seed); 134 | uint32_t HashDjb2_AVX2(const uint8_t* src, int count, uint32_t seed); 135 | 136 | #ifdef __cplusplus 137 | } // extern "C" 138 | } // namespace libyuv 139 | #endif 140 | 141 | #endif // INCLUDE_LIBYUV_COMPARE_ROW_H_ 142 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/cpu_id.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_CPU_ID_H_ 12 | #define INCLUDE_LIBYUV_CPU_ID_H_ 13 | 14 | #include "libyuv/basic_types.h" 15 | 16 | #ifdef __cplusplus 17 | namespace libyuv { 18 | extern "C" { 19 | #endif 20 | 21 | // Internal flag to indicate cpuid requires initialization. 22 | static const int kCpuInitialized = 0x1; 23 | 24 | // These flags are only valid on ARM processors. 25 | static const int kCpuHasARM = 0x2; 26 | static const int kCpuHasNEON = 0x4; 27 | // 0x8 reserved for future ARM flag. 28 | 29 | // These flags are only valid on x86 processors. 30 | static const int kCpuHasX86 = 0x10; 31 | static const int kCpuHasSSE2 = 0x20; 32 | static const int kCpuHasSSSE3 = 0x40; 33 | static const int kCpuHasSSE41 = 0x80; 34 | static const int kCpuHasSSE42 = 0x100; // unused at this time. 35 | static const int kCpuHasAVX = 0x200; 36 | static const int kCpuHasAVX2 = 0x400; 37 | static const int kCpuHasERMS = 0x800; 38 | static const int kCpuHasFMA3 = 0x1000; 39 | static const int kCpuHasF16C = 0x2000; 40 | static const int kCpuHasGFNI = 0x4000; 41 | static const int kCpuHasAVX512BW = 0x8000; 42 | static const int kCpuHasAVX512VL = 0x10000; 43 | static const int kCpuHasAVX512VBMI = 0x20000; 44 | static const int kCpuHasAVX512VBMI2 = 0x40000; 45 | static const int kCpuHasAVX512VBITALG = 0x80000; 46 | static const int kCpuHasAVX512VPOPCNTDQ = 0x100000; 47 | 48 | // These flags are only valid on MIPS processors. 49 | static const int kCpuHasMIPS = 0x200000; 50 | static const int kCpuHasMSA = 0x400000; 51 | static const int kCpuHasMMI = 0x800000; 52 | 53 | // Optional init function. TestCpuFlag does an auto-init. 54 | // Returns cpu_info flags. 55 | LIBYUV_API 56 | int InitCpuFlags(void); 57 | 58 | // Detect CPU has SSE2 etc. 59 | // Test_flag parameter should be one of kCpuHas constants above. 60 | // Returns non-zero if instruction set is detected 61 | static __inline int TestCpuFlag(int test_flag) { 62 | LIBYUV_API extern int cpu_info_; 63 | #ifdef __ATOMIC_RELAXED 64 | int cpu_info = __atomic_load_n(&cpu_info_, __ATOMIC_RELAXED); 65 | #else 66 | int cpu_info = cpu_info_; 67 | #endif 68 | return (!cpu_info ? InitCpuFlags() : cpu_info) & test_flag; 69 | } 70 | 71 | // Internal function for parsing /proc/cpuinfo. 72 | LIBYUV_API 73 | int ArmCpuCaps(const char* cpuinfo_name); 74 | 75 | // For testing, allow CPU flags to be disabled. 76 | // ie MaskCpuFlags(~kCpuHasSSSE3) to disable SSSE3. 77 | // MaskCpuFlags(-1) to enable all cpu specific optimizations. 78 | // MaskCpuFlags(1) to disable all cpu specific optimizations. 79 | // MaskCpuFlags(0) to reset state so next call will auto init. 80 | // Returns cpu_info flags. 81 | LIBYUV_API 82 | int MaskCpuFlags(int enable_flags); 83 | 84 | // Sets the CPU flags to |cpu_flags|, bypassing the detection code. |cpu_flags| 85 | // should be a valid combination of the kCpuHas constants above and include 86 | // kCpuInitialized. Use this method when running in a sandboxed process where 87 | // the detection code might fail (as it might access /proc/cpuinfo). In such 88 | // cases the cpu_info can be obtained from a non sandboxed process by calling 89 | // InitCpuFlags() and passed to the sandboxed process (via command line 90 | // parameters, IPC...) which can then call this method to initialize the CPU 91 | // flags. 92 | // Notes: 93 | // - when specifying 0 for |cpu_flags|, the auto initialization is enabled 94 | // again. 95 | // - enabling CPU features that are not supported by the CPU will result in 96 | // undefined behavior. 97 | // TODO(fbarchard): consider writing a helper function that translates from 98 | // other library CPU info to libyuv CPU info and add a .md doc that explains 99 | // CPU detection. 100 | static __inline void SetCpuFlags(int cpu_flags) { 101 | LIBYUV_API extern int cpu_info_; 102 | #ifdef __ATOMIC_RELAXED 103 | __atomic_store_n(&cpu_info_, cpu_flags, __ATOMIC_RELAXED); 104 | #else 105 | cpu_info_ = cpu_flags; 106 | #endif 107 | } 108 | 109 | // Low level cpuid for X86. Returns zeros on other CPUs. 110 | // eax is the info type that you want. 111 | // ecx is typically the cpu number, and should normally be zero. 112 | LIBYUV_API 113 | void CpuId(int info_eax, int info_ecx, int* cpu_info); 114 | 115 | #ifdef __cplusplus 116 | } // extern "C" 117 | } // namespace libyuv 118 | #endif 119 | 120 | #endif // INCLUDE_LIBYUV_CPU_ID_H_ 121 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/rotate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_ROTATE_H_ 12 | #define INCLUDE_LIBYUV_ROTATE_H_ 13 | 14 | #include "libyuv/basic_types.h" 15 | 16 | #ifdef __cplusplus 17 | namespace libyuv { 18 | extern "C" { 19 | #endif 20 | 21 | // Supported rotation. 22 | typedef enum RotationMode { 23 | kRotate0 = 0, // No rotation. 24 | kRotate90 = 90, // Rotate 90 degrees clockwise. 25 | kRotate180 = 180, // Rotate 180 degrees. 26 | kRotate270 = 270, // Rotate 270 degrees clockwise. 27 | 28 | // Deprecated. 29 | kRotateNone = 0, 30 | kRotateClockwise = 90, 31 | kRotateCounterClockwise = 270, 32 | } RotationModeEnum; 33 | 34 | // Rotate I420 frame. 35 | LIBYUV_API 36 | int I420Rotate(const uint8_t* src_y, 37 | int src_stride_y, 38 | const uint8_t* src_u, 39 | int src_stride_u, 40 | const uint8_t* src_v, 41 | int src_stride_v, 42 | uint8_t* dst_y, 43 | int dst_stride_y, 44 | uint8_t* dst_u, 45 | int dst_stride_u, 46 | uint8_t* dst_v, 47 | int dst_stride_v, 48 | int width, 49 | int height, 50 | enum RotationMode mode); 51 | 52 | // Rotate I444 frame. 53 | LIBYUV_API 54 | int I444Rotate(const uint8_t* src_y, 55 | int src_stride_y, 56 | const uint8_t* src_u, 57 | int src_stride_u, 58 | const uint8_t* src_v, 59 | int src_stride_v, 60 | uint8_t* dst_y, 61 | int dst_stride_y, 62 | uint8_t* dst_u, 63 | int dst_stride_u, 64 | uint8_t* dst_v, 65 | int dst_stride_v, 66 | int width, 67 | int height, 68 | enum RotationMode mode); 69 | 70 | // Rotate NV12 input and store in I420. 71 | LIBYUV_API 72 | int NV12ToI420Rotate(const uint8_t* src_y, 73 | int src_stride_y, 74 | const uint8_t* src_uv, 75 | int src_stride_uv, 76 | uint8_t* dst_y, 77 | int dst_stride_y, 78 | uint8_t* dst_u, 79 | int dst_stride_u, 80 | uint8_t* dst_v, 81 | int dst_stride_v, 82 | int width, 83 | int height, 84 | enum RotationMode mode); 85 | 86 | // Rotate a plane by 0, 90, 180, or 270. 87 | LIBYUV_API 88 | int RotatePlane(const uint8_t* src, 89 | int src_stride, 90 | uint8_t* dst, 91 | int dst_stride, 92 | int width, 93 | int height, 94 | enum RotationMode mode); 95 | 96 | // Rotate planes by 90, 180, 270. Deprecated. 97 | LIBYUV_API 98 | void RotatePlane90(const uint8_t* src, 99 | int src_stride, 100 | uint8_t* dst, 101 | int dst_stride, 102 | int width, 103 | int height); 104 | 105 | LIBYUV_API 106 | void RotatePlane180(const uint8_t* src, 107 | int src_stride, 108 | uint8_t* dst, 109 | int dst_stride, 110 | int width, 111 | int height); 112 | 113 | LIBYUV_API 114 | void RotatePlane270(const uint8_t* src, 115 | int src_stride, 116 | uint8_t* dst, 117 | int dst_stride, 118 | int width, 119 | int height); 120 | 121 | LIBYUV_API 122 | void RotateUV90(const uint8_t* src, 123 | int src_stride, 124 | uint8_t* dst_a, 125 | int dst_stride_a, 126 | uint8_t* dst_b, 127 | int dst_stride_b, 128 | int width, 129 | int height); 130 | 131 | // Rotations for when U and V are interleaved. 132 | // These functions take one input pointer and 133 | // split the data into two buffers while 134 | // rotating them. Deprecated. 135 | LIBYUV_API 136 | void RotateUV180(const uint8_t* src, 137 | int src_stride, 138 | uint8_t* dst_a, 139 | int dst_stride_a, 140 | uint8_t* dst_b, 141 | int dst_stride_b, 142 | int width, 143 | int height); 144 | 145 | LIBYUV_API 146 | void RotateUV270(const uint8_t* src, 147 | int src_stride, 148 | uint8_t* dst_a, 149 | int dst_stride_a, 150 | uint8_t* dst_b, 151 | int dst_stride_b, 152 | int width, 153 | int height); 154 | 155 | // The 90 and 270 functions are based on transposes. 156 | // Doing a transpose with reversing the read/write 157 | // order will result in a rotation by +- 90 degrees. 158 | // Deprecated. 159 | LIBYUV_API 160 | void TransposePlane(const uint8_t* src, 161 | int src_stride, 162 | uint8_t* dst, 163 | int dst_stride, 164 | int width, 165 | int height); 166 | 167 | LIBYUV_API 168 | void TransposeUV(const uint8_t* src, 169 | int src_stride, 170 | uint8_t* dst_a, 171 | int dst_stride_a, 172 | uint8_t* dst_b, 173 | int dst_stride_b, 174 | int width, 175 | int height); 176 | 177 | #ifdef __cplusplus 178 | } // extern "C" 179 | } // namespace libyuv 180 | #endif 181 | 182 | #endif // INCLUDE_LIBYUV_ROTATE_H_ 183 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/rotate_argb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_ROTATE_ARGB_H_ 12 | #define INCLUDE_LIBYUV_ROTATE_ARGB_H_ 13 | 14 | #include "libyuv/basic_types.h" 15 | #include "libyuv/rotate.h" // For RotationMode. 16 | 17 | #ifdef __cplusplus 18 | namespace libyuv { 19 | extern "C" { 20 | #endif 21 | 22 | // Rotate ARGB frame 23 | LIBYUV_API 24 | int ARGBRotate(const uint8_t* src_argb, 25 | int src_stride_argb, 26 | uint8_t* dst_argb, 27 | int dst_stride_argb, 28 | int src_width, 29 | int src_height, 30 | enum RotationMode mode); 31 | 32 | #ifdef __cplusplus 33 | } // extern "C" 34 | } // namespace libyuv 35 | #endif 36 | 37 | #endif // INCLUDE_LIBYUV_ROTATE_ARGB_H_ 38 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/scale_argb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_SCALE_ARGB_H_ 12 | #define INCLUDE_LIBYUV_SCALE_ARGB_H_ 13 | 14 | #include "libyuv/basic_types.h" 15 | #include "libyuv/scale.h" // For FilterMode 16 | 17 | #ifdef __cplusplus 18 | namespace libyuv { 19 | extern "C" { 20 | #endif 21 | 22 | LIBYUV_API 23 | int ARGBScale(const uint8_t* src_argb, 24 | int src_stride_argb, 25 | int src_width, 26 | int src_height, 27 | uint8_t* dst_argb, 28 | int dst_stride_argb, 29 | int dst_width, 30 | int dst_height, 31 | enum FilterMode filtering); 32 | 33 | // Clipped scale takes destination rectangle coordinates for clip values. 34 | LIBYUV_API 35 | int ARGBScaleClip(const uint8_t* src_argb, 36 | int src_stride_argb, 37 | int src_width, 38 | int src_height, 39 | uint8_t* dst_argb, 40 | int dst_stride_argb, 41 | int dst_width, 42 | int dst_height, 43 | int clip_x, 44 | int clip_y, 45 | int clip_width, 46 | int clip_height, 47 | enum FilterMode filtering); 48 | 49 | // Scale with YUV conversion to ARGB and clipping. 50 | LIBYUV_API 51 | int YUVToARGBScaleClip(const uint8_t* src_y, 52 | int src_stride_y, 53 | const uint8_t* src_u, 54 | int src_stride_u, 55 | const uint8_t* src_v, 56 | int src_stride_v, 57 | uint32_t src_fourcc, 58 | int src_width, 59 | int src_height, 60 | uint8_t* dst_argb, 61 | int dst_stride_argb, 62 | uint32_t dst_fourcc, 63 | int dst_width, 64 | int dst_height, 65 | int clip_x, 66 | int clip_y, 67 | int clip_width, 68 | int clip_height, 69 | enum FilterMode filtering); 70 | 71 | #ifdef __cplusplus 72 | } // extern "C" 73 | } // namespace libyuv 74 | #endif 75 | 76 | #endif // INCLUDE_LIBYUV_SCALE_ARGB_H_ 77 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/libyuv/version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The LibYuv Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #ifndef INCLUDE_LIBYUV_VERSION_H_ 12 | #define INCLUDE_LIBYUV_VERSION_H_ 13 | 14 | #define LIBYUV_VERSION 1741 15 | 16 | #endif // INCLUDE_LIBYUV_VERSION_H_ 17 | -------------------------------------------------------------------------------- /app/src/main/cpp/jniLibs/arm64-v8a/libyuv.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/cpp/jniLibs/arm64-v8a/libyuv.so -------------------------------------------------------------------------------- /app/src/main/cpp/jniLibs/armeabi-v7a/libyuv.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/cpp/jniLibs/armeabi-v7a/libyuv.so -------------------------------------------------------------------------------- /app/src/main/cpp/jniLibs/x86/libyuv.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/cpp/jniLibs/x86/libyuv.so -------------------------------------------------------------------------------- /app/src/main/cpp/jniLibs/x86_64/libyuv.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/cpp/jniLibs/x86_64/libyuv.so -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/App.java: -------------------------------------------------------------------------------- 1 | package you.chen.media; 2 | 3 | import android.app.Application; 4 | 5 | import you.chen.media.utils.Utils; 6 | 7 | /** 8 | * Created by you on 2018-01-18. 9 | */ 10 | public class App extends Application { 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | Utils.init(this); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/YuvTests.java: -------------------------------------------------------------------------------- 1 | package you.chen.media; 2 | 3 | /** 4 | * Created by you on 2018-07-06. 5 | * 简单的nv21与i420,NV12,YV12转换, 如需要旋转裁剪可以使用YuvUtils高性能 6 | * @deprecated Use {@link you.chen.media.core.YuvUtils} 7 | */ 8 | 9 | @Deprecated 10 | public class YuvTests { 11 | 12 | public static void nv21ToI420(byte[] nv21, byte[] i420, int w, int h) { 13 | int frameSize = w * h; 14 | System.arraycopy(nv21, 0, i420, 0, frameSize); 15 | 16 | int start = frameSize + (frameSize >> 2); 17 | int i, half; 18 | for (i = 0; i < frameSize >> 1; i += 2) { 19 | half = i >> 1; 20 | i420[frameSize + half] = nv21[frameSize + i + 1]; 21 | i420[start + half] = nv21[frameSize + i]; 22 | } 23 | } 24 | 25 | public static void nv21ToNV12(byte[] nv21, byte[] nv12, int w, int h) { 26 | int frameSize = w * h; 27 | System.arraycopy(nv21, 0, nv12, 0, frameSize); 28 | 29 | int i, start; 30 | for (i = 0; i < frameSize >> 2; i++) { 31 | start = frameSize + (i << 1); 32 | nv12[start] = nv21[start + 1]; 33 | nv12[start + 1] = nv21[start]; 34 | } 35 | } 36 | 37 | public static void nv21ToYV12(byte[] nv21, byte[] yv12, int w, int h) { 38 | int frameSize = w * h; 39 | System.arraycopy(nv21, 0, yv12, 0, frameSize); 40 | 41 | int start = frameSize + (frameSize >> 2); 42 | int i, half; 43 | for (i = 0; i < frameSize >> 1; i += 2) { 44 | half = i >> 1; 45 | yv12[frameSize + half] = nv21[frameSize + i]; 46 | yv12[start + half] = nv21[frameSize + i + 1]; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/camera/CameraUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.camera; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Matrix; 6 | import android.graphics.Point; 7 | import android.graphics.PointF; 8 | 9 | import you.chen.media.utils.LogUtils; 10 | 11 | /** 12 | * 13 | * Created by you on 2018/3/24. 14 | */ 15 | final public class CameraUtils { 16 | 17 | private CameraUtils() {} 18 | 19 | /** 20 | * 计算出控件中坐标点击效果在裁剪旋转90度之前的实际坐标比例,逆旋转90度时,newX = y, newY = w - x 21 | * @param x 22 | * @param y 23 | * @param w 24 | * @param h 25 | * @param cameraMatrix 26 | * @return 27 | */ 28 | public static PointF reverseRotate(float x, float y, int w, int h, Matrix cameraMatrix) { 29 | if (cameraMatrix != null) { 30 | float[] floats = new float[9];//数组0下标为X轴scale, 4为Y轴scale 31 | cameraMatrix.getValues(floats); 32 | if (floats[0] == 1.0f && floats[4] > 1.0f) {//Y轴被裁剪 33 | float preHeight = h * floats[4];//裁剪前的高度 34 | y = (preHeight - h) / 2 + y;//加上被裁剪的长度 35 | LogUtils.i("Y轴被裁剪了, x = %f, y = %f", x, y); 36 | return new PointF(y / preHeight, (w - x) / w); 37 | } else if (floats[0] > 1.0f && floats[4] == 1.0f) {//X轴被裁剪 38 | float preWidth = w * floats[0];//裁剪前的宽度 39 | x = (preWidth - w) / 2 + x;//加上被裁剪的长度 40 | LogUtils.i("X轴被裁剪了, x = %f, y = %f", x, y); 41 | return new PointF(y / h, (preWidth - x) / preWidth); 42 | } 43 | } 44 | return new PointF(y / h, (w - x) / w); 45 | } 46 | 47 | /** 48 | * 矩阵缩放处理后的大小 49 | * @param w 原始预览宽 50 | * @param h 原始预览高 51 | * @param matrix 52 | * @return 53 | */ 54 | public static Point matrixSize(int w, int h, Matrix matrix) { 55 | if (matrix != null) { 56 | float[] floats = new float[9];//数组0下标为X轴scale, 4为Y轴scale 57 | matrix.getValues(floats); 58 | 59 | if (floats[0] == 1.0f && floats[4] > 1.0f) { //Y轴需要裁剪,实则裁剪相机的width 60 | int clipWidth = (int) (w / floats[4]); 61 | clipWidth = (clipWidth + 1) & ~1;//裁剪后的宽高都必须为偶数 62 | if (clipWidth < w) { 63 | return new Point(clipWidth, h); 64 | } 65 | } else if (floats[0] > 1.0f && floats[4] == 1.0f) {//X轴需要裁剪,实则裁剪相机的height 66 | int clipHeight = (int) (h / floats[0]); 67 | clipHeight = (clipHeight + 1) & ~1;//裁剪后的宽高都必须为偶数 68 | if (clipHeight < h) { 69 | return new Point(w, clipHeight); 70 | } 71 | } 72 | } 73 | return new Point(w, h); 74 | } 75 | 76 | /** 77 | * 旋转Bitmap 78 | * @param bitmap 79 | * @param degree 80 | * @return 81 | */ 82 | public static Bitmap rotateBitmap(Bitmap bitmap, int degree) { 83 | if (degree == 0) return bitmap; 84 | Matrix matrix = new Matrix(); 85 | matrix.setRotate(degree); 86 | Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); 87 | bitmap.recycle(); 88 | return newBitmap; 89 | } 90 | 91 | /** 92 | * 剪切并旋转角度 93 | * @param bitmap 94 | * @param x 95 | * @param y 96 | * @param degree 97 | * @return 98 | */ 99 | public static Bitmap rotateClipBitmap(Bitmap bitmap, int x, int y, int width, int height, int degree) { 100 | Matrix matrix = null; 101 | if (degree > 0) { 102 | matrix = new Matrix(); 103 | matrix.setRotate(degree); 104 | } 105 | Bitmap clipBitmap = Bitmap.createBitmap(bitmap, x, y, width, height, matrix, true); 106 | bitmap.recycle(); 107 | return clipBitmap; 108 | } 109 | 110 | /** 111 | * 考虑到在Camera中直接设置parameters.setRotation(getCameraRotation(orientation)); 112 | * 对部分机型无效,因此统一采用预览拍照后再作旋转 113 | * 对原始图片数据裁剪旋转 114 | * @param datas 115 | * @param cameraMatrix 116 | * @param orientation 117 | * @return 118 | */ 119 | public static Bitmap bytesToBitmap(byte[] datas, Matrix cameraMatrix, int orientation) { 120 | Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length); 121 | orientation += 90;//需要在原有之上增加旋转的角度 122 | if (orientation >= 360) orientation = 0; 123 | if (cameraMatrix != null) { 124 | float[] floats = new float[9];//数组0下标为X轴scale, 4为Y轴scale 125 | cameraMatrix.getValues(floats); 126 | if (floats[0] == 1.0f && floats[4] > 1.0f) {//Y轴需要裁剪,实际为width需要裁剪 127 | int clipWidth = (int) (bitmap.getWidth() / floats[4]); 128 | int x = (bitmap.getWidth() - clipWidth) >> 1; 129 | return rotateClipBitmap(bitmap, x, 0, clipWidth, bitmap.getHeight(), orientation); 130 | } else if (floats[0] > 1.0f && floats[4] == 1.0f) {//X轴需要裁剪,实际为height需要裁剪 131 | int clipHeight = (int) (bitmap.getHeight() / floats[0]); 132 | int y = (bitmap.getHeight() - clipHeight) >> 1; 133 | return rotateClipBitmap(bitmap, 0, y, bitmap.getWidth(), clipHeight, orientation); 134 | } 135 | } 136 | //不需要裁剪 137 | return rotateBitmap(bitmap, orientation); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/camera/OrientationHelper.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.camera; 2 | 3 | import android.content.Context; 4 | import android.view.OrientationEventListener; 5 | 6 | 7 | /** 8 | * Created by you on 2018-03-21. 9 | * 只监听手机方向感应旋转 0, 90, 180, 270 10 | * 11 | * 可以在后面新的appcompat版本中结合Lifecycle 实现 LifecycleEventObserver, 使用时 getLifecycle().addObserver(OrientationHelper)即可 12 | * override fun onStateChanged(owner: LifecycleOwner, event: Lifecycle.Event) { 13 | * if (event == Lifecycle.Event.ON_RESUME) { 14 | * enable() 15 | * } else if (event == Lifecycle.Event.ON_PAUSE) { 16 | * disable() 17 | * } else if (event == Lifecycle.Event.ON_DESTROY) { 18 | * owner.lifecycle.removeObserver(this) 19 | * } 20 | * } 21 | */ 22 | public class OrientationHelper { //上面注释 23 | 24 | private int orientation = 0; 25 | 26 | private final OrientationListener listener; 27 | 28 | public OrientationHelper(Context context) { 29 | listener = new OrientationListener(context.getApplicationContext(), this); 30 | } 31 | 32 | /** 33 | * 开启方向感应 34 | */ 35 | public final void enable() { 36 | if (listener.canDetectOrientation()) { 37 | listener.enable(); 38 | } else { 39 | listener.disable(); 40 | } 41 | } 42 | 43 | /** 44 | * 取消方向感应 45 | */ 46 | public final void disable() { 47 | listener.disable(); 48 | } 49 | 50 | /** 51 | * 获取当前方向感应 52 | * @return 53 | */ 54 | public final int getOrientation() { 55 | return orientation; 56 | } 57 | 58 | /** 59 | * 旋转角度改变 60 | * @param orientation 61 | */ 62 | public void onOrientationChanged(int orientation) { 63 | //nothing to override 64 | } 65 | 66 | static class OrientationListener extends OrientationEventListener { 67 | 68 | OrientationHelper helper; 69 | 70 | public OrientationListener(Context context, OrientationHelper helper) { 71 | super(context); 72 | this.helper = helper; 73 | } 74 | 75 | /** 76 | * 转换当前角度为 0, 90, 180, 270, 360即为0 77 | * @param orientation 78 | * @return 79 | */ 80 | private int transformOrientation(int orientation) { 81 | int rotation = (orientation + 45) / 90 * 90; 82 | if (rotation >= 360) rotation = 0; 83 | return rotation; 84 | } 85 | 86 | @Override 87 | public void onOrientationChanged(int orientation) { 88 | if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return; 89 | orientation = transformOrientation(orientation); 90 | if (helper.orientation != orientation) { 91 | helper.orientation = orientation; 92 | helper.onOrientationChanged(orientation); 93 | } 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/camera/SizeFilter.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.camera; 2 | 3 | import android.hardware.Camera; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * pic与preview 尺寸筛选器 9 | * Created by you on 2018/3/24. 10 | */ 11 | public interface SizeFilter { 12 | 13 | /** 14 | * 查找出最合适的图片尺寸, 视频可以忽略 15 | * @param outs 16 | * @param w 17 | * @param h 18 | * @return 19 | */ 20 | Camera.Size findOptimalPicSize(List outs, int w, int h); 21 | 22 | /** 23 | * 查找出最合适的预览尺寸, 一般先确认拍摄Pic的尺寸 24 | * @param outs 25 | * @param picSize 26 | * @param w 27 | * @param h 28 | * @return 29 | */ 30 | Camera.Size findOptimalPreSize(List outs, Camera.Size picSize, int w, int h); 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/camera/impl/HightSizeFilter.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.camera.impl; 2 | 3 | import android.hardware.Camera; 4 | 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | 9 | import you.chen.media.camera.SizeFilter; 10 | 11 | /** 12 | * 高清拍照, 只需要找出最大的尺寸即可,不推荐使用 13 | * Created by you on 2018/3/24. 14 | */ 15 | public class HightSizeFilter implements SizeFilter { 16 | 17 | private CameraCompare compare; 18 | 19 | public HightSizeFilter() { 20 | compare = new CameraCompare(); 21 | } 22 | 23 | @Override 24 | public Camera.Size findOptimalPicSize(List outs, int w, int h) { 25 | if (outs != null && !outs.isEmpty()) { 26 | Collections.sort(outs, compare); 27 | return outs.get(0); 28 | } 29 | return null; 30 | } 31 | 32 | /** 33 | * 优先找出宽高比例最按近pic的, 再找出尺寸最接近surface的 34 | * @param outs 35 | * @param picSize 36 | * @param w 37 | * @param h 38 | * @return 39 | */ 40 | @Override 41 | public Camera.Size findOptimalPreSize(List outs, Camera.Size picSize, int w, int h) { 42 | if (outs == null || outs.isEmpty()) return null; 43 | 44 | float targetScale = picSize.width / (float) picSize.height; 45 | long targetSize = w * (long) h; 46 | 47 | Camera.Size optimalSize = null; 48 | float optimalDiff = Float.MAX_VALUE; //宽高比例与pic的差 49 | long optimalMaxDif = Long.MAX_VALUE; //最优的最大值差距 50 | 51 | for (Camera.Size size : outs) { 52 | float newDiff = Math.abs(size.width / (float) size.height - targetScale); 53 | if (newDiff < optimalDiff) {//更好的比例 54 | optimalDiff = newDiff; 55 | optimalSize = size; 56 | optimalMaxDif = Math.abs(targetSize - size.width * (long) size.height); 57 | } else if (newDiff == optimalDiff) { 58 | long newOptimalMaxDif = Math.abs(targetSize - size.width * (long) size.height); 59 | if (newOptimalMaxDif < optimalMaxDif) { 60 | optimalSize = size; 61 | optimalMaxDif = newOptimalMaxDif; 62 | } 63 | } 64 | } 65 | return optimalSize; 66 | } 67 | 68 | private static class CameraCompare implements Comparator { 69 | @Override 70 | public int compare(Camera.Size o1, Camera.Size o2) { 71 | return Long.signum((long) o2.width * o2.height - (long) o1.width * o1.height); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/camera/impl/VideoSizeFilter.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.camera.impl; 2 | 3 | import android.hardware.Camera; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import you.chen.media.camera.SizeFilter; 9 | 10 | 11 | /** 12 | * 常用的尺寸筛选, 优先采用尺寸限制(适用于尺寸比较固定的需求) 13 | * 优先筛选尺寸最接近, 再筛选宽高比例最接近的 14 | * Created by you on 2018/3/24. 15 | */ 16 | public class VideoSizeFilter implements SizeFilter { 17 | 18 | //默认固定最大尺寸,此参数已属于高清,若节省流量适配早期低配手机可以设置 540 * 960 19 | public static final int DEF_MAX_WIDTH = 720; 20 | public static final int DEF_MAX_HEIGHT = 1280; 21 | 22 | private final int maxWidth, maxHeight; 23 | 24 | public VideoSizeFilter() { 25 | this(DEF_MAX_WIDTH, DEF_MAX_HEIGHT); 26 | } 27 | 28 | public VideoSizeFilter(int maxWidth, int maxHeight) { 29 | this.maxWidth = maxWidth; 30 | this.maxHeight = maxHeight; 31 | } 32 | 33 | /** 34 | * 拍摄视频可忽略Picture尺寸 35 | * @param outs 36 | * @param w 37 | * @param h 38 | * @return 39 | */ 40 | @Override 41 | public Camera.Size findOptimalPicSize(List outs, int w, int h) { 42 | return null; 43 | } 44 | 45 | /** 46 | * 查找出最佳预览尺寸,优先找出尺寸小于并最接近最大限制的尺寸 47 | * @param outs 48 | * @param picSize 49 | * @param w 50 | * @param h 51 | * @return 52 | */ 53 | @Override 54 | public Camera.Size findOptimalPreSize(List outs, Camera.Size picSize, int w, int h) { 55 | List filterSize = filterSize(outs); 56 | if (filterSize == null || filterSize.isEmpty()) return null; 57 | Camera.Size optimalSize = null; 58 | 59 | float targetScale = h / (float) w; 60 | int minDiffSize = Integer.MAX_VALUE; 61 | int maxDiffSize = Integer.MAX_VALUE; 62 | float optimalScale = Float.MAX_VALUE; 63 | 64 | for (Camera.Size size : filterSize) { 65 | float newOptimalScale = Math.abs(size.width / (float) size.height - targetScale); 66 | int diffWidth = Math.abs(maxWidth - size.height); 67 | int diffHeight = Math.abs(maxHeight - size.width); 68 | //先找出宽高与最大宽高需求的最小与最大差值 69 | int newMinDiffSize, newMaxDiffSize; 70 | if (diffWidth > diffHeight) { 71 | newMinDiffSize = diffHeight; 72 | newMaxDiffSize = diffWidth; 73 | } else { 74 | newMinDiffSize = diffWidth; 75 | newMaxDiffSize = diffHeight; 76 | } 77 | 78 | if (newMinDiffSize < minDiffSize) { 79 | optimalSize = size; 80 | minDiffSize = newMinDiffSize; 81 | maxDiffSize = newMaxDiffSize; 82 | optimalScale = newOptimalScale; 83 | } else if (newMinDiffSize == minDiffSize) { 84 | if (newMaxDiffSize < maxDiffSize) { 85 | optimalSize = size; 86 | maxDiffSize = newMaxDiffSize; 87 | optimalScale = newOptimalScale; 88 | } else if (newMaxDiffSize == maxDiffSize) { 89 | if (newOptimalScale < optimalScale) { 90 | optimalSize = size; 91 | optimalScale = newOptimalScale; 92 | } 93 | } 94 | } 95 | } 96 | return optimalSize; 97 | } 98 | 99 | /** 100 | * 优先找出尺寸小于最大限制的尺寸 101 | * @param outs 102 | * @return 103 | */ 104 | private List filterSize(List outs) { 105 | if (outs == null || outs.isEmpty()) return null; 106 | List filterSizes = new ArrayList<>();//最佳的筛选 107 | List secondarySizes = new ArrayList<>();//次要的筛选 108 | for (Camera.Size size : outs) { 109 | if (size.width <= maxHeight && size.height <= maxWidth) { 110 | filterSizes.add(size); 111 | } else if (size.width <= maxHeight || size.height <= maxWidth) { 112 | secondarySizes.add(size); 113 | } 114 | } 115 | if (!filterSizes.isEmpty()) { 116 | return filterSizes; 117 | } 118 | if (!secondarySizes.isEmpty()) { 119 | return secondarySizes; 120 | } 121 | return outs; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/BytePool.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 字节池, 在裁剪与编解码时会大量的使用大的byte[],防止内存抖动 7 | * Created by you on 2018/3/24. 8 | */ 9 | public final class BytePool { 10 | 11 | private static final int DEF_MAX = 10; 12 | 13 | //缓存的字节数组大小, 只缓存一种大小的字节池 14 | private final int length; 15 | //最大缓存数量 16 | private final int maxSize; 17 | 18 | private final LinkedList bytepools = new LinkedList<>(); 19 | 20 | public BytePool(int length) { 21 | this(DEF_MAX, length); 22 | } 23 | public BytePool(int maxSize, int length) { 24 | this.maxSize = maxSize; 25 | this.length = length; 26 | } 27 | 28 | public int getLength() { 29 | return length; 30 | } 31 | 32 | public synchronized final boolean put(byte[] bytes) { 33 | if (bytes.length == length && bytepools.size() < maxSize) { 34 | return bytepools.add(bytes); 35 | } 36 | return false; 37 | } 38 | 39 | public synchronized final byte[] get() { 40 | if (bytepools.isEmpty()) return new byte[length]; 41 | return bytepools.remove(); 42 | } 43 | 44 | public void clear() { 45 | bytepools.clear(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/Constant.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.MediaCodecInfo; 5 | 6 | /** 7 | * Created by you on 2018-05-19. 8 | * 多媒体编码的常用参数 9 | */ 10 | public interface Constant { 11 | 12 | // ----------------- H264 ----------------- 13 | int FRAME_RATE = 20; //Camera中一般支持7~30 14 | int IFRAME_INTERVAL = 10; //关键帧间隔 15 | 16 | //相机的默认最小与最大帧率参数, 包含 FRAME_RATE 范围 17 | int DEF_MIN_FPS = 15000; 18 | int DEF_MAX_FPS = 25000; 19 | 20 | //扫描类的可以适当帧率高一些 21 | int SCAN_MIN_FPS = 2500; 22 | int SCAN_MAX_FPS = 3000; 23 | 24 | /** 25 | * 码率系数, w * h * 3, 此参数越大拍出的视频质量越大,最好不超过FRAME_RATE 26 | */ 27 | int VIDEO_BITRATE_COEFFICIENT = 3; 28 | 29 | 30 | //----------------- Audio ----------------- 31 | 32 | //双声道 33 | int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO; 34 | int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT; 35 | 36 | int SAMPLE_RATE = 44100; 37 | //两声道对应的channelConfig为AudioFormat.CHANNEL_OUT_STEREO 38 | int CHANNEL_COUNT = 2; 39 | int AUDIO_RATE = 1000 << 6; 40 | int AAC_PROFILE = MediaCodecInfo.CodecProfileLevel.AACObjectLC; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/MediaEncoder.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core; 2 | 3 | import android.media.MediaCodec; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.util.concurrent.BlockingQueue; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.LinkedBlockingDeque; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | /** 12 | * Created by you on 2018-05-10. 13 | * MediaCodec核心编码器 14 | */ 15 | public final class MediaEncoder implements Runnable { 16 | 17 | private static final String TAG = MediaEncoder.class.getSimpleName(); 18 | 19 | public static final int DEF_TIMEOUT_USEC = 10_000; 20 | //编码器 21 | protected final MediaCodec mediaCodec; 22 | //待编码的数据 Camera.byte[], pcm 23 | private final BlockingQueue bufferQueue = new LinkedBlockingDeque<>(); 24 | //字节池 25 | private final BytePool bytePool; 26 | //数据转换 27 | private final Transform transform; 28 | //编码的buffer信息 29 | private MediaCodec.BufferInfo bufferInfo; 30 | //是否正在录制待编码 31 | private final AtomicBoolean isCoding = new AtomicBoolean(false); 32 | //callback 33 | private final Callback callback; 34 | //等待取出编码后的数据时长 35 | private final int timeoutUs; 36 | 37 | public MediaEncoder(MediaCodec mediaCodec, BytePool bytePool, Transform transform, Callback callback) { 38 | this(mediaCodec, bytePool, transform, callback, DEF_TIMEOUT_USEC); 39 | } 40 | 41 | public MediaEncoder(MediaCodec mediaCodec, BytePool bytePool, Transform transform, Callback callback, int timeoutUs) { 42 | this.mediaCodec = mediaCodec; 43 | this.bytePool = bytePool; 44 | this.transform = transform; 45 | this.callback = callback; 46 | this.timeoutUs = timeoutUs; 47 | } 48 | 49 | /** 50 | * start 51 | * @param executorService 52 | */ 53 | public void start(ExecutorService executorService) { 54 | if (mediaCodec != null) { 55 | mediaCodec.start(); 56 | isCoding.set(true); 57 | } 58 | executorService.execute(this); 59 | } 60 | 61 | //stop 62 | public void stop() { 63 | isCoding.set(false); 64 | //如果队列已经处于阻塞时,用此唤醒, notify? 65 | bufferQueue.add(new byte[0]); 66 | } 67 | 68 | //往里添加数据 69 | public void push(byte[] data, int len) { 70 | if (isCoding.get()) { 71 | byte[] buffer = bytePool.get(); 72 | //LogUtils.i(TAG, "buffQueue un codec %s - %d", callback.getClass().getSimpleName(), bufferQueue.size()); 73 | transform.transform(data, buffer, len); 74 | //实际项目开发中要对Queue限制最大等待数量 75 | bufferQueue.add(buffer); 76 | } 77 | } 78 | 79 | public void push(byte[] data) { 80 | push(data, data.length); 81 | } 82 | 83 | //释放资源 84 | private void release() { 85 | bufferQueue.clear(); 86 | bytePool.clear(); 87 | if (mediaCodec != null) { 88 | mediaCodec.stop(); 89 | mediaCodec.release(); 90 | } 91 | callback.onRelease(); 92 | } 93 | 94 | @Override 95 | public void run() { 96 | bufferInfo = new MediaCodec.BufferInfo(); 97 | callback.onInitStart(); 98 | while (isCoding.get()) { 99 | try { 100 | byte[] buffer = bufferQueue.take(); 101 | if (buffer == null || buffer.length == 0) { 102 | break;//用空byte[]来终止循环与阻塞 103 | } 104 | codecDatas(buffer); 105 | //缓存 106 | bytePool.put(buffer); 107 | } catch (InterruptedException e) { 108 | if (!isCoding.get()) { 109 | break; 110 | } 111 | } 112 | } 113 | release(); 114 | } 115 | 116 | /** 117 | * 编码datas数据 118 | * @param buffer 119 | */ 120 | private void codecDatas(byte[] buffer) { 121 | //加入缓冲区, -1如果当前没有可用的缓冲时会进入阻塞状态, 0时会立刻返回 122 | int index = mediaCodec.dequeueInputBuffer(-1); 123 | if (index >= 0) { 124 | //填充数据 125 | ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index); 126 | inputBuffer.clear(); 127 | inputBuffer.put(buffer, 0, buffer.length); 128 | 129 | callback.onEncodeInputBuffer(mediaCodec, buffer, index); 130 | } 131 | int encodeStatus; 132 | while (true) { 133 | //返回的三种状态 INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED, INFO_OUTPUT_BUFFERS_CHANGED, 134 | encodeStatus = mediaCodec.dequeueOutputBuffer(bufferInfo, timeoutUs); 135 | if (encodeStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 136 | break;//稍后重试 137 | } else if (encodeStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ 138 | //这里只会回调一次用于初始化 139 | callback.onFormatChanged(mediaCodec); 140 | } else if (encodeStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 141 | //忽略 142 | } else { 143 | //正常编码获得缓冲下标 144 | ByteBuffer encodeData = mediaCodec.getOutputBuffer(encodeStatus); 145 | //写入编码后的数据 146 | callback.onWriteData(bufferInfo, encodeData); 147 | //释放缓存冲,后续可以存放新的编码后的数据 148 | mediaCodec.releaseOutputBuffer(encodeStatus, false); 149 | } 150 | } 151 | } 152 | 153 | /** 154 | * 编码回调类 155 | */ 156 | public interface Callback { 157 | /** 158 | * 初始化开始 159 | */ 160 | void onInitStart(); 161 | 162 | /** 163 | * 编码编入数据 164 | * @param mediaCodec 165 | * @param buffer 166 | * @param inputBufferIndex 167 | */ 168 | void onEncodeInputBuffer(MediaCodec mediaCodec, byte[] buffer, int inputBufferIndex); 169 | 170 | /** 171 | * MediaCodec初始化 172 | * @param mediaCodec 173 | */ 174 | void onFormatChanged(MediaCodec mediaCodec); 175 | 176 | /** 177 | * 写入数据 178 | * @param bufferInfo 179 | * @param encodeData 180 | */ 181 | void onWriteData(MediaCodec.BufferInfo bufferInfo, ByteBuffer encodeData); 182 | 183 | /** 184 | * release 185 | */ 186 | void onRelease(); 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/MediaUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaCodecInfo; 5 | import android.media.MediaCodecList; 6 | import android.media.MediaFormat; 7 | 8 | import java.io.IOException; 9 | 10 | import you.chen.media.utils.LogUtils; 11 | 12 | /** 13 | * Created by you on 2018-05-10. 14 | * MediaCodec Utils 15 | */ 16 | final public class MediaUtils { 17 | 18 | private MediaUtils() {} 19 | 20 | //创建AVC MediaCodec 21 | public static MediaCodec createAvcMediaCodec(int w, int h, int colorFormat, 22 | @Orientation.OrientationMode int orientation, 23 | int bitRate, int frameRate, int frameInterval) { 24 | MediaFormat format; 25 | if (orientation == Orientation.ROTATE90 || orientation == Orientation.ROTATE270) { 26 | format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, h, w); 27 | } else { 28 | format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, w, h); 29 | } 30 | //色彩空间 31 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); 32 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); 33 | format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); 34 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, frameInterval); 35 | 36 | try { 37 | MediaCodec mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); 38 | mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 39 | return mediaCodec; 40 | } catch (IOException e) { 41 | LogUtils.e(e); 42 | } 43 | return null; 44 | } 45 | 46 | //创建AAC MediaCodec 47 | public static MediaCodec createAacMediaCodec(int bufferSize, int sampleRate, 48 | int channelCount, int bitRate, int aacProfile) { 49 | MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount); 50 | format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize); 51 | format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); 52 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, aacProfile); 53 | try { 54 | MediaCodec mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC); 55 | mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 56 | return mediaCodec; 57 | } catch (IOException e) { 58 | LogUtils.e(e); 59 | } 60 | return null; 61 | } 62 | 63 | public static MediaCodecInfo selectCodecInfo(String mime) { 64 | int numCodecs = MediaCodecList.getCodecCount(); 65 | for (int i = 0; i < numCodecs; i++) { 66 | MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); 67 | if (!codecInfo.isEncoder()) { 68 | continue; 69 | } 70 | String[] types = codecInfo.getSupportedTypes(); 71 | for (int j = 0; j < types.length; j++) { 72 | if (types[j].equalsIgnoreCase(mime)) { 73 | return codecInfo; 74 | } 75 | } 76 | } 77 | return null; 78 | } 79 | 80 | //查询支持的输入格式 81 | public static int selectColorFormat() { 82 | MediaCodecInfo codecInfo = selectCodecInfo(MediaFormat.MIMETYPE_VIDEO_AVC); 83 | if (codecInfo == null) { 84 | return -1; 85 | } 86 | MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_AVC); 87 | int[] colorFormats = capabilities.colorFormats; 88 | for (int i = 0; i < colorFormats.length; i++) { 89 | if (colorFormats[i] == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar 90 | || colorFormats[i] == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar 91 | || colorFormats[i] == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar 92 | || colorFormats[i] == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar) { 93 | return colorFormats[i]; 94 | } 95 | } 96 | return -1; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/Orientation.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import androidx.annotation.IntDef; 7 | 8 | /** 9 | * Created by you on 2018-03-10. 10 | * 图像数据旋转角度 11 | */ 12 | public interface Orientation { 13 | 14 | @IntDef({ROTATE0, ROTATE90, ROTATE180, ROTATE270}) 15 | @Retention(RetentionPolicy.SOURCE) 16 | @interface OrientationMode {} 17 | 18 | /** 19 | * 此四种旋转的值与JNI中的RotationMode值对应 20 | */ 21 | int ROTATE0 = 0;//不旋转 22 | int ROTATE90 = 90; 23 | int ROTATE180 = 180; 24 | int ROTATE270 = 270; 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/Transform.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core; 2 | 3 | /** 4 | * 编码数据的转换器, 图像NV21相关的一些转换或者音频数据的处理 5 | * Created by you on 2018-03-20. 6 | */ 7 | public interface Transform { 8 | 9 | /** 10 | * 将原有数据进行转换 11 | * @param src 12 | * @param outs 13 | */ 14 | void transform(byte[] src, byte[] outs, int len); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/YuvUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core; 2 | 3 | /** 4 | * Created by you on 2018-03-12. 5 | * 图像数据nv21,i420, nv12, yv12的一些转换裁剪旋转的相关操作 6 | */ 7 | public final class YuvUtils { 8 | 9 | private YuvUtils() {} 10 | 11 | static { 12 | System.loadLibrary("yuv-utils"); 13 | } 14 | 15 | /** 16 | * 同时将NV21数据转换成I420并旋转, 不剪切 17 | * @param nv21 camera datas 18 | * @param i420 out datas 19 | * @param w 20 | * @param h 21 | * @param orientation 22 | */ 23 | public static native void nv21ToI420Rotate(byte[] nv21, 24 | byte[] i420, 25 | int w, int h, 26 | @Orientation.OrientationMode int orientation); 27 | 28 | /** 29 | * 同时剪切NV21数据转换成I420并旋转 30 | * @param nv21 31 | * @param i420 32 | * @param w 相机原尺寸 33 | * @param h 34 | * @param cw 需要裁剪后的尺寸,必须都为偶数且 <= w 35 | * @param ch 同上 <= h 36 | * @param left 37 | * @param top 38 | * @param orientation 39 | */ 40 | public static native void clipNv21ToI420Rotate(byte[] nv21, 41 | byte[] i420, 42 | int w, int h, 43 | int cw, int ch, 44 | int left, int top, 45 | @Orientation.OrientationMode int orientation); 46 | 47 | /** 48 | * 同时将NV21数据转换成NV12并旋转, 不剪切 49 | * @param nv21 camera datas 50 | * @param nv12 out datas 51 | * @param w 52 | * @param h 53 | * @param orientation 54 | */ 55 | public static native void nv21ToNV12Rotate(byte[] nv21, 56 | byte[] nv12, 57 | int w, int h, 58 | @Orientation.OrientationMode int orientation); 59 | 60 | /** 61 | * 同时剪切NV21数据转换成NV12并旋转 62 | * @param nv21 63 | * @param nv12 64 | * @param w 相机原尺寸 65 | * @param h 66 | * @param cw 需要裁剪后的尺寸,必须都为偶数且 <= w, h 67 | * @param ch 同上 68 | * @param left 69 | * @param top 70 | * @param orientation 71 | */ 72 | public static native void clipNv21ToNV12Rotate(byte[] nv21, 73 | byte[] nv12, 74 | int w, int h, 75 | int cw, int ch, 76 | int left, int top, 77 | @Orientation.OrientationMode int orientation); 78 | 79 | /** 80 | * 同时将NV21数据转换成YV12并旋转, 不剪切 81 | * @param nv21 camera datas 82 | * @param yv12 out datas 83 | * @param w 84 | * @param h 85 | * @param orientation 86 | */ 87 | public static native void nv21ToYV12Rotate(byte[] nv21, 88 | byte[] yv12, 89 | int w, int h, 90 | @Orientation.OrientationMode int orientation); 91 | 92 | /** 93 | * 同时剪切NV21数据转换成YV12并旋转 94 | * @param nv21 95 | * @param yv12 96 | * @param w 相机原尺寸 97 | * @param h 98 | * @param cw 需要裁剪后的尺寸,必须都为偶数且 <= w 99 | * @param ch 同上 <= h 100 | * @param left 101 | * @param top 102 | * @param orientation 103 | */ 104 | public static native void clipNv21ToYV12Rotate(byte[] nv21, 105 | byte[] yv12, 106 | int w, int h, 107 | int cw, int ch, 108 | int left, int top, 109 | @Orientation.OrientationMode int orientation); 110 | 111 | /** 112 | * 将NV21数据旋转, 不剪切 113 | * @param nv21 camera datas 114 | * @param outs out datas 115 | * @param w 116 | * @param h 117 | * @param orientation 118 | */ 119 | public static native void nv21Rotate(byte[] nv21, 120 | byte[] outs, 121 | int w, int h, 122 | @Orientation.OrientationMode int orientation); 123 | 124 | /** 125 | * 同时剪切NV21数据并旋转 126 | * @param nv21 127 | * @param outs 128 | * @param w 相机原尺寸 129 | * @param h 130 | * @param cw 需要裁剪后的尺寸,必须都为偶数且 <= w 131 | * @param ch 同上 <= h 132 | * @param left 133 | * @param top 134 | * @param orientation 135 | */ 136 | public static native void clipNv21Rotate(byte[] nv21, 137 | byte[] outs, 138 | int w, int h, 139 | int cw, int ch, 140 | int left, int top, 141 | @Orientation.OrientationMode int orientation); 142 | 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/audio/AudioCallback.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.audio; 2 | 3 | import android.media.MediaCodec; 4 | 5 | import java.io.BufferedOutputStream; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | 10 | import you.chen.media.core.MediaEncoder; 11 | import you.chen.media.utils.FileUtils; 12 | import you.chen.media.utils.LogUtils; 13 | 14 | /** 15 | * Created by you on 2018-05-19. 16 | * @deprecated use {@link AudioMuxerCallback} 17 | */ 18 | @Deprecated 19 | public class AudioCallback implements MediaEncoder.Callback { 20 | 21 | //BufferInfo中的大小不固定,可以用大小固定的缓冲数组写出 22 | public static final int WRITE_BUFFER_SIZE = 1024; 23 | 24 | private final String path; 25 | //音频时间计算器 26 | private final AudioPresentationTime presentationTime; 27 | 28 | //储存数据 29 | private FileOutputStream fos; 30 | private BufferedOutputStream bos; 31 | //写入数据缓冲 32 | private byte[] writeBuffer; 33 | //aac header 34 | private byte[] adtsHeader = new byte[7]; 35 | 36 | //aac header 37 | private static final int profile = 2; 38 | private static final int freqIdx = 4;//对应的44100 H 39 | private static final int chanCfg = 2; 40 | 41 | public AudioCallback(String path, AudioPresentationTime presentationTime) { 42 | this.path = path; 43 | this.presentationTime = presentationTime; 44 | } 45 | 46 | @Override 47 | public void onInitStart() { 48 | writeBuffer = new byte[WRITE_BUFFER_SIZE]; 49 | //header前三位是固定 50 | adtsHeader[0] = (byte) 0xFF; 51 | adtsHeader[1] = (byte) 0xF9; 52 | adtsHeader[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2)); 53 | 54 | presentationTime.start(); 55 | } 56 | 57 | @Override 58 | public void onFormatChanged(MediaCodec mediaCodec) { 59 | try { 60 | fos = new FileOutputStream(path); 61 | bos = new BufferedOutputStream(fos); 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | 67 | @Override 68 | public void onEncodeInputBuffer(MediaCodec mediaCodec, byte[] buffer, int inputBufferIndex) { 69 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, buffer.length, presentationTime.getPresentationTimeUs(), 0); 70 | } 71 | 72 | @Override 73 | public void onWriteData(MediaCodec.BufferInfo bufferInfo, ByteBuffer encodeData) { 74 | if (bufferInfo.size != 0) { 75 | encodeData.position(bufferInfo.offset); 76 | encodeData.limit(bufferInfo.offset + bufferInfo.size); 77 | 78 | addADTStoPacket(bufferInfo.size + 7); 79 | try { 80 | bos.write(adtsHeader, 0, 7); 81 | } catch (IOException e) { 82 | e.printStackTrace(); 83 | } 84 | 85 | //将ByteBuffer中的数据写到文件中 86 | LogUtils.i("write buffinfosize %d", bufferInfo.size); 87 | int offset = bufferInfo.offset; 88 | int bufferSize = bufferInfo.size; 89 | while (bufferSize > writeBuffer.length) { 90 | writeByteBuffer(encodeData, offset, writeBuffer.length); 91 | bufferSize -= writeBuffer.length; 92 | offset += writeBuffer.length; 93 | } 94 | if (bufferSize > 0) { 95 | writeByteBuffer(encodeData, offset, bufferSize); 96 | } 97 | //byte[] buf = new byte[bufferInfo.size]; 98 | //encodeData.get(buf); 不能用此种方式写入,内存抖动极大 99 | } 100 | } 101 | 102 | @Override 103 | public void onRelease() { 104 | FileUtils.closeCloseable(bos, fos); 105 | } 106 | 107 | private void addADTStoPacket(int packetLen) { 108 | adtsHeader[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11)); 109 | adtsHeader[4] = (byte) ((packetLen & 0x7FF) >> 3); 110 | adtsHeader[5] = (byte) (((packetLen & 7) << 5) + 0x1F); 111 | adtsHeader[6] = (byte) 0xFC; 112 | } 113 | 114 | /** 115 | * 将ByteBuffer通过byte[]写入到文件 116 | * @param encodeData 117 | * @param offset 118 | * @param length 119 | */ 120 | private void writeByteBuffer(ByteBuffer encodeData, int offset, int length) { 121 | encodeData.position(offset); 122 | encodeData.limit(offset + length); 123 | 124 | encodeData.get(writeBuffer, 0, length); 125 | try { 126 | bos.write(writeBuffer, 0, length); 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/audio/AudioMuxerCallback.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.audio; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaMuxer; 5 | 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | 9 | import you.chen.media.core.MediaEncoder; 10 | 11 | /** 12 | * Created by you on 2018-05-19. 13 | * 14 | */ 15 | public class AudioMuxerCallback implements MediaEncoder.Callback { 16 | 17 | //混合器 18 | private final MediaMuxer mediaMuxer; 19 | 20 | private final AudioPresentationTime presentationTime; 21 | 22 | private int audioTrack; 23 | 24 | private boolean isMuxerStarted; 25 | 26 | public AudioMuxerCallback(String path, AudioPresentationTime presentationTime) throws IOException { 27 | mediaMuxer = new MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 28 | this.presentationTime = presentationTime; 29 | } 30 | 31 | @Override 32 | public void onInitStart() { 33 | audioTrack = -1; 34 | isMuxerStarted = false; 35 | 36 | presentationTime.start(); 37 | } 38 | 39 | @Override 40 | public void onFormatChanged(MediaCodec mediaCodec) { 41 | audioTrack = mediaMuxer.addTrack(mediaCodec.getOutputFormat()); 42 | mediaMuxer.start(); 43 | isMuxerStarted = true; 44 | } 45 | 46 | @Override 47 | public void onEncodeInputBuffer(MediaCodec mediaCodec, byte[] buffer, int inputBufferIndex) { 48 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, buffer.length, presentationTime.getPresentationTimeUs(), 0); 49 | } 50 | 51 | @Override 52 | public void onWriteData(MediaCodec.BufferInfo bufferInfo, ByteBuffer encodeData) { 53 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 54 | bufferInfo.size = 0; 55 | } 56 | if (bufferInfo.size != 0) { 57 | encodeData.position(bufferInfo.offset); 58 | encodeData.limit(bufferInfo.offset + bufferInfo.size); 59 | mediaMuxer.writeSampleData(audioTrack, encodeData, bufferInfo); 60 | } 61 | } 62 | 63 | @Override 64 | public void onRelease() { 65 | if (isMuxerStarted) { 66 | mediaMuxer.stop(); 67 | mediaMuxer.release(); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/audio/AudioPresentationTime.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.audio; 2 | 3 | import android.media.AudioFormat; 4 | 5 | /** 6 | * Created by you on 2018-05-16. 7 | * 音频时间采样计算 8 | * E/MPEG4Writer: timestampUs 6220411 < lastTimestampUs 6220442 for Audio track 9 | * 10 | */ 11 | public final class AudioPresentationTime { 12 | 13 | private long startTime; 14 | 15 | private final long bufferDurationUs; 16 | 17 | private long currentCount; 18 | 19 | /** 20 | * 21 | * @param bufferSize 22 | * @param sampleRate 23 | * @param channelCount 24 | * @param audioFormat 25 | */ 26 | public AudioPresentationTime(int bufferSize, int sampleRate, int channelCount, int audioFormat) { 27 | int bitByteSize = audioFormat == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1; //16bit = 2 byte 28 | bufferDurationUs = 1_000_000L * (bufferSize / (channelCount * bitByteSize)) / sampleRate; 29 | } 30 | 31 | public void start() { 32 | startTime = System.nanoTime() / 1000L; 33 | currentCount = 0; 34 | } 35 | 36 | public long getPresentationTimeUs() { 37 | return currentCount++ * bufferDurationUs + startTime; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/audio/AudioRecorder.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.audio; 2 | 3 | import android.media.AudioRecord; 4 | import android.media.MediaCodec; 5 | import android.media.MediaRecorder; 6 | 7 | import java.io.IOException; 8 | import java.util.concurrent.ExecutorService; 9 | 10 | import you.chen.media.core.BytePool; 11 | import you.chen.media.core.Constant; 12 | import you.chen.media.core.MediaEncoder; 13 | import you.chen.media.core.MediaUtils; 14 | 15 | /** 16 | * Created by you on 2018-05-13. 17 | */ 18 | public class AudioRecorder { 19 | 20 | public static AudioRecorder createMuxerAudioRecorder(String path) throws IOException { 21 | return createMuxerAudioRecorder(path, Constant.SAMPLE_RATE, Constant.CHANNEL_COUNT, 22 | Constant.AUDIO_RATE, Constant.AAC_PROFILE, Constant.CHANNEL_CONFIG, Constant.AUDIO_FORMAT); 23 | } 24 | 25 | public static AudioRecorder createMuxerAudioRecorder(String path, int sampleRate, int channelCount, 26 | int audioBitRate, int aacProfile, 27 | int channelConfig, int audioFormat) throws IOException { 28 | int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); 29 | MediaCodec mediaCodec = MediaUtils.createAacMediaCodec(bufferSize, 30 | sampleRate, channelCount, audioBitRate, aacProfile); 31 | AudioPresentationTime presentationTime = new AudioPresentationTime(bufferSize, sampleRate, channelCount, audioFormat); 32 | AudioMuxerCallback callback = new AudioMuxerCallback(path, presentationTime); 33 | 34 | MediaEncoder encoder = new MediaEncoder(mediaCodec, new BytePool(bufferSize), new AudioTransform(), callback); 35 | AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 36 | sampleRate, channelConfig, audioFormat, bufferSize); 37 | AudioTaker audioTaker = new AudioTaker(audioRecord, bufferSize, encoder); 38 | return new AudioRecorder(encoder, audioTaker); 39 | } 40 | 41 | @Deprecated 42 | public static AudioRecorder createAudioRecorder(String path) { 43 | return createAudioRecorder(path, Constant.SAMPLE_RATE, Constant.CHANNEL_COUNT, 44 | Constant.AUDIO_RATE, Constant.AAC_PROFILE, Constant.CHANNEL_CONFIG, Constant.AUDIO_FORMAT); 45 | } 46 | 47 | @Deprecated 48 | public static AudioRecorder createAudioRecorder(String path, int sampleRate, int channelCount, 49 | int audioBitRate, int aacProfile, 50 | int channelConfig, int audioFormat) { 51 | int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); 52 | MediaCodec mediaCodec = MediaUtils.createAacMediaCodec(bufferSize, 53 | sampleRate, channelCount, audioBitRate, aacProfile); 54 | AudioPresentationTime presentationTime = new AudioPresentationTime(bufferSize, sampleRate, channelCount, audioFormat); 55 | AudioCallback callback = new AudioCallback(path, presentationTime); 56 | MediaEncoder encoder = new MediaEncoder(mediaCodec, new BytePool(bufferSize), new AudioTransform(), callback); 57 | 58 | AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 59 | sampleRate, channelConfig, audioFormat, bufferSize); 60 | AudioTaker audioTaker = new AudioTaker(audioRecord, bufferSize, encoder); 61 | return new AudioRecorder(encoder, audioTaker); 62 | } 63 | 64 | private final MediaEncoder encoder; 65 | 66 | private final AudioTaker audioTaker; 67 | 68 | public AudioRecorder(MediaEncoder encoder, AudioTaker audioTaker) { 69 | this.encoder = encoder; 70 | this.audioTaker = audioTaker; 71 | } 72 | 73 | /** 74 | * 启动 75 | * @param service 需要最少2个线程 76 | */ 77 | public void start(ExecutorService service) { 78 | encoder.start(service); 79 | audioTaker.start(service); 80 | } 81 | 82 | public void stop() { 83 | audioTaker.stop(); 84 | encoder.stop(); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/audio/AudioTaker.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.audio; 2 | 3 | import android.media.AudioRecord; 4 | 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | 8 | import you.chen.media.core.MediaEncoder; 9 | import you.chen.media.utils.LogUtils; 10 | 11 | /** 12 | * Created by you on 2018-05-10. 13 | * AudioRecord 取PCM数据 14 | */ 15 | public class AudioTaker implements Runnable { 16 | 17 | //语音录制 18 | private final AudioRecord audioRecord; 19 | //编码器 20 | private final MediaEncoder encoder; 21 | //缓冲数组 22 | private final byte[] buffer; 23 | //是否正在录制 24 | private final AtomicBoolean isRecording = new AtomicBoolean(false); 25 | 26 | public AudioTaker(AudioRecord audioRecord, int bufferSize, MediaEncoder encoder) { 27 | this.audioRecord = audioRecord; 28 | this.buffer = new byte[bufferSize]; 29 | this.encoder = encoder; 30 | } 31 | 32 | /** 33 | * 在线程执行runnable前调用start 34 | */ 35 | public final void start(ExecutorService service) { 36 | audioRecord.startRecording(); 37 | isRecording.set(true); 38 | service.execute(this); 39 | } 40 | 41 | public final void stop() { 42 | isRecording.set(false); 43 | } 44 | 45 | @Override 46 | public void run() { 47 | while (isRecording.get()) { 48 | int len = audioRecord.read(buffer, 0, buffer.length); 49 | if (len > 0) { 50 | encoder.push(buffer, len); 51 | } 52 | } 53 | release(); 54 | } 55 | 56 | /** 57 | * 释放资源 58 | */ 59 | private void release() { 60 | LogUtils.i("audio release..."); 61 | if (audioRecord != null) { 62 | audioRecord.stop(); 63 | audioRecord.release(); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/audio/AudioTransform.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.audio; 2 | 3 | import you.chen.media.core.Transform; 4 | 5 | /** 6 | * Created by you on 2018-03-20. 7 | * 暂不做处理 8 | */ 9 | public class AudioTransform implements Transform { 10 | 11 | @Override 12 | public void transform(byte[] src, byte[] outs, int len) { 13 | System.arraycopy(src, 0, outs, 0, len); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/h264/AvcTransform.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.h264; 2 | 3 | import android.media.MediaCodecInfo; 4 | 5 | import you.chen.media.core.Orientation; 6 | import you.chen.media.core.Transform; 7 | import you.chen.media.core.YuvUtils; 8 | 9 | /** 10 | * 相机NV21数据转换操作 11 | */ 12 | public class AvcTransform implements Transform { 13 | 14 | /** 15 | * 转换的宽与高 16 | */ 17 | private final int w, h; 18 | //MediaCodec colorFormat 19 | private final int colorFormat; 20 | 21 | @Orientation.OrientationMode 22 | private final int orientation; 23 | 24 | public AvcTransform(int w, int h, int colorFormat, @Orientation.OrientationMode int orientation) { 25 | this.w = w; 26 | this.h = h; 27 | this.colorFormat = colorFormat; 28 | this.orientation = orientation; 29 | } 30 | 31 | /** 32 | * 33 | * @param nv21 相机出来的数据 34 | * @param outs 转变后的数据储存数组 35 | */ 36 | @Override 37 | public void transform(byte[] nv21, byte[] outs, int len) { 38 | switch (colorFormat) { 39 | case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: 40 | YuvUtils.nv21ToI420Rotate(nv21, outs, w, h, orientation); 41 | break; 42 | case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: 43 | YuvUtils.nv21ToNV12Rotate(nv21, outs, w, h, orientation); 44 | break; 45 | case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: 46 | YuvUtils.nv21ToYV12Rotate(nv21, outs, w, h, orientation); 47 | break; 48 | default: 49 | YuvUtils.nv21Rotate(nv21, outs, w, h, orientation); 50 | break; 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/h264/ClipAvcTransform.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.h264; 2 | 3 | import android.media.MediaCodecInfo; 4 | 5 | import you.chen.media.core.Orientation; 6 | import you.chen.media.core.Transform; 7 | import you.chen.media.core.YuvUtils; 8 | import you.chen.media.utils.LogUtils; 9 | 10 | /** 11 | * 相机NV21数据剪切转换操作 12 | */ 13 | public class ClipAvcTransform implements Transform { 14 | /** 15 | * 转换后的宽与高 16 | */ 17 | private final int w, h; 18 | //MediaCodec colorFormat 19 | private final int colorFormat; 20 | 21 | @Orientation.OrientationMode 22 | private final int orientation; 23 | 24 | /** 25 | * 原始预览的图片宽与高 26 | */ 27 | private final int width, height; 28 | /** 29 | * 裁剪的点坐标 30 | */ 31 | private final int left, top; 32 | 33 | public ClipAvcTransform(int w, int h, int width, int height, int colorFormat, 34 | @Orientation.OrientationMode int orientation) { 35 | this.w = w; 36 | this.h = h; 37 | this.colorFormat = colorFormat; 38 | this.orientation = orientation; 39 | this.width = width; 40 | this.height = height; 41 | 42 | this.left = w < width ? (((width - w) / 2 + 1) & ~1) : 0;//偏移也必须为偶数 43 | this.top = h < height ? (((height - h) / 2 + 1) & ~1) : 0; 44 | LogUtils.i("%d - %d, %d - %d, %d - %d, %d", width, height, w, h, left, top, colorFormat); 45 | } 46 | 47 | @Override 48 | public void transform(byte[] nv21, byte[] outs, int len) { 49 | switch (colorFormat) { 50 | case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: 51 | YuvUtils.clipNv21ToI420Rotate(nv21, outs, width, height, w, h, left, top, orientation); 52 | break; 53 | case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: 54 | YuvUtils.clipNv21ToNV12Rotate(nv21, outs, width, height, w, h, left, top, orientation); 55 | break; 56 | case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: 57 | YuvUtils.clipNv21ToYV12Rotate(nv21, outs, width, height, w, h, left, top, orientation); 58 | break; 59 | default: 60 | YuvUtils.clipNv21Rotate(nv21, outs, width, height, w, h, left, top, orientation); 61 | break; 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/h264/H264Callback.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.h264; 2 | 3 | import android.media.MediaCodec; 4 | 5 | import java.io.BufferedOutputStream; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | 10 | import you.chen.media.core.MediaEncoder; 11 | import you.chen.media.utils.FileUtils; 12 | 13 | /** 14 | * Created by you on 2018-05-10. 15 | * h264是没有时间戳概念的,就是一堆流文件,需要播放速度统一可以使用MediaMuxer混合器进行时间戳对齐 16 | * @deprecated To use {@link H264MuxerCallback} 17 | */ 18 | @Deprecated 19 | public final class H264Callback implements MediaEncoder.Callback { 20 | 21 | //BufferInfo中的大小不固定,可以用大小固定的缓冲数组写出 22 | public static final int WRITE_BUFFER_SIZE = 1024 << 4; 23 | //储存数据 24 | private final String path; 25 | private FileOutputStream fos; 26 | private BufferedOutputStream bos; 27 | //写入数据缓冲 28 | private byte[] writeBuffer; 29 | 30 | public H264Callback(String path) { 31 | this.path = path; 32 | } 33 | 34 | @Override 35 | public void onInitStart() { 36 | writeBuffer = new byte[WRITE_BUFFER_SIZE]; 37 | } 38 | 39 | @Override 40 | public void onFormatChanged(MediaCodec mediaCodec) { 41 | try { 42 | fos = new FileOutputStream(path); 43 | bos = new BufferedOutputStream(fos); 44 | } catch (IOException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | @Override 50 | public void onEncodeInputBuffer(MediaCodec mediaCodec, byte[] buffer, int inputBufferIndex) { 51 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, buffer.length, 52 | System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME); 53 | } 54 | 55 | @Override 56 | public void onWriteData(MediaCodec.BufferInfo bufferInfo, ByteBuffer encodeData) { 57 | if (bufferInfo.size != 0) { 58 | //将ByteBuffer中的数据写到文件中 59 | // LogUtils.i("write buffinfosize %d", bufferInfo.size); 60 | int offset = bufferInfo.offset; 61 | int bufferSize = bufferInfo.size; 62 | while (bufferSize > writeBuffer.length) { 63 | writeByteBuffer(encodeData, offset, writeBuffer.length); 64 | bufferSize -= writeBuffer.length; 65 | offset += writeBuffer.length; 66 | } 67 | if (bufferSize > 0) { 68 | writeByteBuffer(encodeData, offset, bufferSize); 69 | } 70 | //byte[] buf = new byte[bufferInfo.size]; 71 | //encodeData.get(buf); 不能用此种方式写入,内存抖动极大 72 | } 73 | } 74 | 75 | @Override 76 | public void onRelease() { 77 | FileUtils.closeCloseable(bos, fos); 78 | } 79 | 80 | /** 81 | * 将ByteBuffer通过byte[]写入到文件 82 | * @param encodeData 83 | * @param offset 84 | * @param length 85 | */ 86 | private void writeByteBuffer(ByteBuffer encodeData, int offset, int length) { 87 | encodeData.position(offset); 88 | encodeData.limit(offset + length); 89 | 90 | encodeData.get(writeBuffer, 0, length); 91 | try { 92 | bos.write(writeBuffer, 0, length); 93 | } catch (IOException e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/h264/H264MuxerCallback.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.h264; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaMuxer; 5 | 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | 9 | import you.chen.media.core.MediaEncoder; 10 | 11 | /** 12 | * Created by you on 2018-05-10. 13 | * Muxer时间对齐的H264 14 | */ 15 | public class H264MuxerCallback implements MediaEncoder.Callback { 16 | 17 | //混合器 18 | private final MediaMuxer mediaMuxer; 19 | 20 | private int h264TrackIndex = -1; 21 | 22 | private boolean isMuxerStarted = false; 23 | 24 | public H264MuxerCallback(String path) throws IOException { 25 | mediaMuxer = new MediaMuxer(path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 26 | } 27 | 28 | @Override 29 | public void onInitStart() { 30 | h264TrackIndex = -1; 31 | isMuxerStarted = false; 32 | } 33 | 34 | @Override 35 | public void onEncodeInputBuffer(MediaCodec mediaCodec, byte[] buffer, int inputBufferIndex) { 36 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, buffer.length, 37 | System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME); 38 | } 39 | 40 | @Override 41 | public void onFormatChanged(MediaCodec mediaCodec) { 42 | h264TrackIndex = mediaMuxer.addTrack(mediaCodec.getOutputFormat()); 43 | mediaMuxer.start(); 44 | isMuxerStarted = true; 45 | } 46 | 47 | @Override 48 | public void onWriteData(MediaCodec.BufferInfo bufferInfo, ByteBuffer encodeData) { 49 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 50 | bufferInfo.size = 0; 51 | } 52 | if (bufferInfo.size != 0) { 53 | encodeData.position(bufferInfo.offset); 54 | encodeData.limit(bufferInfo.offset + bufferInfo.size); 55 | mediaMuxer.writeSampleData(h264TrackIndex, encodeData, bufferInfo); 56 | } 57 | } 58 | 59 | @Override 60 | public void onRelease() { 61 | if (isMuxerStarted) { 62 | mediaMuxer.stop(); 63 | mediaMuxer.release(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/h264/H264Utils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.h264; 2 | 3 | import android.graphics.Matrix; 4 | import android.graphics.Point; 5 | import android.media.MediaCodec; 6 | 7 | import you.chen.media.camera.CameraUtils; 8 | import you.chen.media.core.BytePool; 9 | import you.chen.media.core.Constant; 10 | import you.chen.media.core.MediaEncoder; 11 | import you.chen.media.core.MediaUtils; 12 | import you.chen.media.core.Orientation; 13 | import you.chen.media.core.Transform; 14 | 15 | /** 16 | * Created by you on 2018-05-19. 17 | */ 18 | public final class H264Utils { 19 | 20 | private H264Utils() {} 21 | 22 | public static MediaEncoder createH264MediaEncoder(int width, int height, Matrix matrix, 23 | @Orientation.OrientationMode int orientation, 24 | MediaEncoder.Callback callback) { 25 | int h264BitRate = width * height * Constant.VIDEO_BITRATE_COEFFICIENT; 26 | return createH264MediaEncoder(MediaUtils.selectColorFormat(), width, height, matrix, 27 | orientation, h264BitRate, Constant.FRAME_RATE, Constant.IFRAME_INTERVAL, callback); 28 | } 29 | 30 | public static MediaEncoder createH264MediaEncoder(int colorFormat, int width, int height, Matrix matrix, 31 | @Orientation.OrientationMode int orientation, 32 | int h264BitRate, int frameRate, int frameInterval, 33 | MediaEncoder.Callback callback) { 34 | Point matrixSize = CameraUtils.matrixSize(width, height, matrix); 35 | BytePool bytePool = new BytePool(matrixSize.x * matrixSize.y * 3 / 2); 36 | MediaCodec h264Codec = MediaUtils.createAvcMediaCodec(matrixSize.x, matrixSize.y, 37 | colorFormat, orientation, h264BitRate, frameRate, frameInterval); 38 | Transform transform = matrixSize.equals(width, height) ? 39 | new AvcTransform(width, height, colorFormat, orientation) 40 | : new ClipAvcTransform(matrixSize.x, matrixSize.y, width, height, colorFormat, orientation); 41 | return new MediaEncoder(h264Codec, bytePool, transform, callback); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/scan/DecodeThread.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.scan; 2 | 3 | import android.os.Handler; 4 | 5 | import com.google.zxing.Result; 6 | 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | import you.chen.media.core.Transform; 10 | import you.chen.media.utils.LogUtils; 11 | 12 | /** 13 | * Created by you on 2018-04-26. 14 | * 解析线程, 不断的{@link DecodeThread#push(byte[])} 并解析 15 | */ 16 | public class DecodeThread extends Thread { 17 | 18 | public static final int HANDLE_SUCCESS = 1; 19 | 20 | private final int w, h; 21 | //扫描解析器 22 | private final FormatDecoder decoder; 23 | //要解码的转换处理后的数据 24 | private final byte[] buffer; 25 | 26 | private Handler handler; 27 | 28 | private final Transform transform; 29 | 30 | private boolean isRunning; 31 | 32 | //是否正在解码 33 | private final AtomicBoolean isCoding = new AtomicBoolean(false); 34 | //扫描成功后退出 35 | private final boolean successQuit; 36 | 37 | /** 38 | * Camera.setDisplayOrientation(90), 270; 时 w, h顺序调换 39 | * @param w 40 | * @param h 41 | * @param decoder 42 | * @param handler 43 | * @param transform 44 | * @param successQuit 需要持续性的扫描时可以设置false 45 | */ 46 | public DecodeThread(int w, int h, FormatDecoder decoder, Handler handler, Transform transform, boolean successQuit) { 47 | this.w = w; 48 | this.h = h; 49 | this.decoder = decoder; 50 | this.buffer = new byte[w * h * 3 / 2]; 51 | this.handler = handler; 52 | this.transform = transform; 53 | this.successQuit = successQuit; 54 | } 55 | 56 | public synchronized final void push(byte[] data) { 57 | if (isCoding.get() || !isRunning) { 58 | return; 59 | } 60 | transform.transform(data, buffer, data.length); 61 | isCoding.set(true); 62 | notify(); 63 | } 64 | 65 | private synchronized void decode() { 66 | while (!isCoding.get() && isRunning) { 67 | try { 68 | wait(); 69 | } catch (InterruptedException e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | } 74 | 75 | @Override 76 | public synchronized void start() { 77 | isRunning = true; 78 | super.start(); 79 | } 80 | 81 | public synchronized void quit() { 82 | isRunning = false; 83 | notify(); 84 | } 85 | 86 | @Override 87 | public void run() { 88 | LogUtils.i("ScanDecoder thread start..."); 89 | while (isRunning) { 90 | decode(); 91 | if (!isRunning) { 92 | break; 93 | } 94 | Result result = decoder.decode(buffer, w, h); 95 | if (result != null) { 96 | handler.sendMessage(handler.obtainMessage(HANDLE_SUCCESS, result)); 97 | if (successQuit) { 98 | isRunning = false; 99 | break; 100 | } 101 | try { 102 | sleep(2000);//需要持续性的扫描功能时,可以在扫描成功时短暂睡眠 103 | } catch (InterruptedException e) { 104 | e.printStackTrace(); 105 | } 106 | } 107 | isCoding.set(false); 108 | } 109 | LogUtils.i("ScanDecoder quit..."); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/scan/DecoderHandler.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.scan; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | 6 | import com.google.zxing.Result; 7 | 8 | import java.lang.ref.WeakReference; 9 | 10 | import androidx.annotation.NonNull; 11 | import you.chen.media.core.Transform; 12 | import you.chen.media.utils.Utils; 13 | 14 | /** 15 | * Created by you on 2018-04-26. 16 | * {@link DecodeThread}与主线程 交互 17 | */ 18 | public final class DecoderHandler extends Handler { 19 | 20 | private final DecodeThread decodeThread; 21 | 22 | private SoundVibratorHelper soundVibratorHelper; 23 | 24 | private final WeakReference callbackWeakReference; 25 | 26 | public DecoderHandler(int w, int h, Transform transform, DecoderCallback callback) { 27 | this(w, h, new FormatDecoder(), transform, true, callback); 28 | } 29 | 30 | public DecoderHandler(int w, int h, Transform transform, boolean successQuit, DecoderCallback callback) { 31 | this(w, h, new FormatDecoder(), transform, successQuit, callback); 32 | } 33 | 34 | public DecoderHandler(int w, int h, FormatDecoder decoder, 35 | Transform transform, boolean successQuit, DecoderCallback callback) { 36 | callbackWeakReference = new WeakReference<>(callback); 37 | decodeThread = new DecodeThread(w, h, decoder, this, transform, successQuit); 38 | soundVibratorHelper = new SoundVibratorHelper(Utils.context()); 39 | decodeThread.start(); 40 | } 41 | 42 | public void push(byte[] data) { 43 | decodeThread.push(data); 44 | } 45 | 46 | public void stop() { 47 | soundVibratorHelper.stop(); 48 | decodeThread.quit(); 49 | } 50 | 51 | @Override 52 | public void handleMessage(@NonNull Message msg) { 53 | if (msg.what == DecodeThread.HANDLE_SUCCESS && msg.obj != null) { 54 | Result result = (Result) msg.obj; 55 | DecoderCallback callback = callbackWeakReference.get(); 56 | if (callback != null) { 57 | soundVibratorHelper.play(); 58 | callback.handleResult(result); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * 回调处理 65 | */ 66 | public interface DecoderCallback { 67 | 68 | void handleResult(Result result); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/scan/FormatDecoder.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.scan; 2 | 3 | import com.google.zxing.BarcodeFormat; 4 | import com.google.zxing.BinaryBitmap; 5 | import com.google.zxing.DecodeHintType; 6 | import com.google.zxing.MultiFormatReader; 7 | import com.google.zxing.PlanarYUVLuminanceSource; 8 | import com.google.zxing.Result; 9 | import com.google.zxing.ResultPointCallback; 10 | import com.google.zxing.common.HybridBinarizer; 11 | 12 | import java.util.EnumMap; 13 | import java.util.EnumSet; 14 | import java.util.Set; 15 | 16 | /** 17 | * Created by you on 2018-04-26. 18 | * 扫描解析器 19 | */ 20 | public class FormatDecoder { 21 | 22 | private MultiFormatReader formatReader; 23 | 24 | /** 25 | * 26 | * //根据实际需求添加BarcodeFormat, 此构造只支持二维码与各种条形码 27 | * BarcodeFormat.DATA_MATRIX, BarcodeFormat.AZTEC, BarcodeFormat.PDF_417 28 | * 29 | */ 30 | public FormatDecoder() { 31 | this(EnumSet.of(BarcodeFormat.QR_CODE, 32 | BarcodeFormat.UPC_A, 33 | BarcodeFormat.UPC_E, 34 | BarcodeFormat.EAN_13, 35 | BarcodeFormat.EAN_8, 36 | BarcodeFormat.RSS_14, 37 | BarcodeFormat.RSS_EXPANDED, 38 | BarcodeFormat.CODE_39, 39 | BarcodeFormat.CODE_93, 40 | BarcodeFormat.CODE_128, 41 | BarcodeFormat.ITF, 42 | BarcodeFormat.CODABAR), null, null); 43 | } 44 | 45 | public FormatDecoder(Set formatSet, String characterSet, ResultPointCallback callback) { 46 | formatReader = new MultiFormatReader(); 47 | EnumMap hints = new EnumMap<>(DecodeHintType.class); 48 | hints.put(DecodeHintType.POSSIBLE_FORMATS, formatSet); 49 | if (characterSet != null) { 50 | hints.put(DecodeHintType.CHARACTER_SET, characterSet); 51 | } 52 | if (callback != null) { 53 | hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, callback); 54 | } 55 | formatReader.setHints(hints); 56 | } 57 | 58 | /** 59 | * 解码camera datas 60 | * @param datas 61 | * @param w 62 | * @param h 63 | * @return 64 | */ 65 | public Result decode(byte[] datas, int w, int h) { 66 | PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(datas, w, h, 0, 0, w, h, false); 67 | BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); 68 | try { 69 | return formatReader.decodeWithState(bitmap); 70 | } catch (Exception e) { 71 | // LogUtils.e(e); 72 | } finally { 73 | formatReader.reset(); 74 | } 75 | return null; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/core/scan/SoundVibratorHelper.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.core.scan; 2 | 3 | import android.content.Context; 4 | import android.media.AudioAttributes; 5 | import android.media.AudioManager; 6 | import android.media.SoundPool; 7 | import android.os.Vibrator; 8 | 9 | import you.chen.media.R; 10 | 11 | /** 12 | * Created by you on 2018-04-26. 13 | * 扫描成功时的声音震动操作 14 | */ 15 | public final class SoundVibratorHelper { 16 | /** 17 | * 振动时间 18 | */ 19 | private static final long DEF_VIBRATE_DURATION = 200L; 20 | 21 | private SoundPool soundPool; 22 | 23 | private int soundId; 24 | /** 25 | * 振动 26 | */ 27 | private Vibrator vibrator; 28 | 29 | public SoundVibratorHelper(Context context) { 30 | context = context.getApplicationContext(); 31 | vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 32 | 33 | SoundPool.Builder builder = new SoundPool.Builder(); 34 | builder.setMaxStreams(2); 35 | 36 | AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder(); 37 | attrBuilder.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION); 38 | builder.setAudioAttributes(attrBuilder.build()); 39 | soundPool = builder.build(); 40 | 41 | soundId = soundPool.load(context, R.raw.beep, 1); 42 | } 43 | 44 | public void play() { 45 | vibrator.cancel(); 46 | vibrator.vibrate(DEF_VIBRATE_DURATION); 47 | soundPool.play(soundId, 1, 1, 0, 0, 1); 48 | } 49 | 50 | public void stop() { 51 | soundPool.release(); 52 | vibrator.cancel(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/NothingSubscribe.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx; 2 | 3 | import io.reactivex.Observer; 4 | import io.reactivex.disposables.Disposable; 5 | 6 | /** 7 | * Created by you on 2018/7/30. 8 | */ 9 | 10 | public abstract class NothingSubscribe implements Observer { 11 | 12 | private Disposable disposable; 13 | 14 | @Override 15 | public void onSubscribe(Disposable disposable) { 16 | this.disposable = disposable; 17 | } 18 | 19 | @Override 20 | public void onError(Throwable e) { 21 | RxUtils.dispose(disposable); 22 | } 23 | 24 | @Override 25 | public void onComplete() { 26 | RxUtils.dispose(disposable); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/RequestRetry.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import io.reactivex.Observable; 6 | import io.reactivex.ObservableSource; 7 | import io.reactivex.functions.Function; 8 | 9 | /** 10 | * Created by you on 2017/7/5. 11 | * 请求重试类 12 | */ 13 | 14 | public class RequestRetry implements Function, ObservableSource> { 15 | /** 16 | * 默认重试次数 17 | */ 18 | private static final int DEF_MAXRETRIES = 1; 19 | /** 20 | * 默认重试时间间隔 21 | */ 22 | private static final int DEF_DELAYMILLIS = 2000; 23 | /** 24 | * 最大重试次数 25 | */ 26 | private final int maxRetries; 27 | /** 28 | * 重试间隔 29 | */ 30 | private final int retryDelayMillis; 31 | 32 | private int retryCount; 33 | 34 | public static RequestRetry def() { 35 | return new RequestRetry(DEF_MAXRETRIES, DEF_DELAYMILLIS); 36 | } 37 | 38 | public RequestRetry(int maxRetries, int retryDelayMillis) { 39 | this.maxRetries = maxRetries; 40 | this.retryDelayMillis = retryDelayMillis; 41 | } 42 | 43 | @Override 44 | public ObservableSource apply(Observable observable) throws Exception { 45 | return observable.flatMap(new Function>() { 46 | @Override 47 | public ObservableSource apply(Throwable throwable) throws Exception { 48 | if (++retryCount <= maxRetries) { 49 | return Observable.timer(retryDelayMillis, TimeUnit.MILLISECONDS); 50 | } 51 | return Observable.error(throwable); 52 | } 53 | }); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/RxUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import io.reactivex.Observable; 9 | import io.reactivex.android.schedulers.AndroidSchedulers; 10 | import io.reactivex.disposables.Disposable; 11 | import you.chen.media.rx.viewbind.TextViewChangedOnSubscribe; 12 | import you.chen.media.rx.viewbind.ViewClickOnSubscribe; 13 | 14 | /** 15 | * Created by you on 2018/1/11. 16 | */ 17 | 18 | public final class RxUtils { 19 | 20 | private RxUtils() {} 21 | 22 | public static void dispose(Disposable...disposables) { 23 | if (disposables != null && disposables.length > 0) { 24 | for (Disposable disposable : disposables) { 25 | if (disposable != null && !disposable.isDisposed()) { 26 | disposable.dispose(); 27 | } 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * 默认防抖一秒 34 | */ 35 | public static final int DEF_DURATION_CLICK = 1000; 36 | 37 | public static Observable click(View... views) { 38 | return click(DEF_DURATION_CLICK, views); 39 | } 40 | 41 | /** 42 | * 43 | * @param viewArray 方便BindViews时的参数传入 44 | * @param views 45 | * @return 46 | */ 47 | public static Observable click(View[] viewArray, View... views) { 48 | return click(DEF_DURATION_CLICK, viewArray, views); 49 | } 50 | 51 | /** 52 | * 生成防抖 53 | * @param clickDuration 54 | * @param views 55 | * @return 56 | */ 57 | public static Observable click(long clickDuration , View... views ) { 58 | ViewClickOnSubscribe clickOnSubscribe = new ViewClickOnSubscribe(); 59 | clickOnSubscribe.addOnClickListener(views); 60 | return Observable.create(clickOnSubscribe).throttleFirst(clickDuration , TimeUnit.MILLISECONDS); 61 | } 62 | 63 | /** 64 | * 65 | * @param clickDuration 66 | * @param viewArray 方便BindViews时的参数传入 67 | * @param views 68 | * @return 69 | */ 70 | public static Observable click(long clickDuration, View[] viewArray, View... views) { 71 | ViewClickOnSubscribe clickOnSubscribe = new ViewClickOnSubscribe(); 72 | clickOnSubscribe.addOnClickListener(viewArray); 73 | clickOnSubscribe.addOnClickListener(views); 74 | return Observable.create(clickOnSubscribe).throttleFirst(clickDuration , TimeUnit.MILLISECONDS); 75 | } 76 | 77 | public static final int DEF_TIMEOUT = 300; 78 | 79 | public static Observable textChanged(TextView tv) { 80 | return textChanged(DEF_TIMEOUT, tv); 81 | } 82 | 83 | public static Observable textChanged(long timeout, TextView tv) { 84 | TextViewChangedOnSubscribe subscribe = new TextViewChangedOnSubscribe(); 85 | subscribe.addTextViewWatcher(tv); 86 | return Observable.create(subscribe).debounce(timeout, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()); 87 | } 88 | 89 | /** 90 | * 秒倒计时 91 | * @param second 92 | * @return 93 | */ 94 | public static Observable stimer(long second) { 95 | return Observable.timer(second, TimeUnit.SECONDS, AndroidSchedulers.mainThread()); 96 | } 97 | 98 | /** 99 | * 定时发送, 主线程 100 | * @param second 秒 101 | * @return 102 | */ 103 | public static Observable sinterval(long second) { 104 | return Observable.interval(second, TimeUnit.SECONDS, AndroidSchedulers.mainThread()); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/ThrowableConsumer.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx; 2 | 3 | 4 | import io.reactivex.functions.Consumer; 5 | 6 | /** 7 | * Created by you on 2017-02-10. 8 | */ 9 | public class ThrowableConsumer implements Consumer { 10 | 11 | @Override 12 | public void accept(Throwable throwable) throws Exception { 13 | //nothing 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/perm/Permission.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx.perm; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by you on 2018/3/5. 7 | */ 8 | 9 | public class Permission { 10 | 11 | public final String name; 12 | /** 13 | * 是否放行 14 | */ 15 | public final boolean granted; 16 | 17 | public final boolean shouldShowRequestPermissionRationale; 18 | 19 | public Permission(String name, boolean granted) { 20 | this(name, granted, false); 21 | } 22 | 23 | public Permission(String name, boolean granted, boolean shouldShowRequestPermissionRationale) { 24 | this.name = name; 25 | this.granted = granted; 26 | this.shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale; 27 | } 28 | 29 | public Permission(List permissions) { 30 | name = combineName(permissions); 31 | granted = combineGranted(permissions); 32 | shouldShowRequestPermissionRationale = combineShouldShowRequestPermissionRationale(permissions); 33 | } 34 | 35 | @Override 36 | public boolean equals(final Object o) { 37 | if (this == o) return true; 38 | if (o == null || getClass() != o.getClass()) return false; 39 | final Permission that = (Permission) o; 40 | if (granted != that.granted) return false; 41 | if (shouldShowRequestPermissionRationale != that.shouldShowRequestPermissionRationale) 42 | return false; 43 | return name.equals(that.name); 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | int result = name.hashCode(); 49 | result = 31 * result + (granted ? 1 : 0); 50 | result = 31 * result + (shouldShowRequestPermissionRationale ? 1 : 0); 51 | return result; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "Permission{" + 57 | "name='" + name + '\'' + 58 | ", granted=" + granted + 59 | ", shouldShowRequestPermissionRationale=" + shouldShowRequestPermissionRationale + 60 | '}'; 61 | } 62 | 63 | private String combineName(List permissions) { 64 | if (permissions == null || permissions.isEmpty()) return ""; 65 | StringBuilder sb = new StringBuilder(); 66 | for (Permission permission : permissions) { 67 | if (sb.length() == 0) { 68 | sb.append(permission.name); 69 | } else { 70 | sb.append(", ").append(permission.name); 71 | } 72 | } 73 | return sb.toString(); 74 | } 75 | 76 | private Boolean combineGranted(List permissions) { 77 | if (permissions == null || permissions.isEmpty()) return false; 78 | for (Permission permission : permissions) { 79 | if (!permission.granted) { 80 | return false; 81 | } 82 | } 83 | return true; 84 | } 85 | 86 | private Boolean combineShouldShowRequestPermissionRationale(List permissions) { 87 | if (permissions == null || permissions.isEmpty()) return false; 88 | for (Permission permission : permissions) { 89 | if (permission.shouldShowRequestPermissionRationale) { 90 | return true; 91 | } 92 | } 93 | return false; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/perm/PermissionFragment.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx.perm; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.util.Log; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.fragment.app.Fragment; 15 | import io.reactivex.subjects.PublishSubject; 16 | 17 | 18 | /** 19 | * Created by you on 2018/3/5. 20 | */ 21 | 22 | public class PermissionFragment extends Fragment { 23 | 24 | private static final String TAG = "PermissionFragment"; 25 | 26 | private static final int PERMISSIONS_REQUEST_CODE = 42; 27 | private Map> mSubjects = new HashMap<>(); 28 | 29 | @Override 30 | public void onCreate(@Nullable Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setRetainInstance(true); 33 | } 34 | 35 | void requestPermissions(@NonNull String[] permissions) { 36 | requestPermissions(permissions, PERMISSIONS_REQUEST_CODE); 37 | } 38 | 39 | @Override 40 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 41 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 42 | if (requestCode != PERMISSIONS_REQUEST_CODE) return; 43 | boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length]; 44 | for (int i = 0; i < permissions.length; i++) { 45 | shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]); 46 | } 47 | onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale); 48 | } 49 | 50 | void onRequestPermissionsResult(String permissions[], int[] grantResults, boolean[] shouldShowRequestPermissionRationale) { 51 | for (int i = 0, size = permissions.length; i < size; i++) { 52 | PublishSubject subject = mSubjects.get(permissions[i]); 53 | if (subject == null) { 54 | // No subject found 55 | Log.e(TAG, "RxPermissions.onRequestPermissionsResult invoked but didn't find the corresponding permission request."); 56 | return; 57 | } 58 | mSubjects.remove(permissions[i]); 59 | boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED; 60 | subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i])); 61 | subject.onComplete(); 62 | } 63 | } 64 | 65 | @TargetApi(Build.VERSION_CODES.M) 66 | boolean isGranted(String permission) { 67 | return getActivity().checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; 68 | } 69 | 70 | @TargetApi(Build.VERSION_CODES.M) 71 | boolean isRevoked(String permission) { 72 | return getActivity().getPackageManager().isPermissionRevokedByPolicy(permission, getActivity().getPackageName()); 73 | } 74 | 75 | public PublishSubject getSubjectByPermission(@NonNull String permission) { 76 | return mSubjects.get(permission); 77 | } 78 | 79 | public boolean containsByPermission(@NonNull String permission) { 80 | return mSubjects.containsKey(permission); 81 | } 82 | 83 | public PublishSubject setSubjectForPermission(@NonNull String permission, @NonNull PublishSubject subject) { 84 | return mSubjects.put(permission, subject); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/perm/PermissionMustCallback.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx.perm; 2 | 3 | 4 | import io.reactivex.Observer; 5 | import io.reactivex.disposables.Disposable; 6 | import you.chen.media.rx.RxUtils; 7 | 8 | /** 9 | * Created by you on 2018/4/19. 10 | * 必要的权限请求,所请求的权限必须获取 11 | */ 12 | 13 | public abstract class PermissionMustCallback implements Observer { 14 | 15 | private boolean isGranted = true; 16 | 17 | private Disposable permissionDisposable; 18 | 19 | @Override 20 | public void onSubscribe(Disposable disposable) { 21 | permissionDisposable = disposable; 22 | } 23 | 24 | @Override 25 | public void onComplete() { 26 | permissionCallback(isGranted); 27 | RxUtils.dispose(permissionDisposable); 28 | } 29 | 30 | @Override 31 | public void onError(Throwable e) { 32 | RxUtils.dispose(permissionDisposable); 33 | } 34 | 35 | @Override 36 | public void onNext(Permission permission) { 37 | isGranted &= permission.granted; 38 | } 39 | 40 | protected abstract void permissionCallback(boolean isGranted); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/viewbind/TextViewChangedOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx.viewbind; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | import android.widget.TextView; 6 | 7 | import io.reactivex.ObservableEmitter; 8 | import io.reactivex.ObservableOnSubscribe; 9 | import io.reactivex.functions.Cancellable; 10 | 11 | /** 12 | * Created by you on 2017/4/10. 13 | */ 14 | 15 | public class TextViewChangedOnSubscribe implements ObservableOnSubscribe { 16 | 17 | private TextView mTextView; 18 | 19 | public void addTextViewWatcher(TextView mTextView) { 20 | this.mTextView = mTextView; 21 | } 22 | 23 | @Override 24 | public void subscribe(final ObservableEmitter emitter) throws Exception { 25 | final TextWatcher watcher = new TextWatcher() { 26 | @Override 27 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 28 | } 29 | 30 | @Override 31 | public void onTextChanged(CharSequence s, int start, int before, int count) { 32 | } 33 | 34 | @Override 35 | public void afterTextChanged(Editable s) { 36 | if (!emitter.isDisposed()) { 37 | emitter.onNext(s.toString().trim()); 38 | } 39 | } 40 | }; 41 | mTextView.addTextChangedListener(watcher); 42 | emitter.setCancellable(new Cancellable() { 43 | @Override 44 | public void cancel() throws Exception { 45 | mTextView.removeTextChangedListener(watcher); 46 | } 47 | }); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/rx/viewbind/ViewClickOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.rx.viewbind; 2 | 3 | import android.view.View; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | import io.reactivex.ObservableEmitter; 10 | import io.reactivex.ObservableOnSubscribe; 11 | import io.reactivex.functions.Cancellable; 12 | 13 | /** 14 | * Created by you on 2016/10/19. 15 | */ 16 | 17 | public class ViewClickOnSubscribe implements ObservableOnSubscribe { 18 | 19 | /** 20 | * 注册防抖点击的控件 21 | */ 22 | private List clickViews = new ArrayList(); 23 | 24 | /** 25 | * 添加控件点击事件 26 | * @param views 27 | */ 28 | public void addOnClickListener(View... views) { 29 | if (views == null) return; 30 | for (View v : views) { 31 | clickViews.add(v); 32 | } 33 | } 34 | 35 | @Override 36 | public void subscribe(final ObservableEmitter emitter) throws Exception { 37 | View.OnClickListener listener = new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | if (!emitter.isDisposed()) { 41 | emitter.onNext(v); 42 | } 43 | } 44 | }; 45 | for (View v : clickViews) { 46 | v.setOnClickListener(listener); 47 | } 48 | emitter.setCancellable(new Cancellable() { 49 | @Override 50 | public void cancel() throws Exception { 51 | Iterator iterator = clickViews.iterator(); 52 | while (iterator.hasNext()) { 53 | iterator.next().setOnClickListener(null); 54 | iterator.remove(); 55 | } 56 | } 57 | }); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/ui/AacActivity.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.CheckBox; 8 | import android.widget.TextView; 9 | 10 | import java.io.IOException; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | 14 | import androidx.appcompat.app.AppCompatActivity; 15 | import you.chen.media.R; 16 | import you.chen.media.core.audio.AudioRecorder; 17 | import you.chen.media.utils.FileUtils; 18 | 19 | /** 20 | * Created by you on 2018-03-10. 21 | */ 22 | public class AacActivity extends AppCompatActivity implements View.OnClickListener { 23 | 24 | TextView bt; 25 | 26 | CheckBox cb_muxer; 27 | 28 | AudioRecorder recorder; 29 | 30 | //线程池执行 31 | ExecutorService service; 32 | 33 | public static void lanuch(Context context) { 34 | context.startActivity(new Intent(context, AacActivity.class)); 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.act_aac); 41 | service = Executors.newFixedThreadPool(2); 42 | 43 | initView(); 44 | } 45 | 46 | private void initView() { 47 | cb_muxer = findViewById(R.id.cb_muxer); 48 | bt = findViewById(R.id.bt); 49 | bt.setOnClickListener(this); 50 | } 51 | 52 | @Override 53 | public void onClick(View v) { 54 | switch (v.getId()) { 55 | case R.id.bt: 56 | if (!bt.isSelected()) {//start 57 | startRecording(); 58 | } else { 59 | stopRecording(); 60 | } 61 | break; 62 | } 63 | } 64 | 65 | private void startRecording() { 66 | String saveName = cb_muxer.isChecked() ? "audioMuxer.aac" : "audioTest.aac"; 67 | String path = FileUtils.getCacheDirPath() + saveName; 68 | try { 69 | recorder = cb_muxer.isChecked() ? AudioRecorder.createMuxerAudioRecorder(path) : AudioRecorder.createAudioRecorder(path); 70 | recorder.start(service); 71 | } catch (IOException e) { 72 | e.printStackTrace(); 73 | } 74 | 75 | 76 | bt.setSelected(true); 77 | bt.setText("end"); 78 | } 79 | 80 | private void stopRecording() { 81 | if (recorder != null) { 82 | recorder.stop(); 83 | recorder = null; 84 | } 85 | bt.setSelected(false); 86 | bt.setText("start"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/ui/CameraActivity.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Matrix; 7 | import android.graphics.SurfaceTexture; 8 | import android.hardware.Camera; 9 | import android.os.Bundle; 10 | import android.view.TextureView; 11 | import android.view.View; 12 | 13 | import java.io.File; 14 | 15 | import androidx.appcompat.app.AppCompatActivity; 16 | import you.chen.media.R; 17 | import you.chen.media.camera.CameraHelper; 18 | import you.chen.media.camera.CameraUtils; 19 | import you.chen.media.camera.OrientationHelper; 20 | import you.chen.media.camera.SizeFilter; 21 | import you.chen.media.camera.impl.PictureSizeFilter; 22 | import you.chen.media.utils.BitmapUtils; 23 | import you.chen.media.utils.FileUtils; 24 | import you.chen.media.utils.LogUtils; 25 | import you.chen.media.utils.Utils; 26 | import you.chen.media.widget.CameraView; 27 | import you.chen.media.widget.FlashView; 28 | import you.chen.media.widget.FocusView; 29 | 30 | /** 31 | * Created by you on 2018-01-08. 32 | * 33 | */ 34 | public class CameraActivity extends AppCompatActivity 35 | implements TextureView.SurfaceTextureListener, View.OnClickListener { 36 | 37 | CameraHelper helper; 38 | //camera最佳筛选 39 | SizeFilter filter; 40 | //预览的缩放相关参数 41 | Matrix matrix; 42 | 43 | //surface 44 | CameraView cv_camera; 45 | //聚焦动画控件 46 | FocusView fv_focus; 47 | //前后置相机切换 48 | View iv_switch; 49 | //闪光灯 50 | FlashView fv_flash; 51 | //方向传感 52 | OrientationHelper orientationHelper; 53 | //后,前置摄像头 54 | int cameraId = Camera.CameraInfo.CAMERA_FACING_BACK; 55 | 56 | public static void lanuch(Context context) { 57 | context.startActivity(new Intent(context, CameraActivity.class)); 58 | } 59 | 60 | @Override 61 | protected void onCreate(Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | setContentView(R.layout.act_camera); 64 | 65 | helper = new CameraHelper(); 66 | filter = new PictureSizeFilter(); 67 | orientationHelper = new OrientationHelper(Utils.context()); 68 | 69 | initView(); 70 | } 71 | 72 | private void initView() { 73 | cv_camera = findViewById(R.id.cv_camera); 74 | fv_focus = findViewById(R.id.fv_focus); 75 | iv_switch = findViewById(R.id.iv_switch); 76 | fv_flash = findViewById(R.id.fv_flash); 77 | 78 | findViewById(R.id.bt).setOnClickListener(this); 79 | iv_switch.setOnClickListener(this); 80 | 81 | cv_camera.setOnCameraGestureListener(new CameraView.OnCameraGestureListener() { 82 | @Override 83 | public void onHandleZoom(float zoomScale) { 84 | helper.handleZoom(zoomScale); 85 | LogUtils.i("handlerZoom %f", zoomScale); 86 | } 87 | 88 | @Override 89 | public void onHandleFocus(float x, float y, int w, int h) { 90 | if (cameraId != Camera.CameraInfo.CAMERA_FACING_FRONT) { 91 | helper.handleFocus(CameraUtils.reverseRotate(x, y, w, h, matrix)); 92 | fv_focus.setCenter(x, y); 93 | } 94 | } 95 | }); 96 | 97 | fv_flash.setOnFlashChangedListener(model -> helper.setFlashMode(model)); 98 | if (CameraHelper.isSupportFrontCamera()) { 99 | iv_switch.setVisibility(View.VISIBLE); 100 | } 101 | } 102 | 103 | @Override 104 | protected void onResume() { 105 | super.onResume(); 106 | orientationHelper.enable(); 107 | if (cv_camera.isAvailable()) { 108 | startCamera(); 109 | } else { 110 | cv_camera.setSurfaceTextureListener(this); 111 | } 112 | } 113 | 114 | @Override 115 | protected void onPause() { 116 | orientationHelper.disable(); 117 | stopCamera(); 118 | super.onPause(); 119 | } 120 | 121 | @Override 122 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 123 | startCamera(); 124 | } 125 | 126 | @Override 127 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 128 | } 129 | 130 | @Override 131 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 132 | return true; 133 | } 134 | 135 | @Override 136 | public void onSurfaceTextureUpdated(SurfaceTexture surface) {//nothing 137 | } 138 | 139 | @Override 140 | public void onClick(View view) { 141 | switch (view.getId()) { 142 | case R.id.bt: 143 | helper.takePicture((data, camera) -> { 144 | Bitmap bitmap = CameraUtils.bytesToBitmap(data, matrix, orientationHelper.getOrientation()); 145 | File f = new File(FileUtils.getCacheDirPath(), "123.png"); 146 | BitmapUtils.saveBitmap(bitmap, f, true); 147 | }); 148 | break; 149 | case R.id.iv_switch: 150 | if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { 151 | cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT; 152 | } else { 153 | cameraId = Camera.CameraInfo.CAMERA_FACING_BACK; 154 | } 155 | helper.closeCamera(); 156 | matrix = null; 157 | startCamera(); 158 | break; 159 | } 160 | } 161 | 162 | //开启相机 163 | private void startCamera() { 164 | matrix = helper.openPicCamera(cv_camera.getSurfaceTexture(), cameraId, 165 | cv_camera.getWidth(), cv_camera.getHeight(), filter, orientationHelper.getOrientation()); 166 | 167 | if (matrix != null) { 168 | cv_camera.setTransform(matrix); 169 | } 170 | cv_camera.setMaxScale(helper.getMaxZoomScale()); 171 | fv_flash.setFlashModes(helper.getSupportedFlashModes(), cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); 172 | } 173 | 174 | //释放相机 175 | private void stopCamera() { 176 | helper.closeCamera(); 177 | matrix = null; 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.ui; 2 | 3 | import android.Manifest; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import you.chen.media.R; 9 | import you.chen.media.rx.perm.PermissionMustCallback; 10 | import you.chen.media.rx.perm.RxPermissions; 11 | 12 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 13 | 14 | RxPermissions permissions; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.act_main); 20 | 21 | permissions = new RxPermissions(this); 22 | findViewById(R.id.bt1).setOnClickListener(this); 23 | findViewById(R.id.bt2).setOnClickListener(this); 24 | findViewById(R.id.bt3).setOnClickListener(this); 25 | findViewById(R.id.bt4).setOnClickListener(this); 26 | findViewById(R.id.bt5).setOnClickListener(this); 27 | findViewById(R.id.bt6).setOnClickListener(this); 28 | findViewById(R.id.bt7).setOnClickListener(this); 29 | findViewById(R.id.bt8).setOnClickListener(this); 30 | } 31 | 32 | @Override 33 | public void onClick(View view) { 34 | switch (view.getId()) { 35 | case R.id.bt1: 36 | permissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE, 37 | Manifest.permission.READ_EXTERNAL_STORAGE, 38 | Manifest.permission.CAMERA) 39 | .subscribe(new PermissionMustCallback() { 40 | @Override 41 | protected void permissionCallback(boolean isGranted) { 42 | CameraActivity.lanuch(MainActivity.this); 43 | } 44 | }); 45 | break; 46 | case R.id.bt2: 47 | permissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE, 48 | Manifest.permission.READ_EXTERNAL_STORAGE, 49 | Manifest.permission.CAMERA) 50 | .subscribe(new PermissionMustCallback() { 51 | @Override 52 | protected void permissionCallback(boolean isGranted) { 53 | H264Activity.lanuch(MainActivity.this); 54 | } 55 | }); 56 | break; 57 | case R.id.bt3: 58 | TestActivity.lanuch(this); 59 | break; 60 | case R.id.bt4: 61 | permissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE, 62 | Manifest.permission.READ_EXTERNAL_STORAGE, 63 | Manifest.permission.RECORD_AUDIO) 64 | .subscribe(new PermissionMustCallback() { 65 | @Override 66 | protected void permissionCallback(boolean isGranted) { 67 | AacActivity.lanuch(MainActivity.this); 68 | } 69 | }); 70 | break; 71 | case R.id.bt5: 72 | permissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE, 73 | Manifest.permission.READ_EXTERNAL_STORAGE, 74 | Manifest.permission.RECORD_AUDIO, 75 | Manifest.permission.CAMERA) 76 | .subscribe(new PermissionMustCallback() { 77 | @Override 78 | protected void permissionCallback(boolean isGranted) { 79 | Mp4Activity.lanuch(MainActivity.this); 80 | } 81 | }); 82 | break; 83 | case R.id.bt6: 84 | permissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE, 85 | Manifest.permission.READ_EXTERNAL_STORAGE, 86 | Manifest.permission.CAMERA) 87 | .subscribe(new PermissionMustCallback() { 88 | @Override 89 | protected void permissionCallback(boolean isGranted) { 90 | ScanActivity.lanuch(MainActivity.this); 91 | } 92 | }); 93 | break; 94 | case R.id.bt7: 95 | ViewTestActivity.lanuch(this); 96 | break; 97 | case R.id.bt8: 98 | permissions.requestEach(Manifest.permission.WRITE_EXTERNAL_STORAGE, 99 | Manifest.permission.READ_EXTERNAL_STORAGE, 100 | Manifest.permission.RECORD_AUDIO) 101 | .subscribe(new PermissionMustCallback() { 102 | @Override 103 | protected void permissionCallback(boolean isGranted) { 104 | 105 | } 106 | }); 107 | break; 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/ui/ScanActivity.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Matrix; 6 | import android.graphics.Point; 7 | import android.graphics.SurfaceTexture; 8 | import android.os.Bundle; 9 | import android.view.TextureView; 10 | import android.widget.Toast; 11 | 12 | import com.google.zxing.BarcodeFormat; 13 | import com.google.zxing.Result; 14 | import com.google.zxing.ResultPoint; 15 | import com.google.zxing.ResultPointCallback; 16 | 17 | import java.util.EnumSet; 18 | 19 | import androidx.appcompat.app.AppCompatActivity; 20 | import you.chen.media.R; 21 | import you.chen.media.camera.CameraHelper; 22 | import you.chen.media.camera.CameraUtils; 23 | import you.chen.media.camera.SizeFilter; 24 | import you.chen.media.camera.impl.PictureSizeFilter; 25 | import you.chen.media.core.Constant; 26 | import you.chen.media.core.Orientation; 27 | import you.chen.media.core.Transform; 28 | import you.chen.media.core.h264.AvcTransform; 29 | import you.chen.media.core.h264.ClipAvcTransform; 30 | import you.chen.media.core.scan.DecoderHandler; 31 | import you.chen.media.core.scan.FormatDecoder; 32 | import you.chen.media.utils.LogUtils; 33 | import you.chen.media.widget.CameraView; 34 | 35 | /** 36 | * Created by you on 2018-04-09. 37 | */ 38 | public class ScanActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener, DecoderHandler.DecoderCallback { 39 | 40 | CameraView cv_camera; 41 | 42 | CameraHelper helper; 43 | //camera最佳筛选 44 | SizeFilter filter; 45 | //预览的缩放相关参数 46 | Matrix matrix; 47 | //扫描解析处理 48 | DecoderHandler handler; 49 | 50 | public static void lanuch(Context context) { 51 | context.startActivity(new Intent(context, ScanActivity.class)); 52 | } 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | setContentView(R.layout.act_scan); 58 | helper = new CameraHelper(); 59 | filter = new PictureSizeFilter(); 60 | 61 | helper.setPreviewCallback((data, camera) -> { 62 | if (handler != null) { 63 | handler.push(data); 64 | } 65 | }); 66 | 67 | initView(); 68 | } 69 | 70 | private void initView() { 71 | cv_camera = findViewById(R.id.cv_camera); 72 | 73 | cv_camera.setOnCameraGestureListener(new CameraView.OnCameraGestureListener() { 74 | @Override 75 | public void onHandleZoom(float zoomScale) { 76 | helper.handleZoom(zoomScale); 77 | } 78 | 79 | @Override 80 | public void onHandleFocus(float x, float y, int w, int h) { 81 | helper.handleFocus(CameraUtils.reverseRotate(x, y, w, h, matrix)); 82 | } 83 | }); 84 | } 85 | 86 | @Override 87 | protected void onResume() { 88 | super.onResume(); 89 | if (cv_camera.isAvailable()) { 90 | startCamera(); 91 | } else { 92 | cv_camera.setSurfaceTextureListener(this); 93 | } 94 | } 95 | 96 | @Override 97 | protected void onPause() { 98 | stopCamera(); 99 | super.onPause(); 100 | } 101 | 102 | @Override 103 | public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 104 | startCamera(); 105 | } 106 | 107 | @Override 108 | public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 109 | } 110 | 111 | @Override 112 | public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 113 | return true; 114 | } 115 | 116 | @Override 117 | public void onSurfaceTextureUpdated(SurfaceTexture texture) { 118 | } 119 | 120 | @Override 121 | public void handleResult(Result result) { 122 | if (isFinishing()) return; 123 | LogUtils.i("handlerResult " + result.getText()); 124 | Toast.makeText(this, result.getText(), Toast.LENGTH_LONG).show(); 125 | } 126 | 127 | private void startCamera() { 128 | matrix = helper.openScanCamera(cv_camera.getSurfaceTexture(), cv_camera.getWidth(), cv_camera.getHeight(), 129 | filter, Constant.SCAN_MIN_FPS, Constant.SCAN_MAX_FPS); 130 | 131 | if (matrix != null) { 132 | cv_camera.setTransform(matrix); 133 | } 134 | cv_camera.setMaxScale(helper.getMaxZoomScale()); 135 | 136 | int w = helper.getPreSize().width; 137 | int h = helper.getPreSize().height; 138 | Point matrixSize = CameraUtils.matrixSize(w, h, matrix); 139 | 140 | Transform transform = matrixSize.equals(w, h) ? 141 | new AvcTransform(w, h, 0, Orientation.ROTATE90) 142 | : new ClipAvcTransform(matrixSize.x, matrixSize.y, w, h, 0, Orientation.ROTATE90); 143 | //Camera旋转90, 270时, w, h调换 144 | handler = new DecoderHandler(matrixSize.y, matrixSize.x, new FormatDecoder(EnumSet.of(BarcodeFormat.QR_CODE), null, new ResultPointCallback() { 145 | @Override 146 | public void foundPossibleResultPoint(ResultPoint point) { 147 | if (point != null) { 148 | LogUtils.i("point " + point.getX() + " " + point.getY() + " " + point.hashCode()); 149 | } else { 150 | LogUtils.i("point null"); 151 | } 152 | } 153 | }), transform, false, this); 154 | } 155 | 156 | //释放相机 157 | private void stopCamera() { 158 | helper.closeCamera(); 159 | if (handler != null) { 160 | handler.stop(); 161 | handler = null; 162 | } 163 | matrix = null; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/ui/TestActivity.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | import you.chen.media.R; 10 | import you.chen.media.camera.OrientationHelper; 11 | import you.chen.media.core.YuvUtils; 12 | import you.chen.media.utils.LogUtils; 13 | import you.chen.media.utils.Utils; 14 | 15 | /** 16 | * Created by you on 2018-01-08. 17 | */ 18 | public class TestActivity extends AppCompatActivity implements View.OnClickListener { 19 | 20 | int width = 12; 21 | int height = 10; 22 | int ySize = width * height; 23 | int size = ySize * 3 / 2; 24 | byte[] src = new byte[size]; 25 | 26 | int clipWidth = 8; 27 | int clipHeight = 4; 28 | 29 | int top = 2; 30 | int left = 2; 31 | 32 | //方向传感 33 | OrientationHelper orientationHelper; 34 | 35 | public static void lanuch(Context context) { 36 | context.startActivity(new Intent(context, TestActivity.class)); 37 | } 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.act_test); 43 | 44 | findViewById(R.id.bt1).setOnClickListener(this); 45 | findViewById(R.id.bt2).setOnClickListener(this); 46 | findViewById(R.id.bt3).setOnClickListener(this); 47 | findViewById(R.id.bt4).setOnClickListener(this); 48 | findViewById(R.id.bt5).setOnClickListener(this); 49 | findViewById(R.id.bt6).setOnClickListener(this); 50 | findViewById(R.id.bt7).setOnClickListener(this); 51 | findViewById(R.id.bt8).setOnClickListener(this); 52 | 53 | orientationHelper = new OrientationHelper(Utils.context()); 54 | 55 | for (byte i = 0; i < ySize; i++) {//正数代表Y数据 56 | src[i] = i; 57 | } 58 | for (int i = ySize; i < size; i++) {//负数代表UV数据 59 | src[i] = (byte) -(i - ySize); 60 | } 61 | 62 | printfBuf(src); 63 | } 64 | 65 | @Override 66 | protected void onResume() { 67 | super.onResume(); 68 | orientationHelper.enable(); 69 | } 70 | 71 | @Override 72 | protected void onPause() { 73 | super.onPause(); 74 | orientationHelper.disable(); 75 | } 76 | 77 | /** 78 | * 测试时只需要旋转手机即可传入不同的方向参数 79 | * @param view 80 | */ 81 | @Override 82 | public void onClick(View view) { 83 | byte[] buff = null; 84 | switch (view.getId()) { 85 | case R.id.bt1: 86 | buff = new byte[width * height * 3 / 2]; 87 | YuvUtils.nv21ToI420Rotate(src, buff, width, height, orientationHelper.getOrientation()); 88 | break; 89 | case R.id.bt2: 90 | buff = new byte[clipWidth * clipHeight * 3 / 2]; 91 | YuvUtils.clipNv21ToI420Rotate(src, buff, width, height, clipWidth, clipHeight, left, top, orientationHelper.getOrientation()); 92 | break; 93 | case R.id.bt3: 94 | buff = new byte[width * height * 3 / 2]; 95 | YuvUtils.nv21ToNV12Rotate(src, buff, width, height, orientationHelper.getOrientation()); 96 | break; 97 | case R.id.bt4: 98 | buff = new byte[clipWidth * clipHeight * 3 / 2]; 99 | YuvUtils.clipNv21ToNV12Rotate(src, buff, width, height, clipWidth, clipHeight, left, top, orientationHelper.getOrientation()); 100 | break; 101 | case R.id.bt5: 102 | buff = new byte[width * height * 3 / 2]; 103 | YuvUtils.nv21ToYV12Rotate(src, buff, width, height, orientationHelper.getOrientation()); 104 | break; 105 | case R.id.bt6: 106 | buff = new byte[clipWidth * clipHeight * 3 / 2]; 107 | YuvUtils.clipNv21ToYV12Rotate(src, buff, width, height, clipWidth, clipHeight, left, top, orientationHelper.getOrientation()); 108 | break; 109 | case R.id.bt7: 110 | buff = new byte[width * height * 3 / 2]; 111 | YuvUtils.nv21Rotate(src, buff, width, height, orientationHelper.getOrientation()); 112 | break; 113 | case R.id.bt8: 114 | buff = new byte[clipWidth * clipHeight * 3 / 2]; 115 | YuvUtils.clipNv21Rotate(src, buff, width, height, clipWidth, clipHeight, left, top, orientationHelper.getOrientation()); 116 | break; 117 | } 118 | printfBuf(buff); 119 | } 120 | 121 | private void printfBuf(byte[] buff) { 122 | int ySize = buff.length * 2 / 3; 123 | StringBuilder sb = new StringBuilder("YData:"); 124 | for (int i = 0; i < ySize; i++) { 125 | sb.append(buff[i]).append(" "); 126 | } 127 | sb.append('\n').append("UVdata:"); 128 | for (int i = ySize; i < buff.length; i++) { 129 | sb.append(buff[i]).append(" "); 130 | } 131 | LogUtils.i(sb.toString()); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/ui/ViewTestActivity.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.TextureView; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import you.chen.media.R; 11 | 12 | /** 13 | * Created by you on 2018-04-27. 14 | */ 15 | public class ViewTestActivity extends AppCompatActivity { 16 | 17 | private TextureView tv_test; 18 | 19 | public static void lanuch(Context context) { 20 | context.startActivity(new Intent(context, ViewTestActivity.class)); 21 | } 22 | 23 | @Override 24 | protected void onCreate(@Nullable Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.act_viewtest); 27 | tv_test = findViewById(R.id.tv_test); 28 | } 29 | 30 | 31 | 32 | 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/utils/BitmapUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.ImageFormat; 6 | import android.graphics.Rect; 7 | import android.graphics.YuvImage; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | 14 | public final class BitmapUtils { 15 | 16 | private BitmapUtils() {} 17 | 18 | public static boolean saveBitmap(Bitmap bitmap, File file) { 19 | return saveBitmap(bitmap, file, false); 20 | } 21 | 22 | public static boolean saveBitmap(Bitmap bmp, File file, boolean recycle) { 23 | FileOutputStream fos = null; 24 | try { 25 | fos = new FileOutputStream(file); 26 | bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos); 27 | fos.flush(); 28 | return true; 29 | }catch (IOException e) { 30 | LogUtils.e(e); 31 | } finally { 32 | if (recycle) { 33 | bmp.recycle(); 34 | } 35 | FileUtils.closeCloseable(fos); 36 | } 37 | return false; 38 | } 39 | 40 | //NV21转bitmap 41 | public static Bitmap nv21ToBitmap(byte[] nv21, int width, int height) { 42 | Bitmap bitmap = null; 43 | try { 44 | YuvImage image = new YuvImage(nv21, ImageFormat.NV21, width, height, null); 45 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 46 | image.compressToJpeg(new Rect(0, 0, width, height), 100, stream); 47 | bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size()); 48 | stream.close(); 49 | } catch (IOException e) { 50 | LogUtils.e(e); 51 | } 52 | return bitmap; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.utils; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.Closeable; 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | 10 | /** 11 | * Created by you on 2016/12/2. 12 | */ 13 | 14 | public final class FileUtils { 15 | 16 | private FileUtils() { 17 | } 18 | 19 | /** 20 | * 缓存文件根目录名 21 | */ 22 | private static final String FILE_DIR = "youxiaochen"; 23 | 24 | /** 25 | * SD卡是否存在 26 | */ 27 | public static boolean isSDCardExist() { 28 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 29 | } 30 | 31 | /** 32 | * 获取缓存目录路径 33 | * 34 | * @return 35 | */ 36 | public static String getCacheDirPath() { 37 | if (isSDCardExist()) { 38 | String path = Environment.getExternalStorageDirectory() + File.separator + FILE_DIR + File.separator; 39 | File directory = new File(path); 40 | if (!directory.exists()) directory.mkdirs(); 41 | return path; 42 | } else { 43 | File directory = new File(Utils.context().getCacheDir(), FileUtils.FILE_DIR); 44 | if (!directory.exists()) directory.mkdirs(); 45 | return directory.getAbsolutePath(); 46 | } 47 | } 48 | 49 | /** 50 | * 获取缓存目录 51 | * 52 | * @return 53 | */ 54 | public static File getCacheDir() { 55 | if (isSDCardExist()) { 56 | String path = Environment.getExternalStorageDirectory() + File.separator + FILE_DIR + File.separator; 57 | File directory = new File(path); 58 | if (!directory.exists()) directory.mkdirs(); 59 | return directory; 60 | } else { 61 | File directory = new File(Utils.context().getCacheDir(), FileUtils.FILE_DIR); 62 | if (!directory.exists()) directory.mkdirs(); 63 | return directory; 64 | } 65 | } 66 | 67 | /** 68 | * 关闭资源 69 | * 70 | * @param closees 71 | */ 72 | public static void closeCloseable(Closeable...closees) { 73 | for (Closeable close : closees) { 74 | if (close != null) { 75 | try { 76 | close.close(); 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/utils/LogUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.utils; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.PrintWriter; 6 | import java.io.StringWriter; 7 | import java.io.Writer; 8 | import java.util.Locale; 9 | 10 | public final class LogUtils { 11 | 12 | private static final String TAG = "youxiaochen"; 13 | 14 | private LogUtils() {} 15 | 16 | public static void i(String msg) { 17 | Log.i(TAG, msg); 18 | } 19 | 20 | public static void i(String tag, String msg) { 21 | Log.i(tag, msg); 22 | } 23 | 24 | public static void i(String format, Object ...args) { 25 | i(TAG, String.format(format, args)); 26 | } 27 | 28 | public static void i(String tag, String format, Object ...args) { 29 | i(tag, String.format(format, args)); 30 | } 31 | 32 | public static void e(Throwable throwable) { 33 | Log.e(TAG, throwableToString(throwable)); 34 | } 35 | 36 | /** 37 | * 打印异常信息到log中 38 | * @param throwable 39 | * @return 40 | */ 41 | private static String throwableToString(Throwable throwable) { 42 | if (throwable == null) { 43 | return "throwable is null"; 44 | } 45 | Writer info = new StringWriter(); 46 | PrintWriter printWriter = new PrintWriter(info); 47 | throwable.printStackTrace(printWriter); 48 | return info.toString(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.utils; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | /** 8 | * Created by you on 2017-02-20. 9 | * for context 10 | */ 11 | public final class Utils { 12 | 13 | private static Context context; 14 | 15 | public static void init(@NonNull Context context) { 16 | if (Utils.context != null) return; 17 | Utils.context = context.getApplicationContext(); 18 | } 19 | 20 | public static Context context() { 21 | if (context == null) { 22 | throw new NullPointerException("MediaUtils context must be init"); 23 | } 24 | return context; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/utils/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.utils; 2 | 3 | /** 4 | * Created by you on 2018/1/15. 5 | */ 6 | 7 | public final class ViewUtils { 8 | 9 | private ViewUtils() {} 10 | 11 | /** 12 | * dp 转 px 13 | * @param dpValue 14 | * @return 15 | */ 16 | public static int dp2px(float dpValue) { 17 | final float scale = Utils.context().getResources().getDisplayMetrics().density; 18 | return (int) (dpValue * scale + 0.5f); 19 | } 20 | 21 | /** 22 | * px 转 dp 23 | * @param pxValue 24 | * @return 25 | */ 26 | public static int px2dp(float pxValue) { 27 | final float scale = Utils.context().getResources().getDisplayMetrics().density; 28 | return (int) (pxValue / scale + 0.5f); 29 | } 30 | 31 | /** 32 | * sp 转 px 33 | * @param spValue 34 | * @return 35 | */ 36 | public static int sp2px(float spValue) { 37 | final float fontScale = Utils.context().getResources().getDisplayMetrics().scaledDensity; 38 | return (int) (spValue * fontScale + 0.5f); 39 | } 40 | 41 | /** 42 | * px 转 sp 43 | * @param pxValue 44 | * @return 45 | */ 46 | public static int px2sp(float pxValue) { 47 | final float fontScale = Utils.context().getResources().getDisplayMetrics().scaledDensity; 48 | return (int) (pxValue / fontScale + 0.5f); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/widget/CameraView.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.GestureDetector; 6 | import android.view.MotionEvent; 7 | import android.view.ScaleGestureDetector; 8 | import android.view.TextureView; 9 | 10 | /** 11 | * Created by you on 2018-03-23. 12 | */ 13 | public class CameraView extends TextureView { 14 | 15 | private GestureDetector gestureDetector; 16 | //手势监听 17 | private OnCameraGestureListener listener; 18 | 19 | private float currentScale = 1.0f; 20 | 21 | private float maxScale = Float.MAX_VALUE; 22 | 23 | public CameraView(Context context) { 24 | super(context); 25 | init(context); 26 | } 27 | 28 | public CameraView(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | init(context); 31 | } 32 | 33 | public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | init(context); 36 | } 37 | 38 | private void init(Context context) { 39 | gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { 40 | @Override 41 | public boolean onDown(MotionEvent e) { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean onSingleTapConfirmed(MotionEvent e) { 47 | if (listener != null) { 48 | listener.onHandleFocus(e.getX(), e.getY(), getWidth(), getHeight()); 49 | } 50 | return super.onSingleTapConfirmed(e); 51 | } 52 | }); 53 | 54 | scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { 55 | @Override 56 | public boolean onScale(ScaleGestureDetector detector) { 57 | currentScale *= detector.getScaleFactor(); 58 | if (currentScale < 1.f) currentScale = 1.f; 59 | if (currentScale > maxScale) currentScale = maxScale; 60 | if (listener != null) { 61 | listener.onHandleZoom(currentScale); 62 | } 63 | return true; 64 | } 65 | }); 66 | } 67 | 68 | private ScaleGestureDetector scaleGestureDetector; 69 | 70 | @Override 71 | public boolean onTouchEvent(MotionEvent event) { 72 | boolean res = scaleGestureDetector.onTouchEvent(event); 73 | if (!scaleGestureDetector.isInProgress()) { 74 | return gestureDetector.onTouchEvent(event); 75 | } 76 | return res; 77 | } 78 | 79 | public void setOnCameraGestureListener(OnCameraGestureListener listener) { 80 | this.listener = listener; 81 | } 82 | 83 | /** 84 | * 设置支持的最大比例 85 | * @param maxScale 86 | */ 87 | public final void setMaxScale(float maxScale) { 88 | this.maxScale = maxScale; 89 | } 90 | 91 | /** 92 | * 相机手势操作, 点击聚集与缩放预览 93 | */ 94 | public interface OnCameraGestureListener { 95 | /** 96 | * 缩放手势 97 | * @param zoomScale 放大比例 98 | */ 99 | void onHandleZoom(float zoomScale); 100 | 101 | /** 102 | * 聚集坐标 103 | * @param x 104 | * @param y 105 | * @param w 106 | * @param h 107 | */ 108 | void onHandleFocus(float x, float y, int w, int h); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/widget/FlashView.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.widget; 2 | 3 | import android.content.Context; 4 | import android.hardware.Camera; 5 | import android.util.AttributeSet; 6 | import android.view.Gravity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.RadioGroup; 12 | 13 | import java.util.List; 14 | 15 | import androidx.annotation.Nullable; 16 | import you.chen.media.R; 17 | import you.chen.media.utils.ViewUtils; 18 | 19 | /** 20 | * Created by you on 2018-03-26. 21 | */ 22 | public class FlashView extends LinearLayout implements 23 | RadioGroup.OnCheckedChangeListener, ToggleRadioButton.OnUnToggleListener { 24 | 25 | 26 | private ImageView iv_flash; 27 | 28 | private RadioGroup rg_flash; 29 | 30 | private ToggleRadioButton rb_close, rb_open, rb_auto, rb_light; 31 | 32 | public FlashView(Context context) { 33 | super(context); 34 | init(context); 35 | } 36 | 37 | public FlashView(Context context, @Nullable AttributeSet attrs) { 38 | super(context, attrs); 39 | init(context); 40 | } 41 | 42 | public FlashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 43 | super(context, attrs, defStyleAttr); 44 | init(context); 45 | } 46 | 47 | private void init(Context context) { 48 | this.setOrientation(HORIZONTAL); 49 | this.setGravity(Gravity.CENTER_VERTICAL); 50 | 51 | iv_flash = new ImageView(context); 52 | int padding = ViewUtils.dp2px(13); 53 | iv_flash.setPadding(padding, 0, padding, 0); 54 | iv_flash.setImageResource(R.drawable.flash_camera); 55 | iv_flash.setOnClickListener(v -> rg_flash.setVisibility(View.VISIBLE)); 56 | this.addView(iv_flash, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 57 | 58 | LayoutInflater.from(context).inflate(R.layout.include_flash, this); 59 | rg_flash = findViewById(R.id.rg_flash); 60 | rb_close = rg_flash.findViewById(R.id.rb_close); 61 | rb_open = rg_flash.findViewById(R.id.rb_open); 62 | rb_auto = rg_flash.findViewById(R.id.rb_auto); 63 | rb_light = rg_flash.findViewById(R.id.rb_light); 64 | 65 | rg_flash.setOnCheckedChangeListener(this); 66 | rb_close.setOnUnToggleListener(this); 67 | rb_open.setOnUnToggleListener(this); 68 | rb_auto.setOnUnToggleListener(this); 69 | rb_light.setOnUnToggleListener(this); 70 | } 71 | 72 | @Override 73 | public void onCheckedChanged(RadioGroup group, int checkedId) { 74 | rg_flash.setVisibility(View.INVISIBLE); 75 | switch (checkedId) { 76 | case R.id.rb_close: 77 | iv_flash.setSelected(false); 78 | if (listener != null) { 79 | listener.onFlashChanged(Camera.Parameters.FLASH_MODE_OFF); 80 | } 81 | break; 82 | case R.id.rb_open: 83 | iv_flash.setSelected(true); 84 | if (listener != null) { 85 | listener.onFlashChanged(Camera.Parameters.FLASH_MODE_ON); 86 | } 87 | break; 88 | case R.id.rb_auto: 89 | iv_flash.setSelected(false); 90 | if (listener != null) { 91 | listener.onFlashChanged(Camera.Parameters.FLASH_MODE_AUTO); 92 | } 93 | break; 94 | case R.id.rb_light: 95 | iv_flash.setSelected(true); 96 | if (listener != null) { 97 | listener.onFlashChanged(Camera.Parameters.FLASH_MODE_TORCH); 98 | } 99 | break; 100 | } 101 | 102 | } 103 | 104 | @Override 105 | public void onUnToggle(ToggleRadioButton button) { 106 | rg_flash.setVisibility(View.INVISIBLE); 107 | } 108 | 109 | public void setFlashModes(List flashModes, boolean isFront) { 110 | if (isFront || flashModes == null || flashModes.size() < 2) { 111 | this.setVisibility(View.GONE); 112 | return; 113 | } 114 | //一定会有关闭功能 115 | this.setVisibility(View.VISIBLE); 116 | iv_flash.setSelected(false); 117 | rb_close.setChecked(true); 118 | rb_open.setVisibility(flashModes.contains(Camera.Parameters.FLASH_MODE_ON) ? View.VISIBLE : View.GONE); 119 | rb_auto.setVisibility(flashModes.contains(Camera.Parameters.FLASH_MODE_AUTO) ? View.VISIBLE : View.GONE); 120 | rb_light.setVisibility(flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH) ? View.VISIBLE : View.GONE); 121 | } 122 | 123 | private OnFlashChangedListener listener; 124 | 125 | public void setOnFlashChangedListener(OnFlashChangedListener listener) { 126 | this.listener = listener; 127 | } 128 | 129 | public interface OnFlashChangedListener { 130 | void onFlashChanged(String model); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/widget/FocusView.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.view.animation.AlphaAnimation; 7 | import android.view.animation.Animation; 8 | import android.view.animation.AnimationSet; 9 | import android.view.animation.ScaleAnimation; 10 | import android.widget.FrameLayout; 11 | import android.widget.ImageView; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import you.chen.media.R; 16 | 17 | /** 18 | * Created by you on 2018-03-26. 19 | */ 20 | public class FocusView extends FrameLayout { 21 | 22 | private static final int DEF_SIZE = 200; 23 | 24 | private ImageView iv_focus; 25 | 26 | private Animation animation; 27 | 28 | public FocusView(@NonNull Context context) { 29 | super(context); 30 | init(context); 31 | } 32 | 33 | public FocusView(@NonNull Context context, @Nullable AttributeSet attrs) { 34 | super(context, attrs); 35 | init(context); 36 | } 37 | 38 | public FocusView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 39 | super(context, attrs, defStyleAttr); 40 | init(context); 41 | } 42 | 43 | private void init(Context context) { 44 | iv_focus = new ImageView(context); 45 | iv_focus.setScaleType(ImageView.ScaleType.FIT_XY); 46 | iv_focus.setImageResource(R.drawable.focus_camera); 47 | iv_focus.setVisibility(View.INVISIBLE); 48 | LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 49 | addView(iv_focus, params); 50 | } 51 | 52 | @Override 53 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 54 | int widthModel = MeasureSpec.getMode(widthMeasureSpec); 55 | int size;//宽高一样 56 | if (widthModel == MeasureSpec.EXACTLY) { 57 | size = MeasureSpec.getSize(widthMeasureSpec); 58 | } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) { 59 | size = MeasureSpec.getSize(heightMeasureSpec); 60 | } else { 61 | size = DEF_SIZE; 62 | } 63 | int sizeSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 64 | super.onMeasure(sizeSpec, sizeSpec); 65 | } 66 | 67 | @Override 68 | protected void onDetachedFromWindow() { 69 | iv_focus.clearAnimation(); 70 | super.onDetachedFromWindow(); 71 | } 72 | 73 | /** 74 | * 设置当前聚焦中心坐标点 75 | * @param x 76 | * @param y 77 | */ 78 | public final void setCenter(float x, float y) { 79 | View parent = (View) getParent(); 80 | int left = parent.getPaddingLeft(); 81 | int right = parent.getWidth() - parent.getPaddingRight() - getWidth(); 82 | int top = parent.getPaddingTop(); 83 | int bottom = parent.getHeight() - parent.getPaddingBottom() - getHeight(); 84 | 85 | x = x - getWidth() / 2.0f + parent.getPaddingLeft(); 86 | y = y - getHeight() / 2.0f + parent.getPaddingTop(); 87 | //不能超过边缘 88 | x = clamp(x, left, right); 89 | y = clamp(y, top, bottom); 90 | setX(x); 91 | setY(y); 92 | 93 | iv_focus.clearAnimation(); 94 | if (animation == null) { 95 | animation = initAnim(); 96 | } 97 | iv_focus.startAnimation(animation); 98 | } 99 | 100 | /** 101 | * x值不能超出min~max范围 102 | */ 103 | private float clamp(float x, int min, int max) { 104 | if (x > max) return max; 105 | if (x < min) return min; 106 | return x; 107 | } 108 | 109 | private Animation initAnim() { 110 | AnimationSet animation = new AnimationSet(false); 111 | 112 | ScaleAnimation scaleAnim = new ScaleAnimation(1.f, 0.5f, 1.f, 0.5f, 113 | iv_focus.getWidth() / 2f, iv_focus.getHeight() / 2f); 114 | scaleAnim.setDuration(300); 115 | scaleAnim.setFillAfter(true); 116 | AlphaAnimation alphaAnim = new AlphaAnimation(1.f, 0.75f); 117 | alphaAnim.setDuration(700); 118 | 119 | animation.addAnimation(scaleAnim); 120 | animation.addAnimation(alphaAnim); 121 | 122 | animation.setAnimationListener(new Animation.AnimationListener() { 123 | @Override 124 | public void onAnimationStart(Animation animation) { 125 | iv_focus.setVisibility(View.VISIBLE); 126 | } 127 | 128 | @Override 129 | public void onAnimationEnd(Animation animation) { 130 | iv_focus.setVisibility(View.INVISIBLE); 131 | } 132 | 133 | @Override 134 | public void onAnimationRepeat(Animation animation) { 135 | } 136 | }); 137 | return animation; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/widget/ScanFormatView.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.Drawable; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import you.chen.media.R; 13 | import you.chen.media.utils.LogUtils; 14 | import you.chen.media.utils.ViewUtils; 15 | 16 | /** 17 | * Created by you on 2018-04-27. 18 | * 扫码控件 19 | */ 20 | public class ScanFormatView extends View { 21 | 22 | private static final int DEF_SIZE = ViewUtils.dp2px(100); 23 | //刷新界面的时间 24 | private static final long ANIMATION_DELAY = 40L; 25 | //动画移动间距 26 | private static final int ANIMATION_SIZE = ViewUtils.dp2px(5); 27 | 28 | private int lineHeight = ViewUtils.dp2px(2); 29 | 30 | private Drawable lineDrawable; 31 | 32 | private Rect rect; 33 | 34 | public ScanFormatView(@NonNull Context context) { 35 | super(context); 36 | init(context); 37 | } 38 | 39 | public ScanFormatView(@NonNull Context context, @Nullable AttributeSet attrs) { 40 | super(context, attrs); 41 | init(context); 42 | } 43 | 44 | public ScanFormatView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 45 | super(context, attrs, defStyleAttr); 46 | init(context); 47 | } 48 | 49 | private void init(Context context) { 50 | lineDrawable = context.getResources().getDrawable(R.drawable.qrcode_scan_line); 51 | rect = new Rect(); 52 | } 53 | 54 | @Override 55 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 56 | int widthModel = MeasureSpec.getMode(widthMeasureSpec); 57 | int size;//宽高一样 58 | if (widthModel == MeasureSpec.EXACTLY) { 59 | size = MeasureSpec.getSize(widthMeasureSpec); 60 | } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) { 61 | size = MeasureSpec.getSize(heightMeasureSpec); 62 | } else { 63 | size = DEF_SIZE; 64 | } 65 | int sizeSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 66 | super.onMeasure(sizeSpec, sizeSpec); 67 | } 68 | 69 | @Override 70 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 71 | super.onLayout(changed, left, top, right, bottom); 72 | rect.set(0, 0, getWidth(), lineHeight); 73 | } 74 | 75 | @Override 76 | protected void onDraw(Canvas canvas) { 77 | lineDrawable.setBounds(rect); 78 | lineDrawable.draw(canvas); 79 | rect.offset(0, ANIMATION_SIZE); 80 | if (rect.bottom > getHeight()) { 81 | rect.top = 0; 82 | rect.bottom = lineHeight; 83 | } 84 | postInvalidateDelayed(ANIMATION_DELAY); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/you/chen/media/widget/ToggleRadioButton.java: -------------------------------------------------------------------------------- 1 | package you.chen.media.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.RadioButton; 6 | 7 | /** 8 | * Created by you on 2018-03-27. 9 | */ 10 | public class ToggleRadioButton extends RadioButton { 11 | 12 | public ToggleRadioButton(Context context) { 13 | super(context); 14 | } 15 | 16 | public ToggleRadioButton(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | } 19 | 20 | public ToggleRadioButton(Context context, AttributeSet attrs, int defStyleAttr) { 21 | super(context, attrs, defStyleAttr); 22 | } 23 | 24 | @Override 25 | public void toggle() { 26 | if (!isChecked()) { 27 | super.toggle(); 28 | } else { 29 | if (listener != null) { 30 | listener.onUnToggle(this); 31 | } 32 | } 33 | } 34 | 35 | private OnUnToggleListener listener; 36 | 37 | public void setOnUnToggleListener(OnUnToggleListener listener) { 38 | this.listener = listener; 39 | } 40 | 41 | /** 42 | * 已经选中时的点击响应 43 | */ 44 | public interface OnUnToggleListener { 45 | 46 | void onUnToggle(ToggleRadioButton button); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/res/color/flash_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/flash_camera_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/res/drawable-xxhdpi/flash_camera_s.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/flash_camera_un.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/res/drawable-xxhdpi/flash_camera_un.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/focus_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/res/drawable-xxhdpi/focus_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/line_scan_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/res/drawable-xxhdpi/line_scan_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/qrcode_scan_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/res/drawable-xxhdpi/qrcode_scan_line.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/scan_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/res/drawable-xxhdpi/scan_success.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youxiaochen/CameraMedia/2d9ed6882e1e2a9b0dc80126dafb118d9cfa64af/app/src/main/res/drawable-xxhdpi/switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/flash_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/act_aac.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 18 | 19 | 20 | 21 |