`
gidlsl
  • 浏览: 14621 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

linux下编程实现mplayer播放器总结

阅读更多

linux下编程实现mplayer播放器总结
2011年05月27日
  一:mplayer简介
  MPlayer是一款开源的 多媒体播放器,以GNU通用公共许可证发布。此款软件可在各主流作业系统使用,例如Linux和其他类Unix作业系统、 微软的视窗系统及苹果电脑的Mac OS X系统。MPlayer是建基于命令行界面,在各作业系统可选择安装不同的图形界面。
  因为linux下都是命令行的操作方式,所以对mplayer的各种操作都是用命令来实现的,这次主要用的是它的slave工作方式
  slave模式协议
  1,简介:
  默认mplayer是从键盘上获得控制信息
  mplayer另外提供了一种更为灵活的控制方式,用来进行播放控制--slave模式
  在slave模式下,MPlayer为后台运行其他程序,不再截获键盘事件,
  MPlayer会从标准输入读一个换行符(\n)分隔开的命令。
  2,操作:
  #mplayer -input cmdlist
  // 会打印出一份当前mplayer所支持的所有slave模式的命令
  方法一:从控制台输入控制命令(测试使用)
  运行mplayer -slave -quiet ,并在控制台窗口输入slave命令。
  //-slave 启动从模式  
  //-quiet 不输出冗余的信息
  常用到的 Mplayer指令:
  loadfile   string   //参数string 为 歌曲名字。 
  volume 100 1 //设置音量 中间的为音量的大小。
  mute 1/0//静音开关
  pause //暂停/取消暂停
  get_time_length //返回值是播放文件的长度,以秒为单位。
  seek value  //向前查找到文件的位置播放 参数value为秒数。
  get_percent_pos//返回文件的百分比(0--100)
  get_time_pos//打印出在文件的当前位置用秒表示,采用浮点数
  volume  [abs] //增大/减小音量,或将其设置为,如果[abs]不为零
  get_file_name//打印出当前文件名
  get_meta_album//打印出当前文件的'专辑'的元数据
  get_meta_artist//打印出当前文件的'艺术家'的元数据
  get_meta_comment//打印出当前文件的'评论'的元数据
  get_meta_genre//打印出当前文件的'流派'的元数据
  get_meta_title//打印出当前文件的'标题'的元数据
  get_meta_year//打印出当前文件的'年份'的元数据
  方法二:从有名管道(fifo)输入控制命令(应用编程中使用)
  #mkfifo 
  #mplayer  -slave  -input  file= 
  //用户可以通过往管道里写入slave命令来实现对应的功能
  主进程创建一个无名管道和一个有名管道
  1:开一个子进程
  在子进程中:
  启动Mplayer,参数规定通过命名管道进行通信;
  把子进程的标准输出重定向无名管道的写端;
  Mplayer从命名管道读到主进程发送的命令;
  Mplayer发出的内容发送到无名管道中,父进程通过读管道就可以读到Mplayer发出的信息。
  2:在父进程中:
  启动两个线程
  第一个线程,不断使用fgets从键盘获取一个字符串命令,并写入命名管道中
  第二个线程,循环检测无名管道是否有信息可读,有信息将其打印输出在屏幕上       二,实现的功能:
  1、 显示播放列表 
  2、 当前播放的歌曲名字,字体为白色,背景为蓝色。 
  3、 可以通过点击相应的歌曲名字,播放相应的歌曲 
  4、 实现歌曲的播放,暂停,上一首,下一首。 
  5、 实现快进功能。 
  6、 在歌词秀窗口中显示歌词。 
  三,实现的方案:
  第0步: 功能:初始化图形库和触摸屏,实现背景窗口的初始化。
  要求:创建一个mplayer_init.c 在此文件中  写一个函数tft_init()初始化窗口
  提示:用到的接口函数
  1:TFT_Init();图形库的初始化函数
  2:ts_cal_init();  触摸屏的初始化函数
  3:创建窗口
  背景窗口:
  WindowBack =  TFT_CreateWindowEx(0,0,320,240,COLOR_WHITE);
  歌曲进度窗口:
  Window_rate =  TFT_CreateWindowEx(5,102,180,5,COLOR_GREEN);
  音量调节窗口:
  Window_vol= TFT_CreateWindowEx(110,118,45,5,COLOR_GREEN);
  歌词显示窗口:
  Window_lrc=TFT_CreateWindowEx(4,151,180,86,COLOR_B LUE);
  歌词列表窗口:
  WindowList =  TFT_CreateWindowEx(194,30,123,200,COLOR_BLACK); 
  歌曲实时信息窗口
  Window_time =  TFT_CreateWindowEx(5,17,177,28,COLOR_BLACK); 
  歌曲信息窗口
  Window_msg =  TFT_CreateWindowEx(5,47,177,54,0xf81f); 
  第1步:功能:从文件夹中读取歌曲名字,保存在全局的指针数组中。在屏幕上实现歌词列表。
  要求:1:把某个目录下的歌曲文件名字,全部赋值给指针数组。(写一个函数get_song_list()实现)
  2:把所有的歌曲列在歌词列表窗口中,当前播放歌曲的名字在列表中应该用个矩形框反显一下,以示区别:通过清窗口,然后重新往窗口打印来实现)
  在mplayer_init.c 中写一个函数去实现功能。
  提示:获取某个目录下文件的名称所用函数
  DIR* opendir(char* pathname);   打开歌曲文件夹
  struct dirent * readdir(DIR* dir);  获取过来保存在指针数组中
  int closedir(DIR *dir);           关闭该文件夹
  opendir 返回一个DIR类型的指针。
  readdir参数是opendir返回的指针。返回值是struct dirent类型的指针。
  比如:readdir函数返回值为dp,dp->d_name 即文件的名字。
  循环把dp->d_name 给全局的指针数组赋值即可,
  赋值之前要判断一下dp->d_name[0]=='.' 如果相等则continue,
  还要判断后缀是否是.mp3;否则循环给指针数组赋值
  注意每次赋值之前要malloc空间。
  循环赋值的时候给一个变量++ 测出有多少首歌
  第2步:功能:播放\暂停,上一首、下一首,快进、快退,点播放列表中歌曲的名字实现切换歌曲。 // 用命名管道
  要求:切换歌曲的时候,播放列表中的相应歌曲名字要反显。
  提示:歌曲的切换通过改变指针数组中的参数实现,即按下相应切换键的时候改变指针数组的参数,再发送指令切换歌曲。
  在歌词列表实现之后,创建子进程,在子进程中启动Mplayer
  启动Mplayer的语句: execl("./mplayer","mplayer","-ac","mad","-slave"," -quiet","-input","file=/tmp/my_fifo",buf,NULL);   参数:"-ac" "mad"             用的mad解码器
  "-slave"  Mplayer运行在slave模式下,在关于slave模式,MPlayer为后台运行,不再截获键盘事件,MPlayer 从标准输入读取以新行 (\n) 分隔开的命令行
  "-quiet"  使得控制台消息少输出; 特别地, 阻止状态行 (即 A: 0.7 V: 0.6 A-V: 0.068 ...)的显示。
  "-intput" "file=/tmp/fifo"    Mplayer 通过命名管道获取命令。
  buf    是歌曲的路径字符串的首地址
  NULL     在参数的最后一个为NULL,Mplayer可以通过它来判断到底有多少个参数,这个是必须有的。
  在execl中规定MPlayer从命名管道中获取消息,主进程中就必须通过向命名管道写"命令字符串"来控制Mplayer,
  所以在主进程中必须创建子进程之前创建fifo,父子进程通过fifo通信。
  主进程中创建touch_pthread线程。任务是:检测触摸屏,检测到相应的键后,干相应的事情。
  第3步:功能:屏幕上显示歌曲长度、当前播放到多少秒、当前歌曲的"专辑、歌手、标题、发行年份",进度条
  要求:
  1:在屏幕歌曲信息窗口中显示歌曲的总长度,当前播放时间.(切换歌曲活快进快退的时候刷新信息)
  2:在歌曲信息窗口中显示 歌曲的"专辑、歌手、标题、发行年份".(切换歌曲的时候刷新信息).
  3:播放进度条随着时间推移。(切换歌曲、或快进快退的时候可以刷新)
  提示:1: 父子进程通过管道通信。即子进程通过管道把消息传给父进程
  子进程把Mplayer输出的信息重定向到管道中。
  主进程从管道中读,读出来后解析再做相应的处理。
  2: 主进程几个创建子线程
  1:pipe_read循环读管道把读到的消息保存在字符数组中。
  2:pipe_read_dispose循环解析读到的消息,把有用的消息解析出来,做相应的处理
  3:get_percent_pos每隔一段时间发一条检测时间的命令,获取当前播放时间。
  第4步:功能:在歌词窗口显示歌词。
  要求:歌词循环打印,歌词与歌曲同步,切换歌曲的时候切换新的歌词。
  提示:可以用Mplayer返回回来的当前播放时间去查找歌词解析里的时间,这样快进歌词也可以跟着同步。
  如果用以前的虚拟时间,歌曲快进,歌词不能同步。
  歌词解析功能也是创建一个新的线程去完成
  四,思路
  1,初始化:
  在编写任何一个项目程序之前,都有一些初始化工作要做,首先必须把该项目要用的硬件配置好,也就是静态的程序工作,上边的第零步和第一步都是初始化工作。还有一个初始化的就是触摸屏,因为之词用的是图片,所以要找到图片是对应功能键在触摸屏上的位置,包括x,y坐标的范围和对应的功能键,这个以通过建立一个结果体数组,然后有键按下后判断其范围,并把对应的键值返回:
  这在touchscreen.c文件里实现
  2,从最基本的功能一步一步实现最终的功能,基本功能是实现最终功能的基础
  3,写程序之前应该分析项目的整体实现方法,要有可行性,不要最后走到死胡同
  4,做完之后要检测,看某些地方有没有再好的实现方法。
  在分析项目的时候,看要不要用进程,用不上进程的地方就尽量不要用,进程一般用在、、、;用了进程之后,进程之间如何通信,有关联的进程数据传输一般用无名管道,无关联的用有名管道,
  一些实时性要求比较高的地方要用到线程,比如等待触摸屏,独立的线程处理比较简单,但关联的线程处理起来就比较麻烦,信号,互斥锁,信号量,都不能用的时候就自己建立一个标志位,进行控制;
  Mplayer的执行和控制部分:
  1,简单的播放歌曲
  在该项目中,mplayer可执行程序的运行要通过exec函数来实现,这种函数执行完之后就退出线程了,因此必须给他新建一个子线程,
  if((pid=fork())==-1)
  {
  perror("fork");
  exit(1);
  }
  else if(pid==0)//在子进程中播放歌曲
  {
  char song[SONG_CHNUM];
  close(pipedes[0]);
  dup2(pipedes[1],1);
  sprintf(song,"%s%s","./song/",song_list[0]);//得到整个歌曲路径
  execlp("./mplayer","","-ac","mad","-slave","-quiet ","-input","file=fifo",song,NULL);}
  通过程序控制mplayer要用有名管道传送命令,通过无名管道读取mplayer返回的信息,因为mplayer默认是把信息发送到标准输出上,所以要用dup2()中定向标准输出到无名管道的写端:dup2(pipedes[1],1);。
  创建有名管道和无名管道
  unlink(FIFO);//如果管道存在,先删除 if(mkfifo("fifo",IPC_CREAT|0x744)==-1)//创建有名管道
  {
  perror("mkfifo");
  exit(1);
  }
  if(pipe(pipedes)==-1)//创建无名管道用于从mplayer读取歌曲信息
  {
  perror("pipe");
  exit(1);
  }
  因为在该项目中要经常向mplayer发送命令,那么就建立一个函数通过写有名管道向mplayer发送命令:
  写之前在主进程中打开:
  if((fd =open(FIFO,O_RDWR))==-1)
  {
  perror("open");
  exit(1);
  }
  void send_cmd(char *cmd)//通过有名管道向mplayer发送命令
  {
  if((write(fd,cmd,strlen(cmd)))!=strlen(cmd))
  {
  perror("write cmd");
  }
  }
  这样一个简单的mplayer就建立成功了,运行这个框架下的程序,可以自己播放一首歌,一首歌播完后由于exec函数的性质,整个程序就执行完了。
  2,触摸屏处理
  要进行控制的话就要对触摸屏进行处理,初始化不必说了,因为触摸屏要实时监测,所以必须起一个新的线程,而该线程只检测触摸屏,没有跟其他县城竞争资源:
  void *touch_pthread()
  {
  int key=0;
  while(1)
  {
  usleep(100*MS);//延时处理触摸屏键值
  if(ts_read(&ts)==-1)//读取按下的点
  continue;
  if((key=Touch_Trans(ts.x,ts.y))==-1)//将按下的点转化成设定的键值
  continue;
  key_dispose(key);//处理键值
  }
  return NULL;
  }
  该进程通过执行读取触摸屏函数ts_read(&ts)和合判断键值函数Touch_Trans(ts.x,ts.y)的到按下点对应的键值key,送往键值处理函数key_dispose(key)去执行相应的按键动作。
  在按键处理函数里:
  有暂停,快进,快退,上一首,下一首等等,这些都是通过发送给mplayer命令来实现的,mplayer,有这样一个属性,在暂停之后,不管向它发送什么命令,他都会结束暂停,所以在发送暂停命令之后要关闭其它线程向mplayer发送命令,通过以下方法来实现:
  case SONG_PAUSE://暂停
  {
  i++;
  if(i%2==1) 
  my_lock=0;/*向mplayer发送命令暂停标志位*/
  else if(i%2==0)
  my_lock=1;
  send_cmd("pause\n");
  break;
  }
  my_lock是个全局变量,但它为1时,其他线程才能给mplayer发送命令。与2取余是因为暂停和取消暂停时一个键(命令),
  换歌是通过发送命令来实现的而不是改变歌名
  每次换歌之后要执行一些操作,所以在换歌后设置一个标志位:new_song_flag=1; 换个的时候注意歌曲数目不要越界;
  3,获取歌曲信息
  获取歌曲信息也是通过向歌曲发送命令来执行的,然后读取mplayer返回来的信息,有两种类型的信息,
  一种是在开始播放歌曲,即换歌后要发送的命令:获取歌曲信息命令
  一个是在整个歌曲播放过程中一直发送的命令:获取歌曲时间和进度命令
  这两种命令不能同时发送,否则有的时候收到的信息就是混杂的,不方便解析;
  实时发送时间命令是一个线程,发送歌词信息命令在另一个线程暂时性的执行,这种互斥访问首先先到的是互斥锁,但在切歌后,获取歌词信息命令必须发送,而互斥锁有个线程抢占的过程,那么暂时性的执行就有可能得不到互斥锁,那么只能自己设定标志位了:
  发送获取歌词函数:
  void get_song_msg()
  {
  int i;
  my_lock=0;//设置标志位,让两一个进程停止向mplayer发送命令
  printf("my_lock=%d\n", my_lock );
  char *song_msg_cmd[4]={"get_meta_title\n","get_meta_art ist\n",
  "get_meta_album\n","get_meta_year\n"};
  for(i=0;iread(pipedes[0],buf,sizeof(buf))) == -1)//读取mplayer发过来的歌曲信息
  {
  perror("read pipe");
  exit(1);
  }
  if( size == 0)//如果没有读到信息,则返回继续读取
  continue;
  buf[size]='\0';//使信息变成字符串,便于处理
  //printf("******************msg_buf=%s\n\n",buf);
  strcpy(msg_buf,buf);
  if(strncmp(buf,"ANS_META",8) ==0)//获取歌曲信息
  {
  buf[strlen(buf)-2]='\0';//多减一个去掉引号
  msg_dispose(buf);
  }
  sem_post(&cmd_sem) ;
  }
  return NULL;
  }
  处理信息线程:
  void *pipe_read_dispose_pthread()
  {
  char buf[REC_MSG_CHNUM];
  while(1)
  {
  sem_wait(&cmd_sem) ;
  strcpy(buf,msg_buf);
  if(strncmp(buf,"ANS_PERCENT_POSITION", 20)==0)//获取进度信息
  {
  percent_dispose(buf);
  } 
  else if(strncmp(buf,"ANS_TIME_POSITION", 17) ==0)//获取歌曲当前播放时间
  {
  time_dispose(buf);
  }
  else if(strncmp(buf,"ANS_LENGTH",10) ==0)//获得歌的总长度
  {
  length_dispose(buf);
  }
  }
  return NULL;
  }
  在这遇到一个问题,搞了好长时间,自动切歌的时候,不更新歌曲信息:
  有两个原因:第一个是,发送获取歌词信息命令和获取时间命令同时发送时,接收到信号不规范:
  ******************msg_buf=POSITION=0.0
  ANS_PERCENT_POSITION=0
  ANS_META_TITLE='梦醒时分'
  ANS_META_ARTIST='陈淑桦'
  ANS_META_ALBU
  ******************msg_buf=M='情牵淑桦'
  ANS_META_YEAR='1999'
  ANS_LENGTH=246.00
  ANS_TIME_POSITION=0.5
  ANS_PERCENT_POSITION=0
  就这种,一个buf,就收到好几条信息
  用标志位隔开后,好了,可以接受单条信息,但就在接收歌词信息的时候,程序不执行信息解析线程,就只好把歌曲信息解析判断放在上边了;
  信号量的作用是在有收到信息的情况下解析信息。
  如果放在同一线程中,是不是每次解析之前延时一段时间,活用一个类似于信号功能的方法,让他不要一直解析。   解析了信息之后就进行相应的操作,
  更新歌曲信息
  更新时间,显示时间
  实时更新进度条,但播放到了99%就自动换歌,跟执行手动换歌一样。
  4,加上歌词
  在歌曲播放开始获取歌词,解析到链表里,另建一个线程,在播放歌曲的同时通过比较歌曲播放时间来显示歌词,
  滚屏用移位法,
  TFT_ClearWindow(Window_lrc);
  TFT_SetColor(Window_lrc,COLOR_WHITE);
  for(i=0;ilrctent);
  就是在每次显示之前,清屏,然后把显示缓冲区的字符串数组前一个歌词用后一个覆盖,新的歌词加到最后一个缓冲区
  每次切歌之后,都要在歌词列表区对当前播放的歌进行高亮显示,获取歌曲信息,获取歌词,显示歌词,释放上次歌词解析是为链表molloc的空间。
  void slice_song(void) {      high_song();//高亮当前歌曲名     free_link();//释放链表空间  get_lrc();  //得到歌词     sleep(1);//延时让mplayer发送完刚开始的信息     get_song_msg();//发送获得当前歌曲信息的命令     sleep(1) ;     msg_disply();//显示歌曲信息    } 
  学到的知识:
  指针:
  指针必须指向一块地址之后才能对其操作
  在二维指针中
  Char *buf[10];
  这只是定义了10个指针,这10个指针的地址在一块,但他们指向的空间还没有分配,用之前必须分配地址大小
  对于指针而言,它是指向一个内存区域,你对它操作的时候,应该知道你操作的实际地址是什么,这个地址里的数据你在以后还会用吗,这次改变对后续操作有影响吗?一般来说用指针是方便读取数据,最好不要用指针对程序多出共享地址(全区地址)的内容进行修改
  格式输入输出:sprintf(song_msg.length,"%02d:%02d",minu,sec);
  数据宽度是2,不够的话在左边补0;
  对进程的使用和理解,线程,互斥锁,信号等等
  文件包含:
  每一个.c文件对应一个.h文件
  .c文件定义全局变量,函数以及函数功能的实现
  .h文件声明全局变量,声明函数,宏定义,结构体定义等等
  然后总的定义一个.h文件,把所有的.h文件包含,每个.c文件包含这个总的.h文件
  每个.h文件要有条件编译;
  来至:http://blog.csdn.net/panda19881/archive/2011/01/19 /6152581.aspx
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics