存档

文章标签 ‘GTK’

新开源项目Hybrid开发手记

七月 30th, {2011 31 条评论 23,410 人阅读过  

博客有两个月没更新了吧,先说说这段时间都在做些什么吧,六月初的时候实验室项目验收完,以为可以轻松下来了,于是开了一个新的开源项目在做,这个稍后再说,没过多久就被拉去给实验室的新项目做设计文档去了,这活做起来比硬编码要麻烦得多,于是大多数时间都在为这个事情心烦,稍微有点闲下来的时间就去写点代码,接下来说下最近在做的这个开源项目。

之前的Openfetion虽然也受到了一些开源社区朋友的好评,但软件质量怎么样我心里比谁都明白,在twitter上我也公开承认过Openfetion的代码质量以及软件架构都非常差,说到代码质量,最初在开发Openfetion的时候没有想过会把它作为一个通用的软件拿出来给大家用,而是自己纯粹在写着玩,于是写得很随意,当然这也得怪我这种恶劣的编程习惯,专业的coder即便是写一个测试用的小代码也会严格按规范来,这是一种习惯,我承认之前做得不够好。再说到软件架构,对于IM软件我之前一样没有什么经验,在没有经过调研的情况就盲目开始编码,完全没有参考现有的开源软件架构,甚至没有个合适的事件循环,所以Openfetion目前的状态是勉强能用。

之前考虑过重构Openfetion,但其实重构的成本要远远高于重写,于是我决定终止Openfetion项目,不再为Openfetion增加/修改代码,也不鼓励其它人对它进行修改。

作为Openfetion的替代品,我发起了新的开源项目Hybrid(https://github.com/levin108/hybrid),这同样是一款IM软件,我把它定位为类似于Pidgin/Empathy的IM框架,但即没有使用libpurple也没有使用telepathy,这两个库使用起来都还是有很多限制,我喜欢自由自在的写代码,我不太想考虑太多关于Hybrid这个软件能走多远,能有多少人用,我需要一款这样的软件,我想让它支持飞信和Gtalk,这是我最需要的两种通信协议,这就够了。

Hybrid同样也使用了GTK作为UI库,纯C语言开发,用了OpenSSL处理加密逻辑,使用glib的GIOChannel来实现了异步IO事件循环,同时把libxml2的API做了封装,xml的处理代码看起来简洁多了(libxml2的API命名规则看起来实在太难受了,而且还需要经常对它的自定义数据类型进行转换)。软件的整体架构上参考了一个pidgin,之前的Openfetion用多线程来处理并发逻辑,线程之间的同步开销很大,现在经常还会出现的崩溃问题大多来源于线程同步没有做好。现在采用的方法是采用单线程,将处理逻辑都交给GUI线程来进行处理,当然所有的IO都必须是非阻塞IO,通过异步IO的方法来检测IO事件,当前所有的操作都必须是非阻塞的,包括SSL/TLS握手等,但目前还存在的一个问题是DNS解析过程仍然是阻塞的,我还没有去研究如何实现非阻塞DNS解析,这个会加入TODO List中。至于模块化也是采用了glib中的gmodule来实现的,没有直接使用dlopen的方法,这样应该会提高一点可移植性吧。

关于协议模块,飞信的协议模块在现有的pidgin插件的基础上重写了,使用了封装过的xml接口,同样也使用了一致的编码风格,但在之前的基础上写代码效率就高了很多,很快就完成了。另外关于Gtalk的协议模块也没有花太多时间,用了一个周末的时间就把基础框架弄好了,因为用的是XMPP,开放的协议实现起来非常简单,只需要读一遍协议然后照着开发就好了,一路写下来都很顺,没有遇到什么大的麻烦。

总之到目前为止,这款软件已经基本能满足我的需要了,但还是有很多需要完善的地方,如好友的搜索,webkit的聊天界面,以及自定义请求对话框等,这些等以后慢慢再更新,现在面临毕业找工作,时间相对也较少,有空的时候就更新一点。

还没有想好什么时候要发布一个正式版,有一点点追求完美,总想把它做得更好一点,有感兴趣的同学可以去把代码clone下来玩玩,有用过的同学欢迎反鐀意见。

分类: C/C++ 标签: ,

gstreamer播放wav文件

五月 6th, {2010 3 条评论 11,042 人阅读过  

有网友留言希望我在飞信上加上声音提示,下午的时候整了整,很早以前写过一个直接操作/dev/dsp的小程序用来播放wav,后来发现在当另一个程序在使用这个设备文件的时候,程序就无法打开/dev/dsp这个文件,也就无法完成播放,但因为实现简单我在飞信的第一个版本里面用了这种方法,后来觉得不好就直接把声音提示给去掉了。

在网上查了一下实现wav播放的方法,大多数都是这种直接操作/dev/dsp的,GTK也没有像QT那样的直接播放音频文件的类,于是我只好使用第三方的库了,网上很流行的gstreamer,今天简单读了一下它的手册,发现gstreamer确实是很强大,和GTK一样,它也是基于面向对象的思想来实现的,C语言的面向对象,这样对象和对象之间的继承和派生的关系他们都实现的很完美,有时间一定要仔细研究一下具体的实现方法,gstreamer还有一个更强大的特点是它的管道流机制,gstreamer里面的基本元素是element,element上有pad,每一个element都是一个独立的组件,来完成相应的功能,pad应该可以理解成接口吧,我也不知道该翻译成什么,element之间通过各种pad连接起来形成一个pipe,数据流就在这个pipe里面流动,每经过一个element就会经过一层过滤,比如经过数据压缩的element,数据解码的element,这让我想到了很多年前开发OA的时候里面的工作流,不过这种机制用在这里确实是个好主意,要实现一个复杂的功能只需要把各种独立实现某个小功能的element连接成一个pipe,数据流依次流过这个pipe的时候就完成了对这个数据流的复杂的处理,可扩展性也很高,当需要新的功能的时候只需要开发新的组件,提供相应的接口就可以添加到应用程序中去。

gst-inspect命令列出当前系统中已经安装了的gstreamer组件以及这些组件所具有的特性。

gst-launch命令创建并运行一个pipe,组件之间的管道用”!”作为分隔符,为了跟shell的管道分隔符区分开没有用”|”,下面是几个例子:

播放一个mp3文件,第二个组件mad是用来对mp3进行解码的,我系统上没装这个组件…
gst-launch filesrc location=music.mp3 ! mad ! osssink

其中location是组件filesrc的一个属性,filesrc是要从一个文件读取源数据,location指出了文件的路径
具体还有很多,详见http://linux.about.com/library/cmd/blcmdl1_gst-launch.htm

播放wav的时候需要用到wavparse这个组件,它在gst-plugins-good这个包里面,我的slackware没有预装,我在slackbuild.org上下载上来装上了。

在命令行用gst-launch命令播放的时候跟播放mp3时候类似,但在程序里面实现的时候就不同了,简单的把filesrc,wavparse和alsasink这三个组件连接起来会失败,这困扰了我好久,最后终于弄明白了,wavparse这个组件的sink pad只能在数据流来了以后才可以连接,因为wavparse需要先知道输入给它的数据的类型,所以在wavparse创建的时候link就会失败。解决的办法是使用信号动态地连接pad,gst_bin_add_many这个函数调用的时候会触发add_pad信号,回调函数参数列表里面就有新创建的pad,把这个pad连接到alsasink的sink pad上去就可以了,下面是gstreamer播放wav的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <gst/gst.h>
 
static void
add_pad (GstElement *element , GstPad *pad , gpointer data){
 
	gchar *name;
	GstElement *sink = (GstElement*)data;
 
	name = gst_pad_get_name(pad);
	gst_element_link_pads(element , name , sink , "sink");
	g_free(name);
}
 
static gboolean
bus_watch(GstBus *bus , GstMessage *msg , gpointer data)
{
    GMainLoop *loop = (GMainLoop *) data;
    if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS){
        g_main_loop_quit(loop);
    }
    return TRUE;
}
 
void
play_file(const char *filename){
 
	GMainLoop *loop;
	GstElement *pipeline;
	GstBus *bus;
	GstElement *source , *parser , *sink;
 
	loop = g_main_loop_new(NULL , TRUE);
 
	pipeline = gst_pipeline_new("audio-player");
 
	source = gst_element_factory_make("filesrc" , "source");
	parser = gst_element_factory_make("wavparse" , "parser");
	sink = gst_element_factory_make("alsasink" , "output");
 
	g_object_set(G_OBJECT(source) , "location"
			, filename , NULL);
 
	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
	gst_bus_add_watch(bus , bus_watch , loop);
	g_object_unref(bus);
 
	gst_bin_add_many(GST_BIN(pipeline)
			, source , parser , sink , NULL);
 
	g_signal_connect(parser
			, "pad-added" , G_CALLBACK(add_pad) , sink);
 
	if(! gst_element_link(source , parser)){
		g_warning("linke source to parser failed");
	}
 
	gst_element_set_state(pipeline , GST_STATE_PLAYING);
	printf("Start playing...\n");
	g_main_loop_run(loop);
	printf("Playing stopped!!!\n");
	gst_element_set_state(pipeline , GST_STATE_NULL);
	g_object_unref(pipeline);
}
 
int
main(int argc , char *argv[]){
 
	gst_init(&argc , &argv);
 
	play_file(argv[1]);
 
	return 0;
}

编译:

1
$gcc -o gst gst.c `pkg-config --cflags --libs gstreamer-0.10`

分类: C/C++ 标签: , ,

关于C/C++的内存管理和函数回调

一月 29th, {2010 5 条评论 8,708 人阅读过  

最近一直都在用C++写程序,这几年都在用像C#,java这样的高级语言,很多C/C++的机制用起来突然发现自己已经那么不熟练了,甚至有些东西在当初就没有理解透彻,简单地说一下自己遇到的一些小儿科的问题。

1.C的内存管理

首先便是C/C++的内存管理机制,C#和java这样的高级语言是很牛的,他们有自己的自动内存管理机制,不需要程序员去人为地释放分配的内存,而之于C++便不同了,每一次在内存中new一块内存空间,在使用完后都要及时地释放,一个new便一定要对应一个delete,new完没有delete,这可能不会马上出现太大的问题,不过程序却会蚕食着内存,直到耗尽。

另外就是变量在内存中的存储位置问题,其实以前在用高级语言的时候都从来不考虑这个问题的,而现在不同了,想做一个c/c++ coder ,就需要把这些细节都弄清楚,通过new动态分配的内存空间是存在于堆上的,而且函数内部定义的变量的内存空间是存在于堆栈上的,当初对这个问题产生了一个弱弱的疑问,像下面这段代码:

1
2
3
4
void test()
{
       char* name = new char[10];
}

当初就是想不明白,那name到底是存放在哪里的呢,哈,其实现在想想都觉得当初真是傻啊,name是一个指针,它本身就是一个占四个字节空间的变量,这4个字节理所当然是存在于栈上的了,而name所指向的那10个字节的内存空间是存在于堆上的,逻辑关系其实非常清晰。

因为函数成员变量只在函数局部有效,函数执行完毕后堆栈上的东西便不存在了,于是想要返回一个字符串的时候就有可能会出这样的错误:

1
2
3
4
5
char* test()
{
       char name[] = "hello world";
       return name;
}

这样写的话编译器好像会提示吧,记不清了,反正我没这样写过,返回字符串我常用的方法有两种:

1
2
3
4
5
6
char* test()
{
       char* name = new char[10];
       ........
       return name;
}

这样在通过调用test函数得到一个返回值,用完后必须要记得释放那块内存空间,这个问题很容易遗忘,检查代码的时候发现了好几外这样的问题。
另外一种是通过函数参数

1
2
3
4
void test(char* name)
{
        strcpy(name , "hello world!");
}

这样在使用的时候就可以这样调用了:

1
2
char name[20] = { 0 };//我习惯这样初始化字符数组
test(name);

或者new一个字符数组,这样在用完后还是在这一层次上delete掉,一个new对应一个delete,逻辑关系很明确,不太容易出问题。

2.C++使用回调函数

这个问题以前也没有考虑过,最近在用C++和GTK+做开发,本来是打算用QT,后来觉得在GTK的桌面环境下QT的程序跑起来看上去总是很别扭,后来也考虑了gtkmm,可觉得gtkmm不能用在c里面,我也不一定一直都用C++,不如借此机会学习学习GTK,然后就选了GTK,还是习惯面向对象,然后最后决定用C++和GTK这种组合,自己都觉得有点不伦不类。

GTK中的信号机制是采用的函数回调,这在C里面是肯定没有问题的,但如果要在C++的类中引入回调函数,确实有一些麻烦,C++是不允许将非静态成员函数作为回调函数的,这也就是说回调函数一定要是一个类的静态成员函数,当然这样又出现了一个问题,非静态成员不能够调用类的成员变量 和成员函数,这是个大问题,然后我就想了一种方法,下面是一个gtk中信号实现的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class test
{
    public:
       test();
       void hello();
       void initialize();
       static test* pthis;
       static void testfunc(GtkWidget* widget , gpointer data);
};
void test::initialize()
{
    test* pthis = this;//静态成员函数是不能在类中赋值的,这里相当于定义。
    ...............
    g_signal_connect(someobj , "clicked" , G_CALLBACK(testfunc) , pthis);
}
void test::testfunc(GtkWidget* widget , gpointer data);
{
    test* pthis = static_cast<test*>(data);
    pthis->hello();
}

我一直都是这样干的,不知道这算不算个野路子,不过我的程序是没有在这方面出过问题。

分类: C/C++ 标签: ,