虚拟摄像头搭配高德AR导航出现的内存和cpu问题

最近在处理虚拟摄像头搭配高德AR导航时,出现了一些内存、cpu和优先级的问题,这里做个初步记录,后面再完善

CPU

1.)开一个hal3的后台录相、预览app,再开一个高德AR导航虚拟摄像头。这时cpu消耗最高达到了600%以上, AR导航界面卡顿严重,录相APP录下来的帧丢帧严重。

    刚开始是从降cpu着手的,因为AR导航占用的cpu达到了300以上,加上地图的其他进程消耗,高德一个APP就达到了400%以上。

    刚开始降cpu手段:

    1.在虚拟摄像头hal3的processCaptureRequest函数里,将收到的数据,从yv12转i420,再转rgba的这个过程,合并一个过程。因为yv12和i420的y都是相同的,只是u和发刚好相反,所以直接从yv12指定的位置传给libyuv:i420toRGBA,这样就省去了从yv12拷到i420的这个memcpy过程。因为一张1920*1080的yv12大概有w*h*1.5,有3M左右大小,这个拷贝是非常费时的,所以可以省去8%左右的cpu.

 

    2.)将我们的录相app从api2改为api1。因为api2里面,每一个流都是一个surface,而每一个surface,无论你显示还是不显示,都会在SurfaceTextureRenderer.java里的drawIntoSurfaces函数里去渲染。在只有一个surface时这个渲染过程以及api2的requestThreader线程,app的占用的cpu在60%以上。如果有两个surface,大概还要增加20%左右。我们api2录相时,会有一个preview的suface,一个recorder的surface。如果改用api1的话,这两个渲染过程,就可以省掉,也就是可以省50%以上的cpu.

    3.)在mtk的DisplayClient.BufOps.cpp文件里的handlebuffer函数中,将预览的数据送显代码给动态关闭。让录相app在后台时,就关闭预览,在前台时,就打开预览。少了这个送显的过程,还可以降10%左右的cpu。mtk上的预览数据,是在DisplayClient.BufOps.cpp里的handleReturnBuffers函数中处理的。送显的代码是enquePrvOps(pStreamImgBuf);这句,如果将这句屏掉,预览时,就没有显示画面了。当然,如果用的是api2,并且用的surface录相的话,这里就不能屏掉。因为用surface录相的时候,只有DisplayClient.BufOps.cpp这一路数据流。如果用的是api1,或者说录相时用的是camerasource做为数据源,那么这里屏掉,就不会影响录相。因为这时录相走的是RecordClient这一路数据流。修改后的代码如下:

bool
DisplayClient::
handleReturnBuffers(Vector<ImgBufQueNode>const& rvQueNode)
{
    ....
{
    CAM_TRACE_NAME("handleReturnBuffers:enque");
    //enquePrvOps(pStreamImgBuf);

//#####################################################
//####  Control preview switch and frame rate by xuhui
//#####################################################
    if(mOpenId == CAMERA_FACING_BACK)
    {
        char isStopDisplay[PROPERTY_VALUE_MAX];
        ::property_get("yov.stop.preview",isStopDisplay,"1");
        int isStopDisplay_value = atoi(isStopDisplay);
        MY_LOGE("Drop Preview frame yov.stop.preview == %d", isStopDisplay_value);
        if(1 == isStopDisplay_value){
              cancelPrvOps(pStreamImgBuf);
        }
        else
        {
            enquePrvOps(pStreamImgBuf);
        }//add end

            
    }
    else
    {
        enquePrvOps(pStreamImgBuf);
    }
}
MY_LOGD_IF((2<=miLogLevel),"Display Enque-");
    ....
}

    mtk的录相、预览、回调的数据流如下图:

    4.)因为高德的AR导航用到了ImageReader,而它们将这个ImageReader的获取格式,设置的是YUV_420_888。但是ImageReader里的surface的grallocbuffer的格式,android结构上,都统一默认用的是rgb的。所以在SurfaceTextureRenderer.java的drawIntoSurfaces函数中渲染时,会先通过GLES20.glReadPixels,将当前这一帧的数据,从gpu里拷出来,然后再在外面转成了yuv的,这样才能返回给app正确的格式。但是GLES20.glReadPixels是一个非常耗时耗cpu的操作。而如果app上将imagereader的格式,设为rgba,也就是和surface里的格式保持一致的话,surface渲染时,可以直接使用这数据,而不用去转换。这里可以省掉10%左右的cpu.

    通过上面的这些优化,一共省掉了80%以上的cpu。但是这个时候,预览还是非常的卡顿。而我们是8核的cpu,卡顿时最高的cpu才60%多,并没有到极限,且我们已经将cpu的工作频率设为了最大,模式也是全功率工作模式,但是仍然会卡。这时我们怀疑,应该不是cpu的问题了。于是我们开始了内存的优化。

内存:

1.)通过procrank发现在[email protected]进程,占用了60m左右的空间。这个空间有点偏高。于是用dumpsys media.camera查看相关的信息,发现hal3的max HAL buffers为8. 当我们的分辨率为1920*1080时,一张图片大概要3m左右的内存。这里用8个buff的话,就是24m左右。因为我们的cpu是8核,还没有达到使用的极限,显然这个buff个数不需要这么多,减掉一半,用4个,cpu处理速度了来得及。于是将这里改成了4. 省下了12m左右的空间。 但是这个内存,仍然有些不正常,不应该有这么高。但是暂时没有什么头绪,就先去看下高德的app

再  adb shell showmap 268查看268进程的各个.so以及 heap,stack的使用情况,做初步的判断

2.)我们在AR卡的时候,通过free将剩余的空间打了出来,发现只剩几m的空间了。且swap空间里用了几百m。再通过procrank查看AR导航的内存使用情况,从VSS(虚拟耗用内存,包括共享库占用的内存)和uss(进程独自占用的物理内存)发现AR导航一个应用就占了近200m的空间。高德是个大头,但是他们不改,没办法,只能我们去改。

3.)我们经过反复测试,发现AR卡的时候,是车机进入待机后恢复后,变卡的。于是我将待机前的top AR导航和hal3的cpu占用率,以及procrank的uss保存起来,和从待机到唤醒后的cpu、内存对比。发现待机后,hal3的占用率和内存比之前的高了一倍。这时再查看刚刚用adb logcat 抓的log,发现唤醒后,高德又调用了一次cameraService里的connect。也就是说,每次唤醒,他们都重新open了一次camera,但是待机时,他们显然忘记释放camera了。  

    而hal3的[email protected]进程,正常是在70%左右。如果没有释放的话,会多出70%。内存正常是30m左右,没有释放的话,也多出了30m。

    后来通知高德修改后,cpu一共省了这里的70%和前面的几种优化80%,一共省了150%以上。内存40m以上。然后再将fstab.swap里的swapsize大小从1g调到500m。可用空间多了500m。速度一下就上来了,不再卡顿。

优先级

        通过内存和CPU的优先后,高德的AR导航预览界面是不再卡顿了,但是因为AR导航仍占据了大量的cput和内存,在老化测试,或者将车机装上汽车,在外面跑一段时间后,车机整体温度升高,cpu核心温度达到七八十,甚至九十多度时,很多应用都开始卡顿起来。比如在同时录相和高德AR预览的时候,再在后台播放音乐,或者地图实时播报路况,就有可能出现语音卡顿的现象。甚至录相时,录相会丢帧。 

        出现这个情况的原因,是老化测试后,整机性能下降,cpu处理速度变慢,系统会将主要的cpu资源优先供给了预览界面。其他应用可用的cpu资源变少,自然就容易出现卡顿、甚至被杀死的情况。

        针对这个情况,主要是对系统的优先级做了一些调整。默认的,所有的用到了mediacodec的进程,在android_media_MediaCodec.cpp里的构造函数JMediaCodec中,自动将优先级设为了-10,也就是ANDROID_PRIORITY_VIDEO,这个值定义在system\core\include\system\thread_defs.h里。 所以高德应用,默认的优先级就是-10。反倒是我们的system_server进程的优先级是-2,也就是THREAD_PRIORITY_FOREGROUND,这个值定义在frameworks\base\core\java\android\os\Process.java里。

        这个默认的优先级策略,个人觉得,很是有问题的。你一个相机预览、录相应用,再怎么重要,也不应该让它的优先级变得比系统服务system_server更重要。否则一旦cpu不够用时,不先把app给干掉,反而把系统给干了,这还怎么玩?所以这个system_server的优先级必须要比ANDROID_PRIORITY_VIDEO这个优先级高。修改代码如下:

frameworks\base\services\java\com\android\server\SystemServer.java
private void run() {
    ....
    // Prepare the main looper thread (this thread).
    // android.os.Process.setThreadPriority(
    //     android.os.Process.THREAD_PRIORITY_FOREGROUND);
    //将优先级从-2提升到-19        
    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
    ....
}

        另外还有audioserver、cameraserver、camerahalserver、[email protected][email protected]等这几个系统线程的优先级也要提升起来,必须要比app的高。总之一句话,app所依赖的系统进程的优先级,必须要比app的高。否则系统进程都挂了,你这个app写得再好,也只是个空中楼阁而已。下面是对应几个进程优先级修改的地方:

frameworks/av/camera/cameraserver/main_cameraserver.cpp
 26 int main(int argc __unused, char** argv __unused)
  27 {
  28     signal(SIGPIPE, SIG_IGN);
  29 
  30     pid_t tid = gettid();
  31     int pr = androidGetThreadPriority(tid);
  32     if(pr>ANDROID_PRIORITY_AUDIO){
             //提升cameraserver进程的优先级到-16, ANDROID_PRIORITY_AUDIO
  33         androidSetThreadPriority(tid, ANDROID_PRIORITY_AUDIO);
  34     }
  35     pr = androidGetThreadPriority(tid);
  36     ALOGE("cameraserver thread priority:%d tid:%d",pr,tid);
  37 
  38     //!++
  39     #if 0
  40     // Set 3 threads for HIDL calls
  41     hardware::configureRpcThreadpool(3, /*willjoin*/ false);
  42     #else
  43     // Set 6 threads for HIDL calls, features need more threads to handle preview+capture
  44     hardware::configureRpcThreadpool(6, /*willjoin*/ false);
  45     #endif
  46     //!--
  47 
  48     sp<ProcessState> proc(ProcessState::self());
  49     sp<IServiceManager> sm = defaultServiceManager();
  50     ALOGI("ServiceManager: %p", sm.get());
  51     CameraService::instantiate();
  52     ProcessState::self()->startThreadPool();
  53     IPCThreadState::self()->joinThreadPool();
  54 }
frameworks/av/media/audioserver/main_audioserver.cpp
int main(int argc __unused, char **argv)
{
    signal(SIGPIPE, SIG_IGN);

    bool doLog = (bool) property_get_bool("ro.test_harness", 0);

    pid_t tid = gettid();
    int pr = androidGetThreadPriority(tid);
    if(pr>ANDROID_PRIORITY_AUDIO){
        //将audioserver进程的优先级提升到-19, ANDROID_PRIORITY_URGENT_AUDIO
        androidSetThreadPriority(tid, ANDROID_PRIORITY_URGENT_AUDIO);
    }
    pr = androidGetThreadPriority(tid);
    ALOGE("audioserver thread priority:%d tid:%d",pr,tid);   

    ....
}

vendor/mediatek/proprietary/hardware/audio/common/service/2.0/service.cpp
int main(int /* argc */, char * /* argv */ []) {
    ALOGD("Start audiohalservice");

    signal(SIGPIPE, SIG_IGN);

    pid_t tid = gettid();
    int pr = androidGetThreadPriority(tid);
    if(pr>ANDROID_PRIORITY_AUDIO){
        //将[email protected]进程的优先级提升到-19,ANDROID_PRIORITY_URGENT_AUDIO
        androidSetThreadPriority(tid, ANDROID_PRIORITY_URGENT_AUDIO);
    }
    pr = androidGetThreadPriority(tid);
    ALOGE("audiohalservice thread priority:%d tid:%d",pr,tid);
    ....   
}


/vendor/mediatek/proprietary/hardware/mtkcam/main/hal/service/service.cpp
int main()
{
    ALOGI("Camera HAL Server is starting..., ADV_CAM_SUPPORT(%d)", MTKCAM_ADV_CAM_SUPPORT);

    signal(SIGPIPE, SIG_IGN);

    pid_t tid = gettid();
    int pr = androidGetThreadPriority(tid);
    if(pr>ANDROID_PRIORITY_AUDIO){
        //将camerahalserver进程的优先级提升到ANDROID_PRIORITY_AUDIO -16
        androidSetThreadPriority(tid, ANDROID_PRIORITY_AUDIO);
    }
    ....
}

       上面这些提升优先级的思路就是,将系统的优先级要提升得比app的高. app如果消耗资源过多,更多的是要app自己去解决,而不能影响到我们的系统服务. 

       经过上面降cpu、降内存、调整优先的一系列措施后,在同时开启后台录相、AR导航预览虚拟摄像头、后台播放音乐等应用的情况下,录相不再丢帧,预览不再卡顿,语音也不再卡顿。总的而言,各方面达成了一个资源分配比较合理的情况 

猜你喜欢

转载自blog.csdn.net/xuhui_7810/article/details/107393920