崩溃直接原因是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