2019年4月27日星期六

借用网上的代码要当心之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这篇文章。文中提到的方法对分析这类问题很有帮助。

2019年4月17日星期三

UTF-8格式的csv文件用Office打开乱码

我在Linux平台下用C语言开发的一个程序,需要和系统的其他组成部分交互数据,另一部分是用C#语言开发的程序。双方约定使用UTF-8格式的csv文件传递数据。
我这边使用fprintf, fputs等参数直接把要写入的数据以指定的格式和UTF-8编码存入csv文件中。在终端下使用cat命令显示csv文件内容是正确的,编码确实是UTF-8。但是如果用Excel打开这个csv文件却是乱码,对方用C#的相关库解析文件也无法解析。只有用Excel的导入功能,在导入向导中选择编码为UTF-8, 才能正确显示数据。
后来经过研究得知,UTF-8格式的文本文件(csv文件也属于文本文件范畴),可以在文件的开头添加三个字节——0xEF, 0xBB, 0xBF. 这三个字节称为“签名”或者BOM. 一些比较高级的文本编辑器在保存文件的时候可以选择“带BOM的UTF-8”或者“带签名的UTF-8". 这样Excel和C#的解析库就不会把文件识别为乱码了。
对于fprintf,fputs等函数来说,可以在第一次向文件写数据的时候,写做
fputs("\xef\xbb\xbf地址,ID\n", fp);
这样就添加了BOM.

2019年4月2日星期二

奥林匹克森林公园的特步跑道(待续)

本想攒够了一整条跑道的图片再发布,但是由于种种原因,我已经有近一年时间没有去过了,所以先把这三张发出来,以免哪天手机坏了或者丢了,这三张也没了。



1.5km照片拍摄于2019年4月27日,上传于2019年10月4日

2.0km照片拍摄于2020年1月24日,上传于2020年2月26日

2.5km照片拍摄于2020年1月25日,上传于2020年2月26日

3.0km照片拍摄于2020年1月26日,上传于2020年2月26日

3.5km照片拍摄于2020年1月29日,上传于2020年2月26日

4.0km照片拍摄于2020年2月29日,上传于2020年5月17日

4.5km照片拍摄于2020年3月1日,上传于2020年5月17日

5.0km照片拍摄于2020年12月20日,上传于2021年5月1日


5.5km照片拍摄于2020年7月26日,上传于2020年12月12日

6.0km照片拍摄于2020年12月27日,上传于2021年5月2日

2019年4月1日星期一