MPlayer 学习应用笔记(一)

Feb 1, 2016


记录开发中实际涉及的MPlayer知识


启动MPlayer的代码执行流程

项目中我从TF卡中读取所有被支持的音频文件,创建成一个简单的playlist.txt。应用程序中收到播放音乐命令时通过以下代码启动MPlayer。

ret = execlp("mplayer", "mplayer",
			 "-slave", "-input",
			 "file=/tmp/my_fifo",
			 "-playlist",
			 "/usr/sbin/playlist.txt",
			 "-loop", "0",
			 NULL
			);

-slave选项是将MPlayer设置为slave模式,这样就可以通过向MPlayer进程发送命令来控制其运行状态。通过-input选项来指定如何传输命令。

-input选项详细说明

命令 描述
conf=文件 读取另外的input.conf.如果没有给出路径名,将假设是~/.mplayer.
ar-delay 在开始自动重复一个键之前等待多少毫米(0表示禁用)。
ar-rate 当自动重复时,每秒重复多少次。
keylist 列出所有可以被绑定的键。
cmdlist 列出所有可以被绑定的命令。
js-dev 指定可用的游戏操纵杆设备。
file 从指定文件读取命令,用于命令管道。

上面我们就是通过file=/temp/my_fifo指定my_fifo这个命令管道来接受我们应用程序发送过来的命令。

-playlist选项

由于我们是想让MPlayer播放我们所建playlist.txt中的音乐文件,所以我们通过-playlist传入/usr/sbin/playlist.txt参数,让MPlayer读取这个文件生成playtree。 下面我们来追踪一下MPlayer是如何一步步生成playtree的。

mpctx->playtree = m_config_parse_mp_command_line(mconfig, argc, argv);

MPlayer.c文件中我们可以找到MPlayer的main函数,上面这行代码就是解析-playlist选项生成playtree的入口。

tmp = is_entry_option(opt,(i+1<argc) ? argv[i + 1] : NULL,&entry);

进入m_config_parse_mp_command_line,我们会发现代码应该会进入上面这句继续执行,里面出现的“if(strcasecmp(opt,”playlist”) == 0)”这句话验证了我们的推断。

static int is_entry_option(char *opt, char *param, play_tree_t** ret) {
  play_tree_t* entry = NULL;

  *ret = NULL;

  if(strcasecmp(opt,"playlist") == 0) { // We handle playlist here
    if(!param)
      return M_OPT_MISSING_PARAM;

    entry = parse_playlist_file(param);
    if(!entry)
      return -1;
    else {
       *ret=entry;
       return 1;
    }
  }
    return 0;
}

显然得进入parse_playlist_file函数继续追踪。

play_tree_t*
parse_playlist_file(char* file) {
  stream_t *stream;
  play_tree_t* ret;
  int f=DEMUXER_TYPE_PLAYLIST;
  stream = open_stream(file,0,&f);

  if(!stream) {
    return NULL;
  }
  ret = parse_playtree(stream,1);
  free_stream(stream);

  play_tree_add_bpf(ret, file);

  return ret;
}

上面的open_stream主要工作是继续调用open_stream_full()

stream_t* open_stream_full(const char* filename,int mode, char** options, int* file_format) {
  int i,j,l,r;
  const stream_info_t* sinfo;
  stream_t* s;
  char *redirected_url = NULL;

  for(i = 0 ; auto_open_streams[i] ; i++) {
    sinfo = auto_open_streams[i];
    if(!sinfo->protocols) {
      continue;
    }
    for(j = 0 ; sinfo->protocols[j] ; j++) {
      l = strlen(sinfo->protocols[j]);
      // l == 0 => Don't do protocol matching (ie network and filenames)
      if((l == 0 && !strstr(filename, "://")) {
	*file_format = DEMUXER_TYPE_UNKNOWN;
	s = open_stream_plugin(sinfo,filename,mode,options,file_format,&r,
				&redirected_url);
	if(s) return s;
	}
	break;
      }
    }
  }

  return NULL;
}

上面的代码还是比较复杂的,我已经去除了其他的LOG输出和不会进入的程序分支,排除一下干扰,简化一下代码。 BY THE WAY,看到这里我感觉MPlayer的代码写的很一般。 auto_open_streams[]是定义在Stream.c中的静态全局数组,其实里面就是注册一些MPlayer所支持的流格式的“处理器”。 查看auto_open_streams[]中的所有注册的处理器,我们发现只有stream_info_file是用来处理我们传入的playlist的。

struct stream;
typedef struct stream_info_st {
  const char *info;
  const char *name;
  const char *author;
  const char *comment;
  /// mode isn't used atm (ie always READ) but it shouldn't be ignored
  /// opts is at least in it's defaults settings and may have been
  /// altered by url parsing if enabled and the options string parsing.
  int (*open)(struct stream* st, int mode, void* opts, int* file_format);
  const char* protocols[MAX_STREAM_PROTOCOLS];
  const void* opts;
  int opts_url; /* If this is 1 we will parse the url as an option string
		 * too. Otherwise options are only parsed from the
		 * options string given to open_stream_plugin */
} stream_info_t;

const stream_info_t stream_info_file = {
  "File",
  "file",
  "Albeu",
  "based on the code from ??? (probably Arpi)",
  open_f,
  { "file", "", NULL },
  &stream_opts,
  1 // Urls are an option string
};

stream_info_file的protocols[]里面是{“file”, “”, NULL},所以符合if((l == 0 && !strstr(filename, “://”))代码处的判断,所以程序 会继续调用open_stream_plugin()

static stream_t* open_stream_plugin(const stream_info_t* sinfo, const char* filename,
                                    int mode, char** options, int* file_format,
                                    int* ret, char** redirected_url)
{
  void* arg = NULL;
  stream_t* s;
  m_struct_t* desc = (m_struct_t*)sinfo->opts;

  // Parse options
  if(desc) {
   
    if(options) {

    }
  }
  s = new_stream(-2,-2);
  s->url=strdup(filename);
  s->flags |= mode;
  *ret = sinfo->open(s,mode,arg,file_format);
  if((*ret) != STREAM_OK) {

  }

  s->mode = mode;

  return s;
}

open_stream_plugin()函数处理的东西本身是比较复杂的,这里我仍然只列出本应用会执行的部分。 其中的new_stream()只是创建一个stream对象,然后初始化这个对象里面的一些变量。 主要的部分还是执行sinfo->open(s,mode,arg,file_format);。这里的回调函数open就是之前stream_info_file中的open_f

static int open_f(stream_t *stream,int mode, void* opts, int* file_format) {
  int f;
  mode_t m = 0;
  off_t len;
  unsigned char *filename;
  struct stream_priv_s* p = (struct stream_priv_s*)opts;

  if(mode == STREAM_READ)
    m = O_RDONLY;
  else if(mode == STREAM_WRITE)
    m = O_RDWR|O_CREAT|O_TRUNC;

  if(p->filename)
    filename = p->filename;
  else if(p->filename2)
    filename = p->filename2;
  else
    filename = NULL;

  m |= O_BINARY;

  if(!strcmp(filename,"-")){

  } else {
      mode_t openmode = S_IRUSR|S_IWUSR;

      f=open(filename,m, openmode);
    if(f<0) {
      m_struct_free(&stream_opts,opts);
      return STREAM_ERROR;
    }
  }

  len=lseek(f,0,SEEK_END); lseek(f,0,SEEK_SET);

  if(len == -1) {
    if(mode == STREAM_READ) stream->seek = seek_forward;
    stream->type = STREAMTYPE_STREAM; // Must be move to STREAMTYPE_FILE
    stream->flags |= MP_STREAM_SEEK_FW;
  } else if(len >= 0) {
    stream->seek = seek;
    stream->end_pos = len;
    stream->type = STREAMTYPE_FILE;
  }

  stream->fd = f;
  stream->fill_buffer = fill_buffer;
  stream->write_buffer = write_buffer;
  stream->control = control;

  m_struct_free(&stream_opts,opts);
  return STREAM_OK;
}

上面代码实现的功能还是很清晰的,通过系统调用f=open(filename,m, openmode);打开我们传入的文件,然后将文件的描述符和文件大小信息注册进stream对象。 另外就是注册seek()fill_buffer()write_buffer()control()这4个回调函数。一切顺利的话,函数就返回STREAM_OK。 追踪完parse_playlist_file()里的open_stream(),让我们回过头来继续下面的代码。 open_stream()中打开了文件,接下来的ret = parse_playtree(stream,1);就是解析打开的文件,并最终生成playtreeparse_playtree()函数实现还是比较复杂的,涉及到树的操作,暂时就不张开分析,以后会单独拉出来进行详细分析。 至此,我们差不多弄清了MPlayer中main函数中m_config_parse_mp_command_line()的实现细节。下面回到MPlayer中main中去。 详细地见下一篇