|
|
|
@ -1,81 +1,55 @@
|
|
|
|
|
package com.yinuo.safetywatcher.watcher.utils
|
|
|
|
|
|
|
|
|
|
import android.graphics.Bitmap
|
|
|
|
|
import android.graphics.BitmapFactory
|
|
|
|
|
import android.graphics.ImageFormat
|
|
|
|
|
import android.graphics.Rect
|
|
|
|
|
import android.graphics.YuvImage
|
|
|
|
|
import android.opengl.EGL14
|
|
|
|
|
import android.os.Handler
|
|
|
|
|
import android.os.HandlerThread
|
|
|
|
|
import android.view.TextureView
|
|
|
|
|
import com.common.commonlib.CommonApplication
|
|
|
|
|
import com.common.commonlib.utils.BitmapUtils
|
|
|
|
|
import com.yinuo.library.vlc.encoder.BaseMovieEncoder.EncoderConfig
|
|
|
|
|
import com.yinuo.library.vlc.encoder.MovieEncoder1
|
|
|
|
|
import org.easydarwin.TxtOverlay
|
|
|
|
|
import org.easydarwin.sw.JNIUtil
|
|
|
|
|
import java.io.ByteArrayOutputStream
|
|
|
|
|
import java.io.IOException
|
|
|
|
|
import java.nio.ByteBuffer
|
|
|
|
|
import java.util.concurrent.BlockingQueue
|
|
|
|
|
import java.util.concurrent.LinkedBlockingQueue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
object RecordHelper {
|
|
|
|
|
private val mVideoEncoder: MovieEncoder1
|
|
|
|
|
val width = 1920
|
|
|
|
|
val height = 1080
|
|
|
|
|
private const val width = 1920
|
|
|
|
|
private const val height = 1080
|
|
|
|
|
|
|
|
|
|
val utils by lazy {
|
|
|
|
|
NV21ToBitmap(CommonApplication.getContext())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val mTaskQueue: BlockingQueue<ByteArray> = LinkedBlockingQueue()
|
|
|
|
|
private val workHandler by lazy {
|
|
|
|
|
val mHandlerThread = HandlerThread("recordAndEncode")
|
|
|
|
|
mHandlerThread.start()
|
|
|
|
|
Handler(mHandlerThread.looper)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
mVideoEncoder = MovieEncoder1(CommonApplication.getContext(), width, height, true)
|
|
|
|
|
Thread {
|
|
|
|
|
while (true) {
|
|
|
|
|
val take = mTaskQueue.take()
|
|
|
|
|
if (take != null) {
|
|
|
|
|
val overLayBitmap: Bitmap? = TxtOverlay.getOverlayBitmap()
|
|
|
|
|
val dst = ByteArray(take.size)
|
|
|
|
|
JNIUtil.ConvertFromI420(take, dst, width, height, 2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mVideoBitmap = utils.nv21ToBitmap(dst, width, height)
|
|
|
|
|
if (overLayBitmap != null && mVideoBitmap != null) {
|
|
|
|
|
mVideoBitmap = BitmapUtils.mergeBitmap(mVideoBitmap!!, overLayBitmap)
|
|
|
|
|
}
|
|
|
|
|
val baos2 = ByteArrayOutputStream()
|
|
|
|
|
mVideoBitmap?.compress(Bitmap.CompressFormat.PNG, 100, baos2)
|
|
|
|
|
val data = baos2.toByteArray()
|
|
|
|
|
mVideoEncoder.frameAvailable(data, System.nanoTime())
|
|
|
|
|
fun onFrameAvailable(view: TextureView) {
|
|
|
|
|
if (!mVideoEncoder.isRecording) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
workHandler.post {
|
|
|
|
|
val nanoTime = System.nanoTime()
|
|
|
|
|
var bitmap = view.bitmap
|
|
|
|
|
bitmap?.let {
|
|
|
|
|
val overLayBitmap: Bitmap? = TxtOverlay.getOverlayBitmap()
|
|
|
|
|
overLayBitmap?.let {
|
|
|
|
|
bitmap = BitmapUtils.mergeBitmap(bitmap!!, overLayBitmap)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}.start()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun onFrameAvailable(buffer: ByteBuffer) {
|
|
|
|
|
val input = ByteArray(buffer.capacity())
|
|
|
|
|
buffer.clear()
|
|
|
|
|
buffer[input]
|
|
|
|
|
TxtOverlay.setShowTip("111@2222")
|
|
|
|
|
val overLayBitmap: Bitmap? = TxtOverlay.getOverlayBitmap()
|
|
|
|
|
val dst = ByteArray(buffer.capacity())
|
|
|
|
|
JNIUtil.ConvertFromI420(input, dst, width, height, 2)
|
|
|
|
|
|
|
|
|
|
val yuvimage = YuvImage(dst, ImageFormat.NV21, width, height, null)
|
|
|
|
|
val baos = ByteArrayOutputStream()
|
|
|
|
|
yuvimage.compressToJpeg(Rect(0, 0, width, height), 80, baos)
|
|
|
|
|
val toByteArray = baos.toByteArray()
|
|
|
|
|
var mVideoBitmap = BitmapFactory.decodeByteArray(toByteArray, 0, toByteArray.size)
|
|
|
|
|
baos.close()
|
|
|
|
|
if (overLayBitmap != null && mVideoBitmap != null) {
|
|
|
|
|
mVideoBitmap = BitmapUtils.mergeBitmap(mVideoBitmap!!, overLayBitmap)
|
|
|
|
|
val buffer = ByteBuffer.allocate(bitmap!!.getByteCount())
|
|
|
|
|
bitmap!!.copyPixelsToBuffer(buffer)
|
|
|
|
|
bitmap!!.recycle()
|
|
|
|
|
mVideoEncoder.frameAvailable(buffer.array(), nanoTime)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val yuvByBitmap = getYUVByBitmap(mVideoBitmap)
|
|
|
|
|
buffer.clear()
|
|
|
|
|
buffer.limit(yuvByBitmap!!.size)
|
|
|
|
|
buffer.put(yuvByBitmap)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun startRecording() {
|
|
|
|
@ -89,151 +63,4 @@ object RecordHelper {
|
|
|
|
|
mVideoEncoder.stopRecording()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun rgb2YCbCr420(pixels: IntArray, width: Int, height: Int): ByteArray {
|
|
|
|
|
val len = width * height
|
|
|
|
|
// yuv格式数组大小,y亮度占len长度,u,v各占len/4长度。
|
|
|
|
|
val yuv = ByteArray(len * 3 / 2)
|
|
|
|
|
var y: Int
|
|
|
|
|
var u: Int
|
|
|
|
|
var v: Int
|
|
|
|
|
for (i in 0 until height) {
|
|
|
|
|
for (j in 0 until width) {
|
|
|
|
|
// 屏蔽ARGB的透明度值
|
|
|
|
|
val rgb = pixels[i * width + j] and 0x00FFFFFF
|
|
|
|
|
// 像素的颜色顺序为bgr,移位运算。
|
|
|
|
|
val r = rgb and 0xFF
|
|
|
|
|
val g = rgb shr 8 and 0xFF
|
|
|
|
|
val b = rgb shr 16 and 0xFF
|
|
|
|
|
// 套用公式
|
|
|
|
|
y = (66 * r + 129 * g + 25 * b + 128 shr 8) + 16
|
|
|
|
|
u = (-38 * r - 74 * g + 112 * b + 128 shr 8) + 128
|
|
|
|
|
v = (112 * r - 94 * g - 18 * b + 128 shr 8) + 128
|
|
|
|
|
// rgb2yuv
|
|
|
|
|
// y = (int) (0.299 * r + 0.587 * g + 0.114 * b);
|
|
|
|
|
// u = (int) (-0.147 * r - 0.289 * g + 0.437 * b);
|
|
|
|
|
// v = (int) (0.615 * r - 0.515 * g - 0.1 * b);
|
|
|
|
|
// RGB转换YCbCr
|
|
|
|
|
// y = (int) (0.299 * r + 0.587 * g + 0.114 * b);
|
|
|
|
|
// u = (int) (-0.1687 * r - 0.3313 * g + 0.5 * b + 128);
|
|
|
|
|
// if (u > 255)
|
|
|
|
|
// u = 255;
|
|
|
|
|
// v = (int) (0.5 * r - 0.4187 * g - 0.0813 * b + 128);
|
|
|
|
|
// if (v > 255)
|
|
|
|
|
// v = 255;
|
|
|
|
|
// 调整
|
|
|
|
|
y = if (y < 16) 16 else if (y > 255) 255 else y
|
|
|
|
|
u = if (u < 0) 0 else if (u > 255) 255 else u
|
|
|
|
|
v = if (v < 0) 0 else if (v > 255) 255 else v
|
|
|
|
|
// 赋值
|
|
|
|
|
yuv[i * width + j] = y.toByte()
|
|
|
|
|
yuv[len + ((i shr 1) * width) + (j and 1.inv()) + 0] = u.toByte()
|
|
|
|
|
yuv[len + (+(i shr 1) * width) + (j and 1.inv()) + 1] = v.toByte()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return yuv
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun decodeYUV420SP(
|
|
|
|
|
rgbBuf: ByteArray?, yuv420sp: ByteArray?,
|
|
|
|
|
width: Int, height: Int
|
|
|
|
|
) {
|
|
|
|
|
val frameSize = width * height
|
|
|
|
|
if (rgbBuf == null) throw NullPointerException("buffer 'rgbBuf' is null")
|
|
|
|
|
if (rgbBuf.size < frameSize * 3) throw IllegalArgumentException(
|
|
|
|
|
("buffer 'rgbBuf' size "
|
|
|
|
|
+ rgbBuf.size + " < minimum ") + frameSize * 3
|
|
|
|
|
)
|
|
|
|
|
if (yuv420sp == null) throw NullPointerException("buffer 'yuv420sp' is null")
|
|
|
|
|
if (yuv420sp.size < frameSize * 3 / 2) throw IllegalArgumentException(
|
|
|
|
|
("buffer 'yuv420sp' size "
|
|
|
|
|
+ yuv420sp.size + " < minimum " + (frameSize * 3 / 2))
|
|
|
|
|
)
|
|
|
|
|
var i = 0
|
|
|
|
|
var y = 0
|
|
|
|
|
var uvp = 0
|
|
|
|
|
var u = 0
|
|
|
|
|
var v = 0
|
|
|
|
|
var y1192 = 0
|
|
|
|
|
var r = 0
|
|
|
|
|
var g = 0
|
|
|
|
|
var b = 0
|
|
|
|
|
var j = 0
|
|
|
|
|
var yp = 0
|
|
|
|
|
while (j < height) {
|
|
|
|
|
uvp = frameSize + (j shr 1) * width
|
|
|
|
|
u = 0
|
|
|
|
|
v = 0
|
|
|
|
|
i = 0
|
|
|
|
|
while (i < width) {
|
|
|
|
|
y = (0xff and (yuv420sp[yp].toInt())) - 16
|
|
|
|
|
if (y < 0) y = 0
|
|
|
|
|
if ((i and 1) == 0) {
|
|
|
|
|
v = (0xff and yuv420sp[uvp++].toInt()) - 128
|
|
|
|
|
u = (0xff and yuv420sp[uvp++].toInt()) - 128
|
|
|
|
|
}
|
|
|
|
|
y1192 = 1192 * y
|
|
|
|
|
r = (y1192 + 1634 * v)
|
|
|
|
|
g = (y1192 - (833 * v) - (400 * u))
|
|
|
|
|
b = (y1192 + 2066 * u)
|
|
|
|
|
if (r < 0) r = 0 else if (r > 262143) r = 262143
|
|
|
|
|
if (g < 0) g = 0 else if (g > 262143) g = 262143
|
|
|
|
|
if (b < 0) b = 0 else if (b > 262143) b = 262143
|
|
|
|
|
rgbBuf[yp * 3] = (r shr 10).toByte()
|
|
|
|
|
rgbBuf[yp * 3 + 1] = (g shr 10).toByte()
|
|
|
|
|
rgbBuf[yp * 3 + 2] = (b shr 10).toByte()
|
|
|
|
|
i++
|
|
|
|
|
yp++
|
|
|
|
|
}
|
|
|
|
|
j++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 获取位图的RGB数据
|
|
|
|
|
*/
|
|
|
|
|
fun getRGBByBitmap(bitmap: Bitmap?): ByteArray? {
|
|
|
|
|
if (bitmap == null) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
val width = bitmap.width
|
|
|
|
|
val height = bitmap.height
|
|
|
|
|
val size = width * height
|
|
|
|
|
val pixels = IntArray(size)
|
|
|
|
|
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
|
|
|
|
return convertColorToByte(pixels)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 获取位图的YUV数据
|
|
|
|
|
*/
|
|
|
|
|
fun getYUVByBitmap(bitmap: Bitmap?): ByteArray? {
|
|
|
|
|
if (bitmap == null) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
val width = bitmap.width
|
|
|
|
|
val height = bitmap.height
|
|
|
|
|
val size = width * height
|
|
|
|
|
val pixels = IntArray(size)
|
|
|
|
|
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
|
|
|
|
|
|
|
|
|
// byte[] data = convertColorToByte(pixels);
|
|
|
|
|
return rgb2YCbCr420(pixels, width, height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 像素数组转化为RGB数组
|
|
|
|
|
*/
|
|
|
|
|
fun convertColorToByte(color: IntArray?): ByteArray? {
|
|
|
|
|
if (color == null) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
val data = ByteArray(color.size * 3)
|
|
|
|
|
for (i in color.indices) {
|
|
|
|
|
data[i * 3] = ((color[i] shr 16) and 0xff).toByte()
|
|
|
|
|
data[i * 3 + 1] = ((color[i] shr 8) and 0xff).toByte()
|
|
|
|
|
data[i * 3 + 2] = (color[i] and 0xff).toByte()
|
|
|
|
|
}
|
|
|
|
|
return data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|