├── .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 |
5 |
6 |
7 |
8 |
9 |
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 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
14 |
15 |
19 |
20 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_camera2x.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
24 |
25 |
32 |
33 |
40 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_h264_player.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
24 |
25 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_mmkv.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
18 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_project_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_send_video_call.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_recyclerview_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Video_Audio
3 | MediaCodec播放H264
4 | 编码输出图片
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
18 |
--------------------------------------------------------------------------------
/app/src/test/java/com/peakmain/video_audio/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.peakmain.video_audio
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.3.72"
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.3'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | maven { url 'https://jitpack.io' }
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Peakmain/Video_Audio/ae4cb3b4c840cd6a33dee100dcd34506809328f0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Aug 20 14:18:31 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/javaLib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/javaLib/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | }
4 |
5 | java {
6 | sourceCompatibility = JavaVersion.VERSION_1_7
7 | targetCompatibility = JavaVersion.VERSION_1_7
8 | }
--------------------------------------------------------------------------------
/javaLib/src/main/java/com/peakmain/javalib/H264Parse.java:
--------------------------------------------------------------------------------
1 | package com.peakmain.javalib;
2 |
3 | /**
4 | * author:Peakmain
5 | * createTime:2021/8/26
6 | * mail:2726449200@qq.com
7 | * describe:哥伦布算法
8 | */
9 | public class H264Parse {
10 | /**
11 | * 5还原成4
12 | */
13 | public void test() {
14 | //5的字节码是101,转成8位字节码
15 | byte data = 6 & 0xFF;
16 | //0000 0101
17 | int i = 3;
18 | //统计0的个数
19 | int zeroNum = 0;
20 | while (i < 8) {
21 | //找到第一个不为0
22 | if ((data & (0x80 >> i)) != 0) {
23 | break;
24 | }
25 | zeroNum++;
26 | i++;
27 | }
28 | i++;
29 | //找到第一个不为0之后,往后找zeroNum个数
30 | int value = 0;
31 | for (int j = 0; j < zeroNum; j++) {
32 | value <<= 1;
33 | //找到元素不为0 的个数
34 | if ((data & (0x80 >> i)) != 0) {
35 | value += 1;
36 | }
37 | i++;
38 | }
39 | int result = (1 << zeroNum) + value - 1;
40 | System.out.println(result);
41 | }
42 |
43 | //哥伦布编码
44 | public static int columbusCode(byte[] pBuff) {
45 | //统计0的个数
46 | int zeroNum = 0;
47 | while (i < pBuff.length * 8) {
48 | //找到第一个不为0
49 | if ((pBuff[i / 8] & (0x80 >> (i % 8))) != 0) {
50 | break;
51 | }
52 | zeroNum++;
53 | i++;
54 | }
55 | i++;
56 | //找到第一个不为0之后,往后找zeroNum个数
57 | int value = 0;
58 | for (int j = 0; j < zeroNum; j++) {
59 | value <<= 1;
60 | //找到元素不为0 的个数
61 | if ((pBuff[i / 8] & (0x80 >> (i % 8))) != 0) {
62 | value += 1;
63 | }
64 | i++;
65 | }
66 | int result = (1 << zeroNum) + value - 1;
67 | return result;
68 | }
69 |
70 | public static byte[] hexStringToByteArray(String s) {
71 | //十六进制转byte数组
72 | int len = s.length();
73 | byte[] bs = new byte[len / 2];
74 | for (int i = 0; i < len; i += 2) {
75 | bs[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
76 | }
77 | return bs;
78 | }
79 |
80 |
81 | static int i = 0;
82 |
83 | /**
84 | * @bitIndex 字节数位数
85 | */
86 | private static int paraseH264(int bitIndex, byte[] h264) {
87 | int value = 0;
88 | for (int j = 0; j < bitIndex; j++) {
89 | value <<= 1;
90 | //获取到每个字节
91 | if ((h264[i / 8] & (0x80 >> (i % 8))) != 0) {
92 | value += 1;
93 | }
94 | i++;
95 | }
96 | return value;
97 | }
98 |
99 | public static void main(String[] args) {
100 | //十六进制转byte数组
101 | byte[] h264 = hexStringToByteArray("00 00 00 01 67 64 00 15 AC D9 41 70 C6 84 00 00 03 00 04 00 00 03 00 F0 3C 58 B6 58".replace(" ", ""));
102 | i = 4 * 8;
103 | //禁止位
104 | int forbidden_zero_bit = paraseH264(1, h264);
105 | System.out.println("forbidden_zero_bit:" + forbidden_zero_bit);
106 | //重要性
107 | int nal_ref_idc = paraseH264(2, h264);
108 | System.out.println("nal_ref_idc:" + nal_ref_idc);
109 | //帧类型
110 | int nal_unit_type = paraseH264(5, h264);
111 | System.out.println("nal_unit_type:" + nal_unit_type);
112 | if (nal_unit_type == 7) {
113 | //编码等级
114 | int profile_idc = paraseH264(8, h264);
115 | System.out.println("profile_idc:" + profile_idc);
116 | //64后面的(00),基本用不到
117 | //当constrained_set0_flag值为1的时候,就说明码流应该遵循基线profile(Baseline profile)的所有约束.constrained_set0_flag值为0时,说明码流不一定要遵循基线profile的所有约束。
118 | int constraint_set0_flag = paraseH264(1, h264);//(h264[1] & 0x80)>>7;
119 | // 当constrained_set1_flag值为1的时候,就说明码流应该遵循主profile(Main profile)的所有约束.constrained_set1_flag值为0时,说明码流不一定要遵
120 | int constraint_set1_flag = paraseH264(1, h264);//(h264[1] & 0x40)>>6;
121 | //当constrained_set2_flag值为1的时候,就说明码流应该遵循扩展profile(Extended profile)的所有约束.constrained_set2_flag值为0时,说明码流不一定要遵循扩展profile的所有约束。
122 | int constraint_set2_flag = paraseH264(1, h264);//(h264[1] & 0x20)>>5;
123 | //注意:当constraint_set0_flag,constraint_set1_flag或constraint_set2_flag中不只一个值为1的话,那么码流必须满足所有相应指明的profile约束。
124 | int constraint_set3_flag = paraseH264(1, h264);//(h264[1] & 0x10)>>4;
125 | // 4个零位
126 | int reserved_zero_4bits = paraseH264(4, h264);
127 |
128 | //码流等级
129 | int level_idc = paraseH264(8, h264);
130 | System.out.println("level_idc:" + level_idc);
131 | //(AC)就是哥伦布编码
132 | int seq_parameter_set_id = columbusCode(h264);
133 | System.out.println("seq_parameter_set_id:" + seq_parameter_set_id);
134 | if (profile_idc == 100) {
135 | int chroma_format_idc = columbusCode(h264);
136 | System.out.println("chroma_format_idc:" + chroma_format_idc);
137 | int bit_depth_luma_minus8 = columbusCode(h264);
138 | System.out.println("bit_depth_luma_minus8:" + bit_depth_luma_minus8);
139 | int bit_depth_chroma_minus8 = columbusCode(h264);
140 | System.out.println("bit_depth_chroma_minus8:" + bit_depth_chroma_minus8);
141 | int qpprime_y_zero_transform_bypass_flag = paraseH264(1, h264);
142 | System.out.println("qpprime_y_zero_transform_bypass_flag:" + qpprime_y_zero_transform_bypass_flag);
143 | //缩放标志位
144 | int seq_scaling_matrix_present_flag = paraseH264(1, h264);
145 | System.out.println("seq_scaling_matrix_present_flag:" + seq_scaling_matrix_present_flag);
146 | }
147 | //最大帧率
148 | int log2_max_frame_num_minus4 = columbusCode(h264);
149 | System.out.println("log2_max_frame_num_minus4:" + log2_max_frame_num_minus4);
150 | //确定播放顺序和解码顺序的映射
151 | int pic_order_cnt_type = columbusCode(h264);
152 | System.out.println("pic_order_cnt_type:" + pic_order_cnt_type);
153 | int log2_max_pic_order_cnt_lsb_minus4 = columbusCode(h264);
154 | System.out.println("log2_max_pic_order_cnt_lsb_minus4:" + log2_max_pic_order_cnt_lsb_minus4);
155 | int num_ref_frames = columbusCode(h264);
156 | System.out.println("num_ref_frames:" + num_ref_frames);
157 | int gaps_in_frame_num_value_allowed_flag = paraseH264(1, h264);
158 | System.out.println("gaps_in_frame_num_value_allowed_flag:" + gaps_in_frame_num_value_allowed_flag);
159 | System.out.println("------startBit " + i);
160 | int pic_width_in_mbs_minus1 = columbusCode(h264);
161 | System.out.println("------startBit " + i);
162 | int pic_height_in_map_units_minus1 = columbusCode(h264);
163 | int width = (pic_width_in_mbs_minus1 + 1) * 16;
164 | int height = (pic_height_in_map_units_minus1 + 1) * 16;
165 | System.out.println("width : " + width + " height: " + height);
166 | }
167 | }
168 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':javaLib'
2 | include ':app'
3 | rootProject.name = "Video_Audio"
--------------------------------------------------------------------------------