From 9952ebfe6997bfd660f0adc6ef0b941fe788e784 Mon Sep 17 00:00:00 2001 From: xiaowusky Date: Tue, 26 Sep 2023 17:37:42 +0800 Subject: [PATCH] =?UTF-8?q?desc:record=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../safetywatcher/watcher/ui/HomeActivity.kt | 2 +- .../watcher/utils/NV21ToBitmap.java | 39 ++++ .../watcher/utils/RecordHelper.kt | 216 +++++++++++++++++- .../com/common/commonlib/utils/BitmapUtils.kt | 2 +- 4 files changed, 246 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/yinuo/safetywatcher/watcher/utils/NV21ToBitmap.java diff --git a/app/src/main/java/com/yinuo/safetywatcher/watcher/ui/HomeActivity.kt b/app/src/main/java/com/yinuo/safetywatcher/watcher/ui/HomeActivity.kt index 484dbcd..1499bf6 100644 --- a/app/src/main/java/com/yinuo/safetywatcher/watcher/ui/HomeActivity.kt +++ b/app/src/main/java/com/yinuo/safetywatcher/watcher/ui/HomeActivity.kt @@ -101,12 +101,12 @@ class HomeActivity : NoOptionsActivity() { */ private fun setForCamera() { mClient = EasyPlayerClient(this@HomeActivity, mBinding.surface, null) { +// RecordHelper.onFrameAvailable(it) if (!AppData.hasCameraData()) { AppData.setCameraData(true) changeViewStatus() closeLoadingDialog() } -// RecordHelper.onFrameAvailable(it.array().copyOf()) watchCamera(DELAY_TIME_CHECK_CAMERA) } mClient?.play(CAMERA_URL) diff --git a/app/src/main/java/com/yinuo/safetywatcher/watcher/utils/NV21ToBitmap.java b/app/src/main/java/com/yinuo/safetywatcher/watcher/utils/NV21ToBitmap.java new file mode 100644 index 0000000..ec9f058 --- /dev/null +++ b/app/src/main/java/com/yinuo/safetywatcher/watcher/utils/NV21ToBitmap.java @@ -0,0 +1,39 @@ +package com.yinuo.safetywatcher.watcher.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicYuvToRGB; +import android.renderscript.Type; + +/** + * Created by caydencui on 2018/12/7. + */ +public class NV21ToBitmap { + private RenderScript rs; + private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic; + private Type.Builder yuvType, rgbaType; + private Allocation in, out; + + public NV21ToBitmap(Context context) { + rs = RenderScript.create(context); + yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)); + } + + public Bitmap nv21ToBitmap(byte[] nv21, int width, int height) { + if (yuvType == null) { + yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length); + in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT); + rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height); + out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT); + } + in.copyFrom(nv21); + yuvToRgbIntrinsic.setInput(in); + yuvToRgbIntrinsic.forEach(out); + Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + out.copyTo(bmpout); + return bmpout; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yinuo/safetywatcher/watcher/utils/RecordHelper.kt b/app/src/main/java/com/yinuo/safetywatcher/watcher/utils/RecordHelper.kt index 5d7518c..d765eba 100644 --- a/app/src/main/java/com/yinuo/safetywatcher/watcher/utils/RecordHelper.kt +++ b/app/src/main/java/com/yinuo/safetywatcher/watcher/utils/RecordHelper.kt @@ -1,34 +1,81 @@ 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 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 - private val mVideoBitmap: Bitmap + val width = 1920 + val height = 1080 + + val utils by lazy { + NV21ToBitmap(CommonApplication.getContext()) + } + + private val mTaskQueue: BlockingQueue = LinkedBlockingQueue() init { - val width = 1920 - val height = 1080 mVideoEncoder = MovieEncoder1(CommonApplication.getContext(), width, height, true) - mVideoBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + 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()) + } + } + }.start() } - fun onFrameAvailable(buffer: ByteBuffer?) { + fun onFrameAvailable(buffer: ByteBuffer) { + val input = ByteArray(buffer.capacity()) + buffer.clear() + buffer[input] + TxtOverlay.setShowTip("111@2222") val overLayBitmap: Bitmap? = TxtOverlay.getOverlayBitmap() - mVideoBitmap.copyPixelsFromBuffer(buffer?.position(0)) - if (overLayBitmap != null) { - val mVideoBitmap = BitmapUtils.mergeBitmap(mVideoBitmap, overLayBitmap) - buffer?.clear() - mVideoBitmap?.copyPixelsToBuffer(buffer) + 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) } - mVideoEncoder.frameAvailable(buffer?.array(), System.nanoTime()) + + val yuvByBitmap = getYUVByBitmap(mVideoBitmap) + buffer.clear() + buffer.limit(yuvByBitmap!!.size) + buffer.put(yuvByBitmap) } fun startRecording() { @@ -42,4 +89,151 @@ 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 + } } diff --git a/library-common/src/main/java/com/common/commonlib/utils/BitmapUtils.kt b/library-common/src/main/java/com/common/commonlib/utils/BitmapUtils.kt index 7f519a3..693df8a 100644 --- a/library-common/src/main/java/com/common/commonlib/utils/BitmapUtils.kt +++ b/library-common/src/main/java/com/common/commonlib/utils/BitmapUtils.kt @@ -11,7 +11,7 @@ object BitmapUtils { * @param frontBitmap 盖在上面的位图 * @return */ - fun mergeBitmap(backBitmap: Bitmap?, frontBitmap: Bitmap?): Bitmap? { + fun mergeBitmap(backBitmap: Bitmap, frontBitmap: Bitmap?): Bitmap { if (backBitmap == null || backBitmap.isRecycled || frontBitmap == null || frontBitmap.isRecycled) { return backBitmap }