前言
在 MLT 视频编辑框架简介(二):框架设计简述 我们总结了 mlt 中各模块的使用方式,我们先回顾下上期的内容:
- Producer:Producer 是数据的来源,它负责从各种来源(如文件、网络流、生成器等)读取音视频数据。Producer 是一个基本的组件,它生成帧并将它们传递给其他组件进行处理。
- Filter:Filter 是对输入帧执行某种操作的组件。这些操作可以包括更改颜色、添加特效、调整音量等。Filter 可以连接到 Producer 或其他 Filter,从而形成一个处理链,以便按顺序应用多个效果。
- Transition:Transition 是用于在两个音视频片段之间创建平滑过渡的组件。例如,淡入淡出、滑动、覆盖等。Transition 可以连接到两个不同的 Producer 或 Filter,并生成一个混合了这两个输入的输出帧。
- Consumer:Consumer 是数据的终点,负责处理从 Producer 和 Filter 接收到的帧。Consumer 可以将帧显示在屏幕上、编码为输出文件或发送到其他设备。Consumer 连接到处理链的末端,并负责播放、导出或传输最终的音视频数据。
- Playlist:Playlist 是一个组件,它可以将多个 Producer 按顺序组合成一个连续的序列。Playlist 提供了一个简单的线性编辑方式,允许您轻松地按顺序播放多个音视频片段,还可以在片段之间插入空白(blank)或其他特殊片段。
- Tractor:Tractor 是一个高级组件,用于处理更复杂的音视频项目。它包含一个 Multitrack 和一个 Field,允许您管理多个轨道并在轨道之间应用过渡效果。Tractor 可以容纳多个 Producer、Filter 和 Transition,以创建复杂的处理流程。
- Multitrack:Multitrack 是 Tractor 的一个子组件,负责管理轨道(track)。每个轨道都可以包含一个或多个音视频片段(producer),这些片段可以是独立的文件,也可以是处理过的片段。
- Field:Field 是 Tractor 的另一个子组件,负责处理轨道之间的过渡效果。Field 可以管理多个过渡(transition),并根据需要在轨道之间应用它们
本期内容带你进行 MLT 实战,使用上述类完成编辑任务。在 mlt 源码仓库下有一个 demo 目录,里头有很多使用 mlt 功能示例,例如滤镜、视频转场、音频转场、文字、水印等等。demo 目录下脚本的脚本使用 melt.c 进行解析和执行,为此 mlt framework 单独搞了一个 Producer 来适配和解析脚本的参数,你可以在 producer_melt.c 中看到具体的逻辑。
MLT 其实也提供了 C++ 接口,在 mlt++ 目录下,是对 C 接口的一层封装。C++ 接口使用起来比 C 更灵活和方便。我们将使用 mlt c++ 接口来实现 demo 下的几个示例。通过这几个示例,你将了解 MLT 模块的使用方式。
所有代码已上传至 github,需要源码的同学自行查看:mlt_framework_examples,内容包括:
- mlt_all,播放多个视频
- mlt_audio_stuff,多轨混音
- mlt_avantika_title,显示文字
- mlt_bouncy,画中画 + 水印
- mlt_composite_transition,转场
- mlt_clock_in_and_out,更酷的转场
- mlt_compile,视频导出
- mlt_effect_in_middle,视频特效
接下来对几个示例进行代码说明,一通百通,其他示例大家自己看吧。
示例
mlt_all:播放多个文件
void play(const std::vector<std::string> &filenames) {
Profile profile; // defaults to dv_pal
profile.set_frame_rate(30, 1);
profile.set_width(640);
profile.set_height(360);
profile.set_progressive(1);
profile.set_sample_aspect(1, 1);
profile.set_display_aspect(16, 9);
profile.set_colorspace(709);
Producer producer(profile, NULL, input_file.c_str());
Filter filter(profile, "greyscale");
filter.set("in", 100);
filter.set("out", 199);
producer.attach(filter);
Consumer consumer(profile, "sdl2", nullptr);
consumer.set("real_time", 1);
consumer.set("terminate_on_pause", 1);
consumer.connect(producer);
consumer.start();
while (!consumer.is_stopped()) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
consumer.stop();
break;
}
}
}
consumer.stop();
}
- 设置 Profile 相关属性,例如视频大小,帧率等等。
- 创建一个 PlayList,根据输入文件逐一创建 Producer,并将 Producer 添加到 PlayList 中。注:当 Producer 构造函数中参数
id
为 nullptr 时,mlt 会使用 “loader” 这个 Producer,而 loader 可以根据输入文件的参数来判断具体使用哪个 Producer。例如我们输入的是一个视频文件,那么它最终会使用 “avformat”。规则参考 loader.dict。 - 创建一个 SDL2 Consumer 用于视频播放。并将 PlayList 与它相连接
-
consumer.start()
让 Consumer 开始工作 - 使用一个
while
循环来处理 SDL 事件,如果没有这个循环那么你无法看到视频窗口,只能听到声音。 - 当视频播放结束后,调用
consumer.stop()
释放相关资源。
mlt_compile:视频转码
void compile(const char *in_path, const char *out_path) {
Profile profile; // defaults to dv_pal
profile.set_frame_rate(30, 1);
profile.set_width(640);
profile.set_height(360);
profile.set_progressive(1);
profile.set_sample_aspect(1, 1);
profile.set_display_aspect(16, 9);
profile.set_colorspace(709);
Producer producer(profile, nullptr, in_path);
Consumer consumer(profile, "avformat", out_path);
consumer.connect(producer);
consumer.run();
consumer.stop();
}
- 设置 Profile 相关属性,例如视频大小,帧率等等。
- 创建 Producer 和 Consumer。注意,我们现在要导出一个文件,因此 Consumer 类型指定为 ”avformat“。
- 链接 Consumer 和 Producer,并调用
run
方法开始工作,当处理结束后会自动退出run
mlt_effect_in_middle: 滤镜使用
void play(const char *filename) {
Profile profile;
Producer producer(profile, NULL, input_file.c_str());
Filter filter(profile, "greyscale");
filter.set("in", 100);
filter.set("out", 199);
producer.attach(filter);
Consumer consumer(profile, "sdl2", nullptr);
consumer.set("real_time", 1);
consumer.set("terminate_on_pause", 1);
consumer.connect(producer);
consumer.start();
while (!consumer.is_stopped()) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
consumer.stop();
break;
}
}
}
consumer.stop();
}
- 创建 “greyscale” 滤镜,并通过
attach
方法绑定在 Producer 上。filter.set("in", 100); filter.set("out", 199);
设置滤镜的生效时间,其中 “in” 和 “out” 表示在时间轴上的帧数,也就是说该滤镜在第 100 帧时开始生效,一直到 199 帧。
mlt_audio_stuff:设置背景音乐
void play(const vector<string> &filenames, const string &bgm_filename,
float bgm_gain) {
Profile profile;
Playlist video_track;
for (auto &filename : filenames) {
Producer producer(profile, NULL, filename.c_str());
video_track.append(producer);
}
Producer bgm_track(profile, NULL, bgm_filename.c_str());
Filter volume(profile, "volume");
volume.set("level", bgm_gain);
bgm_track.attach(volume);
Tractor tractor(profile);
tractor.set_track(bgm_track, 0);
tractor.set_track(video_track, 1);
Transition audio_mixer(profile, "mix");
audio_mixer.set("out", 9999);
tractor.plant_transition(audio_mixer, 0, 1);
// Create a consumer and connect it to the playlist
Consumer consumer(profile, nullptr, nullptr);
// Start playing
consumer.set("real_time", 1);
consumer.set("terminate_on_pause", 1);
consumer.connect(tractor);
consumer.start();
while (!consumer.is_stopped()) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
consumer.stop();
break;
}
}
}
consumer.stop();
}
- 本示例中有两个 Track:Track 0 放置多个视频文件,使用 PlayList 进行组织;Track 1 放置一个背景音乐文件。
- 创建 “volume” 音频滤镜,并设置参数 “level” 即音量(单位是 db)。将 volume 滤镜 attach 在 Track 1 上以便控制 BGM 的音量大小。
- 接着,我们需要对两轨的音频做 mix。为此我们需要引入 Tractor,因为 mix 的任务涉及到多个轨道的概念,而 Tractor 负责多轨场景的操作。具体的,创建一个 Tractor,并设置 track 0 和 track 1 的 Producer:
tractor.set_track(bgm_track, 0);
tractor.set_track(video_track, 1);
- 创建 “mix” 转场,并通过
tractor.plant_transition(audio_mixer, 0, 1)
指定转场需要的两路信号分别来自 track 0 和 track 1。audio_mixer.set("out", 9999);
其中 9999 代指一个很大的值,意思是转场会一直生效。
总结
本文介绍了如何使用 MTL API 来完成编辑任务,包括播放、滤镜、转场、导出等等。并给出了不同任务的具体示例,详细参看 mlt_framework_examples。对于 MLT API 的使用,总结以下几点:
- 对于 filter 的使用,你可以用 connect 或者 attach 方法。有时候 attach 更加灵活。
- 对于多轨的操作,你需要使用 Tractor 对象。当 Tractor 有多个轨道时,默认使用 index 更大的那轨的数据。如果你想显示多个轨道的数据(画中画)那么需要添加转场。
- 转场需要多路信号,因此你需要使用 Tractor 才能添加转场。然后将转场看成是 filter 就好了。