libmvcp 客户端库
Copyright (C) 2004-2014 Meltytech, LLC
Last Revision: 2009-05-15
目录
-----------------
0. 概述
0.1. 目标读者
0.2. 术语
1. 解析器的定义
1.1. 构造本地解析器
1.2. 构造远程解析器
1.3. 使用解析器
1.4. 关闭解析器
2. 高层解析器封装器
2.1. 连接
2.2. mvcp_error_code
2.3. 使用高层封装器
2.4. 获取目录内容
2.5. 获取节点列表
2.6. 获取单元列表
2.7. 单元状态信息
2.8. 服务器端排队 API
2.9. 直接访问底层解析器
2.10. 清理
2.11. 示例
3. 底层解析器 API
3.1. 执行命令
3.2. 解读 mvcp_response
3.3. 访问单元状态
附录 A - 编译与链接
附录 B - 完整的高层解析器封装器 API 列表
附录 C - 完整的底层解析器 API 列表
附录 D - 参考资料
0. 概述
-----------
本文档详细说明应用程序如何与 MVCP 功能进行对接交互。
0.1. 目标读者
----------------------
本文档大量借鉴了 MVCP 设计文档 (1),并假定读者对 MVCP 核心所提供的
功能具备基本的了解。
本文面向希望使用或维护该 API 的开发者。
0.2. 术语
----------------
该 API 的设计旨在让客户端应用程序能够与独立的 melted 服务器通信,
或将 MVCP 核心完全嵌入到某个客户端应用程序实例之中。
两者的区别在于 '解析器(parser)' 的构造方式。
该 '解析器' 可用于发出 MVCP 命令并接收响应;同时提供了一个
'高层解析器封装器(high level parser wrapper)',以简化使用方式,
并将应用程序与 MVCP 命令集解耦。
1. 解析器的定义
-------------------------
解析器提供了一个底层 API,允许执行文本形式的 MVCP 命令,并将响应
返回给调用方。命令和响应均为 ASCII 格式的文本。
提供两种解析器——本地(local)和远程(remote)。
本地解析器是实际负责接收并执行命令的物理实现。
远程解析器是一种网络抽象,它将命令转发给一个承载了本地解析器的
melted 实例。
1.1. 构造本地解析器
-----------------------------------
要构造一个本地解析器,你必须包含:
#include <melted/melted_local.h>
初始化解析器的代码如下所示:
mvcp_parser parser = melted_parser_init_local( );
编译与链接的详细信息参见附录 A。
1.2. 构造远程解析器
------------------------------------
要构造一个远程解析器,你必须包含:
#include <mvcp/mvcp_remote.h>
初始化解析器的代码如下所示:
mvcp_parser parser = mvcp_parser_init_remote( "server", port );
编译与链接的详细信息参见附录 A。
1.3. 使用解析器
---------------------
尽管可以直接使用解析器来发送命令和接收响应,但这种底层用法把以
有意义的方式解析响应的责任压在了开发者身上。
虽然应用程序并非严格禁止这种用法,但并不提倡,因为构造命令以及
有意义地解析响应,会使客户端不必要地依赖于服务器的输入和输出。
因此,提供了一个更高层的解析器封装器 API——该 API 将命令构造与
响应解析封装了起来。
下面两节将详细介绍这些访问方式。
1.4. 关闭解析器
-----------------------
无论如何使用,创建者都有责任在解析器离开作用域之前将其关闭。
关闭方式为:
mvcp_parser_close( parser );
2. 高层解析器封装器
--------------------------------
访问解析器的推荐方式是通过 mvcp API。要使用该 API,你必须包含:
#include <mvcp/mvcp.h>
构造封装器的代码是:
mvcp client = mvcp_init( parser );
请注意,此处既可以使用远程解析器,也可以使用本地解析器,用法上
没有区别,尽管某些错误返回并不适用于两者。
建议应用程序尊重并处理两者的错误返回,这样应用程序就可以互换
解析器。
另请注意,mvcp 不是线程安全的,因此你不应在多个线程中使用同一个
结构。正确的解决办法是为每个线程创建一个 mvcp——你可以为每个线程
安全地使用同一个解析器,例如:
/* mvcp for the application */
mvcp client = mvcp_init( parser );
/* mvcp for the status handling thread. */
mvcp client_status = mvcp_init( parser );
为简便起见,本节余下部分假定使用的是远程解析器。
2.1. 连接
---------------
构造完成后,下一步就是 '连接(connect)':
mvcp_error_code error = mvcp_connect( client );
该函数调用会初始化解析器(即:如果是远程解析器,它会建立到服务器的
连接;如果是本地解析器,它会初始化各单元及相关支持对象的状态)。
请注意,如果你在同一解析器上有多个 mvcp 实例,则只需连接其中一个
实例。
2.2. mvcp_error_code
----------------------
构成 mvcp API 的函数中,除了少数几个之外,其余都会返回一个
mvcp_error_code。
其定义如下:
mvcp_ok = 0,
mvcp_malloc_failed,
mvcp_unknown_error,
mvcp_no_response,
mvcp_invalid_command,
mvcp_server_timeout,
mvcp_missing_argument,
mvcp_server_unavailable,
mvcp_unit_creation_failed,
mvcp_unit_unavailable,
mvcp_invalid_file,
mvcp_invalid_position
在大多数情况下,只需检查返回值是否为 mvcp_ok 即可。
要获取某个特定错误的文字描述,你可以使用:
char *mvcp_error_description( mvcp_error_code );
2.3. 使用高层封装器
---------------------------------
下面的代码片段假定 client 是一个已初始化且已连接的 mvcp 结构:
mvcp_error_code error = mvcp_unit_play( client, 0 );
if ( error == mvcp_ok )
fprintf( stderr, "Unit 0 is now playing\n" );
else
fprintf( stderr, "Play on unit 0 failed: %s\n",
mvcp_error_description( error ) );
mvcp 的完整接口列于本文档的附录 B 中。
2.4. 获取目录内容
--------------------------------
要获取相对于服务器 ROOT 属性的某个给定目录中的文件和子目录列表,
MVCP 提供了 CLS 命令。
CLS 的一次有效执行类似于:
CLS "/Stuff"
会提供如下格式的响应:
201 OK
"More Stuff/"
"file0001.dv" 15552000
"file0002.dv" 15552000
末尾带一个空行。
第一行指示错误值,第二行给出了一个子目录的示例,第三行和第四行
列出了恰好存在于该目录中的两个文件。
mvcp 提供了一种针对此的高层视图,它通过 mvcp_dir 结构及相关函数,
自动且正确地解析来自服务器的响应。
使用示例如下:
mvcp_dir dir = mvcp_dir_init( client, "/Stuff" );
mvcp_error_code error = mvcp_dir_get_error_code( dir );
if ( error == mvcp_ok )
{
if ( mvcp_dir_count( dir ) > 0 )
{
mvcp_dir_entry_t entry;
int index = 0;
for ( index = 0; index < mvcp_dir_count( dir ); index ++ )
{
mvcp_dir_get( dir, index, &entry );
if ( entry.dir )
printf( "<%s>\n", entry.name );
else
printf( "%30s %8d", entry.name, entry.size );
}
}
else
{
fprintf( stderr, "Directory is empty\n" );
}
}
else
{
fprintf( stderr, "Directory listing failed: %s\n",
mvcp_error_description( error ) );
}
mvcp_dir_close( dir );
请注意,entry.name 提供不带目录前缀的文件或目录名称。作为一种便利,
entry.full 提供了带前缀的名称,因此你随后可以使用:
error = mvcp_unit_load( client, 0, entry.full );
将一个条目加载到单元 0。
2.5. 获取节点列表
----------------------------
目前 melted 尚未定义该功能。
2.6. 获取单元列表
----------------------------
要获取已定义单元的列表,MVCP 提供了 ULS 命令。
ULS 的一次有效执行是:
ULS
会提供如下格式的响应:
201 OK
U0 00 sdl2:360x288 1
末尾带一个空行。
响应中每条记录的字段依次表示单元、节点、mlt 消费器(consumer)以及
在线状态。
mvcp 提供了一种针对此的高层视图,它通过 mvcp_units 结构及相关函数,
自动且正确地解析来自服务器的响应。
使用示例如下:
mvcp_units units = mvcp_units_init( client );
mvcp_error_code error = mvcp_units_get_error_code( units );
if ( error == mvcp_ok )
{
if ( mvcp_units_count( units ) > 0 )
{
mvcp_unit_entry_t entry;
int index = 0;
for ( index = 0; index < mvcp_units_count( units ); index ++ )
{
mvcp_units_get( units, index, &entry );
printf( "U%d %02d %s %s\n",
entry.unit,
entry.node,
entry.guid,
entry.online ? "online" : "offline" );
}
}
else
{
fprintf( stderr, "Unit list is empty\n" );
}
}
else
{
fprintf( stderr, "Unit listing failed: %s\n",
mvcp_error_description( error ) );
}
mvcp_units_close( units );
2.7. 单元状态信息
----------------------------
客户端有两种方法可获取单元状态信息。
第一种是通过 MVCP 的 USTA 命令,通常经由以下方式访问:
USTA U0
会提供如下格式的响应:
202 OK
0 playing "a.dv" 58 1000 25.00 0 6999 7000 "a.dv" 157 0 6999 7000 1 4 0
末尾不带空行。
记录中的各项依次为:
* 单元(Unit)
* 状态(undefined 未定义、offline 离线、not_loaded 未加载、stopped 停止、
playing 播放、paused 暂停、disconnected 断开 [服务器宕机时])
* 片段(Clip)名称
* 片段内位置
* 速度 × 1000
* 每秒帧数
* 片段起点(入点)
* 片段终点(出点)
* 片段长度
* 预读片段(Read ahead clip)
* 预读位置
* 预读片段入点
* 预读片段出点
* 预读片段长度
* 可定位标志(Seekable flag)
* 播放列表代数(Playlist generation)
* 片段索引
同样,mvcp 提供了一种通过 mvcp_unit_status 函数和 mvcp_status 结构
获取此信息的高层手段:
mvcp_status_t status;
mvcp_error_code error = mvcp_unit_status( client, 0, &status );
if ( error == mvcp_ok )
{
switch( status.status )
{
case unit_offline:
printf( "offline " );
break;
case unit_undefined:
printf( "undefined " );
break;
case unit_not_loaded:
printf( "unloaded " );
break;
case unit_stopped:
printf( "stopped " );
break;
case unit_playing:
printf( "playing " );
break;
default:
printf( "unknown " );
break;
}
printf( "%06lld %06lld %06lld %s\n", status.in,
status.position,
status.out,
status.clip );
}
else
{
fprintf( stderr, "Unit status failed: %s\n",
mvcp_error_description( error ) );
}
获取单元状态的第二种方式是通过自动通知。
这通过 mvcp_notifier API 完成。要从高层 API 获取通知器(notifier),
你可以使用:
mvcp_notifier notifier = mvcp_get_notifier( client );
要获取与某个单元相关联的最近一次状态,你可以使用:
int unit = 1;
mvcp_status_t status;
mvcp_notifier_get( notifier, &status, unit );
要等待来自任意单元的下一个状态,你可以使用:
mvcp_notifier_wait( notifier, &status );
如果你希望触发与应用程序针对某个特定单元的等待处理相关联的动作,
你可以使用:
mvcp_notifier_get( notifier, &status, unit );
mvcp_notifier_put( notifier, &status );
有关此内容的详细信息参见下方的示例。
状态结构中字段的完整列表如下:
int unit;
unit_status status;
char clip[ 2048 ];
int64_t position;
int speed;
double fps;
int64_t in;
int64_t out;
int64_t length;
char tail_clip[ 2048 ];
int64_t tail_position;
int64_t tail_in;
int64_t tail_out;
int64_t tail_length;
int seekable;
int generation;
int clip_index;
对于每一帧输出,你都会收到一条状态记录。
预读信息用于客户端排队。客户端排队假定已对该单元应用了 uset eof=pause。
客户端可以利用预读信息,检测先前排定的片段何时播放完毕,并排定下一个
片段。虽然仍支持这种工作方式,但建议新的客户端使用下一节所述的服务器端
排队机制。
2.8. 服务器端排队 API
------------------------------
本节介绍用于实现服务器端排队的可用 API。
其概念是每个单元维护自己的播放列表,其中包含多个片段。与播放列表相关联
的是一个代数(generation)编号,每次修改播放列表时该编号都会递增。状态
记录中提供了当前的播放列表代数,以便客户端知道何时刷新其对列表的展示。
状态记录还会指出当前处于活动状态的是哪个片段。
可对播放列表执行的动作概括如下:
* list(列出)—— 列出所有片段及其关联的入点/出点和大小
* loading a clip(加载片段)—— 加载操作会清空当前列表,并用指定片段替换之
* appending a clip(追加片段)—— 追加操作会始终将指定片段放到播放列表的
末尾
* inserting a clip(插入片段)—— 插入操作会将新片段放到播放列表中的
指定位置
* moving a clip(移动片段)—— 移动操作允许在播放列表中移动片段
* removing a clip(移除片段)—— 移除操作会将指定片段从播放列表中删除
* clean(清理)—— 清理操作会将播放列表中除正在播放片段之外的所有片段删除
此外,以下已有动作能够感知片段:
* goto 允许你将当前播放位置移动到某个特定的片段位置
* 设置入点/出点 允许你修改片段的入点和出点
通过新增一组能够感知片段的 API(其命名约定为 mvcp_unit_clip_*)来保持
向后兼容性。
这些 API 列于附录 B 中。
下面展示了获取单元 0 上排队片段的示例:
mvcp_list list = mvcp_list_init( client, 0 );
mvcp_list_entry_t entry;
int index;
printf( "Generation = %d\n", list->generation );
for ( index = 0; index < mvcp_list_count( list ); index ++ )
{
mvcp_list_get( list, index, &entry );
printf( "%d %s %d %d %d %d\n",
entry.clip,
entry.full,
entry.in,
entry.out,
entry.max,
entry.size );
}
mvcp_list_close( list );
在单元 0 上加载一个片段:
mvcp_unit_load( client, 0, "/path/clip.dv" );
在单元 0 上追加一个片段:
mvcp_unit_append( client, 0, "/path/clip.dv", -1, -1 );
请注意,最后两个参数指定片段的入点和出点,其中 -1 表示默认取整个文件。
要在单元 0 的位置 0 插入一个片段,我们可以使用以下内容:
mvcp_unit_clip_insert( client, 0, clip_absolute, 0, "/path/clip.dv", -1, -1 );
这里的第 3 个和第 4 个参数对所有 mvcp_unit_clip 函数都是通用的。它们的
形式为 [clip_absolute, n] 表示一个绝对片段索引,或 [clip_relative, n]
表示一个相对于当前正在播放片段的片段索引。
因此,要在当前正在播放的片段之前紧邻插入一个片段,我们可以使用:
mvcp_unit_clip_insert( client, 0, clip_relative, -1, "/path/clip.dv", -1, -1 );
要将当前片段移动到列表中的下一个位置:
mvcp_unit_clip_move( client, 0, clip_relative, 0, clip_relative, 1 );
要移除某个特定片段:
mvcp_unit_clip_remove( client, 0, clip_absolute, index );
要除当前正在播放的片段外全部移除:
mvcp_unit_clean( client, 0 );
要跳转到第一个片段的第一帧,你可以使用:
mvcp_unit_clip_goto( client, 0, clip_absolute, 0, 0 );
要设置当前片段的入点和出点:
mvcp_unit_clip_set_in( client, 0, clip_relative, 0, 0 );
mvcp_unit_clip_set_out( client, 0, clip_relative, 0, 1000 );
服务器端排队更完整的用法示例可参见:
https://github.com/mltframework/rugen
mvcp 自带的演示客户端用于保持与客户端排队 API 的向后兼容性。
2.9. 直接访问底层解析器
--------------------------------------------
可以从高层 API 直接访问底层解析器及其关联结构,但实际中极少需要这么做。
这些方法通过一对高层方法提供:
mvcp_error_code error = mvcp_execute( client, 1024, "USTA U%d", unit );
mvcp_response response = mvcp_get_last_response( client );
int index = 0;
for ( index = 0; index < mvcp_response_count( response ); index ++ )
printf( "%d: %s\n", index, mvcp_response_get_line( response,index ) );
有关 mvcp_response 结构的更多细节可参见本文档第 3 节。
2.10. 清理
-----------------
在 mvcp 和解析器离开作用域之前,你需要运行:
mvcp_close( client );
mvcp_parser_close( parser );
请注意,你应当先关闭所有 mvcp 实例,再关闭解析器。
2.11. 示例
--------------
有关随项目提供的示例,请参考 mvcp-console 和 mvcp-client 源码。
3. 底层解析器 API
---------------------------
底层解析器 API 提供了一种非常简单的机制,用于构造命令和接收响应。
如第 2 节所述,解析器构造为本地或远程,这对于构造底层解析器而言已经
足够。
3.1. 执行命令
------------------------
所有命令都可以通过单个可变参数函数 mvcp_parser_executef 来执行,并且
该函数返回一个 mvcp_response,即:
mvcp_response response = mvcp_parser_executef( parser, "CLS \"%s\"", dir );
请注意,不需要回车/换行符(添加它们反而是错误的)。
由响应的接收方负责将其关闭。
mvcp_response_close( response );
3.2. 解读 mvcp_response
-----------------------------
所接收到的响应可能为 NULL,但以下调用是安全的:
int error = mvcp_response_get_error_code( response );
它将返回:
* 如果 response 为 NULL,返回 -1,
* 如果响应没有内容,返回 -2,
* 如果响应的第一行不对应有效的 MVCP 响应,返回 0
* 或者返回响应第一行上给出的 MVCP 协议错误码
mvcp_response 结构的一个简单用法如下:
mvcp_response response = mvcp_parser_executef( parser, "CLS \"%s\"", dir );
int error = mvcp_response_get_error_code( response );
if ( error >= 0 )
{
int index = 0;
for ( index = 0; index < mvcp_response_count( response ); index ++ )
printf( "%3d: %s\n", index, mvcp_response_get_line( response, index ) );
}
else
{
/* interpret error */
}
mvcp_response_close( response );
请注意,无论指示了何种错误条件,调用 mvcp_response_close 都是安全的。
3.3. 访问单元状态
--------------------------
与高层解析器一样,获取单元状态信息有两种可选方式——可通过 USTA MVCP
命令,也可通过 mvcp_notifier。
对于希望从状态中提取有意义信息、同时避免在特定客户端中重复解析过程的
任何应用程序而言,后者是推荐方式。
可通过以下方式获取通知器:
mvcp_notifier notifier = mvcp_parser_get_notifier( parser );
通知器与底层解析器的用法与第 2 节所述完全一致——要获取与某个单元相关联
的最近一次状态,你可以使用:
int unit = 1;
mvcp_status_t status;
mvcp_notifier_get( notifier, &status, unit );
要等待来自任意单元的下一个状态,你可以使用:
mvcp_notifier_wait( notifier, &status );
附录 A - 编译与链接
------------------------------------
编译标志为:
-I /include
其中 prefix 默认为 /usr/local。
客户端的链接标志为:
-L /lib/ -lmvcp
或者对于本地解析器:
-L /lib/ -lmelted
请注意,你绝不需要同时使用这两个库。
附录 B - 完整的高层解析器封装器 API 列表
-----------------------------------------------------------
mvcp mvcp_init( mvcp_parser );
mvcp_error_code mvcp_connect( mvcp );
mvcp_error_code mvcp_set( mvcp, char *, char * );
mvcp_error_code mvcp_get( mvcp, char *, char *, int );
mvcp_error_code mvcp_unit_add( mvcp, char * );
mvcp_error_code mvcp_unit_load( mvcp, int, char * );
mvcp_error_code mvcp_unit_load_clipped( mvcp,int,char *,long,long );
mvcp_error_code mvcp_unit_load_back( mvcp, int, char * );
mvcp_error_code mvcp_unit_load_back_clipped(mvcp,int,char *,long,long)
mvcp_error_code mvcp_unit_play( mvcp, int );
mvcp_error_code mvcp_unit_play_at_speed( mvcp, int, int );
mvcp_error_code mvcp_unit_stop( mvcp, int );
mvcp_error_code mvcp_unit_pause( mvcp, int );
mvcp_error_code mvcp_unit_rewind( mvcp, int );
mvcp_error_code mvcp_unit_fast_forward( mvcp, int );
mvcp_error_code mvcp_unit_step( mvcp, int, int );
mvcp_error_code mvcp_unit_goto( mvcp, int, int );
mvcp_error_code mvcp_unit_set_in( mvcp, int, int );
mvcp_error_code mvcp_unit_set_out( mvcp, int, int );
mvcp_error_code mvcp_unit_clear_in( mvcp, int );
mvcp_error_code mvcp_unit_clear_out( mvcp, int );
mvcp_error_code mvcp_unit_clear_in_out( mvcp, int );
mvcp_error_code mvcp_unit_set( mvcp, int, char *, char * );
mvcp_error_code mvcp_unit_get( mvcp, int, char * );
mvcp_error_code mvcp_unit_status( mvcp, int, mvcp_status );
mvcp_notifier mvcp_get_notifier( mvcp );
mvcp_dir mvcp_dir_init( mvcp, char * );
mvcp_error_code mvcp_dir_get( mvcp_dir, int, mvcp_dir_entry );
int mvcp_dir_count( mvcp_dir );
void mvcp_dir_close( mvcp_dir );
mvcp_nodes mvcp_nodes_init( mvcp );
mvcp_error_code mvcp_nodes_get(mvcp_nodes,int,mvcp_node_entry);
int mvcp_nodes_count( mvcp_nodes );
void mvcp_nodes_close( mvcp_nodes );
mvcp_units mvcp_units_init( mvcp );
mvcp_error_code mvcp_units_get(mvcp_units,int,mvcp_unit_entry);
int mvcp_units_count( mvcp_units );
void mvcp_units_close( mvcp_units );
mvcp_response mvcp_get_last_response( mvcp );
mvcp_error_code mvcp_execute( mvcp, size_t, char *, ... );
void mvcp_close( mvcp );
通知器函数
------------------
void mvcp_notifier_get( mvcp_notifier, mvcp_status, int );
void mvcp_notifier_put( mvcp_notifier, mvcp_status );
int mvcp_notifier_wait( mvcp_notifier, mvcp_status );
void mvcp_notifier_close( mvcp_notifier );
服务器端排队
-------------------
mvcp_list mvcp_list_init( mvcp, int )
mvcp_error_code mvcp_list_get_error_code( mvcp_list )
mvcp_error_code mvcp_list_get( mvcp_list, int, mvcp_list_entry )
int mvcp_list_count( mvcp_list )
void mvcp_list_close( mvcp_list )
mvcp_error_code mvcp_unit_clean( mvcp client, int unit )
mvcp_error_code mvcp_unit_append( mvcp client, int unit, char *file, int in, int out )
mvcp_error_code mvcp_unit_remove_current_clip( mvcp client, int unit )
mvcp_error_code mvcp_unit_clip_goto( mvcp client, int unit, mvcp_clip_offset offset, int clip, int position )
mvcp_error_code mvcp_unit_clip_set_in( mvcp client, int unit, mvcp_clip_offset offset, int clip, int in )
mvcp_error_code mvcp_unit_clip_set_out( mvcp client, int unit, mvcp_clip_offset offset, int clip, int in )
mvcp_error_code mvcp_unit_clip_move( mvcp client, int unit, mvcp_clip_offset offset, int src, mvcp_clip_offset offset, int dest )
mvcp_error_code mvcp_unit_clip_remove( mvcp client, int unit, mvcp_clip_offset offset, int clip )
mvcp_error_code mvcp_unit_clip_insert( mvcp client, int unit, mvcp_clip_offset offset, int clip, char *file, int in, int out )
附录 C - 完整的底层解析器 API 列表
--------------------------------------------------
mvcp_response mvcp_parser_connect( mvcp_parser );
mvcp_response mvcp_parser_execute( mvcp_parser, char * );
mvcp_response mvcp_parser_executef( mvcp_parser, char *, ... );
mvcp_response mvcp_parser_run( mvcp_parser, char * );
mvcp_notifier mvcp_parser_get_notifier( mvcp_parser );
void mvcp_parser_close( mvcp_parser );
mvcp_response mvcp_response_init( );
mvcp_response mvcp_response_clone( mvcp_response );
int mvcp_response_get_error_code( mvcp_response );
char *mvcp_response_get_error_string( mvcp_response );
char *mvcp_response_get_line( mvcp_response, int );
int mvcp_response_count( mvcp_response );
void mvcp_response_set_error( mvcp_response, int, char * );
int mvcp_response_printf( mvcp_response, size_t, char *, ... );
int mvcp_response_write( mvcp_response, char *, int );
void mvcp_response_close( mvcp_response );
附录 D - 参考资料
-----------------------
(1) doc/mvcp.txt - MVCP 协议
(2) doc/testing.txt - 测试流程
</pre>