工作上的一个上层调度台应用(Windows 7),业务功能上有并发调取多个视频的需求,发现调取30左右路D1视频后会导致崩溃,日志提示:except.c !!!FATAL: unhandled exception PJLIB/No memory!,内存不足,在开发环境下验证发现内存占用已经达到2G以上(32位程序默认最高给2G内存,通过配置能达到3G),所以确实发生系统内存分配失败的问题,开始着手检查内存消耗的点。
之前为了某个项目能解码3.5K视频,把解码能力提升到了4K(4096*2160@60),profile-level-id增加到"428034"
一个视频呼叫过程中,大块内存分配的位置有(编码分辨率1280*720,最大解码分辨率4096*2160):
1、video_port 解码渲染buf,YUV420P 4:2:0,直接按照最大解码size,分配4096*2160*1.5 = 13271040,13M
2、video_port 采集buf,YUY2 4:2:2,按照编码size,1280*720*2 = 1843200,1.8M
3、video_port 采集转换buf,YUY2->YUV420P,编码分辨率size,1280*720*1.5 = 1382400,1.3M
4、video_port 本地预览buf,YUV420P,按照编码size,1280*720*1.5 = 1382400,1.3M
5、vid_stream 编码通道buf,按照编码size,1280*720*4+12(RTP头) = 3686412,3M
6、vid_stream 解码帧buf,按照最大解码size,4096*2160*4 = 35389400,35M
7、ffmpeg 编码buf,YUV420P,按照编码size,1280*720 = 1382400 ,1.3M
8、ffmpeg 解码buf,YUV420P,按照最大解码size,4096*2160*1.5 = 13271040,13M
9、语音编解码及其他,忽略
合计:13271040+1843200+1382400+1382400+3686412+35389400+1382400+13271040=71608292,71M
30路则大约2.1G内存开销,已经达到默认32位进程上限,验证了结果(第二路以上视频呼叫时采集buf、采集转换buf、本地预览buf已经不会再次分配了)。
分析及思路:编码方向因为是按需设置分辨率,除非调整架构,很多地方没办法动,重点是解码方向,为了兼容最大解码分辨率,每次都分配足够4KYUV视频的空间是有点不必要的,毕竟4K视频仅仅是偶尔出现,绝大多数分辨率仍然覆盖在是720P~VGA之间,所以完全可以将解码分辨率降低到VGA(profile-level-id仍然设置最大值),相关buf不足时重新分配即可。
具体改动:ffmpeg解码buf已经有了Realloc的逻辑
1、vid_stream编码buf,由
stream->frame_size = vfd_enc->size.w * vfd_enc->size.h * 4;
改为
stream->frame_size = vfd_enc->size.w * vfd_enc->size.h * 1.5;
2、vid_stream解码帧buf,由
stream->dec_max_size = vfd_dec->size.w * vfd_dec->size.h * 4;
改为
stream->dec_max_size = vfd_dec->size.w * vfd_dec->size.h * 1.5;
3、vid_stream成功解码后收到PJMEDIA_EVENT_FMT_CHANGED,在get_frame方法中增加解码帧buf大小的判定逻辑
//to realloc stream dec frame buf and size
pj_int32_t new_size = fmt_chg_data->new_fmt.det.vid.size.h*fmt_chg_data->new_fmt.det.vid.size.w*1.5;
if (stream->dec_max_size < new_size)
{
PJ_LOG(5, (THIS_FILE, "Reallocating vid_stream dec_buffer %u --> %u",
(unsigned)stream->dec_max_size,
(unsigned)new_size));
pj_mutex_lock(stream->jb_mutex);
stream->dec_max_size = new_size;
stream->dec_frame.buf = pj_pool_alloc(stream->own_pool, stream->dec_max_size);
pj_mutex_unlock(stream->jb_mutex);
}
4、同样在vid_port 解码渲染buf,client_port_event_cb方法中增加判断buf大小的逻辑
// realloc mem
int new_size = vp->conv.conv_param.dst.det.vid.size.h*vp->conv.conv_param.dst.det.vid.size.w*1.5;
PJ_LOG(5, (THIS_FILE, "Reallocating vid_port frm_buffer %u --> %u",
(unsigned)vp->frm_buf_size,
(unsigned)new_size));
if (vp->frm_buf_size < new_size)
{
vp->frm_buf_size = new_size;
vp->frm_buf->buf = pj_pool_alloc(vp->pool, new_size);
vp->frm_buf->size = vp->frm_buf_size;
}
由此显著降低并发视频呼叫情况下的内存占用过多问题,另外64路并发呼叫的宏定义可以这样:
#define PJSIP_MAX_TSX_COUNT 1023 //默认
#define PJSIP_MAX_DIALOG_COUNT 511 //默认
#define PJSUA_MAX_CALLS 64
#define PJSUA_MAX_VID_WINS 64
#define PJ_IOQUEUE_MAX_HANDLES 512 //默认值16,最大呼叫数量15,
#define PJSIP_TPMGR_HTABLE_SIZE 512