├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── step3web ├── css │ └── main.css ├── package.json ├── index.html ├── .gitignore ├── index.js └── js │ └── main.js ├── step4web ├── css │ └── main.css ├── package.json ├── index.html ├── .gitignore ├── index.js └── js │ └── main.js ├── step1camera ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── cc │ │ │ └── rome753 │ │ │ └── wat │ │ │ └── MainActivity.java │ ├── test │ │ └── java │ │ │ └── cc │ │ │ └── rome753 │ │ │ └── wat │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── cc │ │ └── rome753 │ │ └── wat │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── step2loopback ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── cc │ │ │ │ └── rome753 │ │ │ │ └── wat │ │ │ │ ├── SdpAdapter.java │ │ │ │ ├── PeerConnectionAdapter.java │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── cc │ │ │ └── rome753 │ │ │ └── wat │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── cc │ │ └── rome753 │ │ └── wat │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── step5opengl ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── cc │ │ │ │ └── rome753 │ │ │ │ └── wat │ │ │ │ ├── App.kt │ │ │ │ ├── YUVRender.kt │ │ │ │ ├── egl │ │ │ │ ├── EglThread.java │ │ │ │ └── EglCore.java │ │ │ │ ├── ImageBytes.java │ │ │ │ ├── EglProcessor.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── ShaderUtils.java │ │ │ │ └── YUVShader.kt │ │ ├── assets │ │ │ ├── shader_yuv_v.glsl │ │ │ └── shader_yuv_f.glsl │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── cc │ │ │ └── rome753 │ │ │ └── wat │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── cc │ │ └── rome753 │ │ └── wat │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── step3signaling ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── cc │ │ │ │ └── rome753 │ │ │ │ └── wat │ │ │ │ ├── SdpAdapter.java │ │ │ │ ├── PeerConnectionAdapter.java │ │ │ │ ├── SignalingClient.java │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── cc │ │ │ └── rome753 │ │ │ └── wat │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── cc │ │ └── rome753 │ │ └── wat │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── step4multipeers ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── cc │ │ │ │ └── rome753 │ │ │ │ └── wat │ │ │ │ ├── SdpAdapter.java │ │ │ │ ├── PeerConnectionAdapter.java │ │ │ │ ├── SignalingClient.java │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── cc │ │ │ └── rome753 │ │ │ └── wat │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── cc │ │ └── rome753 │ │ └── wat │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── .idea ├── vcs.xml ├── compiler.xml ├── misc.xml └── jarRepositories.xml ├── README.md ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':step1camera', ':step2loopback', 'step3signaling', 'step4multipeers', 'step5opengl' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /step3web/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | video { 6 | max-width: 100%; 7 | width: 320px; 8 | } 9 | -------------------------------------------------------------------------------- /step4web/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | video { 6 | max-width: 100%; 7 | width: 320px; 8 | } 9 | -------------------------------------------------------------------------------- /step1camera/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /step1camera/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | webrtc-android-tutorial 3 | 4 | -------------------------------------------------------------------------------- /step2loopback/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | webrtc-android-tutorial 3 | 4 | -------------------------------------------------------------------------------- /step5opengl/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /step5opengl/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | webrtc-android-tutorial 3 | 4 | -------------------------------------------------------------------------------- /step3signaling/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | webrtc-android-tutorial 3 | 4 | -------------------------------------------------------------------------------- /step4multipeers/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | webrtc-android-tutorial 3 | 4 | -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step1camera/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step2loopback/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step5opengl/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step3signaling/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rome753/webrtc-android-tutorial/HEAD/step4multipeers/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /step3web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc-codelab", 3 | "version": "0.0.1", 4 | "description": "WebRTC codelab", 5 | "dependencies": { 6 | "node-static": "^0.7.10", 7 | "socket.io": "^2.0.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /step4web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc-codelab", 3 | "version": "0.0.1", 4 | "description": "WebRTC codelab", 5 | "dependencies": { 6 | "node-static": "^0.7.10", 7 | "socket.io": "^2.0.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /step1camera/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /step5opengl/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 12 20:19:15 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 7 | -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/App.kt: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat 2 | 3 | import android.app.Application 4 | 5 | class App : Application() { 6 | 7 | override fun onCreate() { 8 | super.onCreate() 9 | app = this 10 | } 11 | 12 | companion object { 13 | var app : App? = null 14 | } 15 | } -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step1camera/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step5opengl/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webrtc-android-tutorial 2 | The simplest tutorial for android WebRTC, based on [Google official tutorial(Javascript)](https://codelabs.developers.google.com/codelabs/webrtc-web/#0). 3 | 4 | A packaging of the webrtc library. 5 | https://mvnrepository.com/artifact/io.github.100mslive/webrtc 6 | 7 | [中文文档](https://www.jianshu.com/p/eb5fd116e6c8) 8 | -------------------------------------------------------------------------------- /step1camera/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /step5opengl/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /step1camera/src/test/java/cc/rome753/wat/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /step2loopback/src/test/java/cc/rome753/wat/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /step3signaling/src/test/java/cc/rome753/wat/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /step4multipeers/src/test/java/cc/rome753/wat/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /step5opengl/src/test/java/cc/rome753/wat/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /step5opengl/src/main/assets/shader_yuv_v.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | layout (location = 0) in vec3 vPosition; 3 | layout (location = 1) in vec3 vColor; 4 | layout (location = 2) in vec2 vTexCoord; 5 | out vec3 aColor; 6 | out vec2 aTexCoord; 7 | 8 | uniform mat4 transform; 9 | 10 | void main() { 11 | gl_Position = vec4(vPosition, 1.0f); 12 | aColor = vColor; 13 | aTexCoord = (transform * vec4(vTexCoord, 1.0f, 1.0f)).xy; 14 | 15 | // aTexCoord.y = 1.0f - aTexCoord.y; 16 | // aTexCoord = vTexCoord.xy; 17 | } -------------------------------------------------------------------------------- /step3web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Realtime communication with WebRTC 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Realtime communication with WebRTC

15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /step4web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Realtime communication with WebRTC 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Realtime communication with WebRTC

15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /step1camera/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /step5opengl/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /step2loopback/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /step3signaling/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /step4multipeers/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /step1camera/src/androidTest/java/cc/rome753/wat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cc.rome753.wat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /step2loopback/src/androidTest/java/cc/rome753/wat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cc.rome753.wat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /step5opengl/src/androidTest/java/cc/rome753/wat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cc.rome753.wat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /step3signaling/src/androidTest/java/cc/rome753/wat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cc.rome753.wat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /step4multipeers/src/androidTest/java/cc/rome753/wat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cc.rome753.wat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /step1camera/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /step5opengl/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /step3web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | */node_modules 33 | jspm_packages 34 | package-lock.json 35 | .DS_Store 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Optional eslint cache 41 | .eslintcache 42 | 43 | # Optional REPL history 44 | .node_repl_history 45 | 46 | # Output of 'npm pack' 47 | *.tgz 48 | 49 | # Yarn Integrity file 50 | .yarn-integrity 51 | 52 | cert.pem 53 | key.pem 54 | -------------------------------------------------------------------------------- /step4web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | */node_modules 33 | jspm_packages 34 | package-lock.json 35 | .DS_Store 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Optional eslint cache 41 | .eslintcache 42 | 43 | # Optional REPL history 44 | .node_repl_history 45 | 46 | # Output of 'npm pack' 47 | *.tgz 48 | 49 | # Yarn Integrity file 50 | .yarn-integrity 51 | 52 | cert.pem 53 | key.pem 54 | -------------------------------------------------------------------------------- /step2loopback/src/main/java/cc/rome753/wat/SdpAdapter.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.util.Log; 4 | 5 | import org.webrtc.SdpObserver; 6 | import org.webrtc.SessionDescription; 7 | 8 | /** 9 | * Created by chao on 2019/1/29. 10 | */ 11 | 12 | public class SdpAdapter implements SdpObserver { 13 | 14 | 15 | private String tag; 16 | 17 | public SdpAdapter(String tag) { 18 | this.tag = "chao " + tag; 19 | } 20 | 21 | private void log(String s) { 22 | Log.d(tag, s); 23 | } 24 | 25 | @Override 26 | public void onCreateSuccess(SessionDescription sessionDescription) { 27 | log("onCreateSuccess " + sessionDescription); 28 | } 29 | 30 | @Override 31 | public void onSetSuccess() { 32 | log("onSetSuccess "); 33 | } 34 | 35 | @Override 36 | public void onCreateFailure(String s) { 37 | log("onCreateFailure " + s); 38 | } 39 | 40 | @Override 41 | public void onSetFailure(String s) { 42 | log("onSetFailure " + s); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /step3signaling/src/main/java/cc/rome753/wat/SdpAdapter.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.util.Log; 4 | 5 | import org.webrtc.SdpObserver; 6 | import org.webrtc.SessionDescription; 7 | 8 | /** 9 | * Created by chao on 2019/1/29. 10 | */ 11 | 12 | public class SdpAdapter implements SdpObserver { 13 | 14 | 15 | private String tag; 16 | 17 | public SdpAdapter(String tag) { 18 | this.tag = "chao " + tag; 19 | } 20 | 21 | private void log(String s) { 22 | Log.d(tag, s); 23 | } 24 | 25 | @Override 26 | public void onCreateSuccess(SessionDescription sessionDescription) { 27 | log("onCreateSuccess " + sessionDescription); 28 | } 29 | 30 | @Override 31 | public void onSetSuccess() { 32 | log("onSetSuccess "); 33 | } 34 | 35 | @Override 36 | public void onCreateFailure(String s) { 37 | log("onCreateFailure " + s); 38 | } 39 | 40 | @Override 41 | public void onSetFailure(String s) { 42 | log("onSetFailure " + s); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /step4multipeers/src/main/java/cc/rome753/wat/SdpAdapter.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.util.Log; 4 | 5 | import org.webrtc.SdpObserver; 6 | import org.webrtc.SessionDescription; 7 | 8 | /** 9 | * Created by chao on 2019/1/29. 10 | */ 11 | 12 | public class SdpAdapter implements SdpObserver { 13 | 14 | 15 | private String tag; 16 | 17 | public SdpAdapter(String tag) { 18 | this.tag = "chao " + tag; 19 | } 20 | 21 | private void log(String s) { 22 | Log.d(tag, s); 23 | } 24 | 25 | @Override 26 | public void onCreateSuccess(SessionDescription sessionDescription) { 27 | log("onCreateSuccess " + sessionDescription); 28 | } 29 | 30 | @Override 31 | public void onSetSuccess() { 32 | log("onSetSuccess "); 33 | } 34 | 35 | @Override 36 | public void onCreateFailure(String s) { 37 | log("onCreateFailure " + s); 38 | } 39 | 40 | @Override 41 | public void onSetFailure(String s) { 42 | log("onSetFailure " + s); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/YUVRender.kt: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat 2 | 3 | import android.media.Image 4 | import android.opengl.GLES20 5 | import android.opengl.GLSurfaceView 6 | import javax.microedition.khronos.egl.EGLConfig 7 | import javax.microedition.khronos.opengles.GL10 8 | 9 | class YUVRender: GLSurfaceView.Renderer { 10 | 11 | var yuvShader: YUVShader? = null 12 | // var yuvShader = DiffShader() 13 | var imageBytes: ImageBytes? = null 14 | 15 | override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { 16 | if (yuvShader == null) { 17 | yuvShader = YUVShader() 18 | yuvShader!!.init() 19 | GLES20.glClearColor(0.5f, 0.5f, 0.5f, 0.5f) 20 | } 21 | } 22 | 23 | override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) { 24 | GLES20.glViewport(0, 0, width, height) 25 | } 26 | 27 | override fun onDrawFrame(gl: GL10?) { 28 | imageBytes?.let { 29 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 30 | yuvShader!!.draw(it) 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/egl/EglThread.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat.egl; 2 | 3 | import android.os.Handler; 4 | import android.os.HandlerThread; 5 | 6 | public class EglThread { 7 | Handler mHandler; 8 | EglCore mEglCore; 9 | 10 | public EglThread(String name) { 11 | HandlerThread ht = new HandlerThread(name); 12 | ht.start(); 13 | 14 | mHandler = new Handler(ht.getLooper()); 15 | } 16 | 17 | public void initEglCore(int w, int h) { 18 | if (mEglCore == null) { 19 | mHandler.post(() -> { 20 | mEglCore = new EglCore(); 21 | mEglCore.makeCurrent(w, h); 22 | }); 23 | } 24 | } 25 | 26 | public void post(Runnable r) { 27 | if (mHandler != null) { 28 | mHandler.post(r); 29 | } 30 | } 31 | 32 | 33 | public void destroy() { 34 | if (mHandler != null) { 35 | mHandler.post(() -> { 36 | mEglCore.release(); 37 | mHandler.getLooper().quitSafely(); 38 | }); 39 | } 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /step1camera/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | # Uncomment the following line if you do not want to check your keystore files in. 41 | #*.jks 42 | 43 | # External native build folder generated in Android Studio 2.2 and later 44 | .externalNativeBuild 45 | 46 | # Google Services (e.g. APIs or Firebase) 47 | google-services.json 48 | 49 | # Freeline 50 | freeline.py 51 | freeline/ 52 | freeline_project_description.json 53 | 54 | # fastlane 55 | fastlane/report.xml 56 | fastlane/Preview.html 57 | fastlane/screenshots 58 | fastlane/test_output 59 | fastlane/readme.md 60 | -------------------------------------------------------------------------------- /step5opengl/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /step5opengl/src/main/assets/shader_yuv_f.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision mediump float; 4 | in vec3 aColor; 5 | in vec2 aTexCoord; 6 | out vec4 fragColor; 7 | 8 | uniform sampler2D tex0; 9 | uniform sampler2D tex1; 10 | uniform sampler2D tex2; 11 | 12 | void main() { 13 | 14 | float y = texture(tex0, aTexCoord).r; 15 | float u = texture(tex1, aTexCoord).r - 0.5; 16 | float v = texture(tex1, aTexCoord).a - 0.5; 17 | 18 | // float y = texture(tex0, aTexCoord).r; 19 | // float u = texture(tex2, aTexCoord).r - 0.5; 20 | // float v = texture(tex1, aTexCoord).r - 0.5; 21 | 22 | vec3 yuv = vec3(y, v, u); 23 | 24 | // 下面两种视觉上差异不大 25 | 26 | // // BT.601, which is the standard for SDTV is provided as a reference 27 | // vec3 rgb = mat3( 1, 1, 1, 28 | // 0, -.39465, 2.03211, 29 | // 1.13983, -.58060, 0) * yuv; 30 | 31 | // Using BT.709 which is the standard for HDTV 32 | vec3 rgb = mat3( 1, 1, 1, 33 | 0, -.21482, 2.12798, 34 | 1.28033, -.38059, 0) * yuv; 35 | 36 | 37 | // fragColor = vec4(rgb, 1.0); 38 | fragColor = 1.0f - vec4(rgb, 1.0); 39 | 40 | } -------------------------------------------------------------------------------- /step1camera/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdk 34 5 | defaultConfig { 6 | applicationId "cc.rome753.wat" 7 | minSdkVersion 21 8 | targetSdkVersion 34 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_11 21 | targetCompatibility JavaVersion.VERSION_11 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'androidx.appcompat:appcompat:1.1.0' 28 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'androidx.test:runner:1.2.0' 31 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 32 | 33 | implementation 'io.github.100mslive:webrtc:m94-4606' 34 | } 35 | -------------------------------------------------------------------------------- /step2loopback/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdk 34 5 | defaultConfig { 6 | applicationId "cc.rome753.wat" 7 | minSdkVersion 21 8 | targetSdkVersion 34 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_11 21 | targetCompatibility JavaVersion.VERSION_11 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'androidx.appcompat:appcompat:1.1.0' 28 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'androidx.test:runner:1.2.0' 31 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 32 | 33 | implementation 'io.github.100mslive:webrtc:m94-4606' 34 | } 35 | -------------------------------------------------------------------------------- /step2loopback/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /step3signaling/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /step4multipeers/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /step5opengl/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'org.jetbrains.kotlin.android' 3 | 4 | android { 5 | compileSdk 34 6 | defaultConfig { 7 | applicationId "cc.rome753.wat" 8 | minSdkVersion 21 9 | targetSdkVersion 34 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation 'androidx.appcompat:appcompat:1.1.0' 29 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test:runner:1.2.0' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 34 | 35 | implementation 'io.github.100mslive:webrtc:m94-4606' 36 | } 37 | -------------------------------------------------------------------------------- /step3signaling/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdk 34 5 | defaultConfig { 6 | applicationId "cc.rome753.wat" 7 | minSdkVersion 21 8 | targetSdkVersion 34 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_8 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'androidx.appcompat:appcompat:1.1.0' 28 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'androidx.test:runner:1.2.0' 31 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 32 | 33 | implementation 'io.github.100mslive:webrtc:m94-4606' 34 | implementation('io.socket:socket.io-client:0.8.3') { 35 | // excluding org.json which is provided by Android 36 | exclude group: 'org.json', module: 'json' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /step4multipeers/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdk 34 5 | defaultConfig { 6 | applicationId "cc.rome753.wat" 7 | minSdkVersion 21 8 | targetSdkVersion 34 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_8 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'androidx.appcompat:appcompat:1.1.0' 28 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'androidx.test:runner:1.2.0' 31 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 32 | 33 | implementation 'io.github.100mslive:webrtc:m94-4606' 34 | implementation('io.socket:socket.io-client:0.8.3') { 35 | // excluding org.json which is provided by Android 36 | exclude group: 'org.json', module: 'json' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 33 | 34 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 33 | 34 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /step1camera/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /step5opengl/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /step2loopback/src/main/java/cc/rome753/wat/PeerConnectionAdapter.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.util.Log; 4 | 5 | import org.webrtc.DataChannel; 6 | import org.webrtc.IceCandidate; 7 | import org.webrtc.MediaStream; 8 | import org.webrtc.PeerConnection; 9 | import org.webrtc.RtpReceiver; 10 | 11 | /** 12 | * Created by chao on 2019/1/28. 13 | */ 14 | 15 | public class PeerConnectionAdapter implements PeerConnection.Observer { 16 | 17 | private String tag; 18 | 19 | public PeerConnectionAdapter(String tag) { 20 | this.tag = "chao " + tag; 21 | } 22 | 23 | private void log(String s) { 24 | Log.d(tag, s); 25 | } 26 | 27 | @Override 28 | public void onSignalingChange(PeerConnection.SignalingState signalingState) { 29 | log("onSignalingChange " + signalingState); 30 | } 31 | 32 | @Override 33 | public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { 34 | log("onIceConnectionChange " + iceConnectionState); 35 | } 36 | 37 | @Override 38 | public void onIceConnectionReceivingChange(boolean b) { 39 | log("onIceConnectionReceivingChange " + b); 40 | } 41 | 42 | @Override 43 | public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) { 44 | log("onIceGatheringChange " + iceGatheringState); 45 | } 46 | 47 | @Override 48 | public void onIceCandidate(IceCandidate iceCandidate) { 49 | log("onIceCandidate " + iceCandidate); 50 | } 51 | 52 | @Override 53 | public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) { 54 | log("onIceCandidatesRemoved " + iceCandidates); 55 | } 56 | 57 | @Override 58 | public void onAddStream(MediaStream mediaStream) { 59 | log("onAddStream " + mediaStream); 60 | } 61 | 62 | @Override 63 | public void onRemoveStream(MediaStream mediaStream) { 64 | log("onRemoveStream " + mediaStream); 65 | } 66 | 67 | @Override 68 | public void onDataChannel(DataChannel dataChannel) { 69 | log("onDataChannel " + dataChannel); 70 | } 71 | 72 | @Override 73 | public void onRenegotiationNeeded() { 74 | log("onRenegotiationNeeded "); 75 | } 76 | 77 | @Override 78 | public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) { 79 | log("onAddTrack " + mediaStreams); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /step3signaling/src/main/java/cc/rome753/wat/PeerConnectionAdapter.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.util.Log; 4 | 5 | import org.webrtc.DataChannel; 6 | import org.webrtc.IceCandidate; 7 | import org.webrtc.MediaStream; 8 | import org.webrtc.PeerConnection; 9 | import org.webrtc.RtpReceiver; 10 | 11 | /** 12 | * Created by chao on 2019/1/28. 13 | */ 14 | 15 | public class PeerConnectionAdapter implements PeerConnection.Observer { 16 | 17 | private String tag; 18 | 19 | public PeerConnectionAdapter(String tag) { 20 | this.tag = "chao " + tag; 21 | } 22 | 23 | private void log(String s) { 24 | Log.d(tag, s); 25 | } 26 | 27 | @Override 28 | public void onSignalingChange(PeerConnection.SignalingState signalingState) { 29 | log("onSignalingChange " + signalingState); 30 | } 31 | 32 | @Override 33 | public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { 34 | log("onIceConnectionChange " + iceConnectionState); 35 | } 36 | 37 | @Override 38 | public void onIceConnectionReceivingChange(boolean b) { 39 | log("onIceConnectionReceivingChange " + b); 40 | } 41 | 42 | @Override 43 | public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) { 44 | log("onIceGatheringChange " + iceGatheringState); 45 | } 46 | 47 | @Override 48 | public void onIceCandidate(IceCandidate iceCandidate) { 49 | log("onIceCandidate " + iceCandidate); 50 | } 51 | 52 | @Override 53 | public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) { 54 | log("onIceCandidatesRemoved " + iceCandidates); 55 | } 56 | 57 | @Override 58 | public void onAddStream(MediaStream mediaStream) { 59 | log("onAddStream " + mediaStream); 60 | } 61 | 62 | @Override 63 | public void onRemoveStream(MediaStream mediaStream) { 64 | log("onRemoveStream " + mediaStream); 65 | } 66 | 67 | @Override 68 | public void onDataChannel(DataChannel dataChannel) { 69 | log("onDataChannel " + dataChannel); 70 | } 71 | 72 | @Override 73 | public void onRenegotiationNeeded() { 74 | log("onRenegotiationNeeded "); 75 | } 76 | 77 | @Override 78 | public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) { 79 | log("onAddTrack " + mediaStreams); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /step3web/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var os = require('os'); 4 | var nodeStatic = require('node-static'); 5 | var https = require('https'); 6 | var socketIO = require('socket.io'); 7 | var fs = require('fs'); 8 | var options = { 9 | key: fs.readFileSync('key.pem'), 10 | cert: fs.readFileSync('cert.pem') 11 | }; 12 | 13 | var fileServer = new(nodeStatic.Server)(); 14 | var app = https.createServer(options, function(req, res) { 15 | fileServer.serve(req, res); 16 | }).listen(8080); 17 | 18 | var io = socketIO.listen(app); 19 | io.sockets.on('connection', function(socket) { 20 | 21 | // convenience function to log server messages on the client 22 | function log() { 23 | var array = ['Message from server:']; 24 | array.push.apply(array, arguments); 25 | socket.emit('log', array); 26 | 27 | console.log('chao', array); 28 | } 29 | 30 | socket.on('message', function(message) { 31 | log('Client said: ', message); 32 | // for a real app, would be room-only (not broadcast) 33 | socket.broadcast.emit('message', message); 34 | }); 35 | 36 | socket.on('create or join', function(room) { 37 | log('Received request to create or join room ' + room); 38 | 39 | var clientsInRoom = io.sockets.adapter.rooms[room]; 40 | var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; 41 | log('Room ' + room + ' now has ' + numClients + ' client(s)'); 42 | 43 | if (numClients === 0) { 44 | socket.join(room); 45 | log('Client ID ' + socket.id + ' created room ' + room); 46 | socket.emit('created', room, socket.id); 47 | 48 | } else if (numClients === 1) { 49 | log('Client ID ' + socket.id + ' joined room ' + room); 50 | io.sockets.in(room).emit('join', room); 51 | socket.join(room); 52 | socket.emit('joined', room, socket.id); 53 | io.sockets.in(room).emit('ready'); 54 | } else { // max two clients 55 | socket.emit('full', room); 56 | } 57 | }); 58 | 59 | socket.on('ipaddr', function() { 60 | var ifaces = os.networkInterfaces(); 61 | for (var dev in ifaces) { 62 | ifaces[dev].forEach(function(details) { 63 | if (details.family === 'IPv4' && details.address !== '127.0.0.1') { 64 | socket.emit('ipaddr', details.address); 65 | } 66 | }); 67 | } 68 | }); 69 | 70 | socket.on('bye', function(){ 71 | console.log('received bye'); 72 | }); 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /step4multipeers/src/main/java/cc/rome753/wat/PeerConnectionAdapter.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.util.Log; 4 | 5 | import org.webrtc.DataChannel; 6 | import org.webrtc.IceCandidate; 7 | import org.webrtc.MediaStream; 8 | import org.webrtc.PeerConnection; 9 | import org.webrtc.RtpReceiver; 10 | 11 | /** 12 | * Created by chao on 2019/1/28. 13 | */ 14 | 15 | public class PeerConnectionAdapter implements PeerConnection.Observer { 16 | 17 | private String tag; 18 | 19 | public PeerConnectionAdapter(String tag) { 20 | this.tag = "chao " + tag; 21 | } 22 | 23 | private void log(String s) { 24 | Log.d(tag, s); 25 | } 26 | 27 | @Override 28 | public void onSignalingChange(PeerConnection.SignalingState signalingState) { 29 | log("onSignalingChange " + signalingState); 30 | } 31 | 32 | @Override 33 | public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { 34 | log("onIceConnectionChange " + iceConnectionState); 35 | } 36 | 37 | @Override 38 | public void onIceConnectionReceivingChange(boolean b) { 39 | log("onIceConnectionReceivingChange " + b); 40 | } 41 | 42 | @Override 43 | public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) { 44 | log("onIceGatheringChange " + iceGatheringState); 45 | } 46 | 47 | @Override 48 | public void onIceCandidate(IceCandidate iceCandidate) { 49 | log("onIceCandidate " + iceCandidate); 50 | } 51 | 52 | @Override 53 | public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) { 54 | log("onIceCandidatesRemoved " + iceCandidates); 55 | } 56 | 57 | @Override 58 | public void onAddStream(MediaStream mediaStream) { 59 | log("onAddStream " + mediaStream); 60 | } 61 | 62 | @Override 63 | public void onRemoveStream(MediaStream mediaStream) { 64 | log("onRemoveStream " + mediaStream); 65 | } 66 | 67 | @Override 68 | public void onDataChannel(DataChannel dataChannel) { 69 | log("onDataChannel " + dataChannel); 70 | } 71 | 72 | @Override 73 | public void onRenegotiationNeeded() { 74 | log("onRenegotiationNeeded "); 75 | } 76 | 77 | @Override 78 | public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) { 79 | log("onAddTrack " + mediaStreams); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /step4web/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var os = require('os'); 4 | var nodeStatic = require('node-static'); 5 | var https = require('https'); 6 | var socketIO = require('socket.io'); 7 | var fs = require('fs'); 8 | var options = { 9 | key: fs.readFileSync('key.pem'), 10 | cert: fs.readFileSync('cert.pem') 11 | }; 12 | 13 | var fileServer = new(nodeStatic.Server)(); 14 | var app = https.createServer(options, function(req, res) { 15 | fileServer.serve(req, res); 16 | }).listen(8080); 17 | 18 | var io = socketIO.listen(app); 19 | io.sockets.on('connection', function(socket) { 20 | 21 | // convenience function to log server messages on the client 22 | function log() { 23 | var array = ['Message from server:']; 24 | array.push.apply(array, arguments); 25 | socket.emit('log', array); 26 | 27 | console.log('chao', array); 28 | } 29 | 30 | socket.on('message', function(message) { 31 | // for a real app, would be room-only (not broadcast) 32 | // socket.broadcast.emit('message', message); 33 | 34 | var to = message['to']; 35 | log('from:' + socket.id + " to:" + to, message); 36 | io.sockets.sockets[to].emit('message', message); 37 | }); 38 | 39 | socket.on('create or join', function(room) { 40 | log('Received request to create or join room ' + room); 41 | 42 | var clientsInRoom = io.sockets.adapter.rooms[room]; 43 | var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; 44 | log('Room ' + room + ' now has ' + numClients + ' client(s)'); 45 | 46 | if (numClients === 0) { 47 | socket.join(room); 48 | log('Client ID ' + socket.id + ' created room ' + room); 49 | socket.emit('created', room, socket.id); 50 | 51 | } else { 52 | log('Client ID ' + socket.id + ' joined room ' + room); 53 | io.sockets.in(room).emit('join', room, socket.id); 54 | socket.join(room); 55 | socket.emit('joined', room, socket.id); 56 | io.sockets.in(room).emit('ready'); 57 | } 58 | }); 59 | 60 | socket.on('ipaddr', function() { 61 | var ifaces = os.networkInterfaces(); 62 | for (var dev in ifaces) { 63 | ifaces[dev].forEach(function(details) { 64 | if (details.family === 'IPv4' && details.address !== '127.0.0.1') { 65 | socket.emit('ipaddr', details.address); 66 | } 67 | }); 68 | } 69 | }); 70 | 71 | socket.on('bye', function(){ 72 | console.log('received bye'); 73 | }); 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 35 | 36 | 44 | 45 | 51 | 52 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/ImageBytes.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.media.Image; 4 | 5 | import org.webrtc.VideoFrame; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | public class ImageBytes { 10 | public byte[] bytes; 11 | public int width; 12 | public int height; 13 | 14 | public ByteBuffer bufY; 15 | public ByteBuffer bufU; 16 | public ByteBuffer bufV; 17 | 18 | public ImageBytes(byte[] bytes, int width, int height) { 19 | this.bytes = bytes; 20 | this.width = width; 21 | this.height = height; 22 | 23 | int r0 = width * height; 24 | int u0 = r0 / 4; 25 | int v0 = u0; 26 | 27 | bufY = ByteBuffer.allocate(r0).put(bytes, 0, r0); 28 | // camera1的nv21格式,需要把uv全部放到bufU中,否则画面只有一半正常 29 | // 实际上bufU只使用了一半,这跟相机画面扫描的方向有关 30 | bufU = ByteBuffer.allocate(u0 + v0).put(bytes, r0, u0 + v0); 31 | bufV = ByteBuffer.allocate(v0).put(bytes, r0 + u0, v0); 32 | 33 | bufY.position(0); 34 | bufU.position(0); 35 | bufV.position(0); 36 | } 37 | 38 | public ImageBytes(Image image) { 39 | final Image.Plane[] planes = image.getPlanes(); 40 | 41 | Image.Plane p0 = planes[0]; 42 | Image.Plane p1 = planes[1]; 43 | Image.Plane p2 = planes[2]; 44 | 45 | ByteBuffer b0 = p0.getBuffer(); 46 | ByteBuffer b1 = p1.getBuffer(); 47 | ByteBuffer b2 = p2.getBuffer(); 48 | 49 | int r0 = b0.remaining(); 50 | 51 | int w0 = p0.getRowStride(); 52 | int h0 = r0 / w0; 53 | if(r0 % w0 != 0) h0++; 54 | 55 | this.width = w0; 56 | this.height = h0; 57 | this.bufY = b0; 58 | this.bufU = b1; 59 | this.bufV = b2; 60 | } 61 | 62 | public static ImageBytes create(VideoFrame image) { 63 | VideoFrame.I420Buffer buf = image.getBuffer().toI420(); 64 | 65 | ByteBuffer b0 = buf.getDataY(); 66 | ByteBuffer b1 = buf.getDataU(); 67 | ByteBuffer b2 = buf.getDataV(); 68 | 69 | int r0 = b0.remaining(); 70 | int r1 = b1.remaining(); 71 | int r2 = b2.remaining(); 72 | 73 | int w0 = buf.getStrideY(); 74 | int h0 = r0 / w0; 75 | if(r0 % w0 > 0) h0++; 76 | int w1 = buf.getStrideU(); 77 | int h1 = r1 / w1; 78 | if(r1 % w1 > 1) h1++; 79 | int w2 = buf.getStrideV(); 80 | int h2 = r2 / w2; 81 | if(r2 % w2 > 2) h2++; 82 | 83 | int y = w0 * h0; 84 | int u = w1 * h1; 85 | int v = w2 * h2; 86 | 87 | byte[] bytes = new byte[y + u + v]; 88 | 89 | b0.get(bytes, 0, r0); 90 | b1.get(bytes, y, r1); // u 91 | b2.get(bytes, y + u, r2); // v 92 | 93 | return new ImageBytes(bytes, w0, h0); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/EglProcessor.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.opengl.GLES20; 4 | 5 | import org.webrtc.JavaI420Buffer; 6 | import org.webrtc.VideoFrame; 7 | import org.webrtc.VideoProcessor; 8 | import org.webrtc.VideoSink; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.nio.IntBuffer; 12 | 13 | import cc.rome753.wat.egl.EglThread; 14 | 15 | public class EglProcessor implements VideoProcessor { 16 | 17 | EglThread eglThread = new EglThread("my-egl"); 18 | YUVRender yuvRender = new YUVRender(); 19 | VideoSink videoSink; 20 | 21 | ByteBuffer dataY; 22 | ByteBuffer dataU; 23 | ByteBuffer dataV; 24 | 25 | @Override 26 | public void setSink(VideoSink videoSink) { 27 | this.videoSink = videoSink; 28 | } 29 | 30 | @Override 31 | public void onCapturerStarted(boolean b) { 32 | } 33 | 34 | @Override 35 | public void onCapturerStopped() { 36 | 37 | } 38 | 39 | @Override 40 | public void onFrameCaptured(VideoFrame videoFrame) { 41 | int w = videoFrame.getBuffer().getWidth(); 42 | int h = videoFrame.getBuffer().getHeight(); 43 | 44 | // ImageBytes imageBytes = ImageBytes.create(videoFrame); 45 | VideoFrame.I420Buffer ori = videoFrame.getBuffer().toI420(); 46 | 47 | eglThread.initEglCore(w, h); 48 | eglThread.post(() -> { 49 | yuvRender.onSurfaceCreated(null, null); 50 | yuvRender.onSurfaceChanged(null, w, h); 51 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 52 | yuvRender.getYuvShader().draw(w, h, ori.getDataY(), ori.getDataU(), ori.getDataU()); 53 | 54 | IntBuffer buffer = IntBuffer.allocate(w * h); 55 | GLES20.glReadPixels(0, 0, w, h, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); 56 | byte[] yuv = YUVTools.rgb2Yuv420(buffer.array(), w, h); 57 | buffer.rewind(); 58 | 59 | if (dataY == null) { 60 | dataY = ByteBuffer.allocateDirect(w * h); 61 | } 62 | dataY.put(yuv, 0, w * h); 63 | 64 | if (dataU == null) { 65 | dataU = ByteBuffer.allocateDirect(w * h / 4); 66 | } 67 | dataU.put(yuv, w * h, w * h / 4); 68 | 69 | if (dataV == null) { 70 | dataV = ByteBuffer.allocateDirect(w * h / 4); 71 | } 72 | dataV.put(yuv, w * h + w * h / 4, w * h / 4); 73 | 74 | dataY.position(0); 75 | dataU.position(0); 76 | dataV.position(0); 77 | 78 | JavaI420Buffer nb = JavaI420Buffer.wrap(w, h, dataY, ori.getStrideY(), dataU, ori.getStrideU(), dataV, ori.getStrideV(), null); 79 | VideoFrame newFrame = new VideoFrame(nb, videoFrame.getRotation(), videoFrame.getTimestampNs()); 80 | videoSink.onFrame(newFrame); 81 | }); 82 | 83 | // videoSink.onFrame(videoFrame); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /step1camera/src/main/java/cc/rome753/wat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import org.webrtc.AudioSource; 7 | import org.webrtc.AudioTrack; 8 | import org.webrtc.Camera1Enumerator; 9 | import org.webrtc.EglBase; 10 | import org.webrtc.MediaConstraints; 11 | import org.webrtc.PeerConnectionFactory; 12 | import org.webrtc.SurfaceTextureHelper; 13 | import org.webrtc.SurfaceViewRenderer; 14 | import org.webrtc.VideoCapturer; 15 | import org.webrtc.VideoSource; 16 | import org.webrtc.VideoTrack; 17 | 18 | public class MainActivity extends AppCompatActivity { 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | // create PeerConnectionFactory 26 | PeerConnectionFactory.InitializationOptions initializationOptions = 27 | PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions(); 28 | PeerConnectionFactory.initialize(initializationOptions); 29 | PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory(); 30 | 31 | // create AudioSource 32 | AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints()); 33 | AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("101", audioSource); 34 | 35 | EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext(); 36 | 37 | SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext); 38 | // create VideoCapturer 39 | VideoCapturer videoCapturer = createCameraCapturer(); 40 | VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast()); 41 | videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver()); 42 | videoCapturer.startCapture(480, 640, 30); 43 | 44 | SurfaceViewRenderer localView = findViewById(R.id.localView); 45 | localView.setMirror(true); 46 | localView.init(eglBaseContext, null); 47 | 48 | // create VideoTrack 49 | VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource); 50 | // display in localView 51 | videoTrack.addSink(localView); 52 | } 53 | 54 | private VideoCapturer createCameraCapturer() { 55 | Camera1Enumerator enumerator = new Camera1Enumerator(false); 56 | final String[] deviceNames = enumerator.getDeviceNames(); 57 | 58 | // First, try to find front facing camera 59 | for (String deviceName : deviceNames) { 60 | if (enumerator.isFrontFacing(deviceName)) { 61 | VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); 62 | 63 | if (videoCapturer != null) { 64 | return videoCapturer; 65 | } 66 | } 67 | } 68 | 69 | // Front facing camera not found, try something else 70 | for (String deviceName : deviceNames) { 71 | if (!enumerator.isFrontFacing(deviceName)) { 72 | VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); 73 | 74 | if (videoCapturer != null) { 75 | return videoCapturer; 76 | } 77 | } 78 | } 79 | 80 | return null; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/egl/EglCore.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat.egl; 2 | 3 | import android.opengl.EGL14; 4 | import android.opengl.EGLConfig; 5 | import android.opengl.EGLContext; 6 | import android.opengl.EGLDisplay; 7 | import android.opengl.EGLSurface; 8 | import android.util.Log; 9 | 10 | 11 | public class EglCore { 12 | 13 | EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY; 14 | EGLContext mEglContext = EGL14.EGL_NO_CONTEXT; 15 | EGLConfig mEglConfig = null; 16 | 17 | EGLSurface mEglSurface = null; 18 | 19 | public EglCore() { 20 | mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 21 | if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { 22 | throw new RuntimeException("unable to get EGL14 display"); 23 | } 24 | int[] version = new int[2]; 25 | if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 0)) { 26 | mEglDisplay = null; 27 | throw new RuntimeException("unable to initialize EGL14"); 28 | } 29 | 30 | String vendor = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_VENDOR); 31 | Log.d("chao", "egl vendor: " + vendor); // 打印此版本EGL的实现厂商 32 | 33 | String versionStr = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_VERSION); 34 | Log.d("chao", "egl version: " + versionStr);// 打印EGL版本号 35 | 36 | String extension = EGL14.eglQueryString(mEglDisplay, EGL14.EGL_EXTENSIONS); 37 | Log.d("chao", "egl extension: " + extension); //打印支持的EGL扩展 38 | 39 | 40 | int[] attributes = new int[] { 41 | EGL14.EGL_RED_SIZE, 8, //指定RGB中的R大小(bits) 42 | EGL14.EGL_GREEN_SIZE, 8, //指定G大小 43 | EGL14.EGL_BLUE_SIZE, 8, //指定B大小 44 | EGL14.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四项实际上指定了像素格式 45 | EGL14.EGL_DEPTH_SIZE, 16, //指定深度缓存(Z Buffer)大小 46 | EGL14.EGL_RENDERABLE_TYPE, 4, //指定渲染api类别,这里或者是硬编码的4,或者是EGL14.EGL_OPENGL_ES2_BIT 47 | EGL14.EGL_NONE }; //总是以EGL10.EGL_NONE结尾 48 | 49 | EGLConfig[] configs = new EGLConfig[1]; 50 | int[] numConfigs = new int[1]; 51 | if (!EGL14.eglChooseConfig(mEglDisplay, attributes, 0, configs, 0, configs.length, numConfigs, 0)) { //获取所有 52 | Log.w("chao", "unable to find RGB8888 suitable EGLConfig"); 53 | } 54 | mEglConfig = configs[0]; 55 | 56 | int[] attrs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE }; 57 | mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT, attrs, 0); 58 | } 59 | 60 | EGLSurface createPbufferSurface(int w, int h) { 61 | int [] attrs = { 62 | EGL14.EGL_WIDTH, w, 63 | EGL14.EGL_HEIGHT, h, 64 | EGL14.EGL_NONE 65 | }; 66 | EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attrs, 0); 67 | if (eglSurface == null) { 68 | throw new RuntimeException("eglCreatePbufferSurface failed!"); 69 | } 70 | return eglSurface; 71 | } 72 | 73 | public void makeCurrent(int w, int h) { 74 | mEglSurface = createPbufferSurface(w, h); 75 | if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 76 | Log.e("chao", "eglMakeCurrent failed"); 77 | } 78 | } 79 | 80 | public void release() { 81 | EGL14.eglMakeCurrent(mEglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); 82 | EGL14.eglDestroySurface(mEglDisplay, mEglSurface); 83 | EGL14.eglDestroyContext(mEglDisplay, mEglContext); 84 | EGL14.eglReleaseThread(); 85 | EGL14.eglTerminate(mEglDisplay); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.graphics.Bitmap; 4 | import android.opengl.GLES20; 5 | import android.os.Bundle; 6 | import android.widget.ImageView; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | import org.webrtc.AudioSource; 11 | import org.webrtc.AudioTrack; 12 | import org.webrtc.Camera1Enumerator; 13 | import org.webrtc.EglBase; 14 | import org.webrtc.JavaI420Buffer; 15 | import org.webrtc.MediaConstraints; 16 | import org.webrtc.PeerConnectionFactory; 17 | import org.webrtc.SurfaceTextureHelper; 18 | import org.webrtc.SurfaceViewRenderer; 19 | import org.webrtc.VideoCapturer; 20 | import org.webrtc.VideoFrame; 21 | import org.webrtc.VideoProcessor; 22 | import org.webrtc.VideoSink; 23 | import org.webrtc.VideoSource; 24 | import org.webrtc.VideoTrack; 25 | 26 | import java.nio.ByteBuffer; 27 | import java.nio.IntBuffer; 28 | import java.util.Arrays; 29 | 30 | import cc.rome753.wat.egl.EglThread; 31 | 32 | public class MainActivity extends AppCompatActivity { 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_main); 38 | 39 | // create PeerConnectionFactory 40 | PeerConnectionFactory.InitializationOptions initializationOptions = 41 | PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions(); 42 | PeerConnectionFactory.initialize(initializationOptions); 43 | PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory(); 44 | 45 | // create AudioSource 46 | AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints()); 47 | AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("101", audioSource); 48 | 49 | EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext(); 50 | 51 | SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext); 52 | // create VideoCapturer 53 | VideoCapturer videoCapturer = createCameraCapturer(); 54 | VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast()); 55 | videoSource.setVideoProcessor(new EglProcessor()); 56 | videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver()); 57 | videoCapturer.startCapture(480, 640, 30); 58 | 59 | SurfaceViewRenderer localView = findViewById(R.id.localView); 60 | localView.setMirror(true); 61 | localView.init(eglBaseContext, null); 62 | 63 | // create VideoTrack 64 | VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource); 65 | // display in localView 66 | videoTrack.addSink(localView); 67 | } 68 | 69 | private VideoCapturer createCameraCapturer() { 70 | Camera1Enumerator enumerator = new Camera1Enumerator(false); 71 | final String[] deviceNames = enumerator.getDeviceNames(); 72 | 73 | // First, try to find front facing camera 74 | for (String deviceName : deviceNames) { 75 | if (enumerator.isFrontFacing(deviceName)) { 76 | VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); 77 | 78 | if (videoCapturer != null) { 79 | return videoCapturer; 80 | } 81 | } 82 | } 83 | 84 | // Front facing camera not found, try something else 85 | for (String deviceName : deviceNames) { 86 | if (!enumerator.isFrontFacing(deviceName)) { 87 | VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); 88 | 89 | if (videoCapturer != null) { 90 | return videoCapturer; 91 | } 92 | } 93 | } 94 | 95 | return null; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/ShaderUtils.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import static android.opengl.GLES30.GL_COMPILE_STATUS; 4 | import static android.opengl.GLES30.GL_FRAGMENT_SHADER; 5 | import static android.opengl.GLES30.GL_LINK_STATUS; 6 | import static android.opengl.GLES30.GL_VERTEX_SHADER; 7 | import static android.opengl.GLES30.glAttachShader; 8 | import static android.opengl.GLES30.glCompileShader; 9 | import static android.opengl.GLES30.glCreateProgram; 10 | import static android.opengl.GLES30.glCreateShader; 11 | import static android.opengl.GLES30.glDeleteProgram; 12 | import static android.opengl.GLES30.glDeleteShader; 13 | import static android.opengl.GLES30.glGetProgramInfoLog; 14 | import static android.opengl.GLES30.glGetProgramiv; 15 | import static android.opengl.GLES30.glGetShaderInfoLog; 16 | import static android.opengl.GLES30.glGetShaderiv; 17 | import static android.opengl.GLES30.glLinkProgram; 18 | import static android.opengl.GLES30.glShaderSource; 19 | 20 | import android.graphics.Bitmap; 21 | import android.graphics.BitmapFactory; 22 | import android.util.Log; 23 | 24 | import java.io.InputStream; 25 | import java.nio.charset.StandardCharsets; 26 | 27 | public class ShaderUtils { 28 | 29 | public static int loadProgram() { 30 | int vShader = ShaderUtils.loadShader(GL_VERTEX_SHADER, loadAssets("shader_base_v.glsl")); 31 | int fShader = ShaderUtils.loadShader(GL_FRAGMENT_SHADER, loadAssets("shader_base_f.glsl")); 32 | return linkProgram(vShader, fShader); 33 | } 34 | 35 | public static int loadProgramYUV() { 36 | int vShader = ShaderUtils.loadShader(GL_VERTEX_SHADER, loadAssets("shader_yuv_v.glsl")); 37 | int fShader = ShaderUtils.loadShader(GL_FRAGMENT_SHADER, loadAssets("shader_yuv_f.glsl")); 38 | return linkProgram(vShader, fShader); 39 | } 40 | 41 | public static int loadProgramDiff() { 42 | int vShader = ShaderUtils.loadShader(GL_VERTEX_SHADER, loadAssets("shader_diff_v.glsl")); 43 | int fShader = ShaderUtils.loadShader(GL_FRAGMENT_SHADER, loadAssets("shader_diff_f.glsl")); 44 | return linkProgram(vShader, fShader); 45 | } 46 | 47 | public static int loadProgram(String vs, String fs) { 48 | int vShader = ShaderUtils.loadShader(GL_VERTEX_SHADER, vs); 49 | int fShader = ShaderUtils.loadShader(GL_FRAGMENT_SHADER, fs); 50 | return linkProgram(vShader, fShader); 51 | } 52 | 53 | private static int loadShader(int type, String shaderSrc) { 54 | int shader = glCreateShader(type); 55 | if (shader == 0) { 56 | Log.e("chao", "compile shader == 0"); 57 | return 0; 58 | } 59 | glShaderSource(shader, shaderSrc); 60 | glCompileShader(shader); 61 | int[] compileStatus = new int[1]; 62 | glGetShaderiv(shader, GL_COMPILE_STATUS, compileStatus, 0); 63 | if (compileStatus[0] == 0) { 64 | String log = glGetShaderInfoLog(shader); 65 | Log.e("chao", "glGetShaderiv fail " + log); 66 | glDeleteShader(shader); 67 | return 0; 68 | } 69 | return shader; 70 | } 71 | 72 | private static int linkProgram(int vShader, int fShader) { 73 | int program = glCreateProgram(); 74 | if (program == 0) { 75 | Log.e("chao", "program == 0"); 76 | return 0; 77 | } 78 | 79 | glAttachShader(program, vShader); 80 | glAttachShader(program, fShader); 81 | 82 | glLinkProgram(program); 83 | int[] linkStatus = new int[1]; 84 | glGetProgramiv(program, GL_LINK_STATUS, linkStatus, 0); 85 | if (linkStatus[0] == 0) { 86 | String log = glGetProgramInfoLog(program); 87 | Log.e("chao", "linkProgram fail " + log); 88 | glDeleteProgram(program); 89 | return 0; 90 | } 91 | return program; 92 | } 93 | 94 | 95 | public static String loadAssets(String name) { 96 | String s = null; 97 | try { 98 | InputStream is = App.Companion.getApp().getAssets().open(name); 99 | int size = is.available(); 100 | byte[] buffer = new byte[size]; 101 | is.read(buffer); 102 | is.close(); 103 | s = new String(buffer, StandardCharsets.UTF_8); 104 | } catch (Exception e) { 105 | e.printStackTrace(); 106 | } 107 | return s; 108 | } 109 | 110 | public static Bitmap loadImageAssets(String name) { 111 | try { 112 | InputStream is = App.Companion.getApp().getAssets().open(name); 113 | return BitmapFactory.decodeStream(is); 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } 117 | return null; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /step5opengl/src/main/java/cc/rome753/wat/YUVShader.kt: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat 2 | 3 | import android.media.Image 4 | import android.opengl.GLES20.* 5 | import android.opengl.GLES30 6 | import android.opengl.GLES30.glBindVertexArray 7 | import android.opengl.GLES30.glGenVertexArrays 8 | import android.opengl.Matrix 9 | import java.nio.ByteBuffer 10 | import java.nio.ByteOrder 11 | import java.nio.FloatBuffer 12 | import java.nio.IntBuffer 13 | 14 | class YUVShader { 15 | 16 | var vertices = floatArrayOf( // ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 - 17 | -1f, -1f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下 18 | 1f, -1f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下 19 | -1f, 1f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 左上 20 | 1f, 1f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f // 右上 21 | ) 22 | 23 | val indices = intArrayOf( // 注意索引从0开始! 24 | 0, 1, 2, // 第一个三角形 25 | 1, 2, 3 // 第二个三角形 26 | ) 27 | 28 | var program = 0 29 | var vertexBuffer: FloatBuffer? = null 30 | var intBuffer: IntBuffer? = null 31 | var vao: IntArray = IntArray(1) 32 | 33 | var tex: IntArray = IntArray(3) // yuv 34 | 35 | var transform = FloatArray(16) 36 | 37 | fun init() { 38 | program = ShaderUtils.loadProgramYUV() 39 | //分配内存空间,每个浮点型占4字节空间 40 | vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4) 41 | .order(ByteOrder.nativeOrder()) 42 | .asFloatBuffer() 43 | //传入指定的坐标数据 44 | vertexBuffer!!.put(vertices) 45 | vertexBuffer!!.position(0) 46 | vao = IntArray(1) 47 | glGenVertexArrays(1, vao, 0) 48 | glBindVertexArray(vao[0]) 49 | val vbo = IntArray(1) 50 | glGenBuffers(1, vbo, 0) 51 | glBindBuffer(GL_ARRAY_BUFFER, vbo[0]) 52 | glBufferData(GL_ARRAY_BUFFER, vertices.size * 4, vertexBuffer, GL_STATIC_DRAW) 53 | 54 | intBuffer = IntBuffer.allocate(indices.size * 4) 55 | intBuffer!!.put(indices) 56 | intBuffer!!.position(0) 57 | val ebo = IntArray(1) 58 | glGenBuffers(1, ebo, 0) 59 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]) 60 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size * 4, intBuffer, GL_STATIC_DRAW) 61 | 62 | glUseProgram(program) 63 | glGenTextures(3, tex, 0) 64 | for (i in 0..2) { 65 | glActiveTexture(GL_TEXTURE0 + i) 66 | glBindTexture(GL_TEXTURE_2D, tex[i]) 67 | // 为当前绑定的纹理对象设置环绕、过滤方式 68 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) 69 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) 70 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) 71 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) 72 | // val bitmap: Bitmap = ShaderUtils.loadImageAssets("face.png") 73 | // GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0) 74 | // glGenerateMipmap(GL_TEXTURE_2D) 75 | 76 | val loc0 = glGetUniformLocation(program, "tex$i") 77 | glUniform1i(loc0, i) 78 | } 79 | 80 | // Load the vertex data 81 | glVertexAttribPointer(0, 3, GL_FLOAT, false, 8 * 4, 0) 82 | glEnableVertexAttribArray(0) 83 | glVertexAttribPointer(1, 3, GL_FLOAT, false, 8 * 4, 3 * 4) 84 | glEnableVertexAttribArray(1) 85 | glVertexAttribPointer(2, 2, GL_FLOAT, false, 8 * 4, 6 * 4) 86 | glEnableVertexAttribArray(2) 87 | 88 | glBindBuffer(GL_ARRAY_BUFFER, 0) 89 | glBindVertexArray(0) 90 | 91 | Matrix.setIdentityM(transform, 0) 92 | 93 | } 94 | 95 | fun draw(ib: ImageBytes) { 96 | draw(ib.width, ib.height, ib.bufY, ib.bufU, ib.bufV) 97 | } 98 | 99 | fun draw(w0: Int, h0: Int, bufY: ByteBuffer, bufU: ByteBuffer, bufV: ByteBuffer) { 100 | val w1 = w0 / 2 101 | val h1 = h0 / 2 102 | glUseProgram(program) 103 | 104 | glActiveTexture(tex[0]) 105 | glBindTexture(GL_TEXTURE_2D, tex[0]) 106 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w0, h0, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, bufY) 107 | 108 | glActiveTexture(tex[1]) 109 | glBindTexture(GL_TEXTURE_2D, tex[1]) 110 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w1, h1, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, bufU) 111 | // glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w1, h1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, ib.bufU) 112 | 113 | glActiveTexture(tex[2]) 114 | glBindTexture(GL_TEXTURE_2D, tex[2]) 115 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w1, h1, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, bufV) 116 | // glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w1, h1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, ib.bufV) 117 | 118 | val loc = glGetUniformLocation(program, "transform") 119 | glUniformMatrix4fv(loc, 1, false, transform, 0) 120 | 121 | glBindVertexArray(vao[0]) 122 | glDrawElements(GL_TRIANGLES, vertices.size, GL_UNSIGNED_INT, 0) 123 | } 124 | 125 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /step3signaling/src/main/java/cc/rome753/wat/SignalingClient.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.telecom.Call; 4 | import android.util.Log; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | import org.webrtc.IceCandidate; 9 | import org.webrtc.SessionDescription; 10 | 11 | import java.net.URISyntaxException; 12 | import java.security.KeyManagementException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.cert.CertificateException; 15 | import java.security.cert.X509Certificate; 16 | import java.util.Arrays; 17 | 18 | import javax.net.ssl.SSLContext; 19 | import javax.net.ssl.TrustManager; 20 | import javax.net.ssl.X509TrustManager; 21 | 22 | import io.socket.client.IO; 23 | import io.socket.client.Socket; 24 | 25 | /** 26 | * Created by chao on 2019/1/30. 27 | */ 28 | 29 | public class SignalingClient { 30 | 31 | private static SignalingClient instance; 32 | private SignalingClient(){ 33 | init(); 34 | } 35 | public static SignalingClient get() { 36 | if(instance == null) { 37 | synchronized (SignalingClient.class) { 38 | if(instance == null) { 39 | instance = new SignalingClient(); 40 | } 41 | } 42 | } 43 | return instance; 44 | } 45 | 46 | private Socket socket; 47 | private String room = "OldPlace"; 48 | private Callback callback; 49 | 50 | private final TrustManager[] trustAll = new TrustManager[]{ 51 | new X509TrustManager() { 52 | @Override 53 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 54 | 55 | } 56 | 57 | @Override 58 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 59 | 60 | } 61 | 62 | @Override 63 | public X509Certificate[] getAcceptedIssuers() { 64 | return new X509Certificate[0]; 65 | } 66 | } 67 | }; 68 | 69 | public void setCallback(Callback callback) { 70 | this.callback = callback; 71 | } 72 | 73 | private void init() { 74 | try { 75 | SSLContext sslContext = SSLContext.getInstance("TLS"); 76 | sslContext.init(null, trustAll, null); 77 | IO.setDefaultHostnameVerifier((hostname, session) -> true); 78 | IO.setDefaultSSLContext(sslContext); 79 | 80 | socket = IO.socket("https://192.168.1.97:8080"); 81 | socket.connect(); 82 | 83 | socket.emit("create or join", room); 84 | 85 | socket.on("created", args -> { 86 | Log.e("chao", "room created"); 87 | callback.onCreateRoom(); 88 | }); 89 | socket.on("full", args -> { 90 | Log.e("chao", "room full"); 91 | }); 92 | socket.on("join", args -> { 93 | Log.e("chao", "peer joined"); 94 | callback.onPeerJoined(); 95 | }); 96 | socket.on("joined", args -> { 97 | Log.e("chao", "self joined"); 98 | callback.onSelfJoined(); 99 | }); 100 | socket.on("log", args -> { 101 | Log.e("chao", "log call " + Arrays.toString(args)); 102 | }); 103 | socket.on("bye", args -> { 104 | Log.e("chao", "bye " + args[0]); 105 | callback.onPeerLeave((String) args[0]); 106 | }); 107 | socket.on("message", args -> { 108 | Log.e("chao", "message " + Arrays.toString(args)); 109 | Object arg = args[0]; 110 | if(arg instanceof String) { 111 | 112 | } else if(arg instanceof JSONObject) { 113 | JSONObject data = (JSONObject) arg; 114 | String type = data.optString("type"); 115 | if("offer".equals(type)) { 116 | callback.onOfferReceived(data); 117 | } else if("answer".equals(type)) { 118 | callback.onAnswerReceived(data); 119 | } else if("candidate".equals(type)) { 120 | callback.onIceCandidateReceived(data); 121 | } 122 | } 123 | }); 124 | 125 | } catch (NoSuchAlgorithmException e) { 126 | e.printStackTrace(); 127 | } catch (KeyManagementException e) { 128 | e.printStackTrace(); 129 | } catch (URISyntaxException e) { 130 | e.printStackTrace(); 131 | } 132 | } 133 | 134 | public void sendIceCandidate(IceCandidate iceCandidate) { 135 | JSONObject jo = new JSONObject(); 136 | try { 137 | jo.put("type", "candidate"); 138 | jo.put("label", iceCandidate.sdpMLineIndex); 139 | jo.put("id", iceCandidate.sdpMid); 140 | jo.put("candidate", iceCandidate.sdp); 141 | 142 | socket.emit("message", jo); 143 | } catch (JSONException e) { 144 | e.printStackTrace(); 145 | } 146 | } 147 | 148 | public void sendSessionDescription(SessionDescription sdp) { 149 | JSONObject jo = new JSONObject(); 150 | try { 151 | jo.put("type", sdp.type.canonicalForm()); 152 | jo.put("sdp", sdp.description); 153 | 154 | socket.emit("message", jo); 155 | } catch (JSONException e) { 156 | e.printStackTrace(); 157 | } 158 | } 159 | 160 | public interface Callback { 161 | void onCreateRoom(); 162 | void onPeerJoined(); 163 | void onSelfJoined(); 164 | void onPeerLeave(String msg); 165 | 166 | void onOfferReceived(JSONObject data); 167 | void onAnswerReceived(JSONObject data); 168 | void onIceCandidateReceived(JSONObject data); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /step1camera/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /step5opengl/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /step2loopback/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /step3signaling/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /step4multipeers/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /step4multipeers/src/main/java/cc/rome753/wat/SignalingClient.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.telecom.Call; 4 | import android.util.Log; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | import org.webrtc.IceCandidate; 9 | import org.webrtc.SessionDescription; 10 | 11 | import java.net.URISyntaxException; 12 | import java.security.KeyManagementException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.cert.CertificateException; 15 | import java.security.cert.X509Certificate; 16 | import java.util.Arrays; 17 | 18 | import javax.net.ssl.SSLContext; 19 | import javax.net.ssl.TrustManager; 20 | import javax.net.ssl.X509TrustManager; 21 | 22 | import io.socket.client.IO; 23 | import io.socket.client.Socket; 24 | 25 | /** 26 | * Created by chao on 2019/1/30. 27 | */ 28 | 29 | public class SignalingClient { 30 | 31 | private static SignalingClient instance; 32 | private SignalingClient(){} 33 | public static SignalingClient get() { 34 | if(instance == null) { 35 | synchronized (SignalingClient.class) { 36 | if(instance == null) { 37 | instance = new SignalingClient(); 38 | } 39 | } 40 | } 41 | return instance; 42 | } 43 | 44 | private Socket socket; 45 | private String room = "OldPlace"; 46 | private Callback callback; 47 | 48 | private final TrustManager[] trustAll = new TrustManager[]{ 49 | new X509TrustManager() { 50 | @Override 51 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 52 | 53 | } 54 | 55 | @Override 56 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 57 | 58 | } 59 | 60 | @Override 61 | public X509Certificate[] getAcceptedIssuers() { 62 | return new X509Certificate[0]; 63 | } 64 | } 65 | }; 66 | 67 | public void init(Callback callback) { 68 | this.callback = callback; 69 | try { 70 | SSLContext sslContext = SSLContext.getInstance("TLS"); 71 | sslContext.init(null, trustAll, null); 72 | IO.setDefaultHostnameVerifier((hostname, session) -> true); 73 | IO.setDefaultSSLContext(sslContext); 74 | 75 | socket = IO.socket("https://192.168.1.97:8080"); 76 | socket.connect(); 77 | 78 | socket.emit("create or join", room); 79 | 80 | socket.on("created", args -> { 81 | Log.e("chao", "room created:" + socket.id()); 82 | callback.onCreateRoom(); 83 | }); 84 | socket.on("full", args -> { 85 | Log.e("chao", "room full"); 86 | }); 87 | socket.on("join", args -> { 88 | Log.e("chao", "peer joined " + Arrays.toString(args)); 89 | callback.onPeerJoined(String.valueOf(args[1])); 90 | }); 91 | socket.on("joined", args -> { 92 | Log.e("chao", "self joined:" + socket.id()); 93 | callback.onSelfJoined(); 94 | }); 95 | socket.on("log", args -> { 96 | Log.e("chao", "log call " + Arrays.toString(args)); 97 | }); 98 | socket.on("bye", args -> { 99 | Log.e("chao", "bye " + args[0]); 100 | callback.onPeerLeave((String) args[0]); 101 | }); 102 | socket.on("message", args -> { 103 | Log.e("chao", "message " + Arrays.toString(args)); 104 | Object arg = args[0]; 105 | if(arg instanceof String) { 106 | 107 | } else if(arg instanceof JSONObject) { 108 | JSONObject data = (JSONObject) arg; 109 | String type = data.optString("type"); 110 | if("offer".equals(type)) { 111 | callback.onOfferReceived(data); 112 | } else if("answer".equals(type)) { 113 | callback.onAnswerReceived(data); 114 | } else if("candidate".equals(type)) { 115 | callback.onIceCandidateReceived(data); 116 | } 117 | } 118 | }); 119 | } catch (NoSuchAlgorithmException e) { 120 | e.printStackTrace(); 121 | } catch (KeyManagementException e) { 122 | e.printStackTrace(); 123 | } catch (URISyntaxException e) { 124 | e.printStackTrace(); 125 | } 126 | } 127 | 128 | public void destroy() { 129 | socket.emit("bye", socket.id()); 130 | socket.disconnect(); 131 | socket.close(); 132 | instance = null; 133 | } 134 | 135 | public void sendIceCandidate(IceCandidate iceCandidate, String to) { 136 | JSONObject jo = new JSONObject(); 137 | try { 138 | jo.put("type", "candidate"); 139 | jo.put("label", iceCandidate.sdpMLineIndex); 140 | jo.put("id", iceCandidate.sdpMid); 141 | jo.put("candidate", iceCandidate.sdp); 142 | jo.put("from", socket.id()); 143 | jo.put("to", to); 144 | 145 | socket.emit("message", jo); 146 | } catch (JSONException e) { 147 | e.printStackTrace(); 148 | } 149 | } 150 | 151 | public void sendSessionDescription(SessionDescription sdp, String to) { 152 | JSONObject jo = new JSONObject(); 153 | try { 154 | jo.put("type", sdp.type.canonicalForm()); 155 | jo.put("sdp", sdp.description); 156 | jo.put("from", socket.id()); 157 | jo.put("to", to); 158 | socket.emit("message", jo); 159 | } catch (JSONException e) { 160 | e.printStackTrace(); 161 | } 162 | } 163 | 164 | public interface Callback { 165 | void onCreateRoom(); 166 | void onPeerJoined(String socketId); 167 | void onSelfJoined(); 168 | void onPeerLeave(String msg); 169 | 170 | void onOfferReceived(JSONObject data); 171 | void onAnswerReceived(JSONObject data); 172 | void onIceCandidateReceived(JSONObject data); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /step3web/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isChannelReady = false; 4 | var isInitiator = false; 5 | var isStarted = false; 6 | var localStream; 7 | var pc; 8 | var remoteStream; 9 | var turnReady; 10 | 11 | var pcConfig = { 12 | 'iceServers': [{ 13 | 'urls': 'stun:stun.l.google.com:19302' 14 | }] 15 | }; 16 | 17 | // Set up audio and video regardless of what devices are present. 18 | var sdpConstraints = { 19 | offerToReceiveAudio: true, 20 | offerToReceiveVideo: true 21 | }; 22 | 23 | ///////////////////////////////////////////// 24 | 25 | var room = 'OldPlace'; 26 | // Could prompt for room name: 27 | // room = prompt('Enter room name:'); 28 | 29 | var socket = io.connect(); 30 | 31 | if (room !== '') { 32 | socket.emit('create or join', room); 33 | console.log('Attempted to create or join room', room); 34 | } 35 | 36 | socket.on('created', function(room) { 37 | console.log('Created room ' + room); 38 | isInitiator = true; 39 | }); 40 | 41 | socket.on('full', function(room) { 42 | console.log('Room ' + room + ' is full'); 43 | }); 44 | 45 | socket.on('join', function (room){ 46 | console.log('Another peer made a request to join room ' + room); 47 | console.log('This peer is the initiator of room ' + room + '!'); 48 | isChannelReady = true; 49 | }); 50 | 51 | socket.on('joined', function(room) { 52 | console.log('joined: ' + room); 53 | isChannelReady = true; 54 | }); 55 | 56 | socket.on('log', function(array) { 57 | console.log.apply(console, array); 58 | }); 59 | 60 | //////////////////////////////////////////////// 61 | 62 | function sendMessage(message) { 63 | console.log('Client sending message: ', message); 64 | socket.emit('message', message); 65 | } 66 | 67 | // This client receives a message 68 | socket.on('message', function(message) { 69 | console.log('Client received message:', message); 70 | if (message === 'got user media') { 71 | maybeStart(); 72 | } else if (message.type === 'offer') { 73 | if (!isInitiator && !isStarted) { 74 | maybeStart(); 75 | } 76 | pc.setRemoteDescription(new RTCSessionDescription(message)); 77 | doAnswer(); 78 | } else if (message.type === 'answer' && isStarted) { 79 | pc.setRemoteDescription(new RTCSessionDescription(message)); 80 | } else if (message.type === 'candidate' && isStarted) { 81 | var candidate = new RTCIceCandidate({ 82 | sdpMLineIndex: message.label, 83 | candidate: message.candidate 84 | }); 85 | pc.addIceCandidate(candidate); 86 | } else if (message === 'bye' && isStarted) { 87 | handleRemoteHangup(); 88 | } 89 | }); 90 | 91 | //////////////////////////////////////////////////// 92 | 93 | var localVideo = document.querySelector('#localVideo'); 94 | var remoteVideo = document.querySelector('#remoteVideo'); 95 | 96 | navigator.mediaDevices.getUserMedia({ 97 | audio: false, 98 | video: true 99 | }) 100 | .then(gotStream) 101 | .catch(function(e) { 102 | alert('getUserMedia() err: ' + e.name + ' ' + e.message); 103 | }); 104 | 105 | function gotStream(stream) { 106 | console.log('Adding local stream.'); 107 | localStream = stream; 108 | localVideo.srcObject = stream; 109 | sendMessage('got user media'); 110 | if (isInitiator) { 111 | maybeStart(); 112 | } 113 | } 114 | 115 | var constraints = { 116 | video: true 117 | }; 118 | 119 | console.log('Getting user media with constraints', constraints); 120 | 121 | if (location.hostname !== 'localhost') { 122 | requestTurn( 123 | 'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913' 124 | ); 125 | } 126 | 127 | function maybeStart() { 128 | console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); 129 | if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { 130 | console.log('>>>>>> creating peer connection'); 131 | createPeerConnection(); 132 | pc.addStream(localStream); 133 | isStarted = true; 134 | console.log('isInitiator', isInitiator); 135 | if (isInitiator) { 136 | doCall(); 137 | } 138 | } 139 | } 140 | 141 | window.onbeforeunload = function() { 142 | sendMessage('bye'); 143 | }; 144 | 145 | ///////////////////////////////////////////////////////// 146 | 147 | function createPeerConnection() { 148 | try { 149 | pc = new RTCPeerConnection(null); 150 | pc.onicecandidate = handleIceCandidate; 151 | pc.onaddstream = handleRemoteStreamAdded; 152 | pc.onremovestream = handleRemoteStreamRemoved; 153 | console.log('Created RTCPeerConnnection'); 154 | } catch (e) { 155 | console.log('Failed to create PeerConnection, exception: ' + e.message); 156 | alert('Cannot create RTCPeerConnection object.'); 157 | return; 158 | } 159 | } 160 | 161 | function handleIceCandidate(event) { 162 | console.log('icecandidate event: ', event); 163 | if (event.candidate) { 164 | sendMessage({ 165 | type: 'candidate', 166 | label: event.candidate.sdpMLineIndex, 167 | id: event.candidate.sdpMid, 168 | candidate: event.candidate.candidate 169 | }); 170 | } else { 171 | console.log('End of candidates.'); 172 | } 173 | } 174 | 175 | function handleCreateOfferError(event) { 176 | console.log('createOffer() error: ', event); 177 | } 178 | 179 | function doCall() { 180 | console.log('Sending offer to peer'); 181 | pc.createOffer(setLocalAndSendMessage, handleCreateOfferError); 182 | } 183 | 184 | function doAnswer() { 185 | console.log('Sending answer to peer.'); 186 | pc.createAnswer().then( 187 | setLocalAndSendMessage, 188 | onCreateSessionDescriptionError 189 | ); 190 | } 191 | 192 | function setLocalAndSendMessage(sessionDescription) { 193 | pc.setLocalDescription(sessionDescription); 194 | console.log('setLocalAndSendMessage sending message', sessionDescription); 195 | sendMessage(sessionDescription); 196 | } 197 | 198 | function onCreateSessionDescriptionError(error) { 199 | trace('Failed to create session description: ' + error.toString()); 200 | } 201 | 202 | function requestTurn(turnURL) { 203 | var turnExists = false; 204 | for (var i in pcConfig.iceServers) { 205 | if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') { 206 | turnExists = true; 207 | turnReady = true; 208 | break; 209 | } 210 | } 211 | if (!turnExists) { 212 | console.log('Getting TURN server from ', turnURL); 213 | // No TURN server. Get one from computeengineondemand.appspot.com: 214 | var xhr = new XMLHttpRequest(); 215 | xhr.onreadystatechange = function() { 216 | if (xhr.readyState === 4 && xhr.status === 200) { 217 | var turnServer = JSON.parse(xhr.responseText); 218 | console.log('Got TURN server: ', turnServer); 219 | pcConfig.iceServers.push({ 220 | 'urls': 'turn:' + turnServer.username + '@' + turnServer.turn, 221 | 'credential': turnServer.password 222 | }); 223 | turnReady = true; 224 | } 225 | }; 226 | xhr.open('GET', turnURL, true); 227 | xhr.send(); 228 | } 229 | } 230 | 231 | function handleRemoteStreamAdded(event) { 232 | console.log('Remote stream added.'); 233 | remoteStream = event.stream; 234 | remoteVideo.srcObject = remoteStream; 235 | } 236 | 237 | function handleRemoteStreamRemoved(event) { 238 | console.log('Remote stream removed. Event: ', event); 239 | } 240 | 241 | function hangup() { 242 | console.log('Hanging up.'); 243 | stop(); 244 | sendMessage('bye'); 245 | } 246 | 247 | function handleRemoteHangup() { 248 | console.log('Session terminated.'); 249 | stop(); 250 | isInitiator = false; 251 | } 252 | 253 | function stop() { 254 | isStarted = false; 255 | pc.close(); 256 | pc = null; 257 | } 258 | -------------------------------------------------------------------------------- /step4web/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isChannelReady = false; 4 | var isInitiator = false; 5 | var isStarted = false; 6 | var localStream; 7 | var pc; 8 | var remoteStream; 9 | var turnReady; 10 | 11 | var pcConfig = { 12 | 'iceServers': [{ 13 | 'urls': 'stun:stun.l.google.com:19302' 14 | }] 15 | }; 16 | 17 | // Set up audio and video regardless of what devices are present. 18 | var sdpConstraints = { 19 | offerToReceiveAudio: true, 20 | offerToReceiveVideo: true 21 | }; 22 | 23 | ///////////////////////////////////////////// 24 | 25 | var room = 'OldPlace'; 26 | // Could prompt for room name: 27 | // room = prompt('Enter room name:'); 28 | 29 | var socket = io.connect(); 30 | 31 | if (room !== '') { 32 | socket.emit('create or join', room); 33 | console.log('Attempted to create or join room', room); 34 | } 35 | 36 | socket.on('created', function(room) { 37 | console.log('Created room ' + room); 38 | isInitiator = true; 39 | }); 40 | 41 | socket.on('full', function(room) { 42 | console.log('Room ' + room + ' is full'); 43 | }); 44 | 45 | socket.on('join', function (room){ 46 | console.log('Another peer made a request to join room ' + room); 47 | console.log('This peer is the initiator of room ' + room + '!'); 48 | isChannelReady = true; 49 | }); 50 | 51 | socket.on('joined', function(room) { 52 | console.log('joined: ' + room); 53 | isChannelReady = true; 54 | }); 55 | 56 | socket.on('log', function(array) { 57 | console.log.apply(console, array); 58 | }); 59 | 60 | //////////////////////////////////////////////// 61 | 62 | function sendMessage(message) { 63 | console.log('Client sending message: ', message); 64 | socket.emit('message', message); 65 | } 66 | 67 | // This client receives a message 68 | socket.on('message', function(message) { 69 | console.log('Client received message:', message); 70 | if (message === 'got user media') { 71 | maybeStart(); 72 | } else if (message.type === 'offer') { 73 | if (!isInitiator && !isStarted) { 74 | maybeStart(); 75 | } 76 | pc.setRemoteDescription(new RTCSessionDescription(message)); 77 | doAnswer(); 78 | } else if (message.type === 'answer' && isStarted) { 79 | pc.setRemoteDescription(new RTCSessionDescription(message)); 80 | } else if (message.type === 'candidate' && isStarted) { 81 | var candidate = new RTCIceCandidate({ 82 | sdpMLineIndex: message.label, 83 | candidate: message.candidate 84 | }); 85 | pc.addIceCandidate(candidate); 86 | } else if (message === 'bye' && isStarted) { 87 | handleRemoteHangup(); 88 | } 89 | }); 90 | 91 | //////////////////////////////////////////////////// 92 | 93 | var localVideo = document.querySelector('#localVideo'); 94 | var remoteVideo = document.querySelector('#remoteVideo'); 95 | 96 | navigator.mediaDevices.getUserMedia({ 97 | audio: false, 98 | video: true 99 | }) 100 | .then(gotStream) 101 | .catch(function(e) { 102 | alert('getUserMedia() err: ' + e.name + ' ' + e.message); 103 | }); 104 | 105 | function gotStream(stream) { 106 | console.log('Adding local stream.'); 107 | localStream = stream; 108 | localVideo.srcObject = stream; 109 | sendMessage('got user media'); 110 | if (isInitiator) { 111 | maybeStart(); 112 | } 113 | } 114 | 115 | var constraints = { 116 | video: true 117 | }; 118 | 119 | console.log('Getting user media with constraints', constraints); 120 | 121 | if (location.hostname !== 'localhost') { 122 | requestTurn( 123 | 'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913' 124 | ); 125 | } 126 | 127 | function maybeStart() { 128 | console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); 129 | if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { 130 | console.log('>>>>>> creating peer connection'); 131 | createPeerConnection(); 132 | pc.addStream(localStream); 133 | isStarted = true; 134 | console.log('isInitiator', isInitiator); 135 | if (isInitiator) { 136 | doCall(); 137 | } 138 | } 139 | } 140 | 141 | window.onbeforeunload = function() { 142 | sendMessage('bye'); 143 | }; 144 | 145 | ///////////////////////////////////////////////////////// 146 | 147 | function createPeerConnection() { 148 | try { 149 | pc = new RTCPeerConnection(null); 150 | pc.onicecandidate = handleIceCandidate; 151 | pc.onaddstream = handleRemoteStreamAdded; 152 | pc.onremovestream = handleRemoteStreamRemoved; 153 | console.log('Created RTCPeerConnnection'); 154 | } catch (e) { 155 | console.log('Failed to create PeerConnection, exception: ' + e.message); 156 | alert('Cannot create RTCPeerConnection object.'); 157 | return; 158 | } 159 | } 160 | 161 | function handleIceCandidate(event) { 162 | console.log('icecandidate event: ', event); 163 | if (event.candidate) { 164 | sendMessage({ 165 | type: 'candidate', 166 | label: event.candidate.sdpMLineIndex, 167 | id: event.candidate.sdpMid, 168 | candidate: event.candidate.candidate 169 | }); 170 | } else { 171 | console.log('End of candidates.'); 172 | } 173 | } 174 | 175 | function handleCreateOfferError(event) { 176 | console.log('createOffer() error: ', event); 177 | } 178 | 179 | function doCall() { 180 | console.log('Sending offer to peer'); 181 | pc.createOffer(setLocalAndSendMessage, handleCreateOfferError); 182 | } 183 | 184 | function doAnswer() { 185 | console.log('Sending answer to peer.'); 186 | pc.createAnswer().then( 187 | setLocalAndSendMessage, 188 | onCreateSessionDescriptionError 189 | ); 190 | } 191 | 192 | function setLocalAndSendMessage(sessionDescription) { 193 | pc.setLocalDescription(sessionDescription); 194 | console.log('setLocalAndSendMessage sending message', sessionDescription); 195 | sendMessage(sessionDescription); 196 | } 197 | 198 | function onCreateSessionDescriptionError(error) { 199 | trace('Failed to create session description: ' + error.toString()); 200 | } 201 | 202 | function requestTurn(turnURL) { 203 | var turnExists = false; 204 | for (var i in pcConfig.iceServers) { 205 | if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') { 206 | turnExists = true; 207 | turnReady = true; 208 | break; 209 | } 210 | } 211 | if (!turnExists) { 212 | console.log('Getting TURN server from ', turnURL); 213 | // No TURN server. Get one from computeengineondemand.appspot.com: 214 | var xhr = new XMLHttpRequest(); 215 | xhr.onreadystatechange = function() { 216 | if (xhr.readyState === 4 && xhr.status === 200) { 217 | var turnServer = JSON.parse(xhr.responseText); 218 | console.log('Got TURN server: ', turnServer); 219 | pcConfig.iceServers.push({ 220 | 'urls': 'turn:' + turnServer.username + '@' + turnServer.turn, 221 | 'credential': turnServer.password 222 | }); 223 | turnReady = true; 224 | } 225 | }; 226 | xhr.open('GET', turnURL, true); 227 | xhr.send(); 228 | } 229 | } 230 | 231 | function handleRemoteStreamAdded(event) { 232 | console.log('Remote stream added.'); 233 | remoteStream = event.stream; 234 | remoteVideo.srcObject = remoteStream; 235 | } 236 | 237 | function handleRemoteStreamRemoved(event) { 238 | console.log('Remote stream removed. Event: ', event); 239 | } 240 | 241 | function hangup() { 242 | console.log('Hanging up.'); 243 | stop(); 244 | sendMessage('bye'); 245 | } 246 | 247 | function handleRemoteHangup() { 248 | console.log('Session terminated.'); 249 | stop(); 250 | isInitiator = false; 251 | } 252 | 253 | function stop() { 254 | isStarted = false; 255 | pc.close(); 256 | pc = null; 257 | } 258 | -------------------------------------------------------------------------------- /step3signaling/src/main/java/cc/rome753/wat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import org.json.JSONObject; 7 | import org.webrtc.AudioSource; 8 | import org.webrtc.AudioTrack; 9 | import org.webrtc.Camera1Enumerator; 10 | import org.webrtc.DefaultVideoDecoderFactory; 11 | import org.webrtc.DefaultVideoEncoderFactory; 12 | import org.webrtc.EglBase; 13 | import org.webrtc.IceCandidate; 14 | import org.webrtc.MediaConstraints; 15 | import org.webrtc.MediaStream; 16 | import org.webrtc.PeerConnection; 17 | import org.webrtc.PeerConnectionFactory; 18 | import org.webrtc.SessionDescription; 19 | import org.webrtc.SurfaceTextureHelper; 20 | import org.webrtc.SurfaceViewRenderer; 21 | import org.webrtc.VideoCapturer; 22 | import org.webrtc.VideoSource; 23 | import org.webrtc.VideoTrack; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | public class MainActivity extends AppCompatActivity implements SignalingClient.Callback { 29 | 30 | PeerConnectionFactory peerConnectionFactory; 31 | PeerConnection peerConnection; 32 | SurfaceViewRenderer localView; 33 | SurfaceViewRenderer remoteView; 34 | MediaStream mediaStream; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_main); 40 | EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext(); 41 | 42 | // create PeerConnectionFactory 43 | PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions 44 | .builder(this) 45 | .createInitializationOptions()); 46 | PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); 47 | DefaultVideoEncoderFactory defaultVideoEncoderFactory = 48 | new DefaultVideoEncoderFactory(eglBaseContext, true, true); 49 | DefaultVideoDecoderFactory defaultVideoDecoderFactory = 50 | new DefaultVideoDecoderFactory(eglBaseContext); 51 | peerConnectionFactory = PeerConnectionFactory.builder() 52 | .setOptions(options) 53 | .setVideoEncoderFactory(defaultVideoEncoderFactory) 54 | .setVideoDecoderFactory(defaultVideoDecoderFactory) 55 | .createPeerConnectionFactory(); 56 | 57 | SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext); 58 | // create VideoCapturer 59 | VideoCapturer videoCapturer = createCameraCapturer(true); 60 | VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast()); 61 | videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver()); 62 | videoCapturer.startCapture(480, 640, 30); 63 | 64 | localView = findViewById(R.id.localView); 65 | localView.setMirror(true); 66 | localView.init(eglBaseContext, null); 67 | 68 | // create VideoTrack 69 | VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource); 70 | // // display in localView 71 | videoTrack.addSink(localView); 72 | 73 | 74 | remoteView = findViewById(R.id.remoteView); 75 | remoteView.setMirror(false); 76 | remoteView.init(eglBaseContext, null); 77 | 78 | 79 | AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints()); 80 | AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("101", audioSource); 81 | 82 | mediaStream = peerConnectionFactory.createLocalMediaStream("mediaStream"); 83 | mediaStream.addTrack(videoTrack); 84 | mediaStream.addTrack(audioTrack); 85 | 86 | SignalingClient.get().setCallback(this); 87 | call(); 88 | } 89 | 90 | 91 | private void call() { 92 | List iceServers = new ArrayList<>(); 93 | iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()); 94 | peerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("localconnection") { 95 | @Override 96 | public void onIceCandidate(IceCandidate iceCandidate) { 97 | super.onIceCandidate(iceCandidate); 98 | SignalingClient.get().sendIceCandidate(iceCandidate); 99 | } 100 | 101 | @Override 102 | public void onAddStream(MediaStream mediaStream) { 103 | super.onAddStream(mediaStream); 104 | VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0); 105 | runOnUiThread(() -> { 106 | remoteVideoTrack.addSink(remoteView); 107 | }); 108 | } 109 | }); 110 | 111 | peerConnection.addStream(mediaStream); 112 | } 113 | 114 | private VideoCapturer createCameraCapturer(boolean isFront) { 115 | Camera1Enumerator enumerator = new Camera1Enumerator(false); 116 | final String[] deviceNames = enumerator.getDeviceNames(); 117 | 118 | // First, try to find front facing camera 119 | for (String deviceName : deviceNames) { 120 | if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) { 121 | VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); 122 | 123 | if (videoCapturer != null) { 124 | return videoCapturer; 125 | } 126 | } 127 | } 128 | 129 | return null; 130 | } 131 | 132 | @Override 133 | public void onCreateRoom() { 134 | 135 | } 136 | 137 | @Override 138 | public void onPeerJoined() { 139 | 140 | } 141 | 142 | @Override 143 | public void onSelfJoined() { 144 | peerConnection.createOffer(new SdpAdapter("local offer sdp") { 145 | @Override 146 | public void onCreateSuccess(SessionDescription sessionDescription) { 147 | super.onCreateSuccess(sessionDescription); 148 | peerConnection.setLocalDescription(new SdpAdapter("local set local"), sessionDescription); 149 | SignalingClient.get().sendSessionDescription(sessionDescription); 150 | } 151 | }, new MediaConstraints()); 152 | } 153 | 154 | @Override 155 | public void onPeerLeave(String msg) { 156 | 157 | } 158 | 159 | @Override 160 | public void onOfferReceived(JSONObject data) { 161 | runOnUiThread(() -> { 162 | peerConnection.setRemoteDescription(new SdpAdapter("localSetRemote"), 163 | new SessionDescription(SessionDescription.Type.OFFER, data.optString("sdp"))); 164 | peerConnection.createAnswer(new SdpAdapter("localAnswerSdp") { 165 | @Override 166 | public void onCreateSuccess(SessionDescription sdp) { 167 | super.onCreateSuccess(sdp); 168 | peerConnection.setLocalDescription(new SdpAdapter("localSetLocal"), sdp); 169 | SignalingClient.get().sendSessionDescription(sdp); 170 | } 171 | }, new MediaConstraints()); 172 | 173 | }); 174 | } 175 | 176 | @Override 177 | public void onAnswerReceived(JSONObject data) { 178 | peerConnection.setRemoteDescription(new SdpAdapter("localSetRemote"), 179 | new SessionDescription(SessionDescription.Type.ANSWER, data.optString("sdp"))); 180 | } 181 | 182 | @Override 183 | public void onIceCandidateReceived(JSONObject data) { 184 | peerConnection.addIceCandidate(new IceCandidate( 185 | data.optString("id"), 186 | data.optInt("label"), 187 | data.optString("candidate") 188 | )); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /step2loopback/src/main/java/cc/rome753/wat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | import org.webrtc.Camera1Enumerator; 8 | import org.webrtc.DefaultVideoDecoderFactory; 9 | import org.webrtc.DefaultVideoEncoderFactory; 10 | import org.webrtc.EglBase; 11 | import org.webrtc.IceCandidate; 12 | import org.webrtc.MediaConstraints; 13 | import org.webrtc.MediaStream; 14 | import org.webrtc.PeerConnection; 15 | import org.webrtc.PeerConnectionFactory; 16 | import org.webrtc.SessionDescription; 17 | import org.webrtc.SurfaceTextureHelper; 18 | import org.webrtc.SurfaceViewRenderer; 19 | import org.webrtc.VideoCapturer; 20 | import org.webrtc.VideoSource; 21 | import org.webrtc.VideoTrack; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | public class MainActivity extends AppCompatActivity { 27 | 28 | PeerConnectionFactory peerConnectionFactory; 29 | PeerConnection peerConnectionLocal; 30 | PeerConnection peerConnectionRemote; 31 | SurfaceViewRenderer localView; 32 | SurfaceViewRenderer remoteView; 33 | MediaStream mediaStreamLocal; 34 | MediaStream mediaStreamRemote; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_main); 40 | EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext(); 41 | 42 | // create PeerConnectionFactory 43 | PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions 44 | .builder(this) 45 | .createInitializationOptions()); 46 | PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); 47 | DefaultVideoEncoderFactory defaultVideoEncoderFactory = 48 | new DefaultVideoEncoderFactory(eglBaseContext, true, true); 49 | DefaultVideoDecoderFactory defaultVideoDecoderFactory = 50 | new DefaultVideoDecoderFactory(eglBaseContext); 51 | peerConnectionFactory = PeerConnectionFactory.builder() 52 | .setOptions(options) 53 | .setVideoEncoderFactory(defaultVideoEncoderFactory) 54 | .setVideoDecoderFactory(defaultVideoDecoderFactory) 55 | .createPeerConnectionFactory(); 56 | 57 | SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext); 58 | // create VideoCapturer 59 | VideoCapturer videoCapturer = createCameraCapturer(true); 60 | VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast()); 61 | videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver()); 62 | videoCapturer.startCapture(480, 640, 30); 63 | 64 | localView = findViewById(R.id.localView); 65 | localView.setMirror(true); 66 | localView.init(eglBaseContext, null); 67 | 68 | // create VideoTrack 69 | VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource); 70 | // // display in localView 71 | // videoTrack.addSink(localView); 72 | 73 | 74 | 75 | 76 | SurfaceTextureHelper remoteSurfaceTextureHelper = SurfaceTextureHelper.create("RemoteCaptureThread", eglBaseContext); 77 | // create VideoCapturer 78 | VideoCapturer remoteVideoCapturer = createCameraCapturer(false); 79 | VideoSource remoteVideoSource = peerConnectionFactory.createVideoSource(remoteVideoCapturer.isScreencast()); 80 | remoteVideoCapturer.initialize(remoteSurfaceTextureHelper, getApplicationContext(), remoteVideoSource.getCapturerObserver()); 81 | remoteVideoCapturer.startCapture(480, 640, 30); 82 | 83 | remoteView = findViewById(R.id.remoteView); 84 | remoteView.setMirror(false); 85 | remoteView.init(eglBaseContext, null); 86 | 87 | // create VideoTrack 88 | VideoTrack remoteVideoTrack = peerConnectionFactory.createVideoTrack("102", remoteVideoSource); 89 | // // display in remoteView 90 | // remoteVideoTrack.addSink(remoteView); 91 | 92 | 93 | 94 | mediaStreamLocal = peerConnectionFactory.createLocalMediaStream("mediaStreamLocal"); 95 | mediaStreamLocal.addTrack(videoTrack); 96 | 97 | mediaStreamRemote = peerConnectionFactory.createLocalMediaStream("mediaStreamRemote"); 98 | mediaStreamRemote.addTrack(remoteVideoTrack); 99 | 100 | call(mediaStreamLocal, mediaStreamRemote); 101 | } 102 | 103 | 104 | private void call(MediaStream localMediaStream, MediaStream remoteMediaStream) { 105 | List iceServers = new ArrayList<>(); 106 | peerConnectionLocal = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("localconnection") { 107 | @Override 108 | public void onIceCandidate(IceCandidate iceCandidate) { 109 | super.onIceCandidate(iceCandidate); 110 | peerConnectionRemote.addIceCandidate(iceCandidate); 111 | } 112 | 113 | @Override 114 | public void onAddStream(MediaStream mediaStream) { 115 | super.onAddStream(mediaStream); 116 | VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0); 117 | runOnUiThread(() -> { 118 | remoteVideoTrack.addSink(localView); 119 | }); 120 | } 121 | }); 122 | 123 | peerConnectionRemote = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("remoteconnection") { 124 | @Override 125 | public void onIceCandidate(IceCandidate iceCandidate) { 126 | super.onIceCandidate(iceCandidate); 127 | peerConnectionLocal.addIceCandidate(iceCandidate); 128 | } 129 | 130 | @Override 131 | public void onAddStream(MediaStream mediaStream) { 132 | super.onAddStream(mediaStream); 133 | VideoTrack localVideoTrack = mediaStream.videoTracks.get(0); 134 | runOnUiThread(() -> { 135 | localVideoTrack.addSink(remoteView); 136 | }); 137 | } 138 | }); 139 | 140 | peerConnectionLocal.addStream(localMediaStream); 141 | peerConnectionLocal.createOffer(new SdpAdapter("local offer sdp") { 142 | @Override 143 | public void onCreateSuccess(SessionDescription sessionDescription) { 144 | super.onCreateSuccess(sessionDescription); 145 | // todo crashed here 146 | peerConnectionLocal.setLocalDescription(new SdpAdapter("local set local"), sessionDescription); 147 | peerConnectionRemote.addStream(remoteMediaStream); 148 | peerConnectionRemote.setRemoteDescription(new SdpAdapter("remote set remote"), sessionDescription); 149 | peerConnectionRemote.createAnswer(new SdpAdapter("remote answer sdp") { 150 | @Override 151 | public void onCreateSuccess(SessionDescription sdp) { 152 | super.onCreateSuccess(sdp); 153 | peerConnectionRemote.setLocalDescription(new SdpAdapter("remote set local"), sdp); 154 | peerConnectionLocal.setRemoteDescription(new SdpAdapter("local set remote"), sdp); 155 | } 156 | }, new MediaConstraints()); 157 | } 158 | }, new MediaConstraints()); 159 | } 160 | 161 | private VideoCapturer createCameraCapturer(boolean isFront) { 162 | Camera1Enumerator enumerator = new Camera1Enumerator(false); 163 | final String[] deviceNames = enumerator.getDeviceNames(); 164 | 165 | // First, try to find front facing camera 166 | for (String deviceName : deviceNames) { 167 | if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) { 168 | VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); 169 | 170 | if (videoCapturer != null) { 171 | return videoCapturer; 172 | } 173 | } 174 | } 175 | 176 | return null; 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /step4multipeers/src/main/java/cc/rome753/wat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cc.rome753.wat; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import org.json.JSONObject; 7 | import org.webrtc.Camera1Enumerator; 8 | import org.webrtc.DefaultVideoDecoderFactory; 9 | import org.webrtc.DefaultVideoEncoderFactory; 10 | import org.webrtc.EglBase; 11 | import org.webrtc.IceCandidate; 12 | import org.webrtc.MediaConstraints; 13 | import org.webrtc.MediaStream; 14 | import org.webrtc.PeerConnection; 15 | import org.webrtc.PeerConnectionFactory; 16 | import org.webrtc.SdpObserver; 17 | import org.webrtc.SessionDescription; 18 | import org.webrtc.SurfaceTextureHelper; 19 | import org.webrtc.SurfaceViewRenderer; 20 | import org.webrtc.VideoCapturer; 21 | import org.webrtc.VideoSource; 22 | import org.webrtc.VideoTrack; 23 | 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | 28 | public class MainActivity extends AppCompatActivity implements SignalingClient.Callback { 29 | 30 | EglBase.Context eglBaseContext; 31 | PeerConnectionFactory peerConnectionFactory; 32 | SurfaceViewRenderer localView; 33 | MediaStream mediaStream; 34 | List iceServers; 35 | 36 | HashMap peerConnectionMap; 37 | SurfaceViewRenderer[] remoteViews; 38 | int remoteViewsIndex = 0; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_main); 44 | 45 | peerConnectionMap = new HashMap<>(); 46 | iceServers = new ArrayList<>(); 47 | iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()); 48 | 49 | eglBaseContext = EglBase.create().getEglBaseContext(); 50 | 51 | // create PeerConnectionFactory 52 | PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions 53 | .builder(this) 54 | .createInitializationOptions()); 55 | PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); 56 | DefaultVideoEncoderFactory defaultVideoEncoderFactory = 57 | new DefaultVideoEncoderFactory(eglBaseContext, true, true); 58 | DefaultVideoDecoderFactory defaultVideoDecoderFactory = 59 | new DefaultVideoDecoderFactory(eglBaseContext); 60 | peerConnectionFactory = PeerConnectionFactory.builder() 61 | .setOptions(options) 62 | .setVideoEncoderFactory(defaultVideoEncoderFactory) 63 | .setVideoDecoderFactory(defaultVideoDecoderFactory) 64 | .createPeerConnectionFactory(); 65 | 66 | SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext); 67 | // create VideoCapturer 68 | VideoCapturer videoCapturer = createCameraCapturer(true); 69 | VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast()); 70 | videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver()); 71 | videoCapturer.startCapture(480, 640, 30); 72 | 73 | localView = findViewById(R.id.localView); 74 | localView.setMirror(true); 75 | localView.init(eglBaseContext, null); 76 | 77 | // create VideoTrack 78 | VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource); 79 | // // display in localView 80 | videoTrack.addSink(localView); 81 | 82 | 83 | 84 | remoteViews = new SurfaceViewRenderer[]{ 85 | findViewById(R.id.remoteView), 86 | findViewById(R.id.remoteView2), 87 | findViewById(R.id.remoteView3), 88 | }; 89 | for(SurfaceViewRenderer remoteView : remoteViews) { 90 | remoteView.setMirror(false); 91 | remoteView.init(eglBaseContext, null); 92 | } 93 | 94 | 95 | mediaStream = peerConnectionFactory.createLocalMediaStream("mediaStream"); 96 | mediaStream.addTrack(videoTrack); 97 | 98 | SignalingClient.get().init(this); 99 | } 100 | 101 | 102 | private synchronized PeerConnection getOrCreatePeerConnection(String socketId) { 103 | PeerConnection peerConnection = peerConnectionMap.get(socketId); 104 | if(peerConnection != null) { 105 | return peerConnection; 106 | } 107 | peerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("PC:" + socketId) { 108 | @Override 109 | public void onIceCandidate(IceCandidate iceCandidate) { 110 | super.onIceCandidate(iceCandidate); 111 | SignalingClient.get().sendIceCandidate(iceCandidate, socketId); 112 | } 113 | 114 | @Override 115 | public void onAddStream(MediaStream mediaStream) { 116 | super.onAddStream(mediaStream); 117 | VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0); 118 | runOnUiThread(() -> { 119 | remoteVideoTrack.addSink(remoteViews[remoteViewsIndex++]); 120 | }); 121 | } 122 | }); 123 | peerConnection.addStream(mediaStream); 124 | peerConnectionMap.put(socketId, peerConnection); 125 | return peerConnection; 126 | } 127 | 128 | @Override 129 | public void onCreateRoom() { 130 | 131 | } 132 | 133 | @Override 134 | public void onPeerJoined(String socketId) { 135 | PeerConnection peerConnection = getOrCreatePeerConnection(socketId); 136 | peerConnection.createOffer(new SdpAdapter("createOfferSdp:" + socketId) { 137 | @Override 138 | public void onCreateSuccess(SessionDescription sessionDescription) { 139 | super.onCreateSuccess(sessionDescription); 140 | peerConnection.setLocalDescription(new SdpAdapter("setLocalSdp:" + socketId), sessionDescription); 141 | SignalingClient.get().sendSessionDescription(sessionDescription, socketId); 142 | } 143 | }, new MediaConstraints()); 144 | } 145 | 146 | @Override 147 | public void onSelfJoined() { 148 | 149 | } 150 | 151 | @Override 152 | public void onPeerLeave(String msg) { 153 | 154 | } 155 | 156 | @Override 157 | public void onOfferReceived(JSONObject data) { 158 | runOnUiThread(() -> { 159 | final String socketId = data.optString("from"); 160 | PeerConnection peerConnection = getOrCreatePeerConnection(socketId); 161 | peerConnection.setRemoteDescription(new SdpAdapter("setRemoteSdp:" + socketId), 162 | new SessionDescription(SessionDescription.Type.OFFER, data.optString("sdp"))); 163 | peerConnection.createAnswer(new SdpAdapter("localAnswerSdp") { 164 | @Override 165 | public void onCreateSuccess(SessionDescription sdp) { 166 | super.onCreateSuccess(sdp); 167 | peerConnectionMap.get(socketId).setLocalDescription(new SdpAdapter("setLocalSdp:" + socketId), sdp); 168 | SignalingClient.get().sendSessionDescription(sdp, socketId); 169 | } 170 | }, new MediaConstraints()); 171 | 172 | }); 173 | } 174 | 175 | @Override 176 | public void onAnswerReceived(JSONObject data) { 177 | String socketId = data.optString("from"); 178 | PeerConnection peerConnection = getOrCreatePeerConnection(socketId); 179 | peerConnection.setRemoteDescription(new SdpAdapter("setRemoteSdp:" + socketId), 180 | new SessionDescription(SessionDescription.Type.ANSWER, data.optString("sdp"))); 181 | } 182 | 183 | @Override 184 | public void onIceCandidateReceived(JSONObject data) { 185 | String socketId = data.optString("from"); 186 | PeerConnection peerConnection = getOrCreatePeerConnection(socketId); 187 | peerConnection.addIceCandidate(new IceCandidate( 188 | data.optString("id"), 189 | data.optInt("label"), 190 | data.optString("candidate") 191 | )); 192 | } 193 | 194 | @Override 195 | protected void onDestroy() { 196 | super.onDestroy(); 197 | SignalingClient.get().destroy(); 198 | } 199 | 200 | private VideoCapturer createCameraCapturer(boolean isFront) { 201 | Camera1Enumerator enumerator = new Camera1Enumerator(false); 202 | final String[] deviceNames = enumerator.getDeviceNames(); 203 | 204 | // First, try to find front facing camera 205 | for (String deviceName : deviceNames) { 206 | if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) { 207 | VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); 208 | 209 | if (videoCapturer != null) { 210 | return videoCapturer; 211 | } 212 | } 213 | } 214 | 215 | return null; 216 | } 217 | } 218 | --------------------------------------------------------------------------------