MLT是为电视广播设计的开源多媒体框架。严格来说,它为使项目包含新的音视频源、 滤镜、场景过渡和播放设备提供了可插拔式的架构。
本框架为使用了MLT的服务或应用程序提供了结构体系与实用功能。
就框架本身而言,它只提供了为管理资源,如内存,属性,动态对象加载和实例化服务的抽象类和实用功能程序。
本文档大致分为三部分。第一部分提供对MLT的基础描述,第二部分展示了它如何被使用,最后一部分则结合了扩展系统的强调提示展示了框架的结构与设计。
本文档作为一份框架的”产品路线图“被提供,并且对那些想在框架层次进行开发的人来说,请务必考虑通读本文。
这包括:
1. 框架维护人员
2. 模块开发者
3. 应用开发者
4. 任何对MLT感兴趣的人
本文档强调的部分在于公共接口,而非具体实现细节。
阅读 MLT 客户端/服务端集成是没有必要的,请参考libmvsp.txt和mvsp.txt寻找关于此区域的更多信息。
MLT使用C语言编写,框架除了C99标准与pthread库外没有别的依赖。它遵循基本的面向对象设计范式,许多设计宽松地基于生产者/消费者设计模式。
它为音频与视频特效应用了逆波兰表达。
框架被设计为不影响色彩空间——然而当前实现的模块非常趋向于8bit YUV422格式,但是理论上,这些模块可以被完全替换掉。
一些关于这些术语的粗略解读将贯穿于这篇文档的剩余部分。
一个MLT ‘网络’ 的总体结构可描述为一个‘生产者’与一个‘消费者’之间的连接:
一个典型的消费者从生产者处请求MLT帧对象,随后对其进行一些操作,并在完成一帧后将其关闭。
一个常见的对此处使用到的”生产者/消费者“术语的混肴是,消费者也许会’生产‘某些东西。举个例子,libdv消费者生产DV并且libdv生产者似乎会去消耗DV。然而,此处的命名约定仅表示MLT 帧对象的生产者与消费者。
换言之——一个生产者生产MLT帧对象,并且一个消费者将消耗MLT帧对象。
一个MLT帧本质上提供一张未压缩的,且与音频样本相关联的图片。
滤镜也可以被放置于生产者与消费者之间:
一个’服务’是一组生产者、滤镜、消费者的集合名称。
连接起来的生产者与消费者或服务之间的交流将执行三个阶段:
MLT采用‘惰性评估’——图像与音频不需要从源中解压,直到获取图片与音频的方法被引用。
实质上,消费者从其所连接的模块中获取资源——这意味着线程通常属于消费者实现的领域,并且在消费者类上提供了一些基本方法以确保实时吞吐量。
在我们进入框架架构细节之前,下面提供了一个有效的使用例。
以下部分简单的提供了一个媒体播放器:
#include
#include
#include
int main( int argc, char *argv[] )
{
// Initialise the factory
if ( mlt_factory_init( NULL ) == 0 )
{
// Create the default consumer
mlt_consumer hello = mlt_factory_consumer( NULL, NULL );
// Create via the default producer
mlt_producer world = mlt_factory_producer( NULL, argv[ 1 ] );
// Connect the producer to the consumer
mlt_consumer_connect( hello, mlt_producer_service( world ) );
// Start the consumer
mlt_consumer_start( hello );
// Wait for the consumer to terminate
while( !mlt_consumer_is_stopped( hello ) )
sleep( 1 );
// Close the consumer
mlt_consumer_close( hello );
// Close the producer
mlt_producer_close( world );
// Close the factory
mlt_factory_close( );
}
else
{
// Report an error during initialisation
fprintf( stderr, "Unable to locate factory modules\n" );
}
// End of program
return 0;
}
这是一个简单的例子——它不提供任何查找功能或运行时配置设定。
任何MLT应用程序的第一步都是工厂的初始化——这保证了环境配置与MLT能够正常运行。下面是对工厂的细节介绍。
如上例的mlt_factory_consumer与mlt_factory_producer调用,所有的服务都通过工厂实例化。对于滤镜和过渡效果也有类似的工厂。在services.txt中包括了标准服务的详细信息。
此处要求的默认值是一个特殊情况——NULL使用请求代表使用默认的生产者与消费者。
默认生产者是“加载器(loader)”。此生产者通过匹配文件名来定位要使用的服务,并且附加‘标准化滤镜’(如缩放、去交错、重采样和字段标准化器) 到加载的内容当中——这些滤镜保证了消费者得到它所寻求的结果。(Frame?)
默认的消费者是“sdl”。加载器与sdl的组合将提供一个媒体播放器。
在这个例子当中,我们连接生产者并随后启动消费者。我们接下来等待直到消费者运行停止 (此例子中指关闭SDL_Window这一动作) 并在应用程序退出前最终关闭消费者、生产者与工厂。
注意,消费者是线程化(异步?)的——在启动消费者之后以及停止或关闭消费者之前,总是需要等待一些种类的事件。
另外也请注意。你可以重载默认值如下:
MLT_CONSUMER=xml ./hello file.avi
这将在标准输出上创建一个xml文档
MLT_CONSUMER=xml MLT_PRODUCER=avformat ./hello file.avi
这将会直接使用avformat生产者播放视频,因此他将避开标准化方法。
MLT_CONSUMER=libdv ./hello file.avi > /dev/dv1394
如果您足够幸运,可以随手将file.avi
实时转换为DV格式,并将其广播到您的DV设备中。
正如’Hello World’例子中所展示的那样,工厂可以创建服务对象。
框架本身不提供服务——服务以插件的形式被提供。插件以"模块"的形式组织,并且一个模块可以提供许多不同种类的服务。
一旦工厂被初始化,所有配置好的服务就都可以被使用。
mlt_factory_prefix()
返回安装各模块的目录路径,这可以被明确表示在mlt_factory_init
调用其本身种,或它可以通过环境变量MLT_REPOSITORY
明确表示,亦或者在这两者都缺乏配置的情况下,它将默认返回安装了prefix/shared/mlt/modules
的路径。
mlt_environment()
提供与如下表格中所示的名称=值
集合的只读连接:
名称 | 描述 | 值 |
---|---|---|
MLT_NORMALISATION | 系统的标准化 | PAL或NTSC |
MLT_PRODUCER | 默认生产者 | “loader”或其他 |
MLT_CONSUMER | 默认消费者 | "sdl"或其他 |
MLT_TEST_CARD | 默认检测卡生产者 | 任何生产者 |
这些值将从同名的环境变量中初始化。
如上方所展示的,一个生产者可以用"默认标准化"生产者来创建,并且他们也可以被使用名字来请求。滤镜和过渡总是被用名字来请求——此处没有它们的’默认’概念。
所有的服务都拥有它们能够用来操纵影响它们行为的属性集合。
为了在服务上设置属性,我们需要检索与他相联系的属性。对生产者来说,这是被调用以下方法完成的:
mlt_properties properties = mlt_producer_properties(producer);
所有的服务都有一个相似的关系方法。
一旦完成了数据检索,设置与获取属性就能够直接在这个对象上操作完成,举个例子:
mlt_properties_set(properties, "name", "value");
更多关于属性对象的完备的描述可以在下方找到。
到目前为止,我们已经展示了一个简单的生产者/消费者配置——下一阶段是要将生产者组织到播放列表当中。
假设我们正在改写"Hello World"样例,并且希望添加一系列文件到播放队列,即:
hello*.avi
我们将创建一个新的名为create_playlist
的函数而不是直接调用mlt_factory_producer
。此函数负责创建播放列表,创建每一个生产者并将它们添加到播放列表当中。
mlt_producer create_playlist( int argc, char **argv )
{
// We're creating a playlist here
mlt_playlist playlist = mlt_playlist_init( );
// We need the playlist properties to ensure clean up
mlt_properties properties = mlt_playlist_properties( playlist );
// Loop through each of the arguments
int i = 0;
for ( i = 1; i < argc; i ++ )
{
// Create the producer
mlt_producer producer = mlt_factory_producer( NULL, argv[ i ] );
// Add it to the playlist
mlt_playlist_append( playlist, producer );
// Close the producer (see below)
mlt_producer_close( producer );
}
// Return the playlist as a producer
return mlt_playlist_producer( playlist );
}
注意到我们在添加生产者到播放列表后就关闭了它们,实际上,我们所做的是关闭我们对此生产者的引用——播放列表创建了属于播放列表自身的对生产者的引用并将其插入,并且当播放列表被销毁时,它会关闭自身对生产者的引用。
还要注意,如果添加同一生产者的多个实例到播放列表,它将创建对其的多个引用。
现在我们所要做的是替换主函数中的这一行:
// Create a normalised producer
mlt_producer world = mlt_factory_prodi
为
// Create a normalised producer
mlt_producer world = create_playlist(argc, argv);
然后我们便有方法去播放复数片段。
[*] 此处的引用设计在MLT 0.1.2中有介绍——它100%适用于早期的注册引用与销毁播放列表对象的属性。
在生产者和消费者之间插入滤镜只是对其的一种实例化,第一步滤镜连接到生产者,再将滤镜连接到消费者。
举个例子:
// Create a producer from something
mlt_producer producer = mlt_factory_producer( ... );
// Create a consumer from something
mlt_consumer consumer = mlt_factory_consumer( ... );
// Create a greyscale filter
mlt_filter filter = mlt_factory_filter( "greyscale", NULL );
// Connect the filter to the producer
mlt_filter_connect( filter, mlt_producer_service( producer ), 0 );
// Connect the consumer to filter
mlt_consumer_connect( consumer, mlt_filter_service( filter ) );
与生产者和消费者一样,滤镜也能被通过修改它的属性对象操作——mlt_filter_properties
方法能够被调用并且属性可以按要求进行设定。
滤镜连接函数中的附加参数很重要,因为它规定了滤镜运作的’轨迹’。对基础的生产者与播放列表,它们只有一个轨迹(0),正如您将在下一节看到的,即使有多个轨迹也只有单一的能够产生输出。
所有的服务都可以拥有附加滤镜。
考虑下面的例子:
// Create a producer
mlt_producer producer = mlt_factory_producer( NULL, clip );
// Get the service object of the producer
mlt_producer service = mlt_producer_service( producer );
// Create a filter
mlt_filter filter = mlt_factory_filter( "greyscale" );
// Create a playlist
mlt_playlist playlist = mlt_playlist_init( );
// Attach the filter to the producer
mlt_service_attach( producer, filter );
// Construct a playlist with various cuts from the producer
mlt_playlist_append_io( producer, 0, 99 );
mlt_playlist_append_io( producer, 450, 499 );
mlt_playlist_append_io( producer, 200, 399 );
// We can close the producer and filter now
mlt_producer_close( producer );
mlt_filter_close( filter );
当播放结束时,灰度缩放滤镜将会对播放列表中来自特点生产者的每一帧执行处理。
此外,每个剪辑都可以拥有它们自身的附加滤镜,这些滤镜将在生产者的滤镜后执行。举个例子:
// Create a new filter
filter = mlt_factory_filter( "invert", NULL );
// Get the second 'clip' in the playlist
producer = mlt_playlist_get_clip( 1 );
// Get the service object of the clip
service = mlt_producer_service( producer );
// Attach the filter
mlt_service_attach( producer, filter );
// Close the filter
mlt_filter_close( filter );
甚至播放列表本身也可以附加滤镜
// Create a new filter
filter = mlt_factory_filter( "watermark", "+Hello.txt" );
// Get the service object of the playlist
service = mlt_playlist_service( playlist );
// Attach the filter
mlt_service_attach( service, filter );
// Close the filter
mlt_filter_close( filter );
当然,播放列表作为生产者,可以被切分并放置在另一个播放列表上,并且滤镜可以被添加到这些切分或新的播放列表本身上。
附加滤镜的主要优势是它们能够保持连接并且不会遭受插入项目 和计算替换出入点等维护问题的影响——如果您在多轨道领域内大量使用插入分离式的滤镜,这将会成为一个主要问题。