├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── dbnavigator.xml
├── encodings.xml
├── misc.xml
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── devyk
│ │ └── av
│ │ └── rtmppush
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── devyk
│ │ │ └── av
│ │ │ └── rtmppush
│ │ │ ├── LiveActivity.kt
│ │ │ ├── SPUtils.java
│ │ │ ├── Utils.java
│ │ │ └── base
│ │ │ └── BaseActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_live.xml
│ │ └── address_dialog.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── camera_change.png
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── live.png
│ │ ├── stop.png
│ │ └── watemark.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ └── live_logo.jpeg
│ │ ├── 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
│ └── devyk
│ └── av
│ └── rtmppush
│ └── ExampleUnitTest.kt
├── build.gradle
├── config.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── devyk
│ │ └── av
│ │ └── rtmp
│ │ └── library
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ │ ├── CMakeLists.txt
│ │ ├── callback
│ │ │ ├── JavaCallback.cpp
│ │ │ └── JavaCallback.h
│ │ ├── common
│ │ │ ├── IObserver.cpp
│ │ │ ├── IObserver.h
│ │ │ ├── IPush.cpp
│ │ │ ├── IPush.h
│ │ │ ├── IThread.cpp
│ │ │ ├── IThread.h
│ │ │ ├── PushProxy.cpp
│ │ │ └── PushProxy.h
│ │ ├── jni
│ │ │ └── native_rtmp_push.cpp
│ │ ├── librtmp
│ │ │ ├── include
│ │ │ │ ├── amf.h
│ │ │ │ ├── http.h
│ │ │ │ ├── log.h
│ │ │ │ └── rtmp.h
│ │ │ └── libs
│ │ │ │ ├── arm64-v8a
│ │ │ │ └── librtmp.a
│ │ │ │ └── armeabi-v7a
│ │ │ │ └── librtmp.a
│ │ └── push
│ │ │ ├── AVQueue.cpp
│ │ │ ├── AVQueue.h
│ │ │ ├── RTMPPush.cpp
│ │ │ └── RTMPPush.h
│ ├── java
│ │ └── com
│ │ │ └── devyk
│ │ │ └── av
│ │ │ └── rtmp
│ │ │ └── library
│ │ │ ├── Contacts.kt
│ │ │ ├── annotation
│ │ │ └── RendererMode.kt
│ │ │ ├── audio
│ │ │ ├── AudioProcessor.kt
│ │ │ └── AudioUtils.kt
│ │ │ ├── black
│ │ │ └── BlackListHelper.kt
│ │ │ ├── callback
│ │ │ ├── ICameraOpenListener.kt
│ │ │ ├── IController.kt
│ │ │ ├── IGLThreadConfig.kt
│ │ │ ├── ILog.kt
│ │ │ ├── IRenderer.kt
│ │ │ ├── OnAudioDataListener.kt
│ │ │ ├── OnAudioEncodeListener.kt
│ │ │ ├── OnConnectListener.kt
│ │ │ └── OnVideoEncodeListener.kt
│ │ │ ├── camera
│ │ │ ├── CameraData.kt
│ │ │ ├── CameraHolder.kt
│ │ │ ├── CameraRecorder.kt
│ │ │ ├── CameraUtils.kt
│ │ │ ├── EglHelper.kt
│ │ │ ├── GLThread.kt
│ │ │ ├── ShaderHelper.kt
│ │ │ ├── Watermark.kt
│ │ │ ├── exception
│ │ │ │ ├── CameraDisabledException.kt
│ │ │ │ ├── CameraHardwareException.kt
│ │ │ │ ├── CameraNotSupportException.kt
│ │ │ │ ├── NoCameraException.java
│ │ │ │ └── NoCameraException.kt
│ │ │ └── renderer
│ │ │ │ ├── CameraRenderer.kt
│ │ │ │ ├── DefaultRenderer.kt
│ │ │ │ ├── EncodeRenderer.kt
│ │ │ │ └── FboRenderer.kt
│ │ │ ├── common
│ │ │ ├── IThread.kt
│ │ │ └── ThreadImpl.kt
│ │ │ ├── config
│ │ │ ├── AudioConfiguration.kt
│ │ │ ├── CameraConfiguration.kt
│ │ │ ├── RendererConfiguration.kt
│ │ │ └── VideoConfiguration.kt
│ │ │ ├── controller
│ │ │ ├── AudioController.kt
│ │ │ ├── StreamController.kt
│ │ │ └── VideoController.kt
│ │ │ ├── mediacodec
│ │ │ ├── AudioEncoder.kt
│ │ │ ├── AudioMediaCodec.kt
│ │ │ ├── BaseAudioCodec.kt
│ │ │ ├── BaseVideoEncoder.kt
│ │ │ ├── IAudioCodec.kt
│ │ │ ├── IVideoCodec.kt
│ │ │ ├── VideoEncoder.kt
│ │ │ └── VideoMediaCodec.kt
│ │ │ ├── stream
│ │ │ ├── AnnexbHelper.kt
│ │ │ ├── PacketType.kt
│ │ │ ├── amf
│ │ │ │ ├── AmfArray.java
│ │ │ │ ├── AmfBoolean.java
│ │ │ │ ├── AmfData.java
│ │ │ │ ├── AmfDecoder.java
│ │ │ │ ├── AmfMap.java
│ │ │ │ ├── AmfNull.java
│ │ │ │ ├── AmfNumber.java
│ │ │ │ ├── AmfObject.java
│ │ │ │ ├── AmfString.java
│ │ │ │ ├── AmfType.java
│ │ │ │ ├── AmfUndefined.java
│ │ │ │ └── Util.java
│ │ │ ├── packer
│ │ │ │ ├── DefaultPacker.kt
│ │ │ │ ├── Packer.kt
│ │ │ │ ├── flv
│ │ │ │ │ └── FlvPackerHelper.java
│ │ │ │ └── rtmp
│ │ │ │ │ └── RtmpPacker.kt
│ │ │ └── sender
│ │ │ │ ├── Sender.kt
│ │ │ │ └── rtmp
│ │ │ │ └── RtmpSender.kt
│ │ │ ├── utils
│ │ │ ├── BitmapUtils.kt
│ │ │ └── LogHelper.kt
│ │ │ └── widget
│ │ │ ├── AVLiveView.kt
│ │ │ ├── CameraView.kt
│ │ │ └── GLSurfaceView.kt
│ └── res
│ │ ├── layout
│ │ └── include_live.xml
│ │ ├── raw
│ │ ├── fragment_shader.glsl
│ │ ├── fragment_shader_camera.glsl
│ │ ├── vertex_shader.glsl
│ │ └── vertex_shader_matrix.glsl
│ │ └── values
│ │ ├── attrs.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── devyk
│ └── av
│ └── rtmp
│ └── library
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # FILE
11 | file/
12 | *.yuv
13 | *.h264
14 | *.pcm
15 | *.aac
16 |
17 | # Java class files
18 | *.class
19 |
20 | # Generated files
21 | bin/
22 | gen/
23 | out/
24 | # Uncomment the following line in case you need and you don't have the release build type files in your app
25 | # release/
26 |
27 | # Gradle files
28 | .gradle/
29 | build/
30 |
31 | # Local configuration file (sdk path, etc)
32 | local.properties
33 |
34 | # Proguard folder generated by Eclipse
35 | proguard/
36 |
37 | # Log Files
38 | *.log
39 |
40 | # Android Studio Navigation editor temp files
41 | .navigation/
42 |
43 | # Android Studio captures folder
44 | captures/
45 |
46 | # IntelliJ
47 | *.iml
48 | .idea/workspace.xml
49 | .idea/tasks.xml
50 | .idea/gradle.xml
51 | .idea/assetWizardSettings.xml
52 | .idea/dictionaries
53 | .idea/libraries
54 | # Android Studio 3 in .gitignore file.
55 | .idea/caches
56 | .idea/modules.xml
57 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
58 | .idea/navEditor.xml
59 |
60 | # Keystore files
61 | # Uncomment the following lines if you do not want to check your keystore files in.
62 | #*.jks
63 | #*.keystore
64 |
65 | # External native build folder generated in Android Studio 2.2 and later
66 | .externalNativeBuild
67 | .cxx/
68 |
69 | # Google Services (e.g. APIs or Firebase)
70 | # google-services.json
71 |
72 | # Freeline
73 | freeline.py
74 | freeline/
75 | freeline_project_description.json
76 |
77 | # fastlane
78 | fastlane/report.xml
79 | fastlane/Preview.html
80 | fastlane/screenshots
81 | fastlane/test_output
82 | fastlane/readme.md
83 |
84 | # Version control
85 | vcs.xml
86 |
87 | # lint
88 | lint/intermediates/
89 | lint/generated/
90 | lint/outputs/
91 | lint/tmp/
92 | # lint/reports/
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
8 | * author : devyk on 2020-07-16 22:32 9 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 10 | * github : https://github.com/yangkun19921001 11 | * mailbox : yang1001yk@gmail.com 12 | * desc : This is Utils 13 | *14 | */ 15 | class Utils { 16 | private static Application sApp; 17 | 18 | public static Context getApp() { 19 | return sApp; 20 | } 21 | 22 | public static void init(Application application) { 23 | sApp = application; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/devyk/av/rtmppush/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.ikavedit.base 2 | 3 | import android.annotation.SuppressLint 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import android.os.SystemClock 7 | import android.view.Choreographer 8 | import android.view.View 9 | import android.view.Window 10 | import android.view.WindowManager 11 | import android.widget.Chronometer 12 | import android.widget.Toast 13 | import androidx.appcompat.app.AppCompatActivity 14 | import com.devyk.av.rtmppush.R 15 | import com.devyk.av.rtmppush.SPUtils 16 | import com.tbruyelle.rxpermissions2.RxPermissions 17 | 18 | /** 19 | *
20 | * author : devyk on 2020-05-24 23:40 21 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 22 | * github : https://github.com/yangkun19921001 23 | * mailbox : yang1001yk@gmail.com 24 | * desc : This is BaseActivity 25 | *26 | */ 27 | 28 | abstract class BaseActivity
5 | * author : devyk on 2020-07-15 21:44 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is Contacts 10 | *11 | */ 12 | public object Contacts { 13 | public var TAG = "AVRtmpPush" 14 | //rtmp 初始化失败 15 | var RTMP_INIT_ERROR = -9 16 | //设置 rtmp url 失败 17 | var RTMP_SET_URL_ERROR = -10 18 | //连接服务器失败 19 | var RTMP_CONNECT_ERROR = -11 20 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/annotation/RendererMode.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.annotation 2 | 3 | import androidx.annotation.IntDef 4 | import com.devyk.av.rtmp.library.widget.GLSurfaceView 5 | import java.lang.annotation.Retention 6 | import java.lang.annotation.RetentionPolicy 7 | 8 | /** 9 | *
10 | * author : devyk on 2020-07-06 11:35 11 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 12 | * github : https://github.com/yangkun19921001 13 | * mailbox : yang1001yk@gmail.com 14 | * desc : This is RendererMode 用于 OpenGL ES 渲染模式 @link com.devyk.av.camera_recorder.widget.base.GLSurfaceView 15 | *16 | */ 17 | @IntDef(GLSurfaceView.RENDERERMODE_WHEN_DIRTY, GLSurfaceView.RENDERERMODE_CONTINUOUSLY) 18 | @Target(AnnotationTarget.VALUE_PARAMETER) //用于参数上 19 | @Retention(RetentionPolicy.SOURCE) //编译器 20 | annotation class RendererMode {} -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/audio/AudioProcessor.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.audio 2 | 3 | import android.media.AudioFormat 4 | import android.media.MediaRecorder 5 | import com.devyk.av.rtmp.library.Contacts 6 | import com.devyk.av.rtmp.library.audio.AudioUtils.AUDIO_CHANNEL_CONFIG 7 | import com.devyk.av.rtmp.library.audio.AudioUtils.AUDIO_FROMAT 8 | import com.devyk.av.rtmp.library.audio.AudioUtils.SAMPLE_RATE_IN_HZ 9 | import com.devyk.av.rtmp.library.audio.AudioUtils.getBufferSize 10 | import com.devyk.av.rtmp.library.common.ThreadImpl 11 | import com.devyk.av.rtmp.library.utils.LogHelper 12 | import java.util.* 13 | 14 | /** 15 | *
16 | * author : devyk on 2020-07-15 21:16 17 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 18 | * github : https://github.com/yangkun19921001 19 | * mailbox : yang1001yk@gmail.com 20 | * desc : This is AudioProcessor 21 | *22 | */ 23 | public class AudioProcessor : ThreadImpl() { 24 | /** 25 | * 读取大小 26 | */ 27 | private var mReadSize = 1024; 28 | 29 | /** 30 | * 录制监听 31 | */ 32 | private var mRecordListener: OnRecordListener? = null 33 | 34 | 35 | /** 36 | * Java 中的锁 37 | */ 38 | private val mLock = java.lang.Object() 39 | 40 | /** 41 | * 是否禁言 42 | */ 43 | private var isMute = false 44 | 45 | /** 46 | * 初始化 47 | */ 48 | public fun init( 49 | audioSource: Int = AudioUtils.AUDIO_SOURCE, 50 | sampleRateInHz: Int = AudioUtils.SAMPLE_RATE_IN_HZ, 51 | channelConfig: Int = AudioUtils.AUDIO_CHANNEL_CONFIG, 52 | audioFormat: Int = AudioUtils.AUDIO_FROMAT 53 | ) { 54 | try { 55 | if (AudioUtils.initAudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat)) { 56 | mReadSize = getBufferSize() 57 | } 58 | } catch (error: Exception) { 59 | mRecordListener?.onError(error.message) 60 | LogHelper.e(Contacts.TAG, error.message) 61 | } 62 | } 63 | 64 | override fun setPause(pause: Boolean) { 65 | super.setPause(pause) 66 | if (pause == true) { 67 | mRecordListener?.onPause() 68 | } else { 69 | mLock.notifyAll() 70 | mRecordListener?.onResume() 71 | } 72 | } 73 | 74 | 75 | /** 76 | * 开始执行 77 | */ 78 | fun startRcording() { 79 | super.start { main() } 80 | AudioUtils.startRecord() 81 | mRecordListener?.onStart() 82 | } 83 | 84 | 85 | override fun stop() { 86 | super.stop() 87 | AudioUtils.stopRecord() 88 | mRecordListener?.onStop() 89 | } 90 | 91 | /** 92 | * 设置禁言 93 | */ 94 | public fun setMute(mute: Boolean) { 95 | this.isMute = mute 96 | } 97 | 98 | public fun isMute() = isMute 99 | 100 | /** 101 | * 子线程执行的函数入口 102 | */ 103 | public fun main() { 104 | var data = ByteArray(mReadSize); 105 | while (isRuning()) { 106 | val name = Thread.currentThread().name 107 | synchronized(mLock) { 108 | 109 | if (isPause()) { 110 | mLock.wait() 111 | } 112 | 113 | if (isMute()) { 114 | Arrays.fill(data, 0) 115 | mRecordListener?.onPcmData(data) 116 | return@synchronized 117 | } 118 | 119 | 120 | if (AudioUtils.read(data.size, data) > 0) { 121 | mRecordListener?.onPcmData(data) 122 | } 123 | } 124 | } 125 | } 126 | 127 | 128 | public fun addRecordListener(listener: OnRecordListener) { 129 | mRecordListener = listener 130 | } 131 | 132 | public interface OnRecordListener { 133 | fun onStart( 134 | sampleRate: Int = SAMPLE_RATE_IN_HZ, 135 | channels: Int = AUDIO_CHANNEL_CONFIG, 136 | sampleFormat: Int = AUDIO_FROMAT 137 | ) 138 | fun onError(meg: String?) 139 | fun onPcmData(byteArray: ByteArray); 140 | fun onPause(){} 141 | fun onResume(){} 142 | fun onStop(){} 143 | } 144 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/audio/AudioUtils.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.audio 2 | 3 | import android.media.AudioFormat 4 | import android.media.AudioRecord 5 | import android.media.MediaRecorder 6 | import android.util.Log 7 | import java.lang.RuntimeException 8 | import java.nio.ByteBuffer 9 | 10 | /** 11 | *
12 | * author : devyk on 2020-07-15 21:10 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is AudioUtils 17 | *18 | */ 19 | 20 | /** 21 | * 调用 startRecording 开始录音,调用 stopRecord 停止录音 22 | * 利用adb pull 导出PCM文件 23 | * adb pull record . 24 | * 利用ffplay播放声音 25 | * ffplay -f s16le -sample_rate 44100 -channels 1 -i /record.pcm 26 | * 利用ffmpeg将PCM文件转换为WAV文件 27 | * ffmpeg -f s16le -sample_rate 44100 -channels 1 -i record.pcm -acodec pcm_s16le record.wav 28 | */ 29 | public object AudioUtils { 30 | 31 | 32 | private var TAG = javaClass.simpleName; 33 | 34 | 35 | /** 36 | * 录音对象 37 | * @see AudioRecord 38 | */ 39 | private var mAudioRecord: AudioRecord? = null; 40 | 41 | /** 42 | * 声音通道 43 | * 默认 单声道 44 | * @see AudioFormat.CHANNEL_IN_MONO 单声道 45 | * @see AudioFormat.CHANNEL_IN_STEREO 立体声 46 | */ 47 | public var AUDIO_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO 48 | 49 | /** 50 | * 采样率 如果 AudioRecord 初始化失败,那么可以降低为 16000 ,或者检查权限是否开启 51 | * 默认 44100 52 | */ 53 | public var SAMPLE_RATE_IN_HZ = 44100 54 | 55 | /** 56 | * 采样格式 57 | * 默认 16bit 存储 58 | * 59 | * @see AudioFormat.ENCODING_PCM_16BIT 兼容大部分手机 60 | */ 61 | public var AUDIO_FROMAT = AudioFormat.ENCODING_PCM_16BIT 62 | 63 | /** 64 | * 录音源 65 | * @see MediaRecorder.AudioSource.MIC 手机麦克风 66 | * @see MediaRecorder.AudioSource.VOICE_RECOGNITION 用于语音识别,等同于默认 67 | * @see MediaRecorder.AudioSource.VOICE_COMMUNICATION 用于 VOIP 应用 68 | */ 69 | public var AUDIO_SOURCE = MediaRecorder.AudioSource.MIC; 70 | 71 | /** 72 | * 配置内部音频缓冲区的大小,由于不同厂商会有不同的实现。那么我们可以通过一个静态函数来 getMinBufferSize 来定义 73 | * @see AudioRecord.getMinBufferSize 74 | */ 75 | private var mBufferSizeInBytes = 0; 76 | 77 | 78 | /** 79 | * 获取音频缓冲区大小 80 | */ 81 | public fun getMinBufferSize( 82 | sampleRateInHz: Int = SAMPLE_RATE_IN_HZ, 83 | channelConfig: Int = AUDIO_CHANNEL_CONFIG, 84 | audioFormat: Int = AUDIO_FROMAT 85 | ): Int = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); 86 | 87 | /** 88 | * 拿到 AudioRecord 对象 89 | */ 90 | public fun initAudioRecord( 91 | audioSource: Int = AUDIO_SOURCE, 92 | sampleRateInHz: Int = SAMPLE_RATE_IN_HZ, 93 | channelConfig: Int = AUDIO_CHANNEL_CONFIG, 94 | audioFormat: Int = AUDIO_FROMAT 95 | ): Boolean { 96 | this.AUDIO_FROMAT = audioFormat 97 | this.AUDIO_CHANNEL_CONFIG = channelConfig 98 | this.AUDIO_SOURCE = audioSource 99 | this.SAMPLE_RATE_IN_HZ = sampleRateInHz 100 | 101 | //如果 AudioRecord 不为 null 那么直接销毁 102 | mAudioRecord?.run { 103 | release(); 104 | } 105 | try { 106 | //得到录音缓冲大小 107 | mBufferSizeInBytes = getMinBufferSize(sampleRateInHz, channelConfig, audioFormat) 108 | mAudioRecord = AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, mBufferSizeInBytes) 109 | } catch (error: Exception) { 110 | Log.e(TAG, "AudioRecord init error :${error.message}") 111 | return false 112 | } 113 | 114 | //如果初始化失败那么降低采样率 115 | if (mAudioRecord == null || mAudioRecord?.state != AudioRecord.STATE_INITIALIZED) { 116 | throw RuntimeException("检查音频源是否为占用,或者是否打开录音权限?") 117 | } 118 | return true 119 | } 120 | 121 | /** 122 | * 拿到录音设备 123 | */ 124 | public fun getAudioRecord(): AudioRecord? = mAudioRecord 125 | 126 | /** 127 | * 开始录制 128 | */ 129 | public fun startRecord() { 130 | mAudioRecord?.run { 131 | if (state == AudioRecord.STATE_INITIALIZED) 132 | startRecording() 133 | } 134 | 135 | } 136 | 137 | /** 138 | * 开始录制 139 | */ 140 | public fun stopRecord() { 141 | mAudioRecord?.run { 142 | if (mAudioRecord?.state == AudioRecord.STATE_INITIALIZED) 143 | stop() 144 | } 145 | } 146 | 147 | /** 148 | * 释放资源 149 | */ 150 | public fun releaseRecord() { 151 | mAudioRecord?.run { 152 | release() 153 | } 154 | mAudioRecord = null 155 | } 156 | 157 | /** 158 | * 读取音频数据 159 | */ 160 | public fun read(bufferSize: Int = mBufferSizeInBytes, offsetInBytes: Int = 0, byte: ByteArray): Int { 161 | var ret = 0; 162 | mAudioRecord?.run { 163 | ret = read(byte, offsetInBytes, bufferSize) 164 | } 165 | return ret; 166 | } 167 | 168 | /** 169 | * 读取音频数据 170 | */ 171 | public fun read(bufferSize: Int = mBufferSizeInBytes, offsetInBytes: Int = 0, short: ShortArray): Int { 172 | var ret = 0; 173 | mAudioRecord?.run { 174 | ret = read(short, offsetInBytes, bufferSize) 175 | } 176 | return ret; 177 | } 178 | 179 | /** 180 | * 读取音频数据 181 | */ 182 | public fun read(bufferSize: Int = mBufferSizeInBytes, buffer: ByteBuffer): Int { 183 | var ret = 0; 184 | mAudioRecord?.run { 185 | ret = read(buffer, bufferSize) 186 | } 187 | return ret; 188 | } 189 | 190 | /** 191 | * 读取音频数据 192 | */ 193 | public fun read(bufferSize: Int = mBufferSizeInBytes, buffer: ByteArray): Int { 194 | var ret = 0; 195 | mAudioRecord?.run { 196 | ret = read(buffer, 0, bufferSize) 197 | } 198 | return ret; 199 | } 200 | 201 | 202 | /** 203 | * 拿到缓冲大小 204 | */ 205 | public fun getBufferSize(): Int = mBufferSizeInBytes 206 | 207 | 208 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/black/BlackListHelper.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.black 2 | 3 | import android.os.Build 4 | import android.text.TextUtils 5 | import java.util.* 6 | 7 | /** 8 | *
9 | * author : devyk on 2020-06-14 22:11 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is BlackListHelper 14 | *15 | */ 16 | 17 | object BlackListHelper { 18 | private val BLACKLISTED_AEC_MODELS = arrayOf("Nexus 5")// Nexus 5 19 | 20 | private val BLACKLISTED_FPS_MODELS = arrayOf("OPPO R9", "Nexus 6P") 21 | 22 | 23 | fun deviceInAecBlacklisted(): Boolean { 24 | val blackListedModels = Arrays.asList(*BLACKLISTED_AEC_MODELS) 25 | for (blackModel in blackListedModels) { 26 | val model = Build.MODEL 27 | if (!TextUtils.isEmpty(model) && model.contains(blackModel)) { 28 | return true 29 | } 30 | } 31 | return false 32 | } 33 | 34 | fun deviceInFpsBlacklisted(): Boolean { 35 | val blackListedModels = Arrays.asList(*BLACKLISTED_FPS_MODELS) 36 | for (blackModel in blackListedModels) { 37 | val model = Build.MODEL 38 | if (!TextUtils.isEmpty(model) && model.contains(blackModel)) { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/ICameraOpenListener.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.callback 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-07-08 21:35 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is ICameraOpenListener 10 | *11 | */ 12 | 13 | public interface ICameraOpenListener { 14 | fun onCameraOpen() 15 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/IController.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.callback 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | *
9 | * author : devyk on 2020-07-15 22:13 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is IController 14 | *15 | */ 16 | public interface IController { 17 | 18 | fun start() 19 | 20 | fun pause() 21 | 22 | fun resume() 23 | 24 | fun stop() 25 | 26 | fun setMute(isMute: Boolean) {} 27 | 28 | fun setAudioDataListener(audioDataListener: OnAudioDataListener) {} 29 | fun setVideoDataListener(videoDataListener: OnVideoDataListener) {} 30 | fun setVideoBps(bps:Int){} 31 | 32 | 33 | public interface OnAudioDataListener { 34 | /** 35 | * 当 Audio 编码数据的时候 36 | */ 37 | fun onAudioData(bb: ByteBuffer, bi: MediaCodec.BufferInfo); 38 | 39 | /** 40 | * 编码的输出格式 41 | */ 42 | fun onAudioOutformat(outputFormat: MediaFormat?) 43 | 44 | fun onError(error:String?); 45 | 46 | 47 | } 48 | 49 | public interface OnVideoDataListener { 50 | /** 51 | * 当 Audio 编码数据的时候 52 | */ 53 | fun onVideoData(bb: ByteBuffer?, bi: MediaCodec.BufferInfo?); 54 | 55 | /** 56 | * 编码的输出格式 57 | */ 58 | fun onVideoOutformat(outputFormat: MediaFormat?); 59 | 60 | fun onError(error:String?); 61 | } 62 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/IGLThreadConfig.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.camera_recorder.callback 2 | 3 | import android.view.Surface 4 | import javax.microedition.khronos.egl.EGLContext 5 | 6 | /** 7 | *
8 | * author : devyk on 2020-07-08 20:51 9 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 10 | * github : https://github.com/yangkun19921001 11 | * mailbox : yang1001yk@gmail.com 12 | * desc : This is IGLThreadConfig ELThread 需要的配置 13 | *14 | */ 15 | public interface IGLThreadConfig { 16 | /** 17 | * 拿到渲染器 18 | */ 19 | fun getRenderer(): IRenderer? 20 | 21 | /** 22 | * 拿到渲染的 Surface 23 | */ 24 | fun getSurface(): Surface? 25 | 26 | /** 27 | * 拿到 EGL 环境的上下文 28 | */ 29 | fun getEGLContext(): EGLContext? 30 | 31 | /** 32 | * 拿到渲染模式 33 | */ 34 | fun getRendererMode(): Int 35 | 36 | 37 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/ILog.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.callback 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-07-15 21:26 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is ILog 10 | *11 | */ 12 | public interface ILog { 13 | fun i(tag: String = javaClass.simpleName, info: String?); 14 | fun e(tag: String = javaClass.simpleName, info: String?); 15 | fun w(tag: String = javaClass.simpleName, info: String?); 16 | fun d(tag: String = javaClass.simpleName, info: String?); 17 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/IRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.camera_recorder.callback 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-07-06 11:05 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is IRenderer 10 | * 11 | * 12 | * OpenGL ES 坐标系: 13 | * @see  14 | *15 | */ 16 | public interface IRenderer { 17 | /** 18 | * 当 Surface 创建的时候 19 | */ 20 | public fun onSurfaceCreate(width: Int, height: Int); 21 | 22 | /** 23 | * 当 surface 窗口改变的时候 24 | */ 25 | public fun onSurfaceChange(width: Int, height: Int); 26 | 27 | /** 28 | * 绘制的时候 29 | */ 30 | public fun onDraw(); 31 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/OnAudioDataListener.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.callback 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-07-16 10:30 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is OnAudioDataListener 10 | *11 | */ -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/OnAudioEncodeListener.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.callback 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | *
9 | * author : devyk on 2020-06-13 16:09 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is OnAudioEncodeListener 14 | *15 | */ 16 | public interface OnAudioEncodeListener { 17 | fun onAudioEncode(bb: ByteBuffer, bi: MediaCodec.BufferInfo) 18 | fun onAudioOutformat(outputFormat: MediaFormat?) 19 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/OnConnectListener.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.callback 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-07-16 23:10 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is OnConnectListener 10 | *11 | */ 12 | public interface OnConnectListener { 13 | /** 14 | * 开始链接 15 | */ 16 | fun onConnecting() 17 | 18 | /** 19 | * 连接成功 20 | */ 21 | fun onConnected() 22 | 23 | /** 24 | * 推送失败 25 | */ 26 | fun onFail(message:String) 27 | 28 | /** 29 | * 关闭 30 | */ 31 | fun onClose() 32 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/callback/OnVideoEncodeListener.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.callback 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | * 编码回调 9 | */ 10 | interface OnVideoEncodeListener { 11 | abstract fun onVideoEncode(bb: ByteBuffer?, bi: MediaCodec.BufferInfo?) 12 | fun onVideoOutformat(outputFormat: MediaFormat?) 13 | } 14 | -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/CameraData.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-05-28 23:18 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is CameraData 10 | *11 | */ 12 | class CameraData { 13 | 14 | var cameraID: Int = 0 //camera的id 15 | var cameraFacing: Int = 0 //区分前后摄像头 16 | var cameraWidth: Int = 0 //camera的宽度 17 | var cameraHeight: Int = 0 //camera的高度 18 | var hasLight: Boolean = false 19 | var orientation: Int = 0 20 | var supportTouchFocus: Boolean = false 21 | var touchFocusMode: Boolean = false 22 | 23 | constructor(id: Int, facing: Int, width: Int, height: Int) { 24 | cameraID = id 25 | cameraFacing = facing 26 | cameraWidth = width 27 | cameraHeight = height 28 | } 29 | 30 | constructor(id: Int, facing: Int) { 31 | cameraID = id 32 | cameraFacing = facing 33 | } 34 | 35 | companion object { 36 | val FACING_FRONT = 1 37 | val FACING_BACK = 2 38 | } 39 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/CameraRecorder.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera 2 | 3 | import android.content.Context 4 | import android.view.Surface 5 | import com.devyk.av.camera_recorder.callback.IGLThreadConfig 6 | import com.devyk.av.camera_recorder.callback.IRenderer 7 | import com.devyk.av.rtmp.library.camera.renderer.EncodeRenderer 8 | import com.devyk.av.rtmp.library.mediacodec.VideoEncoder 9 | import com.devyk.av.rtmp.library.widget.GLSurfaceView 10 | import java.lang.ref.WeakReference 11 | import javax.microedition.khronos.egl.EGLContext 12 | 13 | /** 14 | *
15 | * author : devyk on 2020-07-11 15:18 16 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 17 | * github : https://github.com/yangkun19921001 18 | * mailbox : yang1001yk@gmail.com 19 | * desc : This is CameraRecorder 摄像头录制 20 | *21 | */ 22 | public class CameraRecorder(context: Context, textureId: Int, eglContext: EGLContext?) : VideoEncoder(), 23 | IGLThreadConfig { 24 | 25 | 26 | protected lateinit var mRenderer: EncodeRenderer 27 | protected var mEGLContext: EGLContext? 28 | protected var mRendererMode = GLSurfaceView.RENDERERMODE_CONTINUOUSLY 29 | protected var mGLThread: EncodeRendererThread? = null 30 | protected var mSurface: Surface? = null 31 | 32 | init { 33 | this.mEGLContext = eglContext 34 | this.mRenderer = EncodeRenderer(context, textureId) 35 | } 36 | 37 | 38 | /** 39 | * surface 创建的时候开始进行 GL 线程渲染 40 | */ 41 | override fun onSurfaceCreate(surface: Surface?) { 42 | super.onSurfaceCreate(surface) 43 | mSurface = surface 44 | mGLThread = EncodeRendererThread(WeakReference(this)) 45 | mGLThread?.run { 46 | setRendererSize(mConfiguration!!.width, mConfiguration!!.height) 47 | isCreate = true 48 | isChange = true 49 | start() 50 | } 51 | } 52 | 53 | 54 | override fun start() { 55 | super.start() 56 | 57 | } 58 | 59 | override fun stop() { 60 | super.stop() 61 | mGLThread?.onDestory() 62 | } 63 | 64 | 65 | public fun pause() { 66 | mGLThread?.setPause() 67 | 68 | } 69 | 70 | public fun resume() { 71 | mGLThread?.setResume() 72 | } 73 | 74 | override fun getSurface(): Surface? { 75 | if (mSurface != null) 76 | return mSurface 77 | return super.getSurface() 78 | } 79 | 80 | override fun getRenderer(): IRenderer? = mRenderer 81 | override fun getEGLContext(): EGLContext? = mEGLContext 82 | override fun getRendererMode(): Int = mRendererMode 83 | 84 | fun setWatermark(watermark: Watermark) { 85 | mRenderer?.setWatemark(watermark) 86 | } 87 | 88 | 89 | /** 90 | * 摄像头渲染线程 91 | */ 92 | class EncodeRendererThread(weakReference: WeakReference
9 | * author : devyk on 2020-07-04 14:49 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is EglHelper EGL 环境 14 | *15 | */ 16 | 17 | 18 | class EglHelper { 19 | 20 | private var mEgl: EGL10? = null 21 | private var mEglDisplay: EGLDisplay? = null 22 | private var mEglContext: EGLContext? = null 23 | private var mEglSurface: EGLSurface? = null 24 | 25 | /** 26 | * 初始化 EGL 27 | * @param surface 28 | * @param eglContext 29 | */ 30 | fun initEgl(surface: Surface?, eglContext: EGLContext?) { 31 | 32 | //1、得到 EGL 实例 33 | mEgl = EGLContext.getEGL() as EGL10 34 | 35 | //2、得到默认的显示设备 36 | mEglDisplay = mEgl!!.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) 37 | if (mEglDisplay === EGL10.EGL_NO_DISPLAY) { 38 | throw RuntimeException("eglGetDisplay failed") 39 | } 40 | 41 | //3、初始化默认的显示设备 42 | val version = IntArray(2) 43 | if (!mEgl!!.eglInitialize(mEglDisplay, version)) { 44 | throw RuntimeException("eglInitialize failed") 45 | } 46 | 47 | //4、设置显示设备的属性 48 | val attrbutes = intArrayOf( 49 | EGL10.EGL_RED_SIZE, 50 | 8, 51 | EGL10.EGL_GREEN_SIZE, 52 | 8, 53 | EGL10.EGL_BLUE_SIZE, 54 | 8, 55 | EGL10.EGL_ALPHA_SIZE, 56 | 8, 57 | EGL10.EGL_DEPTH_SIZE, 58 | 8, 59 | EGL10.EGL_STENCIL_SIZE, 60 | 8, 61 | EGL10.EGL_RENDERABLE_TYPE, 62 | 4, 63 | EGL10.EGL_NONE 64 | ) 65 | 66 | 67 | val num_config = IntArray(1) 68 | require(mEgl!!.eglChooseConfig(mEglDisplay, attrbutes, null, 1, num_config)) { "eglChooseConfig failed" } 69 | 70 | val numConfigs = num_config[0] 71 | require(numConfigs > 0) { "No configs match configSpec" } 72 | 73 | //5、重系统从获取对应属性的配置 74 | val configs = arrayOfNulls
11 | * author : devyk on 2020-07-08 21:03 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is GLThread 自定义 GLThread 线程类,主要用于 OpenGL 的绘制操作 16 | *17 | */ 18 | public open class GLThread(weakReference: WeakReference
17 | * author : devyk on 2020-07-06 16:48 18 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 19 | * github : https://github.com/yangkun19921001 20 | * mailbox : yang1001yk@gmail.com 21 | * desc : This is ShaderHelper 对 shader 加载的一些操作帮助类 22 | *23 | */ 24 | public object ShaderHelper { 25 | 26 | private var TAG = this.javaClass.simpleName 27 | 28 | 29 | /** 30 | * 从资源文件中加载着色器源代码 31 | */ 32 | fun getRawShaderResource(context: Context?, id: Int): String { 33 | context?.let { ctx -> 34 | val inputStream = ctx.resources.openRawResource(id) 35 | val bufferedReader = BufferedReader(InputStreamReader(inputStream)) 36 | var sb = StringBuffer() 37 | var line: String? = null 38 | 39 | while (true) { 40 | try { 41 | line = bufferedReader.readLine() 42 | if (TextUtils.isEmpty(line)) { 43 | bufferedReader.close() 44 | return sb.toString() 45 | } 46 | sb.append(line).append("\n") 47 | } catch (error: IOException) { 48 | LogHelper.e(TAG, error.message) 49 | } 50 | } 51 | 52 | } 53 | return "" 54 | } 55 | 56 | /** 57 | * 加载 着色器 代码 58 | */ 59 | @Synchronized 60 | private fun loadShader(shaderType: Int, source: String): Int { 61 | //1、创建着色器 62 | var shader = GLES20.glCreateShader(shaderType) 63 | if (shader == 0) return -1 64 | //2、加载着色器源码 65 | GLES20.glShaderSource(shader, source) 66 | //3. 编译着色器 67 | GLES20.glCompileShader(shader) 68 | //4. 检查是否编译成功 69 | val compile = IntArray(1) 70 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compile, 0) 71 | if (compile[0] != GLES20.GL_TRUE) { 72 | LogHelper.e(TAG, "shader compile error"); 73 | GLES20.glDeleteShader(shader); 74 | shader = -1; 75 | } 76 | return shader; 77 | } 78 | 79 | /** 80 | * 创建一个 着色器 的执行程序代码 81 | */ 82 | fun createProgram(vertecSource: String, frameSource: String): Int { 83 | val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertecSource) 84 | val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, frameSource) 85 | if (vertexShader == -1 || fragmentShader == -1) return -1 86 | //1. 创建一个渲染程序 87 | val program = GLES20.glCreateProgram() 88 | //2. 将着色器程序添加到渲染程序中 89 | GLES20.glAttachShader(program, vertexShader) 90 | GLES20.glAttachShader(program, fragmentShader) 91 | //3. 链接程序 92 | GLES20.glLinkProgram(program) 93 | return program 94 | } 95 | 96 | 97 | fun createTextImage(text: String, textSize: Int, textColor: String, bgColor: String?, padding: Int): Bitmap { 98 | val paint = Paint() 99 | paint.color = Color.parseColor(textColor) 100 | paint.textSize = textSize.toFloat() 101 | paint.style = Paint.Style.FILL 102 | paint.isAntiAlias = true 103 | 104 | val width = paint.measureText(text, 0, text.length) 105 | 106 | val top = paint.fontMetrics.top 107 | val bottom = paint.fontMetrics.bottom 108 | 109 | val bm = Bitmap.createBitmap( 110 | (width + padding * 2).toInt(), 111 | (bottom - top + padding * 2).toInt(), 112 | Bitmap.Config.ARGB_8888 113 | ) 114 | val canvas = Canvas(bm) 115 | 116 | canvas.drawColor(Color.parseColor(bgColor)) 117 | 118 | 119 | canvas.drawText(text, padding.toFloat(), -top + padding, paint) 120 | return bm 121 | } 122 | 123 | fun loadBitmapTexture(bitmap: Bitmap): Int { 124 | val textureIds = IntArray(1) 125 | GLES20.glGenTextures(1, textureIds, 0) 126 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]) 127 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT) 128 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT) 129 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) 130 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) 131 | 132 | val bitmapBuffer = ByteBuffer.allocate(bitmap.height * bitmap.width * 4) 133 | bitmap.copyPixelsToBuffer(bitmapBuffer) 134 | bitmapBuffer.flip() 135 | 136 | GLES20.glTexImage2D( 137 | GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap.width, 138 | bitmap.height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, bitmapBuffer 139 | ) 140 | return textureIds[0] 141 | } 142 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/Watermark.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera 2 | 3 | import android.graphics.Bitmap 4 | 5 | /** 6 | *
7 | * author : devyk on 2020-07-18 19:43 8 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 9 | * github : https://github.com/yangkun19921001 10 | * mailbox : yang1001yk@gmail.com 11 | * desc : This is Watermark 12 | *13 | */ 14 | class Watermark { 15 | 16 | var markImg: Bitmap? = null 17 | var txt: String? = null 18 | var textColor = -1 19 | var textSize = -1 20 | 21 | var floatArray: FloatArray? = null 22 | 23 | 24 | constructor( 25 | markImg: Bitmap 26 | , floatArray: FloatArray? 27 | ) { 28 | this.markImg = markImg 29 | this.floatArray = floatArray 30 | } 31 | 32 | constructor( 33 | txt: String, 34 | txtColor: Int, 35 | txtSize: Int 36 | , floatArray: FloatArray? 37 | ) { 38 | this.txt = txt 39 | this.textSize = txtSize 40 | this.textColor = txtColor 41 | this.floatArray = floatArray 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/exception/CameraDisabledException.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera.exception 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-05-28 23:31 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is CameraDisabledException 10 | *11 | */ 12 | class CameraDisabledException : Exception() -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/exception/CameraHardwareException.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera.exception 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-05-29 15:04 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is CameraHardwareException 10 | *11 | */ 12 | class CameraHardwareException(t: Throwable) : Exception(t) -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/exception/CameraNotSupportException.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera.exception 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-05-28 23:26 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is CameraNotSupportException 10 | *11 | */ 12 | public class CameraNotSupportException :Exception(){ 13 | 14 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/exception/NoCameraException.java: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera.exception; 2 | 3 | public class NoCameraException extends Exception { 4 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/exception/NoCameraException.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera.exception 2 | /** 3 | *
4 | * author : devyk on 2020-05-28 23:32 5 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 6 | * github : https://github.com/yangkun19921001 7 | * mailbox : yang1001yk@gmail.com 8 | * desc : This is NoCameraException 9 | *10 | */ -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/camera/renderer/DefaultRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.camera.renderer 2 | 3 | import android.opengl.GLES20 4 | import com.devyk.av.camera_recorder.callback.IRenderer 5 | 6 | /** 7 | *
8 | * author : devyk on 2020-07-06 11:57 9 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 10 | * github : https://github.com/yangkun19921001 11 | * mailbox : yang1001yk@gmail.com 12 | * desc : This is DefaultRenderer 渲染 13 | *14 | */ 15 | public class DefaultRenderer : IRenderer { 16 | override fun onSurfaceCreate(width: Int, height: Int) { 17 | } 18 | 19 | override fun onSurfaceChange(width: Int, height: Int) { 20 | //清屏 21 | GLES20.glViewport(0, 0, width, height) 22 | } 23 | 24 | override fun onDraw() { 25 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 26 | GLES20.glClearColor(0f, 1f, 0f, 1f) 27 | } 28 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/common/IThread.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.common 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-07-15 20:42 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is IThread 10 | *11 | */ 12 | public interface IThread { 13 | 14 | /** 15 | * 开始执行线程 16 | */ 17 | fun start(main:()->Unit) 18 | 19 | /** 20 | * 停止执行 21 | */ 22 | fun stop() 23 | 24 | /** 25 | *设置是否暂停 26 | */ 27 | fun setPause(pause: Boolean) 28 | 29 | /** 30 | * 停止 31 | */ 32 | fun isPause(): Boolean 33 | 34 | /** 35 | * 是否运行 36 | */ 37 | fun isRuning(): Boolean 38 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/common/ThreadImpl.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.common 2 | 3 | import com.devyk.av.rtmp.library.utils.LogHelper 4 | 5 | /** 6 | *
7 | * author : devyk on 2020-07-15 20:48 8 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 9 | * github : https://github.com/yangkun19921001 10 | * mailbox : yang1001yk@gmail.com 11 | * desc : This is ThreadImpl 12 | *13 | */ 14 | public open class ThreadImpl : IThread { 15 | 16 | private var isPause = false 17 | 18 | private var isRuning = false 19 | 20 | private var TAG = javaClass.simpleName 21 | 22 | 23 | override fun start(main: () -> Unit) { 24 | if (isRuning())return 25 | isRuning = true 26 | isPause = false 27 | Thread { 28 | main() 29 | LogHelper.d(TAG, "thread start!") 30 | }.start() 31 | } 32 | 33 | /** 34 | * 线程停止 35 | */ 36 | override fun stop() { 37 | isRuning = false 38 | isPause = true 39 | LogHelper.d(TAG, "thread stop!") 40 | } 41 | 42 | /** 43 | * 设置停止 44 | */ 45 | override fun setPause(pause: Boolean) { 46 | this.isPause = pause 47 | LogHelper.d(TAG, "thread pause:${pause}!") 48 | } 49 | 50 | /** 51 | * 是否停止 52 | */ 53 | override fun isPause(): Boolean = isPause 54 | 55 | 56 | /** 57 | * 是否执行 58 | */ 59 | override fun isRuning(): Boolean = isRuning 60 | 61 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/config/AudioConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.config 2 | 3 | import android.media.* 4 | 5 | /** 6 | *
7 | * author : devyk on 2020-06-13 15:26 8 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 9 | * github : https://github.com/yangkun19921001 10 | * mailbox : yang1001yk@gmail.com 11 | * desc : This is AudioConfiguration 12 | *13 | */ 14 | class AudioConfiguration private constructor(builder: Builder) { 15 | 16 | val minBps: Int 17 | val maxBps: Int 18 | val frequency: Int 19 | val encoding: Int 20 | val codeType: CodeType 21 | val channelCount: Int 22 | val adts: Int 23 | val aacProfile: Int 24 | val mime: String 25 | val aec: Boolean 26 | val mediaCodec: Boolean 27 | val audioSource:Int 28 | 29 | init { 30 | minBps = builder.minBps 31 | maxBps = builder.maxBps 32 | frequency = builder.frequency 33 | encoding = builder.encoding 34 | codeType = builder.codeType 35 | channelCount = builder.channelCount 36 | adts = builder.adts 37 | mime = builder.mime 38 | aacProfile = builder.aacProfile 39 | aec = builder.aec 40 | mediaCodec = builder.mediaCodec 41 | audioSource = builder.audioSource 42 | } 43 | 44 | class Builder { 45 | public var mediaCodec = DEFAULT_MEDIA_CODEC 46 | public var minBps = DEFAULT_MIN_BPS 47 | public var maxBps = DEFAULT_MAX_BPS 48 | public var frequency = DEFAULT_FREQUENCY 49 | public var encoding = DEFAULT_AUDIO_ENCODING 50 | public var channelCount = DEFAULT_CHANNEL_COUNT 51 | public var adts = DEFAULT_ADTS 52 | public var mime = DEFAULT_MIME 53 | public var codeType = DEFAULT_CODE_TYPE 54 | public var aacProfile = DEFAULT_AAC_PROFILE 55 | public var aec = DEFAULT_AEC 56 | public var audioSource = DEFAULT_AUDIO_SOURCE 57 | 58 | fun setBps(minBps: Int, maxBps: Int): Builder { 59 | this.minBps = minBps 60 | this.maxBps = maxBps 61 | return this 62 | } 63 | 64 | fun setFrequency(frequency: Int): Builder { 65 | this.frequency = frequency 66 | return this 67 | } 68 | 69 | fun setEncoding(encoding: Int): Builder { 70 | this.encoding = encoding 71 | return this 72 | } 73 | 74 | fun setChannelCount(channelCount: Int): Builder { 75 | this.channelCount = channelCount 76 | return this 77 | } 78 | 79 | fun setAdts(adts: Int): Builder { 80 | this.adts = adts 81 | return this 82 | } 83 | 84 | fun setAacProfile(aacProfile: Int): Builder { 85 | this.aacProfile = aacProfile 86 | return this 87 | } 88 | 89 | fun setMime(mime: String): Builder { 90 | this.mime = mime 91 | return this 92 | } 93 | 94 | fun setAec(aec: Boolean): Builder { 95 | this.aec = aec 96 | return this 97 | } 98 | 99 | fun setMediaCodec(meidaCodec: Boolean): AudioConfiguration.Builder { 100 | this.mediaCodec = meidaCodec 101 | return this 102 | } 103 | 104 | fun setCodecType(codeType: CodeType): Builder { 105 | this.codeType = codeType 106 | return this 107 | } 108 | 109 | fun setAudioSource(source:Int):Builder{ 110 | this.audioSource = source 111 | return this 112 | } 113 | 114 | fun build(): AudioConfiguration { 115 | return AudioConfiguration(this) 116 | } 117 | } 118 | 119 | companion object { 120 | val DEFAULT_FREQUENCY = 44100 121 | val DEFAULT_MAX_BPS = 64 122 | val DEFAULT_MIN_BPS = 32 123 | val DEFAULT_ADTS = 0 124 | val DEFAULT_CODE_TYPE = CodeType.ENCODE 125 | val DEFAULT_MIME = MediaFormat.MIMETYPE_AUDIO_AAC 126 | val DEFAULT_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT 127 | val DEFAULT_AAC_PROFILE = MediaCodecInfo.CodecProfileLevel.AACObjectLC 128 | val DEFAULT_CHANNEL_COUNT = 1 129 | val DEFAULT_AEC = false 130 | val DEFAULT_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC 131 | val DEFAULT_MEDIA_CODEC = true 132 | 133 | fun createDefault(): AudioConfiguration { 134 | return Builder().build() 135 | } 136 | } 137 | 138 | 139 | public enum class CodeType(codeType: Int) { 140 | ENCODE(1), 141 | DECODE(2), 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/config/CameraConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.config 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-05-28 23:20 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is CameraConfiguration 10 | *11 | */ 12 | class CameraConfiguration private constructor(builder: Builder) { 13 | 14 | val height: Int 15 | val width: Int 16 | val fps: Int 17 | val rotation: Int 18 | val facing: Facing 19 | val orientation: Orientation 20 | val focusMode: FocusMode 21 | 22 | init { 23 | height = builder.height 24 | width = builder.width 25 | facing = builder.facing 26 | fps = builder.fps 27 | orientation = builder.orientation 28 | focusMode = builder.focusMode 29 | rotation = builder.rotation 30 | } 31 | 32 | enum class Facing { 33 | FRONT, 34 | BACK 35 | } 36 | 37 | enum class Orientation { 38 | LANDSCAPE, 39 | PORTRAIT 40 | } 41 | 42 | enum class FocusMode { 43 | AUTO, 44 | TOUCH 45 | } 46 | 47 | 48 | class Builder { 49 | var height = DEFAULT_HEIGHT 50 | var width = DEFAULT_WIDTH 51 | var fps = DEFAULT_FPS 52 | var rotation = DEFAULT_ROTATION 53 | var facing = DEFAULT_FACING 54 | var orientation = DEFAULT_ORIENTATION 55 | var focusMode = DEFAULT_FOCUSMODE 56 | 57 | fun setPreview(height: Int, width: Int): Builder { 58 | this.height = height 59 | this.width = width 60 | return this 61 | } 62 | 63 | fun setFacing(facing: Facing): Builder { 64 | this.facing = facing 65 | return this 66 | } 67 | 68 | fun setOrientation(orientation: Orientation): Builder { 69 | this.orientation = orientation 70 | return this 71 | } 72 | 73 | fun setFps(fps: Int): Builder { 74 | this.fps = fps 75 | return this 76 | } 77 | 78 | fun setFocusMode(focusMode: FocusMode): Builder { 79 | this.focusMode = focusMode 80 | return this 81 | } 82 | 83 | fun setRotation(rot: Int): Builder { 84 | this.rotation = rot 85 | return this 86 | } 87 | 88 | fun build(): CameraConfiguration { 89 | return CameraConfiguration(this) 90 | } 91 | } 92 | 93 | companion object { 94 | val DEFAULT_HEIGHT = 1280 95 | val DEFAULT_WIDTH = 720 96 | val DEFAULT_FPS = 25 97 | val DEFAULT_ROTATION = 0 98 | val DEFAULT_FACING = Facing.BACK 99 | val DEFAULT_ORIENTATION = Orientation.PORTRAIT 100 | val DEFAULT_FOCUSMODE = FocusMode.AUTO 101 | 102 | fun createDefault(): CameraConfiguration { 103 | return Builder().build() 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/config/RendererConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.config 2 | 3 | import android.view.Surface 4 | import com.devyk.av.camera_recorder.callback.IRenderer 5 | import com.devyk.av.rtmp.library.annotation.RendererMode 6 | import com.devyk.av.rtmp.library.camera.renderer.DefaultRenderer 7 | import com.devyk.av.rtmp.library.widget.GLSurfaceView 8 | import javax.microedition.khronos.egl.EGLContext 9 | 10 | /** 11 | *
12 | * author : devyk on 2020-07-06 11:45 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is RendererConfiguration 17 | *18 | */ 19 | public class RendererConfiguration private constructor(builder: Builder) { 20 | 21 | 22 | val renderer: IRenderer 23 | 24 | val rendererMode: Int 25 | 26 | val surface: Surface? = null 27 | 28 | var eglContext: EGLContext? = null 29 | 30 | val width: Int 31 | 32 | val height: Int 33 | 34 | 35 | init { 36 | renderer = builder.renderer 37 | rendererMode = builder.rendererMode 38 | width = builder.width 39 | height = builder.height 40 | builder.eglContext?.let { 41 | eglContext = it 42 | } 43 | } 44 | 45 | 46 | class Builder { 47 | var renderer: IRenderer = DEFAULT_RENDERER 48 | 49 | var rendererMode: Int = DEFAULT_RENDERERMODE 50 | 51 | var surface: Surface? = null 52 | 53 | var eglContext: EGLContext? = null 54 | 55 | var width: Int = DEFAULT_WIDTH 56 | 57 | var height: Int = DEFAULT_HEIGHT 58 | 59 | 60 | /** 61 | * 设置渲染器 62 | */ 63 | fun setRenderer(renderer: IRenderer): Builder { 64 | this.renderer = renderer; 65 | return this 66 | } 67 | 68 | /** 69 | * 设置渲染模式 70 | */ 71 | fun setRendererMode(@RendererMode mode: Int): Builder { 72 | this.rendererMode = mode 73 | return this 74 | } 75 | 76 | /** 77 | * 设置显示的 Surface 78 | */ 79 | fun setSurface(surface: Surface): Builder { 80 | this.surface = surface 81 | return this 82 | } 83 | 84 | /** 85 | * 设置 EGL 上下文 86 | */ 87 | fun setEGLContext(context: EGLContext?): Builder { 88 | this.eglContext = context 89 | return this; 90 | } 91 | 92 | /** 93 | * 设置窗口大小 94 | */ 95 | fun setSize(width: Int, height: Int): Builder { 96 | this.width = width 97 | this.height = height 98 | return this 99 | } 100 | 101 | /** 102 | * 构建配置 103 | */ 104 | fun build(): RendererConfiguration { 105 | return RendererConfiguration(this) 106 | } 107 | 108 | 109 | } 110 | 111 | 112 | companion object { 113 | val DEFAULT_RENDERER = DefaultRenderer() 114 | val DEFAULT_RENDERERMODE = GLSurfaceView.RENDERERMODE_CONTINUOUSLY 115 | val DEFAULT_WIDTH = 720 116 | val DEFAULT_HEIGHT = 1280 117 | 118 | 119 | fun createDefault(): RendererConfiguration { 120 | return Builder().build() 121 | } 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/config/VideoConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.config 2 | 3 | import android.media.MediaFormat 4 | import android.view.Surface 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | *
9 | * author : devyk on 2020-06-14 22:07 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is VideoConfiguration 14 | *15 | */ 16 | class VideoConfiguration private constructor(builder: Builder) { 17 | 18 | val height: Int 19 | val width: Int 20 | val minBps: Int 21 | val maxBps: Int 22 | val fps: Int 23 | val mediaCodec: Boolean 24 | val ifi: Int 25 | val codeType: ICODEC 26 | val mime: String 27 | var surface: Surface?=null 28 | var spspps: ByteBuffer?=null 29 | 30 | init { 31 | height = builder.height 32 | width = builder.width 33 | minBps = builder.minBps 34 | maxBps = builder.maxBps 35 | fps = builder.fps 36 | ifi = builder.ifi 37 | codeType = builder.codeType 38 | mime = builder.mime 39 | mediaCodec = builder.mediaCodec 40 | surface = builder.surface 41 | spspps = builder.byteBuffer 42 | } 43 | 44 | class Builder { 45 | var mediaCodec = DEFAULT_MEDIA_CODEC 46 | var codeType = DEFAULT_CODEC_TYPE 47 | var height = DEFAULT_HEIGHT 48 | var width = DEFAULT_WIDTH 49 | var minBps = DEFAULT_MIN_BPS 50 | var maxBps = DEFAULT_MAX_BPS 51 | var fps = DEFAULT_FPS 52 | var ifi = DEFAULT_IFI 53 | var mime = DEFAULT_MIME 54 | var surface = DEFAULT_DECODE_SURFACE 55 | var byteBuffer = DEFAULT_SPS_PPS_BUFFER 56 | 57 | fun setSize(width: Int, height: Int): Builder { 58 | this.width = width 59 | this.height = height 60 | return this 61 | } 62 | 63 | fun setBps(minBps: Int, maxBps: Int): Builder { 64 | this.minBps = minBps 65 | this.maxBps = maxBps 66 | return this 67 | } 68 | 69 | fun setFps(fps: Int): Builder { 70 | this.fps = fps 71 | return this 72 | } 73 | 74 | fun setIfi(ifi: Int): Builder { 75 | this.ifi = ifi 76 | return this 77 | } 78 | 79 | fun setMime(mime: String): Builder { 80 | this.mime = mime 81 | return this 82 | } 83 | 84 | fun setMediaCodec(meidaCodec: Boolean): Builder { 85 | this.mediaCodec = meidaCodec 86 | return this 87 | } 88 | 89 | fun setCodeType(codeType: ICODEC): Builder { 90 | this.codeType = codeType 91 | return this 92 | } 93 | 94 | fun setSurface(surface: Surface):Builder{ 95 | this.surface = surface; 96 | return this; 97 | } 98 | 99 | fun setSpsPpsBuffer(buffer: ByteBuffer):Builder{ 100 | this.byteBuffer = buffer; 101 | return this; 102 | } 103 | 104 | fun build(): VideoConfiguration { 105 | return VideoConfiguration(this) 106 | } 107 | } 108 | 109 | companion object { 110 | val DEFAULT_HEIGHT = 1280 111 | val DEFAULT_WIDTH = 720 112 | val DEFAULT_FPS = 25 113 | val DEFAULT_MAX_BPS = 1800 114 | val DEFAULT_MIN_BPS = 400 115 | val DEFAULT_IFI = 2 116 | val DEFAULT_MIME = MediaFormat.MIMETYPE_VIDEO_AVC 117 | val DEFAULT_MEDIA_CODEC = true 118 | val DEFAULT_CODEC_TYPE = ICODEC.ENCODE 119 | val DEFAULT_SPS_PPS_BUFFER : ByteBuffer?= null 120 | /** 121 | * 用于解码显示的 surface 122 | */ 123 | val DEFAULT_DECODE_SURFACE: Surface? = null 124 | 125 | fun createDefault(): VideoConfiguration { 126 | return Builder().build() 127 | } 128 | } 129 | 130 | 131 | public enum class ICODEC(type: Int) { 132 | ENCODE(1), 133 | DECODE(2), 134 | } 135 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/controller/AudioController.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.controller 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import com.devyk.av.rtmp.library.audio.AudioProcessor 6 | import com.devyk.av.rtmp.library.callback.IController 7 | import com.devyk.av.rtmp.library.callback.OnAudioEncodeListener 8 | import com.devyk.av.rtmp.library.config.AudioConfiguration 9 | import com.devyk.av.rtmp.library.mediacodec.AudioEncoder 10 | import java.nio.ByteBuffer 11 | 12 | /** 13 | *
14 | * author : devyk on 2020-07-15 22:05 15 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 16 | * github : https://github.com/yangkun19921001 17 | * mailbox : yang1001yk@gmail.com 18 | * desc : This is AudioController 音频采集和音频编码的控制 19 | *20 | */ 21 | 22 | public class AudioController(audioConfiguration: AudioConfiguration) : IController, AudioProcessor.OnRecordListener, 23 | OnAudioEncodeListener { 24 | 25 | 26 | /** 27 | * 音频采集-》编解码 需要用到的默认参数 28 | */ 29 | private var mAudioConfiguration = AudioConfiguration.createDefault() 30 | 31 | /** 32 | * 音频编解码用到的实体程序 33 | */ 34 | private lateinit var mAudioEncoder: AudioEncoder 35 | 36 | /** 37 | * 音频采集用到的实体程序 38 | */ 39 | private lateinit var mAudioProcessor: AudioProcessor 40 | 41 | /** 42 | * 音频数据的监听 43 | */ 44 | private var mAudioDataListener: IController.OnAudioDataListener? = null 45 | 46 | 47 | init { 48 | mAudioConfiguration = audioConfiguration 49 | mAudioProcessor = AudioProcessor() 50 | mAudioEncoder = AudioEncoder(mAudioConfiguration) 51 | mAudioProcessor.init( 52 | mAudioConfiguration.audioSource, 53 | mAudioConfiguration.frequency, 54 | mAudioConfiguration.channelCount 55 | ) 56 | mAudioProcessor.addRecordListener(this) 57 | mAudioEncoder.setOnAudioEncodeListener(this) 58 | } 59 | 60 | 61 | /** 62 | * 触发 开始 63 | */ 64 | override fun start() { 65 | mAudioProcessor.startRcording() 66 | } 67 | 68 | /** 69 | * 触发 暂停 70 | */ 71 | override fun pause() { 72 | mAudioProcessor.setPause(true) 73 | } 74 | 75 | /** 76 | * 触发恢复 77 | */ 78 | override fun resume() { 79 | mAudioProcessor.setPause(false) 80 | } 81 | 82 | /** 83 | * 触发停止 84 | */ 85 | override fun stop() { 86 | mAudioProcessor.stop() 87 | 88 | } 89 | 90 | /** 91 | * 当采集 PCM 数据的时候返回 92 | */ 93 | override fun onPcmData(pcmData: ByteArray) { 94 | mAudioEncoder?.enqueueCodec(pcmData) 95 | } 96 | 97 | /** 98 | * 当开始采集 99 | */ 100 | override fun onStart(sampleRate: Int, channels: Int, sampleFormat: Int) { 101 | mAudioEncoder?.start() 102 | } 103 | 104 | /** 105 | * 设置禁言 106 | */ 107 | override fun setMute(isMute: Boolean) { 108 | super.setMute(isMute) 109 | mAudioProcessor?.setMute(isMute) 110 | 111 | 112 | } 113 | 114 | override fun onStop() { 115 | super.onStop() 116 | mAudioEncoder?.stop() 117 | } 118 | 119 | /** 120 | * 当采集出现错误 121 | */ 122 | override fun onError(meg: String?) { 123 | mAudioDataListener?.onError(meg) 124 | } 125 | 126 | /** 127 | * 当 Audio 编码数据的时候 128 | */ 129 | override fun onAudioEncode(bb: ByteBuffer, bi: MediaCodec.BufferInfo) { 130 | mAudioDataListener?.onAudioData(bb, bi) 131 | } 132 | 133 | /** 134 | * 编码的输出格式 135 | */ 136 | override fun onAudioOutformat(outputFormat: MediaFormat?) { 137 | mAudioDataListener?.onAudioOutformat(outputFormat) 138 | } 139 | 140 | override fun setAudioDataListener(audioDataListener: IController.OnAudioDataListener) { 141 | super.setAudioDataListener(audioDataListener) 142 | mAudioDataListener = audioDataListener 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/controller/VideoController.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.controller 2 | 3 | import android.content.Context 4 | import android.media.MediaCodec 5 | import android.media.MediaFormat 6 | import com.devyk.av.rtmp.library.callback.IController 7 | import com.devyk.av.rtmp.library.callback.OnVideoEncodeListener 8 | import com.devyk.av.rtmp.library.camera.CameraRecorder 9 | import com.devyk.av.rtmp.library.camera.Watermark 10 | import com.devyk.av.rtmp.library.config.VideoConfiguration 11 | import java.nio.ByteBuffer 12 | import javax.microedition.khronos.egl.EGLContext 13 | 14 | /** 15 | *
16 | * author : devyk on 2020-07-15 22:07 17 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 18 | * github : https://github.com/yangkun19921001 19 | * mailbox : yang1001yk@gmail.com 20 | * desc : This is VideoController Camera 预览和 H264 编码的控制 21 | *22 | */ 23 | 24 | public class VideoController(context: Context,textureId:Int,eglContext: EGLContext?,videoConfiguration: VideoConfiguration) : IController ,OnVideoEncodeListener{ 25 | 26 | 27 | private var mCameraVideoController: CameraRecorder? = null 28 | 29 | private var mListener : IController.OnVideoDataListener?=null 30 | 31 | init { 32 | mCameraVideoController = CameraRecorder(context, textureId, eglContext) 33 | mCameraVideoController?.prepare(videoConfiguration) 34 | mCameraVideoController?.setOnVideoEncodeListener(this) 35 | } 36 | override fun start() { 37 | mCameraVideoController?.start() 38 | } 39 | 40 | override fun stop() { 41 | mCameraVideoController?.stop() 42 | } 43 | 44 | override fun pause() { 45 | mCameraVideoController?.pause() 46 | } 47 | 48 | override fun resume() { 49 | mCameraVideoController?.resume() 50 | } 51 | 52 | 53 | override fun onVideoEncode(bb: ByteBuffer?, bi: MediaCodec.BufferInfo?) { 54 | mListener?.onVideoData(bb,bi) 55 | } 56 | 57 | override fun onVideoOutformat(outputFormat: MediaFormat?) { 58 | mListener?.onVideoOutformat(outputFormat) 59 | } 60 | 61 | 62 | override fun setVideoBps(bps: Int) { 63 | mCameraVideoController?.setEncodeBps(bps) 64 | } 65 | 66 | override fun setVideoDataListener(videoDataListener: IController.OnVideoDataListener) { 67 | mListener = videoDataListener 68 | } 69 | 70 | fun setWatermark(watermark: Watermark) { 71 | mCameraVideoController?.setWatermark(watermark) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/mediacodec/AudioEncoder.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.mediacodec 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import com.devyk.av.rtmp.library.callback.OnAudioEncodeListener 6 | import com.devyk.av.rtmp.library.config.AudioConfiguration 7 | import java.nio.ByteBuffer 8 | 9 | /** 10 | *
11 | * author : devyk on 2020-06-13 16:08 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is AudioEncoder AAC 编码 16 | *17 | */ 18 | 19 | class AudioEncoder(private val mAudioConfiguration: AudioConfiguration?) : BaseAudioCodec(mAudioConfiguration) { 20 | 21 | override fun onAudioOutformat(outputFormat: MediaFormat?) { 22 | mListener?.onAudioOutformat(outputFormat) 23 | } 24 | 25 | public var mListener: OnAudioEncodeListener? = null 26 | 27 | override fun onAudioData(bb: ByteBuffer, bi: MediaCodec.BufferInfo) { 28 | mListener?.onAudioEncode(bb, bi) 29 | } 30 | 31 | fun setOnAudioEncodeListener(listener: OnAudioEncodeListener?) { 32 | mListener = listener 33 | } 34 | 35 | 36 | override fun start() { 37 | super.start() 38 | } 39 | 40 | override fun stop() { 41 | super.stop() 42 | mListener = null 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/mediacodec/AudioMediaCodec.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.mediacodec 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaCodecInfo 5 | import android.media.MediaCodecList 6 | import android.media.MediaFormat 7 | import com.devyk.av.rtmp.library.audio.AudioUtils 8 | import com.devyk.av.rtmp.library.config.AudioConfiguration 9 | 10 | import java.nio.ByteBuffer 11 | 12 | /** 13 | *
14 | * author : devyk on 2020-06-13 15:28 15 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 16 | * github : https://github.com/yangkun19921001 17 | * mailbox : yang1001yk@gmail.com 18 | * desc : This is AudioMediaCodec 19 | *20 | */ 21 | public class AudioMediaCodec { 22 | companion object { 23 | 24 | fun selectCodec(mimeType: String): MediaCodecInfo? { 25 | val numCodecs = MediaCodecList.getCodecCount() 26 | for (i in 0 until numCodecs) { 27 | val codecInfo = MediaCodecList.getCodecInfoAt(i) 28 | if (!codecInfo.isEncoder) { 29 | continue 30 | } 31 | val types = codecInfo.supportedTypes 32 | for (j in types.indices) { 33 | if (types[j].equals(mimeType, ignoreCase = true)) { 34 | return codecInfo 35 | } 36 | } 37 | } 38 | return null 39 | } 40 | 41 | 42 | fun getAudioMediaCodec(configuration: AudioConfiguration): MediaCodec? { 43 | //数据类型 "audio/mp4a-latm" 44 | val format = 45 | MediaFormat.createAudioFormat(configuration.mime, configuration.frequency, configuration.channelCount) 46 | if (configuration.mime.equals(AudioConfiguration.DEFAULT_MIME)) { 47 | //用来标记aac的类型 48 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, configuration.aacProfile) 49 | } 50 | //比特率 51 | format.setInteger(MediaFormat.KEY_BIT_RATE, configuration.maxBps * 1024) 52 | //采样率 53 | format.setInteger(MediaFormat.KEY_SAMPLE_RATE, configuration.frequency) 54 | //缓冲区大小 55 | val maxInputSize = 56 | AudioUtils.getMinBufferSize(configuration.frequency, configuration.channelCount, configuration.encoding) 57 | //最大的缓冲区 58 | format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize) 59 | //通道数量 60 | format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, configuration.channelCount) 61 | var mediaCodec: MediaCodec? = null 62 | try { 63 | if (configuration.codeType == AudioConfiguration.CodeType.ENCODE) { 64 | mediaCodec = MediaCodec.createEncoderByType(configuration.mime) 65 | mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) 66 | } else if (configuration.codeType == AudioConfiguration.CodeType.DECODE) { 67 | //用来标记AAC是否有adts头,1->有 68 | format.setInteger(MediaFormat.KEY_IS_ADTS, configuration.adts); 69 | //ByteBuffer key(暂时不了解该参数的含义,但必须设置) 70 | val data = byteArrayOf(0x11.toByte(), 0x90.toByte()) 71 | val csd_0 = ByteBuffer.wrap(data) 72 | //配置解码器 csd-0 信息 73 | //参考:https://developer.android.com/reference/android/media/MediaCodec 74 | format.setByteBuffer("csd-0", csd_0) 75 | mediaCodec = MediaCodec.createDecoderByType(configuration.mime) 76 | mediaCodec.configure(format, null, null, 0) 77 | } 78 | 79 | } catch (e: Exception) { 80 | e.printStackTrace() 81 | if (mediaCodec != null) { 82 | mediaCodec.stop() 83 | mediaCodec.release() 84 | mediaCodec = null 85 | } 86 | } 87 | 88 | return mediaCodec 89 | } 90 | } 91 | 92 | 93 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/mediacodec/BaseAudioCodec.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.mediacodec 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import android.util.Log 6 | import com.devyk.av.rtmp.library.config.AudioConfiguration 7 | import com.devyk.av.rtmp.library.utils.LogHelper 8 | 9 | import java.nio.ByteBuffer 10 | 11 | /** 12 | *
13 | * author : devyk on 2020-06-13 23:53 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is BaseCoder 18 | *19 | */ 20 | abstract class BaseAudioCodec(private val mAudioConfiguration: AudioConfiguration?) : IAudioCodec { 21 | private var mMediaCodec: MediaCodec? = null 22 | 23 | 24 | internal var mBufferInfo = MediaCodec.BufferInfo() 25 | 26 | private var TAG = javaClass.simpleName 27 | private var mPts = 0L 28 | 29 | /** 30 | * 编码完成的函数自己不处理,交由子类处理 31 | */ 32 | abstract fun onAudioData(bb: ByteBuffer, bi: MediaCodec.BufferInfo); 33 | 34 | 35 | @Synchronized 36 | override fun start() { 37 | mMediaCodec = AudioMediaCodec.getAudioMediaCodec(mAudioConfiguration!!) 38 | mMediaCodec!!.start() 39 | Log.e("encode", "--start") 40 | } 41 | 42 | /** 43 | * 将数据入队 java.lang.IllegalStateException 44 | */ 45 | @Synchronized 46 | override fun enqueueCodec(input: ByteArray?) { 47 | if (mMediaCodec == null) { 48 | return 49 | } 50 | val inputBuffers = mMediaCodec!!.inputBuffers 51 | val outputBuffers = mMediaCodec!!.outputBuffers 52 | val inputBufferIndex = mMediaCodec!!.dequeueInputBuffer(12000) 53 | 54 | if (inputBufferIndex >= 0) { 55 | val inputBuffer = inputBuffers[inputBufferIndex] 56 | inputBuffer.clear() 57 | inputBuffer.put(input) 58 | mMediaCodec!!.queueInputBuffer(inputBufferIndex, 0, input!!.size, 0, 0) 59 | } 60 | 61 | var outputBufferIndex = mMediaCodec!!.dequeueOutputBuffer(mBufferInfo, 12000) 62 | if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 63 | onAudioOutformat(mMediaCodec?.outputFormat) 64 | } 65 | 66 | 67 | 68 | while (outputBufferIndex >= 0) { 69 | val outputBuffer = outputBuffers[outputBufferIndex] 70 | 71 | if (mPts == 0L) 72 | mPts = System.nanoTime() / 1000; 73 | 74 | mBufferInfo!!.presentationTimeUs = System.nanoTime() / 1000 - mPts; 75 | 76 | LogHelper.e(TAG, "音频时间戳:${mBufferInfo!!.presentationTimeUs / 1000_000}") 77 | onAudioData(outputBuffer, mBufferInfo) 78 | mMediaCodec!!.releaseOutputBuffer(outputBufferIndex, false) 79 | outputBufferIndex = mMediaCodec!!.dequeueOutputBuffer(mBufferInfo, 0) 80 | } 81 | } 82 | 83 | abstract fun onAudioOutformat(outputFormat: MediaFormat?) 84 | 85 | @Synchronized 86 | override fun stop() { 87 | if (mMediaCodec != null) { 88 | mMediaCodec!!.stop() 89 | mMediaCodec!!.release() 90 | mMediaCodec = null 91 | } 92 | } 93 | 94 | 95 | /** 96 | * 获取输出的格式 97 | */ 98 | public fun getOutputFormat(): MediaFormat? = mMediaCodec?.outputFormat 99 | 100 | 101 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/mediacodec/IAudioCodec.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.mediacodec 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-06-13 23:55 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is ICodec 10 | *11 | */ 12 | public interface IAudioCodec{ 13 | 14 | 15 | /** 16 | * 准备编码 17 | */ 18 | fun start() 19 | 20 | /** 21 | * 将数据送入编解码器 22 | */ 23 | fun enqueueCodec(input: ByteArray?); 24 | 25 | /** 26 | * 停止编码 27 | */ 28 | fun stop(); 29 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/mediacodec/IVideoCodec.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.mediacodec 2 | 3 | import android.media.MediaCodec 4 | 5 | import com.devyk.av.rtmp.library.config.VideoConfiguration 6 | 7 | import java.nio.ByteBuffer 8 | 9 | /** 10 | *
11 | * author : devyk on 2020-06-15 21:42 12 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 13 | * github : https://github.com/yangkun19921001 14 | * mailbox : yang1001yk@gmail.com 15 | * desc : This is ICodec 16 | *17 | */ 18 | 19 | public interface IVideoCodec { 20 | 21 | 22 | 23 | /** 24 | * 初始化编码器 25 | */ 26 | fun prepare(videoConfiguration: VideoConfiguration = VideoConfiguration.createDefault()){}; 27 | 28 | /** 29 | * start 编码 30 | */ 31 | fun start(); 32 | 33 | /** 34 | * 停止编码 35 | */ 36 | fun stop(); 37 | 38 | /** 39 | * 返回编码好的 H264 数据 40 | */ 41 | abstract fun onVideoEncode(bb: ByteBuffer?, mBufferInfo: MediaCodec.BufferInfo) 42 | 43 | 44 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/mediacodec/VideoEncoder.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.mediacodec 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaFormat 5 | import com.devyk.av.rtmp.library.callback.OnVideoEncodeListener 6 | import com.devyk.av.rtmp.library.config.VideoConfiguration 7 | 8 | import java.nio.ByteBuffer 9 | 10 | /** 11 | *
12 | * author : devyk on 2020-07-09 22:57 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is H264Encoder 17 | *18 | */ 19 | public open class VideoEncoder : BaseVideoEncoder() { 20 | 21 | 22 | override fun onVideoOutformat(outputFormat: MediaFormat?) { 23 | mListener?.onVideoOutformat(outputFormat) 24 | } 25 | 26 | override fun prepare(videoConfiguration: VideoConfiguration) { 27 | super.prepare(videoConfiguration) 28 | } 29 | 30 | 31 | private var mListener: OnVideoEncodeListener? = null 32 | 33 | /** 34 | * 视频编码完成的回调 35 | */ 36 | override fun onVideoEncode(bb: ByteBuffer?, bi: MediaCodec.BufferInfo) { 37 | mListener?.onVideoEncode(bb!!, bi) 38 | } 39 | 40 | /** 41 | * 设置编码回调 42 | */ 43 | fun setOnVideoEncodeListener(listener: OnVideoEncodeListener) { 44 | mListener = listener 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/mediacodec/VideoMediaCodec.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.mediacodec 2 | 3 | import android.media.MediaCodec 4 | import android.media.MediaCodec.CONFIGURE_FLAG_ENCODE 5 | import android.media.MediaCodecInfo 6 | import android.media.MediaFormat 7 | import android.util.Log 8 | import com.devyk.av.rtmp.library.black.BlackListHelper 9 | import com.devyk.av.rtmp.library.config.VideoConfiguration 10 | import com.devyk.av.rtmp.library.utils.LogHelper 11 | 12 | /** 13 | *
14 | * author : devyk on 2020-06-14 22:09 15 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 16 | * github : https://github.com/yangkun19921001 17 | * mailbox : yang1001yk@gmail.com 18 | * desc : This is VideoMediaCodec 19 | *20 | */ 21 | object VideoMediaCodec { 22 | private val TAG = this.javaClass.simpleName 23 | 24 | fun getVideoMediaCodec(videoConfiguration: VideoConfiguration): MediaCodec? { 25 | var mediaCodec: MediaCodec? = null 26 | val videoWidth = getVideoSize(videoConfiguration.width) 27 | val videoHeight = getVideoSize(videoConfiguration.height) 28 | val format = MediaFormat.createVideoFormat(videoConfiguration.mime, videoWidth, videoHeight) 29 | if (videoConfiguration.codeType == VideoConfiguration.ICODEC.ENCODE) { 30 | format.setInteger( 31 | MediaFormat.KEY_COLOR_FORMAT, 32 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface 33 | ) 34 | format.setInteger(MediaFormat.KEY_BIT_RATE, videoConfiguration.maxBps * 1024) 35 | var fps = videoConfiguration.fps 36 | 37 | 38 | //设置摄像头预览帧率 39 | if (BlackListHelper.deviceInFpsBlacklisted()) { 40 | Log.d(TAG, "Device in fps setting black list, so set mediacodec fps 15") 41 | fps = 15 42 | } 43 | format.setInteger(MediaFormat.KEY_FRAME_RATE, fps) 44 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, videoConfiguration.ifi) 45 | format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) 46 | format.setInteger(MediaFormat.KEY_COMPLEXITY, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) 47 | 48 | try { 49 | mediaCodec = MediaCodec.createEncoderByType(videoConfiguration.mime) 50 | mediaCodec.configure(format, null, null, CONFIGURE_FLAG_ENCODE) 51 | LogHelper.d(TAG, "mediacodec init successed!") 52 | } catch (e: Exception) { 53 | e.printStackTrace() 54 | mediaCodec = release(mediaCodec) 55 | } 56 | } else if (videoConfiguration.codeType == VideoConfiguration.ICODEC.DECODE) { 57 | try { 58 | mediaCodec = MediaCodec.createDecoderByType(videoConfiguration.mime) 59 | if (videoConfiguration.surface == null) 60 | throw NullPointerException("surface is null?") 61 | if (videoConfiguration.spspps == null) 62 | throw NullPointerException("spspps buffer is null?") 63 | 64 | //csd-0 含义此处有介绍 65 | // @see https://developer.android.com/reference/android/media/MediaCodec 66 | format.setByteBuffer("csd-0", videoConfiguration.spspps) 67 | mediaCodec.configure(format, videoConfiguration.surface, null, 0) 68 | } catch (e: Exception) { 69 | e.printStackTrace() 70 | mediaCodec = release(mediaCodec) 71 | } 72 | } 73 | return mediaCodec 74 | } 75 | 76 | private fun release(mediaCodec: MediaCodec?): MediaCodec? { 77 | var mediaCodec1 = mediaCodec 78 | if (mediaCodec1 != null) { 79 | mediaCodec1.stop() 80 | mediaCodec1.release() 81 | mediaCodec1 = null 82 | } 83 | return mediaCodec1 84 | } 85 | 86 | // We avoid the device-specific limitations on width and height by using values that 87 | // are multiples of 16, which all tested devices seem to be able to handle. 88 | fun getVideoSize(size: Int): Int { 89 | val multiple = Math.ceil(size / 16.0).toInt() 90 | return multiple * 16 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/stream/PacketType.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.stream 2 | 3 | /** 4 | *
5 | * author : devyk on 2020-07-16 21:29 6 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 7 | * github : https://github.com/yangkun19921001 8 | * mailbox : yang1001yk@gmail.com 9 | * desc : This is PacketType 10 | *11 | */ 12 | public enum class PacketType { 13 | FIRST_AUDIO(1), 14 | FIRST_VIDEO(2), 15 | SPS_PPS(3), 16 | AUDIO(4), 17 | KEY_FRAME(5), 18 | VIDEO(6); 19 | 20 | 21 | var type = -1; 22 | constructor(types: Int){ 23 | type = types 24 | } 25 | 26 | 27 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/stream/amf/AmfArray.java: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.stream.amf; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.nio.ByteBuffer; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * @Title: AmfArray 12 | * @Package com.jimfengfly.rtmppublisher.amf 13 | * @Description: 14 | * @Author Jim 15 | * @Date 2016/11/28 16 | * @Time 下午2:51 17 | * @Version 18 | */ 19 | 20 | public class AmfArray implements AmfData { 21 | 22 | private List
13 | * author : devyk on 2020-07-16 21:28 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is DefaultPacker 存在 bug 18 | *19 | */ 20 | public class DefaultPacker : Packer { 21 | override fun start() { 22 | 23 | } 24 | 25 | override fun stop() { 26 | } 27 | 28 | private var TAG = javaClass.simpleName 29 | 30 | override fun onVideoSpsPpsData(sps: ByteArray, pps: ByteArray, spsPps: PacketType) { 31 | mPacketListener?.onPacket(sps, pps, PacketType.SPS_PPS) 32 | } 33 | 34 | override fun onVideoData(bb: ByteBuffer?, bi: MediaCodec.BufferInfo?) { 35 | bb?.let { buffer -> 36 | bi?.let { mediaBuffer -> 37 | buffer.position(mediaBuffer.offset) 38 | buffer.limit(mediaBuffer.offset + mediaBuffer.size) 39 | val video = ByteArray(mediaBuffer.size) 40 | buffer.get(video) 41 | val tag = video[4].and(0x1f).toInt() 42 | 43 | var keyFrame = PacketType.VIDEO 44 | if (tag == 0x05) {//关键帧 45 | keyFrame = PacketType.KEY_FRAME 46 | } else { 47 | keyFrame = PacketType.VIDEO 48 | } 49 | mPacketListener?.onPacket(video, keyFrame) 50 | } 51 | } 52 | } 53 | 54 | private var mPacketListener: Packer.OnPacketListener? = null 55 | 56 | 57 | override fun onAudioData(bb: ByteBuffer, bi: MediaCodec.BufferInfo) { 58 | bb?.let { buffer -> 59 | bi?.let { mediaBuffer -> 60 | buffer.position(mediaBuffer.offset) 61 | buffer.limit(mediaBuffer.offset + mediaBuffer.size) 62 | val audio = ByteArray(mediaBuffer.size) 63 | buffer.get(audio) 64 | mPacketListener?.onPacket(audio, PacketType.AUDIO) 65 | } 66 | } 67 | } 68 | 69 | override fun setPacketListener(packetListener: Packer.OnPacketListener) { 70 | mPacketListener = packetListener 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/stream/packer/Packer.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.stream.packer 2 | 3 | import com.devyk.av.rtmp.library.stream.PacketType 4 | import android.media.MediaCodec 5 | import java.nio.ByteBuffer 6 | 7 | 8 | /** 9 | *
10 | * author : devyk on 2020-07-16 21:24 11 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 12 | * github : https://github.com/yangkun19921001 13 | * mailbox : yang1001yk@gmail.com 14 | * desc : This is Packer 对音视频数据打包 15 | *16 | */ 17 | public interface Packer { 18 | interface OnPacketListener { 19 | fun onPacket(byteArray: ByteArray, packetType: PacketType) 20 | fun onPacket(sps: ByteArray?,pps: ByteArray?, packetType: PacketType){} 21 | } 22 | 23 | /** 24 | * 设置打包监听器 25 | */ 26 | fun setPacketListener(packetListener: OnPacketListener) 27 | 28 | /** 29 | *处理视频硬编编码器输出的数据 30 | */ 31 | fun onVideoData(bb: ByteBuffer?, bi: MediaCodec.BufferInfo?) 32 | 33 | /** 34 | * 处理音频硬编编码器输出的数据 35 | * */ 36 | fun onAudioData(bb: ByteBuffer, bi: MediaCodec.BufferInfo) 37 | 38 | /** 39 | * 处理视频 SPS PPS 数据 40 | */ 41 | fun onVideoSpsPpsData(sps: ByteArray, pps: ByteArray, spsPps: PacketType) { 42 | 43 | } 44 | 45 | 46 | fun start(); 47 | fun stop(); 48 | } 49 | -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/stream/packer/rtmp/RtmpPacker.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.stream.packer.rtmp 2 | 3 | import android.media.MediaCodec 4 | import com.devyk.av.rtmp.library.stream.AnnexbHelper 5 | import com.devyk.av.rtmp.library.stream.PacketType 6 | import com.devyk.av.rtmp.library.stream.packer.Packer 7 | import com.devyk.av.rtmp.library.stream.packer.flv.FlvPackerHelper 8 | import com.devyk.av.rtmp.library.stream.packer.flv.FlvPackerHelper.* 9 | import java.nio.ByteBuffer 10 | 11 | /** 12 | *
13 | * author : devyk on 2020-07-18 15:56 14 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 15 | * github : https://github.com/yangkun19921001 16 | * mailbox : yang1001yk@gmail.com 17 | * desc : This is RtmpPacker 18 | *19 | */ 20 | 21 | class RtmpPacker : Packer, AnnexbHelper.AnnexbNaluListener { 22 | 23 | private var packetListener: Packer.OnPacketListener? = null 24 | private var isHeaderWrite: Boolean = false 25 | private var isKeyFrameWrite: Boolean = false 26 | 27 | private var mAudioSampleRate: Int = 44100 28 | private var mAudioSampleSize: Int = 16 29 | private var mIsStereo: Boolean = false 30 | 31 | private val mAnnexbHelper: AnnexbHelper 32 | 33 | init { 34 | mAnnexbHelper = AnnexbHelper() 35 | } 36 | 37 | override fun setPacketListener(listener: Packer.OnPacketListener) { 38 | packetListener = listener 39 | } 40 | 41 | override fun start() { 42 | mAnnexbHelper.setAnnexbNaluListener(this) 43 | } 44 | 45 | override fun onVideoData(bb: ByteBuffer?, bi: MediaCodec.BufferInfo?) { 46 | mAnnexbHelper.analyseVideoData(bb!!, bi!!) 47 | } 48 | 49 | override fun onAudioData(bb: ByteBuffer, bi: MediaCodec.BufferInfo) { 50 | if (packetListener == null || !isHeaderWrite || !isKeyFrameWrite) { 51 | return 52 | } 53 | bb.position(bi.offset) 54 | bb.limit(bi.offset + bi.size) 55 | 56 | val audio = ByteArray(bi.size) 57 | bb.get(audio) 58 | val size = AUDIO_HEADER_SIZE + audio.size 59 | val buffer = ByteBuffer.allocate(size) 60 | FlvPackerHelper.writeAudioTag(buffer, audio, false, mAudioSampleSize) 61 | packetListener!!.onPacket(buffer.array(), PacketType.AUDIO) 62 | } 63 | 64 | override fun stop() { 65 | isHeaderWrite = false 66 | isKeyFrameWrite = false 67 | mAnnexbHelper.stop() 68 | } 69 | 70 | override fun onVideo(video: ByteArray, isKeyFrame: Boolean) { 71 | if (packetListener == null || !isHeaderWrite) { 72 | return 73 | } 74 | var packetType = PacketType.VIDEO 75 | if (isKeyFrame) { 76 | isKeyFrameWrite = true 77 | packetType = PacketType.KEY_FRAME 78 | } 79 | //确保第一帧是关键帧,避免一开始出现灰色模糊界面 80 | if (!isKeyFrameWrite) { 81 | return 82 | } 83 | val size = VIDEO_HEADER_SIZE + video.size 84 | val buffer = ByteBuffer.allocate(size) 85 | FlvPackerHelper.writeH264Packet(buffer, video, isKeyFrame) 86 | packetListener!!.onPacket(buffer.array(),packetType) 87 | } 88 | 89 | 90 | override fun onSpsPps(sps: ByteArray?, pps: ByteArray?) { 91 | if (packetListener == null) { 92 | return 93 | } 94 | //写入第一个视频信息 95 | writeFirstVideoTag(sps, pps) 96 | //写入第一个音频信息 97 | writeFirstAudioTag() 98 | isHeaderWrite = true 99 | } 100 | 101 | private fun writeFirstVideoTag(sps: ByteArray?, pps: ByteArray?) { 102 | val size = VIDEO_HEADER_SIZE + VIDEO_SPECIFIC_CONFIG_EXTEND_SIZE + sps!!.size + pps!!.size 103 | val buffer = ByteBuffer.allocate(size) 104 | FlvPackerHelper.writeFirstVideoTag(buffer, sps, pps) 105 | packetListener!!.onPacket(buffer.array(), PacketType.FIRST_VIDEO) 106 | } 107 | 108 | private fun writeFirstAudioTag() { 109 | val size = AUDIO_SPECIFIC_CONFIG_SIZE + AUDIO_HEADER_SIZE 110 | val buffer = ByteBuffer.allocate(size) 111 | FlvPackerHelper.writeFirstAudioTag(buffer, mAudioSampleRate, mIsStereo, mAudioSampleSize) 112 | packetListener!!.onPacket(buffer.array(), PacketType.FIRST_AUDIO) 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/stream/sender/Sender.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.stream.sender 2 | 3 | import com.devyk.av.rtmp.library.stream.PacketType 4 | 5 | /** 6 | *
7 | * author : devyk on 2020-07-16 21:26 8 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 9 | * github : https://github.com/yangkun19921001 10 | * mailbox : yang1001yk@gmail.com 11 | * desc : This is Sender 12 | *13 | */ 14 | public interface Sender{ 15 | fun onData(data: ByteArray, type: PacketType) 16 | fun onData(sps: ByteArray,pps: ByteArray, type: PacketType){} 17 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/stream/sender/rtmp/RtmpSender.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.stream.sender.rtmp 2 | 3 | import android.util.Log 4 | import com.devyk.av.rtmp.library.Contacts 5 | import com.devyk.av.rtmp.library.callback.OnConnectListener 6 | import com.devyk.av.rtmp.library.stream.PacketType 7 | import com.devyk.av.rtmp.library.stream.sender.Sender 8 | import java.util.* 9 | 10 | /** 11 | *
12 | * author : devyk on 2020-07-16 21:27 13 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 14 | * github : https://github.com/yangkun19921001 15 | * mailbox : yang1001yk@gmail.com 16 | * desc : This is RtmpSender 17 | *18 | */ 19 | public class RtmpSender : Sender { 20 | private var TAG = javaClass.simpleName 21 | private var listener: OnConnectListener? = null 22 | private var mRtmpUrl: String? = null 23 | 24 | companion object { 25 | init { 26 | System.loadLibrary("AVRtmpPush") 27 | } 28 | } 29 | 30 | 31 | override fun onData(data: ByteArray, type: PacketType) { 32 | if (type == PacketType.FIRST_AUDIO || type == PacketType.AUDIO) { 33 | //音频数据 34 | pushAudio(data, data.size, type.type) 35 | } else if (type == PacketType.FIRST_VIDEO || 36 | type == PacketType.KEY_FRAME || type == PacketType.VIDEO) { 37 | //视频数据 38 | pushVideo(data, data.size, type.type) 39 | } 40 | } 41 | 42 | 43 | fun setDataSource(source: String) { 44 | mRtmpUrl = source 45 | } 46 | 47 | fun connect() { 48 | NativeRtmpConnect(mRtmpUrl) 49 | 50 | } 51 | 52 | fun close() { 53 | NativeRtmpClose() 54 | onClose() 55 | } 56 | 57 | fun setOnConnectListener(lis: OnConnectListener) { 58 | listener = lis 59 | } 60 | 61 | 62 | /** 63 | * C++ 层调用 64 | * 开始链接 65 | */ 66 | fun onConnecting() { 67 | listener?.onConnecting() 68 | } 69 | 70 | /** 71 | * C++ 层调用 72 | * 连接成功 73 | */ 74 | fun onConnected() { 75 | listener?.onConnected() 76 | } 77 | 78 | /** 79 | * C++ 层调用 80 | * 关闭成功 81 | */ 82 | fun onClose() { 83 | listener?.onClose() 84 | } 85 | 86 | 87 | /** 88 | * C++ 层调用 89 | * 推送失败 90 | */ 91 | fun onError(errorCode: Int) { 92 | listener?.onFail(errorCode2errorMessage(errorCode)) 93 | } 94 | 95 | private fun errorCode2errorMessage(errorCode: Int): String { 96 | 97 | var message = "未知错误,请联系管理员!" 98 | if (errorCode == Contacts.RTMP_CONNECT_ERROR) { 99 | message = "RTMP server connect fail!" 100 | } else if (errorCode == Contacts.RTMP_INIT_ERROR) { 101 | message = "RTMP native init fail!" 102 | } else if (errorCode == Contacts.RTMP_SET_URL_ERROR) { 103 | message = "RTMP url set fail!" 104 | } 105 | return message 106 | } 107 | 108 | 109 | private external fun NativeRtmpConnect(url: String?); 110 | private external fun NativeRtmpClose(); 111 | private external fun pushAudio(data: ByteArray, size: Int, type: Int) 112 | private external fun pushVideo(data: ByteArray, size: Int, isKeyFrame: Int) 113 | private external fun pushSpsPps(sps: ByteArray, size: Int, pps: ByteArray, size1: Int) 114 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/utils/BitmapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.BitmapFactory 6 | import android.graphics.Color 7 | import android.graphics.Matrix 8 | import android.view.Gravity 9 | import android.view.View 10 | import android.widget.LinearLayout 11 | import android.widget.TextView 12 | import android.R.attr.bitmap 13 | import android.opengl.ETC1.getHeight 14 | import android.opengl.ETC1.getWidth 15 | 16 | 17 | 18 | /** 19 | *
20 | * author : devyk on 2020-07-09 21:38 21 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 22 | * github : https://github.com/yangkun19921001 23 | * mailbox : yang1001yk@gmail.com 24 | * desc : This is BitmapUtils 25 | *26 | */ 27 | public object BitmapUtils { 28 | /** 29 | * 将文字 生成 文字图片 生成显示编码的Bitmap,目前这个方法是可用的 30 | * 31 | * @param contents 32 | * @param context 33 | * @return 34 | */ 35 | fun creatBitmap(contents: String, context: Context, testSize: Int, testColor: Int, bg: Int): Bitmap { 36 | var scale = context.getResources().getDisplayMetrics().scaledDensity; 37 | var tv = TextView(context); 38 | var layoutParams = LinearLayout.LayoutParams( 39 | LinearLayout.LayoutParams.MATCH_PARENT, 40 | LinearLayout.LayoutParams.WRAP_CONTENT 41 | ); 42 | tv.setLayoutParams(layoutParams); 43 | tv.setText(contents); 44 | tv.setTextSize(scale * testSize); 45 | tv.setGravity(Gravity.CENTER_HORIZONTAL); 46 | tv.setDrawingCacheEnabled(true); 47 | tv.setTextColor(testColor); 48 | tv.measure( 49 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 50 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) 51 | ); 52 | tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight()); 53 | tv.setBackgroundColor(bg); 54 | tv.buildDrawingCache(); 55 | return tv.getDrawingCache(); 56 | } 57 | 58 | fun changeBitmapSize(context: Context, src: Int, width: Float, height: Float): Bitmap { 59 | var bitmap = BitmapFactory.decodeResource(context.applicationContext.resources, src); 60 | return getBitmap(bitmap,width, height); 61 | } 62 | 63 | public fun getBitmap(bitmap: Bitmap,width: Float,height: Float): Bitmap { 64 | var bitmap1 = bitmap 65 | val oldWidth = bitmap1.width 66 | val oldHeight = bitmap1.height 67 | //设置想要的大小 68 | var newWidth = width; 69 | var newHeight = height; 70 | 71 | //计算压缩的比率 72 | var scaleWidth = (newWidth) / oldWidth; 73 | var scaleHeight = (newHeight) / oldHeight; 74 | 75 | //获取想要缩放的matrix 76 | var matrix = Matrix(); 77 | matrix.postScale(scaleWidth, scaleHeight); 78 | //获取新的bitmap 79 | bitmap1 = Bitmap.createBitmap(bitmap, 0, 0, oldWidth, oldHeight, matrix, true); 80 | return bitmap1 81 | } 82 | 83 | 84 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/utils/LogHelper.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.utils 2 | 3 | import android.os.Build 4 | import android.util.Log 5 | import com.devyk.av.rtmp.library.callback.ILog 6 | 7 | /** 8 | *
9 | * author : devyk on 2020-06-02 00:07 10 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 11 | * github : https://github.com/yangkun19921001 12 | * mailbox : yang1001yk@gmail.com 13 | * desc : This is LogHelper 14 | *15 | */ 16 | public object LogHelper : ILog { 17 | 18 | var isShowLog = false 19 | 20 | 21 | override fun i(tag: String, info: String?) { 22 | if (isShowLog) 23 | Log.i(tag, info) 24 | 25 | } 26 | 27 | override fun e(tag: String, info: String?) { 28 | if (isShowLog) 29 | Log.e(tag, info) 30 | } 31 | 32 | override fun w(tag: String, info: String?) { 33 | if (isShowLog) 34 | Log.w(tag, info) 35 | } 36 | 37 | override fun d(tag: String, info: String?) { 38 | if (isShowLog) 39 | Log.d(tag, info) 40 | } 41 | } -------------------------------------------------------------------------------- /library/src/main/java/com/devyk/av/rtmp/library/widget/GLSurfaceView.kt: -------------------------------------------------------------------------------- 1 | package com.devyk.av.rtmp.library.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.SurfaceHolder 6 | import android.view.SurfaceView 7 | import com.devyk.av.camera_recorder.callback.IRenderer 8 | import java.lang.ref.WeakReference 9 | import android.view.Surface 10 | import com.devyk.av.camera_recorder.callback.IGLThreadConfig 11 | import com.devyk.av.rtmp.library.camera.GLThread 12 | import com.devyk.av.rtmp.library.config.RendererConfiguration 13 | import javax.microedition.khronos.egl.EGLContext 14 | 15 | 16 | /** 17 | *
18 | * author : devyk on 2020-07-04 15:36 19 | * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts 20 | * github : https://github.com/yangkun19921001 21 | * mailbox : yang1001yk@gmail.com 22 | * desc : This is GLSurfaceView 23 | *24 | * 25 | * 26 | * 步骤: 27 | * 1、继承 SurfaceView 并实现 CallBack 回调 28 | * 2、自定义 GLThread 线程类,主要用于 OpenGL 绘制工作 29 | * 3、添加设置 Surface 和 EglContext 的方法 30 | * 4、提供和系统 GLSurfaceView 相同的调用方法 31 | * 32 | */ 33 | public open class GLSurfaceView : SurfaceView, SurfaceHolder.Callback, IGLThreadConfig { 34 | 35 | 36 | 37 | 38 | 39 | public val TAG = javaClass.simpleName 40 | 41 | 42 | /** 43 | * 渲染模式-默认自动模式 44 | */ 45 | private var mRendererMode = RENDERERMODE_CONTINUOUSLY 46 | 47 | /** 48 | * 渲染配置 49 | */ 50 | private lateinit var mRendererConfiguration: RendererConfiguration 51 | 52 | /** 53 | * GLES 渲染线程 54 | */ 55 | private lateinit var mEglThread: GLSurfaceThread 56 | 57 | 58 | private var mSurface: Surface? = null 59 | 60 | private var mEGLContext: EGLContext? = null 61 | 62 | 63 | 64 | 65 | companion object { 66 | /** 67 | * 手动调用渲染 68 | */ 69 | const val RENDERERMODE_WHEN_DIRTY = 0 70 | /** 71 | * 自动渲染 72 | */ 73 | const val RENDERERMODE_CONTINUOUSLY = 1 74 | } 75 | 76 | init { 77 | mRendererConfiguration = RendererConfiguration.createDefault() 78 | } 79 | 80 | /** 81 | * 渲染器 82 | */ 83 | public var mRenderer: IRenderer? = null 84 | 85 | constructor(context: Context?) : this(context, null) 86 | constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) 87 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 88 | holder.addCallback(this) 89 | } 90 | 91 | override fun surfaceCreated(holder: SurfaceHolder) { 92 | if (mSurface == null) { 93 | mSurface = holder.surface 94 | } 95 | this.mEglThread = GLSurfaceThread( 96 | WeakReference