fix circle video stream
parent
a08e801918
commit
c67f3d10ad
@ -0,0 +1,379 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.PixelFormat
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.view.View
|
||||
import com.opentok.android.BaseVideoRenderer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import java.nio.ShortBuffer
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
/*
|
||||
* https://nhancv.medium.com/android-how-to-make-a-circular-view-as-a-thumbnail-of-opentok-27992aee15c9
|
||||
* to solve make circle video stream
|
||||
* */
|
||||
|
||||
class DynamicVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
||||
private val mRenderer: MyRenderer
|
||||
|
||||
interface DynamicVideoRendererMetadataListener {
|
||||
fun onMetadataReady(metadata: ByteArray?)
|
||||
}
|
||||
|
||||
fun setDynamicVideoRendererMetadataListener(metadataListener: DynamicVideoRendererMetadataListener?) {
|
||||
mRenderer.metadataListener = metadataListener
|
||||
}
|
||||
|
||||
fun enableThumbnailCircle(enable: Boolean) {
|
||||
mRenderer.requestEnableThumbnailCircle = enable
|
||||
}
|
||||
|
||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
||||
var mTextureIds = IntArray(3)
|
||||
var mScaleMatrix = FloatArray(16)
|
||||
private val mVertexBuffer: FloatBuffer
|
||||
private val mTextureBuffer: FloatBuffer
|
||||
private val mDrawListBuffer: ShortBuffer
|
||||
var requestEnableThumbnailCircle = false
|
||||
var mVideoFitEnabled = true
|
||||
var mVideoDisabled = false
|
||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
||||
|
||||
// vertices
|
||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
varying vec2 vTextureCoord;
|
||||
void main() {
|
||||
gl_Position = uMVPMatrix * aPosition;
|
||||
vTextureCoord = aTextureCoord;
|
||||
}
|
||||
"""
|
||||
private val fragmentShaderCode = """precision mediump float;
|
||||
uniform sampler2D Ytex;
|
||||
uniform sampler2D Utex,Vtex;
|
||||
uniform int enableCircle;
|
||||
uniform vec2 radiusDp;
|
||||
varying vec2 vTextureCoord;
|
||||
void main(void) {
|
||||
float nx,ny,r,g,b,y,u,v;
|
||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
||||
ny=vTextureCoord[1];
|
||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
||||
y=1.1643*(y-0.0625);
|
||||
u=u-0.5;
|
||||
v=v-0.5;
|
||||
r=y+1.5958*v;
|
||||
g=y-0.39173*u-0.81290*v;
|
||||
b=y+2.017*u;
|
||||
if (enableCircle > 0) {
|
||||
float radius = 0.5;
|
||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 color1 = vec4(r, g, b, 1.0);
|
||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
||||
float t = 1.0;
|
||||
if (dist < 0.0) t = 0.0;
|
||||
gl_FragColor = mix(color0, color1, t);
|
||||
}
|
||||
else {
|
||||
gl_FragColor = vec4(r, g, b, 1.0);
|
||||
}
|
||||
}
|
||||
"""
|
||||
var mFrameLock = ReentrantLock()
|
||||
var mCurrentFrame: Frame? = null
|
||||
private var mProgram = 0
|
||||
private var mTextureWidth = 0
|
||||
private var mTextureHeight = 0
|
||||
private var mViewportWidth = 0
|
||||
private var mViewportHeight = 0
|
||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
||||
vertexShaderCode)
|
||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
||||
fragmentShaderCode)
|
||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
||||
// Program
|
||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
||||
// shader to program
|
||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
||||
// shader to
|
||||
// program
|
||||
GLES20.glLinkProgram(mProgram)
|
||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aPosition")
|
||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aTextureCoord")
|
||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
||||
mVertexBuffer)
|
||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
||||
GLES20.glVertexAttribPointer(textureHandle,
|
||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
||||
GLES20.glUseProgram(mProgram)
|
||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
|
||||
fun enableThumbnailCircle(enable: Boolean) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
val enableCircleLocation = GLES20.glGetUniformLocation(mProgram, "enableCircle")
|
||||
GLES20.glUniform1i(enableCircleLocation, if (enable) 1 else 0)
|
||||
}
|
||||
|
||||
fun setupTextures(frame: Frame) {
|
||||
if (mTextureIds[0] != 0) {
|
||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
||||
}
|
||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
||||
val w = frame.width
|
||||
val h = frame.height
|
||||
val hw = w + 1 shr 1
|
||||
val hh = h + 1 shr 1
|
||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
||||
mTextureWidth = frame.width
|
||||
mTextureHeight = frame.height
|
||||
}
|
||||
|
||||
fun updateTextures(frame: Frame) {
|
||||
val width = frame.width
|
||||
val height = frame.height
|
||||
val half_width = width + 1 shr 1
|
||||
val half_height = height + 1 shr 1
|
||||
val y_size = width * height
|
||||
val uv_size = half_width * half_height
|
||||
val bb = frame.buffer
|
||||
// If we are reusing this frame, make sure we reset position and
|
||||
// limit
|
||||
bb.clear()
|
||||
if (bb.remaining() == y_size + uv_size * 2) {
|
||||
bb.position(0)
|
||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
||||
bb)
|
||||
bb.position(y_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
bb.position(y_size + uv_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
} else {
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
mViewportWidth = width
|
||||
mViewportHeight = height
|
||||
}
|
||||
|
||||
var metadataListener: DynamicVideoRendererMetadataListener? = null
|
||||
override fun onDrawFrame(gl: GL10) {
|
||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
if (mTextureWidth != mCurrentFrame!!.width
|
||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
||||
setupTextures(mCurrentFrame!!)
|
||||
}
|
||||
updateTextures(mCurrentFrame!!)
|
||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
||||
var scaleX = 1.0f
|
||||
var scaleY = 1.0f
|
||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
||||
/ mCurrentFrame!!.height)
|
||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
||||
if (mVideoFitEnabled) {
|
||||
if (ratio > vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
} else {
|
||||
if (ratio < vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
}
|
||||
Matrix.scaleM(mScaleMatrix, 0,
|
||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
||||
scaleY, 1f)
|
||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
||||
"uMVPMatrix")
|
||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
||||
mScaleMatrix, 0)
|
||||
enableThumbnailCircle(requestEnableThumbnailCircle)
|
||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
||||
} else {
|
||||
//black frame when video is disabled
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun displayFrame(frame: Frame?) {
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = frame
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun disableVideo(b: Boolean) {
|
||||
mFrameLock.lock()
|
||||
mVideoDisabled = b
|
||||
if (mVideoDisabled) {
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = null
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
||||
mVideoFitEnabled = enableVideoFit
|
||||
}
|
||||
|
||||
companion object {
|
||||
// number of coordinates per vertex in this array
|
||||
const val COORDS_PER_VERTEX = 3
|
||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
||||
var mXYZCoords = floatArrayOf(
|
||||
-1.0f, 1.0f, 0.0f, // top left
|
||||
-1.0f, -1.0f, 0.0f, // bottom left
|
||||
1.0f, -1.0f, 0.0f, // bottom right
|
||||
1.0f, 1.0f, 0.0f // top right
|
||||
)
|
||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
||||
GLES20.glActiveTexture(name)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
||||
width, height, 0, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, null)
|
||||
}
|
||||
|
||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
||||
val shader = GLES20.glCreateShader(type)
|
||||
GLES20.glShaderSource(shader, shaderCode)
|
||||
GLES20.glCompileShader(shader)
|
||||
return shader
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
||||
bb.order(ByteOrder.nativeOrder())
|
||||
mVertexBuffer = bb.asFloatBuffer()
|
||||
mVertexBuffer.put(mXYZCoords)
|
||||
mVertexBuffer.position(0)
|
||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
||||
tb.order(ByteOrder.nativeOrder())
|
||||
mTextureBuffer = tb.asFloatBuffer()
|
||||
mTextureBuffer.put(mUVCoords)
|
||||
mTextureBuffer.position(0)
|
||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
||||
dlb.order(ByteOrder.nativeOrder())
|
||||
mDrawListBuffer = dlb.asShortBuffer()
|
||||
mDrawListBuffer.put(mVertexIndex)
|
||||
mDrawListBuffer.position(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFrame(frame: Frame) {
|
||||
mRenderer.displayFrame(frame)
|
||||
mView.requestRender()
|
||||
}
|
||||
|
||||
override fun setStyle(key: String, value: String) {
|
||||
if (STYLE_VIDEO_SCALE == key) {
|
||||
if (STYLE_VIDEO_FIT == value) {
|
||||
mRenderer.enableVideoFit(true)
|
||||
} else if (STYLE_VIDEO_FILL == value) {
|
||||
mRenderer.enableVideoFit(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
||||
mRenderer.disableVideo(!videoEnabled)
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
return mView
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mView.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
mView.onResume()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
||||
}
|
||||
|
||||
init {
|
||||
mView.setEGLContextClientVersion(2)
|
||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
mRenderer = MyRenderer()
|
||||
mView.setRenderer(mRenderer)
|
||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
||||
}
|
||||
}
|
@ -0,0 +1,357 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.PixelFormat
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.view.View
|
||||
import com.opentok.android.BaseVideoRenderer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import java.nio.ShortBuffer
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
|
||||
class ThumbnailCircleVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
||||
private val mRenderer: MyRenderer
|
||||
|
||||
interface ThumbnailCircleVideoRendererMetadataListener {
|
||||
fun onMetadataReady(metadata: ByteArray?)
|
||||
}
|
||||
|
||||
fun setThumbnailCircleVideoRendererMetadataListener(metadataListener: ThumbnailCircleVideoRendererMetadataListener?) {
|
||||
mRenderer.metadataListener = metadataListener
|
||||
}
|
||||
|
||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
||||
var mTextureIds = IntArray(3)
|
||||
var mScaleMatrix = FloatArray(16)
|
||||
private val mVertexBuffer: FloatBuffer
|
||||
private val mTextureBuffer: FloatBuffer
|
||||
private val mDrawListBuffer: ShortBuffer
|
||||
var mVideoFitEnabled = true
|
||||
var mVideoDisabled = false
|
||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
||||
|
||||
// vertices
|
||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
varying vec2 vTextureCoord;
|
||||
void main() {
|
||||
gl_Position = uMVPMatrix * aPosition;
|
||||
vTextureCoord = aTextureCoord;
|
||||
}
|
||||
"""
|
||||
private val fragmentShaderCode = """precision mediump float;
|
||||
uniform sampler2D Ytex;
|
||||
uniform sampler2D Utex,Vtex;
|
||||
uniform vec2 radiusDp;
|
||||
varying vec2 vTextureCoord;
|
||||
void main(void) {
|
||||
float nx,ny,r,g,b,y,u,v;
|
||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
||||
ny=vTextureCoord[1];
|
||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
||||
y=1.1643*(y-0.0625);
|
||||
u=u-0.5;
|
||||
v=v-0.5;
|
||||
r=y+1.5958*v;
|
||||
g=y-0.39173*u-0.81290*v;
|
||||
b=y+2.017*u;
|
||||
float radius = 0.5;
|
||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 color1 = vec4(r, g, b, 1.0);
|
||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
||||
float t = 1.0;
|
||||
if (dist < 0.0) t = 0.0;
|
||||
gl_FragColor = mix(color0, color1, t);
|
||||
}
|
||||
"""
|
||||
var mFrameLock = ReentrantLock()
|
||||
var mCurrentFrame: Frame? = null
|
||||
private var mProgram = 0
|
||||
private var mTextureWidth = 0
|
||||
private var mTextureHeight = 0
|
||||
private var mViewportWidth = 0
|
||||
private var mViewportHeight = 0
|
||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
||||
vertexShaderCode)
|
||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
||||
fragmentShaderCode)
|
||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
||||
// Program
|
||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
||||
// shader to program
|
||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
||||
// shader to
|
||||
// program
|
||||
GLES20.glLinkProgram(mProgram)
|
||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aPosition")
|
||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aTextureCoord")
|
||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
||||
mVertexBuffer)
|
||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
||||
GLES20.glVertexAttribPointer(textureHandle,
|
||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
||||
GLES20.glUseProgram(mProgram)
|
||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
|
||||
fun setupTextures(frame: Frame) {
|
||||
if (mTextureIds[0] != 0) {
|
||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
||||
}
|
||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
||||
val w = frame.width
|
||||
val h = frame.height
|
||||
val hw = w + 1 shr 1
|
||||
val hh = h + 1 shr 1
|
||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
||||
mTextureWidth = frame.width
|
||||
mTextureHeight = frame.height
|
||||
}
|
||||
|
||||
fun updateTextures(frame: Frame) {
|
||||
val width = frame.width
|
||||
val height = frame.height
|
||||
val half_width = width + 1 shr 1
|
||||
val half_height = height + 1 shr 1
|
||||
val y_size = width * height
|
||||
val uv_size = half_width * half_height
|
||||
val bb = frame.buffer
|
||||
// If we are reusing this frame, make sure we reset position and
|
||||
// limit
|
||||
bb.clear()
|
||||
if (bb.remaining() == y_size + uv_size * 2) {
|
||||
bb.position(0)
|
||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
||||
bb)
|
||||
bb.position(y_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
bb.position(y_size + uv_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
} else {
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
mViewportWidth = width
|
||||
mViewportHeight = height
|
||||
}
|
||||
|
||||
var metadataListener: ThumbnailCircleVideoRendererMetadataListener? = null
|
||||
override fun onDrawFrame(gl: GL10) {
|
||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
if (mTextureWidth != mCurrentFrame!!.width
|
||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
||||
setupTextures(mCurrentFrame!!)
|
||||
}
|
||||
updateTextures(mCurrentFrame!!)
|
||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
||||
var scaleX = 1.0f
|
||||
var scaleY = 1.0f
|
||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
||||
/ mCurrentFrame!!.height)
|
||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
||||
if (mVideoFitEnabled) {
|
||||
if (ratio > vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
} else {
|
||||
if (ratio < vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
}
|
||||
Matrix.scaleM(mScaleMatrix, 0,
|
||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
||||
scaleY, 1f)
|
||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
||||
"uMVPMatrix")
|
||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
||||
mScaleMatrix, 0)
|
||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
||||
} else {
|
||||
//black frame when video is disabled
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun displayFrame(frame: Frame?) {
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = frame
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun disableVideo(b: Boolean) {
|
||||
mFrameLock.lock()
|
||||
mVideoDisabled = b
|
||||
if (mVideoDisabled) {
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = null
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
||||
mVideoFitEnabled = enableVideoFit
|
||||
}
|
||||
|
||||
companion object {
|
||||
// number of coordinates per vertex in this array
|
||||
const val COORDS_PER_VERTEX = 3
|
||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
||||
var mXYZCoords = floatArrayOf(
|
||||
-1.0f, 1.0f, 0.0f, // top left
|
||||
-1.0f, -1.0f, 0.0f, // bottom left
|
||||
1.0f, -1.0f, 0.0f, // bottom right
|
||||
1.0f, 1.0f, 0.0f // top right
|
||||
)
|
||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
||||
GLES20.glActiveTexture(name)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
||||
width, height, 0, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, null)
|
||||
}
|
||||
|
||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
||||
val shader = GLES20.glCreateShader(type)
|
||||
GLES20.glShaderSource(shader, shaderCode)
|
||||
GLES20.glCompileShader(shader)
|
||||
return shader
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
||||
bb.order(ByteOrder.nativeOrder())
|
||||
mVertexBuffer = bb.asFloatBuffer()
|
||||
mVertexBuffer.put(mXYZCoords)
|
||||
mVertexBuffer.position(0)
|
||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
||||
tb.order(ByteOrder.nativeOrder())
|
||||
mTextureBuffer = tb.asFloatBuffer()
|
||||
mTextureBuffer.put(mUVCoords)
|
||||
mTextureBuffer.position(0)
|
||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
||||
dlb.order(ByteOrder.nativeOrder())
|
||||
mDrawListBuffer = dlb.asShortBuffer()
|
||||
mDrawListBuffer.put(mVertexIndex)
|
||||
mDrawListBuffer.position(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFrame(frame: Frame) {
|
||||
mRenderer.displayFrame(frame)
|
||||
mView.requestRender()
|
||||
}
|
||||
|
||||
override fun setStyle(key: String, value: String) {
|
||||
if (STYLE_VIDEO_SCALE == key) {
|
||||
if (STYLE_VIDEO_FIT == value) {
|
||||
mRenderer.enableVideoFit(true)
|
||||
} else if (STYLE_VIDEO_FILL == value) {
|
||||
mRenderer.enableVideoFit(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
||||
mRenderer.disableVideo(!videoEnabled)
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
return mView
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mView.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
mView.onResume()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
||||
}
|
||||
|
||||
init {
|
||||
mView.setEGLContextClientVersion(2)
|
||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
mRenderer = MyRenderer()
|
||||
mView.setRenderer(mRenderer)
|
||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue