├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── peakmain │ │ └── video_audio │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── demo.h264 │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── include │ │ │ ├── crash │ │ │ │ ├── CrashAnalyser.h │ │ │ │ ├── JNIBridge.h │ │ │ │ ├── SignalHandler.h │ │ │ │ ├── Utils.h │ │ │ │ └── stringprintf.h │ │ │ ├── log.h │ │ │ ├── mmkv │ │ │ │ ├── MMKV.h │ │ │ │ └── ProtoBuf.h │ │ │ └── native-lib.h │ │ ├── native-lib.cpp │ │ └── src │ │ │ ├── crash │ │ │ ├── CrashAnalyser.cpp │ │ │ ├── JNIBridge.cpp │ │ │ ├── SignalHandler.cpp │ │ │ ├── Utils.cpp │ │ │ └── stringprintf.cpp │ │ │ └── mmkv │ │ │ ├── MMKV.cpp │ │ │ └── ProtoBuf.cpp │ ├── java │ │ └── com │ │ │ └── peakmain │ │ │ └── video_audio │ │ │ ├── App.kt │ │ │ ├── MainActivity.kt │ │ │ ├── activity │ │ │ ├── Camera2XActivity.kt │ │ │ ├── CameraActivity.kt │ │ │ ├── MMKVActivity.kt │ │ │ ├── ProjectScreenAcceptActivity.kt │ │ │ ├── ProjectScreenSendActivity.kt │ │ │ ├── SimpleActivity.kt │ │ │ ├── VideoAcceptVideoCallActivity.kt │ │ │ ├── VideoSendVideoCallActivity.kt │ │ │ └── simple │ │ │ │ └── H264PlayerActivity.kt │ │ │ ├── basic │ │ │ ├── BaseActivity.kt │ │ │ └── BaseRecyclerStringAdapter.kt │ │ │ ├── simple │ │ │ ├── BasicSurfaceHolderCallback.kt │ │ │ ├── H264Player.java │ │ │ └── mmkv │ │ │ │ └── MMKV.java │ │ │ ├── utils │ │ │ ├── Camera2Helper.kt │ │ │ ├── CameraHelper.kt │ │ │ ├── CameraXHelper.kt │ │ │ ├── CrashUtils.kt │ │ │ ├── FileUtils.java │ │ │ ├── VideoAudioUtils.kt │ │ │ ├── crash │ │ │ │ ├── JavaCrashMonitor.kt │ │ │ │ ├── NativeCrashMonitor.java │ │ │ │ └── callback │ │ │ │ │ └── CrashListener.java │ │ │ └── socket │ │ │ │ ├── CodecLiveH265.java │ │ │ │ ├── SocketAcceptLive.java │ │ │ │ └── WebSocketSendLive.java │ │ │ ├── video │ │ │ ├── DecodecVideoCallLiveH265.java │ │ │ ├── EncodecVideoCallAcceptLiveH265.java │ │ │ └── EncodecVideoCallSendLiveH265.java │ │ │ └── widget │ │ │ ├── CameraSurface.java │ │ │ ├── VideoCallAcceptSurfaceView.java │ │ │ └── VideoCallSendSurfaceView.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_accept_video_call.xml │ │ ├── activity_camera.xml │ │ ├── activity_camera2x.xml │ │ ├── activity_h264_player.xml │ │ ├── activity_main.xml │ │ ├── activity_mmkv.xml │ │ ├── activity_project_screen.xml │ │ ├── activity_send_video_call.xml │ │ └── item_recyclerview_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── peakmain │ └── video_audio │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── javaLib ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── peakmain │ └── javalib │ └── H264Parse.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Video_Audio 2 | Android音视频开发 3 | #### 功能目录 4 | - [Android平台Native奔溃捕获机制及实现](https://www.jianshu.com/p/fbf910bcb38d) 5 | - [Android音视频开发——MediaCodec播放H264视频](https://www.jianshu.com/p/7d7c9a74f48e) 6 | - [Android平台MMKV的原理及实现](https://www.jianshu.com/p/f4837fd9b3b4) 7 | - [Android音视频开发——MedCodec实现屏幕录制编码成H264](https://www.jianshu.com/p/633911d027de) 8 | - [Android音视频开发——SPS分析与提取](https://www.jianshu.com/p/79c305dc40b2) 9 | - [Android音视频开发——Camera、Camera2和CameraX的使用和封装](https://www.jianshu.com/p/8d59abdd5afe) 10 | 11 | #### 关于我 12 | - 我的简书:https://www.jianshu.com/u/3ff32f5aea98 13 | - 我的Github:https://github.com/peakmain -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 28 7 | 8 | defaultConfig { 9 | applicationId "com.peakmain.video_audio" 10 | minSdkVersion 21 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | externalNativeBuild { 17 | cmake { 18 | cppFlags "" 19 | } 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | debug{ 29 | debuggable true 30 | buildConfigField("String","TAG","\"TAG\"") 31 | } 32 | } 33 | externalNativeBuild { 34 | cmake { 35 | path "src/main/cpp/CMakeLists.txt" 36 | version "3.10.2" 37 | } 38 | } 39 | compileOptions { 40 | sourceCompatibility = 1.8 41 | targetCompatibility = 1.8 42 | } 43 | kotlinOptions { 44 | jvmTarget = '1.8' 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation fileTree(dir: "libs", include: ["*.jar"]) 50 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 51 | implementation 'androidx.core:core-ktx:1.3.2' 52 | implementation 'androidx.appcompat:appcompat:1.2.0' 53 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 54 | implementation 'com.google.android.material:material:1.3.0' 55 | testImplementation 'junit:junit:4.12' 56 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 58 | implementation 'com.github.Peakmain:BasicUI:1.1.0-androidx' 59 | implementation "androidx.camera:camera-core:1.0.0-alpha05" 60 | implementation "androidx.camera:camera-camera2:1.0.0-alpha05" 61 | implementation "org.java-websocket:Java-WebSocket:1.4.0" 62 | } 63 | repositories { 64 | mavenCentral() 65 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/peakmain/video_audio/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.peakmain.video_audio", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/assets/demo.h264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/assets/demo.h264 -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.4.1) 3 | 4 | 5 | 6 | 7 | 8 | project("Video_Audio") 9 | include_directories(./include) 10 | 11 | set(source_dir ./src) 12 | aux_source_directory(${source_dir} src_list) 13 | aux_source_directory(${source_dir}/crash src_list) 14 | aux_source_directory(${source_dir}/mmkv src_list) 15 | add_library( # Sets the name of the library. 16 | native-lib 17 | 18 | # Sets the library as a shared library. 19 | SHARED 20 | 21 | # Provides a relative path to your source file(s). 22 | native-lib.cpp 23 | ${src_list}) 24 | 25 | 26 | 27 | find_library( 28 | log-lib 29 | 30 | log) 31 | find_library( jnigraphics-lib 32 | jnigraphics ) 33 | 34 | target_link_libraries( # Specifies the target library. 35 | native-lib 36 | 37 | # Links the target library to the log library 38 | # included in the NDK. 39 | ${log-lib} 40 | ${jnigraphics-lib}) -------------------------------------------------------------------------------- /app/src/main/cpp/include/crash/CrashAnalyser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/20. 3 | // 4 | 5 | #ifndef VIDEO_AUDIO_CRASHANALYSER_H 6 | #define VIDEO_AUDIO_CRASHANALYSER_H 7 | #include 8 | #include "log.h" 9 | #include "threads.h" 10 | #include 11 | using namespace std; 12 | #define BACKTRACE_FRAMES_MAX 32 13 | typedef struct native_handler_context_struct { 14 | int code; 15 | siginfo_t *si; 16 | void *sc; 17 | pid_t pid; 18 | pid_t tid; 19 | const char *processName; 20 | const char *threadName; 21 | int frame_size; 22 | uintptr_t frames[BACKTRACE_FRAMES_MAX]; 23 | } native_handler_context; 24 | 25 | extern void initCondition(); 26 | void *threadCrashMonitor(void *argv); 27 | 28 | extern void waitForSignal(); 29 | 30 | extern void analysisNativeException(); 31 | 32 | extern void notifyCaughtSignal(int code, siginfo_t *si, void *sc); 33 | 34 | extern void copyInfo2Context(int code, siginfo_t *si, void *sc); 35 | #endif //VIDEO_AUDIO_CRASHANALYSER_H 36 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/crash/JNIBridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/20. 3 | // 4 | 5 | #ifndef VIDEO_AUDIO_JNIBRIDGE_H 6 | #define VIDEO_AUDIO_JNIBRIDGE_H 7 | #include "CrashAnalyser.h" 8 | #include 9 | #include "unistd.h" 10 | class JNIBridge { 11 | private: 12 | JavaVM *javaVm;//全局的jvm 13 | jobject callbackObj;//Java回调的接口 14 | jclass nativeCrashMonitorClass;//java的nativeCrashMonitorClass类 15 | 16 | public: 17 | JNIBridge(JavaVM *javaVm, jobject callbackObj, jclass nativeCrashMonitorClass); 18 | public: 19 | void throwException2Java(native_handler_context *handlerContext); 20 | }; 21 | 22 | 23 | #endif //VIDEO_AUDIO_JNIBRIDGE_H 24 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/crash/SignalHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/7/9. 3 | // 4 | 5 | #ifndef NDKACTUALCOMBAT_SIGNALHANDLER_H 6 | #define NDKACTUALCOMBAT_SIGNALHANDLER_H 7 | #include 8 | #include 9 | #include 10 | #include "../include/crash/CrashAnalyser.h" 11 | extern bool installSignalHandlers(); 12 | extern void installAlternateStack(); 13 | // 异常信号量 14 | const int exceptionSignals[] = {SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP}; 15 | const int exceptionSignalsNumber = sizeof(exceptionSignals)/ sizeof(exceptionSignals[0]); 16 | 17 | static struct sigaction oldHandlers[NSIG]; 18 | #endif //NDKACTUALCOMBAT_SIGNALHANDLER_H 19 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/crash/Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/7/9. 3 | // 4 | 5 | #ifndef NDKACTUALCOMBAT_UTILS_H 6 | #define NDKACTUALCOMBAT_UTILS_H 7 | 8 | #include 9 | #include 10 | #define PROCESS_NAME_LENGTH 512 11 | #define THREAD_NAME_LENGTH 512 12 | extern const char* desc_sig(int sig, int code); 13 | 14 | extern const char* getProcessName(pid_t pid); 15 | 16 | extern const char* getThreadName(pid_t tid); 17 | extern bool is_dll(const char* dll_name); 18 | 19 | #endif //NDKACTUALCOMBAT_UTILS_H 20 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/crash/stringprintf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace android { 23 | namespace base { 24 | 25 | // These printf-like functions are implemented in terms of vsnprintf, so they 26 | // use the same attribute for compile-time format string checking. 27 | 28 | // Returns a string corresponding to printf-like formatting of the arguments. 29 | std::string StringPrintf(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2))); 30 | 31 | // Appends a printf-like formatting of the arguments to 'dst'. 32 | void StringAppendF(std::string* dst, const char* fmt, ...) 33 | __attribute__((__format__(__printf__, 2, 3))); 34 | 35 | // Appends a printf-like formatting of the arguments to 'dst'. 36 | void StringAppendV(std::string* dst, const char* format, va_list ap) 37 | __attribute__((__format__(__printf__, 2, 0))); 38 | 39 | } // namespace base 40 | } // namespace android 41 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/log.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/20. 3 | // 4 | 5 | #ifndef VIDEO_AUDIO_LOG_H 6 | #define VIDEO_AUDIO_LOG_H 7 | 8 | #include 9 | #define TAG "VIDEO_AUDIO_JNI_TAG" 10 | #include 11 | # define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) 12 | # define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) 13 | # define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) 14 | # define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) 15 | 16 | #endif //VIDEO_AUDIO_LOG_H 17 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/mmkv/MMKV.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/24. 3 | // 4 | 5 | #ifndef VIDEO_AUDIO_MMKV_H 6 | #define VIDEO_AUDIO_MMKV_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "ProtoBuf.h" 17 | 18 | 19 | static std::string g_rootDir; 20 | 21 | #define DEFAULT_MMAP_ID "peakmain_mmkv" 22 | class MMKV { 23 | public: 24 | MMKV(const char *mmapID); 25 | static void initializeMMKV(const char *path); 26 | static MMKV *defaultMMKV(); 27 | void putInt(const std::string& key, int32_t value); 28 | int32_t getInt(std::string key, int32_t defaultValue); 29 | private: 30 | void loadFromFile(); 31 | void zeroFillFile(int fd, int32_t startPos, int32_t size); 32 | void appendDataWithKey(std::string key, ProtoBuf* value); 33 | public: 34 | //文件路径 35 | std::string m_path; 36 | 37 | //文件句柄 38 | int m_fd; 39 | //文件可写长度 40 | int32_t m_size; 41 | //内存映射地址 42 | int8_t *m_ptr; 43 | //记录原始数据 44 | ProtoBuf *m_output; 45 | //已经使用的长度 & 46 | int32_t m_actualSize = 0; 47 | std::unordered_map map; 48 | 49 | void putString(const std::string &key,const std::string &value); 50 | 51 | std::string getString(std::string key); 52 | }; 53 | 54 | 55 | 56 | #endif //VIDEO_AUDIO_MMKV_H 57 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/mmkv/ProtoBuf.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/20. 3 | // 4 | 5 | #ifndef VIDEO_AUDIO_PROTOBUF_H 6 | #define VIDEO_AUDIO_PROTOBUF_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | class ProtoBuf { 12 | public: 13 | 14 | ProtoBuf(int32_t size); 15 | 16 | ProtoBuf(int8_t *buf, int32_t size, bool isCopy = false); 17 | 18 | ~ProtoBuf(); 19 | 20 | static int32_t computeInt32Size(int32_t value); 21 | 22 | static int32_t computeItemSize(std::string key, ProtoBuf* value); 23 | 24 | static int32_t computeMapSize(std::unordered_map map); 25 | 26 | void restore(); 27 | 28 | public: 29 | std::string readString(); 30 | 31 | ProtoBuf* readData(); 32 | 33 | int32_t readInt(); 34 | 35 | public: 36 | //raw的表示不拼接长度 37 | 38 | 39 | void writeRawInt(int32_t value); 40 | 41 | void writeString(std::string value); 42 | 43 | void writeData(ProtoBuf* data); 44 | 45 | public: 46 | bool isAtEnd() { return m_position == m_size; }; 47 | 48 | int32_t length() const { return m_size; } 49 | 50 | //空闲内存 51 | int32_t spaceLeft() { 52 | return m_size - m_position; 53 | } 54 | 55 | int8_t *getBuf() { 56 | return m_buf; 57 | } 58 | 59 | private: 60 | void writeByte(int8_t value); 61 | 62 | int8_t readByte(); 63 | 64 | private: 65 | int8_t *m_buf; 66 | int32_t m_size; 67 | int32_t m_position; 68 | bool m_isCopy; 69 | }; 70 | 71 | #endif //VIDEO_AUDIO_PROTOBUF_H 72 | -------------------------------------------------------------------------------- /app/src/main/cpp/include/native-lib.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/24. 3 | // 4 | 5 | #ifndef VIDEO_AUDIO_NATIVE_LIB_H 6 | #define VIDEO_AUDIO_NATIVE_LIB_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "../include/log.h" 13 | #include "../include/crash/CrashAnalyser.h" 14 | #include "../include/mmkv/MMKV.h" 15 | #endif //VIDEO_AUDIO_NATIVE_LIB_H 16 | -------------------------------------------------------------------------------- /app/src/main/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | #include "native-lib.h" 2 | 3 | /** 4 | * 捕获native异常 5 | */ 6 | extern "C" 7 | JNIEXPORT void JNICALL 8 | Java_com_peakmain_video_1audio_utils_crash_NativeCrashMonitor_nativeCrashInit(JNIEnv *env, 9 | jobject thiz, 10 | jobject nativeCrashMonitor, 11 | jobject callback) { 12 | //获取全局的jvm 13 | JavaVM *javaVm; 14 | env->GetJavaVM(&javaVm); 15 | //生成全局对象 16 | callback = env->NewGlobalRef(callback); 17 | jclass nativeCrashMonitorClass = env->GetObjectClass(nativeCrashMonitor); 18 | nativeCrashMonitorClass = (jclass) env->NewGlobalRef(nativeCrashMonitorClass); 19 | auto *jniBridge = new JNIBridge(javaVm, callback, nativeCrashMonitorClass); 20 | pthread_t pthread; 21 | //创建一个线程 22 | initCondition(); 23 | //ret=0代表创建成功 24 | int ret = pthread_create(&pthread, NULL, threadCrashMonitor, jniBridge); 25 | if (ret < 0) { 26 | LOGE("%s", "pthread_create error"); 27 | } 28 | } 29 | 30 | extern "C" 31 | JNIEXPORT void JNICALL 32 | Java_com_peakmain_video_1audio_utils_crash_NativeCrashMonitor_nativeSetup(JNIEnv *env, 33 | jobject thiz) { 34 | installAlternateStack(); 35 | installSignalHandlers(); 36 | } 37 | //创建一个native异常 38 | extern "C" 39 | JNIEXPORT void JNICALL 40 | Java_com_peakmain_video_1audio_utils_crash_NativeCrashMonitor_nativeCrashCreate(JNIEnv *env, 41 | jclass clazz) { 42 | /* int *num = NULL; 43 | *num = 100;*/ 44 | } 45 | /** 46 | * MMKV 47 | */ 48 | extern "C" 49 | JNIEXPORT void JNICALL 50 | Java_com_peakmain_video_1audio_simple_mmkv_MMKV_mmkvInit(JNIEnv *env, jclass clazz, 51 | jstring rootDir_) { 52 | const char *rootDir = env->GetStringUTFChars(rootDir_, 0); 53 | 54 | MMKV::initializeMMKV(rootDir); 55 | 56 | env->ReleaseStringUTFChars(rootDir_, rootDir); 57 | 58 | }extern "C" 59 | JNIEXPORT jlong JNICALL 60 | Java_com_peakmain_video_1audio_simple_mmkv_MMKV_getDefaultMMKV(JNIEnv *env, jclass clazz) { 61 | MMKV *kv = MMKV::defaultMMKV(); 62 | return reinterpret_cast(kv); 63 | 64 | }extern "C" 65 | JNIEXPORT jint JNICALL 66 | Java_com_peakmain_video_1audio_simple_mmkv_MMKV_getInt(JNIEnv *env, jobject thiz, jlong handle, 67 | jstring key_, jint defaultValue) { 68 | 69 | const char *key = env->GetStringUTFChars(key_, 0); 70 | MMKV *kv = reinterpret_cast(handle); 71 | int returnValue = kv->getInt(key, defaultValue); 72 | 73 | env->ReleaseStringUTFChars(key_, key); 74 | return returnValue; 75 | 76 | }extern "C" 77 | JNIEXPORT void JNICALL 78 | Java_com_peakmain_video_1audio_simple_mmkv_MMKV_putInt(JNIEnv *env, jobject thiz, jlong handle, 79 | jstring key_, jint value) { 80 | const char *key = env->GetStringUTFChars(key_, 0); 81 | MMKV *kv = reinterpret_cast(handle); 82 | kv->putInt(key, value); 83 | env->ReleaseStringUTFChars(key_, key); 84 | }extern "C" 85 | JNIEXPORT void JNICALL 86 | Java_com_peakmain_video_1audio_simple_mmkv_MMKV_putString(JNIEnv *env, jobject thiz, jlong handle, 87 | jstring key_, jstring value_) { 88 | const char *key = env->GetStringUTFChars(key_, 0); 89 | const char *value = env->GetStringUTFChars(value_, 0); 90 | MMKV *kv = reinterpret_cast(handle); 91 | kv->putString(key, value); 92 | env->ReleaseStringUTFChars(value_, value); 93 | env->ReleaseStringUTFChars(key_, key); 94 | }extern "C" 95 | JNIEXPORT jstring JNICALL 96 | Java_com_peakmain_video_1audio_simple_mmkv_MMKV_getString(JNIEnv *env, jobject thiz, jlong handle, 97 | jstring key_) { 98 | const char *key = env->GetStringUTFChars(key_, 0); 99 | MMKV *kv = reinterpret_cast(handle); 100 | std::string value = kv->getString(key); 101 | LOGE("%s", value.c_str()); 102 | env->ReleaseStringUTFChars(key_, key); 103 | return env->NewStringUTF(value.c_str()); 104 | } -------------------------------------------------------------------------------- /app/src/main/cpp/src/crash/CrashAnalyser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/20. 3 | // 4 | 5 | #include 6 | #include "../../include/crash/CrashAnalyser.h" 7 | #include "../../include/crash/JNIBridge.h" 8 | #include 9 | 10 | //锁的条件变量 11 | pthread_cond_t signalCond; 12 | pthread_mutex_t signalLock; 13 | pthread_cond_t exceptionCond; 14 | pthread_mutex_t exceptionLock; 15 | native_handler_context *handlerContext; 16 | 17 | _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, void *arg) { 18 | native_handler_context *const s = static_cast(arg); 19 | //pc是每个堆栈的栈顶 20 | const uintptr_t pc = _Unwind_GetIP(context); 21 | if (pc != 0x0) { 22 | // 把 pc 值保存到 native_handler_context 23 | s->frames[s->frame_size++] = pc; 24 | } 25 | if (s->frame_size == BACKTRACE_FRAMES_MAX) { 26 | return _URC_END_OF_STACK; 27 | } else { 28 | return _URC_NO_REASON; 29 | } 30 | } 31 | 32 | void initCondition() { 33 | handlerContext = (native_handler_context *) malloc(sizeof(native_handler_context_struct)); 34 | pthread_mutex_init(&signalLock, NULL); 35 | pthread_cond_init(&signalCond, NULL); 36 | pthread_mutex_init(&exceptionLock, NULL); 37 | pthread_cond_init(&exceptionCond, NULL); 38 | } 39 | 40 | void *threadCrashMonitor(void *argv) { 41 | JNIBridge *jniBridge = static_cast(argv); 42 | 43 | while (true) { 44 | //等待信号处理函数唤醒 45 | waitForSignal(); 46 | //唤醒之后,分析native堆栈 47 | analysisNativeException(); 48 | 49 | //抛给java 50 | jniBridge->throwException2Java(handlerContext); 51 | } 52 | } 53 | 54 | //等待信号 55 | void waitForSignal() { 56 | pthread_mutex_lock(&signalLock); 57 | LOGE("waitForSignal start."); 58 | pthread_cond_wait(&signalCond, &signalLock); 59 | LOGE("waitForSignal finish."); 60 | pthread_mutex_unlock(&signalLock); 61 | 62 | } 63 | 64 | // 唤醒等待 65 | void notifyCaughtSignal(int code, siginfo_t *si, void *sc) { 66 | copyInfo2Context(code, si, sc); 67 | pthread_mutex_lock(&signalLock); 68 | pthread_cond_signal(&signalCond); 69 | pthread_mutex_unlock(&signalLock); 70 | } 71 | 72 | //保存唤醒后的信息 73 | void copyInfo2Context(int code, siginfo_t *si, void *sc) { 74 | handlerContext->code = code; 75 | handlerContext->si = si; 76 | handlerContext->sc = sc; 77 | handlerContext->pid = getpid(); 78 | handlerContext->tid = gettid(); 79 | handlerContext->processName = getProcessName(handlerContext->pid); 80 | if (handlerContext->pid == handlerContext->tid) { 81 | handlerContext->threadName = "main"; 82 | } else { 83 | handlerContext->threadName = getThreadName(handlerContext->tid); 84 | } 85 | handlerContext->frame_size = 0; 86 | //捕获c/c++的堆栈信息 87 | _Unwind_Backtrace(unwind_callback, handlerContext); 88 | } 89 | //分析native的异常 90 | void analysisNativeException() { 91 | const char *posixDesc = desc_sig(handlerContext->si->si_signo, handlerContext->si->si_code); 92 | LOGD("posixDesc -> %s", posixDesc); 93 | LOGD("signal -> %d", handlerContext->si->si_signo); 94 | LOGD("address -> %p", handlerContext->si->si_addr); 95 | LOGD("processName -> %s", handlerContext->processName); 96 | LOGD("threadName -> %s", handlerContext->threadName); 97 | LOGD("pid -> %d", handlerContext->pid); 98 | LOGD("tid -> %d", handlerContext->tid); 99 | } -------------------------------------------------------------------------------- /app/src/main/cpp/src/crash/JNIBridge.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/20. 3 | // 4 | 5 | #include 6 | #include "../../include/crash/JNIBridge.h" 7 | #include 8 | #include 9 | #include 10 | #include "cxxabi.h" 11 | JNIBridge::JNIBridge(JavaVM *javaVm, jobject callbackObj, jclass nativeCrashMonitorClass) { 12 | this->javaVm = javaVm; 13 | this->callbackObj = callbackObj; 14 | this->nativeCrashMonitorClass = nativeCrashMonitorClass; 15 | } 16 | 17 | //将异常抛给Java 18 | void JNIBridge::throwException2Java(native_handler_context *handlerContext) { 19 | LOGE("throwException2Java"); 20 | //子线程获取env 21 | JNIEnv *env = NULL; 22 | if (this->javaVm->AttachCurrentThread(&env, NULL) != JNI_OK) { 23 | LOGE("AttachCurrentThread failed"); 24 | } 25 | const char *sig = "(Ljava/lang/String;)Ljava/lang/String;"; 26 | jmethodID getStackInfoByThreadNameMid = env->GetStaticMethodID(this->nativeCrashMonitorClass, 27 | "getStackInfoByThreadName", sig); 28 | //private stifatic String getStackInfoByThreadName(String threadName) 29 | jstring jThreadName = env->NewStringUTF(handlerContext->threadName); 30 | jobject javaStackInfo = env->CallStaticObjectMethod(this->nativeCrashMonitorClass, 31 | getStackInfoByThreadNameMid, jThreadName); 32 | //java的String->native的string 33 | const char *javaExceptionStackInfo = env->GetStringUTFChars((jstring) javaStackInfo, JNI_FALSE); 34 | //获取c++堆栈信息 35 | int frame_size = handlerContext->frame_size; 36 | string result; 37 | for (int index = 0; index < frame_size; ++index) { 38 | uintptr_t pc = handlerContext->frames[index]; 39 | //获取到加载的内存的起始地址 40 | Dl_info stack_info; 41 | void *const addr = (void *) pc; 42 | if (dladdr(addr, &stack_info) != 0 && stack_info.dli_fname != NULL) { 43 | if (stack_info.dli_fbase == 0) { 44 | // No valid map associated with this frame. 45 | result += " "; 46 | } else if (stack_info.dli_fname) { 47 | std::string so_name = std::string(stack_info.dli_fname); 48 | result += " " + so_name; 49 | } else { 50 | result += android::base::StringPrintf(" ", 51 | (uint64_t) stack_info.dli_fbase); 52 | } 53 | if (stack_info.dli_sname) { 54 | char *demangled_name = abi::__cxa_demangle(stack_info.dli_sname, nullptr, nullptr, 55 | nullptr); 56 | if (demangled_name == nullptr) { 57 | result += " ("; 58 | result += stack_info.dli_sname; 59 | } else { 60 | result += " ("; 61 | result += demangled_name; 62 | free(demangled_name); 63 | } 64 | if (stack_info.dli_saddr != 0) { 65 | uintptr_t offset = pc - (uintptr_t) stack_info.dli_saddr; 66 | result += android::base::StringPrintf(":%" PRId64, (uint64_t) offset); 67 | } 68 | result += ')'; 69 | } 70 | result += '\n'; 71 | } 72 | } 73 | //回掉Java的接口 74 | jclass crashClass = env->GetObjectClass(this->callbackObj); 75 | jmethodID crashMethod = env->GetMethodID(crashClass, "onCrash", 76 | "(Ljava/lang/String;Ljava/lang/Error;)V"); 77 | jclass jErrorClass = env->FindClass("java/lang/Error"); 78 | jmethodID jErrorInitMethod = env->GetMethodID(jErrorClass, "", "(Ljava/lang/String;)V"); 79 | result = result += javaExceptionStackInfo; 80 | jstring errorMessage = env->NewStringUTF(result.c_str()); 81 | //错误信息给Error 82 | jobject errorObject = env->NewObject(jErrorClass, jErrorInitMethod, errorMessage); 83 | env->CallVoidMethod(this->callbackObj, crashMethod, jThreadName, errorObject); 84 | if (this->javaVm->DetachCurrentThread() != JNI_OK) { 85 | LOGE("DetachCurrentThread failed!"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/cpp/src/crash/SignalHandler.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by peakmain on 2021/8/21. 3 | // 4 | #include "../include/crash/SignalHandler.h" 5 | //额外的栈空间 6 | void installAlternateStack() { 7 | stack_t newStack; 8 | stack_t oldStack; 9 | memset(&newStack, 0, sizeof(newStack)); 10 | memset(&oldStack, 0, sizeof(oldStack)); 11 | static const unsigned sigaltstackSize = std::max(16384, SIGSTKSZ); 12 | if (sigaltstack(NULL, &oldStack) == -1 13 | || !oldStack.ss_sp 14 | || oldStack.ss_size < sigaltstackSize) { 15 | newStack.ss_sp = calloc(1, sigaltstackSize); 16 | newStack.ss_size = sigaltstackSize; 17 | if (sigaltstack(&newStack, NULL) == -1) { 18 | free(newStack.ss_sp); 19 | } 20 | } 21 | } 22 | void signalPass(int code, siginfo_t *si, void *sc) { 23 | LOGE("监听到了native异常"); 24 | // 这里要考虑非信号方式防止死锁 25 | signal(code, SIG_DFL); 26 | signal(SIGALRM, SIG_DFL); 27 | (void) alarm(8); 28 | // 解析栈信息,回调给 java 层,上报到后台或者保存本地文件 29 | notifyCaughtSignal(code, si, sc); 30 | // 给系统原来默认的处理,否则就会进入死循环 31 | oldHandlers[code].sa_sigaction(code, si, sc); 32 | } 33 | 34 | /** 35 | * 安装信号捕获到native crash 36 | */ 37 | bool installSignalHandlers() { 38 | //保存原来的信号处理 39 | for (int i = 0; i < exceptionSignalsNumber; i++) { 40 | // signum:代表信号编码,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号,如果为这两个信号定义自己的处理函数,将导致信号安装错误。 41 | // act:指向结构体sigaction的一个实例的指针,该实例指定了对特定信号的处理,如果设置为空,进程会执行默认处理。 42 | // oldact:和参数act类似,只不过保存的是原来对相应信号的处理,也可设置为NULL。 43 | // int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)); 44 | if (sigaction(exceptionSignals[i], NULL, &oldHandlers[exceptionSignals[i]]) == -1) { 45 | return false; 46 | } 47 | } 48 | struct sigaction sa{}; 49 | memset(&sa, 0, sizeof(sa)); 50 | sigemptyset(&sa.sa_mask); 51 | //不同堆栈处理并且可将参数传递下去 52 | sa.sa_flags = SA_ONSTACK | SA_SIGINFO; 53 | // 指定信号处理的回调函数 54 | sa.sa_sigaction = signalPass; 55 | //处理当前信号量的时候不考虑其他的 56 | for (int i = 0; i < exceptionSignalsNumber; ++i) { 57 | //阻塞其他信号的 58 | sigaddset(&sa.sa_mask, exceptionSignals[i]); 59 | } 60 | for (int i = 0; i < exceptionSignalsNumber; ++i) { 61 | //处理自己的信号,如果成功返回0,失败返回-1 62 | if (sigaction(exceptionSignals[i], &sa, NULL) == -1) { 63 | // 可以输出一个警告 64 | } 65 | } 66 | return true; 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/cpp/src/crash/Utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/7/9. 3 | // 4 | 5 | #include "../../include/crash/Utils.h" 6 | #include "log.h" 7 | 8 | const char *desc_sig(int sig, int code) { 9 | switch (sig) { 10 | case SIGILL: 11 | switch (code) { 12 | case ILL_ILLOPC: 13 | return "Illegal opcode"; 14 | case ILL_ILLOPN: 15 | return "Illegal operand"; 16 | case ILL_ILLADR: 17 | return "Illegal addressing mode"; 18 | case ILL_ILLTRP: 19 | return "Illegal trap"; 20 | case ILL_PRVOPC: 21 | return "Privileged opcode"; 22 | case ILL_PRVREG: 23 | return "Privileged register"; 24 | case ILL_COPROC: 25 | return "Coprocessor error"; 26 | case ILL_BADSTK: 27 | return "Internal stack error"; 28 | default: 29 | return "Illegal operation"; 30 | } 31 | break; 32 | case SIGFPE: 33 | switch (code) { 34 | case FPE_INTDIV: 35 | return "Integer divide by zero"; 36 | case FPE_INTOVF: 37 | return "Integer overflow"; 38 | case FPE_FLTDIV: 39 | return "Floating-point divide by zero"; 40 | case FPE_FLTOVF: 41 | return "Floating-point overflow"; 42 | case FPE_FLTUND: 43 | return "Floating-point underflow"; 44 | case FPE_FLTRES: 45 | return "Floating-point inexact result"; 46 | case FPE_FLTINV: 47 | return "Invalid floating-point operation"; 48 | case FPE_FLTSUB: 49 | return "Subscript out of range"; 50 | default: 51 | return "Floating-point"; 52 | } 53 | break; 54 | case SIGSEGV: 55 | switch (code) { 56 | case SEGV_MAPERR: 57 | return "Address not mapped to object"; 58 | case SEGV_ACCERR: 59 | return "Invalid permissions for mapped object"; 60 | default: 61 | return "Segmentation violation"; 62 | } 63 | break; 64 | case SIGBUS: 65 | switch (code) { 66 | case BUS_ADRALN: 67 | return "Invalid address alignment"; 68 | case BUS_ADRERR: 69 | return "Nonexistent physical address"; 70 | case BUS_OBJERR: 71 | return "Object-specific hardware error"; 72 | default: 73 | return "Bus error"; 74 | } 75 | break; 76 | case SIGTRAP: 77 | switch (code) { 78 | case TRAP_BRKPT: 79 | return "Process breakpoint"; 80 | case TRAP_TRACE: 81 | return "Process trace trap"; 82 | default: 83 | return "Trap"; 84 | } 85 | break; 86 | case SIGCHLD: 87 | switch (code) { 88 | case CLD_EXITED: 89 | return "Child has exited"; 90 | case CLD_KILLED: 91 | return "Child has terminated abnormally and did not create a core file"; 92 | case CLD_DUMPED: 93 | return "Child has terminated abnormally and created a core file"; 94 | case CLD_TRAPPED: 95 | return "Traced child has trapped"; 96 | case CLD_STOPPED: 97 | return "Child has stopped"; 98 | case CLD_CONTINUED: 99 | return "Stopped child has continued"; 100 | default: 101 | return "Child"; 102 | } 103 | break; 104 | case SIGPOLL: 105 | switch (code) { 106 | case POLL_IN: 107 | return "Data input available"; 108 | case POLL_OUT: 109 | return "Output buffers available"; 110 | case POLL_MSG: 111 | return "Input message available"; 112 | case POLL_ERR: 113 | return "I/O error"; 114 | case POLL_PRI: 115 | return "High priority input available"; 116 | case POLL_HUP: 117 | return "Device disconnected"; 118 | default: 119 | return "Pool"; 120 | } 121 | break; 122 | case SIGABRT: 123 | return "Process abort signal"; 124 | case SIGALRM: 125 | return "Alarm clock"; 126 | case SIGCONT: 127 | return "Continue executing, if stopped"; 128 | case SIGHUP: 129 | return "Hangup"; 130 | case SIGINT: 131 | return "Terminal interrupt signal"; 132 | case SIGKILL: 133 | return "Kill"; 134 | case SIGPIPE: 135 | return "Write on a pipe with no one to read it"; 136 | case SIGQUIT: 137 | return "Terminal quit signal"; 138 | case SIGSTOP: 139 | return "Stop executing"; 140 | case SIGTERM: 141 | return "Termination signal"; 142 | case SIGTSTP: 143 | return "Terminal stop signal"; 144 | case SIGTTIN: 145 | return "Background process attempting read"; 146 | case SIGTTOU: 147 | return "Background process attempting write"; 148 | case SIGUSR1: 149 | return "User-defined signal 1"; 150 | case SIGUSR2: 151 | return "User-defined signal 2"; 152 | case SIGPROF: 153 | return "Profiling timer expired"; 154 | case SIGSYS: 155 | return "Bad system call"; 156 | case SIGVTALRM: 157 | return "Virtual timer expired"; 158 | case SIGURG: 159 | return "High bandwidth data is available at a socket"; 160 | case SIGXCPU: 161 | return "CPU time limit exceeded"; 162 | case SIGXFSZ: 163 | return "File size limit exceeded"; 164 | default: 165 | switch (code) { 166 | case SI_USER: 167 | return "Signal sent by kill()"; 168 | case SI_QUEUE: 169 | return "Signal sent by the sigqueue()"; 170 | case SI_TIMER: 171 | return "Signal generated by expiration of a timer set by timer_settime()"; 172 | case SI_ASYNCIO: 173 | return "Signal generated by completion of an asynchronous I/O request"; 174 | case SI_MESGQ: 175 | return 176 | "Signal generated by arrival of a message on an empty message queue"; 177 | default: 178 | return "Unknown signal"; 179 | } 180 | break; 181 | } 182 | } 183 | 184 | extern const char *getProcessName(pid_t pid) { 185 | // 读一个文件 186 | if (pid <= 1) { 187 | return NULL; 188 | } 189 | char *path = (char *) calloc(1, PATH_MAX); 190 | char *line = (char *) calloc(1, PROCESS_NAME_LENGTH); 191 | snprintf(path, PATH_MAX, "/proc/%d/cmdline", pid); 192 | FILE *cmdFile = NULL; 193 | if (cmdFile = fopen(path, "r")) { 194 | fgets(line, PROCESS_NAME_LENGTH, cmdFile); 195 | fclose(cmdFile); 196 | } 197 | LOGD("line: %s",line); 198 | if (line) { 199 | int length = strlen(line); 200 | if (line[length - 1] == '\n') { 201 | line[length - 1] = '\0'; 202 | } 203 | } 204 | LOGD("line: %s",line); 205 | free(path); 206 | return line; 207 | } 208 | 209 | extern const char *getThreadName(pid_t tid) { 210 | if (tid <= 1) { 211 | return NULL; 212 | } 213 | char *path = (char *) calloc(1, PATH_MAX); 214 | char *line = (char *) calloc(1, THREAD_NAME_LENGTH); 215 | snprintf(path, PATH_MAX, "/proc/%d/comm", tid); 216 | FILE *commFile = NULL; 217 | if (commFile = fopen(path, "r")) { 218 | fgets(line, THREAD_NAME_LENGTH, commFile); 219 | fclose(commFile); 220 | } 221 | if (line) { 222 | int length = strlen(line); 223 | if (line[length - 1] == '\n') { 224 | line[length - 1] = '\0'; 225 | } 226 | } 227 | free(path); 228 | return line; 229 | } 230 | 231 | bool is_dll(const char *name) { 232 | size_t i; 233 | for (int i = 0; name[i] != '\0'; ++i) { 234 | if (name[i + 0] == '.' && name[i + 1] == 's' && name[i + 2] == 'o' 235 | &&(name[i + 3] == '\0' || name[i + 3] == '.')) { 236 | return 1; 237 | } 238 | } 239 | return 0; 240 | } -------------------------------------------------------------------------------- /app/src/main/cpp/src/crash/stringprintf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "../include/crash/stringprintf.h" 18 | 19 | #include 20 | 21 | #include 22 | 23 | namespace android { 24 | namespace base { 25 | 26 | void StringAppendV(std::string* dst, const char* format, va_list ap) { 27 | // First try with a small fixed size buffer 28 | char space[1024]; 29 | 30 | // It's possible for methods that use a va_list to invalidate 31 | // the data in it upon use. The fix is to make a copy 32 | // of the structure before using it and use that copy instead. 33 | va_list backup_ap; 34 | va_copy(backup_ap, ap); 35 | int result = vsnprintf(space, sizeof(space), format, backup_ap); 36 | va_end(backup_ap); 37 | 38 | if (result < static_cast(sizeof(space))) { 39 | if (result >= 0) { 40 | // Normal case -- everything fit. 41 | dst->append(space, result); 42 | return; 43 | } 44 | 45 | if (result < 0) { 46 | // Just an error. 47 | return; 48 | } 49 | } 50 | 51 | // Increase the buffer size to the size requested by vsnprintf, 52 | // plus one for the closing \0. 53 | int length = result + 1; 54 | char* buf = new char[length]; 55 | 56 | // Restore the va_list before we use it again 57 | va_copy(backup_ap, ap); 58 | result = vsnprintf(buf, length, format, backup_ap); 59 | va_end(backup_ap); 60 | 61 | if (result >= 0 && result < length) { 62 | // It fit 63 | dst->append(buf, result); 64 | } 65 | delete[] buf; 66 | } 67 | 68 | std::string StringPrintf(const char* fmt, ...) { 69 | va_list ap; 70 | va_start(ap, fmt); 71 | std::string result; 72 | StringAppendV(&result, fmt, ap); 73 | va_end(ap); 74 | return result; 75 | } 76 | 77 | void StringAppendF(std::string* dst, const char* format, ...) { 78 | va_list ap; 79 | va_start(ap, format); 80 | StringAppendV(dst, format, ap); 81 | va_end(ap); 82 | } 83 | 84 | } // namespace base 85 | } // namespace android 86 | -------------------------------------------------------------------------------- /app/src/main/cpp/src/mmkv/MMKV.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by admin on 2021/8/24. 3 | // 4 | 5 | #include 6 | #include "../../include/mmkv/MMKV.h" 7 | 8 | int32_t DEFAULT_MMAP_SIZE = getpagesize(); 9 | 10 | void MMKV::initializeMMKV(const char *path) { 11 | g_rootDir = path; 12 | //创建文件夹 13 | mkdir(g_rootDir.c_str(), 0777); 14 | } 15 | 16 | MMKV::MMKV(const char *mmapID) { 17 | m_path = g_rootDir + "/" + mmapID; 18 | loadFromFile(); 19 | } 20 | 21 | MMKV *MMKV::defaultMMKV() { 22 | MMKV *kv = new MMKV(DEFAULT_MMAP_ID); 23 | return kv; 24 | } 25 | 26 | void MMKV::loadFromFile() { 27 | m_fd = open(m_path.c_str(), O_RDWR | O_CREAT, S_IRWXU); 28 | //获取文件的具体大小 29 | struct stat st = {0}; 30 | if (fstat(m_fd, &st) != -1) { 31 | m_size = st.st_size; 32 | } 33 | if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) { 34 | //调整为4k整数倍 35 | int32_t oldSize = m_size; 36 | //新的4k整数倍 37 | m_size = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE; 38 | if (ftruncate(m_fd, m_size) != 0) { 39 | m_size = st.st_size; 40 | } 41 | //如果文件大小被增加了, 让增加这些大小的内容变成空 42 | zeroFillFile(m_fd, oldSize, m_size - oldSize); 43 | } 44 | m_ptr = static_cast(mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 45 | 0)); 46 | //内容的总长度 47 | memcpy(&m_actualSize, m_ptr, 4); 48 | if (m_actualSize > 0) { 49 | ProtoBuf inputBuffer(m_ptr + 4, m_actualSize); 50 | //清空 51 | map.clear(); 52 | //已有的数据添加到Map 53 | while (!inputBuffer.isAtEnd()) { 54 | std::string key = inputBuffer.readString(); 55 | LOGE("key=%s ", key.c_str()); 56 | if (key.length() > 0) { 57 | ProtoBuf *value = inputBuffer.readData(); 58 | if (value && value->length() > 0) { 59 | map.emplace(key, value); 60 | } 61 | } 62 | } 63 | } 64 | m_output = new ProtoBuf(m_ptr + 4 + m_actualSize, 65 | m_size - 4 - m_actualSize); 66 | 67 | } 68 | 69 | void MMKV::zeroFillFile(int fd, int32_t startPos, int32_t size) { 70 | if (lseek(fd, startPos, SEEK_SET) < 0) { 71 | return; 72 | } 73 | 74 | 75 | static const char zeros[4096] = {0}; 76 | while (size >= sizeof(zeros)) { 77 | if (write(fd, zeros, sizeof(zeros)) < 0) { 78 | return; 79 | } 80 | size -= sizeof(zeros); 81 | } 82 | if (size > 0) { 83 | if (write(fd, zeros, size) < 0) { 84 | return; 85 | } 86 | } 87 | } 88 | 89 | void MMKV::putInt(const std::string &key, int32_t value) { 90 | //value需要几个字节 91 | int32_t size = ProtoBuf::computeInt32Size(value); 92 | ProtoBuf *buf = new ProtoBuf(size); 93 | buf->writeRawInt(value); 94 | map.emplace(key, buf); 95 | appendDataWithKey(key, buf); 96 | } 97 | 98 | void MMKV::putString(const std::string &key, const std::string &value) { 99 | int32_t size = value.length()+ProtoBuf::computeInt32Size(value.length()); 100 | ProtoBuf *buf = new ProtoBuf(size); 101 | buf->writeString(value); 102 | map.emplace(key, buf); 103 | appendDataWithKey(key, buf); 104 | } 105 | 106 | int32_t MMKV::getInt(std::string key, int32_t defaultValue) { 107 | auto itr = map.find(key); 108 | if (itr != map.end()) { 109 | ProtoBuf *buf = itr->second; 110 | int32_t returnValue = buf->readInt(); 111 | //多次读取,将position还原为0 112 | buf->restore(); 113 | return returnValue; 114 | } 115 | return defaultValue; 116 | } 117 | 118 | std::string MMKV::getString(std::string key) { 119 | auto itr = map.find(key); 120 | 121 | if (itr != map.end()) { 122 | ProtoBuf *buf = itr->second; 123 | std::string returnValue = buf->readString(); 124 | LOGE("%s",returnValue.c_str()); 125 | //多次读取,将position还原为0 126 | buf->restore(); 127 | return returnValue; 128 | } 129 | return ""; 130 | } 131 | 132 | void MMKV::appendDataWithKey(std::string key, ProtoBuf *value) { 133 | //待写入数据的大小 134 | int32_t itemSize = ProtoBuf::computeItemSize(key, value); 135 | if (itemSize > m_output->spaceLeft()) { 136 | //内存不够 137 | //计算map的大小 138 | int32_t needSize = ProtoBuf::computeMapSize(map); 139 | //加上总长度 140 | needSize += 4; 141 | //扩容的大小 142 | //计算每个item的平均长度 143 | int32_t avgItemSize = needSize / std::max(1, map.size()); 144 | int32_t futureUsage = avgItemSize * std::max(8, (map.size() + 1) / 2); 145 | if (needSize + futureUsage >= m_size) { 146 | int32_t oldSize = m_size; 147 | //如果在需要的与将来可能增加的加起来比扩容后还要大,继续扩容 148 | do { 149 | //扩充一倍 150 | m_size *= 2; 151 | 152 | } while (needSize + futureUsage >= m_size); 153 | //重新设定文件大小 154 | ftruncate(m_fd, m_size); 155 | zeroFillFile(m_fd, oldSize, m_size - oldSize); 156 | //解除映射 157 | munmap(m_ptr, oldSize); 158 | //重新映射 159 | m_ptr = (int8_t *) mmap(m_ptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); 160 | } 161 | m_actualSize = needSize - 4; 162 | memcpy(m_ptr, &m_actualSize, 4); 163 | LOGE("extending full write"); 164 | delete m_output; 165 | m_output = new ProtoBuf(m_ptr + 4, 166 | m_size - 4); 167 | auto iter = map.begin(); 168 | for (; iter != map.end(); iter++) { 169 | auto k = iter->first; 170 | auto v = iter->second; 171 | m_output->writeString(k); 172 | m_output->writeData(v); 173 | } 174 | } else { 175 | //内存够 176 | m_actualSize += itemSize; 177 | memcpy(m_ptr, &m_actualSize, 4); 178 | m_output->writeString(key); 179 | m_output->writeData(value); 180 | } 181 | 182 | } 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /app/src/main/cpp/src/mmkv/ProtoBuf.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../../include/mmkv/ProtoBuf.h" 3 | 4 | 5 | ProtoBuf::ProtoBuf(int8_t *buf, int32_t size, bool isCopy) { 6 | m_size = size; 7 | m_position = 0; 8 | m_buf = buf; 9 | m_isCopy = isCopy; 10 | if (isCopy) { 11 | m_buf = static_cast(malloc(size)); 12 | memcpy(m_buf, buf, size); 13 | } 14 | } 15 | 16 | ProtoBuf::ProtoBuf(int32_t size) { 17 | m_position = 0; 18 | m_size = size; 19 | m_isCopy = true; 20 | if (size > 0) { 21 | m_buf = static_cast(malloc(size)); 22 | } 23 | } 24 | 25 | 26 | ProtoBuf::~ProtoBuf() { 27 | if (m_isCopy && m_buf) { 28 | free(m_buf); 29 | } 30 | m_buf = NULL; 31 | } 32 | 33 | int32_t ProtoBuf::computeInt32Size(int32_t value) { 34 | //0xffffffff 表示 uint 最大值 35 | //<< 7 则低7位变成0 与上value 36 | //如果value只要7位就够了则=0,编码只需要一个字节,否则进入其他判断 37 | if ((value & (0xffffffff << 7)) == 0) { 38 | return 1; 39 | } else if ((value & (0xffffffff << 14)) == 0) { 40 | return 2; 41 | } else if ((value & (0xffffffff << 21)) == 0) { 42 | return 3; 43 | } else if ((value & (0xffffffff << 28)) == 0) { 44 | return 4; 45 | } 46 | return 5; 47 | } 48 | 49 | int32_t ProtoBuf::computeItemSize(std::string key, ProtoBuf *value) { 50 | int32_t keyLength = key.length(); 51 | // 保存key的长度与key数据需要的字节 52 | int32_t size = keyLength + ProtoBuf::computeInt32Size(keyLength); 53 | // 加上保存value的长度与value数据需要的字节 54 | size += value->length() + ProtoBuf::computeInt32Size(value->length()); 55 | return size; 56 | } 57 | 58 | 59 | int32_t ProtoBuf::computeMapSize(std::unordered_map map) { 60 | auto iter = map.begin(); 61 | int32_t size = 0; 62 | for (; iter != map.end(); iter++) { 63 | auto key = iter->first; 64 | ProtoBuf* value = iter->second; 65 | size += computeItemSize(key, value); 66 | } 67 | return size; 68 | } 69 | std::string ProtoBuf::readString() { 70 | //获得字符串长度 71 | int32_t size = readInt(); 72 | //剩下的数据有这么多 73 | if (size <= (m_size - m_position) && size > 0) { 74 | std::string result((char *) m_buf + m_position, size); 75 | m_position += size; 76 | return result; 77 | } 78 | return ""; 79 | } 80 | 81 | int32_t ProtoBuf::readInt() { 82 | //todo 不能是负数 uint8_t 83 | uint8_t tmp = readByte(); 84 | //最高1位为0 这个字节是一个有效int。 85 | if ((tmp >> 7) == 0) { 86 | return tmp; 87 | } 88 | //获得低7位数据 89 | int32_t result = tmp & 0x7f; 90 | int32_t i = 1; 91 | do { 92 | //再读一个字节 93 | tmp = readByte(); 94 | if (tmp < 0x80) { 95 | //读取后一个字节左移7位再拼上前一个数据的低7位 96 | result |= tmp << (7 * i); 97 | } else { 98 | result |= (tmp & 0x7f) << (7 * i); 99 | } 100 | i++; 101 | } while (tmp >= 0x80); 102 | return result; 103 | } 104 | 105 | 106 | int8_t ProtoBuf::readByte() { 107 | if (m_position == m_size) { 108 | return 0; 109 | } 110 | return m_buf[m_position++]; 111 | } 112 | 113 | 114 | ProtoBuf *ProtoBuf::readData() { 115 | //获得数据长度 116 | int32_t size = readInt(); 117 | //有效 118 | if (size <= m_size - m_position && size > 0) { 119 | ProtoBuf *data = new ProtoBuf(m_buf + m_position, size, true); 120 | m_position += size; 121 | return data; 122 | } 123 | return NULL; 124 | } 125 | 126 | 127 | void ProtoBuf::writeByte(int8_t value) { 128 | if (m_position == m_size) { 129 | //满啦,出错啦 130 | return; 131 | } 132 | //将byte放入数组 133 | m_buf[m_position++] = value; 134 | } 135 | 136 | void ProtoBuf::writeRawInt(int32_t value) { 137 | while (true) { 138 | //每次处理7位数据,如果写入的数据 <= 0x7f(7位都是1)那么使用7位就可以表示了 139 | if (value <= 0x7f) { 140 | writeByte(value); 141 | return; 142 | } else { 143 | //大于7位,则先记录低7位,并且将最高位置为1 144 | //1、& 0x7F 获得低7位数据 145 | //2、| 0x80 让最高位变成1,表示超过1个字节记录整个数据 146 | writeByte((value & 0x7F) | 0x80); 147 | //7位已经写完了,处理更高位的数据 148 | value >>= 7; 149 | } 150 | } 151 | } 152 | 153 | void ProtoBuf::writeString(std::string value) { 154 | size_t numberOfBytes = value.size(); 155 | writeRawInt(numberOfBytes); 156 | memcpy(m_buf + m_position, value.data(), numberOfBytes); 157 | m_position += numberOfBytes; 158 | } 159 | 160 | void ProtoBuf::writeData(ProtoBuf *data) { 161 | writeRawInt(data->length()); 162 | 163 | size_t numberOfBytes = data->length(); 164 | memcpy(m_buf + m_position, data->getBuf(), numberOfBytes); 165 | m_position += numberOfBytes; 166 | } 167 | 168 | void ProtoBuf::restore() { 169 | m_position = 0; 170 | } 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/App.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio 2 | 3 | import android.app.Application 4 | import android.util.Log 5 | import com.peakmain.ui.utils.LogUtils 6 | import com.peakmain.video_audio.utils.CrashUtils 7 | import com.peakmain.video_audio.utils.crash.callback.CrashListener 8 | import java.lang.Error 9 | 10 | /** 11 | * author :Peakmain 12 | * createTime:2021/8/21 13 | * mail:2726449200@qq.com 14 | * describe: 15 | */ 16 | 17 | class App : Application() { 18 | override fun onCreate() { 19 | super.onCreate() 20 | CrashUtils.init(CrashListener { threadName, error -> 21 | Log.e("Video_Audio","threadName:$threadName\nerror info : $error") 22 | }) 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio 2 | 3 | import android.Manifest 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import com.peakmain.ui.recyclerview.listener.OnItemClickListener 8 | import com.peakmain.video_audio.activity.* 9 | import com.peakmain.video_audio.basic.BaseActivity 10 | import com.peakmain.video_audio.basic.BaseRecyclerStringAdapter 11 | import kotlinx.android.synthetic.main.activity_main.* 12 | 13 | class MainActivity : BaseActivity() { 14 | 15 | private var mData: ArrayList = ArrayList() 16 | override fun getLayoutId(): Int { 17 | return R.layout.activity_main 18 | } 19 | 20 | override fun initView() { 21 | mNavigationBuilder!!.setTitleText("首页").create() 22 | checkPermission() 23 | } 24 | 25 | fun checkPermission(): Boolean { 26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission( 27 | Manifest.permission.WRITE_EXTERNAL_STORAGE 28 | ) != PackageManager.PERMISSION_GRANTED 29 | ) { 30 | requestPermissions( 31 | arrayOf( 32 | Manifest.permission.READ_EXTERNAL_STORAGE, 33 | Manifest.permission.WRITE_EXTERNAL_STORAGE 34 | ), 1 35 | ) 36 | } 37 | return false 38 | } 39 | 40 | override fun initData() { 41 | mData.add("simple的使用") 42 | mData.add("H265实现手机投屏(接收端)") 43 | mData.add("H265实现手机投屏(发送端)") 44 | mData.add("H265音视频通话(发送端)") 45 | mData.add("H265音视频通话(接受端)") 46 | val adapter = BaseRecyclerStringAdapter(this, data = mData) 47 | recycler_view.adapter = adapter 48 | adapter.setOnItemClickListener(object : OnItemClickListener { 49 | override fun onItemClick(position: Int) { 50 | when (position) { 51 | 0 -> { 52 | startActivity(Intent(this@MainActivity, SimpleActivity::class.java)) 53 | } 54 | 1 -> { 55 | startActivity( 56 | Intent( 57 | this@MainActivity, 58 | ProjectScreenAcceptActivity::class.java 59 | ) 60 | ) 61 | } 62 | 2 -> { 63 | startActivity( 64 | Intent( 65 | this@MainActivity, 66 | ProjectScreenSendActivity::class.java 67 | ) 68 | ) 69 | } 70 | 3 -> { 71 | startActivity( 72 | Intent( 73 | this@MainActivity, 74 | VideoSendVideoCallActivity::class.java 75 | ) 76 | ) 77 | } 78 | 4 -> { 79 | startActivity( 80 | Intent( 81 | this@MainActivity, 82 | VideoAcceptVideoCallActivity::class.java 83 | ) 84 | ) 85 | } 86 | } 87 | } 88 | 89 | }) 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/Camera2XActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.graphics.SurfaceTexture 4 | import android.media.MediaCodec 5 | import android.media.MediaCodecInfo 6 | import android.media.MediaFormat 7 | import android.util.Size 8 | import android.view.TextureView 9 | import android.view.View 10 | import com.peakmain.ui.constants.PermissionConstants 11 | import com.peakmain.ui.utils.PermissionUtils 12 | import com.peakmain.video_audio.R 13 | import com.peakmain.video_audio.basic.BaseActivity 14 | import com.peakmain.video_audio.utils.Camera2Helper 15 | import com.peakmain.video_audio.utils.CameraXHelper 16 | import com.peakmain.video_audio.utils.FileUtils 17 | import kotlinx.android.synthetic.main.activity_camera2x.* 18 | 19 | /** 20 | * author :Peakmain 21 | * createTime:2021/8/27 22 | * mail:2726449200@qq.com 23 | * describe: 24 | */ 25 | class Camera2XActivity : BaseActivity(), TextureView.SurfaceTextureListener { 26 | lateinit var camera2Helper: Camera2Helper 27 | lateinit var cameraXHelper: CameraXHelper 28 | override fun getLayoutId(): Int { 29 | return R.layout.activity_camera2x 30 | } 31 | 32 | private var mediaCodec: MediaCodec? = null 33 | override fun initView() { 34 | mNavigationBuilder?.setTitleText("Camera使用")?.create() 35 | if (!PermissionUtils.hasPermission(PermissionConstants.CAMERA)) { 36 | PermissionUtils.request(this, 37 | 100, 38 | PermissionConstants.getPermissions(PermissionConstants.CAMERA), 39 | object : PermissionUtils.OnPermissionListener { 40 | override fun onPermissionDenied(deniedPermissions: List) { 41 | 42 | } 43 | 44 | override fun onPermissionGranted() { 45 | 46 | } 47 | 48 | } 49 | ) 50 | } 51 | texture_preview.surfaceTextureListener = this 52 | } 53 | 54 | override fun initData() { 55 | } 56 | 57 | 58 | override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) { 59 | 60 | } 61 | 62 | override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) { 63 | } 64 | 65 | override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean = false 66 | 67 | override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { 68 | // 打开摄像头 69 | initCamera() 70 | } 71 | 72 | private fun initCamera() { 73 | /* camera2Helper = Camera2Helper(this) 74 | camera2Helper.start(texture_preview, 0)*/ 75 | cameraXHelper = CameraXHelper(this, texture_preview) 76 | cameraXHelper.setCameraXListener { y, u, v, size, stride -> 77 | onPreview(y, u, v, size, stride) 78 | } 79 | } 80 | 81 | private var nv21 82 | : ByteArray? = null 83 | private var nv21_rotated: ByteArray? = null 84 | private var nv12: ByteArray? = null 85 | private fun onPreview( 86 | y: ByteArray?, 87 | u: ByteArray?, 88 | v: ByteArray?, 89 | previewSize: Size, 90 | stride: Int 91 | ) { 92 | if (nv21 == null) { 93 | nv21 = ByteArray(stride * previewSize.height * 3 / 2) 94 | nv21_rotated = ByteArray(stride * previewSize.height * 3 / 2) 95 | } 96 | if (mediaCodec == null) { 97 | initCodec(previewSize) 98 | } 99 | //生成nv21 100 | FileUtils.yuvToNv21(y, u, v, nv21, stride, previewSize.height) 101 | //生成的数据旋转90度 102 | FileUtils.nv21RotateTo90(nv21, nv21_rotated, stride, previewSize.height) 103 | //nv21->nv12 104 | val temp: ByteArray = FileUtils.nv21toNV12(nv21_rotated) 105 | //输出成H264码流 106 | val bufferInfo = MediaCodec.BufferInfo() 107 | val inIndex = mediaCodec!!.dequeueInputBuffer(1000) 108 | //通知dsp去解析 109 | if (inIndex >= 0) { 110 | val byteBuffer = mediaCodec!!.getInputBuffer(inIndex) 111 | byteBuffer.clear() 112 | byteBuffer.put(temp, 0, temp.size) 113 | mediaCodec?.queueInputBuffer(inIndex, 0, temp.size, 0, 0) 114 | } 115 | //取出 116 | val outIndex = mediaCodec!!.dequeueOutputBuffer(bufferInfo, 1000) 117 | if (outIndex >= 0) { 118 | val outputBuffer = mediaCodec!!.getOutputBuffer(outIndex) 119 | val ba = ByteArray(outputBuffer.remaining()) 120 | outputBuffer[ba] 121 | FileUtils.writeContent(ba, "Camera.txt") 122 | FileUtils.writeBytes(ba, "Camera.h264") 123 | mediaCodec?.releaseOutputBuffer(outIndex, false) 124 | } 125 | } 126 | 127 | //初始化编码器 128 | private fun initCodec(previewSize: Size) { 129 | try { 130 | mediaCodec = MediaCodec.createEncoderByType("video/avc") 131 | val format = MediaFormat.createVideoFormat( 132 | "video/avc", 133 | previewSize.height, previewSize.width 134 | ) 135 | //设置帧率 136 | format.setInteger( 137 | MediaFormat.KEY_COLOR_FORMAT, 138 | MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible 139 | ) 140 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 15) 141 | format.setInteger(MediaFormat.KEY_BIT_RATE, 400_000)//越高字节越长,视频越清晰 142 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2) //2s一个I帧 143 | mediaCodec?.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) 144 | mediaCodec?.start() 145 | } catch (e: Exception) { 146 | e.printStackTrace() 147 | } 148 | } 149 | 150 | //———————————————————————————————————————————————————————————————————————————————————————————点击事件———————————————————————————————————————————————————————————————————————————— ——————————————— 151 | fun camera2Click(view: View) { 152 | camera2Helper.setCamera2Listener { y, u, v, size, stride -> 153 | onPreview(y, u, v, size, stride) 154 | } 155 | } 156 | 157 | fun cameraClick(view: View) { 158 | surface.startCaptrue() 159 | } 160 | 161 | fun cameraXClick(view: View) { 162 | texture_preview.post{ 163 | cameraXHelper.startCamera() 164 | } 165 | } 166 | 167 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/CameraActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.media.MediaCodec 4 | import android.view.View 5 | import com.peakmain.video_audio.R 6 | import com.peakmain.video_audio.basic.BaseActivity 7 | import com.peakmain.video_audio.utils.CameraHelper 8 | import com.peakmain.video_audio.utils.FileUtils 9 | import kotlinx.android.synthetic.main.activity_camera.* 10 | 11 | class CameraActivity : BaseActivity() { 12 | lateinit var mCameraHelper: CameraHelper 13 | 14 | override fun getLayoutId(): Int { 15 | return R.layout.activity_camera 16 | } 17 | 18 | override fun initView() { 19 | mCameraHelper = CameraHelper(surface_view) 20 | 21 | mCameraHelper.setCameraListener { data, size -> 22 | val mediaCodec = mCameraHelper.getMediaCodec() 23 | val info = MediaCodec.BufferInfo() 24 | //yuv数据需要去解析 25 | val inIndex = mediaCodec.dequeueInputBuffer(10000) 26 | if (inIndex >= 0) { 27 | val byteBuffer = mediaCodec.getInputBuffer(inIndex) 28 | byteBuffer.clear() 29 | byteBuffer.put(data, 0, data!!.size) 30 | mediaCodec.queueInputBuffer(inIndex, 0, data.size, 0, 0) 31 | } 32 | val outIndex = mediaCodec.dequeueOutputBuffer(info, 10000) 33 | if (outIndex >= 0) { 34 | val byteBuffer = mediaCodec.getOutputBuffer(outIndex) 35 | val ba = ByteArray(byteBuffer.remaining()) 36 | byteBuffer[ba] 37 | FileUtils.writeBytes(ba, "Camera.h264") 38 | mediaCodec.releaseOutputBuffer(outIndex,false) 39 | } 40 | } 41 | } 42 | 43 | 44 | 45 | override fun initData() { 46 | } 47 | 48 | fun cameraClick(view: View) { 49 | mCameraHelper.start() 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/MMKVActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.view.View 4 | import com.peakmain.ui.utils.LogUtils 5 | import com.peakmain.ui.utils.ToastUtils 6 | import com.peakmain.video_audio.R 7 | import com.peakmain.video_audio.basic.BaseActivity 8 | import com.peakmain.video_audio.simple.mmkv.MMKV 9 | 10 | /** 11 | * author :Peakmain 12 | * createTime:2021/8/24 13 | * mail:2726449200@qq.com 14 | * describe: 15 | */ 16 | class MMKVActivity : BaseActivity() { 17 | private var mmkv: MMKV? = null 18 | override fun getLayoutId(): Int { 19 | return R.layout.activity_mmkv 20 | } 21 | 22 | override fun initView() { 23 | mNavigationBuilder!!.setTitleText("MMKV的读写").create() 24 | MMKV.init(this) 25 | mmkv = MMKV.defaultMMKV() 26 | } 27 | 28 | override fun initData() { 29 | } 30 | 31 | fun readClick(view: View) { 32 | ToastUtils.showLong(mmkv!!.getString("name1")) 33 | } 34 | fun writeClick(view: View) { 35 | val start = System.currentTimeMillis() 36 | for (i in 0..2) { 37 | mmkv?.putString("name$i", "peakmain$i") 38 | } 39 | 40 | 41 | val time = System.currentTimeMillis() - start 42 | LogUtils.e("write: 时间花销 $time") 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/ProjectScreenAcceptActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import android.view.Surface 6 | import android.view.SurfaceHolder 7 | import com.peakmain.ui.utils.LogUtils 8 | import com.peakmain.ui.utils.SizeUtils 9 | import com.peakmain.video_audio.R 10 | import com.peakmain.video_audio.basic.BaseActivity 11 | import com.peakmain.video_audio.simple.BasicSurfaceHolderCallback 12 | import com.peakmain.video_audio.utils.socket.SocketAcceptLive 13 | import kotlinx.android.synthetic.main.activity_project_screen.* 14 | 15 | /** 16 | * author :Peakmain 17 | * createTime:2021/8/31 18 | * mail:2726449200@qq.com 19 | * describe:接收端 20 | */ 21 | class ProjectScreenAcceptActivity : BaseActivity(), SocketAcceptLive.SocketCallback { 22 | var mSurface: Surface? = null 23 | lateinit var mMediaCodec: MediaCodec 24 | private val mWidth: Int = SizeUtils.screenWidth 25 | private val mHeight: Int = SizeUtils.screenHeight 26 | override fun getLayoutId(): Int { 27 | return R.layout.activity_project_screen 28 | } 29 | 30 | override fun initView() { 31 | mNavigationBuilder?.setTitleText("H265实现手机投屏(接收端)")?.create() 32 | surface_view.holder.addCallback(object : BasicSurfaceHolderCallback() { 33 | override fun surfaceCreated(holder: SurfaceHolder?) { 34 | mSurface = holder?.surface 35 | initSocket() 36 | initDecoder(mSurface) 37 | } 38 | }) 39 | } 40 | 41 | //初始化解码器 42 | private fun initDecoder(surface: Surface?) { 43 | mMediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC) 44 | val format = 45 | MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, mWidth, mHeight) 46 | format.setInteger(MediaFormat.KEY_BIT_RATE, mWidth * mHeight) 47 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 20) 48 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) 49 | mMediaCodec.configure( 50 | format, 51 | surface, 52 | null, 0 53 | ) 54 | mMediaCodec.start() 55 | } 56 | 57 | private fun initSocket() { 58 | val screenLive = SocketAcceptLive(this) 59 | screenLive.start() 60 | } 61 | 62 | override fun initData() { 63 | } 64 | 65 | override fun callBack(data: ByteArray?) { 66 | //回调 67 | LogUtils.e("接收到数据的长度:${data?.size}") 68 | //客户端主要将获取到的数据进行解码,首先需要通过dsp进行解码 69 | val index = mMediaCodec.dequeueInputBuffer(10000) 70 | if (index >= 0) { 71 | val inputBuffer = mMediaCodec.getInputBuffer(index) 72 | inputBuffer.clear() 73 | inputBuffer.put(data, 0, data!!.size) 74 | //通知dsp芯片帮忙解码 75 | mMediaCodec.queueInputBuffer(index, 0, data.size, System.currentTimeMillis(), 0) 76 | } 77 | //取出数据 78 | val bufferInfo = MediaCodec.BufferInfo() 79 | var outIndex: Int = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000) 80 | while (outIndex > 0) { 81 | mMediaCodec.releaseOutputBuffer(outIndex, true) 82 | outIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000) 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/ProjectScreenSendActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.media.MediaCodec 7 | import android.media.MediaFormat 8 | import android.media.projection.MediaProjection 9 | import android.media.projection.MediaProjectionManager 10 | import android.view.Surface 11 | import android.view.SurfaceHolder 12 | import android.view.View 13 | import com.peakmain.ui.utils.LogUtils 14 | import com.peakmain.ui.utils.SizeUtils 15 | import com.peakmain.video_audio.R 16 | import com.peakmain.video_audio.basic.BaseActivity 17 | import com.peakmain.video_audio.simple.BasicSurfaceHolderCallback 18 | import com.peakmain.video_audio.utils.socket.SocketAcceptLive 19 | import com.peakmain.video_audio.utils.socket.WebSocketSendLive 20 | import kotlinx.android.synthetic.main.activity_project_screen.* 21 | 22 | /** 23 | * author :Peakmain 24 | * createTime:2021/8/31 25 | * mail:2726449200@qq.com 26 | * describe: 27 | */ 28 | class ProjectScreenSendActivity : BaseActivity() { 29 | lateinit var mMediaProjectionManager: MediaProjectionManager 30 | private lateinit var mWebSocketLive: WebSocketSendLive 31 | override fun getLayoutId(): Int { 32 | return R.layout.activity_project_screen 33 | } 34 | 35 | override fun initView() { 36 | mMediaProjectionManager = 37 | getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager 38 | val captureIntent: Intent = mMediaProjectionManager.createScreenCaptureIntent() 39 | startActivityForResult(captureIntent, 100) 40 | } 41 | 42 | 43 | override fun initData() { 44 | mNavigationBuilder?.setTitleText("H265实现手机投屏(发送端)")?.create() 45 | } 46 | 47 | 48 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 49 | super.onActivityResult(requestCode, resultCode, data) 50 | if (resultCode == Activity.RESULT_OK && requestCode == 100) { 51 | val mediaProjection: MediaProjection = 52 | mMediaProjectionManager.getMediaProjection(resultCode, data) 53 | ?: return 54 | mWebSocketLive = WebSocketSendLive() 55 | mWebSocketLive.start(mediaProjection) 56 | } 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/SimpleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.hardware.display.DisplayManager 7 | import android.media.MediaCodec 8 | import android.media.MediaCodecInfo 9 | import android.media.MediaFormat 10 | import android.media.projection.MediaProjection 11 | import android.media.projection.MediaProjectionManager 12 | import android.os.Handler 13 | import android.os.HandlerThread 14 | import com.peakmain.ui.recyclerview.listener.OnItemClickListener 15 | import com.peakmain.ui.utils.SizeUtils 16 | import com.peakmain.video_audio.R 17 | import com.peakmain.video_audio.activity.simple.H264PlayerActivity 18 | import com.peakmain.video_audio.basic.BaseActivity 19 | import com.peakmain.video_audio.basic.BaseRecyclerStringAdapter 20 | import com.peakmain.video_audio.utils.FileUtils 21 | import kotlinx.android.synthetic.main.activity_main.* 22 | 23 | /** 24 | * author :Peakmain 25 | * createTime:2021/8/22 26 | * mail:2726449200@qq.com 27 | * describe: 28 | */ 29 | class SimpleActivity : BaseActivity() { 30 | private var mData: ArrayList = ArrayList() 31 | 32 | //屏幕录制的请求码 33 | private val screenRequestCode = 1010 34 | private lateinit var mMediaProjection: MediaProjection 35 | private lateinit var mMediaProjectionManager: MediaProjectionManager 36 | private lateinit var mMediaCodec: MediaCodec 37 | private lateinit var mHandlerThread: HandlerThread 38 | private lateinit var mHandler: Handler 39 | override fun getLayoutId(): Int { 40 | return R.layout.activity_main 41 | } 42 | 43 | override fun initView() { 44 | mNavigationBuilder!!.setTitleText("音视频简单使用").create() 45 | 46 | mMediaProjectionManager = 47 | getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager 48 | mHandlerThread = HandlerThread("Screen_Record") 49 | mHandlerThread.start() 50 | } 51 | 52 | override fun initData() { 53 | mData.add("MediaCodec实现H264音视频播放") 54 | mData.add("MediaCodec实现H264音视频转成图片(单张)") 55 | mData.add("MMKV的读写") 56 | mData.add("MediaCodec实现屏幕录制") 57 | mData.add("Camera实现预览编码") 58 | mData.add("CameraX和Camera2实现预览编码") 59 | val adapter = BaseRecyclerStringAdapter(this, data = mData) 60 | recycler_view.adapter = adapter 61 | adapter.setOnItemClickListener(object : OnItemClickListener { 62 | override fun onItemClick(position: Int) { 63 | when (position) { 64 | 0 -> { 65 | val intent = Intent(this@SimpleActivity, H264PlayerActivity::class.java) 66 | intent.putExtra("isPrintImage", false) 67 | startActivity(intent) 68 | } 69 | 1 -> { 70 | val intent = Intent(this@SimpleActivity, H264PlayerActivity::class.java) 71 | intent.putExtra("isPrintImage", true) 72 | startActivity(intent) 73 | } 74 | 2 -> { 75 | val intent = Intent(this@SimpleActivity, MMKVActivity::class.java) 76 | startActivity(intent) 77 | } 78 | 3 -> { 79 | val intent: Intent = mMediaProjectionManager.createScreenCaptureIntent() 80 | startActivityForResult(intent, screenRequestCode) 81 | } 82 | 4->{ 83 | val intent = Intent(this@SimpleActivity, CameraActivity::class.java) 84 | startActivity(intent) 85 | } 86 | 5->{ 87 | val intent = Intent(this@SimpleActivity, Camera2XActivity::class.java) 88 | startActivity(intent) 89 | } 90 | } 91 | } 92 | 93 | }) 94 | } 95 | 96 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 97 | super.onActivityResult(requestCode, resultCode, data) 98 | if (requestCode == screenRequestCode && resultCode == Activity.RESULT_OK) { 99 | mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data) 100 | initMediaCodec() 101 | } 102 | } 103 | 104 | //初始化编码器 105 | private fun initMediaCodec() { 106 | mMediaCodec = MediaCodec.createEncoderByType("video/avc") 107 | //配置信息 108 | val format = MediaFormat.createVideoFormat( 109 | "video/avc", 110 | SizeUtils.screenWidth, 111 | SizeUtils.screenHeight 112 | ) 113 | format.setInteger( 114 | MediaFormat.KEY_COLOR_FORMAT, 115 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface 116 | ) 117 | 118 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 15) 119 | //码率,码率越高,字节越长,视频就越清晰 120 | format.setInteger(MediaFormat.KEY_BIT_RATE, 400_000) 121 | //2s一个I帧 122 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2) 123 | //CONFIGURE_FLAG_ENCODE:表示编码 124 | mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) 125 | //创建一个场地 126 | val surface = mMediaCodec.createInputSurface() 127 | mHandler = Handler(mHandlerThread.looper) 128 | mHandler.post { 129 | mMediaCodec.start() 130 | mMediaProjection.createVirtualDisplay( 131 | "screen_codec", 132 | SizeUtils.screenWidth, 133 | SizeUtils.screenHeight, 134 | 1, 135 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, 136 | surface, 137 | null, null 138 | ) 139 | //此时有了源数据,就可以取出数据进行编码 140 | val bufferInfo = MediaCodec.BufferInfo() 141 | while (true) { 142 | val outIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000) 143 | if (outIndex >= 0) { 144 | //这是dsp的数据,无法直接使用 145 | val byteBuffer = mMediaCodec.getOutputBuffer(outIndex) 146 | val outData = ByteArray(bufferInfo.size) 147 | byteBuffer.get(outData) 148 | FileUtils.writeBytes(outData, "codec.h264") 149 | FileUtils.writeContent(outData, "codec.txt") 150 | mMediaCodec.releaseOutputBuffer(outIndex, false) 151 | } 152 | } 153 | } 154 | 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/VideoAcceptVideoCallActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.view.Surface 4 | import android.view.SurfaceHolder 5 | import android.view.View 6 | import com.peakmain.video_audio.R 7 | import com.peakmain.video_audio.basic.BaseActivity 8 | import com.peakmain.video_audio.simple.BasicSurfaceHolderCallback 9 | import com.peakmain.video_audio.utils.socket.SocketAcceptLive 10 | import com.peakmain.video_audio.video.DecodecVideoCallLiveH265 11 | import kotlinx.android.synthetic.main.activity_accept_video_call.* 12 | 13 | /** 14 | * author :Peakmain 15 | * createTime:2021/9/2 16 | * mail:2726449200@qq.com 17 | * describe: 18 | */ 19 | class VideoAcceptVideoCallActivity : BaseActivity(), SocketAcceptLive.SocketCallback { 20 | var surface: Surface? = null 21 | var decodecPlayerLiveH265: DecodecVideoCallLiveH265? = null 22 | override fun getLayoutId(): Int { 23 | return R.layout.activity_accept_video_call 24 | } 25 | 26 | override fun initView() { 27 | surface_view.holder.addCallback(object:BasicSurfaceHolderCallback(){ 28 | override fun surfaceCreated(holder: SurfaceHolder?) { 29 | surface = holder?.surface 30 | decodecPlayerLiveH265 = 31 | DecodecVideoCallLiveH265() 32 | decodecPlayerLiveH265!!.initDecoder(surface) 33 | } 34 | }) 35 | } 36 | 37 | override fun initData() { 38 | } 39 | 40 | fun connect(view: View) { 41 | videoCallAcceptSurfaceView.startCaptrue (this) 42 | } 43 | 44 | override fun callBack(data: ByteArray?) { 45 | if (decodecPlayerLiveH265 != null) { 46 | decodecPlayerLiveH265?.callBack(data) 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/VideoSendVideoCallActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity 2 | 3 | import android.view.Surface 4 | import android.view.SurfaceHolder 5 | import android.view.View 6 | import com.peakmain.video_audio.R 7 | import com.peakmain.video_audio.basic.BaseActivity 8 | import com.peakmain.video_audio.simple.BasicSurfaceHolderCallback 9 | import com.peakmain.video_audio.utils.socket.WebSocketSendLive 10 | import com.peakmain.video_audio.video.DecodecVideoCallLiveH265 11 | import kotlinx.android.synthetic.main.activity_send_video_call.* 12 | 13 | 14 | /** 15 | * author :Peakmain 16 | * createTime:2021/9/2 17 | * mail:2726449200@qq.com 18 | * describe: 19 | */ 20 | class VideoSendVideoCallActivity : BaseActivity(), WebSocketSendLive.SocketCallback { 21 | var surface: Surface? = null 22 | var decodecPlayerLiveH265: DecodecVideoCallLiveH265? = null 23 | override fun getLayoutId(): Int { 24 | return R.layout.activity_send_video_call 25 | } 26 | 27 | override fun initView() { 28 | surface_view.holder.addCallback(object: BasicSurfaceHolderCallback(){ 29 | override fun surfaceCreated(holder: SurfaceHolder?) { 30 | surface = holder?.surface 31 | decodecPlayerLiveH265 = 32 | DecodecVideoCallLiveH265() 33 | decodecPlayerLiveH265!!.initDecoder(surface) 34 | } 35 | }) 36 | } 37 | 38 | override fun initData() { 39 | } 40 | 41 | fun connect(view: View) { 42 | video_call_send_surface_view.startCaptrue (this) 43 | } 44 | 45 | override fun callBack(data: ByteArray?) { 46 | if (decodecPlayerLiveH265 != null) { 47 | decodecPlayerLiveH265?.callBack(data) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/activity/simple/H264PlayerActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.activity.simple 2 | 3 | import android.Manifest 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.os.Environment 7 | import android.view.SurfaceHolder 8 | import android.view.SurfaceView 9 | import android.view.View 10 | import com.peakmain.video_audio.R 11 | import com.peakmain.video_audio.basic.BaseActivity 12 | import com.peakmain.video_audio.simple.BasicSurfaceHolderCallback 13 | import com.peakmain.video_audio.simple.H264Player 14 | import kotlinx.android.synthetic.main.activity_h264_player.* 15 | import java.io.File 16 | 17 | /** 18 | * author :Peakmain 19 | * createTime:2021/8/23 20 | * mail:2726449200@qq.com 21 | * describe: 22 | */ 23 | class H264PlayerActivity : BaseActivity() { 24 | var h264Player: H264Player? = null 25 | private val isPrintImage by lazy{ 26 | intent.getBooleanExtra("isPrintImage",false) 27 | } 28 | override fun getLayoutId(): Int { 29 | return R.layout.activity_h264_player 30 | } 31 | 32 | override fun initView() { 33 | checkPermission() 34 | mNavigationBuilder!!.setTitleText("H264视频播放").create() 35 | initSurface() 36 | } 37 | 38 | private fun initSurface() { 39 | val surfaceHolder = surfaceView.holder 40 | surfaceHolder.addCallback(object : BasicSurfaceHolderCallback() { 41 | override fun surfaceCreated(holder: SurfaceHolder?) { 42 | h264Player = H264Player( 43 | this@H264PlayerActivity, 44 | File( 45 | Environment.getExternalStorageDirectory(), 46 | "out.h264" 47 | ).absolutePath, 48 | surfaceHolder.surface, 49 | isPrintImage 50 | ) 51 | 52 | } 53 | 54 | }) 55 | } 56 | 57 | private fun checkPermission() { 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission( 59 | Manifest.permission.WRITE_EXTERNAL_STORAGE 60 | ) != PackageManager.PERMISSION_GRANTED 61 | ) { 62 | requestPermissions( 63 | arrayOf( 64 | Manifest.permission.READ_EXTERNAL_STORAGE, 65 | Manifest.permission.WRITE_EXTERNAL_STORAGE 66 | ), 1 67 | ) 68 | } 69 | } 70 | 71 | override fun initData() { 72 | 73 | } 74 | 75 | fun mediaCodecH264PlayClick(view: View) { 76 | h264Player?.startPlay() 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/basic/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.basic 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.peakmain.ui.navigationbar.DefaultNavigationBar 7 | import com.peakmain.video_audio.R 8 | 9 | /** 10 | * author :Peakmain 11 | * createTime:2020/3/9 12 | * mail:2726449200@qq.com 13 | * describe: 14 | */ 15 | abstract class BaseActivity : AppCompatActivity() { 16 | @JvmField 17 | var mNavigationBuilder: DefaultNavigationBar.Builder? = null 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | 21 | setContentView(getLayoutId()) 22 | mNavigationBuilder = DefaultNavigationBar.Builder(this, findViewById(android.R.id.content)) 23 | .hideLeftText() 24 | .setDisplayHomeAsUpEnabled(true) 25 | .setNavigationOnClickListener(View.OnClickListener { finish() }) 26 | .hideRightView() 27 | .setToolbarBackgroundColor(R.color.color_802F73F6) 28 | initView() 29 | initData() 30 | 31 | } 32 | 33 | protected abstract fun getLayoutId(): Int 34 | protected abstract fun initView() 35 | protected abstract fun initData() 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/basic/BaseRecyclerStringAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.basic 2 | 3 | import android.content.Context 4 | import com.peakmain.ui.recyclerview.adapter.CommonRecyclerAdapter 5 | import com.peakmain.ui.recyclerview.adapter.ViewHolder 6 | import com.peakmain.video_audio.R 7 | 8 | /** 9 | * author :Peakmain 10 | * createTime:2020/3/9 11 | * mail:2726449200@qq.com 12 | * describe: 13 | */ 14 | class BaseRecyclerStringAdapter(context: Context?, data: List) : CommonRecyclerAdapter(context, data, R.layout.item_recyclerview_main) { 15 | override fun convert(holder: ViewHolder, item: String) { 16 | holder.setText(R.id.tv_title, item) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/simple/BasicSurfaceHolderCallback.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.simple 2 | 3 | import android.view.SurfaceHolder 4 | 5 | /** 6 | * author :Peakmain 7 | * createTime:2021/8/23 8 | * mail:2726449200@qq.com 9 | * describe:接口适配器设计模式 10 | */ 11 | abstract class BasicSurfaceHolderCallback:SurfaceHolder.Callback{ 12 | override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { 13 | 14 | } 15 | 16 | override fun surfaceDestroyed(holder: SurfaceHolder?) { 17 | } 18 | 19 | override fun surfaceCreated(holder: SurfaceHolder?) { 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/simple/H264Player.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.simple; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.ImageFormat; 7 | import android.graphics.Rect; 8 | import android.graphics.YuvImage; 9 | import android.media.MediaCodec; 10 | import android.media.MediaFormat; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.os.Environment; 14 | import android.os.Handler; 15 | import android.os.HandlerThread; 16 | import android.util.Log; 17 | import android.view.Surface; 18 | 19 | import androidx.annotation.RequiresApi; 20 | 21 | import com.peakmain.ui.utils.LogUtils; 22 | import com.peakmain.video_audio.utils.VideoAudioUtils; 23 | 24 | import java.io.BufferedOutputStream; 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.File; 27 | import java.io.FileOutputStream; 28 | import java.io.IOException; 29 | import java.io.OutputStream; 30 | import java.nio.ByteBuffer; 31 | 32 | /** 33 | * author :Peakmain 34 | * createTime:2021/8/22 35 | * mail:2726449200@qq.com 36 | * describe: 37 | */ 38 | public class H264Player { 39 | private Context mContext; 40 | private String mPath; 41 | private Surface mSurface; 42 | private Handler mHandler; 43 | private MediaCodec mediaCodec; 44 | private static int TIMEOUT = 10000; 45 | private static final String TAG = H264Player.class.getSimpleName(); 46 | private boolean isPrintImage; 47 | 48 | public H264Player(Context context, String path, Surface surface, boolean isPrintImage) { 49 | this.mContext = context; 50 | this.mPath = path; 51 | this.mSurface = surface; 52 | HandlerThread handlerThread = new HandlerThread("H264Player"); 53 | handlerThread.start(); 54 | mHandler = new Handler(handlerThread.getLooper()); 55 | this.isPrintImage = isPrintImage; 56 | try { 57 | mediaCodec = MediaCodec.createDecoderByType("video/avc"); 58 | MediaFormat format = MediaFormat.createVideoFormat("video/avc", 720, 1280); 59 | //设置帧数 60 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); 61 | mediaCodec.configure(format, isPrintImage ? null : surface, null, 0); 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | 67 | public void startPlay() { 68 | mHandler.post(() -> { 69 | mediaCodec.start(); 70 | decode(); 71 | }); 72 | } 73 | 74 | private int printImageStatus;//0没有 1 打印了 75 | 76 | private void decode() { 77 | byte[] bytes = null; 78 | try { 79 | bytes = VideoAudioUtils.getBytes(mPath); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | int startIndex = 0; 84 | //总字节数 85 | int totalSize = bytes.length; 86 | while (true) { 87 | if (totalSize == 0 || startIndex >= totalSize) { 88 | return; 89 | } 90 | //+2的目的跳过pps和sps 91 | int nextFrameStart = findByFrame(bytes, startIndex + 2, totalSize); 92 | MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 93 | int decodeInputIndex = mediaCodec.dequeueInputBuffer(TIMEOUT); 94 | if (decodeInputIndex >= 0) { 95 | ByteBuffer byteBuffer = mediaCodec.getInputBuffer(decodeInputIndex); 96 | byteBuffer.clear(); 97 | byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex); 98 | //通知dsp进行解绑 99 | mediaCodec.queueInputBuffer(decodeInputIndex, 0, nextFrameStart - startIndex, 0, 0); 100 | startIndex = nextFrameStart; 101 | } else { 102 | continue; 103 | } 104 | //得到数据 105 | int decodeOutIndex = mediaCodec.dequeueOutputBuffer(info, TIMEOUT); 106 | if (decodeOutIndex >= 0) { 107 | if (isPrintImage) { 108 | //dsp的byteBuffer无法直接使用 109 | ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(decodeOutIndex); 110 | //设置偏移量 111 | byteBuffer.position(info.offset); 112 | byteBuffer.limit(info.size + info.offset); 113 | byte[] ba = new byte[byteBuffer.remaining()]; 114 | byteBuffer.get(ba); 115 | YuvImage yuvImage = new YuvImage(ba, ImageFormat.NV21, 720, 1280, null); 116 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 117 | yuvImage.compressToJpeg(new Rect(0, 0, 720, 1280), 100, baos); 118 | byte[] data = baos.toByteArray(); 119 | Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); 120 | if (bitmap != null) { 121 | if (printImageStatus == 0) { 122 | printImageStatus = 1; 123 | try { 124 | File myCaptureFile = new File(Environment.getExternalStorageDirectory(), "img.png"); 125 | BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(myCaptureFile)); 126 | bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos); 127 | bos.flush(); 128 | bos.close(); 129 | } catch (Exception e) { 130 | e.printStackTrace(); 131 | } 132 | } 133 | } 134 | } 135 | try { 136 | Thread.sleep(25); 137 | } catch (InterruptedException e) { 138 | e.printStackTrace(); 139 | } 140 | //如果绑定了surface则设置为true 141 | mediaCodec.releaseOutputBuffer(decodeOutIndex, !isPrintImage); 142 | } else { 143 | Log.e(TAG, "解绑失败"); 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * 找到下一帧 150 | */ 151 | private int findByFrame(byte[] bytes, int start, int totalSize) { 152 | for (int i = start; i < totalSize - 4; i++) { 153 | //0x 00 00 00 01 154 | if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) { 155 | return i; 156 | } 157 | } 158 | return -1; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/simple/mmkv/MMKV.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.simple.mmkv; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | /** 10 | * author :Peakmain 11 | * createTime:2021/8/24 12 | * mail:2726449200@qq.com 13 | * describe: 14 | */ 15 | public class MMKV { 16 | private static String rootDir = null; 17 | private long nativeHandle; 18 | 19 | private MMKV(long handle) { 20 | nativeHandle = handle; 21 | } 22 | public static String init(@NotNull Context context) { 23 | String root = Environment.getExternalStorageDirectory() + "/mmkv"; 24 | return init(root); 25 | } 26 | public static String init(String rootDir) { 27 | MMKV.rootDir = rootDir; 28 | mmkvInit(rootDir); 29 | return rootDir; 30 | } 31 | @Nullable 32 | public static MMKV defaultMMKV() { 33 | long handle = getDefaultMMKV(); 34 | return new MMKV(handle); 35 | } 36 | 37 | public int getInt(String key, int defaultValue) { 38 | return getInt(nativeHandle, key, defaultValue); 39 | } 40 | 41 | public void putInt(String key, int value) { 42 | putInt(nativeHandle, key, value); 43 | } 44 | 45 | private native static long getDefaultMMKV(); 46 | private static native void mmkvInit(String rootDir); 47 | private native int getInt(long handle, String key, int defaultValue); 48 | private native void putInt(long handle, String key, int value); 49 | private native void putString(long handle, String key, String value); 50 | private native String getString(long handle, String key); 51 | public void putString(@NotNull String key, @NotNull String value) { 52 | putString(nativeHandle,key,value); 53 | } 54 | 55 | @NotNull 56 | public String getString(@NotNull String key) { 57 | return getString(nativeHandle,key); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/CameraHelper.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils 2 | 3 | import android.graphics.ImageFormat 4 | import android.graphics.Rect 5 | import android.graphics.YuvImage 6 | import android.hardware.Camera 7 | import android.media.MediaCodec 8 | import android.media.MediaCodecInfo 9 | import android.media.MediaFormat 10 | import android.os.Environment 11 | import android.view.SurfaceHolder 12 | import android.view.SurfaceView 13 | import com.peakmain.ui.utils.LogUtils 14 | import com.peakmain.ui.utils.ToastUtils 15 | import com.peakmain.video_audio.simple.BasicSurfaceHolderCallback 16 | import java.io.File 17 | import java.io.FileOutputStream 18 | import java.io.IOException 19 | 20 | /** 21 | * author :Peakmain 22 | * createTime:2021/9/1 23 | * mail:2726449200@qq.com 24 | * describe:Camera工具类 25 | */ 26 | class CameraHelper(surfaceView: SurfaceView) : Camera.PreviewCallback { 27 | private lateinit var mCamera: Camera 28 | private lateinit var size: Camera.Size 29 | private lateinit var mBuffer: ByteArray 30 | private lateinit var nv21_rotated: ByteArray 31 | private lateinit var nv12: ByteArray 32 | 33 | private var mCameraListener: ((ByteArray?, size: Camera.Size) -> Unit)? = null 34 | private lateinit var mediaCodec: MediaCodec 35 | 36 | init { 37 | surfaceView.holder.addCallback(object : BasicSurfaceHolderCallback() { 38 | override fun surfaceCreated(holder: SurfaceHolder?) { 39 | startPreview(holder) 40 | } 41 | }) 42 | } 43 | 44 | private fun initMediaCodec() { 45 | try { 46 | mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) 47 | val format = MediaFormat.createVideoFormat( 48 | MediaFormat.MIMETYPE_VIDEO_AVC, 49 | size.height, size.width 50 | ) 51 | LogUtils.e( 52 | "initCodec: width: " + size.height + " width: " + size.width 53 | ) 54 | //设置帧率 55 | format.setInteger( 56 | MediaFormat.KEY_COLOR_FORMAT, 57 | MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible 58 | ) 59 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 15) 60 | format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000) 61 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2) //2s一个I帧 62 | mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) 63 | mediaCodec.start() 64 | } catch (e: IOException) { 65 | e.printStackTrace() 66 | } 67 | } 68 | 69 | private fun startPreview(holder: SurfaceHolder?) { 70 | mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK) 71 | val parameters = mCamera.parameters 72 | size = parameters.previewSize 73 | initMediaCodec() 74 | mCamera.setPreviewDisplay(holder) 75 | mCamera.setDisplayOrientation(90) 76 | mBuffer = ByteArray(size.width * size.height * 3 / 2) 77 | nv21_rotated = ByteArray(size.width * size.height * 3 / 2) 78 | nv12 = ByteArray(size.width * size.height * 3 / 2) 79 | mCamera.addCallbackBuffer(mBuffer) 80 | mCamera.setPreviewCallbackWithBuffer(this) 81 | mCamera.startPreview() 82 | 83 | } 84 | 85 | private var isCaptrue = false 86 | fun start() { 87 | isCaptrue = !isCaptrue 88 | ToastUtils.showShort(if (isCaptrue) "开始录屏" else "停止录屏") 89 | } 90 | 91 | override fun onPreviewFrame(data: ByteArray?, camera: Camera?) { 92 | if (isCaptrue) { 93 | nv12 = FileUtils.nv21toNV12(data) 94 | //旋转九十度 95 | FileUtils.portraitData2Raw( 96 | nv12, 97 | nv21_rotated, 98 | size.width, 99 | size.height 100 | ) 101 | 102 | if (mCameraListener != null) { 103 | mCameraListener?.invoke(nv21_rotated, size) 104 | } 105 | } 106 | mCamera.addCallbackBuffer(data) 107 | } 108 | 109 | 110 | private fun captrue(bytes: ByteArray, index: Int = 0) { 111 | val fileName = "Camera_$index.jpg" 112 | val sdRoot = Environment.getExternalStorageDirectory() 113 | val pictureFile = File(sdRoot, fileName) 114 | if (!pictureFile.exists()) { 115 | try { 116 | pictureFile.createNewFile() 117 | val fileOutputStream = 118 | FileOutputStream(pictureFile) 119 | val image = YuvImage( 120 | bytes, 121 | ImageFormat.NV21, 122 | size.height, 123 | size.width, 124 | null 125 | ) //将NV21 data保存成YuvImage 126 | image.compressToJpeg( 127 | Rect(0, 0, image.width, image.height), 128 | 100, fileOutputStream 129 | ) 130 | } catch (e: IOException) { 131 | e.printStackTrace() 132 | } 133 | } 134 | } 135 | 136 | fun setCameraListener(cameraListener: ((ByteArray?, size: Camera.Size) -> Unit)) { 137 | this.mCameraListener = cameraListener 138 | } 139 | 140 | fun getMediaCodec(): MediaCodec { 141 | return mediaCodec 142 | } 143 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/CameraXHelper.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils 2 | 3 | import android.os.Handler 4 | import android.os.HandlerThread 5 | import android.util.Size 6 | import android.view.TextureView 7 | import android.view.ViewGroup 8 | import androidx.camera.core.* 9 | import androidx.camera.core.CameraX.LensFacing 10 | import androidx.camera.core.Preview.OnPreviewOutputUpdateListener 11 | import androidx.camera.core.Preview.PreviewOutput 12 | import androidx.lifecycle.LifecycleOwner 13 | import com.peakmain.ui.utils.SizeUtils 14 | import java.util.concurrent.locks.ReentrantLock 15 | 16 | /** 17 | * author :Peakmain 18 | * createTime:2021/8/17 19 | * mail:2726449200@qq.com 20 | * describe: 21 | */ 22 | class CameraXHelper( 23 | private var mLifecycleOwner: LifecycleOwner? = null, 24 | private var mTextureView: TextureView 25 | ) : 26 | OnPreviewOutputUpdateListener, ImageAnalysis.Analyzer { 27 | private val mHandlerThread: HandlerThread = HandlerThread("CameraXHelper") 28 | var width = SizeUtils.screenWidth 29 | var height = SizeUtils.screenHeight 30 | 31 | //设置后摄像头 32 | private val currentFacing = LensFacing.BACK 33 | fun startCamera() { 34 | if (mLifecycleOwner == null) { 35 | return 36 | } 37 | CameraX.bindToLifecycle(mLifecycleOwner, preView, analysis) 38 | } 39 | 40 | //预览 41 | //setTargetResolution设置预览尺寸 42 | private val preView: Preview 43 | get() { 44 | //预览 45 | //setTargetResolution设置预览尺寸 46 | val previewConfig = 47 | PreviewConfig.Builder().setTargetResolution(Size(width, height)) 48 | .setLensFacing(currentFacing).build() 49 | val preview = Preview(previewConfig) 50 | preview.onPreviewOutputUpdateListener = this 51 | return preview 52 | } 53 | 54 | private val analysis: ImageAnalysis 55 | get() { 56 | val imageAnalysisConfig = ImageAnalysisConfig.Builder() 57 | .setCallbackHandler(Handler(mHandlerThread.looper)) 58 | .setLensFacing(currentFacing) 59 | .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE) 60 | .setTargetResolution(Size(width, height)) 61 | .build() 62 | val imageAnalysis = ImageAnalysis(imageAnalysisConfig) 63 | imageAnalysis.analyzer = this 64 | return imageAnalysis 65 | } 66 | 67 | override fun onUpdated(output: PreviewOutput) { 68 | val surfaceTexture = output.surfaceTexture 69 | //防止切换镜头报错 70 | if (mTextureView.surfaceTexture !== surfaceTexture) { 71 | if (mTextureView.isAvailable) { 72 | // 当切换摄像头时,会报错 73 | val parent = mTextureView.parent as ViewGroup 74 | parent.removeView(mTextureView) 75 | parent.addView(mTextureView, 0) 76 | parent.requestLayout() 77 | } 78 | mTextureView.surfaceTexture = surfaceTexture 79 | } 80 | } 81 | 82 | private val lock = 83 | ReentrantLock() 84 | private var y: ByteArray?=null 85 | private var u: ByteArray?=null 86 | private var v: ByteArray?=null 87 | override fun analyze(image: ImageProxy, rotationDegrees: Int) { 88 | lock.lock() 89 | try { 90 | val planes = image.planes 91 | //初始化y v u 92 | if (y == null) { 93 | y = ByteArray( 94 | planes[0].buffer.limit() - planes[0].buffer.position() 95 | ) 96 | u = ByteArray( 97 | planes[1].buffer.limit() - planes[1].buffer.position() 98 | ) 99 | v = ByteArray( 100 | planes[2].buffer.limit() - planes[2].buffer.position() 101 | ) 102 | } 103 | if (image.planes[0].buffer.remaining() == y!!.size) { 104 | planes[0].buffer[y] 105 | planes[1].buffer[u] 106 | planes[2].buffer[v] 107 | val stride = planes[0].rowStride 108 | val size = Size(image.width, image.height) 109 | if (cameraXListener != null) { 110 | cameraXListener!!.invoke(y, u, v, size, stride) 111 | } 112 | } 113 | } catch (e: Exception) { 114 | e.printStackTrace() 115 | } finally { 116 | lock.unlock() 117 | } 118 | } 119 | 120 | private var cameraXListener: ((ByteArray?, ByteArray?, ByteArray?, Size, Int) -> Unit)? = null 121 | 122 | fun setCameraXListener(cameraXListener: ((ByteArray?, ByteArray?, ByteArray?, Size, Int) -> Unit)) { 123 | this.cameraXListener = cameraXListener 124 | } 125 | 126 | init { 127 | //子线程中回调 128 | mHandlerThread.start() 129 | mLifecycleOwner = mLifecycleOwner 130 | } 131 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/CrashUtils.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils 2 | 3 | import android.os.Looper 4 | import android.os.MessageQueue 5 | import android.util.Log 6 | import com.peakmain.ui.constants.BasicUIUtils 7 | import com.peakmain.video_audio.utils.crash.JavaCrashMonitor 8 | import com.peakmain.video_audio.utils.crash.NativeCrashMonitor 9 | import com.peakmain.video_audio.utils.crash.callback.CrashListener 10 | import java.io.File 11 | 12 | /** 13 | * author :Peakmain 14 | * createTime:2021/5/7 15 | * mail:2726449200@qq.com 16 | * describe:异常处理工具类 17 | */ 18 | object CrashUtils : MessageQueue.IdleHandler { 19 | private const val CRASH_DIR_JAVA = "javaCrash" 20 | private const val CRASH_DIR_NATIVE = "nativeCrash" 21 | private lateinit var mCrashListener: CrashListener 22 | fun init(listener: CrashListener) { 23 | Looper.myQueue().addIdleHandler(this) 24 | mCrashListener = listener 25 | } 26 | 27 | private fun getNativeCrashDir(): File { 28 | val file = File(BasicUIUtils.application!!.cacheDir, CRASH_DIR_NATIVE) 29 | if (!file.exists()) { 30 | file.mkdirs() 31 | } 32 | return file 33 | } 34 | 35 | private fun getJavaCrashDir(): File { 36 | val file = File(BasicUIUtils.application!!.cacheDir, CRASH_DIR_JAVA) 37 | if (!file.exists()) { 38 | file.mkdirs() 39 | } 40 | return file 41 | } 42 | 43 | var mListener: OnFileUploadListener? = null 44 | 45 | interface OnFileUploadListener { 46 | fun onFileUploadListener(file: File) 47 | } 48 | 49 | //文件上传 50 | fun setOnFileUploadListener(listener: OnFileUploadListener?) { 51 | this.mListener = listener 52 | } 53 | 54 | override fun queueIdle(): Boolean { 55 | val javaCrashDir = getJavaCrashDir() 56 | val nativeCrashDir = getNativeCrashDir() 57 | JavaCrashMonitor.init(javaCrashDir.absolutePath) 58 | NativeCrashMonitor().init { threadName, error -> 59 | mCrashListener.onCrash(threadName, error) 60 | } 61 | return false 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils; 2 | 3 | import android.os.Environment; 4 | import android.util.Log; 5 | 6 | import com.peakmain.ui.BuildConfig; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | 13 | /** 14 | * author :Peakmain 15 | * createTime:2021/8/25 16 | * mail:2726449200@qq.com 17 | * describe: 18 | */ 19 | public class FileUtils { 20 | public static String writeContent(byte[] array, String textName) { 21 | char[] hexCharTable = { 22 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 23 | }; 24 | StringBuilder sb = new StringBuilder(); 25 | for (byte b : array) { 26 | sb.append(hexCharTable[(b & 0xf0) >> 4]); 27 | sb.append(hexCharTable[b & 0x0f]); 28 | } 29 | Log.i(BuildConfig.TAG, "writeContent: " + sb.toString()); 30 | FileWriter writer = null; 31 | try { 32 | // 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件 33 | writer = new FileWriter(Environment.getExternalStorageDirectory() + File.separator + textName, true); 34 | writer.write(sb.toString()); 35 | writer.write("\n"); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } finally { 39 | try { 40 | if (writer != null) { 41 | writer.close(); 42 | } 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | return sb.toString(); 48 | } 49 | 50 | public static void writeBytes(byte[] array, String fileName) { 51 | FileOutputStream writer = null; 52 | try { 53 | writer = new FileOutputStream(Environment.getExternalStorageDirectory() + File.separator + fileName, true); 54 | writer.write(array); 55 | writer.write('\n'); 56 | 57 | } catch (IOException e) { 58 | e.printStackTrace(); 59 | } finally { 60 | try { 61 | if (writer != null) { 62 | writer.close(); 63 | } 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | 70 | static byte[] nv12; 71 | 72 | public static byte[] nv21toNV12(byte[] nv21) { 73 | int size = nv21.length; 74 | nv12 = new byte[size]; 75 | int len = size * 2 / 3; 76 | System.arraycopy(nv21, 0, nv12, 0, len); 77 | int i = len; 78 | while (i < size - 1) { 79 | nv12[i] = nv21[i + 1]; 80 | nv12[i + 1] = nv21[i]; 81 | i += 2; 82 | } 83 | return nv12; 84 | } 85 | 86 | /** 87 | * yuv转成nv21 88 | */ 89 | public static void yuvToNv21(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) { 90 | System.arraycopy(y, 0, nv21, 0, y.length); 91 | // 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算 92 | int length = y.length + u.length / 2 + v.length / 2; 93 | int uIndex = 0, vIndex = 0; 94 | for (int i = stride * height; i < length; i += 2) { 95 | nv21[i] = v[vIndex]; 96 | nv21[i + 1] = u[uIndex]; 97 | vIndex += 2; 98 | uIndex += 2; 99 | } 100 | } 101 | 102 | public static byte[] nv21RotateTo90(byte[] data, byte[] output, int width, int height) { 103 | int yLen = width * height; 104 | int buffserSize = yLen * 3 / 2; 105 | 106 | int i = 0; 107 | int startPos = (height - 1) * width; 108 | for (int x = 0; x < width; x++) { 109 | int offset = startPos; 110 | for (int y = height - 1; y >= 0; y--) { 111 | output[i] = data[offset + x]; 112 | i++; 113 | offset -= width; 114 | } 115 | } 116 | // Rotate the U and V color components 117 | i = buffserSize - 1; 118 | for (int x = width - 1; x > 0; x = x - 2) { 119 | int offset = yLen; 120 | for (int y = 0; y < height / 2; y++) { 121 | output[i] = data[offset + x]; 122 | i--; 123 | output[i] = data[offset + (x - 1)]; 124 | i--; 125 | offset += width; 126 | } 127 | } 128 | return output; 129 | } 130 | 131 | /** 132 | * 旋转90度 133 | */ 134 | public static void portraitData2Raw(byte[] data, byte[] output, int width, int height) { 135 | int yLen = width * height; 136 | int uvHeight = height >> 1; 137 | int k = 0; 138 | for (int j = 0; j < width; j++) { 139 | for (int i = height - 1; i >= 0; i--) { 140 | output[k++] = data[width * i + j]; 141 | } 142 | } 143 | for (int j = 0; j < width; j += 2) { 144 | for (int i = uvHeight - 1; i >= 0; i--) { 145 | output[k++] = data[yLen + width * i + j]; 146 | output[k++] = data[yLen + width * i + j + 1]; 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/VideoAudioUtils.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils 2 | 3 | import android.content.Context 4 | import java.io.* 5 | 6 | /** 7 | * author :Peakmain 8 | * createTime:2021/8/22 9 | * mail:2726449200@qq.com 10 | * describe: 11 | */ 12 | object VideoAudioUtils { 13 | @JvmStatic 14 | @Throws(IOException::class) 15 | fun getBytes(path: String?): ByteArray { 16 | val `is`: InputStream = 17 | DataInputStream(FileInputStream(File(path))) 18 | var len: Int 19 | val size = 1024 20 | var buf: ByteArray 21 | val bos = ByteArrayOutputStream() 22 | buf = ByteArray(size) 23 | while (`is`.read(buf, 0, size).also { len = it } != -1) bos.write(buf, 0, len) 24 | buf = bos.toByteArray() 25 | return buf 26 | } 27 | 28 | @JvmStatic 29 | @Throws(IOException::class) 30 | fun copyAssets(context: Context, assetsName: String, path: String) { 31 | val assetFileDescriptor = context.assets.openFd(assetsName) 32 | val from = 33 | FileInputStream(assetFileDescriptor.fileDescriptor).channel 34 | val to = FileOutputStream(path).channel 35 | from.transferTo(assetFileDescriptor.startOffset, assetFileDescriptor.length, to) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/crash/JavaCrashMonitor.kt: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils.crash 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Build 7 | import android.os.Environment 8 | import android.os.Process 9 | import android.os.StatFs 10 | import android.text.format.Formatter 11 | import com.peakmain.ui.constants.BasicUIUtils 12 | import com.peakmain.ui.utils.ActivityUtils 13 | import com.peakmain.ui.utils.LogUtils 14 | import com.peakmain.video_audio.utils.CrashUtils 15 | import java.io.* 16 | import java.text.SimpleDateFormat 17 | import java.util.* 18 | import kotlin.system.exitProcess 19 | 20 | 21 | /** 22 | * author :Peakmain 23 | * createTime:2021/5/7 24 | * mail:2726449200@qq.com 25 | * describe: 26 | */ 27 | 28 | internal object JavaCrashMonitor { 29 | var CRASH_DIR = "crash_dir" 30 | fun init(crashDir: String) { 31 | Thread.setDefaultUncaughtExceptionHandler(CaughtExceptionHandler()) 32 | CRASH_DIR = crashDir 33 | } 34 | 35 | class CaughtExceptionHandler : Thread.UncaughtExceptionHandler { 36 | private val context = BasicUIUtils.application!! 37 | private val formatter = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINA) 38 | private val LAUNCH_TIME = formatter.format(Date()) 39 | private val mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() 40 | override fun uncaughtException(t: Thread?, e: Throwable?) { 41 | if (!handleException(e) && mDefaultExceptionHandler != null) { 42 | mDefaultExceptionHandler.uncaughtException(t, e) 43 | } 44 | //restartApp() 45 | } 46 | 47 | private fun restartApp() { 48 | val intent: Intent? = 49 | context.packageManager?.getLaunchIntentForPackage(context.packageName) 50 | intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 51 | context.startActivity(intent) 52 | 53 | Process.killProcess(Process.myPid()) 54 | exitProcess(10) 55 | } 56 | 57 | private fun handleException(e: Throwable?): Boolean { 58 | if (e == null) return false 59 | val logInfo = createLogInfo(e) 60 | LogUtils.e(logInfo) 61 | val file = saveCrashInfoToFile(logInfo) 62 | if(CrashUtils.mListener !=null){ 63 | CrashUtils.mListener!!.onFileUploadListener(file) 64 | } 65 | return true 66 | } 67 | 68 | private fun createLogInfo(e: Throwable): String { 69 | val sb = StringBuilder() 70 | sb.append("brand=${Build.BRAND}\n") 71 | sb.append("rom=${Build.MODEL}\n") 72 | sb.append("os=${Build.VERSION.RELEASE}\n") 73 | sb.append("sdk=${Build.VERSION.SDK_INT}\n") 74 | sb.append("launch_time=${LAUNCH_TIME}\n")//启动app的时间 75 | sb.append("crash_time=${formatter.format(Date())}\n")//crash发生时间 76 | sb.append("forground=${ActivityUtils.mInstance.isFront}\n")//应用处于前后台 77 | sb.append("thread=${Thread.currentThread().name}\n")//异常线程名 78 | sb.append("cpu_arch=${Build.CPU_ABI}\n") 79 | 80 | //app 信息 81 | val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) 82 | sb.append("versionCode=${packageInfo.versionCode}\n")//版本号 83 | sb.append("versionName=${packageInfo.versionName}\n") 84 | sb.append("packageName=${packageInfo.packageName}\n") 85 | sb.append("requestedPermission=${Arrays.toString(packageInfo.requestedPermissions)}\n") 86 | 87 | val memoryInfo = ActivityManager.MemoryInfo() 88 | val activityManager = 89 | context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 90 | activityManager.getMemoryInfo(memoryInfo) 91 | sb.append( 92 | "availMemory=${Formatter.formatFileSize( 93 | context, 94 | memoryInfo.availMem 95 | )}\n" 96 | )//可用内存 97 | sb.append( 98 | "totalMemory=${Formatter.formatFileSize( 99 | context, 100 | memoryInfo.totalMem 101 | )}\n" 102 | )//设备总内存 103 | 104 | val file = Environment.getExternalStorageDirectory() 105 | //手机内部可用空间 106 | val statFs = StatFs(file.path) 107 | val availableSize = statFs.availableBlocks * statFs.blockSize 108 | sb.append( 109 | "availStorage=${Formatter.formatFileSize( 110 | context, 111 | availableSize.toLong() 112 | )}\n" 113 | ) 114 | val write: Writer = StringWriter() 115 | val printWriter = PrintWriter(write) 116 | e.printStackTrace(printWriter) 117 | var cause = e.cause 118 | while (cause != null) { 119 | cause.printStackTrace(printWriter) 120 | cause = cause.cause 121 | } 122 | printWriter.close() 123 | sb.append(write.toString()) 124 | return sb.toString() 125 | } 126 | 127 | private fun saveCrashInfoToFile(logInfo: String): File { 128 | val crashDir = File(CRASH_DIR) 129 | if (!crashDir.exists()) { 130 | crashDir.mkdirs() 131 | } 132 | val file = File(crashDir, formatter.format(Date()) + "-crash.txt") 133 | file.createNewFile() 134 | LogUtils.e(file.absolutePath) 135 | val fos = FileOutputStream(file) 136 | try { 137 | fos.write(logInfo.toByteArray()) 138 | fos.flush() 139 | } catch (e: Exception) { 140 | e.printStackTrace() 141 | } finally { 142 | fos.close() 143 | } 144 | return file 145 | } 146 | } 147 | 148 | 149 | } -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/crash/NativeCrashMonitor.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils.crash; 2 | 3 | import android.os.Looper; 4 | import android.text.TextUtils; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.Keep; 8 | 9 | import com.peakmain.video_audio.utils.crash.callback.CrashListener; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | /** 17 | * author :Peakmain 18 | * createTime:2021/8/20 19 | * mail:2726449200@qq.com 20 | * describe: 21 | */ 22 | public class NativeCrashMonitor { 23 | public static volatile boolean isInit = false; 24 | private static ThreadGroup systemThreadGroup; 25 | static { 26 | System.loadLibrary("native-lib"); 27 | try { 28 | Class threadGroupClass = Class.forName("java.lang.ThreadGroup"); 29 | Field threadGroupField = threadGroupClass.getDeclaredField("systemThreadGroup"); 30 | threadGroupField.setAccessible(true); 31 | systemThreadGroup = (ThreadGroup) threadGroupField.get(null); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | 37 | public void init(CrashListener callback) { 38 | if (isInit) { 39 | return; 40 | } 41 | isInit = true; 42 | nativeCrashInit(this, callback); 43 | nativeSetup(); 44 | } 45 | /** 46 | * 根据线程名获取当前线程的堆栈信息 47 | */ 48 | @Keep 49 | private static String getStackInfoByThreadName(String threadName) { 50 | Thread thread = getThreadByName(threadName); 51 | StringBuilder sb = new StringBuilder(); 52 | StackTraceElement[] stackTraceElements = thread.getStackTrace(); 53 | for (StackTraceElement stackTraceElement : stackTraceElements) { 54 | sb.append(stackTraceElement.toString()).append("\r\n"); 55 | } 56 | return sb.toString(); 57 | } 58 | public static Thread getThreadByName(String threadName) { 59 | if (TextUtils.isEmpty(threadName)) { 60 | return null; 61 | } 62 | Thread theThread = null; 63 | if (threadName.equals("main")) { 64 | theThread = Looper.getMainLooper().getThread(); 65 | } else { 66 | Thread[] threadArray = new Thread[]{}; 67 | try { 68 | Set threadSet = getAllStackTraces().keySet(); 69 | threadArray = threadSet.toArray(new Thread[threadSet.size()]); 70 | } catch (Exception e) { 71 | e.printStackTrace(); 72 | } 73 | for (Thread thread : threadArray) { 74 | if (thread.getName().equals(threadName)) { 75 | theThread = thread; 76 | Log.e("TAG", "find it." + threadName); 77 | } 78 | } 79 | } 80 | return theThread; 81 | } 82 | 83 | //获取线程堆栈的map. 84 | private static Map getAllStackTraces() { 85 | if (systemThreadGroup == null) { 86 | return Thread.getAllStackTraces(); 87 | } else { 88 | Map map = new HashMap<>(); 89 | int count = systemThreadGroup.activeCount(); 90 | Thread[] threads = new Thread[count + count / 2]; 91 | Log.d("TAG", "activeCount: " + count); 92 | //赋值所有存活对象到threads 93 | count = systemThreadGroup.enumerate(threads); 94 | for (int i = 0; i < count; i++) { 95 | try { 96 | map.put(threads[i], threads[i].getStackTrace()); 97 | } catch (Throwable e) { 98 | Log.e("TAG", "fail threadName: " + threads[i].getName(), e); 99 | } 100 | } 101 | return map; 102 | } 103 | } 104 | private native void nativeSetup(); 105 | 106 | private native void nativeCrashInit(NativeCrashMonitor nativeCrashMonitor, CrashListener callback); 107 | 108 | public static native void nativeCrashCreate(); 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/crash/callback/CrashListener.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils.crash.callback; 2 | 3 | /** 4 | * author :Peakmain 5 | * createTime:2021/8/20 6 | * mail:2726449200@qq.com 7 | * describe: 8 | */ 9 | public interface CrashListener { 10 | void onCrash(String threadName, Error error); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/socket/CodecLiveH265.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils.socket; 2 | 3 | import android.hardware.display.DisplayManager; 4 | import android.hardware.display.VirtualDisplay; 5 | import android.media.MediaCodec; 6 | import android.media.MediaCodecInfo; 7 | import android.media.MediaFormat; 8 | import android.media.projection.MediaProjection; 9 | import android.os.Handler; 10 | import android.os.HandlerThread; 11 | import android.view.Surface; 12 | 13 | import com.peakmain.ui.utils.SizeUtils; 14 | import com.peakmain.video_audio.utils.FileUtils; 15 | 16 | import java.io.IOException; 17 | import java.nio.ByteBuffer; 18 | 19 | import static android.media.MediaFormat.KEY_BIT_RATE; 20 | import static android.media.MediaFormat.KEY_FRAME_RATE; 21 | 22 | /** 23 | * author :Peakmain 24 | * createTime:2021/8/31 25 | * mail:2726449200@qq.com 26 | * describe:编码H265(服务端开始直播) 27 | */ 28 | public class CodecLiveH265 implements Runnable { 29 | private MediaProjection mMediaProjection; 30 | private WebSocketSendLive mWebSocketSendLive; 31 | private MediaCodec mMediaCodec; 32 | private final int mWidth = SizeUtils.getScreenWidth(); 33 | private final int mHeight = SizeUtils.getScreenHeight(); 34 | VirtualDisplay mVirtualDisplay; 35 | private final Handler mHandler; 36 | 37 | public CodecLiveH265(WebSocketSendLive webSocketSendLive, MediaProjection mediaProjection) { 38 | this.mMediaProjection = mediaProjection; 39 | this.mWebSocketSendLive = webSocketSendLive; 40 | HandlerThread mHandlerThread = new HandlerThread("CodecLiveH265_HandlerThread"); 41 | mHandlerThread.start(); 42 | mHandler = new Handler(mHandlerThread.getLooper()); 43 | } 44 | 45 | public void startLive() { 46 | try { 47 | //服务器端编码H264通过socket发送给客户端 48 | MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, mWidth, mHeight); 49 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 50 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); 51 | format.setInteger(KEY_BIT_RATE, mWidth * mHeight); 52 | format.setInteger(KEY_FRAME_RATE, 20); 53 | mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC); 54 | mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 55 | //创建场地 56 | Surface surface = mMediaCodec.createInputSurface(); 57 | mVirtualDisplay = mMediaProjection.createVirtualDisplay("CodecLiveH265", 58 | mWidth, mHeight, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null); 59 | mHandler.post(this); 60 | } catch (IOException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | @Override 66 | public void run() { 67 | mMediaCodec.start(); 68 | MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 69 | while (true) { 70 | //取出数据发送给客户端 71 | int outIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 1000); 72 | if (outIndex >= 0) { 73 | ByteBuffer buffer = mMediaCodec.getOutputBuffer(outIndex); 74 | dealFrame(buffer, bufferInfo); 75 | mMediaCodec.releaseOutputBuffer(outIndex, false); 76 | } 77 | } 78 | } 79 | 80 | public static final int NAL_I = 19; 81 | public static final int NAL_VPS = 32; 82 | //vps+sps+pps是一帧,所以只需要获取vps 83 | private byte[] vps_sps_pps_buffer; 84 | 85 | private void dealFrame(ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo) { 86 | //过滤掉第一个0x00 00 00 01 或者0x 00 00 01 87 | int offset = 4; 88 | if (buffer.get(2) == 0x01) { 89 | offset = 3; 90 | } 91 | //获取帧类型 92 | int type = (buffer.get(offset) & 0x7E) >> 1; 93 | if (type == NAL_VPS) { 94 | vps_sps_pps_buffer = new byte[bufferInfo.size]; 95 | buffer.get(vps_sps_pps_buffer); 96 | } else if (type == NAL_I) { 97 | //I帧 98 | final byte[] bytes = new byte[bufferInfo.size]; 99 | buffer.get(bytes); 100 | //vps_pps_sps+I帧的数据 101 | byte[] newBuffer = new byte[vps_sps_pps_buffer.length + bytes.length]; 102 | System.arraycopy(vps_sps_pps_buffer, 0, newBuffer, 0, vps_sps_pps_buffer.length); 103 | System.arraycopy(bytes, 0, newBuffer, vps_sps_pps_buffer.length, bytes.length); 104 | mWebSocketSendLive.sendData(newBuffer); 105 | }else{ 106 | //P帧 B帧直接发送就可以了 107 | final byte[] bytes = new byte[bufferInfo.size]; 108 | buffer.get(bytes); 109 | mWebSocketSendLive.sendData(bytes); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/socket/SocketAcceptLive.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils.socket; 2 | 3 | import android.util.Log; 4 | 5 | import com.peakmain.video_audio.BuildConfig; 6 | 7 | import org.java_websocket.WebSocket; 8 | import org.java_websocket.client.WebSocketClient; 9 | import org.java_websocket.handshake.ServerHandshake; 10 | 11 | import java.net.URI; 12 | import java.net.URISyntaxException; 13 | import java.nio.ByteBuffer; 14 | 15 | /** 16 | * author :Peakmain 17 | * createTime:2021/8/31 18 | * mail:2726449200@qq.com 19 | * describe: 20 | */ 21 | public class SocketAcceptLive { 22 | 23 | private SocketCallback mSocketCallback; 24 | private CustomWebSocketClient mWebSocketClient; 25 | 26 | public SocketAcceptLive(SocketCallback mSocketCallback) { 27 | this.mSocketCallback = mSocketCallback; 28 | } 29 | 30 | public void start() { 31 | try { 32 | URI url = new URI("ws://10.0.29.121:14430"); 33 | mWebSocketClient = new CustomWebSocketClient(url); 34 | mWebSocketClient.connect(); 35 | } catch (URISyntaxException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | public void sendData(byte[] bytes) { 40 | if (mWebSocketClient!=null&&(mWebSocketClient.isOpen())) { 41 | mWebSocketClient.send(bytes); 42 | } 43 | } 44 | 45 | private class CustomWebSocketClient extends WebSocketClient { 46 | 47 | public CustomWebSocketClient(URI uri) { 48 | super(uri); 49 | } 50 | @Override 51 | public void onOpen(ServerHandshake handshakedata) { 52 | Log.e(BuildConfig.TAG, "SocketAcceptLive打开 socket onOpen: "); 53 | } 54 | 55 | @Override 56 | public void onMessage(String message) { 57 | 58 | } 59 | 60 | @Override 61 | public void onMessage(ByteBuffer bytes) { 62 | //接收到消息,进行解码 63 | byte[] buffer = new byte[bytes.remaining()]; 64 | bytes.get(buffer); 65 | mSocketCallback.callBack(buffer); 66 | } 67 | 68 | @Override 69 | public void onClose(int code, String reason, boolean remote) { 70 | Log.e(BuildConfig.TAG, "SocketAcceptLive onClose "); 71 | } 72 | 73 | @Override 74 | public void onError(Exception e) { 75 | Log.e(BuildConfig.TAG, "onError: "+e); 76 | } 77 | } 78 | 79 | public interface SocketCallback { 80 | void callBack(byte[] data); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/utils/socket/WebSocketSendLive.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.utils.socket; 2 | 3 | import android.media.projection.MediaProjection; 4 | import android.util.Log; 5 | 6 | import com.peakmain.ui.BuildConfig; 7 | 8 | import org.java_websocket.WebSocket; 9 | import org.java_websocket.handshake.ClientHandshake; 10 | import org.java_websocket.server.WebSocketServer; 11 | 12 | import java.io.IOException; 13 | import java.net.InetSocketAddress; 14 | import java.nio.ByteBuffer; 15 | 16 | /** 17 | * author :Peakmain 18 | * createTime:2021/8/31 19 | * mail:2726449200@qq.com 20 | * describe:服务端 21 | */ 22 | public class WebSocketSendLive { 23 | private WebSocket mWebSocket; 24 | CodecLiveH265 mCodecLiveH265; 25 | private SocketCallback socketCallback; 26 | 27 | public WebSocketSendLive(SocketCallback socketCallback) { 28 | this.socketCallback = socketCallback; 29 | } 30 | 31 | public void start(MediaProjection mediaProjection) { 32 | //启动服务器 33 | mWebSocketServer.start(); 34 | mCodecLiveH265 = new CodecLiveH265(this, mediaProjection); 35 | mCodecLiveH265.startLive(); 36 | } 37 | 38 | public void start() { 39 | mWebSocketServer.start(); 40 | } 41 | 42 | public void close() { 43 | try { 44 | mWebSocket.close(); 45 | mWebSocketServer.stop(); 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } catch (InterruptedException e) { 49 | e.printStackTrace(); 50 | } 51 | 52 | } 53 | 54 | public WebSocketSendLive() { 55 | } 56 | 57 | private final WebSocketServer mWebSocketServer = new WebSocketServer(new InetSocketAddress(14430)) { 58 | @Override 59 | public void onOpen(WebSocket conn, ClientHandshake handshake) { 60 | WebSocketSendLive.this.mWebSocket = conn; 61 | } 62 | 63 | @Override 64 | public void onClose(WebSocket conn, int code, String reason, boolean remote) { 65 | Log.e(BuildConfig.TAG, "onClose: 关闭 socket "); 66 | } 67 | 68 | @Override 69 | public void onMessage(WebSocket conn, String message) { 70 | 71 | } 72 | 73 | @Override 74 | public void onMessage(WebSocket conn, ByteBuffer bytes) { 75 | byte[] buf = new byte[bytes.remaining()]; 76 | bytes.get(buf); 77 | socketCallback.callBack(buf); 78 | } 79 | 80 | @Override 81 | public void onError(WebSocket conn, Exception e) { 82 | Log.e(BuildConfig.TAG, "onError: " + e.toString()); 83 | } 84 | 85 | @Override 86 | public void onStart() { 87 | Log.e(BuildConfig.TAG, "onStart: 开启 socket: "); 88 | } 89 | }; 90 | 91 | /** 92 | * 发送数据 93 | * 94 | * @param bytes 类型byte[] 95 | */ 96 | public void sendData(byte[] bytes) { 97 | if (mWebSocket != null && mWebSocket.isOpen()) { 98 | mWebSocket.send(bytes); 99 | } 100 | } 101 | 102 | public interface SocketCallback { 103 | void callBack(byte[] data); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/video/DecodecVideoCallLiveH265.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.video; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaFormat; 5 | import android.os.Build; 6 | import android.util.Log; 7 | import android.view.Surface; 8 | 9 | import androidx.annotation.RequiresApi; 10 | 11 | import com.peakmain.video_audio.BuildConfig; 12 | 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | 16 | /** 17 | * author :Peakmain 18 | * createTime:2021/8/12 19 | * mail:2726449200@qq.com 20 | * describe: 21 | */ 22 | public class DecodecVideoCallLiveH265 { 23 | private MediaCodec mediaCodec; 24 | 25 | public void initDecoder(Surface surface) { 26 | try { 27 | mediaCodec = MediaCodec.createDecoderByType("video/hevc"); 28 | final MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, 1080, 1920); 29 | format.setInteger(MediaFormat.KEY_BIT_RATE, 1080 * 1920); 30 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); 31 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); 32 | mediaCodec.configure(format, 33 | surface, 34 | null, 0); 35 | mediaCodec.start(); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | 40 | } 41 | 42 | public void callBack(byte[] data) { 43 | Log.i(BuildConfig.TAG, "接收到消息: " + data.length); 44 | int index = mediaCodec.dequeueInputBuffer(100000); 45 | if (index >= 0) { 46 | ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index); 47 | inputBuffer.clear(); 48 | inputBuffer.put(data, 0, data.length); 49 | //dsp芯片解码 解码 的 传进去的 只需要保证编码顺序就好了 1000 50 | mediaCodec.queueInputBuffer(index, 51 | 0, data.length, System.currentTimeMillis(), 0); 52 | } 53 | // 获取到解码后的数据 编码 ipbn 54 | MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 55 | int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000); 56 | while (outputBufferIndex >= 0) { 57 | mediaCodec.releaseOutputBuffer(outputBufferIndex, true); 58 | outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/video/EncodecVideoCallAcceptLiveH265.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.video; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaCodecInfo; 5 | import android.media.MediaFormat; 6 | 7 | import com.peakmain.video_audio.utils.FileUtils; 8 | import com.peakmain.video_audio.utils.socket.SocketAcceptLive; 9 | 10 | import java.io.IOException; 11 | import java.nio.ByteBuffer; 12 | 13 | /** 14 | * author :Peakmain 15 | * createTime:2021/9/2 16 | * mail:2726449200@qq.com 17 | * describe: 18 | */ 19 | public class EncodecVideoCallAcceptLiveH265 { 20 | private SocketAcceptLive socketLive; 21 | public static final int NAL_I = 19; 22 | public static final int NAL_VPS = 32; 23 | int width; 24 | int height; 25 | //nv21转换成nv12的数据 26 | byte[] nv12; 27 | //旋转之后的yuv数据 28 | byte[] yuv; 29 | private MediaCodec mediaCodec; 30 | int frameIndex; 31 | public EncodecVideoCallAcceptLiveH265(SocketAcceptLive.SocketCallback socketCallback, int width, int height) { 32 | this.socketLive = new SocketAcceptLive(socketCallback); 33 | socketLive.start(); 34 | this.width = width; 35 | this.height = height; 36 | 37 | } 38 | 39 | public void startLive() { 40 | //编码器 41 | try { 42 | mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC); 43 | MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, height, width); 44 | mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height); 45 | mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); 46 | mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); 47 | mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); 48 | mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 49 | mediaCodec.start(); 50 | int bufferLength = width * height * 3 / 2; 51 | nv12 = new byte[bufferLength]; 52 | yuv = new byte[bufferLength]; 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | 57 | } 58 | 59 | public void encodeFrame(byte[] bytes) { 60 | nv12 = FileUtils.nv21toNV12(bytes); 61 | FileUtils.portraitData2Raw(nv12, yuv, width, height); 62 | //解析相机的数据 63 | int inputBufferIndex = mediaCodec.dequeueInputBuffer(100000); 64 | if (inputBufferIndex >= 0) { 65 | ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex); 66 | inputBuffer.clear(); 67 | inputBuffer.put(yuv); 68 | long presentationTimeUs = computePresentationTime(frameIndex); 69 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, yuv.length, presentationTimeUs, 0); 70 | frameIndex++; 71 | } 72 | //编码 73 | MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 74 | int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000); 75 | while (outputBufferIndex >= 0) { 76 | ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex); 77 | dealFrame(outputBuffer, bufferInfo); 78 | mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 79 | outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); 80 | 81 | } 82 | } 83 | 84 | private long computePresentationTime(long frameIndex) { 85 | //帧率是15(自己设置的) 132是偏移量 frameIndex单位是微秒(us) 86 | return 132 + frameIndex * 1_000_000 / 15; 87 | } 88 | private byte[] vps_sps_pps_buf; 89 | private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) { 90 | int offset = 4; 91 | if (bb.get(2) == 0x01) { 92 | offset = 3; 93 | } 94 | int type = (bb.get(offset) & 0x7E) >> 1; 95 | if (type == NAL_VPS) { 96 | vps_sps_pps_buf = new byte[bufferInfo.size]; 97 | bb.get(vps_sps_pps_buf); 98 | } else if (type == NAL_I) { 99 | final byte[] bytes = new byte[bufferInfo.size]; 100 | bb.get(bytes); 101 | byte[] newBuf = new byte[vps_sps_pps_buf.length + bytes.length]; 102 | System.arraycopy(vps_sps_pps_buf, 0, newBuf, 0, vps_sps_pps_buf.length); 103 | System.arraycopy(bytes, 0, newBuf, vps_sps_pps_buf.length, bytes.length); 104 | this.socketLive.sendData(newBuf); 105 | } else { 106 | final byte[] bytes = new byte[bufferInfo.size]; 107 | bb.get(bytes); 108 | this.socketLive.sendData(bytes); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/video/EncodecVideoCallSendLiveH265.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.video; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaCodecInfo; 5 | import android.media.MediaFormat; 6 | 7 | import com.peakmain.video_audio.utils.FileUtils; 8 | import com.peakmain.video_audio.utils.socket.WebSocketSendLive; 9 | 10 | import java.io.IOException; 11 | import java.nio.ByteBuffer; 12 | 13 | /** 14 | * author :Peakmain 15 | * createTime:2021/9/2 16 | * mail:2726449200@qq.com 17 | * describe: 18 | */ 19 | public class EncodecVideoCallSendLiveH265 { 20 | private WebSocketSendLive socketLive; 21 | public static final int NAL_I = 19; 22 | public static final int NAL_VPS = 32; 23 | int width; 24 | int height; 25 | //nv21转换成nv12的数据 26 | byte[] nv12; 27 | //旋转之后的yuv数据 28 | byte[] yuv; 29 | private MediaCodec mediaCodec; 30 | int frameIndex; 31 | public EncodecVideoCallSendLiveH265(WebSocketSendLive.SocketCallback socketCallback, int width, int height) { 32 | this.socketLive = new WebSocketSendLive(socketCallback); 33 | socketLive.start(); 34 | this.width = width; 35 | this.height = height; 36 | 37 | } 38 | 39 | public void startLive() { 40 | //编码器 41 | try { 42 | mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_HEVC); 43 | MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_HEVC, height, width); 44 | mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height); 45 | mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); 46 | mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); 47 | mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); 48 | mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 49 | mediaCodec.start(); 50 | int bufferLength = width * height * 3 / 2; 51 | nv12 = new byte[bufferLength]; 52 | yuv = new byte[bufferLength]; 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | 57 | } 58 | 59 | public void encodeFrame(byte[] bytes) { 60 | nv12 = FileUtils.nv21toNV12(bytes); 61 | FileUtils.portraitData2Raw(nv12, yuv, width, height); 62 | //解析相机的数据 63 | int inputBufferIndex = mediaCodec.dequeueInputBuffer(100000); 64 | if (inputBufferIndex >= 0) { 65 | ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex); 66 | inputBuffer.clear(); 67 | inputBuffer.put(yuv); 68 | long presentationTimeUs = computePresentationTime(frameIndex); 69 | mediaCodec.queueInputBuffer(inputBufferIndex, 0, yuv.length, presentationTimeUs, 0); 70 | frameIndex++; 71 | } 72 | //编码 73 | MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 74 | int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000); 75 | while (outputBufferIndex >= 0) { 76 | ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex); 77 | dealFrame(outputBuffer, bufferInfo); 78 | mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 79 | outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); 80 | 81 | } 82 | } 83 | 84 | private long computePresentationTime(long frameIndex) { 85 | //帧率是15(自己设置的) 132是偏移量 frameIndex单位是微秒(us) 86 | return 132 + frameIndex * 1_000_000 / 15; 87 | } 88 | private byte[] vps_sps_pps_buf; 89 | private void dealFrame(ByteBuffer bb, MediaCodec.BufferInfo bufferInfo) { 90 | int offset = 4; 91 | if (bb.get(2) == 0x01) { 92 | offset = 3; 93 | } 94 | int type = (bb.get(offset) & 0x7E) >> 1; 95 | if (type == NAL_VPS) { 96 | vps_sps_pps_buf = new byte[bufferInfo.size]; 97 | bb.get(vps_sps_pps_buf); 98 | } else if (type == NAL_I) { 99 | final byte[] bytes = new byte[bufferInfo.size]; 100 | bb.get(bytes); 101 | byte[] newBuf = new byte[vps_sps_pps_buf.length + bytes.length]; 102 | System.arraycopy(vps_sps_pps_buf, 0, newBuf, 0, vps_sps_pps_buf.length); 103 | System.arraycopy(bytes, 0, newBuf, vps_sps_pps_buf.length, bytes.length); 104 | this.socketLive.sendData(newBuf); 105 | } else { 106 | final byte[] bytes = new byte[bufferInfo.size]; 107 | bb.get(bytes); 108 | this.socketLive.sendData(bytes); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/widget/CameraSurface.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.ImageFormat; 5 | import android.graphics.Rect; 6 | import android.graphics.YuvImage; 7 | import android.hardware.Camera; 8 | import android.os.Environment; 9 | import android.util.AttributeSet; 10 | import android.view.SurfaceHolder; 11 | import android.view.SurfaceView; 12 | 13 | import com.peakmain.ui.utils.ToastUtils; 14 | import com.peakmain.video_audio.utils.FileUtils; 15 | 16 | import java.io.File; 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | 20 | /** 21 | * author :Peakmain 22 | * createTime:2021/8/27 23 | * mail:2726449200@qq.com 24 | * describe:Camera相机 25 | */ 26 | public class CameraSurface extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback { 27 | private Camera mCamera; 28 | private Camera.Size size; 29 | byte[] mBuffer; 30 | byte[] nv21_rotated; 31 | public CameraSurface(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public CameraSurface(Context context, AttributeSet attrs) { 36 | this(context, attrs, 0); 37 | } 38 | 39 | public CameraSurface(Context context, AttributeSet attrs, int defStyleAttr) { 40 | super(context, attrs, defStyleAttr); 41 | getHolder().addCallback(this); 42 | } 43 | 44 | @Override 45 | public void surfaceCreated(SurfaceHolder holder) { 46 | startPrview(); 47 | } 48 | 49 | private void startPrview() { 50 | //打开相机,后摄像头 51 | mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); 52 | //获取相机相关的参数 53 | Camera.Parameters parameters = mCamera.getParameters(); 54 | size = parameters.getPreviewSize(); 55 | try { 56 | mCamera.setPreviewDisplay(getHolder()); 57 | mCamera.setDisplayOrientation(90); 58 | mBuffer = new byte[size.width * size.height * 3 / 2]; 59 | nv21_rotated = new byte[size.width * size.height * 3 / 2]; 60 | mCamera.addCallbackBuffer(mBuffer); 61 | mCamera.setPreviewCallbackWithBuffer(this); 62 | mCamera.startPreview(); 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | 68 | 69 | private volatile boolean isCaptrue; 70 | 71 | public void startCaptrue() { 72 | isCaptrue = true; 73 | } 74 | 75 | @Override 76 | public void onPreviewFrame(byte[] data, Camera camera) { 77 | if (isCaptrue) { 78 | //nv21_rotated = FileUtils.nv21toNV12(data); 79 | isCaptrue = false; 80 | nv21_rotated = FileUtils.nv21RotateTo90(data, nv21_rotated, size.width, size.height); 81 | captrue(nv21_rotated); 82 | ToastUtils.showLong("保存成功"); 83 | } 84 | mCamera.addCallbackBuffer(data); 85 | } 86 | 87 | int index = 0; 88 | 89 | private void captrue(byte[] bytes) { 90 | String fileName = "Camera_" + index++ + ".jpg"; 91 | File sdRoot = Environment.getExternalStorageDirectory(); 92 | File pictureFile = new File(sdRoot, fileName); 93 | if (!pictureFile.exists()) { 94 | try { 95 | pictureFile.createNewFile(); 96 | FileOutputStream fileOutputStream = new FileOutputStream(pictureFile); 97 | YuvImage image = new YuvImage(bytes, ImageFormat.NV21,size.height, size.width,null); //将NV21 data保存成YuvImage 98 | image.compressToJpeg( 99 | new Rect(0, 0, image.getWidth(), image.getHeight()), 100 | 100, fileOutputStream); 101 | } catch (IOException e) { 102 | e.printStackTrace(); 103 | } 104 | } 105 | } 106 | 107 | 108 | @Override 109 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 110 | 111 | } 112 | 113 | @Override 114 | public void surfaceDestroyed(SurfaceHolder holder) { 115 | 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/widget/VideoCallAcceptSurfaceView.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.widget; 2 | 3 | import android.content.Context; 4 | import android.hardware.Camera; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.view.SurfaceHolder; 8 | import android.view.SurfaceView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.RequiresApi; 12 | 13 | import com.peakmain.video_audio.utils.socket.SocketAcceptLive; 14 | import com.peakmain.video_audio.video.EncodecVideoCallAcceptLiveH265; 15 | 16 | import java.io.IOException; 17 | 18 | /** 19 | * author :Peakmain 20 | * createTime:2021/9/2 21 | * mail:2726449200@qq.com 22 | * describe: 23 | */ 24 | public class VideoCallAcceptSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback { 25 | private Camera.Size size; 26 | private Camera mCamera; 27 | 28 | EncodecVideoCallAcceptLiveH265 encodecPushLiveH265; 29 | 30 | public VideoCallAcceptSurfaceView(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | getHolder().addCallback(this); 33 | } 34 | 35 | @Override 36 | public void surfaceCreated(@NonNull SurfaceHolder holder) { 37 | startPreview(); 38 | } 39 | 40 | byte[] buffer; 41 | 42 | private void startPreview() { 43 | mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); 44 | // 流程 45 | Camera.Parameters parameters = mCamera.getParameters(); 46 | //尺寸 47 | size = parameters.getPreviewSize(); 48 | 49 | try { 50 | mCamera.setPreviewDisplay(getHolder()); 51 | // 横着 52 | mCamera.setDisplayOrientation(90); 53 | //y:u:v=4:1:1 54 | buffer = new byte[size.width * size.height * 3 / 2]; 55 | mCamera.addCallbackBuffer(buffer); 56 | mCamera.setPreviewCallbackWithBuffer(this); 57 | // 输出数据怎么办 58 | mCamera.startPreview(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | @Override 65 | public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { 66 | 67 | } 68 | 69 | @Override 70 | public void surfaceDestroyed(@NonNull SurfaceHolder holder) { 71 | 72 | } 73 | 74 | public void startCaptrue(SocketAcceptLive.SocketCallback socketCallback) { 75 | encodecPushLiveH265 = new EncodecVideoCallAcceptLiveH265(socketCallback, size.width, size.height); 76 | encodecPushLiveH265.startLive(); 77 | 78 | } 79 | 80 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 81 | @Override 82 | public void onPreviewFrame(byte[] bytes, Camera camera) { 83 | if (encodecPushLiveH265 != null) { 84 | encodecPushLiveH265.encodeFrame(bytes); 85 | } 86 | 87 | mCamera.addCallbackBuffer(bytes); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/peakmain/video_audio/widget/VideoCallSendSurfaceView.java: -------------------------------------------------------------------------------- 1 | package com.peakmain.video_audio.widget; 2 | 3 | import android.content.Context; 4 | import android.hardware.Camera; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.view.SurfaceHolder; 8 | import android.view.SurfaceView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.RequiresApi; 12 | 13 | import com.peakmain.video_audio.utils.socket.WebSocketSendLive; 14 | import com.peakmain.video_audio.video.EncodecVideoCallSendLiveH265; 15 | 16 | import java.io.IOException; 17 | 18 | /** 19 | * author:Peakmain 20 | * createTime:2021/8/12 21 | * mail:2726449200@qq.com 22 | * describe: 23 | */ 24 | public class VideoCallSendSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback { 25 | private Camera.Size size; 26 | private Camera mCamera; 27 | EncodecVideoCallSendLiveH265 encodecSendLiveH265; 28 | public VideoCallSendSurfaceView(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | getHolder().addCallback(this); 31 | } 32 | @Override 33 | public void surfaceCreated(@NonNull SurfaceHolder holder) { 34 | startPreview(); 35 | } 36 | byte[] buffer; 37 | private void startPreview() { 38 | mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); 39 | Camera.Parameters parameters = mCamera.getParameters(); 40 | size = parameters.getPreviewSize(); 41 | try { 42 | mCamera.setPreviewDisplay(getHolder()); 43 | mCamera.setDisplayOrientation(90); 44 | buffer = new byte[size.width * size.height * 3 / 2]; 45 | mCamera.addCallbackBuffer(buffer); 46 | mCamera.setPreviewCallbackWithBuffer(this); 47 | mCamera.startPreview(); 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | @Override 53 | public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { 54 | 55 | } 56 | @Override 57 | public void surfaceDestroyed(@NonNull SurfaceHolder holder) { 58 | 59 | } 60 | public void startCaptrue(WebSocketSendLive.SocketCallback socketCallback){ 61 | encodecSendLiveH265 = new EncodecVideoCallSendLiveH265(socketCallback,size.width, size.height); 62 | encodecSendLiveH265.startLive(); 63 | 64 | } 65 | @Override 66 | public void onPreviewFrame(byte[] bytes, Camera camera) { 67 | if (encodecSendLiveH265 != null) { 68 | encodecSendLiveH265.encodeFrame(bytes); 69 | } 70 | mCamera.addCallbackBuffer(bytes); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_accept_video_call.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |