From d0aec90013f06ed4b258235bcabe02e50550271a Mon Sep 17 00:00:00 2001
From: Runt <qingingrunt2010@qq.com>
Date: Mon, 14 Apr 2025 07:40:27 +0000
Subject: [PATCH] 绘制文本功能完善

---
 app/src/main/java/com/runt/live/cpp/LiveMiniView.java      |   43 ++++
 app/src/main/java/com/runt/live/data/LiveStreams.kt        |    9 +
 app/src/main/cpp/server_global.h                           |    5 
 app/src/main/cpp/live_view_call.cpp                        |   51 +++++
 app/src/main/cpp/live_view_call.h                          |   25 ++
 app/src/main/java/com/runt/live/util/BitmapUtils.kt        |    2 
 app/src/main/java/com/runt/live/ui/stream/LiveViewModel.kt |   62 ------
 app/src/main/cpp/native-lib.cpp                            |   63 ------
 app/src/main/cpp/server_global.cpp                         |  232 +++++++++++++++++++------
 9 files changed, 308 insertions(+), 184 deletions(-)

diff --git a/app/src/main/cpp/live_view_call.cpp b/app/src/main/cpp/live_view_call.cpp
new file mode 100644
index 0000000..3b3ddb4
--- /dev/null
+++ b/app/src/main/cpp/live_view_call.cpp
@@ -0,0 +1,51 @@
+//
+// Created by 倪路朋 on 4/13/25.
+//
+
+#include "live_view_call.h"
+#include "server_global.h"
+
+LiveViewCall::LiveViewCall(JNIEnv *env_, jobject instance_) {
+    this->env = env_;
+    this->env->GetJavaVM(&javaVM);
+    this->instance = env->NewGlobalRef(instance_);
+    jclass localClazz = env->FindClass("com/runt/live/cpp/LiveMiniView");
+    if (localClazz == nullptr || env->ExceptionCheck()) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        return;
+    }
+
+    // 拿一个 global ref:关键!!
+    //jobject,jclass等对象在方法执行完后就会被回收
+    miniClazz = (jclass)env->NewGlobalRef(localClazz);
+    jmd_draw_text = env->GetStaticMethodID(miniClazz,"drawText","([BIII)[B");
+};
+
+LiveViewCall::~LiveViewCall() {
+    javaVM = 0 ;
+    env->DeleteGlobalRef(instance);
+    env->DeleteGlobalRef(miniClazz);
+    instance = 0 ;
+}
+uint8_t *LiveViewCall::drawText(int stream_code, uint8_t *yuvData[3],int width,int height) {
+
+    //******转为rgba
+    int size = width * height * 4;
+    uint8_t *rgba_data = yuvToRGBA(yuvData,width,height);
+    //转为java字节数组
+    jint res = javaVM->AttachCurrentThread(&env, nullptr);
+
+    jbyteArray java_array = env->NewByteArray(size);
+    env->SetByteArrayRegion(java_array, 0, size, (jbyte *)rgba_data);
+    delete[] rgba_data;
+    jbyteArray rgbaArray = (jbyteArray)env->CallStaticObjectMethod(miniClazz, jmd_draw_text,java_array,stream_code,width,height);
+    env->DeleteLocalRef(java_array);
+// 2. 分配 native buffer
+    uint8_t* rgba_copy = new uint8_t[size];
+// 3. 拷贝数据
+    env->GetByteArrayRegion(rgbaArray, 0, size, reinterpret_cast<jbyte*>(rgba_copy));
+    env->DeleteLocalRef(rgbaArray);
+    javaVM->DetachCurrentThread();
+    return rgba_copy;
+}
\ No newline at end of file
diff --git a/app/src/main/cpp/live_view_call.h b/app/src/main/cpp/live_view_call.h
new file mode 100644
index 0000000..c1224c0
--- /dev/null
+++ b/app/src/main/cpp/live_view_call.h
@@ -0,0 +1,25 @@
+//
+// Created by 倪路朋 on 4/13/25.
+//
+
+#ifndef LIVEPROJECT_LIVE_VIEW_CALL_H
+#define LIVEPROJECT_LIVE_VIEW_CALL_H
+
+
+#include <jni.h>
+
+class LiveViewCall {
+public:
+    LiveViewCall(JNIEnv *env_, jobject instance_);
+    ~LiveViewCall();
+
+    uint8_t *drawText(int stream_code,uint8_t *yuvData[3],int width,int height);
+private:
+    JavaVM *javaVM;
+    JNIEnv *env;
+    jobject instance;
+    jclass miniClazz;
+    jmethodID jmd_draw_text;
+};
+
+#endif //LIVEPROJECT_LIVE_VIEW_CALL_H
diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp
index 7de54db..8221d0a 100644
--- a/app/src/main/cpp/native-lib.cpp
+++ b/app/src/main/cpp/native-lib.cpp
@@ -115,6 +115,7 @@
 void *task_pushFrames(void *args){
     int64_t time = getCurrentTimestamp();
     while (TRUE){
+        YUVData *mainYuvData;
         int result = pushFrames.pop(mainYuvData);
         if(!result){
             continue;
@@ -364,7 +365,7 @@
         LOGE("nativeWindow 回收");
     }
     if(!jvmMainCall){
-        jvmMainCall = new JavaMainCall(env,clazz);
+        jvmMainCall = new LiveViewCall(env,clazz);
     }
 
 }
@@ -440,64 +441,4 @@
     yuv[2] = reinterpret_cast<uint8_t *>(data + ySize + uSize); // V 平面
     pushYUV(stream_code,yuv);
     env->ReleaseByteArrayElements(bytes,data,0);
-}
-extern "C" JNIEXPORT jbyteArray JNICALL
-Java_com_runt_live_cpp_LiveMiniView_native_1get_1cut_1frame(JNIEnv *env, jclass clazz, jint index, jint stream_code) {
-    uint8_t *yuvData[3];
-    if(mainYuvData){
-        pthread_mutex_lock(&pushVideoMutex);
-        copyYUV(mainYuvData->yuvData,FRAME_WIDTH,FRAME_HEIGHT,yuvData);
-        pthread_mutex_unlock(&pushVideoMutex);
-    }else{
-        copyYUV(blackYUV,FRAME_WIDTH,FRAME_HEIGHT,yuvData);
-    }
-    waterYUV(index,yuvData);
-    //*****裁切画面
-    MiniViewData *miniView = getMiniView(stream_code);
-    int scaleWidth = (FRAME_WIDTH * miniView->viewRate);
-    int scaleHeight = (FRAME_WIDTH / (miniView->width * 1.0 / miniView->height) * miniView->viewRate);
-    //涉及到 YUV 4:2:0 格式的图像时,宽度和高度通常需要是 2 的倍数
-    if(scaleWidth % 2 == 1){
-        scaleWidth+=1;
-    }
-    if(scaleHeight % 2 == 1){
-        scaleHeight+=1;
-    }
-    if(scaleWidth > FRAME_WIDTH){
-        scaleWidth = FRAME_WIDTH;
-    }
-    if(scaleHeight > FRAME_HEIGHT){
-        scaleHeight = FRAME_HEIGHT;
-    }
-    if(!miniView->scaledYuvFrame){
-        miniView->scaledYuvFrame = new YUVData;
-    }
-    miniView->scaledYuvFrame->width = scaleWidth;
-    miniView->scaledYuvFrame->height = scaleHeight;
-    miniView->scaledYuvFrame->pYrate = miniView->pYrate;
-    miniView->scaledYuvFrame->pXrate = miniView->pXrate;
-    uint8_t *dstData[3];
-    //位置
-    int offsetY = (FRAME_HEIGHT - scaleHeight) * miniView->pYrate;
-    int offsetX = (FRAME_WIDTH - scaleWidth) * miniView->pXrate;
-    cutYUV(yuvData,FRAME_WIDTH,FRAME_HEIGHT,offsetX,offsetY,dstData,scaleWidth,scaleHeight);
-    LOGE("index:%d x:%d y:%d %dx%d view:%dx%d",index,offsetX,offsetY,scaleWidth,scaleHeight,miniView->width,miniView->height);
-    copyYUV(dstData,scaleWidth,scaleHeight,miniView->scaledYuvFrame->yuvData);
-    addBlackBorder(miniView->scaledYuvFrame->yuvData,scaleWidth,scaleHeight,3);
-    //源数据没用了
-    delete yuvData[0];
-    delete yuvData[1];
-    delete yuvData[2];
-    //******转为rgba
-    int size = scaleWidth * scaleHeight * 4;
-    uint8_t *rgba_data = yuvToRGBA(dstData,scaleWidth,scaleHeight);
-    //yuv数据没用了
-    delete dstData[0];
-    delete dstData[1];
-    delete dstData[2];
-    //转为java字节数组
-    jbyteArray java_array = env->NewByteArray(size);
-    env->SetByteArrayRegion(java_array, 0, size, (jbyte *)rgba_data);
-    delete[] rgba_data;
-    return java_array;
 }
\ No newline at end of file
diff --git a/app/src/main/cpp/server_global.cpp b/app/src/main/cpp/server_global.cpp
index 803a36b..f987d46 100644
--- a/app/src/main/cpp/server_global.cpp
+++ b/app/src/main/cpp/server_global.cpp
@@ -11,6 +11,7 @@
 std::map<int,LivePusher *> pushers = {};
 vector<MiniViewData *> pushMiniDatas = {};
 MiniViewData *mainView = 0;
+YUVData *mainYuvData = 0;
 int mainStreamCode = 0;
 pthread_mutex_t pushVideoMutex,pushNV21Mutex,pushAudioMutex;
 ANativeWindow *nativeMainWindow = 0;
@@ -24,6 +25,7 @@
 SafeQueue<YUVData *> pushFrames;
 AudioChannel *audioChannel = 0;
 VideoChannel *videoChannel = 0;
+LiveViewCall *jvmMainCall = 0;
 
 void packetCallBack(RTMPPacket *packet){
     //LOGI("packetCallBack safequeue packets:%d",&packets);
@@ -335,8 +337,9 @@
         }
     }
 }
+
 /**
- * 剪切画面
+ * 剪切主画面
  * @param pData
  * @param data
  */
@@ -363,6 +366,23 @@
 
     // 分配目标YUV内存
     int dstYSize = miniWidth * miniHeight;
+    cutYUV(yuvData,width,height,cropX,cropY,dstData,miniWidth,miniHeight);
+}
+
+/**
+ * 裁切画面
+ * @param yuvData 源数据
+ * @param width     源数据宽度
+ * @param height    源数据高度
+ * @param cropX     裁切位置X
+ * @param cropY     裁切位置Y
+ * @param dstData   目标数据
+ * @param dstWidth  目标数据宽度
+ * @param dstHeight 目标数据高度
+ */
+void cutYUV(uint8_t *yuvData[3],int width,int height,int cropX,int cropY,uint8_t *dstData[],int dstWidth,int dstHeight){
+    // 分配目标YUV内存
+    int dstYSize = dstWidth * dstHeight;
     int dstUSize = dstYSize / 4;
 
     dstData[0] = new uint8_t[dstYSize];
@@ -370,23 +390,29 @@
     dstData[2] = new uint8_t[dstUSize];
 
     // 裁剪Y平面
-    for (int y = 0; y < miniHeight; ++y) {
-        memcpy(dstData[0] + y * miniWidth,yuvData[0]+ (cropY + y) * width + cropX,
-               miniWidth);
+    for (int y = 0; y < dstHeight; ++y) {
+        memcpy(dstData[0] + y * dstWidth,yuvData[0]+ (cropY + y) * width + cropX,
+                dstWidth);
     }
 
+    int srcChromaWidth = width / 2;
+    int cropChromaX = cropX / 2;
+    int cropChromaY = cropY / 2;
+    int cropChromaWidth = dstWidth / 2;
+    int cropChromaHeight = dstHeight / 2;
+
     // 裁剪U平面(UV分辨率是Y的1/2)
-    for (int y = 0; y < miniHeight / 2; ++y) {
-        memcpy(dstData[1] + y * (miniWidth / 2),
-               yuvData[1] + (cropY / 2 + y) * (width / 2) + cropX / 2,
-               miniWidth / 2);
+    for (int y = 0; y < cropChromaHeight; ++y) {
+        memcpy(dstData[1] + y * (cropChromaWidth),
+                yuvData[1] + (cropChromaY + y) * (srcChromaWidth) + cropChromaX,
+                cropChromaWidth);
     }
 
     // 裁剪V平面
-    for (int y = 0; y < miniHeight / 2; ++y) {
-        memcpy(dstData[2] + y * (miniWidth / 2),
-               yuvData[2] + (cropY / 2 + y) * (width / 2) + cropX / 2,
-               miniWidth / 2);
+    for (int y = 0; y < cropChromaHeight; ++y) {
+        memcpy(dstData[2] + y * (cropChromaWidth),
+                yuvData[2] + (cropChromaY + y) * (srcChromaWidth) + cropChromaX,
+                cropChromaWidth);
     }
 }
 
@@ -400,43 +426,76 @@
 void waterYUV(int index,uint8_t *mainData[3]) {
     //LOGI("waterYUV 加水印(画中画)");
     for (int i = 0; i < index; ++i) {
-        MiniViewData *pData = pushMiniDatas[i];
-        if(pData->streamCode == mainStreamCode){
+        MiniViewData *miniView = pushMiniDatas[i];
+        if(miniView->streamCode == mainStreamCode){
             continue;
         }
-        if(!pData->videoOn){
+        if(!miniView->videoOn){
             continue;
         }
-        if(pData->streamType == 6){
-            jvmMainCall->drawText(pData->streamCode,mainData);
+        if(miniView->streamType == 6){
+            int64_t t = getCurrentTimestamp();
+            //*****裁切画面
+            int scaleWidth = (FRAME_WIDTH * miniView->viewRate);
+            int scaleHeight = (FRAME_WIDTH / (miniView->width * 1.0 / miniView->height) * miniView->viewRate);
+            //涉及到 YUV 4:2:0 格式的图像时,宽度和高度通常需要是 2 的倍数
+            if(scaleWidth % 2 == 1){
+                scaleWidth+=1;
+            }
+            if(scaleHeight % 2 == 1){
+                scaleHeight+=1;
+            }
+            if(scaleWidth > FRAME_WIDTH){
+                scaleWidth = FRAME_WIDTH;
+            }
+            if(scaleHeight > FRAME_HEIGHT){
+                scaleHeight = FRAME_HEIGHT;
+            }
+            YUVData *yuvFrame = new YUVData;
+            yuvFrame->width = scaleWidth;
+            yuvFrame->height = scaleHeight;
+            yuvFrame->pYrate = miniView->pYrate;
+            yuvFrame->pXrate = miniView->pXrate;
+            uint8_t *dstData[3];
+            //位置
+            int offsetY = (FRAME_HEIGHT - scaleHeight) * miniView->pYrate;
+            int offsetX = (FRAME_WIDTH - scaleWidth) * miniView->pXrate;
+            cutYUV(mainData,FRAME_WIDTH,FRAME_HEIGHT,offsetX,offsetY,dstData,scaleWidth,scaleHeight);
+            //addBlackBorder(yuvFrame->yuvData,scaleWidth,scaleHeight,3);
+            uint8_t *rgbaData = jvmMainCall->drawText(miniView->streamCode,dstData,scaleWidth,scaleHeight);
+            releaseYuvData(dstData);
+            rgbaToYUV((uint8_t *)rgbaData,scaleWidth,scaleHeight,yuvFrame->yuvData);
+            miniView->scaledYuvFrames.push(yuvFrame);
+            av_free(rgbaData);
+            LOGI("drawText 耗时:%d",getCurrentTimestamp() - t);
         }
-        if(!pData->scaledYuvFrame && pData->scaledYuvFrames.size() == 0 ){
+        if(!miniView->scaledYuvFrame && miniView->scaledYuvFrames.size() == 0 ){
             //LOGE("小窗丢帧1");
             continue;
         }
-        if(pData->scaledYuvFrames.size() > 0){
-            releaseYuvFrameData(pData->scaledYuvFrame);
-            pData->scaledYuvFrames.pop(pData->scaledYuvFrame);
+        if(miniView->scaledYuvFrames.size() > 0){
+            releaseYuvFrameData(miniView->scaledYuvFrame);
+            miniView->scaledYuvFrames.pop(miniView->scaledYuvFrame);
         }else{
             LOGE("小窗丢帧2");
         }
         //清除yuv画面
-        if(pData->yuvFrames.size() > 1){
-            if(!pData->yuvFrame){
-                pData->yuvFrames.pop(pData->yuvFrame);
+        if(miniView->yuvFrames.size() > 1){
+            if(!miniView->yuvFrame){
+                miniView->yuvFrames.pop(miniView->yuvFrame);
             }
-            while (pData->yuvFrame->timestamp < pData->scaledYuvFrame->timestamp && pData->yuvFrames.size() > 1 ){
-                releaseYuvFrameData(pData->yuvFrame);
-                pData->yuvFrames.pop(pData->yuvFrame);
+            while (miniView->yuvFrame->timestamp < miniView->scaledYuvFrame->timestamp && miniView->yuvFrames.size() > 1 ){
+                releaseYuvFrameData(miniView->yuvFrame);
+                miniView->yuvFrames.pop(miniView->yuvFrame);
             }
         }
 
-        YUVData *scaledYuvFrame = pData->scaledYuvFrame;
+        YUVData *scaledYuvFrame = miniView->scaledYuvFrame;
         //位置
-        int offsetY = (mainHeight - scaledYuvFrame->height) * (pData->streamType == 6 ? pData->pYrate : scaledYuvFrame->pYrate);
-        int offsetX = (mainWidth - scaledYuvFrame->width) * (pData->streamType == 6 ? pData->pXrate : scaledYuvFrame->pXrate);
+        int offsetY = (mainHeight - scaledYuvFrame->height) *  scaledYuvFrame->pYrate;
+        int offsetX = (mainWidth - scaledYuvFrame->width) * scaledYuvFrame->pXrate;
 
-        LOGI("waterYUV %dx%d x:%d,y:%d  x:%d,y:%d ",scaledYuvFrame->width,scaledYuvFrame->height,pData->pYrate,pData->pXrate,offsetX,offsetY);
+        //LOGI("waterYUV %dx%d x:%d,y:%d  x:%d,y:%d ",scaledYuvFrame->width,scaledYuvFrame->height,miniView->pYrate,miniView->pXrate,offsetX,offsetY);
 
         // 边界检查,确保不会越界
         if (offsetX + scaledYuvFrame->width > mainWidth ) {
@@ -505,6 +564,74 @@
     sws_freeContext(sws_ctx);
     return  rgba_data;
 }
+
+void argbToYUV(uint8_t *rgba,int width,int height,uint8_t *src_slices[3]){
+
+    int frameSize = width * height;
+    int uvSize = (width / 2) * (height / 2);
+    // 为 YUV 数据分配内存
+    int yuvSize = frameSize * 3 / 2;  // YUV420P 的大小
+
+    src_slices[0] = new uint8_t[frameSize];
+    src_slices[1] = new uint8_t[uvSize];
+    src_slices[2] = new uint8_t[uvSize];
+
+    // 使用 libyuv 将 RGBA 转换为 YUV420P
+    libyuv::ARGBToI420(
+            reinterpret_cast<uint8_t*>(rgba), width * 4,   // RGBA 数据和每行宽度(RGBA 每个像素 4 字节)
+            src_slices[0], width,                                     // Y 分量数据
+            src_slices[1], width / 2,                                 // U 分量数据
+            src_slices[2], width / 2,                                 // V 分量数据
+            width,                                             // 宽度
+            height                                             // 高度
+    );
+    //LOGI("pushRGBA width:%d height:%d %d",miniView->width,miniView->height,rgbaData);
+}
+
+void rgbaToYUV(uint8_t *rgba,int width,int height,uint8_t *yuvData[3]){
+
+    int frameSize = width * height;
+    int uvSize = (width / 2) * (height / 2);
+
+    yuvData[0] = new uint8_t[frameSize];
+    yuvData[1] = new uint8_t[uvSize];
+    yuvData[2] = new uint8_t[uvSize];
+
+    // 使用 libyuv 将 RGBA 转换为 YUV420P
+    /*libyuv::RGBAToI420(
+            reinterpret_cast<uint8_t*>(rgba), width * 4,   // RGBA 数据和每行宽度(RGBA 每个像素 4 字节)
+            src_slices[0], width,                                     // Y 分量数据
+            src_slices[1], width / 2,                                 // U 分量数据
+            src_slices[2], width / 2,                                 // V 分量数据
+            width,                                             // 宽度
+            height                                             // 高度
+    );*/
+
+    // 1. 创建 SwsContext
+    SwsContext* swsCtx = sws_getContext(
+            width, height,
+            AV_PIX_FMT_RGBA,         // 输入格式,根据实际可改为 BGRA、ARGB 等
+            width, height,
+            AV_PIX_FMT_YUV420P,      // 输出格式
+            SWS_BILINEAR,
+            nullptr, nullptr, nullptr
+    );
+    // 2. 输入数据封装
+    uint8_t* inData[1] = { rgba };
+    int inLineSize[1] = { width * 4 };
+
+    // 3. 分配输出缓冲区
+    int yuv_linesize[3] = { width, width / 2, width / 2 }; // YUV420P: U 和 V 的宽度是 Y 的一半
+
+    // 4. 执行格式转换
+    sws_scale(swsCtx, inData, inLineSize, 0, height, yuvData, yuv_linesize);
+
+    // 5. 释放资源
+    sws_freeContext(swsCtx);
+    // 注意:yuvBuffer 由调用方在不需要时调用 av_free() 释放
+    //LOGI("pushRGBA width:%d height:%d %d",miniView->width,miniView->height,rgbaData);
+}
+
 void copyYUV(uint8_t *yuvData[3],int width,int height, uint8_t *dstData[3]){
     // 计算每个平面的大小
     size_t y_size = width * height;         // Y 平面大小
@@ -567,7 +694,7 @@
         addBlackBorder(scaleYuvFrame->yuvData,scaleWidth,scaleHeight,3);
         miniView->scaledYuvFrames.push(scaleYuvFrame);
         //最大缓存画面数量不能超过max(5-10)帧
-        int max = 15;int min = 3;
+        int max = 3;int min = 3;
         if(miniView->yuvFrames.size() > (mainStreamCode == miniView->streamCode ? max:min) ){
             if(mainStreamCode == miniView->streamCode){
                 LOGE("%d 溢帧 pushyuv %d",streamCode,miniView->yuvFrames.size() - (miniView->videoOn ? max:min));
@@ -682,33 +809,13 @@
     if(miniView->width == 0 || miniView->height == 0){
         return;
     }
-    int frameSize = miniView->width * miniView->height;
-    int uvSize = (miniView->width / 2) * (miniView->height / 2);
-    // 为 YUV 数据分配内存
-    int yuvSize = frameSize * 3 / 2;  // YUV420P 的大小
-    uint8_t *yuvData = new uint8_t[yuvSize];
-
-    // 将 yuvData 分成 3 部分(Y、U、V)
-    uint8_t *yPlane = yuvData;
-    uint8_t *uPlane = yuvData + frameSize;
-    uint8_t *vPlane = uPlane + uvSize;
-
-    // 使用 libyuv 将 RGBA 转换为 YUV420P
-    libyuv::ARGBToI420(
-            reinterpret_cast<uint8_t*>(rgbaData), miniView->width * 4,   // RGBA 数据和每行宽度(RGBA 每个像素 4 字节)
-            yPlane, miniView->width,                                     // Y 分量数据
-            uPlane, miniView->width / 2,                                 // U 分量数据
-            vPlane, miniView->width / 2,                                 // V 分量数据
-            miniView->width,                                             // 宽度
-            miniView->height                                             // 高度
-    );
-    //LOGI("pushRGBA width:%d height:%d %d",miniView->width,miniView->height,rgbaData);
-    // 输入数据的平面指针
-    uint8_t* src_slices[3] = {yuvData,yuvData + frameSize,yuvData + frameSize + frameSize / 4};
-
+    uint8_t* src_slices[3];
+    argbToYUV(rgbaData,miniView->width,miniView->height,src_slices);
     pushYUV(streamCode,src_slices);
     // 释放资源
-    free(yuvData);
+    delete[] src_slices[0];
+    delete[] src_slices[1];
+    delete[] src_slices[2];
 }
 
 void pushNV21(int streamCode,uint8_t *yData,uint8_t *uData,uint8_t *vData,jint y_stride, jint u_stride,jint v_stride, jint uv_stride,jint angle,jint width,jint height){
@@ -754,10 +861,13 @@
 }
 
 void loadSurface(ANativeWindow *nativeWindow,uint8_t *yuvData[3],int width,int height){
+    uint8_t* rgba_data = yuvToRGBA(yuvData,width,height);
+    loadSurface(nativeWindow,rgba_data,width,height);
+    delete[] rgba_data;
+}
+void loadSurface(ANativeWindow *nativeWindow,uint8_t *rgba_data,int width,int height){
     if(nativeWindow){
         int64_t t = getCurrentTimestamp();
-
-        uint8_t* rgba_data = yuvToRGBA(yuvData,width,height);
         int64_t t1 = getCurrentTimestamp();
         //缓冲区
         //LOGI("视频缓冲");
@@ -779,7 +889,6 @@
         }
         int64_t t4 = getCurrentTimestamp();
         ANativeWindow_unlockAndPost(nativeWindow);
-        delete[] rgba_data;
         //LOGI("loadSurface 耗时:%d sws_scale:%d memcpy:%d ANativeWindow_lock:%d 视频缓冲:%d %dx%d",getCurrentTimestamp() - t,t1-t,t4-t3,t3-t2,t2-t1,width,height);
     }
 }
@@ -807,10 +916,11 @@
 }
 void releaseYuvData(uint8_t *yuvData[3]){
     if(yuvData[0] != NULL){
+        // 释放内存,避免泄漏
         delete[] yuvData[0];
         delete[] yuvData[1];
         delete[] yuvData[2];
-        yuvData[0] = NULL;
+        yuvData[0] = yuvData[1] = yuvData[2] = nullptr;
     }
 }
 
diff --git a/app/src/main/cpp/server_global.h b/app/src/main/cpp/server_global.h
index 36cc348..68da61c 100644
--- a/app/src/main/cpp/server_global.h
+++ b/app/src/main/cpp/server_global.h
@@ -12,6 +12,7 @@
 #include "yuv_data.h"
 #include "video_channel.h"
 #include "audio_channel.h"
+#include "live_view_call.h"
 
 extern "C"{
 #include <libyuv.h>
@@ -29,6 +30,7 @@
 const int FRAME_WIDTH = 1080,FRAME_HEIGHT = 1920,FRAME_PS = 30,FRAME_RATE = 4000;
 extern AudioChannel *audioChannel;
 extern VideoChannel *videoChannel;
+extern LiveViewCall *jvmMainCall;
 extern uint8_t *blackYUV[3];
 extern uint32_t start_time;
 
@@ -53,10 +55,13 @@
 void waterYUV(int index,uint8_t *mainData[3]);
 void getPushYUV(int index,uint8_t *mainData[3]);
 uint8_t* yuvToRGBA(uint8_t *yuvData[3],int width,int height);
+void argbToYUV(uint8_t *rgba,int width,int height,uint8_t *yuvData[3]);
+void rgbaToYUV(uint8_t *rgba,int width,int height,uint8_t *yuvData[3]);
 void pushRGBA(int streamCode,uint8_t *rgbaData);
 void pushNV21(int streamCode, jbyte *data);
 void pushNV21(int streamCode,uint8_t *yData,uint8_t *uData,uint8_t *vData,jint y_stride, jint u_stride,jint v_stride, jint uv_stride,jint angle,jint width,jint height);
 void loadSurface(ANativeWindow *nativeWindow,uint8_t *yuvData[3],int width,int height);
+void loadSurface(ANativeWindow *nativeWindow,uint8_t *rgba,int width,int height);
 void addBlackBorder(uint8_t *yuvData[3],int width,int height,int borderWidth);
 void addCornerAndBlackBorder(uint8_t *yuvData[3],int width,int height,int borderWidth,int cornerRadius);
 void playPCM(uint8_t *out_buffer,int out_buffer_size);
diff --git a/app/src/main/java/com/runt/live/cpp/LiveMiniView.java b/app/src/main/java/com/runt/live/cpp/LiveMiniView.java
index e4f22b8..0568b3c 100644
--- a/app/src/main/java/com/runt/live/cpp/LiveMiniView.java
+++ b/app/src/main/java/com/runt/live/cpp/LiveMiniView.java
@@ -1,10 +1,17 @@
 package com.runt.live.cpp;
 
-import android.media.AudioFormat;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.util.Log;
 import android.view.Surface;
 
+import com.runt.live.R;
+import com.runt.live.data.StreamWindow;
+import com.runt.live.ui.stream.LiveLayoutView;
 import com.runt.live.util.AudioUtil;
+import com.runt.live.util.BitmapUtils;
+import com.runt.open.mvi.OpenApplication;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -18,6 +25,7 @@
     static final String TAG = "LiveMiniView";
     public static AudioUtil audioUtil = new AudioUtil();
     static HashMap<String,PcmData> pcmdatas = new HashMap<>();
+    static HashMap<String,Bitmap> textBitmaps = new HashMap<>();
     private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
     public static int mainStreamCode = 0 ;
     public static final int FRAME_WIDTH = 1080,FRAME_HEIGHT = 1920,FRAME_PS = 30;
@@ -41,7 +49,38 @@
     public static native void native_push_pcm(byte[] bytes);
     public static native void native_set_main_surface(Surface surface);
     public static native void native_release_main_surface();
-    public static native byte[] native_convert_nv21_to_rgba(byte[] bytes,int width,int height);
+
+    public static byte[] drawText(byte[] rgba,int streamCode,int width,int height){
+
+        int length = rgba.length;
+
+        byte[] argb = new byte[length];
+        StreamWindow streamWindow = LiveLayoutView.Companion.getLiveStreamsState().getValue().getStream(streamCode);
+        String key = streamWindow.getId()+"";
+        if(!textBitmaps.containsKey(key)){
+            Bitmap bitmap = BitmapUtils.Companion.getInstance().textToBitmap(streamWindow.getRemark(),130f , OpenApplication.Companion.getApplication().getResources().getColor(R.color.white),
+                    OpenApplication.Companion.getApplication().getResources().getColor(R.color.transparent));
+            textBitmaps.put(key,bitmap);
+        }
+        Bitmap bitmap = textBitmaps.get(key);
+        Bitmap mainBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        //Log.e(TAG , "updateText: ${width}x${cutHeight} ${streamWindow.sizeState.value} ${( streamWindow.sizeState.value.x * 1.0 /  streamWindow.sizeState.value.y )}", )
+        ByteBuffer buffer = ByteBuffer.wrap(rgba);
+        mainBitmap.copyPixelsFromBuffer(buffer);
+        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap,width,height, true);
+
+        Canvas canvas = new Canvas(mainBitmap);
+
+        // 构建目标区域
+        Rect destRect = new Rect(0,0,width,height);
+        // 绘制小图到大图
+        canvas.drawBitmap(scaledBitmap, null, destRect, null);
+
+        ByteBuffer buffer2 = ByteBuffer.allocate(mainBitmap.getByteCount());
+        mainBitmap.copyPixelsToBuffer(buffer2);
+        argb = buffer2.array();
+        return argb;
+    }
 
     /*    external fun native_update_mini_sn(code : Int ,streamCode : Int, sn : Int)
         external fun native_update_mini_live(code : Int ,streamCode : Int, isLive:Boolean)
diff --git a/app/src/main/java/com/runt/live/data/LiveStreams.kt b/app/src/main/java/com/runt/live/data/LiveStreams.kt
index 0b781d0..336de2c 100644
--- a/app/src/main/java/com/runt/live/data/LiveStreams.kt
+++ b/app/src/main/java/com/runt/live/data/LiveStreams.kt
@@ -35,4 +35,13 @@
         }
         return null;
     }
+    fun getStream(streamId:Int):StreamWindow?{
+        for ( i in subStreamWindows.value.indices){
+            if(streamId == subStreamWindows.value[i].id){
+                return subStreamWindows.value[i]
+            }
+        }
+        return null;
+    }
+
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/runt/live/ui/stream/LiveViewModel.kt b/app/src/main/java/com/runt/live/ui/stream/LiveViewModel.kt
index b9c2ac8..71f71f6 100644
--- a/app/src/main/java/com/runt/live/ui/stream/LiveViewModel.kt
+++ b/app/src/main/java/com/runt/live/ui/stream/LiveViewModel.kt
@@ -1,25 +1,18 @@
 package com.runt.live.ui.stream
 
-import android.graphics.Bitmap
-import android.graphics.Canvas
 import android.graphics.Point
-import android.graphics.Rect
+import android.os.Handler
 import android.util.Log
 import android.view.SurfaceHolder
 import com.runt.live.R
 import com.runt.live.cpp.LiveMiniView
-import com.runt.live.cpp.LiveMiniView.FRAME_HEIGHT
-import com.runt.live.cpp.LiveMiniView.FRAME_WIDTH
 import com.runt.live.data.StreamWindow
 import com.runt.live.enum.LiveState
-import com.runt.live.enum.StreamType
 import com.runt.live.native.LivePuller
 import com.runt.live.native.LivePusher
 import com.runt.live.native.MediaPlayer
-import com.runt.live.ui.stream.LiveLayoutView.Companion.subStreamsState
 import com.runt.live.util.BitmapUtils
 import com.runt.open.mvi.base.model.BaseViewModel
-import java.nio.ByteBuffer
 import java.util.concurrent.ConcurrentSkipListMap
 import kotlin.concurrent.thread
 
@@ -145,57 +138,13 @@
             streamWindow.audioState.value == LiveState.IN_LIVE,streamWindow.videoState.value == LiveState.IN_LIVE,
             streamWindow.videoDelay,streamWindow.streamType.value,
             streamWindow.mainPositionRateState.value, streamWindow.viewRateState.value)
+
     }
 
     fun removeMiniView(streamCode :Int){
         LiveMiniView.native_remove_mini_view(streamCode)
     }
 
-    fun updateText(streamWindow : StreamWindow){
-        thread {
-            var bytes = LiveMiniView.native_get_cut_frame(subStreamsState.value.indexOf(streamWindow),streamWindow.id)
-            var bitmap = BitmapUtils.instance!!.textToBitmap(streamWindow.remark!!,130f,getActivity().resources.getColor(R.color.white)!!,getActivity().resources.getColor(R.color.transparent)!!)
-
-            var cutwidth : Int = (FRAME_WIDTH * streamWindow.viewRateState.value).toInt()
-            var cutHeight : Int = (FRAME_WIDTH / ( streamWindow.sizeState.value.x * 1.0 /  streamWindow.sizeState.value.y ) * streamWindow.viewRateState.value).toInt()
-
-            //涉及到 YUV 4:2:0 格式的图像时,宽度和高度通常需要是 2 的倍数
-            if (cutwidth % 2 == 1) {
-                cutwidth += 1
-            }
-            if (cutHeight % 2 == 1) {
-                cutHeight += 1
-            }
-            if (cutwidth > FRAME_WIDTH) {
-                cutwidth = FRAME_WIDTH
-            }
-            if (cutHeight > FRAME_HEIGHT) {
-                cutHeight = FRAME_HEIGHT
-            }
-            val cutBitmap = Bitmap.createBitmap(cutwidth, cutHeight, Bitmap.Config.ARGB_8888)
-            Log.e(TAG , "updateText: ${cutwidth}x${cutHeight} ${streamWindow.sizeState.value} ${( streamWindow.sizeState.value.x * 1.0 /  streamWindow.sizeState.value.y )}", )
-            val buffer = ByteBuffer.wrap(bytes)
-            cutBitmap.copyPixelsFromBuffer(buffer)
-
-            val dstBitmap = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
-
-            val canvas = Canvas(dstBitmap)
-
-            // 构建目标区域
-            val destRect = Rect(0,0,dstBitmap.width,dstBitmap.height)
-
-            // 绘制小图到大图
-            canvas.drawBitmap(cutBitmap, null, destRect, null)
-            //canvas.drawBitmap(bitmap, null, destRect, null)
-
-            val width : Int = bitmap.getWidth()
-            val height : Int = bitmap.getHeight()
-            val argb = IntArray(width * height)
-            dstBitmap.getPixels(argb , 0 , width , 0 , 0 , width , height)
-            //LiveMiniView.native_push_nv21(streamWindow.id,BitmapUtils.instance!!.argbToNV21(argb,width,height));
-        }
-
-    }
     fun openText(streamWindow : StreamWindow){
 
         var bitmap = BitmapUtils.instance!!.textToBitmap(streamWindow.remark!!,130f,getActivity().resources.getColor(R.color.white)!!,getActivity().resources.getColor(R.color.transparent)!!)
@@ -212,11 +161,6 @@
                 streamWindow.listener?.onSurfaceUpdate?.let { it(holder.surface) }
                 thread {
                     BitmapUtils.instance!!.cavansSurface(holder,bitmap);
-                    val width : Int = bitmap.getWidth()
-                    val height : Int = bitmap.getHeight()
-                    val argb = IntArray(width * height)
-                    bitmap.getPixels(argb , 0 , width , 0 , 0 , width , height)
-                    LiveMiniView.native_push_nv21(streamWindow.id,BitmapUtils.instance!!.argbToNV21(argb,width,height));
                 }
                 //Log.w(TAG , "surfaceChanged: ${holder.hashCode()}" , )
             }
@@ -227,7 +171,7 @@
                 //Log.w(TAG , "surfaceDestroyed: ${holder.hashCode()} ${id}" , )
             }
         }
-        streamWindow.listener?.onStarted?.invoke()
+        Handler().postDelayed({streamWindow.listener?.onStarted?.invoke()},500)
     }
 
     /**
diff --git a/app/src/main/java/com/runt/live/util/BitmapUtils.kt b/app/src/main/java/com/runt/live/util/BitmapUtils.kt
index cac5acc..b0d85ff 100644
--- a/app/src/main/java/com/runt/live/util/BitmapUtils.kt
+++ b/app/src/main/java/com/runt/live/util/BitmapUtils.kt
@@ -713,8 +713,8 @@
         val bmpHeight = ceil((lineHeight * lines.size).toDouble()).toInt()
 
         val bitmap = Bitmap.createBitmap(bmpWidth , bmpHeight , Bitmap.Config.ARGB_8888)
+        bitmap.eraseColor(Color.TRANSPARENT); // 设置为完全透明
         val canvas = Canvas(bitmap)
-        canvas.drawColor(bgColor) // 背景色
 
 
         // 逐行绘制文本

--
Gitblit v1.9.1