2019年4月28日星期日

借用网上的代码要当心之3——定位一个播放视频卡死的问题有感

一位同事开发了一个乘客信息系统的程序,用于地铁站站台和地铁车厢内显示屏的显示。屏幕上大部分面积是播放的视频,旁边用文字提示地铁进站时间和一些通知等等。程序运行在Linux+QT+GStreamer环境下。
半年多之前我就知道她这个程序有个问题,播放几个小时的视频后,视频会卡死不动,而文字提示部分仍然可以正常显示。直到最近这位同事离职,这个问题都没有解决。而她离职后,这个程序暂时交接给了我。
我接手后,首先着手解决这个问题。我觉得这个问题已经不仅仅是个bug, 而是基本功能都没有实现。我之前从未接触过QT和GStreamer,但只花了半天时间就找到了问题所在。后来尝试改了一下,果然改好了。问题本身很简单,不值得一写。但一些同事得知此事好奇我是怎么这么快找到问题所在的,所以我还是把过程写一写。
那天下午我首先在网上简单搜了一下QT和GStreamer的教程。互联网时代就是好,网上有非常非常多的各种免费教程,当急需某方面知识的时候,可以快速补课。不过两个东西的知识体系都很庞大,我都只看了一点开头。
我最开始怀疑是不是GStreamer本身的bug。原来负责的同事说用的是GStreamer 1.0版。我本想替换成最新版的试一下,但是不会操作。后来觉得人家敢叫1.0版,应该是充分测试过的,不应该有这么低级的bug,于是放弃了这个方向。
原来负责的同事说,视频播放死掉的时间与播放文件的数量有关。我们平时测试的都是几分钟长度的公益广告,那么可以支撑两三个小时。如果播放几秒钟的视频,那么几分钟视频播放就死掉了。所以我初步怀疑是播放一段视频后,某些资源没有释放掉,播放的文件多了,就没有资源了。我初步的想法是播放结束后,把整个播放器的实例析构掉,确保所有的资源得到释放,然后重新创建播放器的实例,用于播放下一段视频。我简单看了一下原来的代码,大概了解了代码的架构,发现这样改的话需要调整程序的架构,改动量非常大,我目前还不会用QT和GStreamer, 不敢大刀阔斧地修改。
搜索了整套代码,没有调用malloc的地方,应该也不是内存泄漏。而且视频播放死掉的时候,我用ssh登录到播放器上,看了一下进程的内存占用,也只有2M多点(初始启动的时候是400kB左右),不像是内存泄漏。不过我还不死心,在Google搜索"QT 内存泄漏"和"gstreamer 内存泄漏",恰好搜到一篇文章https://blog.csdn.net/yingmuliuchuan/article/details/79347773,讲了GStreamer中使用不当会引起内存泄漏的一些函数。我逐一在代码中搜索,发现这套代码使用了三个。其中一个函数的使用引起了我的高度怀疑。
bool Media::play()
{
    //...
    if (GST_STATE_CHANGE_FAILURE != ret) {
        m_state = PLAYING;
        cout << "Playing " << m_uri << endl;

        g_timeout_add(500, cb_update, this);
        pthread_t tid;
        pthread_create(&tid, NULL, cb_run_loop, this);
        m_speed = 1;

        return true;
    }
    //...
}
播放每段视频时都会调用这个Media::play()函数,而其中g_timeout_add()函数此处创建定时器后,再也没有地方把这个定时器释放调。查看定时器超时回调函数cb_update, 作用是显示xx:xx/xx:xx格式的播放进度。每播放一段视频都创建一个500毫秒超时的定时器,超时函数返回true的话,还会继续创建500毫秒的定时器。因此,播放一些视频后,系统中大量充斥着这种定时器,于是定时器相关的资源就会枯竭,造成程序无法继续运行。我们实际上并不需要显示播放进度,因此这个定时器超时回调似乎没有作用。但我刚接手这个项目,不敢乱改,万一人家有特殊的需要呢。于是微信联系之前负责的同事,她说这些代码都是从CSDN上来的,这个cb_update是做什么的她也不清楚。既然这样,我就把这句连同下面的pthread_create删除了。于是这个问题一下子就改好了。
假设我们的程序需要显示播放进度,那么就应该在一段视频播放完成的时候,让cb_update函数返回false, 让定时器释放掉。
这就是解决这个问题的经过,再次说明了借用网上的代码,一定要弄懂吃透,不要复制粘贴过来发现能用就完事大吉。之前开发这个软件的同事,如果花时间弄清楚这套代码调用的每个函数的原理,所需要的时间远小于她定位这个问题所花费的时间。
分析这个问题,我还用到了GDB, 但最终找到问题并不是靠GDB. 我当时参考了http://www.voidcn.com/article/p-tuyxftdz-dw.html这篇文章。文中提到的方法对分析这类问题很有帮助。

没有评论:

发表评论