詹子聪 5 anos atrás
commit
c7bbb0e352
100 arquivos alterados com 7235 adições e 0 exclusões
  1. 6 0
      .gitignore
  2. 201 0
      LICENSE.txt
  3. 147 0
      README.md
  4. 1 0
      app/.gitignore
  5. 31 0
      app/build.gradle
  6. 40 0
      app/google-services.json
  7. 17 0
      app/proguard-rules.pro
  8. 119 0
      app/src/main/AndroidManifest.xml
  9. BIN
      app/src/main/ic_launcher-web.png
  10. 154 0
      app/src/main/java/com/pedro/rtpstreamer/MainActivity.java
  11. 65 0
      app/src/main/java/com/pedro/rtpstreamer/backgroundexample/BackgroundActivity.kt
  12. 80 0
      app/src/main/java/com/pedro/rtpstreamer/backgroundexample/ConnectCheckerRtp.kt
  13. 180 0
      app/src/main/java/com/pedro/rtpstreamer/backgroundexample/RtpService.kt
  14. 432 0
      app/src/main/java/com/pedro/rtpstreamer/customexample/RtmpActivity.java
  15. 458 0
      app/src/main/java/com/pedro/rtpstreamer/customexample/RtspActivity.java
  16. 222 0
      app/src/main/java/com/pedro/rtpstreamer/defaultexample/ExampleRtmpActivity.java
  17. 223 0
      app/src/main/java/com/pedro/rtpstreamer/defaultexample/ExampleRtspActivity.java
  18. 173 0
      app/src/main/java/com/pedro/rtpstreamer/displayexample/DisplayActivity.java
  19. 173 0
      app/src/main/java/com/pedro/rtpstreamer/displayexample/DisplayService.kt
  20. 319 0
      app/src/main/java/com/pedro/rtpstreamer/filestreamexample/RtmpFromFileActivity.java
  21. 318 0
      app/src/main/java/com/pedro/rtpstreamer/filestreamexample/RtspFromFileActivity.java
  22. 488 0
      app/src/main/java/com/pedro/rtpstreamer/openglexample/OpenGlRtmpActivity.java
  23. 494 0
      app/src/main/java/com/pedro/rtpstreamer/openglexample/OpenGlRtspActivity.java
  24. 219 0
      app/src/main/java/com/pedro/rtpstreamer/surfacemodeexample/SurfaceModeRtmpActivity.java
  25. 220 0
      app/src/main/java/com/pedro/rtpstreamer/surfacemodeexample/SurfaceModeRtspActivity.java
  26. 222 0
      app/src/main/java/com/pedro/rtpstreamer/texturemodeexample/TextureModeRtmpActivity.java
  27. 226 0
      app/src/main/java/com/pedro/rtpstreamer/texturemodeexample/TextureModeRtspActivity.java
  28. 27 0
      app/src/main/java/com/pedro/rtpstreamer/utils/ActivityLink.java
  29. 60 0
      app/src/main/java/com/pedro/rtpstreamer/utils/ImageAdapter.java
  30. 123 0
      app/src/main/java/com/pedro/rtpstreamer/utils/PathUtils.java
  31. BIN
      app/src/main/res/drawable-hdpi/icon_camera.png
  32. BIN
      app/src/main/res/drawable-hdpi/icon_camera_off.png
  33. BIN
      app/src/main/res/drawable-hdpi/icon_menu.png
  34. BIN
      app/src/main/res/drawable-hdpi/icon_microphone.png
  35. BIN
      app/src/main/res/drawable-hdpi/icon_microphone_off.png
  36. BIN
      app/src/main/res/drawable-hdpi/icon_mobile.png
  37. BIN
      app/src/main/res/drawable-mdpi/icon_camera.png
  38. BIN
      app/src/main/res/drawable-mdpi/icon_camera_off.png
  39. BIN
      app/src/main/res/drawable-mdpi/icon_menu.png
  40. BIN
      app/src/main/res/drawable-mdpi/icon_microphone.png
  41. BIN
      app/src/main/res/drawable-mdpi/icon_microphone_off.png
  42. BIN
      app/src/main/res/drawable-mdpi/icon_mobile.png
  43. BIN
      app/src/main/res/drawable-xhdpi/icon_camera.png
  44. BIN
      app/src/main/res/drawable-xhdpi/icon_camera_off.png
  45. BIN
      app/src/main/res/drawable-xhdpi/icon_menu.png
  46. BIN
      app/src/main/res/drawable-xhdpi/icon_microphone.png
  47. BIN
      app/src/main/res/drawable-xhdpi/icon_microphone_off.png
  48. BIN
      app/src/main/res/drawable-xhdpi/icon_mobile.png
  49. BIN
      app/src/main/res/drawable-xxhdpi/icon_camera.png
  50. BIN
      app/src/main/res/drawable-xxhdpi/icon_camera_off.png
  51. BIN
      app/src/main/res/drawable-xxhdpi/icon_menu.png
  52. BIN
      app/src/main/res/drawable-xxhdpi/icon_microphone.png
  53. BIN
      app/src/main/res/drawable-xxhdpi/icon_microphone_off.png
  54. BIN
      app/src/main/res/drawable-xxhdpi/icon_mobile.png
  55. 14 0
      app/src/main/res/drawable/notification_anim.xml
  56. 33 0
      app/src/main/res/layout/activity_background.xml
  57. 88 0
      app/src/main/res/layout/activity_custom.xml
  58. 40 0
      app/src/main/res/layout/activity_display.xml
  59. 56 0
      app/src/main/res/layout/activity_example.xml
  60. 89 0
      app/src/main/res/layout/activity_from_file.xml
  61. 45 0
      app/src/main/res/layout/activity_main.xml
  62. 63 0
      app/src/main/res/layout/activity_open_gl.xml
  63. 58 0
      app/src/main/res/layout/activity_texture_mode.xml
  64. 182 0
      app/src/main/res/menu/gl_menu.xml
  65. 26 0
      app/src/main/res/menu/menu.xml
  66. 76 0
      app/src/main/res/menu/options_rtmp.xml
  67. 81 0
      app/src/main/res/menu/options_rtsp.xml
  68. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  69. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  70. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  71. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  72. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  73. BIN
      app/src/main/res/raw/banana.gif
  74. BIN
      app/src/main/res/raw/big_bunny_240p.mp4
  75. 8 0
      app/src/main/res/transition/slide_in.xml
  76. 8 0
      app/src/main/res/transition/slide_out.xml
  77. 6 0
      app/src/main/res/values-w820dp/dimens.xml
  78. 6 0
      app/src/main/res/values/colors.xml
  79. 16 0
      app/src/main/res/values/dimens.xml
  80. 105 0
      app/src/main/res/values/strings.xml
  81. 20 0
      app/src/main/res/values/styles.xml
  82. 11 0
      app/src/main/res/xml/button_style.xml
  83. 5 0
      app/src/main/res/xml/edit_text_auth.xml
  84. 7 0
      app/src/main/res/xml/edit_text_int.xml
  85. 29 0
      app/src/main/res/xml/options_header.xml
  86. 21 0
      app/src/main/res/xml/radio_group_audio.xml
  87. 35 0
      build.gradle
  88. 1 0
      encoder/.gitignore
  89. 24 0
      encoder/build.gradle
  90. 17 0
      encoder/proguard-rules.pro
  91. 1 0
      encoder/src/main/AndroidManifest.xml
  92. 235 0
      encoder/src/main/java/com/pedro/encoder/BaseEncoder.java
  93. 18 0
      encoder/src/main/java/com/pedro/encoder/EncoderCallback.java
  94. 86 0
      encoder/src/main/java/com/pedro/encoder/Frame.java
  95. 5 0
      encoder/src/main/java/com/pedro/encoder/GetFrame.java
  96. 171 0
      encoder/src/main/java/com/pedro/encoder/audio/AudioEncoder.java
  97. 17 0
      encoder/src/main/java/com/pedro/encoder/audio/GetAacData.java
  98. 84 0
      encoder/src/main/java/com/pedro/encoder/input/audio/AudioPostProcessEffect.java
  99. 10 0
      encoder/src/main/java/com/pedro/encoder/input/audio/CustomAudioEffect.java
  100. 0 0
      encoder/src/main/java/com/pedro/encoder/input/audio/GetMicrophoneData.java

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+*.iml
+.gradle
+/local.properties
+/.idea
+/build
+.DS_Store

+ 201 - 0
LICENSE.txt

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 147 - 0
README.md

@@ -0,0 +1,147 @@
+# rtmp-rtsp-stream-client-java
+
+[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-rtmp%20rtsp%20stream%20client%20java-green.svg?style=true)](https://android-arsenal.com/details/1/5333)
+[![Release](https://jitpack.io/v/pedroSG94/rtmp-rtsp-stream-client-java.svg)](https://jitpack.io/#pedroSG94/rtmp-rtsp-stream-client-java)
+
+Library for stream in RTMP and RTSP. All code in Java.
+
+If you need a player see this project:
+
+https://github.com/pedroSG94/vlc-example-streamplayer
+
+## Wiki
+
+https://github.com/pedroSG94/rtmp-rtsp-stream-client-java/wiki
+
+## Permissions:
+
+```xml
+<uses-permission android:name="android.permission.INTERNET" />
+<uses-permission android:name="android.permission.RECORD_AUDIO" />
+<uses-permission android:name="android.permission.CAMERA" />
+<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+<!--Optional for play store-->
+<uses-feature android:name="android.hardware.camera" android:required="false" />
+<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
+```
+
+## Compile
+
+To use this library in your project with gradle add this to your build.gradle:
+
+```gradle
+allprojects {
+  repositories {
+    maven { url 'https://jitpack.io' }
+  }
+}
+dependencies {
+  implementation 'com.github.pedroSG94.rtmp-rtsp-stream-client-java:rtplibrary:1.9.5'
+}
+
+```
+
+## Features:
+
+- [x] Android min API 16.
+- [x] Support [camera1](https://developer.android.com/reference/android/hardware/Camera.html) and [camera2](https://developer.android.com/reference/android/hardware/camera2/package-summary.html) API
+- [x] Encoder type buffer to buffer.
+- [x] Encoder type surface to buffer.
+- [x] RTMP/RTSP auth.
+- [x] Audio noise suppressor.
+- [x] Audio echo cancellation.
+- [x] Disable/Enable video and audio while streaming.
+- [x] Switch camera while streaming.
+- [x] Change video bitrate while streaming (API 19+).
+- [X] Get upload bandwidth used.
+- [X] Record MP4 file while streaming (API 18+).
+- [x] H264, H265 and AAC hardware encoding.
+- [x] Force H264 and AAC Codec hardware/software encoding (Not recommended).
+- [x] RTSP TCP/UDP.
+- [x] Stream from video and audio files like mp4, webm, mp3, etc (Limited by device decoders). [More info](https://github.com/pedroSG94/rtmp-rtsp-stream-client-java/wiki/Stream-from-file)
+- [x] Stream device display (API 21+).
+- [X] Set Image, Gif or Text to stream on real time.
+- [X] OpenGL real time filters. [More info](https://github.com/pedroSG94/rtmp-rtsp-stream-client-java/wiki/Real-time-filters)
+- [X] RTMPS and RTSPS.
+- [X] RTSP H265 support (Waiting FLV official packetization to add RTMP support).
+
+## Other related projects:
+
+https://github.com/pedroSG94/RTSP-Server
+
+https://github.com/pedroSG94/AndroidReStreamer
+
+https://github.com/pedroSG94/Stream-USB-test
+
+## Use example:
+
+This code is a basic example.
+I recommend you go to Activities in app module and see all examples.
+
+### RTMP:
+
+```java
+
+//default
+
+//create builder
+RtmpCamera1 rtmpCamera1 = new RtmpCamera1(openGlView, connectCheckerRtmp);
+//start stream
+if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) {
+  rtmpCamera1.startStream("rtmp://yourEndPoint");
+} else {
+ /**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found)*/
+}
+//stop stream
+rtmpCamera1.stopStream();
+
+//with params
+
+//create builder
+RtmpCamera1 rtmpCamera1 = new RtmpCamera1(openGlView, connectCheckerRtmp);
+//start stream
+if (rtmpCamera1.prepareAudio(int bitrate, int sampleRate, boolean isStereo, boolean echoCanceler,
+      boolean noiseSuppressor) && rtmpCamera1.prepareVideo(int width, int height, int fps, int bitrate, int rotation)) {
+  rtmpCamera1.startStream("rtmp://yourEndPoint");
+} else {
+ /**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found)*/
+}
+//stop stream
+rtmpCamera1.stopStream();
+
+```
+
+### RTSP:
+
+```java
+
+//default
+
+//create builder
+//by default TCP protocol.
+RtspCamera1 rtspCamera1 = new RtspCamera1(openGlView, connectCheckerRtsp);
+//start stream
+if (rtspCamera1.prepareAudio() && rtspCamera1.prepareVideo()) {
+  rtspCamera1.startStream("rtsp://yourEndPoint");
+} else {
+ /**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found)*/
+}
+//stop stream
+rtspCamera1.stopStream();
+
+//with params
+
+//create builder
+RtspCamera1 rtspCamera1 = new RtspCamera1(openGlView, connectCheckerRtsp);
+rtspCamera1.setProtocol(protocol);
+//start stream
+if (rtspCamera1.prepareAudio(int bitrate, int sampleRate, boolean isStereo, boolean echoCanceler,
+      boolean noiseSuppressor) && rtspCamera1.prepareVideo(int width, int height, int fps, int bitrate, int rotation)) {
+  rtspCamera1.startStream("rtsp://yourEndPoint");
+} else {
+ /**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found)*/
+}
+//stop stream
+rtspCamera1.stopStream();
+
+```

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 31 - 0
app/build.gradle

@@ -0,0 +1,31 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'com.google.gms.google-services'
+apply plugin: 'com.google.firebase.crashlytics'
+
+android {
+  compileSdkVersion 30
+
+  defaultConfig {
+    applicationId "com.pedro.rtpstreamer"
+    minSdkVersion 16
+    targetSdkVersion 30
+    versionCode 195
+    versionName "1.9.5"
+  }
+  buildTypes {
+    release {
+      minifyEnabled false
+      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+    }
+  }
+}
+
+dependencies {
+  implementation project(':rtplibrary')
+  implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
+  implementation 'com.google.firebase:firebase-analytics:18.0.0'
+  implementation 'com.google.android.material:material:1.2.1'
+  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}

+ 40 - 0
app/google-services.json

@@ -0,0 +1,40 @@
+{
+  "project_info": {
+    "project_number": "1029823405259",
+    "firebase_url": "https://rtmp-rtsp-stream-client-java.firebaseio.com",
+    "project_id": "rtmp-rtsp-stream-client-java",
+    "storage_bucket": "rtmp-rtsp-stream-client-java.appspot.com"
+  },
+  "client": [
+    {
+      "client_info": {
+        "mobilesdk_app_id": "1:1029823405259:android:3c2521363b33fd5a58cf4e",
+        "android_client_info": {
+          "package_name": "com.pedro.rtpstreamer"
+        }
+      },
+      "oauth_client": [
+        {
+          "client_id": "1029823405259-9ql0gpi05ktgv9ov5d7qdqhbbkerv6el.apps.googleusercontent.com",
+          "client_type": 3
+        }
+      ],
+      "api_key": [
+        {
+          "current_key": "AIzaSyDSgx6KKR9Pm64lQ1ttdgzPJVivkHRFUbY"
+        }
+      ],
+      "services": {
+        "appinvite_service": {
+          "other_platform_oauth_client": [
+            {
+              "client_id": "1029823405259-9ql0gpi05ktgv9ov5d7qdqhbbkerv6el.apps.googleusercontent.com",
+              "client_type": 3
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "configuration_version": "1"
+}

+ 17 - 0
app/proguard-rules.pro

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/pedro/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 119 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.pedro.rtpstreamer"
+    >
+
+  <uses-permission android:name="android.permission.FLASHLIGHT"/>
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+  <!--some devices need read permission to create folders or files-->
+  <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.INTERNET"/>
+  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+  <uses-permission android:name="android.permission.CAMERA"/>
+
+  <!--needed by background Rtp service to keep service alive-->
+  <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+
+  <!--Optional for play store-->
+  <uses-feature android:name="android.hardware.camera" android:required="false" />
+  <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
+
+  <application
+      android:requestLegacyExternalStorage="true"
+      android:allowBackup="true"
+      android:icon="@mipmap/ic_launcher"
+      android:supportsRtl="true"
+      android:theme="@style/AppTheme"
+      >
+    <activity
+        android:name=".MainActivity"
+        android:label="@string/app_name"
+        android:screenOrientation="portrait"
+        >
+      <intent-filter>
+        <action android:name="android.intent.action.VIEW"/>
+        <action android:name="android.intent.action.MAIN"/>
+
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+    </activity>
+    <activity
+        android:name=".customexample.RtmpActivity"
+        android:label="@string/rtmp_streamer"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".customexample.RtspActivity"
+        android:label="@string/rtsp_streamer"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".defaultexample.ExampleRtspActivity"
+        android:label="@string/default_rtsp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".defaultexample.ExampleRtmpActivity"
+        android:label="@string/default_rtmp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".filestreamexample.RtspFromFileActivity"
+        android:label="@string/from_file_rtsp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".filestreamexample.RtmpFromFileActivity"
+        android:label="@string/from_file_rtmp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".surfacemodeexample.SurfaceModeRtspActivity"
+        android:label="@string/surface_mode_rtsp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".surfacemodeexample.SurfaceModeRtmpActivity"
+        android:label="@string/surface_mode_rtmp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".texturemodeexample.TextureModeRtspActivity"
+        android:label="@string/texture_mode_rtsp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".texturemodeexample.TextureModeRtmpActivity"
+        android:label="@string/texture_mode_rtmp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".openglexample.OpenGlRtmpActivity"
+        android:label="@string/opengl_rtmp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".openglexample.OpenGlRtspActivity"
+        android:label="@string/opengl_rtsp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".displayexample.DisplayActivity"
+        android:label="@string/display_rtmp"
+        android:screenOrientation="portrait"
+        />
+    <activity
+        android:name=".backgroundexample.BackgroundActivity"
+        android:label="@string/service_rtp"
+        />
+
+    <service android:name=".backgroundexample.RtpService"
+        />
+
+    <service android:name=".displayexample.DisplayService"
+        android:foregroundServiceType="mediaProjection"
+        />
+  </application>
+
+</manifest>

BIN
app/src/main/ic_launcher-web.png


+ 154 - 0
app/src/main/java/com/pedro/rtpstreamer/MainActivity.java

@@ -0,0 +1,154 @@
+package com.pedro.rtpstreamer;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.core.app.ActivityCompat;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.GridView;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.pedro.rtpstreamer.backgroundexample.BackgroundActivity;
+import com.pedro.rtpstreamer.customexample.RtmpActivity;
+import com.pedro.rtpstreamer.customexample.RtspActivity;
+import com.pedro.rtpstreamer.defaultexample.ExampleRtmpActivity;
+import com.pedro.rtpstreamer.defaultexample.ExampleRtspActivity;
+import com.pedro.rtpstreamer.displayexample.DisplayActivity;
+import com.pedro.rtpstreamer.filestreamexample.RtmpFromFileActivity;
+import com.pedro.rtpstreamer.filestreamexample.RtspFromFileActivity;
+import com.pedro.rtpstreamer.openglexample.OpenGlRtmpActivity;
+import com.pedro.rtpstreamer.openglexample.OpenGlRtspActivity;
+import com.pedro.rtpstreamer.surfacemodeexample.SurfaceModeRtmpActivity;
+import com.pedro.rtpstreamer.surfacemodeexample.SurfaceModeRtspActivity;
+import com.pedro.rtpstreamer.texturemodeexample.TextureModeRtmpActivity;
+import com.pedro.rtpstreamer.texturemodeexample.TextureModeRtspActivity;
+import com.pedro.rtpstreamer.utils.ActivityLink;
+import com.pedro.rtpstreamer.utils.ImageAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.os.Build.VERSION_CODES.JELLY_BEAN;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+
+public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
+
+  private GridView list;
+  private List<ActivityLink> activities;
+
+  private final String[] PERMISSIONS = {
+      Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA,
+      Manifest.permission.WRITE_EXTERNAL_STORAGE
+  };
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_main);
+    overridePendingTransition(R.transition.slide_in, R.transition.slide_out);
+    TextView tvVersion = findViewById(R.id.tv_version);
+    tvVersion.setText(getString(R.string.version, BuildConfig.VERSION_NAME));
+
+    list = findViewById(R.id.list);
+    createList();
+    setListAdapter(activities);
+
+    if (!hasPermissions(this, PERMISSIONS)) {
+      ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
+    }
+  }
+
+  private void createList() {
+    activities = new ArrayList<>();
+    activities.add(
+        new ActivityLink(new Intent(this, RtmpActivity.class), getString(R.string.rtmp_streamer),
+            JELLY_BEAN));
+    activities.add(
+        new ActivityLink(new Intent(this, RtspActivity.class), getString(R.string.rtsp_streamer),
+            JELLY_BEAN));
+    activities.add(new ActivityLink(new Intent(this, ExampleRtmpActivity.class),
+        getString(R.string.default_rtmp), JELLY_BEAN));
+    activities.add(new ActivityLink(new Intent(this, ExampleRtspActivity.class),
+        getString(R.string.default_rtsp), JELLY_BEAN));
+    activities.add(new ActivityLink(new Intent(this, RtmpFromFileActivity.class),
+        getString(R.string.from_file_rtmp), JELLY_BEAN_MR2));
+    activities.add(new ActivityLink(new Intent(this, RtspFromFileActivity.class),
+        getString(R.string.from_file_rtsp), JELLY_BEAN_MR2));
+    activities.add(new ActivityLink(new Intent(this, SurfaceModeRtmpActivity.class),
+        getString(R.string.surface_mode_rtmp), LOLLIPOP));
+    activities.add(new ActivityLink(new Intent(this, SurfaceModeRtspActivity.class),
+        getString(R.string.surface_mode_rtsp), LOLLIPOP));
+    activities.add(new ActivityLink(new Intent(this, TextureModeRtmpActivity.class),
+        getString(R.string.texture_mode_rtmp), LOLLIPOP));
+    activities.add(new ActivityLink(new Intent(this, TextureModeRtspActivity.class),
+        getString(R.string.texture_mode_rtsp), LOLLIPOP));
+    activities.add(new ActivityLink(new Intent(this, OpenGlRtmpActivity.class),
+        getString(R.string.opengl_rtmp), JELLY_BEAN_MR2));
+    activities.add(new ActivityLink(new Intent(this, OpenGlRtspActivity.class),
+        getString(R.string.opengl_rtsp), JELLY_BEAN_MR2));
+    activities.add(new ActivityLink(new Intent(this, DisplayActivity.class),
+        getString(R.string.display_rtmp), LOLLIPOP));
+    activities.add(new ActivityLink(new Intent(this, BackgroundActivity.class),
+        getString(R.string.service_rtp), LOLLIPOP));
+  }
+
+  private void setListAdapter(List<ActivityLink> activities) {
+    list.setAdapter(new ImageAdapter(activities));
+    list.setOnItemClickListener(this);
+  }
+
+  @Override
+  public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+    if (hasPermissions(this, PERMISSIONS)) {
+      ActivityLink link = activities.get(i);
+      int minSdk = link.getMinSdk();
+      if (Build.VERSION.SDK_INT >= minSdk) {
+        startActivity(link.getIntent());
+        overridePendingTransition(R.transition.slide_in, R.transition.slide_out);
+      } else {
+        showMinSdkError(minSdk);
+      }
+    } else {
+      showPermissionsErrorAndRequest();
+    }
+  }
+
+  private void showMinSdkError(int minSdk) {
+    String named;
+    switch (minSdk) {
+      case JELLY_BEAN_MR2:
+        named = "JELLY_BEAN_MR2";
+        break;
+      case LOLLIPOP:
+        named = "LOLLIPOP";
+        break;
+      default:
+        named = "JELLY_BEAN";
+        break;
+    }
+    Toast.makeText(this, "You need min Android " + named + " (API " + minSdk + " )",
+        Toast.LENGTH_SHORT).show();
+  }
+
+  private void showPermissionsErrorAndRequest() {
+    Toast.makeText(this, "You need permissions before", Toast.LENGTH_SHORT).show();
+    ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
+  }
+
+  private boolean hasPermissions(Context context, String... permissions) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) {
+      for (String permission : permissions) {
+        if (ActivityCompat.checkSelfPermission(context, permission)
+            != PackageManager.PERMISSION_GRANTED) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+}

+ 65 - 0
app/src/main/java/com/pedro/rtpstreamer/backgroundexample/BackgroundActivity.kt

@@ -0,0 +1,65 @@
+package com.pedro.rtpstreamer.backgroundexample
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.SurfaceHolder
+import androidx.appcompat.app.AppCompatActivity
+import com.pedro.rtpstreamer.R
+import kotlinx.android.synthetic.main.activity_background.*
+
+class BackgroundActivity : AppCompatActivity(), SurfaceHolder.Callback {
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    setContentView(R.layout.activity_background)
+    RtpService.init(this)
+    b_start_stop.setOnClickListener {
+      if (isMyServiceRunning(RtpService::class.java)) {
+        stopService(Intent(applicationContext, RtpService::class.java))
+        b_start_stop.setText(R.string.start_button)
+      } else {
+        val intent = Intent(applicationContext, RtpService::class.java)
+        intent.putExtra("endpoint", et_rtp_url.text.toString())
+        startService(intent)
+        b_start_stop.setText(R.string.stop_button)
+      }
+    }
+    surfaceView.holder.addCallback(this)
+  }
+
+  override fun surfaceChanged(holder: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
+    RtpService.setView(surfaceView)
+    RtpService.startPreview()
+  }
+
+  override fun surfaceDestroyed(holder: SurfaceHolder) {
+    RtpService.setView(applicationContext)
+    RtpService.stopPreview()
+  }
+
+  override fun surfaceCreated(holder: SurfaceHolder) {
+
+  }
+
+  override fun onResume() {
+    super.onResume()
+    if (isMyServiceRunning(RtpService::class.java)) {
+      b_start_stop.setText(R.string.stop_button)
+    } else {
+      b_start_stop.setText(R.string.start_button)
+    }
+  }
+
+  @Suppress("DEPRECATION")
+  private fun isMyServiceRunning(serviceClass: Class<*>): Boolean {
+    val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+    for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
+      if (serviceClass.name == service.service.className) {
+        return true
+      }
+    }
+    return false
+  }
+}

+ 80 - 0
app/src/main/java/com/pedro/rtpstreamer/backgroundexample/ConnectCheckerRtp.kt

@@ -0,0 +1,80 @@
+package com.pedro.rtpstreamer.backgroundexample
+
+import com.pedro.rtsp.utils.ConnectCheckerRtsp
+import net.ossrs.rtmp.ConnectCheckerRtmp
+
+/**
+ * (Only working in kotlin)
+ * Mix both connect interfaces to support RTMP and RTSP in service with same code.
+ */
+interface ConnectCheckerRtp: ConnectCheckerRtmp, ConnectCheckerRtsp {
+
+  /**
+   * Commons
+   */
+  fun onConnectionSuccessRtp()
+
+  fun onConnectionFailedRtp(reason: String)
+
+  fun onNewBitrateRtp(bitrate: Long)
+
+  fun onDisconnectRtp()
+
+  fun onAuthErrorRtp()
+
+  fun onAuthSuccessRtp()
+
+  /**
+   * RTMP
+   */
+  override fun onConnectionSuccessRtmp() {
+    onConnectionSuccessRtp()
+  }
+
+  override fun onConnectionFailedRtmp(reason: String) {
+    onConnectionFailedRtp(reason)
+  }
+
+  override fun onNewBitrateRtmp(bitrate: Long) {
+    onNewBitrateRtp(bitrate)
+  }
+
+  override fun onDisconnectRtmp() {
+    onDisconnectRtp()
+  }
+
+  override fun onAuthErrorRtmp() {
+    onAuthErrorRtp()
+  }
+
+  override fun onAuthSuccessRtmp() {
+    onAuthSuccessRtp()
+  }
+
+  /**
+   * RTSP
+   */
+  override fun onConnectionSuccessRtsp() {
+    onConnectionSuccessRtp()
+  }
+
+  override fun onConnectionFailedRtsp(reason: String) {
+    onConnectionFailedRtp(reason)
+  }
+
+  override fun onNewBitrateRtsp(bitrate: Long) {
+    onNewBitrateRtp(bitrate)
+  }
+
+  override fun onDisconnectRtsp() {
+    onDisconnectRtp()
+  }
+
+  override fun onAuthErrorRtsp() {
+    onAuthErrorRtp()
+  }
+
+  override fun onAuthSuccessRtsp() {
+    onAuthSuccessRtp()
+  }
+}

+ 180 - 0
app/src/main/java/com/pedro/rtpstreamer/backgroundexample/RtpService.kt

@@ -0,0 +1,180 @@
+package com.pedro.rtpstreamer.backgroundexample
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.app.NotificationCompat
+import com.pedro.rtplibrary.base.Camera2Base
+import com.pedro.rtplibrary.rtmp.RtmpCamera2
+import com.pedro.rtplibrary.rtsp.RtspCamera2
+import com.pedro.rtplibrary.view.OpenGlView
+import com.pedro.rtpstreamer.R
+
+
+/**
+ * Basic RTMP/RTSP service streaming implementation with camera2
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+class RtpService : Service() {
+
+  private var endpoint: String? = null
+
+  override fun onCreate() {
+    super.onCreate()
+    Log.e(TAG, "RTP service create")
+    notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+      val channel = NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH)
+      notificationManager?.createNotificationChannel(channel)
+    }
+    keepAliveTrick()
+  }
+
+  private fun keepAliveTrick() {
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
+      val notification = NotificationCompat.Builder(this, channelId)
+          .setOngoing(true)
+          .setContentTitle("")
+          .setContentText("").build()
+      startForeground(1, notification)
+    } else {
+      startForeground(1, Notification())
+    }
+  }
+
+  override fun onBind(p0: Intent?): IBinder? {
+    return null
+  }
+
+  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+    Log.e(TAG, "RTP service started")
+    endpoint = intent?.extras?.getString("endpoint")
+    if (endpoint != null) {
+      prepareStreamRtp()
+      startStreamRtp(endpoint!!)
+    }
+    return START_STICKY
+  }
+
+  companion object {
+    private val TAG = "RtpService"
+    private val channelId = "rtpStreamChannel"
+    private val notifyId = 123456
+    private var notificationManager: NotificationManager? = null
+    private var camera2Base: Camera2Base? = null
+    private var openGlView: OpenGlView? = null
+    private var contextApp: Context? = null
+
+    fun setView(openGlView: OpenGlView) {
+      this.openGlView = openGlView
+      camera2Base?.replaceView(openGlView)
+    }
+
+    fun setView(context: Context) {
+      contextApp = context
+      this.openGlView = null
+      camera2Base?.replaceView(context)
+    }
+
+    fun startPreview() {
+      camera2Base?.startPreview()
+    }
+
+    fun init(context: Context) {
+      contextApp = context
+      if (camera2Base == null) camera2Base = RtmpCamera2(context, true, connectCheckerRtp)
+    }
+
+    fun stopStream() {
+      if (camera2Base != null) {
+        if (camera2Base!!.isStreaming) camera2Base!!.stopStream()
+      }
+    }
+
+    fun stopPreview() {
+      if (camera2Base != null) {
+        if (camera2Base!!.isOnPreview) camera2Base!!.stopPreview()
+      }
+    }
+
+
+    private val connectCheckerRtp = object : ConnectCheckerRtp {
+      override fun onConnectionSuccessRtp() {
+        showNotification("Stream started")
+        Log.e(TAG, "RTP service destroy")
+      }
+
+      override fun onNewBitrateRtp(bitrate: Long) {
+
+      }
+
+      override fun onConnectionFailedRtp(reason: String) {
+        showNotification("Stream connection failed")
+        Log.e(TAG, "RTP service destroy")
+      }
+
+      override fun onDisconnectRtp() {
+        showNotification("Stream stopped")
+      }
+
+      override fun onAuthErrorRtp() {
+        showNotification("Stream auth error")
+      }
+
+      override fun onAuthSuccessRtp() {
+        showNotification("Stream auth success")
+      }
+    }
+
+    private fun showNotification(text: String) {
+      contextApp?.let {
+        val notification = NotificationCompat.Builder(it, channelId)
+            .setSmallIcon(R.mipmap.ic_launcher)
+            .setContentTitle("RTP Stream")
+            .setContentText(text).build()
+        notificationManager?.notify(notifyId, notification)
+      }
+    }
+  }
+
+  override fun onDestroy() {
+    super.onDestroy()
+    Log.e(TAG, "RTP service destroy")
+    stopStream()
+  }
+
+  private fun prepareStreamRtp() {
+    stopStream()
+    stopPreview()
+    if (endpoint!!.startsWith("rtmp")) {
+      camera2Base = if (openGlView == null) {
+        RtmpCamera2(baseContext, true, connectCheckerRtp)
+      } else {
+        RtmpCamera2(openGlView, connectCheckerRtp)
+      }
+    } else {
+      camera2Base = if (openGlView == null) {
+        RtspCamera2(baseContext, true, connectCheckerRtp)
+      } else {
+        RtspCamera2(openGlView, connectCheckerRtp)
+      }
+    }
+  }
+
+  private fun startStreamRtp(endpoint: String) {
+    if (!camera2Base!!.isStreaming) {
+      if (camera2Base!!.prepareVideo() && camera2Base!!.prepareAudio()) {
+        camera2Base!!.startStream(endpoint)
+      }
+    } else {
+      showNotification("You are already streaming :(")
+    }
+  }
+}

+ 432 - 0
app/src/main/java/com/pedro/rtpstreamer/customexample/RtmpActivity.java

@@ -0,0 +1,432 @@
+package com.pedro.rtpstreamer.customexample;
+
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.GravityCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+
+import com.google.android.material.navigation.NavigationView;
+import com.pedro.encoder.input.video.CameraHelper;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtplibrary.rtmp.RtmpCamera1;
+import com.pedro.rtpstreamer.R;
+
+import net.ossrs.rtmp.ConnectCheckerRtmp;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera1Base}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpCamera1}
+ */
+public class RtmpActivity extends AppCompatActivity
+    implements Button.OnClickListener, ConnectCheckerRtmp, SurfaceHolder.Callback,
+    View.OnTouchListener {
+
+  private Integer[] orientations = new Integer[] { 0, 90, 180, 270 };
+
+  private RtmpCamera1 rtmpCamera1;
+  private Button bStartStop, bRecord;
+  private EditText etUrl;
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+  //options menu
+  private DrawerLayout drawerLayout;
+  private NavigationView navigationView;
+  private ActionBarDrawerToggle actionBarDrawerToggle;
+  private RadioGroup rgChannel;
+  private Spinner spResolution;
+  private CheckBox cbEchoCanceler, cbNoiseSuppressor;
+  private EditText etVideoBitrate, etFps, etAudioBitrate, etSampleRate, etWowzaUser,
+      etWowzaPassword;
+  private String lastVideoBitrate;
+  private TextView tvBitrate;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_custom);
+    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+    getSupportActionBar().setHomeButtonEnabled(true);
+
+    SurfaceView surfaceView = findViewById(R.id.surfaceView);
+    surfaceView.getHolder().addCallback(this);
+    surfaceView.setOnTouchListener(this);
+    rtmpCamera1 = new RtmpCamera1(surfaceView, this);
+    prepareOptionsMenuViews();
+    tvBitrate = findViewById(R.id.tv_bitrate);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtmp);
+    bStartStop = findViewById(R.id.b_start_stop);
+    bStartStop.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+  }
+
+  private void prepareOptionsMenuViews() {
+    drawerLayout = findViewById(R.id.activity_custom);
+    navigationView = findViewById(R.id.nv_rtp);
+
+    navigationView.inflateMenu(R.menu.options_rtmp);
+    actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.rtmp_streamer,
+        R.string.rtmp_streamer) {
+
+      public void onDrawerOpened(View drawerView) {
+        actionBarDrawerToggle.syncState();
+        lastVideoBitrate = etVideoBitrate.getText().toString();
+      }
+
+      public void onDrawerClosed(View view) {
+        actionBarDrawerToggle.syncState();
+        if (lastVideoBitrate != null && !lastVideoBitrate.equals(
+            etVideoBitrate.getText().toString()) && rtmpCamera1.isStreaming()) {
+          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            int bitrate = Integer.parseInt(etVideoBitrate.getText().toString()) * 1024;
+            rtmpCamera1.setVideoBitrateOnFly(bitrate);
+            Toast.makeText(RtmpActivity.this, "New bitrate: " + bitrate, Toast.LENGTH_SHORT).
+                show();
+          } else {
+            Toast.makeText(RtmpActivity.this, "Bitrate on fly ignored, Required min API 19",
+                Toast.LENGTH_SHORT).show();
+          }
+        }
+      }
+    };
+    drawerLayout.addDrawerListener(actionBarDrawerToggle);
+    //checkboxs
+    cbEchoCanceler =
+        (CheckBox) navigationView.getMenu().findItem(R.id.cb_echo_canceler).getActionView();
+    cbNoiseSuppressor =
+        (CheckBox) navigationView.getMenu().findItem(R.id.cb_noise_suppressor).getActionView();
+    //radiobuttons
+    RadioButton rbTcp =
+        (RadioButton) navigationView.getMenu().findItem(R.id.rb_tcp).getActionView();
+    rgChannel = (RadioGroup) navigationView.getMenu().findItem(R.id.channel).getActionView();
+    rbTcp.setChecked(true);
+    //spinners
+    spResolution = (Spinner) navigationView.getMenu().findItem(R.id.sp_resolution).getActionView();
+
+    ArrayAdapter<Integer> orientationAdapter =
+        new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
+    orientationAdapter.addAll(orientations);
+
+    ArrayAdapter<String> resolutionAdapter =
+        new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
+    List<String> list = new ArrayList<>();
+    for (Camera.Size size : rtmpCamera1.getResolutionsBack()) {
+      list.add(size.width + "X" + size.height);
+    }
+    resolutionAdapter.addAll(list);
+    spResolution.setAdapter(resolutionAdapter);
+    //edittexts
+    etVideoBitrate =
+        (EditText) navigationView.getMenu().findItem(R.id.et_video_bitrate).getActionView();
+    etFps = (EditText) navigationView.getMenu().findItem(R.id.et_fps).getActionView();
+    etAudioBitrate =
+        (EditText) navigationView.getMenu().findItem(R.id.et_audio_bitrate).getActionView();
+    etSampleRate = (EditText) navigationView.getMenu().findItem(R.id.et_samplerate).getActionView();
+    etVideoBitrate.setText("2500");
+    etFps.setText("30");
+    etAudioBitrate.setText("128");
+    etSampleRate.setText("44100");
+    etWowzaUser = (EditText) navigationView.getMenu().findItem(R.id.et_user).getActionView();
+    etWowzaPassword =
+        (EditText) navigationView.getMenu().findItem(R.id.et_password).getActionView();
+  }
+
+  @Override
+  protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+    super.onPostCreate(savedInstanceState);
+    actionBarDrawerToggle.syncState();
+  }
+
+  @Override
+  public boolean onCreateOptionsMenu(Menu menu) {
+    getMenuInflater().inflate(R.menu.menu, menu);
+    return true;
+  }
+
+  @Override
+  public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+      case android.R.id.home:
+        if (!drawerLayout.isDrawerOpen(GravityCompat.START)) {
+          drawerLayout.openDrawer(GravityCompat.START);
+        } else {
+          drawerLayout.closeDrawer(GravityCompat.START);
+        }
+        return true;
+      case R.id.microphone:
+        if (!rtmpCamera1.isAudioMuted()) {
+          item.setIcon(getResources().getDrawable(R.drawable.icon_microphone_off));
+          rtmpCamera1.disableAudio();
+        } else {
+          item.setIcon(getResources().getDrawable(R.drawable.icon_microphone));
+          rtmpCamera1.enableAudio();
+        }
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  @Override
+  public void onClick(View v) {
+    switch (v.getId()) {
+      case R.id.b_start_stop:
+        Log.d("TAG_R", "b_start_stop: ");
+        if (!rtmpCamera1.isStreaming()) {
+          bStartStop.setText(getResources().getString(R.string.stop_button));
+          String user = etWowzaUser.getText().toString();
+          String password = etWowzaPassword.getText().toString();
+          if (!user.isEmpty() && !password.isEmpty()) {
+            rtmpCamera1.setAuthorization(user, password);
+          }
+          if (rtmpCamera1.isRecording() || prepareEncoders()) {
+            rtmpCamera1.startStream(etUrl.getText().toString());
+          } else {
+            //If you see this all time when you start stream,
+            //it is because your encoder device dont support the configuration
+            //in video encoder maybe color format.
+            //If you have more encoder go to VideoEncoder or AudioEncoder class,
+            //change encoder and try
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+            bStartStop.setText(getResources().getString(R.string.start_button));
+          }
+        } else {
+          bStartStop.setText(getResources().getString(R.string.start_button));
+          rtmpCamera1.stopStream();
+        }
+        break;
+      case R.id.b_record:
+        Log.d("TAG_R", "b_start_stop: ");
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+          if (!rtmpCamera1.isRecording()) {
+            try {
+              if (!folder.exists()) {
+                folder.mkdir();
+              }
+              SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+              currentDateAndTime = sdf.format(new Date());
+              if (!rtmpCamera1.isStreaming()) {
+                if (prepareEncoders()) {
+                  rtmpCamera1.startRecord(
+                      folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                  bRecord.setText(R.string.stop_record);
+                  Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+                } else {
+                  Toast.makeText(this, "Error preparing stream, This device cant do it",
+                      Toast.LENGTH_SHORT).show();
+                }
+              } else {
+                rtmpCamera1.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              }
+            } catch (IOException e) {
+              rtmpCamera1.stopRecord();
+              bRecord.setText(R.string.start_record);
+              Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+            }
+          } else {
+            rtmpCamera1.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this,
+                "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+                Toast.LENGTH_SHORT).show();
+            currentDateAndTime = "";
+          }
+        } else {
+          Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
+              Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtmpCamera1.switchCamera();
+        } catch (final CameraOpenException e) {
+          Toast.makeText(RtmpActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  private boolean prepareEncoders() {
+    Camera.Size resolution =
+        rtmpCamera1.getResolutionsBack().get(spResolution.getSelectedItemPosition());
+    int width = resolution.width;
+    int height = resolution.height;
+    return rtmpCamera1.prepareVideo(width, height, Integer.parseInt(etFps.getText().toString()),
+        Integer.parseInt(etVideoBitrate.getText().toString()) * 1024,
+        CameraHelper.getCameraOrientation(this)) && rtmpCamera1.prepareAudio(
+        Integer.parseInt(etAudioBitrate.getText().toString()) * 1024,
+        Integer.parseInt(etSampleRate.getText().toString()),
+        rgChannel.getCheckedRadioButtonId() == R.id.rb_stereo, cbEchoCanceler.isChecked(),
+        cbNoiseSuppressor.isChecked());
+  }
+
+  @Override
+  public void onConnectionSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtmp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
+            .show();
+        rtmpCamera1.stopStream();
+        bStartStop.setText(getResources().getString(R.string.start_button));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
+            && rtmpCamera1.isRecording()) {
+          rtmpCamera1.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(RtmpActivity.this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtmp(final long bitrate) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        tvBitrate.setText(bitrate + " bps");
+      }
+    });
+  }
+
+  @Override
+  public void onDisconnectRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
+            && rtmpCamera1.isRecording()) {
+          rtmpCamera1.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(RtmpActivity.this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+    drawerLayout.openDrawer(GravityCompat.START);
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtmpCamera1.startPreview();
+    // optionally:
+    //rtmpCamera1.startPreview(CameraHelper.Facing.BACK);
+    //or
+    //rtmpCamera1.startPreview(CameraHelper.Facing.FRONT);
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtmpCamera1.isRecording()) {
+      rtmpCamera1.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtmpCamera1.isStreaming()) {
+      rtmpCamera1.stopStream();
+      bStartStop.setText(getResources().getString(R.string.start_button));
+    }
+    rtmpCamera1.stopPreview();
+  }
+
+  @Override
+  public boolean onTouch(View view, MotionEvent motionEvent) {
+    int action = motionEvent.getAction();
+    if (motionEvent.getPointerCount() > 1) {
+      if (action == MotionEvent.ACTION_MOVE) {
+        rtmpCamera1.setZoom(motionEvent);
+      }
+    } else {
+      if (action == MotionEvent.ACTION_UP) {
+        // todo place to add autofocus functional.
+      }
+    }
+    return true;
+  }
+}

+ 458 - 0
app/src/main/java/com/pedro/rtpstreamer/customexample/RtspActivity.java

@@ -0,0 +1,458 @@
+package com.pedro.rtpstreamer.customexample;
+
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.GravityCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+import com.google.android.material.navigation.NavigationView;
+import com.pedro.encoder.input.video.CameraHelper;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtplibrary.rtsp.RtspCamera1;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtsp.rtsp.Protocol;
+import com.pedro.rtsp.utils.ConnectCheckerRtsp;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera1Base}
+ * {@link com.pedro.rtplibrary.rtsp.RtspCamera1}
+ */
+public class RtspActivity extends AppCompatActivity
+    implements Button.OnClickListener, ConnectCheckerRtsp, SurfaceHolder.Callback,
+    View.OnTouchListener {
+
+  private Integer[] orientations = new Integer[] { 0, 90, 180, 270 };
+
+  private RtspCamera1 rtspCamera1;
+  private SurfaceView surfaceView;
+  private Button bStartStop, bRecord;
+  private EditText etUrl;
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+  //options menu
+  private DrawerLayout drawerLayout;
+  private NavigationView navigationView;
+  private ActionBarDrawerToggle actionBarDrawerToggle;
+  private RadioGroup rgChannel;
+  private RadioButton rbTcp, rbUdp;
+  private Spinner spResolution;
+  private CheckBox cbEchoCanceler, cbNoiseSuppressor;
+  private EditText etVideoBitrate, etFps, etAudioBitrate, etSampleRate, etWowzaUser,
+      etWowzaPassword;
+  private String lastVideoBitrate;
+  private TextView tvBitrate;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_custom);
+    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+    getSupportActionBar().setHomeButtonEnabled(true);
+
+    surfaceView = findViewById(R.id.surfaceView);
+    surfaceView.getHolder().addCallback(this);
+    surfaceView.setOnTouchListener(this);
+    rtspCamera1 = new RtspCamera1(surfaceView, this);
+    prepareOptionsMenuViews();
+
+    tvBitrate = findViewById(R.id.tv_bitrate);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtsp);
+    bStartStop = findViewById(R.id.b_start_stop);
+    bStartStop.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+  }
+
+  private void prepareOptionsMenuViews() {
+    drawerLayout = findViewById(R.id.activity_custom);
+    navigationView = findViewById(R.id.nv_rtp);
+    navigationView.inflateMenu(R.menu.options_rtsp);
+    actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.rtsp_streamer,
+        R.string.rtsp_streamer) {
+
+      public void onDrawerOpened(View drawerView) {
+        actionBarDrawerToggle.syncState();
+        lastVideoBitrate = etVideoBitrate.getText().toString();
+      }
+
+      public void onDrawerClosed(View view) {
+        actionBarDrawerToggle.syncState();
+        if (lastVideoBitrate != null && !lastVideoBitrate.equals(
+            etVideoBitrate.getText().toString()) && rtspCamera1.isStreaming()) {
+          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            int bitrate = Integer.parseInt(etVideoBitrate.getText().toString()) * 1024;
+            rtspCamera1.setVideoBitrateOnFly(bitrate);
+            Toast.makeText(RtspActivity.this, "New bitrate: " + bitrate, Toast.LENGTH_SHORT).
+                show();
+          } else {
+            Toast.makeText(RtspActivity.this, "Bitrate on fly ignored, Required min API 19",
+                Toast.LENGTH_SHORT).show();
+          }
+        }
+      }
+    };
+    drawerLayout.addDrawerListener(actionBarDrawerToggle);
+    //checkboxs
+    cbEchoCanceler =
+        (CheckBox) navigationView.getMenu().findItem(R.id.cb_echo_canceler).getActionView();
+    cbNoiseSuppressor =
+        (CheckBox) navigationView.getMenu().findItem(R.id.cb_noise_suppressor).getActionView();
+    //radiobuttons
+    rbTcp = (RadioButton) navigationView.getMenu().findItem(R.id.rb_tcp).getActionView();
+    rbUdp = (RadioButton) navigationView.getMenu().findItem(R.id.rb_udp).getActionView();
+    rgChannel = (RadioGroup) navigationView.getMenu().findItem(R.id.channel).getActionView();
+    rbTcp.setChecked(true);
+    rbTcp.setOnClickListener(this);
+    rbUdp.setOnClickListener(this);
+    //spinners
+    spResolution = (Spinner) navigationView.getMenu().findItem(R.id.sp_resolution).getActionView();
+
+    ArrayAdapter<Integer> orientationAdapter =
+        new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
+    orientationAdapter.addAll(orientations);
+
+    ArrayAdapter<String> resolutionAdapter =
+        new ArrayAdapter<>(this, R.layout.support_simple_spinner_dropdown_item);
+    List<String> list = new ArrayList<>();
+    for (Camera.Size size : rtspCamera1.getResolutionsBack()) {
+      list.add(size.width + "X" + size.height);
+    }
+    resolutionAdapter.addAll(list);
+    spResolution.setAdapter(resolutionAdapter);
+    //edittexts
+    etVideoBitrate =
+        (EditText) navigationView.getMenu().findItem(R.id.et_video_bitrate).getActionView();
+    etFps = (EditText) navigationView.getMenu().findItem(R.id.et_fps).getActionView();
+    etAudioBitrate =
+        (EditText) navigationView.getMenu().findItem(R.id.et_audio_bitrate).getActionView();
+    etSampleRate = (EditText) navigationView.getMenu().findItem(R.id.et_samplerate).getActionView();
+    etVideoBitrate.setText("2500");
+    etFps.setText("30");
+    etAudioBitrate.setText("128");
+    etSampleRate.setText("44100");
+    etWowzaUser = (EditText) navigationView.getMenu().findItem(R.id.et_user).getActionView();
+    etWowzaPassword =
+        (EditText) navigationView.getMenu().findItem(R.id.et_password).getActionView();
+  }
+
+  @Override
+  protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+    super.onPostCreate(savedInstanceState);
+    actionBarDrawerToggle.syncState();
+  }
+
+  @Override
+  public boolean onCreateOptionsMenu(Menu menu) {
+    getMenuInflater().inflate(R.menu.menu, menu);
+    return true;
+  }
+
+  @Override
+  public boolean onOptionsItemSelected(MenuItem item) {
+    switch (item.getItemId()) {
+      case android.R.id.home:
+        if (!drawerLayout.isDrawerOpen(GravityCompat.START)) {
+          drawerLayout.openDrawer(GravityCompat.START);
+        } else {
+          drawerLayout.closeDrawer(GravityCompat.START);
+        }
+        return true;
+      case R.id.microphone:
+        if (!rtspCamera1.isAudioMuted()) {
+          item.setIcon(getResources().getDrawable(R.drawable.icon_microphone_off));
+          rtspCamera1.disableAudio();
+        } else {
+          item.setIcon(getResources().getDrawable(R.drawable.icon_microphone));
+          rtspCamera1.enableAudio();
+        }
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  @Override
+  public void onClick(View v) {
+    switch (v.getId()) {
+      case R.id.b_start_stop:
+        if (!rtspCamera1.isStreaming()) {
+          bStartStop.setText(getResources().getString(R.string.stop_button));
+          if (rbTcp.isChecked()) {
+            rtspCamera1.setProtocol(Protocol.TCP);
+          } else {
+            rtspCamera1.setProtocol(Protocol.UDP);
+          }
+          String user = etWowzaUser.getText().toString();
+          String password = etWowzaPassword.getText().toString();
+          if (!user.isEmpty() && !password.isEmpty()) {
+            rtspCamera1.setAuthorization(user, password);
+          }
+          if (rtspCamera1.isRecording() || prepareEncoders()) {
+            rtspCamera1.startStream(etUrl.getText().toString());
+          } else {
+            //If you see this all time when you start stream,
+            //it is because your encoder device dont support the configuration
+            //in video encoder maybe color format.
+            //If you have more encoder go to VideoEncoder or AudioEncoder class,
+            //change encoder and try
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+            bStartStop.setText(getResources().getString(R.string.start_button));
+          }
+        } else {
+          bStartStop.setText(getResources().getString(R.string.start_button));
+          rtspCamera1.stopStream();
+        }
+        break;
+      case R.id.b_record:
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+          if (!rtspCamera1.isRecording()) {
+            try {
+              if (!folder.exists()) {
+                folder.mkdir();
+              }
+              SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+              currentDateAndTime = sdf.format(new Date());
+              if (!rtspCamera1.isStreaming()) {
+                if (prepareEncoders()) {
+                  rtspCamera1.startRecord(
+                      folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                  bRecord.setText(R.string.stop_record);
+                  Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+                } else {
+                  Toast.makeText(this, "Error preparing stream, This device cant do it",
+                      Toast.LENGTH_SHORT).show();
+                }
+              } else {
+                rtspCamera1.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              }
+            } catch (IOException e) {
+              rtspCamera1.stopRecord();
+              bRecord.setText(R.string.start_record);
+              Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+            }
+          } else {
+            rtspCamera1.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this,
+                "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
+              Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtspCamera1.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      //options menu
+      case R.id.rb_tcp:
+        if (rbUdp.isChecked()) {
+          rbUdp.setChecked(false);
+          rbTcp.setChecked(true);
+        }
+        break;
+      case R.id.rb_udp:
+        if (rbTcp.isChecked()) {
+          rbTcp.setChecked(false);
+          rbUdp.setChecked(true);
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  private boolean prepareEncoders() {
+    Camera.Size resolution =
+        rtspCamera1.getResolutionsBack().get(spResolution.getSelectedItemPosition());
+    int width = resolution.width;
+    int height = resolution.height;
+    return rtspCamera1.prepareVideo(width, height, Integer.parseInt(etFps.getText().toString()),
+        Integer.parseInt(etVideoBitrate.getText().toString()) * 1024,
+        CameraHelper.getCameraOrientation(this)) && rtspCamera1.prepareAudio(
+        Integer.parseInt(etAudioBitrate.getText().toString()) * 1024,
+        Integer.parseInt(etSampleRate.getText().toString()),
+        rgChannel.getCheckedRadioButtonId() == R.id.rb_stereo, cbEchoCanceler.isChecked(),
+        cbNoiseSuppressor.isChecked());
+  }
+
+  @Override
+  public void onConnectionSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtsp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
+            .show();
+        rtspCamera1.stopStream();
+        bStartStop.setText(getResources().getString(R.string.start_button));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
+            && rtspCamera1.isRecording()) {
+          rtspCamera1.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(RtspActivity.this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtsp(final long bitrate) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        tvBitrate.setText(bitrate + " bps");
+      }
+    });
+  }
+
+  @Override
+  public void onDisconnectRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
+            && rtspCamera1.isRecording()) {
+          rtspCamera1.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(RtspActivity.this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        bStartStop.setText(getResources().getString(R.string.start_button));
+        rtspCamera1.stopStream();
+        Toast.makeText(RtspActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
+            && rtspCamera1.isRecording()) {
+          rtspCamera1.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(RtspActivity.this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+    drawerLayout.openDrawer(GravityCompat.START);
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtspCamera1.startPreview();
+    // optionally:
+    //rtspCamera1.startPreview(CameraHelper.Facing.BACK);
+    //or
+    //rtspCamera1.startPreview(CameraHelper.Facing.FRONT);
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtspCamera1.isRecording()) {
+      rtspCamera1.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtspCamera1.isStreaming()) {
+      rtspCamera1.stopStream();
+      bStartStop.setText(getResources().getString(R.string.start_button));
+    }
+    rtspCamera1.stopPreview();
+  }
+
+  @Override
+  public boolean onTouch(View view, MotionEvent motionEvent) {
+    int action = motionEvent.getAction();
+    if (motionEvent.getPointerCount() > 1) {
+      if (action == MotionEvent.ACTION_MOVE) {
+        rtspCamera1.setZoom(motionEvent);
+      }
+    } else {
+      if (action == MotionEvent.ACTION_UP) {
+        // todo place to add autofocus functional.
+      }
+    }
+    return true;
+  }
+}

+ 222 - 0
app/src/main/java/com/pedro/rtpstreamer/defaultexample/ExampleRtmpActivity.java

@@ -0,0 +1,222 @@
+package com.pedro.rtpstreamer.defaultexample;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtplibrary.rtmp.RtmpCamera1;
+import com.pedro.rtpstreamer.R;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import net.ossrs.rtmp.ConnectCheckerRtmp;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera1Base}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpCamera1}
+ */
+public class ExampleRtmpActivity extends AppCompatActivity
+    implements ConnectCheckerRtmp, View.OnClickListener, SurfaceHolder.Callback {
+
+  private RtmpCamera1 rtmpCamera1;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_example);
+    SurfaceView surfaceView = findViewById(R.id.surfaceView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtmp);
+    rtmpCamera1 = new RtmpCamera1(surfaceView, this);
+    rtmpCamera1.setReTries(10);
+    surfaceView.getHolder().addCallback(this);
+  }
+
+  @Override
+  public void onConnectionSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtmpActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtmp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        if (rtmpCamera1.reTry(5000, reason)) {
+          Toast.makeText(ExampleRtmpActivity.this, "Retry", Toast.LENGTH_SHORT)
+              .show();
+        } else {
+          Toast.makeText(ExampleRtmpActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
+              .show();
+          rtmpCamera1.stopStream();
+          button.setText(R.string.start_button);
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtmp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtmpActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtmpActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtmpActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtmpCamera1.isStreaming()) {
+          if (rtmpCamera1.isRecording()
+              || rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtmpCamera1.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtmpCamera1.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtmpCamera1.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+          if (!rtmpCamera1.isRecording()) {
+            try {
+              if (!folder.exists()) {
+                folder.mkdir();
+              }
+              SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+              currentDateAndTime = sdf.format(new Date());
+              if (!rtmpCamera1.isStreaming()) {
+                if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) {
+                  rtmpCamera1.startRecord(
+                      folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                  bRecord.setText(R.string.stop_record);
+                  Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+                } else {
+                  Toast.makeText(this, "Error preparing stream, This device cant do it",
+                      Toast.LENGTH_SHORT).show();
+                }
+              } else {
+                rtmpCamera1.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              }
+            } catch (IOException e) {
+              rtmpCamera1.stopRecord();
+              bRecord.setText(R.string.start_record);
+              Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+            }
+          } else {
+            rtmpCamera1.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this,
+                "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+                Toast.LENGTH_SHORT).show();
+            currentDateAndTime = "";
+          }
+        } else {
+          Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
+              Toast.LENGTH_SHORT).show();
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtmpCamera1.startPreview();
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtmpCamera1.isRecording()) {
+      rtmpCamera1.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtmpCamera1.isStreaming()) {
+      rtmpCamera1.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtmpCamera1.stopPreview();
+  }
+}

+ 223 - 0
app/src/main/java/com/pedro/rtpstreamer/defaultexample/ExampleRtspActivity.java

@@ -0,0 +1,223 @@
+package com.pedro.rtpstreamer.defaultexample;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtplibrary.rtsp.RtspCamera1;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtsp.utils.ConnectCheckerRtsp;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera1Base}
+ * {@link com.pedro.rtplibrary.rtsp.RtspCamera1}
+ */
+public class ExampleRtspActivity extends AppCompatActivity
+    implements ConnectCheckerRtsp, View.OnClickListener, SurfaceHolder.Callback {
+
+  private RtspCamera1 rtspCamera1;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_example);
+    SurfaceView surfaceView = findViewById(R.id.surfaceView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtsp);
+    rtspCamera1 = new RtspCamera1(surfaceView, this);
+    rtspCamera1.setReTries(10);
+    surfaceView.getHolder().addCallback(this);
+  }
+
+  @Override
+  public void onConnectionSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtspActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtsp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        if (rtspCamera1.reTry(5000, reason)) {
+          Toast.makeText(ExampleRtspActivity.this, "Retry", Toast.LENGTH_SHORT)
+              .show();
+        } else {
+          Toast.makeText(ExampleRtspActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
+              .show();
+          rtspCamera1.stopStream();
+          button.setText(R.string.start_button);
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtsp(final long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtspActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtspActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+        rtspCamera1.stopStream();
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(ExampleRtspActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtspCamera1.isStreaming()) {
+          if (rtspCamera1.isRecording()
+              || rtspCamera1.prepareAudio() && rtspCamera1.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtspCamera1.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtspCamera1.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtspCamera1.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+          if (!rtspCamera1.isRecording()) {
+            try {
+              if (!folder.exists()) {
+                folder.mkdir();
+              }
+              SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+              currentDateAndTime = sdf.format(new Date());
+              if (!rtspCamera1.isStreaming()) {
+                if (rtspCamera1.prepareAudio() && rtspCamera1.prepareVideo()) {
+                  rtspCamera1.startRecord(
+                      folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                  bRecord.setText(R.string.stop_record);
+                  Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+                } else {
+                  Toast.makeText(this, "Error preparing stream, This device cant do it",
+                      Toast.LENGTH_SHORT).show();
+                }
+              } else {
+                rtspCamera1.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              }
+            } catch (IOException e) {
+              rtspCamera1.stopRecord();
+              bRecord.setText(R.string.start_record);
+              Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+            }
+          } else {
+            rtspCamera1.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this,
+                "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          Toast.makeText(this, "You need min JELLY_BEAN_MR2(API 18) for do it...",
+              Toast.LENGTH_SHORT).show();
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtspCamera1.startPreview();
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && rtspCamera1.isRecording()) {
+      rtspCamera1.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtspCamera1.isStreaming()) {
+      rtspCamera1.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtspCamera1.stopPreview();
+  }
+}

+ 173 - 0
app/src/main/java/com/pedro/rtpstreamer/displayexample/DisplayActivity.java

@@ -0,0 +1,173 @@
+package com.pedro.rtpstreamer.displayexample;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import com.pedro.rtpstreamer.R;
+import net.ossrs.rtmp.ConnectCheckerRtmp;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.DisplayBase}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpDisplay}
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class DisplayActivity extends AppCompatActivity
+    implements ConnectCheckerRtmp, View.OnClickListener {
+
+  private Button button;
+  private EditText etUrl;
+  private final int REQUEST_CODE_STREAM = 179; //random num
+  private final int REQUEST_CODE_RECORD = 180; //random num
+  private NotificationManager notificationManager;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_display);
+    notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtmp);
+    getInstance();
+
+    if (DisplayService.Companion.isStreaming()) {
+      button.setText(R.string.stop_button);
+    } else {
+      button.setText(R.string.start_button);
+    }
+  }
+
+  private void getInstance() {
+    DisplayService.Companion.init(this);
+  }
+
+  /**
+   * This notification is to solve MediaProjection problem that only render surface if something
+   * changed.
+   * It could produce problem in some server like in Youtube that need send video and audio all time
+   * to work.
+   */
+  private void initNotification() {
+    Notification.Builder notificationBuilder =
+        new Notification.Builder(this).setSmallIcon(R.drawable.notification_anim)
+            .setContentTitle("Streaming")
+            .setContentText("Display mode stream")
+            .setTicker("Stream in progress");
+    notificationBuilder.setAutoCancel(true);
+    if (notificationManager != null) notificationManager.notify(12345, notificationBuilder.build());
+  }
+
+  private void stopNotification() {
+    if (notificationManager != null) {
+      notificationManager.cancel(12345);
+    }
+  }
+
+  @Override
+  public void onConnectionSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(DisplayActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtmp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(DisplayActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
+            .show();
+        stopNotification();
+        stopService(new Intent(DisplayActivity.this, DisplayService.class));
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtmp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(DisplayActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(DisplayActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(DisplayActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    super.onActivityResult(requestCode, resultCode, data);
+    if (data != null && (requestCode == REQUEST_CODE_STREAM
+        || requestCode == REQUEST_CODE_RECORD && resultCode == Activity.RESULT_OK)) {
+      initNotification();
+      DisplayService.Companion.setData(resultCode, data);
+      Intent intent = new Intent(this, DisplayService.class);
+      intent.putExtra("endpoint", etUrl.getText().toString());
+      startService(intent);
+    } else {
+      Toast.makeText(this, "No permissions available", Toast.LENGTH_SHORT).show();
+      button.setText(R.string.start_button);
+    }
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!DisplayService.Companion.isStreaming()) {
+          button.setText(R.string.stop_button);
+          startActivityForResult(DisplayService.Companion.sendIntent(), REQUEST_CODE_STREAM);
+        } else {
+          button.setText(R.string.start_button);
+          stopService(new Intent(DisplayActivity.this, DisplayService.class));
+        }
+        if (!DisplayService.Companion.isStreaming() && !DisplayService.Companion.isRecording()) {
+          stopNotification();
+        }
+        break;
+      default:
+        break;
+    }
+  }
+}

+ 173 - 0
app/src/main/java/com/pedro/rtpstreamer/displayexample/DisplayService.kt

@@ -0,0 +1,173 @@
+package com.pedro.rtpstreamer.displayexample
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.app.NotificationCompat
+import com.pedro.rtplibrary.base.DisplayBase
+import com.pedro.rtplibrary.rtmp.RtmpDisplay
+import com.pedro.rtplibrary.rtsp.RtspDisplay
+import com.pedro.rtpstreamer.R
+import com.pedro.rtpstreamer.backgroundexample.ConnectCheckerRtp
+
+
+/**
+ * Basic RTMP/RTSP service streaming implementation with camera2
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+class DisplayService : Service() {
+
+  private var endpoint: String? = null
+
+  override fun onCreate() {
+    super.onCreate()
+    Log.e(TAG, "RTP Display service create")
+    notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+      val channel = NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH)
+      notificationManager?.createNotificationChannel(channel)
+    }
+    keepAliveTrick()
+  }
+
+  private fun keepAliveTrick() {
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
+      val notification = NotificationCompat.Builder(this, channelId)
+          .setOngoing(true)
+          .setContentTitle("")
+          .setContentText("").build()
+      startForeground(1, notification)
+    } else {
+      startForeground(1, Notification())
+    }
+  }
+
+  override fun onBind(p0: Intent?): IBinder? {
+    return null
+  }
+
+  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+    Log.e(TAG, "RTP Display service started")
+    endpoint = intent?.extras?.getString("endpoint")
+    if (endpoint != null) {
+      prepareStreamRtp()
+      startStreamRtp(endpoint!!)
+    }
+    return START_STICKY
+  }
+
+  companion object {
+    private val TAG = "DisplayService"
+    private val channelId = "rtpDisplayStreamChannel"
+    private val notifyId = 123456
+    private var notificationManager: NotificationManager? = null
+    private var displayBase: DisplayBase? = null
+    private var contextApp: Context? = null
+    private var resultCode: Int? = null
+    private var data: Intent? = null
+
+    fun init(context: Context) {
+      contextApp = context
+      if (displayBase == null) displayBase = RtmpDisplay(context, true, connectCheckerRtp)
+    }
+
+    fun setData(resultCode: Int, data: Intent) {
+      this.resultCode = resultCode
+      this.data = data
+    }
+
+    fun sendIntent(): Intent? {
+      if (displayBase != null) {
+        return displayBase!!.sendIntent()
+      } else {
+        return null
+      }
+    }
+
+    fun isStreaming(): Boolean {
+      return if (displayBase != null) displayBase!!.isStreaming else false
+    }
+
+    fun isRecording(): Boolean {
+      return if (displayBase != null) displayBase!!.isRecording else false
+    }
+
+    fun stopStream() {
+      if (displayBase != null) {
+        if (displayBase!!.isStreaming) displayBase!!.stopStream()
+      }
+    }
+
+    private val connectCheckerRtp = object : ConnectCheckerRtp {
+      override fun onConnectionSuccessRtp() {
+        showNotification("Stream started")
+        Log.e(TAG, "RTP service destroy")
+      }
+
+      override fun onNewBitrateRtp(bitrate: Long) {
+
+      }
+
+      override fun onConnectionFailedRtp(reason: String) {
+        showNotification("Stream connection failed")
+        Log.e(TAG, "RTP service destroy")
+      }
+
+      override fun onDisconnectRtp() {
+        showNotification("Stream stopped")
+      }
+
+      override fun onAuthErrorRtp() {
+        showNotification("Stream auth error")
+      }
+
+      override fun onAuthSuccessRtp() {
+        showNotification("Stream auth success")
+      }
+    }
+
+    private fun showNotification(text: String) {
+      contextApp?.let {
+        val notification = NotificationCompat.Builder(it, channelId)
+            .setSmallIcon(R.mipmap.ic_launcher)
+            .setContentTitle("RTP Display Stream")
+            .setContentText(text).build()
+        notificationManager?.notify(notifyId, notification)
+      }
+    }
+  }
+
+  override fun onDestroy() {
+    super.onDestroy()
+    Log.e(TAG, "RTP Display service destroy")
+    stopStream()
+  }
+
+  private fun prepareStreamRtp() {
+    stopStream()
+    if (endpoint!!.startsWith("rtmp")) {
+      displayBase = RtmpDisplay(baseContext, true, connectCheckerRtp)
+      displayBase?.setIntentResult(resultCode!!, data)
+    } else {
+      displayBase = RtspDisplay(baseContext, true, connectCheckerRtp)
+      displayBase?.setIntentResult(resultCode!!, data)
+    }
+  }
+
+  private fun startStreamRtp(endpoint: String) {
+    if (!displayBase!!.isStreaming) {
+      if (displayBase!!.prepareVideo() && displayBase!!.prepareAudio()) {
+        displayBase!!.startStream(endpoint)
+      }
+    } else {
+      showNotification("You are already streaming :(")
+    }
+  }
+}

+ 319 - 0
app/src/main/java/com/pedro/rtpstreamer/filestreamexample/RtmpFromFileActivity.java

@@ -0,0 +1,319 @@
+package com.pedro.rtpstreamer.filestreamexample;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.pedro.encoder.input.decoder.AudioDecoderInterface;
+import com.pedro.encoder.input.decoder.VideoDecoderInterface;
+import com.pedro.rtplibrary.rtmp.RtmpFromFile;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtpstreamer.utils.PathUtils;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import net.ossrs.rtmp.ConnectCheckerRtmp;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.FromFileBase}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpFromFile}
+ */
+@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class RtmpFromFileActivity extends AppCompatActivity
+    implements ConnectCheckerRtmp, View.OnClickListener, VideoDecoderInterface,
+    AudioDecoderInterface, SeekBar.OnSeekBarChangeListener {
+
+  private RtmpFromFile rtmpFromFile;
+  private Button button, bSelectFile, bReSync, bRecord;
+  private SeekBar seekBar;
+  private EditText etUrl;
+  private TextView tvFile;
+  private String filePath = "";
+  private boolean touching = false;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_from_file);
+    button = findViewById(R.id.b_start_stop);
+    bSelectFile = findViewById(R.id.b_select_file);
+    button.setOnClickListener(this);
+    bSelectFile.setOnClickListener(this);
+    bReSync = findViewById(R.id.b_re_sync);
+    bReSync.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtmp);
+    seekBar = findViewById(R.id.seek_bar);
+    seekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
+    tvFile = findViewById(R.id.tv_file);
+    rtmpFromFile = new RtmpFromFile(this, this, this);
+    seekBar.setOnSeekBarChangeListener(this);
+  }
+
+  @Override
+  protected void onPause() {
+    super.onPause();
+    if (rtmpFromFile.isRecording()) {
+      rtmpFromFile.stopRecord();
+      bRecord.setText(R.string.start_record);
+    }
+    if (rtmpFromFile.isStreaming()) {
+      rtmpFromFile.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+  }
+
+  @Override
+  public void onConnectionSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpFromFileActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtmp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpFromFileActivity.this, "Connection failed. " + reason,
+            Toast.LENGTH_SHORT).show();
+        rtmpFromFile.stopStream();
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtmp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpFromFileActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpFromFileActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtmpFromFileActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    super.onActivityResult(requestCode, resultCode, data);
+    if (requestCode == 5 && data != null) {
+      filePath = PathUtils.getPath(this, data.getData());
+      Toast.makeText(this, filePath, Toast.LENGTH_SHORT).show();
+      tvFile.setText(filePath);
+    }
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtmpFromFile.isStreaming()) {
+          try {
+            if (!rtmpFromFile.isRecording()) {
+              if (prepare()) {
+                button.setText(R.string.stop_button);
+                rtmpFromFile.startStream(etUrl.getText().toString());
+                seekBar.setMax(Math.max((int) rtmpFromFile.getVideoDuration(),
+                    (int) rtmpFromFile.getAudioDuration()));
+                updateProgress();
+              } else {
+                button.setText(R.string.start_button);
+                rtmpFromFile.stopStream();
+                /*This error could be 2 things.
+                 Your device cant decode or encode this file or
+                 the file is not supported for the library.
+                The file need has h264 video codec and acc audio codec*/
+                Toast.makeText(this, "Error: unsupported file", Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              button.setText(R.string.stop_button);
+              rtmpFromFile.startStream(etUrl.getText().toString());
+            }
+          } catch (IOException e) {
+            //Normally this error is for file not found or read permissions
+            Toast.makeText(this, "Error: file not found", Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtmpFromFile.stopStream();
+        }
+        break;
+      case R.id.b_select_file:
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.setType("*/*");
+        startActivityForResult(intent, 5);
+        break;
+      //sometimes async is produced when you move in file several times
+      case R.id.b_re_sync:
+        rtmpFromFile.reSyncFile();
+        break;
+      case R.id.b_record:
+        if (!rtmpFromFile.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtmpFromFile.isStreaming()) {
+              if (prepare()) {
+                rtmpFromFile.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                seekBar.setMax(Math.max((int) rtmpFromFile.getVideoDuration(),
+                    (int) rtmpFromFile.getAudioDuration()));
+                updateProgress();
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtmpFromFile.startRecord(
+                  folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtmpFromFile.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtmpFromFile.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  private boolean prepare() throws IOException {
+    boolean result = rtmpFromFile.prepareVideo(filePath);
+    result |= rtmpFromFile.prepareAudio(filePath);
+    return result;
+  }
+
+  private void updateProgress() {
+    new Thread(new Runnable() {
+      @Override
+      public void run() {
+        while (rtmpFromFile.isStreaming() || rtmpFromFile.isRecording()) {
+          try {
+            Thread.sleep(1000);
+            if (!touching) {
+              runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                  seekBar.setProgress(Math.max((int) rtmpFromFile.getVideoTime(),
+                      (int) rtmpFromFile.getAudioTime()));
+                }
+              });
+            }
+          } catch (InterruptedException e) {
+            e.printStackTrace();
+          }
+        }
+      }
+    }).start();
+  }
+
+  @Override
+  public void onVideoDecoderFinished() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        if (rtmpFromFile.isRecording()) {
+          rtmpFromFile.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(RtmpFromFileActivity.this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        if (rtmpFromFile.isStreaming()) {
+          button.setText(R.string.start_button);
+          Toast.makeText(RtmpFromFileActivity.this, "Video stream finished", Toast.LENGTH_SHORT)
+              .show();
+          rtmpFromFile.stopStream();
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onAudioDecoderFinished() {
+
+  }
+
+  @Override
+  public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+
+  }
+
+  @Override
+  public void onStartTrackingTouch(SeekBar seekBar) {
+    touching = true;
+  }
+
+  @Override
+  public void onStopTrackingTouch(SeekBar seekBar) {
+    if (rtmpFromFile.isStreaming()) rtmpFromFile.moveTo(seekBar.getProgress());
+    touching = false;
+  }
+}
+

+ 318 - 0
app/src/main/java/com/pedro/rtpstreamer/filestreamexample/RtspFromFileActivity.java

@@ -0,0 +1,318 @@
+package com.pedro.rtpstreamer.filestreamexample;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.pedro.encoder.input.decoder.AudioDecoderInterface;
+import com.pedro.encoder.input.decoder.VideoDecoderInterface;
+import com.pedro.rtplibrary.rtsp.RtspFromFile;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtpstreamer.utils.PathUtils;
+import com.pedro.rtsp.utils.ConnectCheckerRtsp;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.FromFileBase}
+ * {@link com.pedro.rtplibrary.rtsp.RtspFromFile}
+ */
+@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class RtspFromFileActivity extends AppCompatActivity
+    implements ConnectCheckerRtsp, View.OnClickListener, VideoDecoderInterface,
+    AudioDecoderInterface, SeekBar.OnSeekBarChangeListener {
+
+  private RtspFromFile rtspFromFile;
+  private Button button, bSelectFile, bReSync, bRecord;
+  private SeekBar seekBar;
+  private EditText etUrl;
+  private TextView tvFile;
+  private String filePath = "";
+  private boolean touching = false;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_from_file);
+    button = findViewById(R.id.b_start_stop);
+    bSelectFile = findViewById(R.id.b_select_file);
+    button.setOnClickListener(this);
+    bSelectFile.setOnClickListener(this);
+    bReSync = findViewById(R.id.b_re_sync);
+    bReSync.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtsp);
+    seekBar = findViewById(R.id.seek_bar);
+    seekBar.getProgressDrawable().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
+    seekBar.setOnSeekBarChangeListener(this);
+    tvFile = findViewById(R.id.tv_file);
+    rtspFromFile = new RtspFromFile(this, this, this);
+  }
+
+  @Override
+  protected void onPause() {
+    super.onPause();
+    if (rtspFromFile.isRecording()) {
+      rtspFromFile.stopRecord();
+      bRecord.setText(R.string.start_record);
+    }
+    if (rtspFromFile.isStreaming()) {
+      rtspFromFile.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+  }
+
+  @Override
+  public void onConnectionSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspFromFileActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtsp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspFromFileActivity.this, "Connection failed. " + reason,
+            Toast.LENGTH_SHORT).show();
+        rtspFromFile.stopStream();
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtsp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspFromFileActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspFromFileActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(RtspFromFileActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    super.onActivityResult(requestCode, resultCode, data);
+    if (requestCode == 5 && data != null) {
+      filePath = PathUtils.getPath(this, data.getData());
+      Toast.makeText(this, filePath, Toast.LENGTH_SHORT).show();
+      tvFile.setText(filePath);
+    }
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtspFromFile.isStreaming()) {
+          try {
+            if (!rtspFromFile.isRecording()) {
+              if (prepare()) {
+                button.setText(R.string.stop_button);
+                rtspFromFile.startStream(etUrl.getText().toString());
+                seekBar.setMax(Math.max((int) rtspFromFile.getVideoDuration(),
+                    (int) rtspFromFile.getAudioDuration()));
+                updateProgress();
+              } else {
+                button.setText(R.string.start_button);
+                rtspFromFile.stopStream();
+                /*This error could be 2 things.
+                 Your device cant decode or encode this file or
+                 the file is not supported for the library.
+                The file need has h264 video codec and acc audio codec*/
+                Toast.makeText(this, "Error: unsupported file", Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              button.setText(R.string.stop_button);
+              rtspFromFile.startStream(etUrl.getText().toString());
+            }
+          } catch (IOException e) {
+            //Normally this error is for file not found or read permissions
+            Toast.makeText(this, "Error: file not found", Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtspFromFile.stopStream();
+        }
+        break;
+      case R.id.b_select_file:
+        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.setType("*/*");
+        startActivityForResult(intent, 5);
+        break;
+      //sometimes async is produced when you move in file several times
+      case R.id.b_re_sync:
+        rtspFromFile.reSyncFile();
+        break;
+      case R.id.b_record:
+        if (!rtspFromFile.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtspFromFile.isStreaming()) {
+              if (prepare()) {
+                rtspFromFile.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                seekBar.setMax(Math.max((int) rtspFromFile.getVideoDuration(),
+                    (int) rtspFromFile.getAudioDuration()));
+                updateProgress();
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtspFromFile.startRecord(
+                  folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtspFromFile.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtspFromFile.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  private boolean prepare() throws IOException {
+    boolean result = rtspFromFile.prepareVideo(filePath);
+    result |= rtspFromFile.prepareAudio(filePath);
+    return result;
+  }
+
+  private void updateProgress() {
+    new Thread(new Runnable() {
+      @Override
+      public void run() {
+        while (rtspFromFile.isStreaming() || rtspFromFile.isRecording()) {
+          try {
+            Thread.sleep(1000);
+            if (!touching) {
+              runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                  seekBar.setProgress(Math.max((int) rtspFromFile.getVideoTime(),
+                      (int) rtspFromFile.getAudioTime()));
+                }
+              });
+            }
+          } catch (InterruptedException e) {
+            e.printStackTrace();
+          }
+        }
+      }
+    }).start();
+  }
+
+  @Override
+  public void onVideoDecoderFinished() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        if (rtspFromFile.isRecording()) {
+          rtspFromFile.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(RtspFromFileActivity.this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        if (rtspFromFile.isStreaming()) {
+          button.setText(R.string.start_button);
+          Toast.makeText(RtspFromFileActivity.this, "Video stream finished", Toast.LENGTH_SHORT)
+              .show();
+          rtspFromFile.stopStream();
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onAudioDecoderFinished() {
+
+  }
+
+  @Override
+  public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+
+  }
+
+  @Override
+  public void onStartTrackingTouch(SeekBar seekBar) {
+    touching = true;
+  }
+
+  @Override
+  public void onStopTrackingTouch(SeekBar seekBar) {
+    if (rtspFromFile.isStreaming()) rtspFromFile.moveTo(seekBar.getProgress());
+    touching = false;
+  }
+}

+ 488 - 0
app/src/main/java/com/pedro/rtpstreamer/openglexample/OpenGlRtmpActivity.java

@@ -0,0 +1,488 @@
+package com.pedro.rtpstreamer.openglexample;
+
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import com.pedro.encoder.input.gl.SpriteGestureController;
+import com.pedro.encoder.input.gl.render.filters.AnalogTVFilterRender;
+import com.pedro.encoder.input.gl.render.filters.AndroidViewFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BasicDeformationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BeautyFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BlackFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BlurFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BrightnessFilterRender;
+import com.pedro.encoder.input.gl.render.filters.CartoonFilterRender;
+import com.pedro.encoder.input.gl.render.filters.CircleFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ColorFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ContrastFilterRender;
+import com.pedro.encoder.input.gl.render.filters.DuotoneFilterRender;
+import com.pedro.encoder.input.gl.render.filters.EarlyBirdFilterRender;
+import com.pedro.encoder.input.gl.render.filters.EdgeDetectionFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ExposureFilterRender;
+import com.pedro.encoder.input.gl.render.filters.FireFilterRender;
+import com.pedro.encoder.input.gl.render.filters.GammaFilterRender;
+import com.pedro.encoder.input.gl.render.filters.GlitchFilterRender;
+import com.pedro.encoder.input.gl.render.filters.GreyScaleFilterRender;
+import com.pedro.encoder.input.gl.render.filters.HalftoneLinesFilterRender;
+import com.pedro.encoder.input.gl.render.filters.Image70sFilterRender;
+import com.pedro.encoder.input.gl.render.filters.LamoishFilterRender;
+import com.pedro.encoder.input.gl.render.filters.MoneyFilterRender;
+import com.pedro.encoder.input.gl.render.filters.NegativeFilterRender;
+import com.pedro.encoder.input.gl.render.filters.NoFilterRender;
+import com.pedro.encoder.input.gl.render.filters.PixelatedFilterRender;
+import com.pedro.encoder.input.gl.render.filters.PolygonizationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RGBSaturationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RainbowFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RippleFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RotationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SaturationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SepiaFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SharpnessFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SnowFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SwirlFilterRender;
+import com.pedro.encoder.input.gl.render.filters.TemperatureFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ZebraFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.GifObjectFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.ImageObjectFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.SurfaceFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.TextObjectFilterRender;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.encoder.utils.gl.TranslateTo;
+import com.pedro.rtplibrary.rtmp.RtmpCamera1;
+import com.pedro.rtplibrary.view.OpenGlView;
+import com.pedro.rtpstreamer.R;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import net.ossrs.rtmp.ConnectCheckerRtmp;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera1Base}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpCamera1}
+ */
+@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class OpenGlRtmpActivity extends AppCompatActivity
+    implements ConnectCheckerRtmp, View.OnClickListener, SurfaceHolder.Callback,
+    View.OnTouchListener {
+
+  private RtmpCamera1 rtmpCamera1;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+  private OpenGlView openGlView;
+  private SpriteGestureController spriteGestureController = new SpriteGestureController();
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_open_gl);
+    openGlView = findViewById(R.id.surfaceView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtmp);
+    rtmpCamera1 = new RtmpCamera1(openGlView, this);
+    openGlView.getHolder().addCallback(this);
+    openGlView.setOnTouchListener(this);
+  }
+
+  @Override
+  public boolean onCreateOptionsMenu(Menu menu) {
+    getMenuInflater().inflate(R.menu.gl_menu, menu);
+    return true;
+  }
+
+  @Override
+  public boolean onOptionsItemSelected(MenuItem item) {
+    //Stop listener for image, text and gif stream objects.
+    spriteGestureController.stopListener();
+    switch (item.getItemId()) {
+      case R.id.e_d_fxaa:
+        rtmpCamera1.getGlInterface().enableAA(!rtmpCamera1.getGlInterface().isAAEnabled());
+        Toast.makeText(this,
+            "FXAA " + (rtmpCamera1.getGlInterface().isAAEnabled() ? "enabled" : "disabled"),
+            Toast.LENGTH_SHORT).show();
+        return true;
+      //filters. NOTE: You can change filter values on fly without reset the filter.
+      // Example:
+      // ColorFilterRender color = new ColorFilterRender()
+      // rtmpCamera1.setFilter(color);
+      // color.setRGBColor(255, 0, 0); //red tint
+      case R.id.no_filter:
+        rtmpCamera1.getGlInterface().setFilter(new NoFilterRender());
+        return true;
+      case R.id.analog_tv:
+        rtmpCamera1.getGlInterface().setFilter(new AnalogTVFilterRender());
+        return true;
+      case R.id.android_view:
+        AndroidViewFilterRender androidViewFilterRender = new AndroidViewFilterRender();
+        androidViewFilterRender.setView(findViewById(R.id.switch_camera));
+        rtmpCamera1.getGlInterface().setFilter(androidViewFilterRender);
+        return true;
+      case R.id.basic_deformation:
+        rtmpCamera1.getGlInterface().setFilter(new BasicDeformationFilterRender());
+        return true;
+      case R.id.beauty:
+        rtmpCamera1.getGlInterface().setFilter(new BeautyFilterRender());
+        return true;
+      case R.id.black:
+        rtmpCamera1.getGlInterface().setFilter(new BlackFilterRender());
+        return true;
+      case R.id.blur:
+        rtmpCamera1.getGlInterface().setFilter(new BlurFilterRender());
+        return true;
+      case R.id.brightness:
+        rtmpCamera1.getGlInterface().setFilter(new BrightnessFilterRender());
+        return true;
+      case R.id.cartoon:
+        rtmpCamera1.getGlInterface().setFilter(new CartoonFilterRender());
+        return true;
+      case R.id.circle:
+        rtmpCamera1.getGlInterface().setFilter(new CircleFilterRender());
+        return true;
+      case R.id.color:
+        rtmpCamera1.getGlInterface().setFilter(new ColorFilterRender());
+        return true;
+      case R.id.contrast:
+        rtmpCamera1.getGlInterface().setFilter(new ContrastFilterRender());
+        return true;
+      case R.id.duotone:
+        rtmpCamera1.getGlInterface().setFilter(new DuotoneFilterRender());
+        return true;
+      case R.id.early_bird:
+        rtmpCamera1.getGlInterface().setFilter(new EarlyBirdFilterRender());
+        return true;
+      case R.id.edge_detection:
+        rtmpCamera1.getGlInterface().setFilter(new EdgeDetectionFilterRender());
+        return true;
+      case R.id.exposure:
+        rtmpCamera1.getGlInterface().setFilter(new ExposureFilterRender());
+        return true;
+      case R.id.fire:
+        rtmpCamera1.getGlInterface().setFilter(new FireFilterRender());
+        return true;
+      case R.id.gamma:
+        rtmpCamera1.getGlInterface().setFilter(new GammaFilterRender());
+        return true;
+      case R.id.glitch:
+        rtmpCamera1.getGlInterface().setFilter(new GlitchFilterRender());
+        return true;
+      case R.id.gif:
+        setGifToStream();
+        return true;
+      case R.id.grey_scale:
+        rtmpCamera1.getGlInterface().setFilter(new GreyScaleFilterRender());
+        return true;
+      case R.id.halftone_lines:
+        rtmpCamera1.getGlInterface().setFilter(new HalftoneLinesFilterRender());
+        return true;
+      case R.id.image:
+        setImageToStream();
+        return true;
+      case R.id.image_70s:
+        rtmpCamera1.getGlInterface().setFilter(new Image70sFilterRender());
+        return true;
+      case R.id.lamoish:
+        rtmpCamera1.getGlInterface().setFilter(new LamoishFilterRender());
+        return true;
+      case R.id.money:
+        rtmpCamera1.getGlInterface().setFilter(new MoneyFilterRender());
+        return true;
+      case R.id.negative:
+        rtmpCamera1.getGlInterface().setFilter(new NegativeFilterRender());
+        return true;
+      case R.id.pixelated:
+        rtmpCamera1.getGlInterface().setFilter(new PixelatedFilterRender());
+        return true;
+      case R.id.polygonization:
+        rtmpCamera1.getGlInterface().setFilter(new PolygonizationFilterRender());
+        return true;
+      case R.id.rainbow:
+        rtmpCamera1.getGlInterface().setFilter(new RainbowFilterRender());
+        return true;
+      case R.id.rgb_saturate:
+        RGBSaturationFilterRender rgbSaturationFilterRender = new RGBSaturationFilterRender();
+        rtmpCamera1.getGlInterface().setFilter(rgbSaturationFilterRender);
+        //Reduce green and blue colors 20%. Red will predominate.
+        rgbSaturationFilterRender.setRGBSaturation(1f, 0.8f, 0.8f);
+        return true;
+      case R.id.ripple:
+        rtmpCamera1.getGlInterface().setFilter(new RippleFilterRender());
+        return true;
+      case R.id.rotation:
+        RotationFilterRender rotationFilterRender = new RotationFilterRender();
+        rtmpCamera1.getGlInterface().setFilter(rotationFilterRender);
+        rotationFilterRender.setRotation(90);
+        return true;
+      case R.id.saturation:
+        rtmpCamera1.getGlInterface().setFilter(new SaturationFilterRender());
+        return true;
+      case R.id.sepia:
+        rtmpCamera1.getGlInterface().setFilter(new SepiaFilterRender());
+        return true;
+      case R.id.sharpness:
+        rtmpCamera1.getGlInterface().setFilter(new SharpnessFilterRender());
+        return true;
+      case R.id.snow:
+        rtmpCamera1.getGlInterface().setFilter(new SnowFilterRender());
+        return true;
+      case R.id.swirl:
+        rtmpCamera1.getGlInterface().setFilter(new SwirlFilterRender());
+        return true;
+      case R.id.surface_filter:
+        SurfaceFilterRender surfaceFilterRender =
+            new SurfaceFilterRender(new SurfaceFilterRender.SurfaceReadyCallback() {
+              @Override
+              public void surfaceReady(SurfaceTexture surfaceTexture) {
+                //You can render this filter with other api that draw in a surface. for example you can use VLC
+                MediaPlayer mediaPlayer =
+                    MediaPlayer.create(OpenGlRtmpActivity.this, R.raw.big_bunny_240p);
+                mediaPlayer.setSurface(new Surface(surfaceTexture));
+                mediaPlayer.start();
+              }
+            });
+        rtmpCamera1.getGlInterface().setFilter(surfaceFilterRender);
+        //Video is 360x240 so select a percent to keep aspect ratio (50% x 33.3% screen)
+        surfaceFilterRender.setScale(50f, 33.3f);
+        spriteGestureController.setBaseObjectFilterRender(surfaceFilterRender); //Optional
+        return true;
+      case R.id.temperature:
+        rtmpCamera1.getGlInterface().setFilter(new TemperatureFilterRender());
+        return true;
+      case R.id.text:
+        setTextToStream();
+        return true;
+      case R.id.zebra:
+        rtmpCamera1.getGlInterface().setFilter(new ZebraFilterRender());
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  private void setTextToStream() {
+    TextObjectFilterRender textObjectFilterRender = new TextObjectFilterRender();
+    rtmpCamera1.getGlInterface().setFilter(textObjectFilterRender);
+    textObjectFilterRender.setText("Hello world", 22, Color.RED);
+    textObjectFilterRender.setDefaultScale(rtmpCamera1.getStreamWidth(),
+        rtmpCamera1.getStreamHeight());
+    textObjectFilterRender.setPosition(TranslateTo.CENTER);
+    spriteGestureController.setBaseObjectFilterRender(textObjectFilterRender); //Optional
+  }
+
+  private void setImageToStream() {
+    ImageObjectFilterRender imageObjectFilterRender = new ImageObjectFilterRender();
+    rtmpCamera1.getGlInterface().setFilter(imageObjectFilterRender);
+    imageObjectFilterRender.setImage(
+        BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
+    imageObjectFilterRender.setDefaultScale(rtmpCamera1.getStreamWidth(),
+        rtmpCamera1.getStreamHeight());
+    imageObjectFilterRender.setPosition(TranslateTo.RIGHT);
+    spriteGestureController.setBaseObjectFilterRender(imageObjectFilterRender); //Optional
+    spriteGestureController.setPreventMoveOutside(false); //Optional
+  }
+
+  private void setGifToStream() {
+    try {
+      GifObjectFilterRender gifObjectFilterRender = new GifObjectFilterRender();
+      gifObjectFilterRender.setGif(getResources().openRawResource(R.raw.banana));
+      rtmpCamera1.getGlInterface().setFilter(gifObjectFilterRender);
+      gifObjectFilterRender.setDefaultScale(rtmpCamera1.getStreamWidth(),
+          rtmpCamera1.getStreamHeight());
+      gifObjectFilterRender.setPosition(TranslateTo.BOTTOM);
+      spriteGestureController.setBaseObjectFilterRender(gifObjectFilterRender); //Optional
+    } catch (IOException e) {
+      Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+    }
+  }
+
+  @Override
+  public void onConnectionSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtmpActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtmp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtmpActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
+            .show();
+        rtmpCamera1.stopStream();
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtmp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtmpActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtmpActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtmpActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtmpCamera1.isStreaming()) {
+          if (rtmpCamera1.isRecording()
+              || rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtmpCamera1.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtmpCamera1.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtmpCamera1.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (!rtmpCamera1.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtmpCamera1.isStreaming()) {
+              if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) {
+                rtmpCamera1.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtmpCamera1.startRecord(folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtmpCamera1.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtmpCamera1.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtmpCamera1.startPreview();
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (rtmpCamera1.isRecording()) {
+      rtmpCamera1.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtmpCamera1.isStreaming()) {
+      rtmpCamera1.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtmpCamera1.stopPreview();
+  }
+
+  @Override
+  public boolean onTouch(View view, MotionEvent motionEvent) {
+    if (spriteGestureController.spriteTouched(view, motionEvent)) {
+      spriteGestureController.moveSprite(view, motionEvent);
+      spriteGestureController.scaleSprite(motionEvent);
+      return true;
+    }
+    return false;
+  }
+}

+ 494 - 0
app/src/main/java/com/pedro/rtpstreamer/openglexample/OpenGlRtspActivity.java

@@ -0,0 +1,494 @@
+package com.pedro.rtpstreamer.openglexample;
+
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.pedro.encoder.input.gl.SpriteGestureController;
+import com.pedro.encoder.input.gl.render.filters.AnalogTVFilterRender;
+import com.pedro.encoder.input.gl.render.filters.AndroidViewFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BasicDeformationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BeautyFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BlackFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BlurFilterRender;
+import com.pedro.encoder.input.gl.render.filters.BrightnessFilterRender;
+import com.pedro.encoder.input.gl.render.filters.CartoonFilterRender;
+import com.pedro.encoder.input.gl.render.filters.CircleFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ColorFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ContrastFilterRender;
+import com.pedro.encoder.input.gl.render.filters.DuotoneFilterRender;
+import com.pedro.encoder.input.gl.render.filters.EarlyBirdFilterRender;
+import com.pedro.encoder.input.gl.render.filters.EdgeDetectionFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ExposureFilterRender;
+import com.pedro.encoder.input.gl.render.filters.FireFilterRender;
+import com.pedro.encoder.input.gl.render.filters.GammaFilterRender;
+import com.pedro.encoder.input.gl.render.filters.GlitchFilterRender;
+import com.pedro.encoder.input.gl.render.filters.GreyScaleFilterRender;
+import com.pedro.encoder.input.gl.render.filters.HalftoneLinesFilterRender;
+import com.pedro.encoder.input.gl.render.filters.Image70sFilterRender;
+import com.pedro.encoder.input.gl.render.filters.LamoishFilterRender;
+import com.pedro.encoder.input.gl.render.filters.MoneyFilterRender;
+import com.pedro.encoder.input.gl.render.filters.NegativeFilterRender;
+import com.pedro.encoder.input.gl.render.filters.NoFilterRender;
+import com.pedro.encoder.input.gl.render.filters.PixelatedFilterRender;
+import com.pedro.encoder.input.gl.render.filters.PolygonizationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RGBSaturationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RainbowFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RippleFilterRender;
+import com.pedro.encoder.input.gl.render.filters.RotationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SaturationFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SepiaFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SharpnessFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SnowFilterRender;
+import com.pedro.encoder.input.gl.render.filters.SwirlFilterRender;
+import com.pedro.encoder.input.gl.render.filters.TemperatureFilterRender;
+import com.pedro.encoder.input.gl.render.filters.ZebraFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.GifObjectFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.ImageObjectFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.SurfaceFilterRender;
+import com.pedro.encoder.input.gl.render.filters.object.TextObjectFilterRender;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.encoder.utils.gl.TranslateTo;
+import com.pedro.rtplibrary.rtsp.RtspCamera1;
+import com.pedro.rtplibrary.view.OpenGlView;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtsp.utils.ConnectCheckerRtsp;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera1Base}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpCamera1}
+ */
+@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class OpenGlRtspActivity extends AppCompatActivity
+    implements ConnectCheckerRtsp, View.OnClickListener, SurfaceHolder.Callback,
+    View.OnTouchListener {
+
+  private RtspCamera1 rtspCamera1;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+  private OpenGlView openGlView;
+  private SpriteGestureController spriteGestureController = new SpriteGestureController();
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_open_gl);
+    openGlView = findViewById(R.id.surfaceView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtsp);
+    rtspCamera1 = new RtspCamera1(openGlView, this);
+    openGlView.getHolder().addCallback(this);
+    openGlView.setOnTouchListener(this);
+  }
+
+  @Override
+  public boolean onCreateOptionsMenu(Menu menu) {
+    getMenuInflater().inflate(R.menu.gl_menu, menu);
+    return true;
+  }
+
+  @Override
+  public boolean onOptionsItemSelected(MenuItem item) {
+    //Stop listener for image, text and gif stream objects.
+    spriteGestureController.stopListener();
+    switch (item.getItemId()) {
+      case R.id.e_d_fxaa:
+        rtspCamera1.getGlInterface().enableAA(!rtspCamera1.getGlInterface().isAAEnabled());
+        Toast.makeText(this,
+            "FXAA " + (rtspCamera1.getGlInterface().isAAEnabled() ? "enabled" : "disabled"),
+            Toast.LENGTH_SHORT).show();
+        return true;
+      //filters. NOTE: You can change filter values on fly without reset the filter.
+      // Example:
+      // ColorFilterRender color = new ColorFilterRender()
+      // rtmpCamera1.setFilter(color);
+      // color.setRGBColor(255, 0, 0); //red tint
+      case R.id.no_filter:
+        rtspCamera1.getGlInterface().setFilter(new NoFilterRender());
+        return true;
+      case R.id.analog_tv:
+        rtspCamera1.getGlInterface().setFilter(new AnalogTVFilterRender());
+        return true;
+      case R.id.android_view:
+        AndroidViewFilterRender androidViewFilterRender = new AndroidViewFilterRender();
+        androidViewFilterRender.setView(findViewById(R.id.switch_camera));
+        rtspCamera1.getGlInterface().setFilter(androidViewFilterRender);
+        return true;
+      case R.id.basic_deformation:
+        rtspCamera1.getGlInterface().setFilter(new BasicDeformationFilterRender());
+        return true;
+      case R.id.beauty:
+        rtspCamera1.getGlInterface().setFilter(new BeautyFilterRender());
+        return true;
+      case R.id.black:
+        rtspCamera1.getGlInterface().setFilter(new BlackFilterRender());
+        return true;
+      case R.id.blur:
+        rtspCamera1.getGlInterface().setFilter(new BlurFilterRender());
+        return true;
+      case R.id.brightness:
+        rtspCamera1.getGlInterface().setFilter(new BrightnessFilterRender());
+        return true;
+      case R.id.cartoon:
+        rtspCamera1.getGlInterface().setFilter(new CartoonFilterRender());
+        return true;
+      case R.id.circle:
+        rtspCamera1.getGlInterface().setFilter(new CircleFilterRender());
+        return true;
+      case R.id.color:
+        rtspCamera1.getGlInterface().setFilter(new ColorFilterRender());
+        return true;
+      case R.id.contrast:
+        rtspCamera1.getGlInterface().setFilter(new ContrastFilterRender());
+        return true;
+      case R.id.duotone:
+        rtspCamera1.getGlInterface().setFilter(new DuotoneFilterRender());
+        return true;
+      case R.id.early_bird:
+        rtspCamera1.getGlInterface().setFilter(new EarlyBirdFilterRender());
+        return true;
+      case R.id.edge_detection:
+        rtspCamera1.getGlInterface().setFilter(new EdgeDetectionFilterRender());
+        return true;
+      case R.id.exposure:
+        rtspCamera1.getGlInterface().setFilter(new ExposureFilterRender());
+        return true;
+      case R.id.fire:
+        rtspCamera1.getGlInterface().setFilter(new FireFilterRender());
+        return true;
+      case R.id.gamma:
+        rtspCamera1.getGlInterface().setFilter(new GammaFilterRender());
+        return true;
+      case R.id.glitch:
+        rtspCamera1.getGlInterface().setFilter(new GlitchFilterRender());
+        return true;
+      case R.id.gif:
+        setGifToStream();
+        return true;
+      case R.id.grey_scale:
+        rtspCamera1.getGlInterface().setFilter(new GreyScaleFilterRender());
+        return true;
+      case R.id.halftone_lines:
+        rtspCamera1.getGlInterface().setFilter(new HalftoneLinesFilterRender());
+        return true;
+      case R.id.image:
+        setImageToStream();
+        return true;
+      case R.id.image_70s:
+        rtspCamera1.getGlInterface().setFilter(new Image70sFilterRender());
+        return true;
+      case R.id.lamoish:
+        rtspCamera1.getGlInterface().setFilter(new LamoishFilterRender());
+        return true;
+      case R.id.money:
+        rtspCamera1.getGlInterface().setFilter(new MoneyFilterRender());
+        return true;
+      case R.id.negative:
+        rtspCamera1.getGlInterface().setFilter(new NegativeFilterRender());
+        return true;
+      case R.id.pixelated:
+        rtspCamera1.getGlInterface().setFilter(new PixelatedFilterRender());
+        return true;
+      case R.id.polygonization:
+        rtspCamera1.getGlInterface().setFilter(new PolygonizationFilterRender());
+        return true;
+      case R.id.rainbow:
+        rtspCamera1.getGlInterface().setFilter(new RainbowFilterRender());
+        return true;
+      case R.id.rgb_saturate:
+        RGBSaturationFilterRender rgbSaturationFilterRender = new RGBSaturationFilterRender();
+        rtspCamera1.getGlInterface().setFilter(rgbSaturationFilterRender);
+        //Reduce green and blue colors 20%. Red will predominate.
+        rgbSaturationFilterRender.setRGBSaturation(1f, 0.8f, 0.8f);
+        return true;
+      case R.id.ripple:
+        rtspCamera1.getGlInterface().setFilter(new RippleFilterRender());
+        return true;
+      case R.id.rotation:
+        RotationFilterRender rotationFilterRender = new RotationFilterRender();
+        rtspCamera1.getGlInterface().setFilter(rotationFilterRender);
+        rotationFilterRender.setRotation(90);
+        return true;
+      case R.id.saturation:
+        rtspCamera1.getGlInterface().setFilter(new SaturationFilterRender());
+        return true;
+      case R.id.sepia:
+        rtspCamera1.getGlInterface().setFilter(new SepiaFilterRender());
+        return true;
+      case R.id.sharpness:
+        rtspCamera1.getGlInterface().setFilter(new SharpnessFilterRender());
+        return true;
+      case R.id.snow:
+        rtspCamera1.getGlInterface().setFilter(new SnowFilterRender());
+        return true;
+      case R.id.swirl:
+        rtspCamera1.getGlInterface().setFilter(new SwirlFilterRender());
+        return true;
+      case R.id.surface_filter:
+        SurfaceFilterRender surfaceFilterRender =
+            new SurfaceFilterRender(new SurfaceFilterRender.SurfaceReadyCallback() {
+              @Override
+              public void surfaceReady(SurfaceTexture surfaceTexture) {
+                //You can render this filter with other api that draw in a surface. for example you can use VLC
+                MediaPlayer mediaPlayer =
+                    MediaPlayer.create(OpenGlRtspActivity.this, R.raw.big_bunny_240p);
+                mediaPlayer.setSurface(new Surface(surfaceTexture));
+                mediaPlayer.start();
+              }
+            });
+        rtspCamera1.getGlInterface().setFilter(surfaceFilterRender);
+        MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.big_bunny_240p);
+        mediaPlayer.setSurface(surfaceFilterRender.getSurface());
+        mediaPlayer.start();
+        //Video is 360x240 so select a percent to keep aspect ratio (50% x 33.3% screen)
+        surfaceFilterRender.setScale(50f, 33.3f);
+        spriteGestureController.setBaseObjectFilterRender(surfaceFilterRender); //Optional
+        return true;
+      case R.id.temperature:
+        rtspCamera1.getGlInterface().setFilter(new TemperatureFilterRender());
+        return true;
+      case R.id.text:
+        setTextToStream();
+        return true;
+      case R.id.zebra:
+        rtspCamera1.getGlInterface().setFilter(new ZebraFilterRender());
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  private void setTextToStream() {
+    TextObjectFilterRender textObjectFilterRender = new TextObjectFilterRender();
+    rtspCamera1.getGlInterface().setFilter(textObjectFilterRender);
+    textObjectFilterRender.setText("Hello world", 22, Color.RED);
+    textObjectFilterRender.setDefaultScale(rtspCamera1.getStreamWidth(),
+        rtspCamera1.getStreamHeight());
+    textObjectFilterRender.setPosition(TranslateTo.CENTER);
+    spriteGestureController.setBaseObjectFilterRender(textObjectFilterRender); //Optional
+  }
+
+  private void setImageToStream() {
+    ImageObjectFilterRender imageObjectFilterRender = new ImageObjectFilterRender();
+    rtspCamera1.getGlInterface().setFilter(imageObjectFilterRender);
+    imageObjectFilterRender.setImage(
+        BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
+    imageObjectFilterRender.setDefaultScale(rtspCamera1.getStreamWidth(),
+        rtspCamera1.getStreamHeight());
+    imageObjectFilterRender.setPosition(TranslateTo.RIGHT);
+    spriteGestureController.setBaseObjectFilterRender(imageObjectFilterRender); //Optional
+    spriteGestureController.setPreventMoveOutside(false); //Optional
+  }
+
+  private void setGifToStream() {
+    try {
+      GifObjectFilterRender gifObjectFilterRender = new GifObjectFilterRender();
+      rtspCamera1.getGlInterface().setFilter(gifObjectFilterRender);
+      gifObjectFilterRender.setGif(getResources().openRawResource(R.raw.banana));
+      gifObjectFilterRender.setDefaultScale(rtspCamera1.getStreamWidth(),
+          rtspCamera1.getStreamHeight());
+      gifObjectFilterRender.setPosition(TranslateTo.BOTTOM);
+      spriteGestureController.setBaseObjectFilterRender(gifObjectFilterRender); //Optional
+    } catch (IOException e) {
+      Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+    }
+  }
+
+  @Override
+  public void onConnectionSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtspActivity.this, "Connection success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtsp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtspActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT)
+            .show();
+        rtspCamera1.stopStream();
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtsp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtspActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtspActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(OpenGlRtspActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtspCamera1.isStreaming()) {
+          if (rtspCamera1.isRecording()
+              || rtspCamera1.prepareAudio() && rtspCamera1.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtspCamera1.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtspCamera1.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtspCamera1.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (!rtspCamera1.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtspCamera1.isStreaming()) {
+              if (rtspCamera1.prepareAudio() && rtspCamera1.prepareVideo()) {
+                rtspCamera1.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtspCamera1.startRecord(folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtspCamera1.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtspCamera1.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtspCamera1.startPreview();
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (rtspCamera1.isRecording()) {
+      rtspCamera1.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtspCamera1.isStreaming()) {
+      rtspCamera1.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtspCamera1.stopPreview();
+  }
+
+  @Override
+  public boolean onTouch(View view, MotionEvent motionEvent) {
+    if (spriteGestureController.spriteTouched(view, motionEvent)) {
+      spriteGestureController.moveSprite(view, motionEvent);
+      spriteGestureController.scaleSprite(motionEvent);
+      return true;
+    }
+    return false;
+  }
+}

+ 219 - 0
app/src/main/java/com/pedro/rtpstreamer/surfacemodeexample/SurfaceModeRtmpActivity.java

@@ -0,0 +1,219 @@
+package com.pedro.rtpstreamer.surfacemodeexample;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtplibrary.rtmp.RtmpCamera2;
+import com.pedro.rtpstreamer.R;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import net.ossrs.rtmp.ConnectCheckerRtmp;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera2Base}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpCamera2}
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class SurfaceModeRtmpActivity extends AppCompatActivity
+    implements ConnectCheckerRtmp, View.OnClickListener, SurfaceHolder.Callback {
+
+  private RtmpCamera2 rtmpCamera2;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_example);
+    SurfaceView surfaceView = findViewById(R.id.surfaceView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtmp);
+    rtmpCamera2 = new RtmpCamera2(surfaceView, this);
+    rtmpCamera2.setReTries(10);
+    surfaceView.getHolder().addCallback(this);
+  }
+
+  @Override
+  public void onConnectionSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtmpActivity.this, "Connection success", Toast.LENGTH_SHORT)
+            .show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtmp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        //Wait 5s and retry connect stream
+        if (rtmpCamera2.reTry(5000, reason)) {
+          Toast.makeText(SurfaceModeRtmpActivity.this, "Retry", Toast.LENGTH_SHORT)
+              .show();
+        } else {
+          Toast.makeText(SurfaceModeRtmpActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT).show();
+          rtmpCamera2.stopStream();
+          button.setText(R.string.start_button);
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtmp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtmpActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtmpActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtmpActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtmpCamera2.isStreaming()) {
+          if (rtmpCamera2.isRecording()
+              || rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtmpCamera2.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtmpCamera2.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtmpCamera2.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (!rtmpCamera2.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtmpCamera2.isStreaming()) {
+              if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo()) {
+                rtmpCamera2.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtmpCamera2.startRecord(folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtmpCamera2.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtmpCamera2.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtmpCamera2.startPreview();
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (rtmpCamera2.isRecording()) {
+      rtmpCamera2.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtmpCamera2.isStreaming()) {
+      rtmpCamera2.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtmpCamera2.stopPreview();
+  }
+}

+ 220 - 0
app/src/main/java/com/pedro/rtpstreamer/surfacemodeexample/SurfaceModeRtspActivity.java

@@ -0,0 +1,220 @@
+package com.pedro.rtpstreamer.surfacemodeexample;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtplibrary.rtsp.RtspCamera2;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtsp.utils.ConnectCheckerRtsp;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera2Base}
+ * {@link com.pedro.rtplibrary.rtsp.RtspCamera2}
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class SurfaceModeRtspActivity extends AppCompatActivity
+    implements ConnectCheckerRtsp, View.OnClickListener, SurfaceHolder.Callback {
+
+  private RtspCamera2 rtspCamera2;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_example);
+    SurfaceView surfaceView = findViewById(R.id.surfaceView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtsp);
+    rtspCamera2 = new RtspCamera2(surfaceView, this);
+    rtspCamera2.setReTries(10);
+    surfaceView.getHolder().addCallback(this);
+  }
+
+  @Override
+  public void onConnectionSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtspActivity.this, "Connection success", Toast.LENGTH_SHORT)
+            .show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtsp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        //Wait 5s and retry connect stream
+        if (rtspCamera2.reTry(5000, reason)) {
+          Toast.makeText(SurfaceModeRtspActivity.this, "Retry", Toast.LENGTH_SHORT)
+              .show();
+        } else {
+          Toast.makeText(SurfaceModeRtspActivity.this, "Connection failed. " + reason, Toast.LENGTH_SHORT).show();
+          rtspCamera2.stopStream();
+          button.setText(R.string.start_button);
+        }
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtsp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtspActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtspActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(SurfaceModeRtspActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtspCamera2.isStreaming()) {
+          if (rtspCamera2.isRecording()
+              || rtspCamera2.prepareAudio() && rtspCamera2.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtspCamera2.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtspCamera2.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtspCamera2.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (!rtspCamera2.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtspCamera2.isStreaming()) {
+              if (rtspCamera2.prepareAudio() && rtspCamera2.prepareVideo()) {
+                rtspCamera2.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtspCamera2.startRecord(folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtspCamera2.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtspCamera2.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void surfaceCreated(SurfaceHolder surfaceHolder) {
+
+  }
+
+  @Override
+  public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
+    rtspCamera2.startPreview();
+  }
+
+  @Override
+  public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+    if (rtspCamera2.isRecording()) {
+      rtspCamera2.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtspCamera2.isStreaming()) {
+      rtspCamera2.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtspCamera2.stopPreview();
+  }
+}

+ 222 - 0
app/src/main/java/com/pedro/rtpstreamer/texturemodeexample/TextureModeRtmpActivity.java

@@ -0,0 +1,222 @@
+package com.pedro.rtpstreamer.texturemodeexample;
+
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.TextureView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtplibrary.rtmp.RtmpCamera2;
+import com.pedro.rtplibrary.view.AutoFitTextureView;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import net.ossrs.rtmp.ConnectCheckerRtmp;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera2Base}
+ * {@link com.pedro.rtplibrary.rtmp.RtmpCamera2}
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class TextureModeRtmpActivity extends AppCompatActivity
+    implements ConnectCheckerRtmp, View.OnClickListener, TextureView.SurfaceTextureListener {
+
+  private RtmpCamera2 rtmpCamera2;
+  private AutoFitTextureView textureView;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_texture_mode);
+    textureView = findViewById(R.id.textureView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtmp);
+    rtmpCamera2 = new RtmpCamera2(textureView, this);
+    textureView.setSurfaceTextureListener(this);
+  }
+
+  @Override
+  public void onConnectionSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtmpActivity.this, "Connection success", Toast.LENGTH_SHORT)
+            .show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtmp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtmpActivity.this, "Connection failed. " + reason,
+            Toast.LENGTH_SHORT).show();
+        rtmpCamera2.stopStream();
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtmp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtmpActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtmpActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtmp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtmpActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtmpCamera2.isStreaming()) {
+          if (rtmpCamera2.isRecording()
+              || rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtmpCamera2.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtmpCamera2.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtmpCamera2.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (!rtmpCamera2.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtmpCamera2.isStreaming()) {
+              if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo()) {
+                rtmpCamera2.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtmpCamera2.startRecord(folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtmpCamera2.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtmpCamera2.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
+    textureView.setAspectRatio(480, 640);
+  }
+
+  @Override
+  public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
+    rtmpCamera2.startPreview();
+  }
+
+  @Override
+  public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+    if (rtmpCamera2.isRecording()) {
+      rtmpCamera2.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtmpCamera2.isStreaming()) {
+      rtmpCamera2.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtmpCamera2.stopPreview();
+
+    return true;
+  }
+
+  @Override
+  public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+
+  }
+}

+ 226 - 0
app/src/main/java/com/pedro/rtpstreamer/texturemodeexample/TextureModeRtspActivity.java

@@ -0,0 +1,226 @@
+package com.pedro.rtpstreamer.texturemodeexample;
+
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.TextureView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+import com.pedro.encoder.input.video.CameraOpenException;
+import com.pedro.rtpstreamer.R;
+import com.pedro.rtplibrary.rtsp.RtspCamera2;
+import com.pedro.rtplibrary.view.AutoFitTextureView;
+import com.pedro.rtsp.utils.ConnectCheckerRtsp;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * More documentation see:
+ * {@link com.pedro.rtplibrary.base.Camera2Base}
+ * {@link com.pedro.rtplibrary.rtsp.RtspCamera2}
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class TextureModeRtspActivity extends AppCompatActivity
+    implements ConnectCheckerRtsp, View.OnClickListener, TextureView.SurfaceTextureListener {
+
+  private RtspCamera2 rtspCamera2;
+  private AutoFitTextureView textureView;
+  private Button button;
+  private Button bRecord;
+  private EditText etUrl;
+
+  private String currentDateAndTime = "";
+  private File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+      + "/rtmp-rtsp-stream-client-java");
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    setContentView(R.layout.activity_texture_mode);
+    textureView = findViewById(R.id.textureView);
+    button = findViewById(R.id.b_start_stop);
+    button.setOnClickListener(this);
+    bRecord = findViewById(R.id.b_record);
+    bRecord.setOnClickListener(this);
+    Button switchCamera = findViewById(R.id.switch_camera);
+    switchCamera.setOnClickListener(this);
+    etUrl = findViewById(R.id.et_rtp_url);
+    etUrl.setHint(R.string.hint_rtsp);
+    rtspCamera2 = new RtspCamera2(textureView, this);
+    textureView.setSurfaceTextureListener(this);
+  }
+
+  @Override
+  public void onConnectionSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtspActivity.this, "Connection success", Toast.LENGTH_SHORT)
+            .show();
+      }
+    });
+  }
+
+  @Override
+  public void onConnectionFailedRtsp(final String reason) {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtspActivity.this, "Connection failed. " + reason,
+            Toast.LENGTH_SHORT).show();
+        rtspCamera2.stopStream();
+        button.setText(R.string.start_button);
+      }
+    });
+  }
+
+  @Override
+  public void onNewBitrateRtsp(long bitrate) {
+
+  }
+
+  @Override
+  public void onDisconnectRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtspActivity.this, "Disconnected", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthErrorRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtspActivity.this, "Auth error", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onAuthSuccessRtsp() {
+    runOnUiThread(new Runnable() {
+      @Override
+      public void run() {
+        Toast.makeText(TextureModeRtspActivity.this, "Auth success", Toast.LENGTH_SHORT).show();
+      }
+    });
+  }
+
+  @Override
+  public void onClick(View view) {
+    switch (view.getId()) {
+      case R.id.b_start_stop:
+        if (!rtspCamera2.isStreaming()) {
+          if (rtspCamera2.isRecording()
+              || rtspCamera2.prepareAudio() && rtspCamera2.prepareVideo()) {
+            button.setText(R.string.stop_button);
+            rtspCamera2.startStream(etUrl.getText().toString());
+          } else {
+            Toast.makeText(this, "Error preparing stream, This device cant do it",
+                Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          button.setText(R.string.start_button);
+          rtspCamera2.stopStream();
+        }
+        break;
+      case R.id.switch_camera:
+        try {
+          rtspCamera2.switchCamera();
+        } catch (CameraOpenException e) {
+          Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+        break;
+      case R.id.b_record:
+        if (!rtspCamera2.isRecording()) {
+          try {
+            if (!folder.exists()) {
+              folder.mkdir();
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
+            currentDateAndTime = sdf.format(new Date());
+            if (!rtspCamera2.isStreaming()) {
+              if (rtspCamera2.prepareAudio() && rtspCamera2.prepareVideo()) {
+                rtspCamera2.startRecord(
+                    folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+                bRecord.setText(R.string.stop_record);
+                Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+              } else {
+                Toast.makeText(this, "Error preparing stream, This device cant do it",
+                    Toast.LENGTH_SHORT).show();
+              }
+            } else {
+              rtspCamera2.startRecord(folder.getAbsolutePath() + "/" + currentDateAndTime + ".mp4");
+              bRecord.setText(R.string.stop_record);
+              Toast.makeText(this, "Recording... ", Toast.LENGTH_SHORT).show();
+            }
+          } catch (IOException e) {
+            rtspCamera2.stopRecord();
+            bRecord.setText(R.string.start_record);
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+          }
+        } else {
+          rtspCamera2.stopRecord();
+          bRecord.setText(R.string.start_record);
+          Toast.makeText(this,
+              "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+              Toast.LENGTH_SHORT).show();
+          currentDateAndTime = "";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  @Override
+  public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
+    textureView.setAspectRatio(480, 640);
+  }
+
+  @Override
+  public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
+    rtspCamera2.startPreview();
+  }
+
+  @Override
+  public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+    if (rtspCamera2.isRecording()) {
+      rtspCamera2.stopRecord();
+      bRecord.setText(R.string.start_record);
+      Toast.makeText(this,
+          "file " + currentDateAndTime + ".mp4 saved in " + folder.getAbsolutePath(),
+          Toast.LENGTH_SHORT).show();
+      currentDateAndTime = "";
+    }
+    if (rtspCamera2.isStreaming()) {
+      rtspCamera2.stopStream();
+      button.setText(getResources().getString(R.string.start_button));
+    }
+    rtspCamera2.stopPreview();
+    return true;
+  }
+
+  @Override
+  public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+
+  }
+
+  @Override
+  public void onPointerCaptureChanged(boolean hasCapture) {
+
+  }
+}

+ 27 - 0
app/src/main/java/com/pedro/rtpstreamer/utils/ActivityLink.java

@@ -0,0 +1,27 @@
+package com.pedro.rtpstreamer.utils;
+
+import android.content.Intent;
+
+public class ActivityLink {
+  private final int minSdk;
+  private final String label;
+  private final Intent intent;
+
+  public ActivityLink(Intent intent, String label, int minSdk) {
+    this.intent = intent;
+    this.label = label;
+    this.minSdk = minSdk;
+  }
+
+  public String getLabel() {
+    return label;
+  }
+
+  public Intent getIntent() {
+    return intent;
+  }
+
+  public int getMinSdk() {
+    return minSdk;
+  }
+}

+ 60 - 0
app/src/main/java/com/pedro/rtpstreamer/utils/ImageAdapter.java

@@ -0,0 +1,60 @@
+package com.pedro.rtpstreamer.utils;
+
+import android.content.res.Resources;
+import androidx.core.content.res.ResourcesCompat;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+import com.pedro.rtpstreamer.R;
+import java.util.List;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+public class ImageAdapter extends BaseAdapter {
+
+  private List<ActivityLink> links;
+
+  public ImageAdapter(List<ActivityLink> links) {
+    this.links = links;
+  }
+
+  public int getCount() {
+    return links.size();
+  }
+
+  public ActivityLink getItem(int position) {
+    return links.get(position);
+  }
+
+  public long getItemId(int position) {
+    return (long) position;
+  }
+
+  @Override
+  public View getView(int position, View convertView, ViewGroup parent) {
+    TextView button;
+    Resources resources = parent.getResources();
+    float fontSize = resources.getDimension(R.dimen.menu_font);
+    int paddingH = resources.getDimensionPixelSize(R.dimen.grid_2);
+    int paddingV = resources.getDimensionPixelSize(R.dimen.grid_5);
+    if (convertView == null) {
+      button = new TextView(parent.getContext());
+      button.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
+      button.setTextColor(ResourcesCompat.getColor(resources, R.color.white, null));
+      button.setBackgroundColor(ResourcesCompat.getColor(resources, R.color.appColor, null));
+      button.setLayoutParams(new GridView.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+      button.setPadding(paddingH, paddingV, paddingH, paddingV);
+      button.setGravity(Gravity.CENTER);
+      convertView = button;
+    } else {
+      button = (TextView) convertView;
+    }
+    button.setText(links.get(position).getLabel());
+    return convertView;
+  }
+}

+ 123 - 0
app/src/main/java/com/pedro/rtpstreamer/utils/PathUtils.java

@@ -0,0 +1,123 @@
+package com.pedro.rtpstreamer.utils;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+
+/**
+ * Created by pedro on 21/06/17.
+ * Get absolute path from onActivityResult
+ * https://stackoverflow.com/questions/33295300/how-to-get-absolute-path-in-android-for-file
+ */
+
+public class PathUtils {
+
+  public static String getPath(final Context context, final Uri uri) {
+
+    // DocumentProvider
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(
+        context, uri)) {
+
+      if (isExternalStorageDocument(uri)) {// ExternalStorageProvider
+        final String docId = DocumentsContract.getDocumentId(uri);
+        final String[] split = docId.split(":");
+        final String type = split[0];
+        String storageDefinition;
+
+        if ("primary".equalsIgnoreCase(type)) {
+
+          return Environment.getExternalStorageDirectory() + "/" + split[1];
+        } else {
+
+          if (Environment.isExternalStorageRemovable()) {
+            storageDefinition = "EXTERNAL_STORAGE";
+          } else {
+            storageDefinition = "SECONDARY_STORAGE";
+          }
+
+          return System.getenv(storageDefinition) + "/" + split[1];
+        }
+      } else if (isDownloadsDocument(uri)) {// DownloadsProvider
+
+        final String id = DocumentsContract.getDocumentId(uri);
+        final Uri contentUri =
+            ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),
+                Long.valueOf(id));
+
+        return getDataColumn(context, contentUri, null, null);
+      } else if (isMediaDocument(uri)) {// MediaProvider
+        final String docId = DocumentsContract.getDocumentId(uri);
+        final String[] split = docId.split(":");
+        final String type = split[0];
+
+        Uri contentUri = null;
+        if ("image".equals(type)) {
+          contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+        } else if ("video".equals(type)) {
+          contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+        } else if ("audio".equals(type)) {
+          contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+        }
+
+        final String selection = "_id=?";
+        final String[] selectionArgs = new String[] {
+            split[1]
+        };
+
+        return getDataColumn(context, contentUri, selection, selectionArgs);
+      }
+    } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general)
+
+      // Return the remote address
+      if (isGooglePhotosUri(uri)) return uri.getLastPathSegment();
+
+      return getDataColumn(context, uri, null, null);
+    } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File
+      return uri.getPath();
+    }
+
+    return null;
+  }
+
+  private static String getDataColumn(Context context, Uri uri, String selection,
+      String[] selectionArgs) {
+
+    Cursor cursor = null;
+    final String column = "_data";
+    final String[] projection = {
+        column
+    };
+
+    try {
+      cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
+      if (cursor != null && cursor.moveToFirst()) {
+        final int column_index = cursor.getColumnIndexOrThrow(column);
+        return cursor.getString(column_index);
+      }
+    } finally {
+      if (cursor != null) cursor.close();
+    }
+    return null;
+  }
+
+  private static boolean isExternalStorageDocument(Uri uri) {
+    return "com.android.externalstorage.documents".equals(uri.getAuthority());
+  }
+
+  private static boolean isDownloadsDocument(Uri uri) {
+    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+  }
+
+  private static boolean isMediaDocument(Uri uri) {
+    return "com.android.providers.media.documents".equals(uri.getAuthority());
+  }
+
+  private static boolean isGooglePhotosUri(Uri uri) {
+    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+  }
+}

BIN
app/src/main/res/drawable-hdpi/icon_camera.png


BIN
app/src/main/res/drawable-hdpi/icon_camera_off.png


BIN
app/src/main/res/drawable-hdpi/icon_menu.png


BIN
app/src/main/res/drawable-hdpi/icon_microphone.png


BIN
app/src/main/res/drawable-hdpi/icon_microphone_off.png


BIN
app/src/main/res/drawable-hdpi/icon_mobile.png


BIN
app/src/main/res/drawable-mdpi/icon_camera.png


BIN
app/src/main/res/drawable-mdpi/icon_camera_off.png


BIN
app/src/main/res/drawable-mdpi/icon_menu.png


BIN
app/src/main/res/drawable-mdpi/icon_microphone.png


BIN
app/src/main/res/drawable-mdpi/icon_microphone_off.png


BIN
app/src/main/res/drawable-mdpi/icon_mobile.png


BIN
app/src/main/res/drawable-xhdpi/icon_camera.png


BIN
app/src/main/res/drawable-xhdpi/icon_camera_off.png


BIN
app/src/main/res/drawable-xhdpi/icon_menu.png


BIN
app/src/main/res/drawable-xhdpi/icon_microphone.png


BIN
app/src/main/res/drawable-xhdpi/icon_microphone_off.png


BIN
app/src/main/res/drawable-xhdpi/icon_mobile.png


BIN
app/src/main/res/drawable-xxhdpi/icon_camera.png


BIN
app/src/main/res/drawable-xxhdpi/icon_camera_off.png


BIN
app/src/main/res/drawable-xxhdpi/icon_menu.png


BIN
app/src/main/res/drawable-xxhdpi/icon_microphone.png


BIN
app/src/main/res/drawable-xxhdpi/icon_microphone_off.png


BIN
app/src/main/res/drawable-xxhdpi/icon_mobile.png


+ 14 - 0
app/src/main/res/drawable/notification_anim.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/selected"
+    android:oneshot="false"
+    >
+  <item
+      android:drawable="@drawable/icon_camera"
+      android:duration="500"
+      />
+  <item
+      android:drawable="@drawable/icon_camera_off"
+      android:duration="500"
+      />
+</animation-list>

+ 33 - 0
app/src/main/res/layout/activity_background.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/activity_example_rtmp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+
+  <com.pedro.rtplibrary.view.OpenGlView
+      android:id="@+id/surfaceView"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"/>
+
+  <EditText
+      android:textColor="@color/appColor"
+      android:textColorHint="@color/appColor"
+      android:inputType="textUri"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="center"
+      android:layout_margin="20dp"
+      android:id="@+id/et_rtp_url"
+      />
+
+    <Button
+        android:text="@string/start_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="20dp"
+        android:id="@+id/b_start_stop"
+        />
+</RelativeLayout>

+ 88 - 0
app/src/main/res/layout/activity_custom.xml

@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:openDrawer="start"
+    android:id="@+id/activity_custom"
+    >
+
+  <RelativeLayout
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      >
+
+    <SurfaceView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/surfaceView"
+        />
+
+    <EditText
+        android:textColor="@color/appColor"
+        android:textColorHint="@color/appColor"
+        android:inputType="textUri"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:layout_margin="20dp"
+        android:id="@+id/et_rtp_url"
+        />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_alignParentBottom="true"
+        android:layout_margin="20dp"
+        android:gravity="center"
+        >
+
+      <Button
+          android:text="@string/start_record"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_marginRight="5dp"
+          android:id="@+id/b_record"
+          />
+
+      <Button
+          android:text="@string/start_button"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_marginRight="5dp"
+          android:id="@+id/b_start_stop"
+          />
+
+      <Button
+          android:text="@string/switch_camera_button"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:id="@+id/switch_camera"
+          />
+    </LinearLayout>
+
+    <TextView
+        android:textColor="@color/appColor"
+        android:id="@+id/tv_bitrate"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentRight="true"
+        android:layout_below="@+id/et_rtp_url"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="20dp"
+        />
+  </RelativeLayout>
+
+  <com.google.android.material.navigation.NavigationView
+      android:layout_width="wrap_content"
+      android:layout_height="match_parent"
+      android:layout_gravity="start"
+      android:paddingBottom="30dp"
+      android:fitsSystemWindows="true"
+      app:headerLayout="@xml/options_header"
+      android:id="@+id/nv_rtp"
+      >
+  </com.google.android.material.navigation.NavigationView>
+</androidx.drawerlayout.widget.DrawerLayout>

+ 40 - 0
app/src/main/res/layout/activity_display.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/activity_example_rtmp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+  <SurfaceView
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:id="@+id/surfaceView"
+      />
+
+  <EditText
+      android:textColor="@color/appColor"
+      android:textColorHint="@color/appColor"
+      android:inputType="textUri"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="center"
+      android:layout_margin="20dp"
+      android:id="@+id/et_rtp_url"
+      />
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      android:layout_alignParentBottom="true"
+      android:layout_margin="20dp"
+      android:gravity="center"
+      >
+
+    <Button
+        android:text="@string/start_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/b_start_stop"
+        />
+  </LinearLayout>
+</RelativeLayout>

+ 56 - 0
app/src/main/res/layout/activity_example.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/activity_example_rtmp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+  <SurfaceView
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:id="@+id/surfaceView"
+      />
+
+  <EditText
+      android:textColor="@color/appColor"
+      android:textColorHint="@color/appColor"
+      android:inputType="textUri"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="center"
+      android:layout_margin="20dp"
+      android:id="@+id/et_rtp_url"
+      />
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      android:layout_alignParentBottom="true"
+      android:layout_margin="20dp"
+      android:gravity="center"
+      >
+
+    <Button
+        android:text="@string/start_record"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_record"
+        />
+
+    <Button
+        android:text="@string/start_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_start_stop"
+        />
+
+    <Button
+        android:text="@string/switch_camera_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/switch_camera"
+        />
+  </LinearLayout>
+</RelativeLayout>

+ 89 - 0
app/src/main/res/layout/activity_from_file.xml

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/black"
+    >
+
+  <EditText
+      android:hint="@string/hint_rtmp"
+      android:textColor="@color/appColor"
+      android:textColorHint="@color/appColor"
+      android:inputType="textUri"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="center"
+      android:layout_margin="20dp"
+      android:id="@+id/et_rtp_url"
+      />
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      android:layout_alignParentBottom="true"
+      android:layout_margin="20dp"
+      android:gravity="center"
+      android:id="@+id/ll_buttons"
+      >
+
+    <Button
+        android:text="@string/start_record"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_record"
+        />
+
+    <Button
+        android:text="@string/start_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_start_stop"
+        />
+
+  </LinearLayout>
+
+  <Button
+      android:text="@string/select_file"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:id="@+id/b_select_file"
+      android:layout_marginBottom="10dp"
+      android:layout_marginRight="10dp"
+      android:layout_above="@+id/seek_bar"
+      android:layout_alignParentRight="true"
+      />
+
+  <Button
+      android:text="@string/resync_file"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_above="@+id/seek_bar"
+      android:layout_marginBottom="10dp"
+      android:layout_marginLeft="10dp"
+      android:id="@+id/b_re_sync"
+      />
+
+  <SeekBar
+      style="?android:attr/progressBarStyleHorizontal"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_above="@+id/ll_buttons"
+      android:layout_centerHorizontal="true"
+      android:id="@+id/seek_bar"
+      android:layout_marginLeft="20dp"
+      android:layout_marginRight="20dp"
+      />
+
+  <TextView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:id="@+id/tv_file"
+      android:layout_above="@+id/b_select_file"
+      android:layout_centerHorizontal="true"
+      android:textColor="@color/appColor"
+      android:layout_margin="10dp"
+      />
+</RelativeLayout>

+ 45 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/activity_main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"
+    android:orientation="vertical"
+    >
+
+  <TextView
+      android:id="@+id/tv_version"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:textColor="@color/appColor"
+      android:textSize="12sp"
+      android:layout_marginTop="@dimen/grid_2"
+      android:layout_marginLeft="@dimen/grid_2"
+      android:layout_marginRight="@dimen/grid_2"
+      />
+  <TextView
+      android:id="@+id/tv_select"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/grid_2"
+      android:layout_marginLeft="@dimen/grid_2"
+      android:layout_marginRight="@dimen/grid_2"
+      android:text="@string/tv_select"
+      android:textColor="@color/appColor"
+      android:textSize="22sp"
+      />
+
+  <GridView
+      android:id="@+id/list"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:layout_marginTop="@dimen/grid_1"
+      android:columnWidth="40dp"
+      android:gravity="center"
+      android:horizontalSpacing="@dimen/grid_2"
+      android:numColumns="2"
+      android:padding="@dimen/grid_2"
+      android:stretchMode="columnWidth"
+      android:verticalSpacing="@dimen/grid_2"
+      />
+</LinearLayout>

+ 63 - 0
app/src/main/res/layout/activity_open_gl.xml

@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/activity_example_rtmp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+  <com.pedro.rtplibrary.view.OpenGlView
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:id="@+id/surfaceView"
+      app:keepAspectRatio="true"
+      app:aspectRatioMode="adjust"
+      app:AAEnabled="false"
+      app:numFilters="1"
+      app:isFlipHorizontal="false"
+      app:isFlipVertical="false"
+      />
+
+  <EditText
+      android:textColor="@color/appColor"
+      android:textColorHint="@color/appColor"
+      android:inputType="textUri"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="center"
+      android:layout_margin="20dp"
+      android:id="@+id/et_rtp_url"
+      />
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      android:layout_alignParentBottom="true"
+      android:layout_margin="20dp"
+      android:gravity="center"
+      >
+
+    <Button
+        android:text="@string/start_record"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_record"
+        />
+
+    <Button
+        android:text="@string/start_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_start_stop"
+        />
+
+    <Button
+        android:text="@string/switch_camera_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/switch_camera"
+        />
+  </LinearLayout>
+</RelativeLayout>

+ 58 - 0
app/src/main/res/layout/activity_texture_mode.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/activity_example_rtmp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/black"
+    >
+
+  <com.pedro.rtplibrary.view.AutoFitTextureView
+      android:id="@+id/textureView"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      />
+
+  <EditText
+      android:id="@+id/et_rtp_url"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_margin="20dp"
+      android:gravity="center"
+      android:inputType="textUri"
+      android:textColor="@color/appColor"
+      android:textColorHint="@color/appColor"
+      />
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      android:layout_alignParentBottom="true"
+      android:layout_margin="20dp"
+      android:gravity="center"
+      >
+
+    <Button
+        android:text="@string/start_record"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_record"
+        />
+
+    <Button
+        android:text="@string/start_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="5dp"
+        android:id="@+id/b_start_stop"
+        />
+
+    <Button
+        android:text="@string/switch_camera_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/switch_camera"
+        />
+  </LinearLayout>
+</RelativeLayout>

+ 182 - 0
app/src/main/res/menu/gl_menu.xml

@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+  <item
+      android:id="@+id/e_d_fxaa"
+      android:title="@string/enable_disable_fxaa"
+    />
+  <item
+      android:title="@string/filters"
+      >
+    <menu>
+      <item
+          android:id="@+id/no_filter"
+          android:title="@string/no_filter"
+          />
+      <item
+          android:id="@+id/analog_tv"
+          android:title="@string/analog_tv"
+          />
+      <item
+          android:id="@+id/android_view"
+          android:title="@string/android_view"
+          />
+      <item
+          android:id="@+id/basic_deformation"
+          android:title="@string/basic_deformation"
+          />
+      <item
+          android:id="@+id/beauty"
+          android:title="@string/beauty"
+          />
+      <item
+          android:id="@+id/black"
+          android:title="@string/black"
+          />
+      <item
+          android:id="@+id/blur"
+          android:title="@string/blur"
+          />
+      <item
+          android:id="@+id/brightness"
+          android:title="@string/brightness"
+          />
+      <item
+          android:id="@+id/cartoon"
+          android:title="@string/cartoon"
+          />
+      <item
+          android:id="@+id/circle"
+          android:title="@string/circle"
+          />
+      <item
+          android:id="@+id/color"
+          android:title="@string/color"
+          />
+      <item
+          android:id="@+id/contrast"
+          android:title="@string/contrast"
+          />
+      <item
+          android:id="@+id/duotone"
+          android:title="@string/duotone"
+          />
+      <item
+          android:id="@+id/early_bird"
+          android:title="@string/earlybird"
+          />
+      <item
+          android:id="@+id/edge_detection"
+          android:title="@string/edge_detection"
+          />
+      <item
+          android:id="@+id/exposure"
+          android:title="@string/exposure"
+          />
+      <item
+          android:id="@+id/fire"
+          android:title="@string/fire"
+          />
+      <item
+          android:id="@+id/gamma"
+          android:title="@string/gamma"
+          />
+      <item
+          android:id="@+id/glitch"
+          android:title="@string/glitch"
+          />
+      <item
+          android:id="@+id/gif"
+          android:title="@string/gif"
+          />
+      <item
+          android:id="@+id/grey_scale"
+          android:title="@string/grey_scale"
+          />
+      <item
+          android:id="@+id/halftone_lines"
+          android:title="@string/halftone_lines"
+          />
+      <item
+          android:id="@+id/image"
+          android:title="@string/image"
+          />
+      <item
+          android:id="@+id/image_70s"
+          android:title="@string/image_70s"
+          />
+      <item
+          android:id="@+id/lamoish"
+          android:title="@string/lamoish"
+          />
+      <item
+          android:id="@+id/money"
+          android:title="@string/money"
+          />
+      <item
+          android:id="@+id/negative"
+          android:title="@string/negative"
+          />
+      <item
+          android:id="@+id/pixelated"
+          android:title="@string/pixelated"
+          />
+      <item
+          android:id="@+id/polygonization"
+          android:title="@string/polygonization"
+          />
+      <item
+          android:id="@+id/rainbow"
+          android:title="@string/rainbow"
+          />
+      <item
+          android:id="@+id/rgb_saturate"
+          android:title="@string/rgb_saturate"
+          />
+      <item
+          android:id="@+id/ripple"
+          android:title="@string/ripple"
+          />
+      <item
+          android:id="@+id/rotation"
+          android:title="@string/rotation"
+          />
+      <item
+          android:id="@+id/saturation"
+          android:title="@string/saturation"
+          />
+      <item
+          android:id="@+id/sepia"
+          android:title="@string/sepia"
+          />
+      <item
+          android:id="@+id/sharpness"
+          android:title="@string/sharpness"
+          />
+      <item
+          android:id="@+id/snow"
+          android:title="@string/snow"
+          />
+      <item
+          android:id="@+id/swirl"
+          android:title="@string/swirl"
+          />
+      <item
+          android:id="@+id/surface_filter"
+          android:title="@string/surface_filter"
+          />
+      <item
+          android:id="@+id/temperature"
+          android:title="@string/temperature"
+          />
+      <item
+          android:id="@+id/text"
+          android:title="@string/text"
+          />
+      <item
+          android:id="@+id/zebra"
+          android:title="@string/zebra"
+          />
+    </menu>
+  </item>
+</menu>

+ 26 - 0
app/src/main/res/menu/menu.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+  <item
+      android:icon="@drawable/icon_mobile"
+      android:title="@string/microphone"
+      app:showAsAction="always"
+      >
+    <menu>
+      <item
+          android:id="@+id/microphone"
+          android:icon="@drawable/icon_microphone"
+          android:title="@string/microphone"
+          app:showAsAction="always"
+          />
+      <item
+          android:id="@+id/camera"
+          android:icon="@drawable/icon_camera"
+          android:title="@string/camera"
+          app:showAsAction="always"
+          />
+    </menu>
+  </item>
+</menu>

+ 76 - 0
app/src/main/res/menu/options_rtmp.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+  <item android:title="@string/protocols">
+    <menu>
+      <item
+          android:title="@string/tcp"
+          app:actionViewClass="android.widget.RadioButton"
+          android:id="@+id/rb_tcp"
+          />
+    </menu>
+  </item>
+  <item android:title="@string/video">
+    <menu>
+      <item
+          android:title="@string/resolution"
+          app:actionViewClass="android.widget.Spinner"
+          android:id="@+id/sp_resolution"
+          />
+      <item
+          android:title="@string/video_bitrate"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_video_bitrate"
+          />
+      <item
+          android:title="@string/fps"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_fps"
+          />
+    </menu>
+  </item>
+  <item android:title="@string/audio">
+    <menu>
+      <item
+          android:title="@string/audio_bitrate"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_audio_bitrate"
+          />
+      <item
+          android:title="@string/samplerate"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_samplerate"
+          />
+      <item
+          android:title="@string/channel"
+          app:actionLayout="@xml/radio_group_audio"
+          android:id="@+id/channel"
+          />
+      <item
+          android:title="@string/echo_canceler"
+          app:actionViewClass="android.widget.CheckBox"
+          android:id="@+id/cb_echo_canceler"
+          />
+      <item
+          android:title="@string/noise_suppressor"
+          app:actionViewClass="android.widget.CheckBox"
+          android:id="@+id/cb_noise_suppressor"
+          />
+    </menu>
+  </item>
+  <item android:title="@string/auth">
+    <menu>
+      <item
+          android:title="@string/user"
+          app:actionLayout="@xml/edit_text_auth"
+          android:id="@+id/et_user"
+          />
+      <item
+          android:title="@string/password"
+          app:actionLayout="@xml/edit_text_auth"
+          android:id="@+id/et_password"
+          />
+    </menu>
+  </item>
+</menu>

+ 81 - 0
app/src/main/res/menu/options_rtsp.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+  <item android:title="@string/protocols">
+    <menu>
+      <item
+          android:title="@string/tcp"
+          app:actionViewClass="android.widget.RadioButton"
+          android:id="@+id/rb_tcp"
+          />
+      <item
+          android:title="@string/udp"
+          app:actionViewClass="android.widget.RadioButton"
+          android:id="@+id/rb_udp"
+          />
+    </menu>
+  </item>
+  <item android:title="@string/video">
+    <menu>
+      <item
+          android:title="@string/resolution"
+          app:actionViewClass="android.widget.Spinner"
+          android:id="@+id/sp_resolution"
+          />
+      <item
+          android:title="@string/video_bitrate"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_video_bitrate"
+          />
+      <item
+          android:title="@string/fps"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_fps"
+          />
+    </menu>
+  </item>
+  <item android:title="@string/audio">
+    <menu>
+      <item
+          android:title="@string/audio_bitrate"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_audio_bitrate"
+          />
+      <item
+          android:title="@string/samplerate"
+          app:actionLayout="@xml/edit_text_int"
+          android:id="@+id/et_samplerate"
+          />
+      <item
+          android:title="@string/channel"
+          app:actionLayout="@xml/radio_group_audio"
+          android:id="@+id/channel"
+          />
+      <item
+          android:title="@string/echo_canceler"
+          app:actionViewClass="android.widget.CheckBox"
+          android:id="@+id/cb_echo_canceler"
+          />
+      <item
+          android:title="@string/noise_suppressor"
+          app:actionViewClass="android.widget.CheckBox"
+          android:id="@+id/cb_noise_suppressor"
+          />
+    </menu>
+  </item>
+  <item android:title="@string/auth">
+    <menu>
+      <item
+          android:title="@string/user"
+          app:actionLayout="@xml/edit_text_auth"
+          android:id="@+id/et_user"
+          />
+      <item
+          android:title="@string/password"
+          app:actionLayout="@xml/edit_text_auth"
+          android:id="@+id/et_password"
+          />
+    </menu>
+  </item>
+</menu>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/raw/banana.gif


BIN
app/src/main/res/raw/big_bunny_240p.mp4


+ 8 - 0
app/src/main/res/transition/slide_in.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@android:integer/config_longAnimTime"
+    android:fromXDelta="100%p"
+    android:toXDelta="0%p"
+    >
+</translate>

+ 8 - 0
app/src/main/res/transition/slide_out.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@android:integer/config_longAnimTime"
+    android:fromXDelta="0%p"
+    android:toXDelta="-100%p"
+    >
+</translate>

+ 6 - 0
app/src/main/res/values-w820dp/dimens.xml

@@ -0,0 +1,6 @@
+<resources>
+  <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+  <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>

+ 6 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="appColor">#e74c3c</color>
+  <color name="black">#000000</color>
+  <color name="white">#ffffff</color>
+</resources>

+ 16 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,16 @@
+<resources>
+  <!-- Default screen margins, per the Android Design guidelines. -->
+  <dimen name="activity_horizontal_margin">16dp</dimen>
+  <dimen name="activity_vertical_margin">16dp</dimen>
+  <dimen name="menu_font">16sp</dimen>
+
+  <dimen name="grid_1">4dp</dimen>
+  <dimen name="grid_2">8dp</dimen>
+  <dimen name="grid_3">12dp</dimen>
+  <dimen name="grid_4">16dp</dimen>
+  <dimen name="grid_5">20dp</dimen>
+  <dimen name="grid_6">24dp</dimen>
+  <dimen name="grid_8">32dp</dimen>
+  <dimen name="grid_9">36dp</dimen>
+  <dimen name="grid_10">40dp</dimen>
+</resources>

+ 105 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,105 @@
+<resources>
+  <string name="app_name">RTMP-RTSP Streamer</string>
+  <string name="version">Version: %1$s</string>
+
+  <string name="rtsp_streamer">Custom RTSP</string>
+  <string name="rtmp_streamer">Custom RTMP</string>
+  <string name="default_rtmp">Default RTMP</string>
+  <string name="default_rtsp">Default RTSP</string>
+  <string name="from_file_rtsp">From file RTSP</string>
+  <string name="from_file_rtmp">From file RTMP</string>
+  <string name="surface_mode_rtsp">Surface mode RTSP</string>
+  <string name="surface_mode_rtmp">Surface mode RTMP</string>
+  <string name="texture_mode_rtsp">Texture mode RTSP</string>
+  <string name="texture_mode_rtmp">Texture mode RTMP</string>
+  <string name="display_rtmp">Display RTMP or RTSP</string>
+  <string name="opengl_rtmp">OpenGl RTMP</string>
+  <string name="opengl_rtsp">OpenGl RTSP</string>
+  <string name="service_rtp">Service RTP</string>
+  <string name="cube_test_rtmp">Cube test RTMP</string>
+
+  <string name="tv_select">Select your streamer</string>
+  <string name="hint_rtsp">rtsp://yourendpoint</string>
+  <string name="hint_rtmp">rtmp://yourendpoint</string>
+  <string name="start_button">Start stream</string>
+  <string name="stop_button">Stop stream</string>
+  <string name="switch_camera_button">Switch camera</string>
+  <string name="select_file">Select file</string>
+  <string name="start_record">Start record</string>
+  <string name="stop_record">Stop record</string>
+  <string name="resync_file">ReSync file</string>
+  <!--menu-->
+  <string name="microphone">Microphone</string>
+  <string name="filters">Filters</string>
+  <string name="clear">Clear</string>
+  <string name="grey_scale">Grey scale</string>
+  <string name="negative">Negative</string>
+  <string name="sepia">Sepia</string>
+  <string name="aqua">Aqua</string>
+  <string name="posterize">Posterize</string>
+  <!--gl menu-->
+  <string name="enable_disable_fxaa">enable/disable FXAA</string>
+  <!--filters-->
+  <string name="no_filter">No filter</string>
+  <string name="analog_tv">Analog TV</string>
+  <string name="android_view">Android view</string>
+  <string name="basic_deformation">Basic deformation</string>
+  <string name="beauty">Beauty</string>
+  <string name="black">Black</string>
+  <string name="blur">Blur</string>
+  <string name="brightness">Brightness</string>
+  <string name="cartoon">Cartoon</string>
+  <string name="circle">Circle</string>
+  <string name="color">Color</string>
+  <string name="contrast">Contrast</string>
+  <string name="duotone">Duotone</string>
+  <string name="earlybird">EarlyBird</string>
+  <string name="edge_detection">Edge detection</string>
+  <string name="exposure">Exposure</string>
+  <string name="fire">Fire</string>
+  <string name="gamma">Gamma</string>
+  <string name="glitch">Glitch</string>
+  <string name="gif">Gif</string>
+  <string name="halftone_lines">Halftone lines</string>
+  <string name="image">Image</string>
+  <string name="image_70s">Image 70s</string>
+  <string name="lamoish">Lamoish</string>
+  <string name="money">Money</string>
+  <string name="pixelated">Pixelated</string>
+  <string name="polygonization">Polygonization</string>
+  <string name="rgb_saturate">RGB saturate</string>
+  <string name="ripple">Ripple</string>
+  <string name="rotation">Rotation</string>
+  <string name="saturation">Saturation</string>
+  <string name="surface_filter">Surface filter</string>
+  <string name="rainbow">Rainbow</string>
+  <string name="sharpness">Sharpness</string>
+  <string name="snow">Snow</string>
+  <string name="swirl">Swirl</string>
+  <string name="temperature">Temperature</string>
+  <string name="text">Text</string>
+  <string name="zebra">Zebra</string>
+  <!--drawer-->
+  <string name="drawer_title">Options</string>
+  <string name="protocols">Protocol</string>
+  <string name="tcp">TCP</string>
+  <string name="udp">UDP</string>
+  <string name="video">Video</string>
+  <string name="audio">Audio</string>
+  <string name="resolution">Resolution</string>
+  <string name="video_bitrate">Bitrate (on fly)</string>
+  <string name="audio_bitrate">Bitrate</string>
+  <string name="fps">FPS</string>
+  <string name="orientation">Orientation</string>
+  <string name="hardware_rotation">Hardware rotation</string>
+  <string name="samplerate">SampleRate</string>
+  <string name="channel">Channel</string>
+  <string name="stereo">Stereo</string>
+  <string name="mono">Mono</string>
+  <string name="echo_canceler">Echo canceler</string>
+  <string name="camera">Camera</string>
+  <string name="auth">Auth</string>
+  <string name="user">User</string>
+  <string name="password">Password</string>
+  <string name="noise_suppressor">Noise suppressor</string>
+</resources>

+ 20 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,20 @@
+<resources>
+
+  <style name="AppTheme"
+      parent="Theme.AppCompat.Light.DarkActionBar">
+    <item name="colorPrimary">@color/appColor</item>
+    <item name="colorPrimaryDark">@color/appColor</item>
+    <item name="colorAccent">@color/appColor</item>
+    <item name="android:buttonStyle">@style/buttonStyle</item>
+    <item name="buttonStyle">@style/buttonStyle</item>
+  </style>
+
+  <style name="buttonStyle"
+      parent="android:style/Widget.Button">
+    <item name="android:background">@xml/button_style</item>
+    <item name="android:padding">8dp</item>
+    <item name="android:textColor">@color/white</item>
+    <item name="android:textStyle">bold</item>
+  </style>
+</resources>
+

+ 11 - 0
app/src/main/res/xml/button_style.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle"
+    >
+  <solid android:color="@color/appColor"/>
+  <stroke
+      android:width="0.5dp"
+      android:color="#000000"
+      />
+  <corners android:radius="2dp"/>
+</shape>

+ 5 - 0
app/src/main/res/xml/edit_text_auth.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="180dp"
+    android:layout_height="wrap_content"
+    />

+ 7 - 0
app/src/main/res/xml/edit_text_int.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--inpuType="number" not used because keyboard can jump to other view and crash-->
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="100dp"
+    android:layout_height="wrap_content"
+    android:digits="1234567890"
+    />

+ 29 - 0
app/src/main/res/xml/options_header.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="160dp"
+    android:orientation="vertical"
+    android:background="@color/appColor"
+    >
+
+  <ImageView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:padding="20dp"
+      android:layout_gravity="center"
+      app:srcCompat="@mipmap/ic_launcher"
+      android:id="@+id/imageView"
+      />
+
+  <TextView
+      android:text="@string/drawer_title"
+      android:textSize="22sp"
+      android:textStyle="bold"
+      android:textColor="@color/white"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="center"
+      />
+
+</LinearLayout>

+ 21 - 0
app/src/main/res/xml/radio_group_audio.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingTop="10dp"
+    android:checkedButton="@+id/rb_stereo"
+    >
+  <RadioButton
+      android:text="@string/stereo"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:id="@+id/rb_stereo"
+      />
+  <RadioButton
+      android:text="@string/mono"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:id="@+id/rb_mono"
+      />
+</RadioGroup>

+ 35 - 0
build.gradle

@@ -0,0 +1,35 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+  ext.kotlin_version = '1.4.10'
+  repositories {
+    jcenter()
+    google()
+    mavenCentral()
+  }
+  dependencies {
+    // Check that you have the Google Services Gradle plugin v4.3.2 or later
+    // (if not, add it).
+    classpath 'com.google.gms:google-services:4.3.4'
+    // Add the Crashlytics Gradle plugin.
+    classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
+
+    classpath 'com.android.tools.build:gradle:4.1.1'
+    classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
+    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    // NOTE: Do not place your application dependencies here; they belong
+    // in the individual module build.gradle files
+  }
+}
+
+allprojects {
+  repositories {
+    jcenter()
+    google()
+    mavenCentral()
+  }
+}
+
+task clean(type: Delete) {
+  delete rootProject.buildDir
+}

+ 1 - 0
encoder/.gitignore

@@ -0,0 +1 @@
+/build

+ 24 - 0
encoder/build.gradle

@@ -0,0 +1,24 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.github.dcendents.android-maven'
+group = 'com.github.pedroSG94'
+
+android {
+  compileSdkVersion 30
+
+  defaultConfig {
+    minSdkVersion 16
+    targetSdkVersion 30
+    versionCode 195
+    versionName "1.9.5"
+  }
+  buildTypes {
+    release {
+      minifyEnabled false
+      consumerProguardFiles 'proguard-rules.pro'
+    }
+  }
+}
+
+dependencies {
+  api 'androidx.annotation:annotation:1.1.0'
+}

+ 17 - 0
encoder/proguard-rules.pro

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/pedro/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 1 - 0
encoder/src/main/AndroidManifest.xml

@@ -0,0 +1 @@
+<manifest package="com.pedro.encoder" />

+ 235 - 0
encoder/src/main/java/com/pedro/encoder/BaseEncoder.java

@@ -0,0 +1,235 @@
+package com.pedro.encoder;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import com.pedro.encoder.utils.CodecUtil;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Created by pedro on 18/09/19.
+ */
+public abstract class BaseEncoder implements EncoderCallback {
+
+  private static final String TAG = "BaseEncoder";
+  private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+  private HandlerThread handlerThread;
+  protected BlockingQueue<Frame> queue = new ArrayBlockingQueue<>(80);
+  protected MediaCodec codec;
+  protected long presentTimeUs;
+  protected volatile boolean running = false;
+  protected boolean isBufferMode = true;
+  protected CodecUtil.Force force = CodecUtil.Force.FIRST_COMPATIBLE_FOUND;
+  private MediaCodec.Callback callback;
+  private long oldTimeStamp = 0L;
+
+  public void restart() {
+    start(false);
+    initCodec();
+  }
+
+  public void start() {
+    start(true);
+    initCodec();
+  }
+
+  private void initCodec() {
+    handlerThread = new HandlerThread(TAG);
+    handlerThread.start();
+    Handler handler = new Handler(handlerThread.getLooper());
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+      createAsyncCallback();
+      codec.setCallback(callback, handler);
+      codec.start();
+    } else {
+      codec.start();
+      handler.post(new Runnable() {
+        @Override
+        public void run() {
+          while (running) {
+            try {
+              getDataFromEncoder();
+            } catch (IllegalStateException e) {
+              Log.i(TAG, "Encoding error", e);
+            }
+          }
+        }
+      });
+    }
+    running = true;
+  }
+
+  public abstract void start(boolean resetTs);
+
+  protected abstract void stopImp();
+
+  protected void fixTimeStamp(MediaCodec.BufferInfo info) {
+    if (oldTimeStamp > info.presentationTimeUs) {
+      info.presentationTimeUs = oldTimeStamp;
+    } else {
+      oldTimeStamp = info.presentationTimeUs;
+    }
+  }
+
+  public void stop() {
+    running = false;
+    stopImp();
+    if (handlerThread != null) {
+      if (handlerThread.getLooper() != null) {
+        if (handlerThread.getLooper().getThread() != null) {
+          handlerThread.getLooper().getThread().interrupt();
+        }
+        handlerThread.getLooper().quit();
+      }
+      handlerThread.quit();
+      if (codec != null) {
+        try {
+          codec.flush();
+        } catch (IllegalStateException ignored) { }
+      }
+      //wait for thread to die for 500ms.
+      try {
+        handlerThread.getLooper().getThread().join(500);
+      } catch (Exception ignored) { }
+    }
+    queue.clear();
+    queue = new ArrayBlockingQueue<>(80);
+    try {
+      codec.stop();
+      codec.release();
+      codec = null;
+    } catch (IllegalStateException | NullPointerException e) {
+      codec = null;
+    }
+    oldTimeStamp = 0L;
+  }
+
+  protected abstract MediaCodecInfo chooseEncoder(String mime);
+
+  protected void getDataFromEncoder() throws IllegalStateException {
+    if (isBufferMode) {
+      int inBufferIndex = codec.dequeueInputBuffer(0);
+      if (inBufferIndex >= 0) {
+        inputAvailable(codec, inBufferIndex);
+      }
+    }
+    while (running) {
+      int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
+      if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+        MediaFormat mediaFormat = codec.getOutputFormat();
+        formatChanged(codec, mediaFormat);
+      } else if (outBufferIndex >= 0) {
+        outputAvailable(codec, outBufferIndex, bufferInfo);
+      } else {
+        break;
+      }
+    }
+  }
+
+  protected abstract Frame getInputFrame() throws InterruptedException;
+
+  private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
+      int inBufferIndex) throws IllegalStateException {
+    try {
+      Frame frame = getInputFrame();
+      while (frame == null) frame = getInputFrame();
+      byteBuffer.clear();
+      int size = Math.max(frame.getSize(), 0);
+      byteBuffer.put(frame.getBuffer(), frame.getOffset(), size);
+      long pts = System.nanoTime() / 1000 - presentTimeUs;
+      mediaCodec.queueInputBuffer(inBufferIndex, 0, size, pts, 0);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    } catch (NullPointerException e) {
+      Log.i(TAG, "Encoding error", e);
+    }
+  }
+
+  protected abstract void checkBuffer(@NonNull ByteBuffer byteBuffer,
+      @NonNull MediaCodec.BufferInfo bufferInfo);
+
+  protected abstract void sendBuffer(@NonNull ByteBuffer byteBuffer,
+      @NonNull MediaCodec.BufferInfo bufferInfo);
+
+  private void processOutput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
+      int outBufferIndex, @NonNull MediaCodec.BufferInfo bufferInfo) throws IllegalStateException {
+    checkBuffer(byteBuffer, bufferInfo);
+    sendBuffer(byteBuffer, bufferInfo);
+    mediaCodec.releaseOutputBuffer(outBufferIndex, false);
+  }
+
+  public void setForce(CodecUtil.Force force) {
+    this.force = force;
+  }
+
+  public boolean isRunning() {
+    return running;
+  }
+
+  @Override
+  public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
+      throws IllegalStateException {
+    ByteBuffer byteBuffer;
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      byteBuffer = mediaCodec.getInputBuffer(inBufferIndex);
+    } else {
+      byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex];
+    }
+    processInput(byteBuffer, mediaCodec, inBufferIndex);
+  }
+
+  @Override
+  public void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
+      @NonNull MediaCodec.BufferInfo bufferInfo) throws IllegalStateException {
+    ByteBuffer byteBuffer;
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      byteBuffer = mediaCodec.getOutputBuffer(outBufferIndex);
+    } else {
+      byteBuffer = mediaCodec.getOutputBuffers()[outBufferIndex];
+    }
+    processOutput(byteBuffer, mediaCodec, outBufferIndex, bufferInfo);
+  }
+
+  @RequiresApi(api = Build.VERSION_CODES.M)
+  private void createAsyncCallback() {
+    callback = new MediaCodec.Callback() {
+      @Override
+      public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex) {
+        try {
+          inputAvailable(mediaCodec, inBufferIndex);
+        } catch (IllegalStateException e) {
+          Log.i(TAG, "Encoding error", e);
+        }
+      }
+
+      @Override
+      public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
+          @NonNull MediaCodec.BufferInfo bufferInfo) {
+        try {
+          outputAvailable(mediaCodec, outBufferIndex, bufferInfo);
+        } catch (IllegalStateException e) {
+          Log.i(TAG, "Encoding error", e);
+        }
+      }
+
+      @Override
+      public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
+        Log.e(TAG, "Error", e);
+      }
+
+      @Override
+      public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec,
+          @NonNull MediaFormat mediaFormat) {
+        formatChanged(mediaCodec, mediaFormat);
+      }
+    };
+  }
+}

+ 18 - 0
encoder/src/main/java/com/pedro/encoder/EncoderCallback.java

@@ -0,0 +1,18 @@
+package com.pedro.encoder;
+
+import android.media.MediaCodec;
+import android.media.MediaFormat;
+import androidx.annotation.NonNull;
+
+/**
+ * Created by pedro on 18/09/19.
+ */
+public interface EncoderCallback {
+  void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
+      throws IllegalStateException;
+
+  void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
+      @NonNull MediaCodec.BufferInfo bufferInfo) throws IllegalStateException;
+
+  void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat);
+}

+ 86 - 0
encoder/src/main/java/com/pedro/encoder/Frame.java

@@ -0,0 +1,86 @@
+package com.pedro.encoder;
+
+import android.graphics.ImageFormat;
+
+/**
+ * Created by pedro on 17/02/18.
+ */
+
+public class Frame {
+
+  private byte[] buffer;
+  private int offset;
+  private int size;
+  private int orientation;
+  private boolean flip;
+  private int format = ImageFormat.NV21; //nv21 or yv12 supported
+
+  /**
+   * Used with video frame
+   */
+  public Frame(byte[] buffer, int orientation, boolean flip, int format) {
+    this.buffer = buffer;
+    this.orientation = orientation;
+    this.flip = flip;
+    this.format = format;
+    offset = 0;
+    size = buffer.length;
+  }
+
+  /**
+   * Used with audio frame
+   */
+  public Frame(byte[] buffer, int offset, int size) {
+    this.buffer = buffer;
+    this.offset = offset;
+    this.size = size;
+  }
+
+  public byte[] getBuffer() {
+    return buffer;
+  }
+
+  public void setBuffer(byte[] buffer) {
+    this.buffer = buffer;
+  }
+
+  public int getOrientation() {
+    return orientation;
+  }
+
+  public void setOrientation(int orientation) {
+    this.orientation = orientation;
+  }
+
+  public boolean isFlip() {
+    return flip;
+  }
+
+  public void setFlip(boolean flip) {
+    this.flip = flip;
+  }
+
+  public int getFormat() {
+    return format;
+  }
+
+  public void setFormat(int format) {
+    this.format = format;
+  }
+
+  public int getOffset() {
+    return offset;
+  }
+
+  public void setOffset(int offset) {
+    this.offset = offset;
+  }
+
+  public int getSize() {
+    return size;
+  }
+
+  public void setSize(int size) {
+    this.size = size;
+  }
+}

+ 5 - 0
encoder/src/main/java/com/pedro/encoder/GetFrame.java

@@ -0,0 +1,5 @@
+package com.pedro.encoder;
+
+public interface GetFrame {
+  Frame getInputFrame();
+}

+ 171 - 0
encoder/src/main/java/com/pedro/encoder/audio/AudioEncoder.java

@@ -0,0 +1,171 @@
+package com.pedro.encoder.audio;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import com.pedro.encoder.BaseEncoder;
+import com.pedro.encoder.Frame;
+import com.pedro.encoder.GetFrame;
+import com.pedro.encoder.input.audio.GetMicrophoneData;
+import com.pedro.encoder.utils.CodecUtil;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by pedro on 19/01/17.
+ *
+ * Encode PCM audio data to ACC and return in a callback
+ */
+
+public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
+
+  private static final String TAG = "AudioEncoder";
+  private GetAacData getAacData;
+  private int bitRate = 64 * 1024;  //in kbps
+  private int sampleRate = 32000; //in hz
+  private int maxInputSize = 0;
+  private boolean isStereo = true;
+  private GetFrame getFrame;
+
+  public AudioEncoder(GetAacData getAacData) {
+    this.getAacData = getAacData;
+  }
+
+  /**
+   * Prepare encoder with custom parameters
+   */
+  public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo,
+      int maxInputSize) {
+    this.bitRate = bitRate;
+    this.sampleRate = sampleRate;
+    this.maxInputSize = maxInputSize;
+    this.isStereo = isStereo;
+    isBufferMode = true;
+    try {
+      MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
+      if (encoder != null) {
+        codec = MediaCodec.createByCodecName(encoder.getName());
+      } else {
+        Log.e(TAG, "Valid encoder not found");
+        return false;
+      }
+
+      int channelCount = (isStereo) ? 2 : 1;
+      MediaFormat audioFormat =
+          MediaFormat.createAudioFormat(CodecUtil.AAC_MIME, sampleRate, channelCount);
+      audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+      audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
+      audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
+          MediaCodecInfo.CodecProfileLevel.AACObjectLC);
+      codec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+      running = false;
+      Log.i(TAG, "prepared");
+      return true;
+    } catch (IOException | IllegalStateException e) {
+      Log.e(TAG, "Create AudioEncoder failed.", e);
+      return false;
+    }
+  }
+
+  public void setGetFrame(GetFrame getFrame) {
+    this.getFrame = getFrame;
+  }
+
+  /**
+   * Prepare encoder with default parameters
+   */
+  public boolean prepareAudioEncoder() {
+    return prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
+  }
+
+  @Override
+  public void start(boolean resetTs) {
+    if (resetTs) {
+      presentTimeUs = System.nanoTime() / 1000;
+    }
+    Log.i(TAG, "started");
+  }
+
+  @Override
+  protected void stopImp() {
+    Log.i(TAG, "stopped");
+  }
+
+  public void reset() {
+    stop();
+    prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
+    restart();
+  }
+
+  @Override
+  protected Frame getInputFrame() throws InterruptedException {
+    return getFrame != null ? getFrame.getInputFrame() : queue.take();
+  }
+
+  @Override
+  protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
+      @NonNull MediaCodec.BufferInfo bufferInfo) {
+    fixTimeStamp(bufferInfo);
+  }
+
+  @Override
+  protected void sendBuffer(@NonNull ByteBuffer byteBuffer,
+      @NonNull MediaCodec.BufferInfo bufferInfo) {
+    getAacData.getAacData(byteBuffer, bufferInfo);
+  }
+
+  /**
+   * Set custom PCM data.
+   * Use it after prepareAudioEncoder(int sampleRate, int channel).
+   * Used too with microphone.
+   */
+  @Override
+  public void inputPCMData(Frame frame) {
+    if (running && !queue.offer(frame)) {
+      Log.i(TAG, "frame discarded");
+    }
+  }
+
+  @Override
+  protected MediaCodecInfo chooseEncoder(String mime) {
+    List<MediaCodecInfo> encoders = new ArrayList<>();
+    if (force == CodecUtil.Force.HARDWARE) {
+      encoders = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
+    } else if (force == CodecUtil.Force.SOFTWARE) {
+      encoders = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
+    }
+
+    if (force == CodecUtil.Force.FIRST_COMPATIBLE_FOUND) {
+      List<MediaCodecInfo> mediaCodecInfoList = CodecUtil.getAllEncoders(mime);
+      for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
+        String name = mediaCodecInfo.getName().toLowerCase();
+        if (!name.contains("omx.google")) return mediaCodecInfo;
+      }
+      if (mediaCodecInfoList.size() > 0) {
+        return mediaCodecInfoList.get(0);
+      } else {
+        return null;
+      }
+    } else {
+      if (encoders.isEmpty()) {
+        return null;
+      } else {
+        return encoders.get(0);
+      }
+    }
+  }
+
+  public void setSampleRate(int sampleRate) {
+    this.sampleRate = sampleRate;
+  }
+
+  @Override
+  public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
+    getAacData.onAudioFormat(mediaFormat);
+  }
+}

+ 17 - 0
encoder/src/main/java/com/pedro/encoder/audio/GetAacData.java

@@ -0,0 +1,17 @@
+package com.pedro.encoder.audio;
+
+import android.media.MediaCodec;
+
+import android.media.MediaFormat;
+import java.nio.ByteBuffer;
+
+/**
+ * Created by pedro on 19/01/17.
+ */
+
+public interface GetAacData {
+
+  void getAacData(ByteBuffer aacBuffer, MediaCodec.BufferInfo info);
+
+  void onAudioFormat(MediaFormat mediaFormat);
+}

+ 84 - 0
encoder/src/main/java/com/pedro/encoder/input/audio/AudioPostProcessEffect.java

@@ -0,0 +1,84 @@
+package com.pedro.encoder.input.audio;
+
+import android.media.audiofx.AcousticEchoCanceler;
+import android.media.audiofx.AutomaticGainControl;
+import android.media.audiofx.NoiseSuppressor;
+import android.util.Log;
+
+/**
+ * Created by pedro on 11/05/17.
+ */
+
+public class AudioPostProcessEffect {
+
+  private final String TAG = "AudioPostProcessEffect";
+
+  private int microphoneId;
+  private AcousticEchoCanceler acousticEchoCanceler = null;
+  private AutomaticGainControl automaticGainControl = null;
+  private NoiseSuppressor noiseSuppressor = null;
+
+  public AudioPostProcessEffect(int microphoneId) {
+    this.microphoneId = microphoneId;
+  }
+
+  public void enableAutoGainControl() {
+    if (AutomaticGainControl.isAvailable() && automaticGainControl == null) {
+      automaticGainControl = AutomaticGainControl.create(microphoneId);
+      if (automaticGainControl != null) {
+        automaticGainControl.setEnabled(true);
+        Log.i(TAG, "AutoGainControl enabled");
+        return;
+      }
+    }
+    Log.e(TAG, "This device does't implement AutoGainControl");
+  }
+
+  public void releaseAutoGainControl() {
+    if (automaticGainControl != null) {
+      automaticGainControl.setEnabled(false);
+      automaticGainControl.release();
+      automaticGainControl = null;
+    }
+  }
+
+  public void enableEchoCanceler() {
+    if (AcousticEchoCanceler.isAvailable() && acousticEchoCanceler == null) {
+      acousticEchoCanceler = AcousticEchoCanceler.create(microphoneId);
+      if (acousticEchoCanceler != null) {
+        acousticEchoCanceler.setEnabled(true);
+        Log.i(TAG, "EchoCanceler enabled");
+        return;
+      }
+    }
+    Log.e(TAG, "This device does't implement EchoCanceler");
+  }
+
+  public void releaseEchoCanceler() {
+    if (acousticEchoCanceler != null) {
+      acousticEchoCanceler.setEnabled(false);
+      acousticEchoCanceler.release();
+      acousticEchoCanceler = null;
+    }
+  }
+
+  public void enableNoiseSuppressor() {
+    if (NoiseSuppressor.isAvailable() && noiseSuppressor == null) {
+      noiseSuppressor = NoiseSuppressor.create(microphoneId);
+      if (noiseSuppressor != null) {
+        noiseSuppressor.setEnabled(true);
+        Log.i(TAG, "NoiseSuppressor enabled");
+        return;
+      }
+    }
+    Log.e(TAG, "This device does't implement NoiseSuppressor");
+  }
+
+  public void releaseNoiseSuppressor() {
+    if (noiseSuppressor != null) {
+      noiseSuppressor.setEnabled(false);
+      noiseSuppressor.release();
+      noiseSuppressor = null;
+    }
+  }
+}

+ 10 - 0
encoder/src/main/java/com/pedro/encoder/input/audio/CustomAudioEffect.java

@@ -0,0 +1,10 @@
+package com.pedro.encoder.input.audio;
+
+public abstract class CustomAudioEffect {
+
+  /**
+   * @param pcmBuffer buffer obtained directly from the microphone.
+   * @return it must be of same size that pcmBuffer parameter.
+   */
+  public abstract byte[] process(byte[] pcmBuffer);
+}

+ 0 - 0
encoder/src/main/java/com/pedro/encoder/input/audio/GetMicrophoneData.java


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff