为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

Linux下mp3播放器的实现

2013-01-06 34页 doc 365KB 62阅读

用户头像

is_737885

暂无简介

举报
Linux下mp3播放器的实现PAGE 基于Linux下mp3播放器的研究实现 学生姓名:XXX 指导老师:XXX 摘 要:由于Linux具有高度便携性和高度自由。最近几年,它在移动设备和个人电脑领域的应用也越来越广泛。鉴于以上应用领域对用户界面友好性方面的要求,几乎所有的此类应用都是采用图形用户界面。基于GTK的GNOME,是Linux领域中最主要的桌面环境。而GTK本身,则是创造图形用户界面的最流行的跨平台GUI工具箱之一。这里将通过一个使用GTK和GStreamer技术的简单mp3播放器的实现,详细描述Linux环境下的图形界面编程技术。 ...
Linux下mp3播放器的实现
PAGE 基于Linux下mp3播放器的研究实现 学生姓名:XXX 指导老师:XXX 摘 要:由于Linux具有高度便携性和高度自由。最近几年,它在移动设备和个人电脑领域的应用也越来越广泛。鉴于以上应用领域对用户界面友好性方面的要求,几乎所有的此类应用都是采用图形用户界面。基于GTK的GNOME,是Linux领域中最主要的桌面环境。而GTK本身,则是创造图形用户界面的最流行的跨平台GUI工具箱之一。这里将通过一个使用GTK和GStreamer技术的简单mp3播放器的实现,详细描述Linux环境下的图形界面编程技术。 关键词:Linux;mp3播放器;GTK The Examination and Implementation of Mp3 Player in Linux Student name: XIAO Hong-zhe Advisor:ZHOU Shu-ren Abstract: Linux editions installed on both mobile devices and personal computers have become increasingly commonplace in recent years, owing largely to its highly portability and high degree of freedom. In view of the importance of user friendly operation, almost all of these editions are based on Graphical User Interface(GUI). Based on GTK, one of the most popular cross-platform widget toolkits for creating graphical user interfaces, Gnome is a dominant desktop environment—the GUI which runs on top of a computer operating system. This paper mainly focused on the implementation of a simple GUI interfaced mp3 player using GTK and GStreamer technology under Gnome environment, will illustrate detailed steps on Linux GUI programming. Key words: Linux;mp3 Player;GTK 目 录 11 引 言 11.1 课程设计目的 11.2课程设计的意义 22 编译环境的搭建和检测 22.1 基本编译环境的搭建 22.2 安装GTK/GNOME编译环境 42.3 安装GStreamer编译环境 63 需求分析 63.1 程序设计需求 63.2 需求分析 84 详细设计 84.1 程序文件的组织 84.2 main.c文件 104.3 图形界面的结构 114.4 PLAY按钮 134.5 菜单 144.6 mp3文件的选取与文件名的显示 164.7 GStreamer和文件的播放 214.8 使用滑块来控制的播放进度 244.9 实现的一点补充 265 系统实现 265.1 Makefile文件的编写 275.2 编译,安装,运行,卸载 275.3 制作源代码安装包 296 结束语 30致 谢 31参考文献 1 引 言 1.1 课程设计目的 随着时代的进步,科技的发展。开源化的Linux在IT行业中运用越来越广泛了。熟练的掌握Linux的相关操作,Linux内的程序开发,是作为一名当代大学生的首要目的。 所以,此课程将通过一个使用GTK和GStreamer技术的简单mp3播放器的实现,详细描述Linux环境下的图形界面编程技术。 1.2课程设计的意义 目前,我国在计算机应用、计算机软件和电子类相关专业的人才培养方面,取得了长足的发展,但同时也让我们深刻地感觉到缺乏实际开发设计项目的经验,不善与综合运用所学理论,对知识的把握缺乏融会贯通的能力。本次课程设计是在我们学完了Linux操作系统之后开展的,通过此次Linux操作系统课程设计,可以让我们把书本上的理论知识用于实践中去,对Linux操作系统的各种操作能真正的理解,在设计的过程中,会出现很多问题是我们想不到的,书上也从来没有的,通过实践,提高了我们解决实际问题的能力。本次我的课题的做一个简单的mp3播放器,现在音乐播放器随处可见,应用范围广,一定程度上对人们的生活做了一定的贡献,此次通过对论坛的设计,可以让自己所学的知识融入到实际生活。 2 编译环境的搭建和检测 2.1 基本编译环境的搭建 刚装好的系统中已经有GCC了,但是这个GCC几乎什么文件都不能编译,因为它缺少一些必须的头文件。这里可以选择安装build-essential这个软件包来解决这个问题,方法是在Synaptic里面搜索build-essential或在终端中输入下面命令: sudo apt-get install build-essential 安装完成后写一个C语言程序testc.c测试一下: #include int main() { printf("Hello Ubuntu!\n"); return 0; } 编译运行: $ gcc -Wall testc.c -o testc $ ./testc $ Hello Ubuntu! 这样,基本开发环境就搭建成功了。 2.2 安装GTK/GNOME编译环境 要安装GTK环境,只需要安装一个libgtk2.0-dev软件包就可以了;而安装GNOME开发环境的话,则需要安装gnome-devel,由于它包含有GTK开发包,此时不再需要libgtk2.0-dev包。[8]在一般情况下,由于考虑到以后需要有一个Glade,并且还需要帮助文件,于是安装gnome-devel包和gnome-dev-doc包: sudo apt-get install gnome-devel gnome-dev-doc 安装完成后也同样做个测试程序: #include void hello(GtkWidget *widget,gpointer data) { g_print("Hello Ubuntu!\n"); } gint delete_event(GtkWidget *widget,GdkEvent *event,gpointer data) { g_print ("delete event occurred\n"); return(TRUE); } void destroy(GtkWidget *widget,gpointer data) { gtk_main_quit(); } int main( int argc, char *argv[] ) { GtkWidget *window; GtkWidget *button; gtk_init (&argc, &argv); window=gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT(window),"delete_event",GTK_SIGNAL_FUNC(delete_event),NULL); gtk_signal_connect (GTK_OBJECT (window), "destroy",GTK_SIGNAL_FUNC (destroy), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); button = gtk_button_new_with_label ("Hello Ubuntu!"); gtk_signal_connect (GTK_OBJECT (button), "clicked",GTK_SIGNAL_FUNC (hello), NULL); gtk_signal_connect_object (GTK_OBJECT (button), "clicked",GTK_SIGNAL_FUNC (gtk_widget_destroy),GTK_OBJECT (window)); gtk_container_add (GTK_CONTAINER (window), button); gtk_widget_show (button); gtk_widget_show (window); /*显示一个窗口*/ gtk_main(); /*进入主循环*/ return(0); } 用下面命令编译运行: $ gcc gtkhello.c -o gtktest `pkg-config --cflags --libs gtk+-2.0` $ ./gtktest 此时会显示一个带有一个按钮的窗口,点击按钮以后窗口关闭,命令行显示Hello Ubuntu! 2.3 安装GStreamer编译环境 在Ubuntu中构建GStreamer开发环境同样非常简单,使用下面命令安装GStreamer就可以了。 $ sudo apt-get install gstreamer0.10-tools gstreamer0.10-x gstreamer0.10-plugins-base gstreamer0.10-plugins-good gstreamer0.10-plugins-ugly gstreamer0.10-plugins-bad gstreamer0.10-ffmpeg gstreamer0.10-alsa gstreamer0.10-schroedinger gstreamer0.10-pulseaudio 本段代码用于测试: #include int main (int   argc, char *argv[]) { const gchar *nano_str; guint major, minor, micro, nano; gst_init (&argc, &argv); gst_version (&major, &minor, µ, &nano); if (nano == 1)     nano_str = “(CVS)”; else if (nano == 2)     nano_str = “(Prerelease)”; else     nano_str = “”; printf (“This program is linked against GStreamer %d.%d.%d %s\n”, major, minor, micro, nano_str); return 0; } 用以下命令进行编译: $ gcc –Wall mu.c –o gstest`pkg-config gstreamer-0.10 –cflags –libs` $ ./gstest 成功则输出:“This program is linked against GStreamer 0.10.21”。 至此,编环境搭建完毕。 3 需求分析 3.1 程序设计需求 结合GTK和GStreamer实现一个图形界面mp3音频播放器。 具体需求如下: (1) 能够方便地选择本机上的一个mp3文件进行播放。选择mp3文件时,不需要有键盘输入操作。 (2) 播放过程中可以暂停,暂停后可以从暂停处再次播放。播放过程中可以停止,按下播放按钮后,可以再次从开头播放本文件。本程序提供“快进”、“快退”按钮,分别跳到后方5%处和前方5%处,播放和暂停状态都可以快进快退,并保持当前状态不变。 (3) 播放、暂停、或停止状态中,都可以选择其它mp3文件代替当前mp3文件进行播放。 (4) 本程序中要有一个滚动条来显示当前的mp3文件播放位置,拖动滚动条,可以调节mp3文件的播放位置。 (5) 在面板上显示mp3文件当前播放的时间和mp3文件的名字。播放时间随时刷新,配合好前面的播放调节操作。 (6) 提供“帮助”,“关于”等菜单按钮。退出按钮要绝对有效,能够完美地结束整个程序,不在内存中留下垃圾。 (7) 界面尽量美观,程序运行时不要出现异常。 (8) 程序能在所有使用Gnome的Linux发行版上安装使用。 3.2 需求分析 用GTK和GStreamer实现一个简单的mp3播放器并不是一件困难。但要搞清楚具体执行逻辑,使写出的程序不出现难以解决的逻辑错误而导致最后的返工,还是需要事先认真规划好的。 图形界面mp3播放器。根据常识来看,打开软件后,会出现图形界面,然后点击图形界面,选择mp3文件,点击开始,即可播放了。 图形界面出现后,要想让其响应用户的点击,就必然要有一个类似服务器的进程来执行接受用户命令的任务,GTK中的gtk_main函数可以担当这个角色,因此,本文将围绕这个函数来进行程序的设计。 程序规划图如图3-1所示: 图3-1 程序规划流程图 无论在什么平台下,无论用什么开发工具,GUI应用程序的基本开发方法都是相似的。开发人员都要编写两方面的代码,即应用于实现图形界面的代码和用于实现程序运行的代码,然后再将这两部分的代码结合起来,本程序也不例外。 用GTK绘制完用户界面后,要给控件加上信号,信号中附上自己编写的函数地址。然后,操纵控件时,比如按下按钮后就会产生信号,gtk_main()循环会捕捉这个信号,按照信号注册时附上的函数地址调用特定的函数:例如播放音频的函数。 这样,程序的大体构架就清楚了。即,程序由事件信号驱动,接收到响应信号时调用预先编写好的处理函数进行处理。 4 详细设计 4.1 程序文件的组织 为了程序逻辑的清晰着想,不能把一个大程序中的所有内容放到一个文件里,否则,当程序增大到一定程度时修改和调试将变得极为困难。 再加之前面需求分析中已经提到的,编写GUI程序时,通常的做法是界面和后台分别编码,这样做简单且逻辑清晰。因此可以在main函数中调用绘制界面的函数,并分别注册上信号,用gtk_main()循环完成函数调度的工作。 但绘制界面如果全部都在main函数中完成的话,程序代码会显得非常混乱,因此可以把绘制界面的工作分成很多步来完成,main函数调用每一步中相应的绘制函数。当然可以把这些绘制函数跟main函数都一起放到main.c文件中,但这样做的话,还是不够清晰。不妨新建一个gui.c文件,将完成图形绘制以及注册信号工作的函数集中放到里面。 接着考虑后台处理函数,很自然的,把这些函数都放到back.c文件中。 综上所述,本程序将建立main.c、gui.c、back.c三个头文件。 由于函数之间具有相互调用关系,如果要将这三个.c文件结合起来,就需要用到头文件。因而,将按照程序的需要,选择性地建立main.h、gui.h、back.h头文件,在其中放入对应的.c文件中非静态函数和变量的声明。 4.2 main.c文件 文件的的开始,程序先引入GTK和GStreamer工具包中所有类和函数的声明: #include #include extern GtkWidget *gui(); 我在随后的gui.c文件中将会编写一个生成图形界面的gui()函数。上面将其它文件中的函数导入时,使用了extern关键字,让程序更规范。 接着声明 window变量: GtkWidget *window; GtkWidget类声明了一个名字为window的指针变量。由于gui.c中的函数要使用本变量,这里将其放到main函数外面,使其具有全局作用域。同时,考虑到main.c文件中只有一个main函数和一个全局变量,我决定不再编写main.h头文件,如果其它文件需要使用window变量的,显式的将所需变量引入即可。 下面的是main()函数: int main(int argc, char *argv[]) main函数中,程序首先分别对GTK和GStreamer进行初始化,初始化会设置函数库并处理命令行参数中的GTK和GStreamer知道的参数。由于本程序中用不到这些参数,因此,这里只是起到初始化的作用。[9] 接着,初始化gtk和gst: gtk_init(&argc, &argv); gst_init(&argc, &argv); 窗口本身是由gtk_window_new()创建的: window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 该函数是一个对象构造器,它返回一个指向新GtkWindow对象的指针。命名约定在这里非常重要,因为GTK中的所有对象构造器和方法都要遵循它——名字的第一部分是类名,这里是GtkWindow(对于函数名来说,将它写为gtk_window,以将它和类本身的数据类型区分开来),然后是方法名。gtk_window_new()接受一个参数,这个参数是一个常量,它指定了要创建的窗口类型。GTK_WINDOW_TOPLEVEL是一个正常的应用程序窗口,它可以通过窗口管理器关闭、最小化和调整大小。 调用gst_container_add()函数: gtk_container_add (GTK_CONTAINER (window), gui()); 随后编写的gui()函数将生成并返回window内的各个控件的集合,程序将window作为一个容器,把gui()生成的图形界面加入进来,然后显示窗口。要注意,GTK生成的窗口和控件默认都是不显示的,程序必须调用gtk_widget_show函数来显示它: gtk_widget_show(window); gtk_main(); GTK应用程序采用事件驱动机制,在完成用户界面创建后进入gtk_main()主循环,由此GTK 接管了控制权,然后在主循环中循环地监听事件和信号的发生。当捕获事件或信号后,则将控制权传递至所注册的信号事件处理程序进行处理,然后根据此程序选择返回或者退出GTK应用程序。后面将用到的函数gtk_main_quit用来结束主事件循环,即退出GTK程序的运行。 至此,main.c文件就结束了。显然,它的脉络很清晰,符合需求分析中所作的程序流程图的规划。以下就要分别对图形界面和后台进行编程了。 4.3 图形界面的结构 现在程序有了一个窗口(window变量),但是,如果没有其他构件来提供有用的用户界面,它是没有什么用处的。GTK+中的窗口是GtkBin的子类,它是一种只能包含一个子构件的容器对象。这好像并不是特别有用,但GTK+还提供了各种可以容纳多个构件的GtkBin子类。因此,GTK+中的窗口布局总是在一个GtkWindow中添加一个多构件容器来构成的。这些容器中最有用的是GtkVBox、GtkHBox和GtkTable。GtkVBox允许任意数量的构件垂直叠放。GtkHBox的功能相同,但提供的是水平排列。GtkTable是上述两者的结合,它允许构件在一个灵活的网格中进行排放。[10] 这三种布局构件几乎提供了所有必要的布局功能。需要注意的是,GTK+很少处理对象的绝对位置或大小——容器和其中的构件将调整大小以适应可用空间。不过可以改变这类大小调整的一些约束以获得所需的效果,如果确实需要精确的像素布局,那么GtkFixed可作为最后的选择。 应该首先选择灵活的盒状模型布局(Box),因为它更容易使用。 最后,mp3播放器的最终效果如图4-1所示 图4-1 播放器的最终效果 上图显示了在装载文件进行播放之前的音乐播放器窗口。从上往下,依次是菜单栏,用于显示文件名的标签,五个控制播放的按钮,下面是显示播放经过时间的标签,最后是一个在当前文件中进行搜寻的滑动条。 整个图形界面的布局如图4-2所示 以上显示的是垂直盒子(vbox)及其内部控件的布局,gui()函数返回这个垂直盒子,正如在main函数中看到的那样,把这个垂直盒子放到了window窗口中,整个图形界面就完成了。 图4-2 图形界面的布局 4.4 PLAY按钮 关于构件盒子里的一个个控件的的方法,以“PLAY”按钮为例进行。 首先,先声明垂直盒子的变量: GtkWidget *vbox; 其次,再创造按钮盒子变量: GtkWidget *buttonbox; 再到PLAY按钮变量: GtkWidget *playbutton; 分别为它们赋值: vbox = gtk_vbox_new(FALSE, 0); buttonbox = gtk_hbox_new(FALSE, 0); playbutton = gtk_button_new_with_label("PLAY"); gtk_vbox_new参数的作用是调整构件在盒子中的摆放方式。第一个参数指明组装盒中的构件是否都必须具有相同的高度,一个false值表示构件可能根据它们的需求有不同的高度。第二个参数指明在构件之间的以像素为单位的间距,把它设置为0。gtk_hbox_new中的参数与vbox中的类似。 gtk_button_new_with_label函数返回一个带有文字标签的按钮,本按钮上的文字是“PLAY”。 可是,单有“PLAY”标签并不能让这个按钮发出播放的命令,程序还要为它注册上信号处理函数,让它既有名又有实: g_signal_connect(G_OBJECT(playbutton), "clicked", G_CALLBACK(play_pressed), NULL); 信号处理函数即play_pressed函数。调用本函数完成信号向gtk_main()的注册,每次“click”鼠标来点击“playbutton”以后,gtk_main()会自动调用play_pressed函数。g_signal_connect的第一个参数是要发出信号的构件,第二个参数是你想要连接的信号的名称,第三个参数是信号被捕获时所要调用的函数,第四个参数是你想传递给这个函数的数据。由于不需要向play_pressed函数传数据,这里忽略了第四个参数。G_OBJECT()和G_CALLBACK()都是宏,被定义为具有强制类型转换的功能。 程序把play_pressed的函数放到back.c文件中。函数的功能调用链接GStreamer插件的函数,进行mp3文件的播放。函数原型如下: void play_pressed(); 现在重新回到这个按钮上来。程序刚刚为它添加了信号处理函数,但是,如何做到将按钮本身添加到面板上去呢?这就需要逐级添加了,先把按钮添加到按钮盒里: gtk_box_pack_start(GTK_BOX(buttonbox), playbutton, TRUE, TRUE, 0);gtk_box_pack_start用于把组件添加到盒子里,添加组件的顺序决定了组件在盒子中的位置。竖直盒子中,先添加的在上面,水平盒子中,先添加的在左边。 第一个参数是将组件塞入盒子的指针,这里是按钮盒。第二个参数是要塞入的组件指针,即PLAY按钮。第三个参数是布尔类型,用来控制组件与盒子之间的空间大小关系,其中TRUE 表示允许组件扩展至分配给盒子的空间大小, FALSE表示盒子空间收缩至组件的实际大小。第四个参数是用来控制是否将多余的空间分配给组件,即选择是否将组件扩展到盒子的大小(TRUE);还是多余的空间不变,保留作为盒子和打包组件间的间隔(FALSE),该参数只有在第三个参数取TRUE时才有效。最后一个参数是指组件四周与盒子的间隔大小。 同样的,再把按钮盒放到垂直盒里: gtk_box_pack_start(GTK_BOX(vbox), buttonbox, FALSE, TRUE, 5);最后,全部把他们显示: gtk_widget_show(playbutton); gtk_widget_show(buttonbox); gtk_widget_show(vbox); 这样,就完成了对PLAY按钮图形界面编程方面的全过程。 对于其它的几个按钮来说,跟上面的PLAY按钮的处理相同,除了要注意一下添加进按钮盒的顺序,无需赘述。 4.5 菜单 本程序中有两个菜单大项File和Help,大项中则分别含有数个小项目。File中有Open File和Quit,用于打开mp3文件和退出程序。Help中Help、Contens和About,分别是帮助的内容和关于本程序的一些信息。 菜单的生成较为繁琐。这里以Open File这个子菜单项为例介绍一下: 先声明file、fileitem、open三个菜单项。然后,为file和open两项赋值: GtkWidget *filemenu; GtkWidget *file; GtkWidget *open; file = gtk_menu_item_new_with_label("File"); open = gtk_menu_item_new_with_label("Open File"); 接下来,要给虚菜单项fileitem赋值: gtk_menu_item_set_submenu(GTK_MENU_ITEM(file), filemenu); 注意,file作为一个主菜单项,和它的子菜单项open都是用同一个函数来赋值,那么,如何区分file的特殊地位就成了问题,于是,引入了虚菜单项filemenu来容纳file的子菜单项。以下是将open子菜单项放入filemunu中的方法: gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), open); 给open子菜单项配置信号处理函数,这里的信号不再是按钮的“clicked”,而是“activate”,“activate”后,系统会调用file_selection函数来选择文件打开。 g_signal_connect(G_OBJECT(open), "activate", G_CALLBACK(file_selection), NULL); 接下来,就是把file主菜单项添加进menubox,menubox添加进vbox并逐个显示,不再列出代码。 图4-3是点击About菜单项后弹出的对话框截图: 图4-3 About对话框 4.6 mp3文件的选取与文件名的显示 上文中提到,面板上的title_label标签用于mp3文件名的显示。由于title_label要被后台程序调用显示文件名,所以,程序把title_label设置为全局变量。在gui()函数内部,程序对其进行赋值: title_label = gtk_label_new("Title:"); gtk_misc_set_alignment(GTK_MISC(title_label), 0.0, 0.5); gtk_box_pack_start(GTK_BOX(vbox), title_label, FALSE, FALSE, 0); gtk_widget_show(title_label); gtk_misc_set_algnment函数用于设置misc子类对象的位置,title_label是misc子类GtkWidget的一个对象指针,因此,可以对其进行设置。函数的第二个参数代表左右对齐的方式,0.0代表左对齐,1.0代表右对齐。第三个参数代表上下对齐的方式,0.0代表靠上,1.0代表靠下。 这样,就完成了文件名标签的创建。 上文举例的“Open File”的信号处理函数是file_selection。程序在gui.c文件中实现了file_selection: void file_selection() { GtkWidget *filew; filew = gtk_file_selection_new ("File selection"); g_signal_connect (G_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), "clicked", G_CALLBACK (file_ok_sel), filew); g_signal_connect_swapped(G_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), "clicked", G_CALLBACK (gtk_widget_destroy), filew); g_signal_connect_swapped (G_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), "clicked", G_CALLBACK (gtk_widget_destroy), filew); gtk_widget_show (filew); } 程序声明了一个名字为filew构件,并用gtk_file_selection_new为它赋值。 然后,分别用三条语句为ok_button和cancel_button的信号连接了处理函数。很容易发现,可以同时为ok_button的信号处理函数设置两个信号处理函数。此时,信号处理函数的执行顺序与注册时的顺序相同。 我编写本程序时,曾经遇到的一个问题是:点击OK按钮后可以选择到文件,也就是file_ok_sel函数执行了,但是,本文件选择对话框本身却不关掉,需要手动再按Cancel按钮才能关掉。也就是说,为ok_button注册的第二个处理函数没有工作!这个问题一度困扰我多时,最后发现,file_ok_sel函数调度的一个函数进入了mp3的预处理循环,使得file_ok_sel没法退出。OK按钮的第二个信号处理函数只有等到第一个返回后才能得到执行。修改程序后问题消失。 点击“Open File”菜单后,弹出的文件选择器外观如图4-4所示: 图4-4 文件选择器 点击完OK按钮,先调用file_ok_sel函数,程序在back.c文件中实现本函数: void file_ok_sel(GtkWidget *w, GtkFileSelection *fs) { if(pipeline != NULL) { gst_element_set_state(pipeline,GST_STATE_NULL); gst_object_unref(GST_OBJECT(pipeline)); } title = gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)); gtk_label_set_text(GTK_LABEL(title_label), 1 + strrchr(title, '/')); } if条件中的语句将在下文中描述,这里介绍title变量。title变量也是全局变量,定义如下: static const gchar *title; 因为title只在back.c文件中使用,所以,将其声明为static变量。又由于gtk_file_selection_get_filename函数的返回值是const gchar *类型,所以,声明title变量时,加上const关键字,避免编译器的警告。 gtk_file_selection_get_filename函数返回mp3文件的绝对路径字符串,比如: “/home/wsy/Music/SlamDunk.mp3”。下文中的1 + strrchr(title, ‘/’)的意思是:找到最后一次出现“/”号的位置,并将此位置加1。因此,字符串变为“SlamDunk.mp3”。gtk_label_set_text函数将此字符串显示到title_label标签上。 于是,选择文件后,title_label标签会更新,显示出新文件的文件名。 4.7 GStreamer和文件的播放 选择了文件后,按下PLAY按钮,程序就开始播放了。前文提到,PLAY按钮注册的信号处理函数是play_pressed(),下面来分析一下这个函数: void play_pressed() { GstState state; gst_element_get_state(pipeline, &state, NULL, -1); if(state == GST_STATE_PLAYING || state == GST_STATE_PAUSED) return; else start(title); } 容易发现,前面的都是分析gst状态的。在if语句中,当gst不在播放和暂停状态时,会调用start函数。 void start(const gchar *filename) { GMainLoop *loop; //定义组件 GstElement *source,*decoder,*sink; GstBus *bus; //创建主循环,在执行 g_main_loop_run后正式开始循环 loop = g_main_loop_new(NULL,FALSE); //创建管道和组件 pipeline = gst_pipeline_new("audio-player"); source = gst_element_factory_make("filesrc","file-source"); decoder = gst_element_factory_make("mad","mad-decoder"); sink = gst_element_factory_make("autoaudiosink","audio-output"); if(!pipeline||!source||!decoder||!sink){ g_printerr("One element could not be created.Exiting.\n"); return; } //设置source的location 参数。即文件地址. g_object_set(G_OBJECT(source),"location", filename,NULL); //得到管道的消息总线 bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); //添加消息监视器 gst_bus_add_watch(bus,bus_call,loop); gst_object_unref(bus); //把组件添加到管道中.管道是一个特殊的组件,可以更好的让数据流动 gst_bin_add_many(GST_BIN(pipeline),source,decoder,sink,NULL); //依次连接组件 gst_element_link_many(source,decoder,sink,NULL); gst_element_set_state(pipeline,GST_STATE_PLAYING); //每隔1000毫秒,更新一次滚动条的位置 g_timeout_add (1000, (GSourceFunc) cb_set_position, NULL); //开始循环 g_main_loop_run(loop); gst_element_set_state(pipeline,GST_STATE_NULL); gst_object_unref(GST_OBJECT(pipeline)); } 这里就是音乐播放器的核心部分了,它将由GStreamer多媒体框架提供支持。 GStreamer的操作需要应用程序的开发者创建管道。每个管道由一组元素组成,每个元素都执行一个特定功能。通常情况下,一个管道以某种类型的源元素开始,这可能是被称为source的元素,它从磁盘上读取文件并提供该文件的内容,也可能是通过一个网络连接提供缓冲数据的元素,甚至可能是从一个视频捕捉设备获取数据的元素。管道中还存在一些其他类型的元素,如解码器(用于将声音文件转换为处理所需的格式)、分离器(用于从一个声音文件中分解出多个声道)或其他类似的处理器。管道以一个输出元素结束,它可以是从一个文件写入器到一个高级Linux音频体系结构(ALSA)音频输出元素或一个基于Open GL的视频播放元素的任何元素。这些输出元素被称为“sink”(接收器)。[11] gst_element_factory_make()用来创建不同的元件。此函数是一个可以构建任何GStreamer元素的通用构造函数。它的第一个参数指定要构建的元素名。GStreamer使用字符串名称来确定元素类型,从而方便添加新元素。如果需要,一个程序可以从配置文件或用户那里接受元素名称并使用新的元素而不需要重新编译程序来包括定义这些元素名的头文件。只要指定的元素是正确的(这可以在程序运行时进行检查),它们就可以完美地操作而不需要改变任何代码。函数的第二个参数用于给元素命名。元素名称在程序的其余部分不再使用,但它对识别一个复杂管道中的元素确实有其用处。本例中,source是filesr工厂创建的,功能是读取磁盘文件;decoder是mad工厂创建,用作MP3解码器;sink是autoaudiosink工厂创建,输出音频流到声卡。程序用gst_bin_add_many()函数将这三个部件都加入管道pipeline中,然后用gst_element_link_many()来连接他们,这样他们就可以配合工作了。 应该注意到,程序中用GMainLoop定义了一个主循环loop对象,这个主循环其实跟GTK主循环类似,用于处理信号。g_main_loop_run()函数开始主循环。 信号是在总线上传播的,程序用gst_pipeline_get_bus(pipeline)从管道中得到总线。然后可以使用gst_bus_add_watch(bus, bus_call,loop),以总线bus、bu_call函数、loop主循环这三项作为参数来创建一个消息处理器来侦听管道。每当管道发出一个消息到总线,这个消息处理器就会被触发,它调用bus_call来处理消息。 程序在这里实现了bus_call函数,这里截取处理消息的Switch语句中的一部分来说明退出主循环的方法: case GST_MESSAGE_EOS: g_main_loop_quit(loop); break; 如果截取到是GST_MESSAGE_EOS类型的消息,就调用g_main_loop_quit()来退出主循环。当mp3文件播放结束后,会生成上述消息,因此,会自动退出主循环。 在start函数中,程序在其退出主循环后,分别调用如下两个函数: gst_element_set_state(pipeline,GST_STATE_NULL); gst_object_unref(GST_OBJECT(pipeline)); gst_element_set_state()用来改变元件的状态,GST_STATE_NULL是默认状态,在这个状态下,会回收元件所占有的资源。gst_object_unref ()来对它进行解引用。这会将一个元件的引用数减少1。任何一个元件在创建时,其引用记数为1。当其引用记数为0时,该元件会被销毁。 这两个函数都是对pipeline进行操作的,实际上,把pipe程序line定义成了全局变量。现在回头再看前面的start函数: GstState state; gst_element_get_state(pipeline, &state, NULL, -1); if(state == GST_STATE_PLAYING || state == GST_STATE_PAUSED) return; else start(title); 现在很清楚了,程序用gst_element_get_state()来获取pipeline的运行状态。第一个参数自然表示要得到state的组件。第二个参数用来设置要放置状态的位置。第三个参数表示未决状态(pending state)的位置。第四个参数是超时时间,-1代表不设置超时时间。[12] 如果状态是正在播放或者暂停,那么,程序就不让PLAY按钮起作用。 那么,怎么暂停呢?程序中使用了PAUSE按钮: void pause_pressed() { GstState state; gst_element_get_state(pipeline, &state, NULL, -1); if(state == GST_STATE_PLAYING) gst_element_set_state(pipeline, GST_STATE_PAUSED); if(state == GST_STATE_PAUSED) gst_element_set_state(pipeline, GST_STATE_PLAYING); } 上面的代码很简单,下面的代码显示的是STOP按钮的实现: void stop_pressed() { if(pipeline != NULL) { gst_element_set_state(pipeline,GST_STATE_NULL); gst_object_unref(GST_OBJECT(pipeline)); } } 加上pipeline是否为NULL的判断,是为了防止在没有进行pipeline的初始化之前,试图去获取其状态,从而引发异常。 4.8 使用滑块来控制的播放进度 要控制播放进度,最理想的办法是允许用户拖动滑块,从而让数据流立刻改变其播放位置。GTK和GStreamer配合可以实现这个功能。当用户改变GtkScale构件的值时,它将发送一个value-changed信号。程序将一个回调函数连接到这个信号: bar = gtk_hscale_new_with_range(0, 100, 1); gtk_scale_set_draw_value(GTK_SCALE(bar), FALSE); gtk_range_set_update_policy(GTK_RANGE(bar), GTK_UPDATE_DISCONTINUOUS); g_signal_connect(G_OBJECT(bar), "value_changed", G_CALLBACK(seek_value_changed), NULL); 先创建一个范围从0到100,粒度为1的滚动条。 gtk_scale_set_draw_value()用于告诉比例构件不要在滑块的旁边以数字形式显示其当前位置。 下一个设定修改的是构件的更新方式,GTK_UPDATE_DISCONTINUOUS指定只有当用户结束拖动时才发送value_changed信号。 程序在back.c中实现了seek_value_changed函数: void seek_value_changed(GtkRange *range, gpointer data) { gdouble val = gtk_range_get_value(range); if (no_seek) return; else seek_to(val); 关于no_seek的意义,下文再谈。 seek_to()使用一个百分比数字作为其参数,它表示用户想要移动的位置离数据流的开始有多远。这个函数也在back.c中实现,如下所示: void seek_to(gdouble percentage) { GstFormat fmt = GST_FORMAT_TIME; gint64 length; if(pipeline && gst_element_query_duration(pipeline, &fmt, &length)) { 首先,该函数将检查是否有一个有效的管道。如果有而且可以成功获取当前数据流的持续时间,它将根据这个持续时间和用户提供的百分比来计算用户想要移动的位置的GStreamer时间值。 gint64 target = ((gdouble)length * (percentage / 100.0)); 实际的播放位置移动是通过gst_element_seek()调用完成的: if(!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, target, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) g_warning("Failed to seek to desired position\n"); } } gst_element_seek()函数使用几个参数来定义移动的方式。值得庆幸的是,对于默认行为来说,大多数参数可以使用预定义的函数库常量来设置。这些参数设置了元素的格式和类型,以及搜索的终止时间和类型。唯一需要提供的参数是接收事件的元素(管道pipeline)和移动时间值(target)。 这样,程序就可以做到,拖动滑块的时候,播放的mp3文件的播放位置随之改变。但是,还有一个问题没有解决:mp3播放时滑块也应该随之移动。 查询GTK的API后发现,有一个函数可以设置滑块的位置:gtk_adjustment_set_value()。程序还需要一个函数来实时返回GStreamer的状态,并调用上述函数,我在GStreamer中找到了g_timeout_add()函数,并把它放到前面的start()函数中调用。 g_timeout_add(1000, (GSourceFunc)cb_set_position, NULL); 本函数可以利用每隔第一个参数的毫秒数就调用第二个参数中的函数指针一次,第三个函数用于传递参数,将它设置为NULL。 void cb_set_position () { GstFormat fmt = GST_FORMAT_TIME; gint64 pos, len; GtkAdjustment *adj = gtk_range_g
/
本文档为【Linux下mp3播放器的实现】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索