您的位置:首页 > 运维架构 > Linux

Linux-视频监控系统(6)-播放器子系统

2017-08-07 21:46 507 查看

播放器功能及设计思路

播放子系统需要实现和服务器的连接,以及接收并显示图像等功能。主要的界面有2个,一个是连接界面,由用户输入IP地址和端口后连接服务器,另一个是播放界面,显示接收过来的图像,并且显示图像格式,帧率、大小等信息。根据这些要求我们开始设计播放子系统,播放子系统的界面采用GTK来编写。由于一次性难以实现全部功能,这里分为2部分来实现。第一步先把播放界面设计好,第二步在界面的基础上实现和服务器的连接以及图像的显示。我们这里来实现第一步。

为了方面系统的实现及信息的保存,我们定义这么几个结构

登录界面的结构:

struct entry_win
{
GtkWidget *win;
GtkWidget *ip_entry;
GtkWidget *port_entry;
GtkWidget *connect_button;
gboolean connected;
int sock;
};

保存客户机的信息:
struct wcam_cli
{
int sock;
pthread_t tid;
bool need_stop;
__u8 req[FRAME_MAX_SZ];
__u8 rsp[FRAME_MAX_SZ + VID_FRMAE_MAX_SZ];

wc_img_porc_t proc;
void *arg;
};

为整个界面创建一个结构:
struct wcam_win
{
GtkWidget *win;
wcc_t client;
entry_win_t entry_win;

GtkWidget *video_area;

guint32 video_format;
guint32 video_width;
guint32 video_height;
gboolean video_fullscreen;
gboolean need_snapshot;

gchar ipaddr[24];

GtkWidget *fps_label;
GtkWidget *frmsize_label;
guint32 frm_cnt;
guint64 last_twenty_frm_us;

GtkWidget *info_area;
GtkWidget *button_area;
GtkWidget *control_area_button;
GtkWidget *control_area;
};

登录界面

/*创建登录界面*/
entry_win_t login_create()
{
struct entry_win *c = calloc(1, sizeof(struct entry_win));

if(!c){
perror("entry_win_create");
return NULL;
}

c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);

//设置标题,图标,位置等
gtk_window_set_title(GTK_WINDOW(c->win), WIN_TITLE);
gtk_window_set_icon(GTK_WINDOW(c->win), gdk_pixbuf_new_from_file(WIN_ICON,NULL));
gtk_window_set_position(GTK_WINDOW(c->win), GTK_WIN_POS_CENTER);
//不能改变大小
gtk_window_set_resizable(GTK_WINDOW(c->win),FALSE);
//设置窗口大小
gtk_container_set_border_width(GTK_CONTAINER(c->win), 0);
//创建关闭窗口的处理事件
g_signal_connect(GTK_OBJECT(c->win),"destroy",G_CALLBACK(gtk_main_quit),NULL);
//按键输入事件和key_press_event函数关联
g_signal_connect(G_OBJECT(c->win), "key_press_event",G_CALLBACK(key_press_func), c);
//允许窗口绘图
gtk_widget_set_app_paintable(c->win, TRUE);
//绘制图片和按钮
entry_win_draw_face(c);
//显示所有控件
gtk_widget_show_all(c->win);

return c;
}

我们把图标的创建,按钮的创建,IP和端口输入窗口的创建封装成函数
static gboolean entry_win_draw_face(struct entry_win *c)

界面的设计我们采用BOX容器来实现,第一行绘制图标,第二行绘制IP和端口的输入界面,第三行绘制OK和cancel按钮

static gboolean entry_win_draw_face(struct entry_win *c)
{
GtkWidget *vbox;
GtkWidget *hbox;

//创建一个垂直组装盒,控件间隔为5个像素
vbox = gtk_vbox_new(FALSE, 5);
gtk_container_add(GTK_CONTAINER(c->win), vbox);

//创建一个水平组装盒,控件间隔为5个像素
hbox = gtk_hbox_new(FALSE, 5);
gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);

//绘制logo
logo_draw(c, hbox);

//绘制IP和端口输入框
hbox = gtk_hbox_new(FALSE, 5);
gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
entry_area_draw(c, hbox);

//绘制OK和cancel按钮
hbox = gtk_hbutton_box_new();
gtk_box_set_spacing(GTK_BOX(hbox), 5);
gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
button_area_draw(c, hbox);

return TRUE;
}

logo绘制,输入窗口的绘制和按钮绘制的函数如下:
static gboolean logo_draw(struct entry_win *c, GtkWidget* box)
{
gtk_box_pack_start_defaults(GTK_BOX(box), gtk_image_new_from_file(LOGO_IMG));
gtk_box_pack_start_defaults(GTK_BOX(box), gtk_image_new_from_file(LOGO_IMG1));
return TRUE;
}

static gboolean entry_area_draw(struct entry_win *c, GtkWidget* box)
{
GtkWidget *label;
GtkWidget *entry;

//创建一个文本标签
label = gtk_label_new("Server Ip:");
gtk_box_pack_start_defaults(GTK_BOX(box), label);

//创建一个输入框
entry = gtk_entry_new();
gtk_entry_set_text(GTK_ENTRY(entry), DEF_CONN_IP);
gtk_entry_set_max_length(GTK_ENTRY(entry), 15);

gtk_box_pack_start_defaults(GTK_BOX(box), entry);
c->ip_entry = entry;

label = gtk_label_new("Server Port:");
gtk_box_pack_start_defaults(GTK_BOX(box), label);

entry = gtk_entry_new();
gtk_entry_set_text(GTK_ENTRY(entry), DEF_PORT);
gtk_entry_set_max_length(GTK_ENTRY(entry), 5);

gtk_box_pack_start_defaults(GTK_BOX(box), entry);
c->port_entry = entry;

return TRUE;
}

static gboolean button_area_draw(struct entry_win *c, GtkWidget* box)
{
GtkWidget *button;

//创建ok按钮
button = gtk_button_new_with_label("OK");
gtk_box_pack_start_defaults(GTK_BOX(box), button);
c->connect_button = button;
//点击事件关联connect_handler函数
g_signal_connect(button, "clicked", G_CALLBACK(connect_handler), c);

button = gtk_button_new_with_label("Cancel");
gtk_box_pack_start_defaults(GTK_BOX(box), button);

g_signal_connect(button, "clicked", G_CALLBACK(gtk_main_quit), NULL);

return TRUE;
}
需要注意的是,对于输入事件的处理我们分为2种,一种是对按键的处理,比如按下回车默认点击OK,按下ESC默认退出;另一种是对OK和cancel按钮事件的处理,点击OK需要实现对服务器的连接,点击cancel则退出界面。对于第二种情况我们在按钮绘制函数中已经把OK按钮和connect_handler函数关联在一起

/*建立和服务器的连接*/
static void connect_handler(GtkButton *button, gpointer data)
{
struct entry_win *c = data;
const gchar *ip;
gint16 port;

//获取输入的ip和端口,保存新的socket
ip = gtk_entry_get_text(GTK_ENTRY(c->ip_entry));
port = atoi(gtk_entry_get_text(GTK_ENTRY(c->port_entry)));

c->connected = TRUE;
gtk_main_quit();
}


对于第一种,我们在登录界面的创建时已经把key_press_event事件和key_press_func关联在一起,
/*对键盘的响应只支持回车和退出*/
static gboolean key_press_func(GtkWidget* widget, GdkEventKey* key, gpointer data)
{
#define KEY_ENTER 0xff0d
#define KEY_ESC 0xff1b
struct entry_win *c = data;
//如果是回车,默认点击OK
if(KEY_ENTER == key->keyval)
{
connect_handler(GTK_BUTTON(c->connect_button), c);
return TRUE;
}
//退出键
else if(KEY_ESC == key->keyval)
{
c->connected = FALSE;
gtk_main_quit();
return TRUE;
}

return FALSE;
}

最终登录界面的显示如下所示:



别忘了测试一下对事件发生的响应是否准确

播放界面

如果连接成功我们进入播放界面,连接失败则退出,那么怎么连接是否成功在于判断是否获取到了socket,然后通过login_run来判断是否连接成功:
int login_run(entry_win_t win)
{
struct entry_win *c = win;

//等待事件的发生
gtk_main();
return c->connected ==
4000
TRUE ? 0 : -1;
}


连接成功后隐藏登录界面,显示播放界面,主函数如下:
gint main(gint argc, gchar* argv[])
{
int res;

/*GTK初始化*/
gtk_init(&argc, &argv);
g_thread_init(NULL);
gdk_threads_init();

struct wcam_win *c = calloc(1,sizeof(struct wcam_win));

c->entry_win = login_create();
res = login_run(c->entry_win);
if(res == -1)
{
goto err_win;
}

//隐藏登录页面
login_hide(c->entry_win);

//创建主工作页面
main_create(c);
main_run();
err_win:
free(c->entry_win);
free(c);

return 0;
}


播放界面我们称之为主工作界面,他的初始化和登录界面类似,这里不再过多介绍了:
/*
*主工作页面
*主要实现显示图片和帧率的功能
*/
static gboolean main_create(struct wcam_win *c)
{
int len;

c->video_width = 640;
c->video_height = 480;

c->video_fullscreen = FALSE;

//把ip和端口拼接到ipaddr中
entry_win_get_ip(c->entry_win, c->ipaddr);
len = strlen(c->ipaddr);
c->ipaddr[len] = ':';
entry_win_get_port(c->entry_win, &c->ipaddr[len+1]);

//创建主工作界面
c->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(c->win), WIN_TITLE);
gtk_window_set_icon(GTK_WINDOW(c->win),
gdk_pixbuf_new_from_file(WIN_ICON, NULL));
gtk_container_set_border_width(GTK_CONTAINER(c->win), 0);
g_signal_connect(c->win, "destroy",
G_CALLBACK(main_quit), c);
gtk_widget_set_app_paintable(c->win, TRUE);
main_win_draw_face(c);
gtk_widget_show_all(c->win);

gtk_widget_hide(c->win);
gtk_window_set_position(GTK_WINDOW(c->win), GTK_WIN_POS_CENTER);
gtk_widget_show(c->win);

return TRUE;

}


主窗口交互信息的绘制:
/*绘制主窗口的控件*/
static gboolean main_win_draw_face(struct wcam_win *c)
{
GtkWidget *box;
GtkWidget *hbox;
GtkWidget *hseparator;

box = gtk_vbox_new(FALSE, 5);
gtk_container_add(GTK_CONTAINER(c->win), box);

//设置图片区域及信息显示区域
hbox = gtk_hbox_new(FALSE, 5);
gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
draw_area_draw(c, hbox);
info_area_draw(c, hbox);

//绘制水平分割线
hseparator = gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(box), hseparator, FALSE, TRUE, 0);

//绘制按钮
main_button_area_draw(c, box);

return TRUE;
}


完整的工程包括Makefile保存在我的GitHub中:
https://github.com/dayL-W/Video-capture-system
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: