iOS下,解码远程桌面视频时app崩溃

崩溃直接原因是app占用大量内存,导致被系统强制退出。为什么会占用大量内存?——aio3399j的h264在编码一些场景,iOS收到这些h264数据,并解码,解码过程中内存使用量会突然大增。举个例子,一直是占用100M,突然以数百M增加,大到超过1G,于是iOS强制退出app。以下是小结出的这种场景特征:1)图像一直没变化,2)送入aio3399j的帧率极高,像50帧甚至更高。

Android也存在这个问题,只不过在这种场景时,Android没表现出突占大量内存,而是解码花去更多时间,于是看去解码像是出现了延时。

如何解决这个问题。曾试着采用从三个方面。

一、尝试过的方法

1.1、让编码、解码h264的两种设备能互相兼容

h264格式不兼容,具体反映在sps(Sequence parameter set)的前三个字节。这三个字节通常叫profile-level-id,aio3399j编码出的这三个字节是42001f,iOS能支持是42e01f。Android也是42e01f。

查下来iOS只能支持42e01f,那只能想法设置aio3399j参数,让mediacodec以profile-level-id=42e01f为参数去编码。可惜,至少目前没找到这方面的办法。

1.2、怀疑libkosapi处理codec出来码流,把它们成帧时有问题

试过直接修改aosp提供的screencap,然后存成文件。iOS直接解码这文件,同样发生app崩溃。

1.3、图像不变时,不送给mediacodec

以rose写的app,至少目前很难做到不变的图像不送给mediacodec,像浮动控件、动画。

好在测下来,图像还是不变,但降低送给mediacodec的帧率,似乎能缓解这个问题。具体这个帧率多少是安全的,目前测试下来,或许选定25帧,而android提供了方法控制送给mediacodec的最大帧率:max-fps-to-encoder。

二、对这问题,目前采用第三种方法,在两方面做修改

2.1、libkosapi.so

launcher以参数max_fps_to_encoder=25调用kosRecordScreenLoop。

static status_t prepareEncoder(float displayFps, sp<MediaCodec>* pCodec, sp<IGraphicBufferProducer>* pBufferProducer)
{
    ...
    sp<AMessage> format = new AMessage;
    format->setInt32("width", gVideoWidth);
    format->setInt32("height", gVideoHeight);
    format->setString("mime", kMimeTypeAvc);
    format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque);
    format->setInt32("bitrate", gBitRate);
    // 虽然会设置max-fps-to-encoder,也必须设置frame-rate,否则MediaCodec不会输出h264数据
    format->setFloat("frame-rate", displayFps);
    format->setInt32("i-frame-interval", 10);
    // displayFps值就是调用kosRecordScreenLoop时传下的max_fps_to_encoder
    format->setFloat("max-fps-to-encoder", displayFps);

    err = codec->configure(format, NULL, NULL, MediaCodec::CONFIGURE_FLAG_ENCODE);
    ...
}

顺便说下MediaCodec如何处理max-fps-to-encoder。一旦app设置了max-fps-to-encoder,MediaCodec会创建一个FrameDropper对象,当收收到新图像时,FrameDropper的shouldDrop方法判断当前是否要丢弃该图像。这丢掉的图像,自然不会进入后绪的编码模块。

bool FrameDropper::shouldDrop(int64_t timeUs) {
    if (mMinIntervalUs <= 0) {
        return false;
    }

    if (mDesiredMinTimeUs < 0) {
        mDesiredMinTimeUs = timeUs + mMinIntervalUs;
        ALOGV("first frame %lld, next desired frame %lld",
                (long long)timeUs, (long long)mDesiredMinTimeUs);
        return false;
    }

    if (timeUs < (mDesiredMinTimeUs - kMaxJitterUs)) {
        ALOGV("drop frame %lld, desired frame %lld, diff %lld",
                (long long)timeUs, (long long)mDesiredMinTimeUs,
                (long long)(mDesiredMinTimeUs - timeUs));
        return true;
    }

    int64_t n = (timeUs - mDesiredMinTimeUs + kMaxJitterUs) / mMinIntervalUs;
    mDesiredMinTimeUs += (n + 1) * mMinIntervalUs;
    ALOGV("keep frame %lld, next desired frame %lld, diff %lld",
            (long long)timeUs, (long long)mDesiredMinTimeUs,
            (long long)(mDesiredMinTimeUs - timeUs));
    return false;
}

shouldDrop返回true表示要丢弃该图像,false则表示该图像要送入后绪编码模块进行编码。参数timeUs是以微秒为单位的当前时刻,mMinIntervalUs是根据max-fps-to-encoder算出的、以微秒为单位的间隔。mDesiredMinTimeUs表示下一帧必须>=此时刻到来,之前来的要丢弃。

mMinIntervalUs = (int64_t) (1000000.0f / maxFrameRate);

从shouldDrop逻辑看出,送入后绪编码器的两帧之间的间隔可能小于mMinIntervalUs,但这次小于会导致下一次拉大间隔,就平均来说还是会保持在最大帧率是max-fps-to-encoder。

2.1、rose

当图像没变化时,不要去调用SDL_RenderPresent。这个估计会有个漫长过程。

不管怎么说,要解决这问题,根本还是让aio3399j输出兼容iOS解码的h264码流。将来如何做到真能改aio3399j输出的h264格式,如何测试是否有效,同时做两处修改。1)rose中的CVideo::flip一定调用SDL_RenderPresent。2)max-fps-to-encoder设为一个较大值,像60。

 

SDP Profile-level-id解析:https://blog.csdn.net/liang12360640/article/details/52096499

解析H264的SPS信息:https://blog.csdn.net/lizhijian21/article/details/80982403

 

全部评论: 0

    写评论: