前言

在 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:播放多个文件

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 就好了。