forked from mltframework/mlt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathframework.txt
1340 lines (990 loc) · 53.4 KB
/
framework.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Framework Documentation
Copyright (C) 2004-2014 Meltytech, LLC
Last Revision: 2005-05-08
MLT FRAMEWORK
-------------
Preamble:
MLT is a multimedia framework designed for television broadcasting. As such,
it provides a pluggable architecture for the inclusion of new audio/video
sources, filters, transitions and playback devices.
The framework provides the structure and utility functionality on which
all of the MLT applications and services are defined.
On its own, the framework provides little more than 'abstract classes' and
utilities for managing resources, such as memory, properties, dynamic object
loading and service instantiation.
This document is split roughly into 3 sections. The first section provides a
basic overview of MLT, the second section shows how it's used and the final
section shows structure and design, with an emphasis on how the system is
extended.
Target Audience:
This document is provided as a 'road map' for the framework and should be
considered mandatory reading for anyone wishing to develop code at the MLT
level.
This includes:
1. framework maintainers;
2. module developers;
3. application developers;
4. anyone interested in MLT.
The emphasis of the document is in explaining the public interfaces, as
opposed to the implementation details.
It is not required reading for the MLT client/server integration - please
refer to libmvsp.txt and mvsp.txt for more details on this area.
SECTION 1 - BASIC OVERVIEW
--------------------------
Basic Design Information:
MLT is written in C.
The framework has no dependencies other than the standard C99 and POSIX
libraries.
It follows a basic Object Oriented design paradigm, and as such, much of the
design is loosely based on the Producer/Consumer design pattern.
It employs Reverse Polish Notation for the application of audio and video FX.
The framework is designed to be colour space neutral - the currently
implemented modules, however, are very much 8bit YUV422 oriented. In theory,
the modules could be entirely replaced.
A vague understanding of these terms is assumed throughout the remainder of
this document.
Structure and Flow:
The general structure of an MLT 'network' is simply the connection of a
'producer' to a 'consumer':
+--------+ +--------+
|Producer|-->|Consumer|
+--------+ +--------+
A typical consumer requests MLT Frame objects from the producer, does
something with them and when finished with a frame, closes it.
/\ A common confusion with the producer/consumer terminology used here is
/!!\ that a consumer may 'produce' something. For example, the libdv consumer
\!!/ produces DV and the libdv producer seems to consume DV. However, the
\/ naming conventions refer only to producers and consumers of MLT Frames.
To put it another way - a producer produces MLT Frame objects and a consumer
consumes MLT Frame objects.
An MLT Frame essentially provides an uncompressed image and its associated
audio samples.
Filters may also be placed between the producer and the consumer:
+--------+ +------+ +--------+
|Producer|-->|Filter|-->|Consumer|
+--------+ +------+ +--------+
A service is the collective name for producers, filters, transitions and
consumers.
The communications between a connected consumer and producer or service are
carried out in 3 phases:
* get the frame
* get the image
* get the audio
MLT employs 'lazy evaluation' - the image and audio need not be extracted
from the source until the get image and audio methods are invoked.
In essence, the consumer pulls from what it's connected to - this means that
threading is typically in the domain of the consumer implementation and some
basic functionality is provided on the consumer class to ensure realtime
throughput.
SECTION 2 - USAGE
-----------------
Hello World:
Before we go in to the specifics of the framework architecture, a working
example of usage is provided.
The following simply provides a media player:
#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;
}
This is a simple example - it doesn't provide any seeking capabilities or
runtime configuration options.
The first step of any MLT application is the factory initialisation - this
ensures that the environment is configured and MLT can function. The factory
is covered in more detail below.
All services are instantiated via the factories, as shown by the
mlt_factory_consumer and mlt_factory_producer calls above. There are similar
factories for filters and transitions. There are details on all the standard
services in services.txt.
The defaults requested here are a special case - the NULL usage requests
that we use the default producers and consumers.
The default producer is "loader". This producer matches file names to
locate a service to use and attaches 'normalising filters' (such as scalers,
deinterlacers, resamplers and field normalisers) to the loaded content -
these filters ensure that the consumer gets what it asks for.
The default consumer is "sdl". The combination of loader and sdl will
provide a media player.
In this example, we connect the producer and then start the consumer. We
then wait until the consumer is stopped (in this case, by the action of the
user closing the SDL window) and finally close the consumer, producer and
factory before exiting the application.
Note that the consumer is threaded - waiting for an event of some sort is
always required after starting and before stopping or closing the consumer.
Also note, you can override the defaults as follows:
$ MLT_CONSUMER=xml ./hello file.avi
This will create a XML document on stdout.
$ MLT_CONSUMER=xml MLT_PRODUCER=avformat ./hello file.avi
This will play the video using the avformat producer directly, thus it will
bypass the normalising functions.
$ MLT_CONSUMER=libdv ./hello file.avi > /dev/dv1394
This might, if you're lucky, do on the fly, realtime conversions of file.avi
to DV and broadcast it to your DV device.
Factories:
As shown in the 'Hello World' example, factories create service objects.
The framework itself provides no services - they are provided in the form of
a plugin structure. A plugin is organised in the form of a 'module' and a
module can provide many services of different types.
Once the factory is initialised, all the configured services are available
for use.
The complete set of methods associated to the factory are as follows:
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( );
The mlt_factory_prefix returns the path to the location of the installed
modules directory. This can be specified in the mlt_factory_init call
itself, or it can be specified via the MLT_REPOSITORY environment variable,
or in the absence of either of those, it will default to the install
prefix/shared/mlt/modules.
The mlt_environment provides read only access to a collection of name=value
pairs as shown in the following table:
+------------------+------------------------------------+------------------+
|Name |Description |Values |
+------------------+------------------------------------+------------------+
|MLT_NORMALISATION |The normalisation of the system |PAL or NTSC |
+------------------+------------------------------------+------------------+
|MLT_PRODUCER |The default producer |"loader" or other |
+------------------+------------------------------------+------------------+
|MLT_CONSUMER |The default consumer |"sdl" or other |
+------------------+------------------------------------+------------------+
|MLT_TEST_CARD |The default test card producer |any producer |
+------------------+------------------------------------+------------------+
These values are initialised from the environment variables of the same
name.
As shown above, a producer can be created using the 'default normalising'
producer, and they can also be requested by name. Filters and transitions
are always requested by name - there is no concept of a 'default' for these.
Service Properties:
As shown in the services.txt document, all services have their own set of
properties than can be manipulated to affect their behaviour.
In order to set properties on a service, we need to retrieve the properties
object associated to it. For producers, this is done by invoking:
mlt_properties properties = mlt_producer_properties( producer );
All services have a similar method associated to them.
Once retrieved, setting and getting properties can be done directly on this
object, for example:
mlt_properties_set( properties, "name", "value" );
A more complete description of the properties object is found below.
Playlists:
So far, we've shown a simple producer/consumer configuration - the next
phase is to organise producers in playlists.
Let's assume that we're adapting the Hello World example, and wish to queue
a number of files for playout, ie:
hello *.avi
Instead of invoking mlt_factory_producer directly, we'll create a new
function called create_playlist. This function is responsible for creating
the playlist, creating each producer and appending to the 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 );
}
Notice that we close the producer after the append. Actually, what we're
doing is closing our reference to it - the playlist creates its own reference
to the producer on append and insert, and it will close its reference
when the playlist is destroyed[*].
Note also that if you append multiple instances of the same producer, it
will create multiple references to it.
Now all we need do is to replace these lines in the main function:
// Create a normalised producer
mlt_producer world = mlt_factory_producer( NULL, argv[ 1 ] );
with:
// Create a playlist
mlt_producer world = create_playlist( argc, argv );
and we have a means to play multiple clips.
[*] This reference functionality was introduced in mlt 0.1.2 - it is 100%
compatible with the early mechanism of registering the reference and
destructor with the properties of the playlist object.
Filters:
Inserting filters between the producer and consumer is just a case of
instantiating the filters, connecting the first to the producer, the next
to the previous filter and the last filter to the consumer.
For example:
// 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 ) );
As with producers and consumers, filters can be manipulated via their
properties object - the mlt_filter_properties method can be invoked and
properties can be set as needed.
The additional argument in the filter connection is an important one as it
dictates the 'track' on which the filter operates. For basic producers and
playlists, there's only one track (0), and as you will see in the next
section, even multiple tracks have a single track output.
Attached Filters:
All services can have attached filters.
Consider the following example:
// 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 );
When this is played out, the greyscale filter will be executed for each frame
in the playlist which comes from that producer.
Further, each cut can have their own filters attached which are executed after
the producer's filters. As an example:
// 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 );
Even the playlist itself can have an attached 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 );
And, of course, the playlist, being a producer, can be cut up and placed on
another playlist, and filters can be attached to those cuts or on the new
playlist itself and so on ad nauseum.
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
(described below).
Introducing the Mix:
The mix is the simplest way to introduce transitions between adjacent clips
on a playlist.
Consider the following playlist:
+-+----------------------+----------------------------+-+
|X|A |B |X|
+-+----------------------+----------------------------+-+
Let's assume that the 'X' is a 'black clip' of 50 frames long.
When you play this out, you'll get a 50 frames of black, abrupt cut into
A, followed by an abrupt cut into B, and finally into black again.
The intention is to convert this playlist into something like:
+-+---------------------+-+------------------------+-+
|X|A |A|B |B|
|A| |B| |X|
+-+---------------------+-+------------------------+-+
Where the clips which refer to 2 clips represent a transition. Notice that
the representation of the second playlist is shorter than the first - this is
to be expected - a single transition of 50 frames between two clips will
reduce the playtime of the result by 50 frames.
This is done via the use of the mlt_playlist_mix method. So, assuming you get
a playlist as shown in the original diagram, to do the first mix, you could do
something like:
// 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 );
This would give you the first transition, subsequently, you would apply a similar
technique to mix clips 1 and 2. Note that this would create a new clip on the
playlist, so the next mix would be between 3 and 4.
As a general hint, to simplify the requirement to know the next clip index,
you might find the following simpler:
// 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 );
}
There are other techniques, like using the mlt_playlist_join between the
current clip and the newly created one (you can determine if a new clip was
created by comparing the playlist length before and after the mix call).
Internally, the mlt_playlist_mix call generates a tractor and multitrack as
described below. Like the attached filters, the mix makes life very simple
when you're inserting items into the playlist.
Also note that it allows a simpler user interface - instead of enforcing the
use of a complex multitrack object, you can do many operations on a single
track. Thus, additional tracks can be used to introduce audio dubs, mixes
or composites which are independently positioned and aren't affected by
manipulations on other tracks. But hey, if you want a bombastic, confusing
and ultimately frustrating traditional NLE experience, that functionality
is provided too ;-).
Practicalities and Optimisations:
In the previous two sections I've introduced some powerful functionality
designed to simplify MLT usage. However, a general issue comes into this -
what happens when you introduce a transition between two cuts from the same
bit of video footage?
Anyone who is familiar with video compression will be aware that seeking
isn't always without consequence from a performance point of view. So if
you happen to require two frames from the same clip for a transition, the
processing is going to be excessive and the result will undoubtedly be very
unpleasant, especially if you're rendering in realtime...
So how do we get round this?
Actually, it's very simple - you invoke mlt_producer_optimise on the top
level object after a modification and MLT will determine how to handle it.
Internally, it determines the maximum number of overlapping instances
throughout the object and creates clones and assigns clone indexes as
required.
In the mix example above, you can simply call:
// Optimise the playlist
mlt_producer_optimise( mlt_playlist_producer( playlist ) );
after the mix calls have be done. Note that this is automatically applied
to deserialised MLT XML.
Multiple Tracks and Transitions:
MLT's approach to multiple tracks is governed by two requirements:
1) The need for a consumer and producer to communicate with one another via
a single frame;
2) The desire to be able to serialise and manipulate a 'network' (or filter
graph if you prefer).
We can visualise a multitrack in the way that an NLE presents it:
+-----------------+ +-----------------------+
0: |a1 | |a2 |
+---------------+-+--------------------------+-+---------------------+
1: |b1 |
+------------------------------+
The overlapping areas of track 0 and 1 would (presumably) have some kind of
transition - without a transition, the frames from b1 and b2 would be shown
during the areas of overlap (ie: by default, the higher numbered track takes
precedence over the lower numbered track).
MLT has a multitrack object, but it is not a producer in the sense that it
can be connected directly to a consumer and everything will work correctly.
A consumer would treat it precisely as it would a normal producer, and, in
the case of the multitrack above, you would never see anything from track 1
other than the transitions between the clips - the gap between a1 and a2
would show test frames.
This happens because a consumer pulls one frame from the producer it's
connected to while a multitrack will provide one frame per track.
Something, somewhere, must ensure that all frames are pulled from the
multitrack and elect the correct frame to pass on.
Hence, MLT provides a wrapper for the multitrack, which is called a
'tractor', and its the tractors task to ensure that all tracks are pulled
evenly, the correct frame is output and that we have 'producer like'
behaviour.
Thus, a multitrack is conceptually 'pulled' by a tractor as shown here:
+----------+
|multitrack|
| +------+ | +-------+
| |track0|-|--->|tractor|
| +------+ | |\ |
| | | \ |
| +------+ | | \ |
| |track1|-|--->|---o---|--->
| +------+ | | / |
| | | / |
| +------+ | |/ |
| |track2|-|--->| |
| +------+ | +-------+
+----------+
With a combination of the two, we can now connect multitracks to consumers.
The last non-test card will be retrieved and passed on.
The tracks can be producers, playlists, or even other tractors.
Now we wish to insert filters and transitions between the multitrack and the
tractor. We can do this directly by inserting filters directly between the
tractor and the multitrack, but this involves a lot of connecting and
reconnecting left and right producers and consumers, and it seemed only fair
that we should be able to automate that process.
So in keeping with our agricultural theme, the concept of the 'field' was
born. We 'plant' filters and transitions in the field and the tractor pulls
the multitrack (think of a combine harvester :-)) over the field and
produces a 'bail' (sorry - kidding - frame :-)).
Conceptually, we can see it like this:
+----------+
|multitrack|
| +------+ | +-------------+ +-------+
| |track0|-|--->|field |--->|tractor|
| +------+ | | | |\ |
| | | filters | | \ |
| +------+ | | and | | \ |
| |track1|-|--->| transitions |--->|---o---|--->
| +------+ | | | | / |
| | | | | / |
| +------+ | | | |/ |
| |track2|-|--->| |--->| |
| +------+ | +-------------+ +-------+
+----------+
So, we need to create the tractor first, and from that we obtain the
multitrack and field objects. We can populate these and finally
connect the tractor to a consumer.
In essence, this is how it looks to the consumer:
+-----------------------------------------------+
|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|/| |
| | +------+ | | | | | | | | | | | |
| +----------+ | +-+ +-+ +-+ +-+ | |
| +--------------------------+ |
+-----------------------------------------------+
An example will hopefully clarify this.
Let's assume that we want to provide a 'watermark' to our hello world
example. We have already extended the example to play multiple clips,
and now we will place a text based watermark, reading 'Hello World' in
the top left hand corner:
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 loader for scaling here
mlt_producer track1 = mlt_factory_producer( "loader", "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 );
}
Now all we need do is to replace these lines in the main function:
// Create a playlist
mlt_producer world = create_playlist( argc, argv );
with:
// Create a watermarked playlist
mlt_producer world = create_tracks( argc, argv );
and we have a means to play multiple clips with a horribly obtrusive
watermark - just what the world needed, right? ;-)
Incidentally, the same thing could be achieved with the more trivial
watermark filter inserted between the producer and the consumer.
SECTION 3 - STRUCTURE AND DESIGN
--------------------------------
Class Hierarchy:
The mlt framework consists of an OO class hierarchy which consists of the
following public classes and abstractions:
mlt_properties
mlt_frame
mlt_service
mlt_producer
mlt_playlist
mlt_tractor
mlt_filter
mlt_transition
mlt_consumer
mlt_deque
mlt_pool
mlt_factory
Each class defined above can be read as extending the classes above and to
the left.
The following sections describe the properties, stacking/queuing and memory
pooling functionality provided by the framework - these are key components
and a basic understanding of these is required for the remainder of the
documentation.
mlt_properties:
The properties class is the base class for the frame and service classes.
It is designed to provide an efficient lookup table for various types of
information, such as strings, integers, floating points values and pointers
to data and data structures.
All properties are indexed by a unique string.
The most basic use of properties is as follows:
// 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" ) );
Steps 2 through 5 demonstrate that the "name" is unique - set operations on
an existing "name" change the value. They also free up memory associated to
the previous value. Note that it also possible to change type in this way
too.
Steps 6 and 7 demonstrate that the properties object handles deserialisation
from strings. The string value of "0" is set, the integer value of 0 is
retrieved.
Steps 8 and 9 demonstrate that the properties object handles serialisation
to strings.
To show all the name/value pairs in a properties, it is possible to iterate
through them:
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 ) );
Note that properties are retrieved in the order in which they are set.
Properties are also used to hold pointers to memory. This is done via the
set_data call:
uint8_t *image = malloc( size );
mlt_properties_set_data( properties, "image", image, size, NULL, NULL );
In this example, we specify that the pointer can be retrieved from
properties by a subsequent request to get_data:
image = mlt_properties_get_data( properties, "image", &size );
or:
image = mlt_properties_get_data( properties, "image", NULL );
if we don't wish to retrieve the size.
Two points here:
1) The allocated memory remains after the properties object is closed unless
you specify a destructor. In the case above, this can be done with:
mlt_properties_set_data( properties, "image", image, size, free, NULL );
When the properties are closed, or the value of "image" is changed, the
destructor is invoked.
2) The string value returned by mlt_properties_get is NULL. Typically, you
wouldn't wish to serialise an image as a string, but other structures
might need such functionality - you can specify a serialiser as the last
argument if required (declaration is char *serialise( void * )).
Properties also provides some more advanced usage capabilities.
It has the ability to inherit all serialisable values from another properties
object:
mlt_properties_inherit( this, that );
It has the ability to mirror properties set on this on another set of
properties:
mlt_properties_mirror( this, that );
After this call, all serialisable values set on this are passed on to that.
mlt_deque:
Stacks and queues are essential components in the MLT framework. Being of a
lazy disposition, we elected to implement a 'Double Ended Queue' (deque) -
this encapsulates the functionality of both.
The API of the deque is defined as follows:
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 );
The stacking operations are used in a number of places:
* Reverse Polish Notation (RPN) image and audio operations
* memory pooling
The queuing operations are used in:
* the consumer base class;
* consumer implementations may require further queues.
mlt_pool:
The MLT framework provides memory pooling capabilities through the mlt_pool
API. Once initilialised, these can be seen as a straightforward drop in
replacement for malloc/realloc/free functionality.
The background behind this API is that malloc/free operations are
notoriously inefficient, especially when dealing with large blocks of memory
(such as an image). On linux, malloc is optimised for memory allocations
less than 128k - memory blocks allocated of these sizes or less are retained
in the process heap for subsequent reuse, thus bypassing the kernel calls
for repeated allocation/frees for small blocks of memory. However, blocks of
memory larger than that require kernel calls and this has a detrimental
impact on performance.
The mlt_pool design is simply to hold a list of stacks - there is one stack
per 2^n bytes (where n is between 8 and 31). When an alloc is called, the
requested size is rounded to the next 2^n, the stack is retrieved for that
size, and an item is popped or created if the stack is empty.
Each item has a 'header', situated immediately before the returned address -
this holds the 'stack' to which the item belongs.
When an item is released, we retrieve the header, obtain the stack and push
it back.
Thus, from the programmers point of view, the API is the same as the
traditional malloc/realloc/free calls:
void *mlt_pool_alloc( int size );
void *mlt_pool_realloc( void *ptr, int size );
void mlt_pool_release( void *release );
mlt_frame:
A frame object is essentially defined as:
+------------+
|frame |
+------------+
| properties |
| image stack|
| audio stack|
+------------+
The life cycle of a frame can be represented as follows:
+-----+----------------------+-----------------------+---------------------+
|Stage|Producer |Filter |Consumer |
+-----+----------------------+-----------------------+---------------------+
| 0.0 | | |Request frame |
+-----+----------------------+-----------------------+---------------------+
| 0.1 | |Receives request | |
| | |Request frame | |
+-----+----------------------+-----------------------+---------------------+
| 0.2 |Receives request | | |
| |Generates frame for | | |
| |current position | | |
| |Increments position | | |
+-----+----------------------+-----------------------+---------------------+
| 0.3 | |Receives frame | |
| | |Updates frame | |
+-----+----------------------+-----------------------+---------------------+
| 0.4 | | |Receives frame |
+-----+----------------------+-----------------------+---------------------+