Документация по инфраструктуре
Copyright (C) 2004 Ushodaya Enterprises Limited
Author: Charles Yates <charles.yates@pandora.be>
Last Revision: 2004-10-08
Translator: Ruslan Popov <radz@yandex.ru>
Last Revision: 2006-01-06
Инфраструктура MLT
Введение
Библиотека MLT является мультимедийной инфраструктурой разработанной для телевизионного вещания и, соответственно, она предоставляет собой расширяемую архитектуру для подключения новых A/V источников, фильтров, переходов и устройств воспроизведения.
Инфраструктура является основным каркасом на основе которого создаются приложения и сервисы использующие библиотеку MLT.
Сама инфраструктура предоставляет нечто большее, чем просто "абстрактные классы" и утилиты для управления ресурсами такими как память, свойства, динамическая подгрузка объектов и обслуживание самого экземпляра библиотеки.
Данный документ разделён на три секции. Первая секция предоставляет собой простой обзор библиотеки MLT, вторая секция показывает как можно использовать библиотеку и заключительная секция показывает дизайн и структуру библиотеки, ставя основной акцент на расширение возможностей.
Целевая Аудитория
Данный документ можно рассматривать как "дорожную карту" по инфраструктуре библиотеки, которая должна быть обязательно изучена тем, кто желает разрабатывать исходный код на уровне библиотеки MLT.
Перечислим таких людей:
- разработчики библиотеки;
- разработчики модулей;
- разработчики приложений;
- любой, кому интересна данная библиотека.
Цель данного документа объяснить использование интерфейсов, а не описывать детали реализации библиотеки.
Данный документ не обязателен для чтения в случае MLT клиент-сервер интеграции. Для этого надо обратиться к
valerie.txt и
dvcp.txt.
Секция 1 - Основы
Дизайн
Библиотека написана на языке C.
Библиотека соответствует стандарту C99 и зависит только от POSIX библиотек.
Библиотека следует основной парадигме объекто-ориентированного дизайна и, следовательно, в большинстве случаев использует шаблон "Производитель (Producer)/Потребитель (Consumer)".
Библиотека использует обратную польскую нотацию для приложений A/V эффектов.
Библиотека разработана так, чтобы быть нейтральной к используемому пространству цветов, но реализованные в настоящее время модули, тем не менее, в своём большинстве ориентированы на 8-ми битное YUV422 пространство цветов. В теории, эти модули могут быть в последствии заменены.
Неопределённость данного срока подразумевается по всему документу.
Структура и поток
Основной структурой "сети" библиотеки является простое соединение "Производителя" к "Потребителю":
+--------+ +--------+
|Producer|-->|Consumer|
+--------+ +--------+
Обычный потребитель запрашивает объекты
MLT Frame у производителя, производит с объектами необходимые действия и закрывает их.
Общий беспорядок с терминологией производителя/потребителя, используемой здесь, состоит в том, что потребитель может 'произвести' кое-что. Например,
libdv потребитель производит
DV поток и кажется как-будто
libdv производитель потребляет этот поток. Однако, соглашения по обозначениям относятся только к производителям и потребителям
MLT Frame.
Другими словами, производитель производит объекты
MLT Frame, а потребитель их потребляет.
По существу,
MLT Frame предоставляет несжатое изображение и связанные с ним звуковой поток.
Между производителем и потребителем могут помещаться фильтры:
+--------+ +------+ +--------+
|Producer|-->|Filter|-->|Consumer|
+--------+ +------+ +--------+
Сервис это коллективное имя для производителей, фильтров, преобразований и потребителей.
Взаимодействие между подсоединёнными потребителем и производителем или сервисов выполняются в три шага:
- получение информации о кадре;
- получение изображения;
- получение звука.
Библиотека использует "ленивую распаковку", т.е. изображение и и связанный с ним звуковой поток не нуждаются в распаковке до первого вызова соответствующих методов.
В основном, потребитель получает информацию из того, к кому он подключен. Это означает, что многопоточность реализуется на стороне потребителя и с его стороны предоставляется некая базовая функциональность, которая будет гарантировать пропускную способность в реальном времени.
Секция 2 Использование
Hello World
Перед тем как погрузиться в архитектурные дебри библиотеки следует предоставить работающий пример.
Нижеприведённый код реализует медиа проигрыватель:
#include <stdio.h>
#include <unistd.h>
#include <framework/mlt.h>
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_factory_consumer и
mlt_factory_producer. Для фильтров и переходов существуют подобные фабрики. В
services.txt описаны все подробности обо всех стандартных сервисах библиотеки.
Использование значений
NULL является особым случаем. Таким образом мы запросили производителей и потребителей определённых по умолчанию.
Производителем по умолчанию является
fezzik. Этот производитель просматривает имена файлов, чтобы найти подходящий сервис и подключает "нормализованные фильтры" (такие как масштабирование, деинтерлейсинг, изменение частоты звука и нормализация полей кадра) к загруженному контенту. Эти фильтры гарантируют, что потребитель получет именно то, что запросил.
Потребителем по умолчанию является
sdl. Комбинация из
fezzik и
sdl предоставляет медиа проигрыватель.
В этом примере, мы подсоединили производитель и затем запустили потребителя. Затем, мы подождали завершения потребителя (в нашем случае, мы просто закрыли окно проигрывателя) и закрыли потребителя, производителя и фабрику перед выходом из приложения.
Следует отметить, что потребитель является многопоточным, ожидание некоторого события всегда требуется только после запуска и тольео перед остановкой или закрытием потребителя.
Также следует отметить, что можно переопределить значения по умолчанию:
$ MLT_CONSUMER=westley ./hello file.avi
Таким образом мы создадим
westley xml документ на стандартном выводе.
$ MLT_CONSUMER=westley MLT_PRODUCER=avformat ./hello file.avi
Таким образом мы проиграем видео напрямую через производитель
avformat, что позволит нам обойти функции нормализации.
$ MLT_CONSUMER=libdv ./hello file.avi > /dev/dv1394
Такой вариант выполнит конвертацию файла
file.avi в формат
DV и отправит контент на ваше
DV устройство.
Фабрики
Как показано в примере "Hello World", фабрики применяются для создания сервисных объектов.
Сама библиотека не предоставляет никаких сервисов сервисы предоставляются в виде плагинов. Плагин это организованная форма (модуль) и модуль можут предоставлять множество сервисов различного типа.
После инициализации фабрики все сконфигурированные сервисы готовы к использованию.
Полный набор методов принадлежащих фабрике показан ниже:
int mlt_factory_init( char *prefix );
const char *mlt_factory_prefix( );
char *mlt_environment( char *name );
mlt_producer mlt_factory_producer( char *name, void *input );
mlt_filter mlt_factory_filter( char *name, void *input );
mlt_transition mlt_factory_transition( char *name, void *input );
mlt_consumer mlt_factory_consumer( char *name, void *input );
void mlt_factory_close( );
Метод
mlt_factory_prefix возвращает путь к каталогу с инсталлированными модулями. Путь может быть определён с помощью метода
mlt_factory_init или может быть задан через переменную окружения
MLT_REPOSITORY. Если путь не определён, то используется значение по умолчанию
$(INSTALLDIR)/shared/mlt/modules.
Метод
mlt_environment предоставляет доступ только на чтение к коллекции пар
name=value, как показано в таблице приведённой ниже:
| Переменная |
Описание |
Значения |
| MLT_NORMALISATION |
Система цветности |
PAL или NTSC |
| MLT_PRODUCER |
Производитель по умолчанию |
fezzik или другой |
| MLT_CONSUMER |
Потребитель по умолчанию |
sdl или другой |
| MLT_TEST_CARD |
Производитель для тестовой карты по умолчанию |
любой |
Эти значения инициализируются из переменных окружения имеющих такие же имена.
Как показано выше, производитель может быть создан с помощью производителя "нормализация по умолчанию" и они могуть быть запрошены по имени. Фильтры и переходы всегда запрашиваются по имени, для них не существует значения по умолчанию.
Свойства сервиса
Как показано в документе
services.txt, у каждого сервиса есть свой собственный набор свойств, через которые можно управлять поведением сервиса.
Для того чтобы установить свойства сервиса следует получить соответствующий объект свойств. Для производителя это делается следующим образом:
mlt_properties properties = mlt_producer_properties( producer );
Все сервисы имеют соответствующий метод.
После получения данного объекта, все операции со свойствами сервиса производятся напрямую с этим объектом, например:
mlt_properties_set( properties, "name", "value" );
Более детальное описание объекта свойств будет приведено далее.
Списки проигрывания
Таким образом, мы показали простой пример. Следующим шагом мы организуем производителей в список проигрывания.
Давайте предположим, что мы адаптируем предыдущий пример и пожелаем проиграть по очереди несколько файлов, т.е.:
hello *.avi
Вместо прямого вызова метода
mlt_factory_producer мы создадим новую функцию
create_playlist. Эта функция будет создавать список проигрывания, создавая каждого производителя и добавляя его к списку.
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 );
}
Следует отметить, что мы закрыли производителя после его добавления. В действительности, то что мы делаем - это закрытие нашей ссылки к нему, т.к. список проигрывания создаёт свою собственную ссылку на производителя и закроет эту ссылку во время удаления списка проигрывания.
Такая функциональность ссылок была введена в версии
0.1.2. Данная функциональность 100% совместима с ранним механизмом регистрации ссылки и деструктора в свойствах объекта списка проигрывания.
При добавлении нескольких экземпляров одного производителя будут создано соответствующее количество ссылок на него.
Теперь, мы заменяем эти строчки в функции
main:
// Create a normalised producer
mlt_producer world = mlt_factory_producer( NULL, argv[ 1 ] );
на:
// Create a playlist
mlt_producer world = create_playlist( argc, argv );
и мы теперь будем проигрывать список клипов.
Фильтры
Вставка фильтров между производителем и потребителем производится следующим образом. Сначала фильтр подключается к производителю, затем к предыдущему фильтру, а последний фильтр к потребителю.
Пример:
// 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 );
Естественно, что список проигрывания, будучи производителем, может быть помещён в другой список проигрывания, а фильтры могут быть подключены к медиа контенту или к созданному новому списку и так далее.
The main advantage of attached filters is that they remain attached and don't suffer from the maintenance problems associated with items being inserted and displacing calculated in/out points - this being a major issue if you exclusively use the connect or insert detached filters in a multitrack field (описано далее).
Введение в микширование
Микширование является простейшим способом использования перехода между соседними клипами в списке проигрывания.
Рассмотрим следующий список проигрывания:
+-+----------------------+----------------------------+-+
|X|A |B |X|
+-+----------------------+----------------------------+-+
Предположим, что "X" является "чёрным клипом" длиной в 50 кадров.
Когда вы запустите проигрывание этого списка, вы получите 50 кадров чёрного, которое резко переключится в клип "A", затем следует переключение на клип "B" и, наконец, опять 50 кадров чёрного.
Намерение состоит в том, чтобы преобразовать список проигрывания похожий на это:
+-+---------------------+-+------------------------+-+
|X|A |A|B |B|
|A| |B| |X|
+-+---------------------+-+------------------------+-+
Причем клип, относящийся к двум клипам, представляет собой переход. Следует отметить, что представление второго списка проигрывания короче чем первого, это очевидно, один 50 кадровый переход между двумя клипами уменшает время проигрывания на 50 кадров.
Микширование производится с помощью метода
mlt_playlist_mix. Предположим, что у вас есть список проигрывания подобный описанному выше и чтобы произвести первое микширование, необходимо сделать следующее:
// Create a transition
mlt_transition transition = mlt_factor_transition( "luma", NULL );
// Mix the first and second clips for 50
mlt_playlist_mix( playlist, 0, 50, transition );
// Close the transition
mlt_transition_close( transition );
Этот код выполнит первое микширование. Таким же образом производится микширование остальных клипов. В результате мы получим новый клип в списке проигрывания, соответственно следующим микшированием будет микширование третьего и четвёртого клипов.
Подсказка, для упрощения определение номера следующего клипа можно сделать так:
// Get the number of clips on the playlist
int i = mlt_playlist_count( );
// Iterate through them in reverse order
while ( i -- )
{
// Create a transition
mlt_transition transition = mlt_factor_transition( "luma", NULL );
// Mix the first and second clips for 50
mlt_playlist_mix( playlist, i, 50, transition );
// Close the transition
mlt_transition_close( transition );
}
Существуют также другие методы, например, использование метода
mlt_playlist_join для объединения текущего клипа и ещё одного (вы можете определить был ли новый клип создан или нет с помощью сравнения длины списка проигрывания до и после вызова метода).
Во время работы метод
mlt_playlist_mix генерирует трактор и мнультитрек, как будет описано ниже. Подобно подключаемым фильтрам, микширование упрощает жизнь при вставке элементов в список проигрывания.
Также следует отметить, что микширование позволяет упростить пользовательский интерфейс, вместо упорного использования сложного многотрекового объекта, вы можете выполнять множество действий на одном треке. Следовательно, дополнительные треки могут быть использованы для дублирования звуковой дорожки, микширования или композиции, которые позиционируются независимо и не подвергаются влиянию при работе с другими треками. Но если вам требуется претенциозный, запутывающий и расстраивающий интерфейс, то эта функциональность тоже поддерживается ;-).
Практичность и оптимизация
В предыдущих двух секциях я описал некоторую мощную функциональность разработанную для упрощения использования библиотеки MLT. Тем не менее, важно пояснить, что происходит когда вы пытаетесь вставить переход между двумя частями одного и того же видеоматериала.
Любой, кто знаком с компрессией видео, будет знать, что быстрый переход по материалу не всегда обходится без последствий с точки зрения производительности. Таким образом, если вам требуется получить два кадра одного клипа для перехода, это может привести к значительной нагрузке, что будет очень неприятно, особенно, если вы выполняете работу в реальном времени.
Как же это преодолеть?
В действительности, это очень просто, вызываете метод
mlt_producer_optimise для объекта верхнего уровня после модификации и библиотека сама определит как его обработать. Метод определяет максимальное число перекрывающихся экземпляров внутри объекта, создаёт необходимое количество клонов и назначает клонам соответствующие индексы.
В приведённом выше примере, вы просто вызываете:
// Optimise the playlist
mlt_producer_optimise( mlt_playlist_producer( playlist ) );
после выполнения микширования. Следует отметить, что вызов этого метода автоматически выполняется для
deserialised westleys.
Мультитрековость и Переходы
Поддержка мультитрековости требует следующее:
1. Необходимо, чтобы производитель и потребитель взаимодействовали между собой через один кадр;
1. Возможность сериализации и работы через "сеть" (или граф фильтра).
Мы можем отобразить мультитрековость стандартным способом:
+-----------------+ +-----------------------+
0: |a1 | |a2 |
+---------------+-+--------------------------+-+---------------------+
1: |b1 |
+------------------------------+
Перекрывающиеся области треков 0 и 1 должны использовать некоторый переход. Без использования перехода, кадры от
b1 и
a2 будут отображаться на время перекрытия треков (по умолчанию, трек с наибольшим номером получает преимущество в отображении).
Библиотека MLT имеет мультитрековый объект, но он не является производителем, в том смысле, что он может быть напрямую подключен к потребителю и всё будет работать. Потребитель будет рассматривать его как обычного производителя и, с точки зрения мультитрековости, вы не увидите ничего с трека 1, кроме переходов между клипами, промежуток между
a1 и
a2 будет заполнен тестовыми кадрами.
Такое случается из-за того, что потребитель получает по одному кадру от производителя, в то в ремя как мультитрек предоставляет один кадр для каждого трека. Что-то где-то должно удостоверяться, что все кадры получены от мультитрекового объекта и выбирать правильный кадр.
Следовательно, библиотека предоставляет прослойку для мультитрека, который называется
трактором. Задаче трактора является отслеживание того, что треки получаются, кадр выбирается нужный и что он себя ведёт как производитель.
Таким образом, мультитрек отдаёт поток трактору как показано ниже:
+----------+
|multitrack|
| +------+ | +-------+
| |track0|-|--->|tractor|
| +------+ | |\ |
| | | \ |
| +------+ | | \ |
| |track1|-|--->|---o---|--->
| +------+ | | / |
| | | / |
| +------+ | |/ |
| |track2|-|--->| |
| +------+ | +-------+
+----------+
Имея такую комбинацию, мы теперь можем подсоединять мультитреки к потребителям. The last non-test card will be retrieved and passed on.
Треки могут быть производителями, списками проигрывания и даже другими тракторами.
Теперь мы желаем вставить фильтры и переходы между мультитреком и трактором. Это можно сделать напрямуя с помощью вставки фильтров между ними. Но такой метод потребует большого количества подсоединений между левыми и правыми производителями и потребителями, т.е. мы должны иметь возможность автоматизировать данный процесс.
Так в соответствии с нашей сельскохозяйственной темой, родилось понятие "поля". Мы "выращиваем" фильтры и переходы в поле, и трактор тянет мультитрек (подумайте о комбайне :-)) по полю и производит кадр.
Концептуально, мы можем видеть это так:
+----------+
|multitrack|
| +------+ | +-------------+ +-------+
| |track0|-|--->|field |--->|tractor|
| +------+ | | | |\ |
| | | filters | | \ |
| +------+ | | and | | \ |
| |track1|-|--->| transitions |--->|---o---|--->
| +------+ | | | | / |
| | | | | / |
| +------+ | | | |/ |
| |track2|-|--->| |--->| |
| +------+ | +-------------+ +-------+
+----------+
Таким образом, на следует сначала создать трактор, через который мы получаем мультитрек и остальные объекты. Мы можем поработать с этими объектами и затем подключить трактор к потребителю.
А вот так это выглядит со стороны потребителя:
+-----------------------------------------------+
|tractor +--------------------------+ |
| +----------+ | +-+ +-+ +-+ +-+ | |
| |multitrack| | |f| |f| |t| |t| | |
| | +------+ | | |i| |i| |r| |r| | |
| | |track0|-|--->| |l|- ->|l|- ->|a|--->|a|\| |
| | +------+ | | |t| |t| |n| |n| | |
| | | | |e| |e| |s| |s| |\ |
| | +------+ | | |r| |r| |i| |i| | \|
| | |track1|-|- ->| |0|--->|1|--->|t|--->|t|-|--o--->
| | +------+ | | | | | | |i| |i| | /|
| | | | | | | | |o| |o| |/ |
| | +------+ | | | | | | |n| |n| | |
| | |track2|-|- ->| | |- ->| |--->|0|- ->|1|/| |
| | +------+ | | | | | | | | | | | |
| +----------+ | +-+ +-+ +-+ +-+ | |
| +--------------------------+ |
+-----------------------------------------------+
Надеемся, что пример прояснит всё.
Предположим, что нам необходимо выводить некую метку в нашем примере. Мы уже расширили код примера для проигрывания нескольких клипов и теперь добавим текстовую метку "Hello World" в верхний левый угол кадра:
mlt_producer create_tracks( int argc, char **argv )
{
// Create the tractor
mlt_tractor tractor = mlt_tractor_new( );
// Obtain the field
mlt_field field = mlt_tractor_field( tractor );
// Obtain the multitrack
mlt_multitrack multitrack = mlt_tractor_multitrack( tractor );
// Create a composite transition
mlt_transition transition = mlt_factory_transition( "composite", "10%/10%:15%x15%" );
// Create track 0
mlt_producer track0 = create_playlist( argc, argv );
// Create the watermark track - note we NEED fezzik for scaling here
mlt_producer track1 = mlt_factory_producer( "fezzik", "pango" );
// Get the length of track0
mlt_position length = mlt_producer_get_playtime( track0 );
// Set the properties of track1
mlt_properties properties = mlt_producer_properties( track1 );
mlt_properties_set( properties, "text", "Hello\nWorld" );
mlt_properties_set_position( properties, "in", 0 );
mlt_properties_set_position( properties, "out", length - 1 );
mlt_properties_set_position( properties, "length", length );
mlt_properties_set_int( properties, "a_track", 0 );
mlt_properties_set_int( properties, "b_track", 1 );
// Now set the properties on the transition
properties = mlt_transition_properties( transition );
mlt_properties_set_position( properties, "in", 0 );
mlt_properties_set_position( properties, "out", length - 1 );
// Add our tracks to the multitrack
mlt_multitrack_connect( multitrack, track0, 0 );
mlt_multitrack_connect( multitrack, track1, 1 );
// Now plant the transition
mlt_field_plant_transition( field, transition, 0, 1 );
// Close our references
mlt_producer_close( track0 );
mlt_producer_close( track1 );
mlt_transition_close( transition );
// Return the tractor
return mlt_tractor_producer( tractor );
}
Всё, что нам теперь необходимо - заменить эти строчки в функции
main:
// Create a playlist
mlt_producer world = create_playlist( argc, argv );
на:
// Create a watermarked playlist
mlt_producer world = create_tracks( argc, argv );
и у нас есть средство для проигрывания клипов с навязчивой меткой - только этого нам и не хватало, не так ли?
Тот же самый результат можно достичь с помощью простого фильтра
watermark, вставленным между производителем и потребителем.
Секция 3 - Структура и дизайн
Иерархия класса
Инфраструктура библиотеки MLT имеет иерархию ОО класса, который состоит из следующих публичных классов и абстракций:
mlt_properties
mlt_frame
mlt_service
mlt_producer
mlt_playlist
mlt_tractor
mlt_filter
mlt_transition
mlt_consumer
mlt_deque
mlt_pool
mlt_factory
Каждый класс указанный выше может рассматриваться как расширение классов, описанных выше и левее.
Следующие секции описывают функциональность предоставляемой инфраструктуры. Это ключевые компоненты, понимание которых необходимо для работы с библиотекой.
Класс mlt_properties
Класс свойств является базовым классом для классов кадра и сервиса.
Он разработан для организации эффективной таблицы переходов для различных типов информации, таких как строки, целые, числа с плавающей точкой, указатели на данные и структуры данных.
Каждое свойство имеет уникальное название.
Обычное использование класса:
// 1. Create a new, empty properties set;
mlt_properties properties = mlt_properties_new( );
// 2. Assign the value "world" to the property "hello";
mlt_properties_set( properties, "hello", "world" );
// 3. Retrieve and print the value of "hello";
printf( "%s\n", mlt_properties_get( properties, "hello" ) );
// 4. Reassign "hello" to "world!";
mlt_properties_set( properties, "hello", "world!" );
// 5. Retrieve and print the value of "hello";
printf( "%s\n", mlt_properties_get( properties, "hello" ) );
// 6. Assign the value "0" to "int";
mlt_properties_set( properties, "int", "0" );
// 7. Retrieve and print the integer value of "int";
printf( "%d\n", mlt_properties_get_int( properties, "int" ) );
// 8. Assign the integer value 50 to "int2";
mlt_properties_set_int( properties, "int2", 50 );
// 9. Retrieve and print the double value of "int2";
printf( "%s\n", mlt_properties_get( properties, "int2" ) );
Шаги со второго по пятый демонстрируют уникальность имени свойства, набор операций над существующим "именем" изменяют его значение. Они также освобождают память, ассоциированную с предыдущим значением. Следует отметить, что таким же образом можно изменить тип значения.
Шаги с шестого по седьмой демонстрируют, как объект обрабатывает распаковку данных из строк. Текстовое значение "0" было определено, целое значение 0 было получено.
Шаги с восьмого по девятый демонстрируют, как объект обрабатывает упаковку в строки.
Для отображения всех пар имя/значение можно организовать небольшую итерацию:
for ( i = 0; i < mlt_properties_count( properties ); i ++ )
printf( "%s = %s\n", mlt_properties_get_name( properties, i ),
mlt_properties_get_value( properties, i ) );
Следует отметить, что свойства получаются в том порядке, в каком они были определены.
Свойства также используются для хранения указателей на области памяти. Это делается так:
uint8_t *image = malloc( size );
mlt_properties_set_data( properties, "image", image, size, NULL, NULL );
В этом примере, мы указываем, что указатель может быть получен из объекта обратным запросом
get_data:
image = mlt_properties_get_data( properties, "image", &size );
или:
image = mlt_properties_get_data( properties, "image", NULL );
если мы не желаем узнавать размер.
Две вещи:
- Выделенная память остаётся после закрытия объекта до тех пока вы не определите деструктор. Для вышеописанного случая, это может быть сделано так:
mlt_properties_set_data( properties, "image", image, size, free, NULL );
При закрытии объекта или при изменении значения "image" будет вызван деструктор.
- Строковое значение возвращаемое методом
mlt_properties_get будет равно NULL. Обычно не требуется упаковка изображения в строку, но для других структур это может понадобиться. Вы можете указать упаковщик в виде последнего аргумента, если это необходимо (его определение char *serialise( void * )).
Свойства также предоставляют более навороченные возможности.
Объект имеет возможность наследовать все упаковываемые значения от другого такого же объекта:
mlt_properties_inherit( this, that );
Объект имеет возможность копировать свой набор свойств для другого объекта:
mlt_properties_mirror( this, that );
После вызова этого метода весь набор упаковывающихся значений передаётся от
this к
that.
Класс mlt_deque
Стеки и очереди являются основными компонентами инфраструктуру MLT. Будучи ленивыми мы выбрали реализацию "Double Ended Queue" (
deque), которая объединяет функциональность обоих.
Методы класса определены ниже:
mlt_deque mlt_deque_init( );
int mlt_deque_count( mlt_deque this );
int mlt_deque_push_back( mlt_deque this, void *item );
void *mlt_deque_pop_back( mlt_deque this );
int mlt_deque_push_front( mlt_deque this, void *item );
void *mlt_deque_pop_front( mlt_deque this );
void *mlt_deque_peek_back( mlt_deque this );
void *mlt_deque_peek_front( mlt_deque this );
void mlt_deque_close( mlt_deque this );
Стековые операции используются в ряде случаев:
- (обратная польская нотация) операции с изображениями и звуком;
- выделение памяти.
Операции с очередями используются в случаях:
- основного класса потребителя;
- реализация потребителя может требовать ещё больше очередей.
Класс mlt_pool
Библиотека MLT предоставляет возможности для работы с памятью через этот класс. После инициализации, методы данного класса могут рассматриваться как замена стандартным функциям
malloc/realloc/free.
Необходимость в таких методах обусловлена известностью о неэффективности операций
malloc/free, особенно при работе с большими блоками памяти (обработка изображений). На Linux, функция
malloc оптимизирована для выделения памяти блоками менее 128 КБ. Блоки памяти такого размера и менее остаются в памяти процесса для последующего повторного использования, предотвращая использование функций ядра для повторяющихся выделений/освобождений небольших блоков памяти. Тем не менее, работа с блоками памяти большего размера сказывается на производительности.
Дизайн данного класса просто хранит список стеков. Один стек отвечает за память размером 2^n байт (где n находится между 8 и 31). Когда происходит выделение памяти, необходимый размер округляется до следующего значения степени двойки, запрашивается стек для этого размера памяти и элемент помещается туда или создаётся, если стек был пуст.
Каждый элемент имеет
заголовок, размещённый прямо перед возвращаемым адресом. Заголовок хранит указатель на стек, к которому принадлежит данный элемент.
При освобождении элемента, мы запрашиваем заголовок, получаем стек и выталкиваем его обратно.
Таким образом, с точки зрения програмиста мы пользуемся "стандартными" функциями:
void *mlt_pool_alloc( int size );
void *mlt_pool_realloc( void *ptr, int size );
void mlt_pool_release( void *release );
Класс mlt_frame
Этот объект определён следующим образом:
+------------+
|frame |
+------------+
| properties |
| image stack|
| audio stack|
+------------+
Жизненный цикл кадра может быть представлен следующим образом:
| Этап |
Производитель |
Фильтр |
Потребитель |
| 0.0 |
|
|
Запрос кадра |
| 0.1 |
|
Получение запроса. Запрос кадра. |
|
| 0.2 |
Получение запроса. Генерация кадра для текущей позиции. Увеличение указателя позиции |
|
|
| 0.3 |
|
Получение кадра. Обновление кадра. |
|
| 0.4 |
|
|
Получение кадра |
Следует отметить, что ни фильтр, ни потребитель не имеют никакого понятия о текущей позиции до тех пор, пока они не получат кадр. Скорость и текущая позиция являются свойствами производителя, которые назначаются объекту кадра при его создании.
Шаг
0.3 является критичным. Если фильтр определяет, что кадр для него интересен, то он должен воздействовать на кадр через его свойства и/или A/V стеки.
Предположим, что фильтр работает с видео и с аудио, следовательно он должен поместить данные и методы в стеки, которые произведут необходимую обработку. Это может быть сделано с помощью методов
mlt_frame_push_image и
mlt_frame_push_audio. Чтобы определить интересы фильтра, стеки должны содержать следующее:
Методы
filter_get вызываются автоматически, когда потребитель вызывает метод
get_image для кадра.
| Этап |
Производитель |
Фильтр |
Потребитель |
| 1.0 |
|
|
frame_get_image |
| 1.1 |
|
filter_get_image: получаем data2 и data1, frame_get_image |
|
| 1.2 |
producer_get_image, генерирует изображение |
|
|
| 1.3 |
|
Получение изображения. Обновление изображения |
|
| 1.4 |
|
|
Получение изображения |
Очевидно, что если фильтр не интересует изображение, то он не должен работать с соответствующим стеком. В этом случае потребитель получит изображение напрямую от производителя.
Аналогично обрабатывается и звук:
| Этап |
Производитель |
Фильтр |
Потребитель |
| 2.0 |
|
|
frame_get_audio |
| 2.1 |
|
filter_get_audio: получаем данные, frame_get_audio |
|
| 2.2 |
producer_get_audio, генерация звука |
|
|
| 2.3 |
|
Получение звука. Обновление звука. |
|
| 2.4 |
|
|
Получение звука. |
И наконец, когда потребитель закончит обрабатывать кадр, он должен закрыть его.
Следует отметить, что потребитель имеет возможность не обрабатывать изображение или звук для любого взятого кадра, особенно при работе в реальном времени. Об этом будет написано далее.
По умолчанию, кадр имеет следующие свойства:
| Имя |
Описание |
Значения |
_position |
Текущая позиция производителя |
от 0 до n |
_speed |
Скорость производителя |
double |
image |
Сгенерированное изображение |
NULL или указатель |
alpha |
Сгенерированная маска прозрачности |
NULL или указатель |
width |
Ширина изображения |
|
height |
Высота изображения |
|
normalised_width |
Нормализованная ширина изображения |
720 |
normalised_height |
Нормализованная высота изображения |
576 или 480 |
progressive |
Прогрессивная или черезстрочкая развёртка |
0 или 1 |
top_field_first |
Верхнее поле идёт первым |
0 или 1 |
audio |
Сгенерированный звук |
NULL или указатель |
frequency |
Частота оцифровки звука |
|
channels |
Количество каналов звука |
|
samples |
Сэмплы звука |
|
aspect_ratio |
Аспект изображения |
double |
test_image |
Нет изображения |
0 или 1 |
test_audio |
Нет звука |
0 или 1 |
Потребитель может добавить следующие свойства, которые воздействуют на стандартное поведение кадра:
| Имя |
Описание |
Значения |
test_card_producer |
Синтезирование тестовых изображений из указанного источника |
NULL или указатель |
consumer_aspect_ratio |
Применять указанный аспект к синтезированному тестовому изображению |
double |
rescale.interp |
Использовать этот метод масштабирования для тестового изображения |
"строка" |
Назначение свойств понятно из их названия, но свойства
normalised_width и
normalised_height требуют небольшого разъяснения. Они необходимы для того, чтобы удостовериться, что эффекты используются для PAL или NTSC изображения независимо от размеров изображения использующихся в производителе/потребителе.
Флаги
test_image и
test_audio используются для определения надо ли синтезировать тестовые изображения или звук или нет.
Дополнительные свойства могут быть назначены реализацией производителя, а фильтры, переходы и потребители имеют возможность добавлять дополнительные свойства для выполнения специфических запросов. Всё это описано в
modules.txt.
Вот полный список методов:
mlt_frame mlt_frame_init( );
mlt_properties mlt_frame_properties( mlt_frame this );
int mlt_frame_is_test_card( mlt_frame this );
int mlt_frame_is_test_audio( mlt_frame this );
double mlt_frame_get_aspect_ratio( mlt_frame this );
int mlt_frame_set_aspect_ratio( mlt_frame this, double value );
mlt_position mlt_frame_get_position( mlt_frame this );
int mlt_frame_set_position( mlt_frame this, mlt_position value );
int mlt_frame_get_image( mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable );
uint8_t *mlt_frame_get_alpha_mask( mlt_frame this );
int mlt_frame_get_audio( mlt_frame this, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples );
int mlt_frame_push_get_image( mlt_frame this, mlt_get_image get_image );
mlt_get_image mlt_frame_pop_get_image( mlt_frame this );
int mlt_frame_push_frame( mlt_frame this, mlt_frame that );
mlt_frame mlt_frame_pop_frame( mlt_frame this );
int mlt_frame_push_service( mlt_frame this, void *that );
void *mlt_frame_pop_service( mlt_frame this );
int mlt_frame_push_audio( mlt_frame this, void *that );
void *mlt_frame_pop_audio( mlt_frame this );
void mlt_frame_close( mlt_frame this );
Класс mlt_service
Базовый класс сервиса расширяет свойства, позволяя иметь 0..m входов и 0..n выходов, как показано ниже:
+-----------+
- ->| |- ->
- ->| Service |- ->
- ->| |
+-----------+
| properties|
+-----------+
Потомки сервиса налагают ограничения на количество подключаемых входов и выходов, предоставляя простой набор свойств. Typically, the service instance is encapsulated by the descendent in order for it to ensure that its connection rules are followed.
При создании сервис не определяет никаких переменных. Следует упомянуть, что производители, фильтры и переходы могут быть упакованы (например, через
westley потребителя) и необходимо различать переменные, которые могут быть упакованы от тех, которые не могут. Мы используем соглашение, что переменные неподлежащие упаковке начинаются с подчёркивания.
Публичный интерфейс имеет следующие методы:
int mlt_service_init( mlt_service this, void *child );
mlt_properties mlt_service_properties( mlt_service this );
int mlt_service_connect_producer( mlt_service this, mlt_service producer, int index );
int mlt_service_get_frame( mlt_service this, mlt_frame_ptr frame, int index );
void mlt_service_close( mlt_service this );
Обычно, только прямые потомки сервисов требуют использование данных методов. Разработчики рекомендуется использовать эти методы при определении новых сервисов.
Класс mlt_producer
У производителя есть один выход и ни одного входа:
+-----------+
| |
| Producer |--->
| |
+-----------+
| service |
+-----------+
Производитель предоставляет абстракцию для чтения файлов, работы с потоками или другим вводом видео и аудио.
После создания производитель имеет следующие свойства:
| Название |
Описание |
Значения |
mlt_type |
Тип производителя |
mlt_producer |
_position |
Позиция производителя (номер кадра) |
От 0 до n |
_speed |
Скорость производителя |
double |
fps |
Количество выпускаемых кадров в секунду |
25 или 29.97 |
in |
Номер кадра во входящем потоке |
От 0 до length-1 |
out |
Номер кадра в выходящем потоке |
От 0 до length-1 |
length |
Количество кадров во входящем потоке |
От 0 до n |
aspect_ratio |
Аспект кадра источника |
От 0 до n |
eof |
Конец клипа |
pause или loop |
resource |
Аргумент конструктора, т.е. имя файла |
|
Дополнительные свойства могут быть предоставлены реализацией производителя.
Публичный интерфейс класса состоит из следующих методов:
mlt_producer mlt_producer_new( );
int mlt_producer_init( mlt_producer this, void *child );
mlt_service mlt_producer_service( mlt_producer this );
mlt_properties mlt_producer_properties( mlt_producer this );
int mlt_producer_seek( mlt_producer this, mlt_position position );
mlt_position mlt_producer_position( mlt_producer this );
mlt_position mlt_producer_frame( mlt_producer this );
int mlt_producer_set_speed( mlt_producer this, double speed );
double mlt_producer_get_speed( mlt_producer this );
double mlt_producer_get_fps( mlt_producer this );
int mlt_producer_set_in_and_out( mlt_producer this, mlt_position in, mlt_position out );
mlt_position mlt_producer_get_in( mlt_producer this );
mlt_position mlt_producer_get_out( mlt_producer this );
mlt_position mlt_producer_get_playtime( mlt_producer this );
mlt_position mlt_producer_get_length( mlt_producer this );
void mlt_producer_prepare_next( mlt_producer this );
void mlt_producer_close( mlt_producer this );
Класс mlt_filter
Публичный интерфейс класса состоит из следующих методов:
int mlt_filter_init( mlt_filter this, void *child );
mlt_filter mlt_filter_new( );
mlt_service mlt_filter_service( mlt_filter this );
mlt_properties mlt_filter_properties( mlt_filter this );
mlt_frame mlt_filter_process( mlt_filter this, mlt_frame that );
int mlt_filter_connect( mlt_filter this, mlt_service producer, int index );
void mlt_filter_set_in_and_out( mlt_filter this, mlt_position in, mlt_position out );
int mlt_filter_get_track( mlt_filter this );
mlt_position mlt_filter_get_in( mlt_filter this );
mlt_position mlt_filter_get_out( mlt_filter this );
void mlt_filter_close( mlt_filter );
Класс mlt_transition
Публичный интерфейс класса состоит из следующих методов:
int mlt_transition_init( mlt_transition this, void *child );
mlt_transition mlt_transition_new( );
mlt_service mlt_transition_service( mlt_transition this );
mlt_properties mlt_transition_properties( mlt_transition this );
int mlt_transition_connect( mlt_transition this, mlt_service producer, int a_track, int b_track );
void mlt_transition_set_in_and_out( mlt_transition this, mlt_position in, mlt_position out );
int mlt_transition_get_a_track( mlt_transition this );
int mlt_transition_get_b_track( mlt_transition this );
mlt_position mlt_transition_get_in( mlt_transition this );
mlt_position mlt_transition_get_out( mlt_transition this );
mlt_frame mlt_transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame );
void mlt_transition_close( mlt_transition this );
Класс mlt_consumer
Публичный интерфейс класса состоит из следующих методов:
int mlt_consumer_init( mlt_consumer this, void *child );
mlt_service mlt_consumer_service( mlt_consumer this );
mlt_properties mlt_consumer_properties( mlt_consumer this );
int mlt_consumer_connect( mlt_consumer this, mlt_service producer );
int mlt_consumer_start( mlt_consumer this );
mlt_frame mlt_consumer_get_frame( mlt_consumer this );
mlt_frame mlt_consumer_rt_frame( mlt_consumer this );
int mlt_consumer_stop( mlt_consumer this );
int mlt_consumer_is_stopped( mlt_consumer this );
void mlt_consumer_close( mlt_consumer );
Специальные производители
Есть два основных типа специальных производителей - это списки проигрывания и тракторы.
Рассмотрим их.
Класс mlt_playlist
Публичный интерфейс класса состоит из следующих методов:
mlt_playlist mlt_playlist_init( );
mlt_producer mlt_playlist_producer( mlt_playlist this );
mlt_service mlt_playlist_service( mlt_playlist this );
mlt_properties mlt_playlist_properties( mlt_playlist this );
int mlt_playlist_count( mlt_playlist this );
int mlt_playlist_clear( mlt_playlist this );
int mlt_playlist_append( mlt_playlist this, mlt_producer producer );
int mlt_playlist_append_io( mlt_playlist this, mlt_producer producer, mlt_position in, mlt_position out );
int mlt_playlist_blank( mlt_playlist this, mlt_position length );
mlt_position mlt_playlist_clip( mlt_playlist this, mlt_whence whence, int index );
int mlt_playlist_current_clip( mlt_playlist this );
mlt_producer mlt_playlist_current( mlt_playlist this );
int mlt_playlist_get_clip_info( mlt_playlist this, mlt_playlist_clip_info *info, int index );
int mlt_playlist_insert( mlt_playlist this, mlt_producer producer, int where, mlt_position in, mlt_position out );
int mlt_playlist_remove( mlt_playlist this, int where );
int mlt_playlist_move( mlt_playlist this, int from, int to );
int mlt_playlist_resize_clip( mlt_playlist this, int clip, mlt_position in, mlt_position out );
void mlt_playlist_close( mlt_playlist this );
Класс mlt_tractor
Публичный интерфейс класса состоит из следующих методов:
--
RaD - 06 Jan 2006