一、问题现象

保存的本地视频非常小,无法播放。

二、问题原因分析

经打印,是编解码类没有输出数据。加强日志,经过详细的调试分析,最终定位到是ScreenEncoder的线程被意外的stop了,没有唤醒。

具体的涉案代码如下:

1.UltraSoundSDK类的reset方法

fun reset() {
    ...
        ScreenRecordHelper.pausing.set(true)
        ScreenRecordHelper.resetting_data.set(true)
        ScreenRecordHelper.native_reset_finish.set(false)
        UltraSoundSDKJni.getInstance().reset(handle)
        //有可能这个reset方法执行很快,resetting_data就不在这里置为false了,让ImageReader自己处理,否则达不到清除缓存的作用了。
//          ScreenRecordHelper.resetting_data.set(false)
        ScreenRecordHelper.pausing.set(false)
        ScreenRecordHelper.native_reset_finish.set(true)
    ...
    }

ScreenRecordHelper.pausing.set(true)的时候,ScreenEncoder检测到了,会调用pauseScreenCapture方法将encoderPaused设置为true。

ScreenEncoder的run方法中,检测到encoderPaused为true会去执行pauseScreenCaptureInner方法使ScreenEncoder休眠。

如果 ScreenRecordHelper.native_reset_finish.set(true)在ScreenEncoder休眠之后再执行,会唤醒 ScreenEncoder线程,一切皆ok,但是如果不是,则ScreenEncoder在此次检查中会一直休眠,导致没有数据写入视频文件。

三、解决办法

在执行activateScreenCapture方法时记录时间戳activateScreenCaptureTime,在ScreenEncoder中判断时间差决定是否执行pauseScreenCaptureInner()方法。

 if(encoderPaused){
                //有可能activateScreenCaptureTime在此之前的瞬间执行了,此时不能pause!!!!!!
                val detTime = abs(System.currentTimeMillis() - activateScreenCaptureTime)
                if(detTime in 0..50){
                    encoderPaused = false
                    //记得重置,否则真正pause状态时则无法pause。
                    activateScreenCaptureTime = -1
                    val log = "give up to call pauseScreenCaptureInner"
                    Log.e(TAG, log)
                    CZLocalLogPrinterHolder.getInstance().localLogPrinter.forceWriteLog(log)
                }else{
                    pauseScreenCaptureInner()
                }
            }

但是这样仍然会有问题,因为activateScreenCapture方法执行后,if(encoderPaused){...}可能已经开始执行了,并且已经进入到pauseScreenCaptureInner方法内部了,最后执行lock.wait()将ScreenEncoder线程休眠。

所以,要在执行pauseScreenCaptureInner执行lock.wait()前,开启一个子线程延时判断encoderPaused是否为false(activateScreenCapture设置的),并且lock.wait已经执行过了,则再次调用activateScreenCapture方法唤醒线程。

 GlobalScope.launch(jobForDelay) {
                delay(50L)
                SuperLog.logI(TAG,"pauseScreenCaptureInner:  encoderPaused is $encoderPaused")
                if(!encoderPaused){
                    if(damonLoopThreadHandler?.isWaiting() == true){
                        log = "ScreenEncoder 线程意外休眠,现在唤醒它,call resumeScreenCapture。"
                        SuperLog.logI(TAG,log)
                        resumeScreenCapture()
                    }
                }
            }

            log = "ScreenEncoder 线程进入休眠状态,获取锁。"
            SuperLog.logI(TAG,log)
            damonLoopThreadHandler?.sleep() // 线程休眠
            log = "ScreenEncoder 线程被唤醒,释放锁。"

但是无法完全保证延迟子线程在damonLoopThreadHandler?.sleep()执行之后执行,这取决于延迟时间和cpu调试。

四、问题仍然存在

在reset方法里加上Thread.sleep(100ms),从源头解决时序问题。问题暂时就没有再复现了,但是根本原因还是没有找到。

fun reset() {
    ...
        ScreenRecordHelper.pausing.set(true)
        ScreenRecordHelper.resetting_data.set(true)
        ScreenRecordHelper.native_reset_finish.set(false)
        UltraSoundSDKJni.getInstance().reset(handle)
        //有可能这个reset方法执行很快,resetting_data就不在这里置为false了,让ImageReader自己处理,否则达不到清除缓存的作用了。
//
Thread.sleep(100ms)
ScreenRecordHelper.resetting_data.set(false)
        ScreenRecordHelper.pausing.set(false)
        ScreenRecordHelper.native_reset_finish.set(true)
    ...
    }

0 条评论

发表回复

您的电子邮箱地址不会被公开。