Cloning optimisations and introduction of the service parser
[melted] / src / framework / mlt_multitrack.c
1 /*
2 * mlt_multitrack.c -- multitrack service class
3 * Copyright (C) 2003-2004 Ushodaya Enterprises Limited
4 * Author: Charles Yates <charles.yates@pandora.be>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #include "config.h"
22
23 #include "mlt_multitrack.h"
24 #include "mlt_playlist.h"
25 #include "mlt_frame.h"
26
27 #include <stdio.h>
28 #include <stdlib.h>
29
30 struct mlt_track_s
31 {
32 mlt_producer producer;
33 mlt_event event;
34 };
35
36 typedef struct mlt_track_s *mlt_track;
37
38 /** Private definition.
39 */
40
41 struct mlt_multitrack_s
42 {
43 // We're extending producer here
44 struct mlt_producer_s parent;
45 mlt_track *list;
46 int size;
47 int count;
48 };
49
50 /** Forward reference.
51 */
52
53 static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index );
54
55 /** Constructor.
56 */
57
58 mlt_multitrack mlt_multitrack_init( )
59 {
60 // Allocate the multitrack object
61 mlt_multitrack this = calloc( sizeof( struct mlt_multitrack_s ), 1 );
62
63 if ( this != NULL )
64 {
65 mlt_producer producer = &this->parent;
66 if ( mlt_producer_init( producer, this ) == 0 )
67 {
68 mlt_properties properties = mlt_multitrack_properties( this );
69 producer->get_frame = producer_get_frame;
70 mlt_properties_set_data( properties, "multitrack", this, 0, NULL, NULL );
71 mlt_properties_set( properties, "log_id", "multitrack" );
72 mlt_properties_set( properties, "resource", "<multitrack>" );
73 }
74 else
75 {
76 free( this );
77 this = NULL;
78 }
79 }
80
81 return this;
82 }
83
84 /** Get the producer associated to this multitrack.
85 */
86
87 mlt_producer mlt_multitrack_producer( mlt_multitrack this )
88 {
89 return this != NULL ? &this->parent : NULL;
90 }
91
92 /** Get the service associated this multitrack.
93 */
94
95 mlt_service mlt_multitrack_service( mlt_multitrack this )
96 {
97 return mlt_producer_service( mlt_multitrack_producer( this ) );
98 }
99
100 /** Get the properties associated this multitrack.
101 */
102
103 mlt_properties mlt_multitrack_properties( mlt_multitrack this )
104 {
105 return mlt_service_properties( mlt_multitrack_service( this ) );
106 }
107
108 /** Initialise position related information.
109 */
110
111 void mlt_multitrack_refresh( mlt_multitrack this )
112 {
113 int i = 0;
114
115 // Obtain the properties of this multitrack
116 mlt_properties properties = mlt_multitrack_properties( this );
117
118 // We need to ensure that the multitrack reports the longest track as its length
119 mlt_position length = 0;
120
121 // We need to ensure that fps are the same on all services
122 double fps = 0;
123
124 // Obtain stats on all connected services
125 for ( i = 0; i < this->count; i ++ )
126 {
127 // Get the producer from this index
128 mlt_track track = this->list[ i ];
129 mlt_producer producer = track->producer;
130
131 // If it's allocated then, update our stats
132 if ( producer != NULL )
133 {
134 // If we have more than 1 track, we must be in continue mode
135 if ( this->count > 1 )
136 mlt_properties_set( mlt_producer_properties( producer ), "eof", "continue" );
137
138 // Determine the longest length
139 if ( !mlt_properties_get_int( mlt_producer_properties( producer ), "hide" ) )
140 length = mlt_producer_get_playtime( producer ) > length ? mlt_producer_get_playtime( producer ) : length;
141
142 // Handle fps
143 if ( fps == 0 )
144 {
145 // This is the first producer, so it controls the fps
146 fps = mlt_producer_get_fps( producer );
147 }
148 else if ( fps != mlt_producer_get_fps( producer ) )
149 {
150 // Generate a warning for now - the following attempt to fix may fail
151 fprintf( stderr, "Warning: fps mismatch on track %d\n", i );
152
153 // It should be safe to impose fps on an image producer, but not necessarily safe for video
154 mlt_properties_set_double( mlt_producer_properties( producer ), "fps", fps );
155 }
156 }
157 }
158
159 // Update multitrack properties now - we'll not destroy the in point here
160 mlt_events_block( properties, properties );
161 mlt_properties_set_position( properties, "length", length );
162 mlt_events_unblock( properties, properties );
163 mlt_properties_set_position( properties, "out", length - 1 );
164 mlt_properties_set_double( properties, "fps", fps );
165 }
166
167 /** Listener for producers on the playlist.
168 */
169
170 static void mlt_multitrack_listener( mlt_producer producer, mlt_multitrack this )
171 {
172 mlt_multitrack_refresh( this );
173 }
174
175 /** Connect a producer to a given track.
176
177 Note that any producer can be connected here, but see special case treatment
178 of playlist in clip point determination below.
179 */
180
181 int mlt_multitrack_connect( mlt_multitrack this, mlt_producer producer, int track )
182 {
183 // Connect to the producer to ourselves at the specified track
184 int result = mlt_service_connect_producer( mlt_multitrack_service( this ), mlt_producer_service( producer ), track );
185
186 if ( result == 0 )
187 {
188 // Resize the producer list if need be
189 if ( track >= this->size )
190 {
191 int i;
192 this->list = realloc( this->list, ( track + 10 ) * sizeof( mlt_track ) );
193 for ( i = this->size; i < track + 10; i ++ )
194 this->list[ i ] = NULL;
195 this->size = track + 10;
196 }
197
198 if ( this->list[ track ] != NULL )
199 {
200 mlt_event_close( this->list[ track ]->event );
201 mlt_producer_close( this->list[ track ]->producer );
202 }
203 else
204 {
205 this->list[ track ] = malloc( sizeof( struct mlt_track_s ) );
206 }
207
208 // Assign the track in our list here
209 this->list[ track ]->producer = producer;
210 this->list[ track ]->event = mlt_events_listen( mlt_producer_properties( producer ), this,
211 "producer-changed", ( mlt_listener )mlt_multitrack_listener );
212 mlt_properties_inc_ref( mlt_producer_properties( producer ) );
213 mlt_event_inc_ref( this->list[ track ]->event );
214
215 // Increment the track count if need be
216 if ( track >= this->count )
217 this->count = track + 1;
218
219 // Refresh our stats
220 mlt_multitrack_refresh( this );
221 }
222
223 return result;
224 }
225
226 /** Get the number of tracks.
227 */
228
229 int mlt_multitrack_count( mlt_multitrack this )
230 {
231 return this->count;
232 }
233
234 /** Get an individual track as a producer.
235 */
236
237 mlt_producer mlt_multitrack_track( mlt_multitrack this, int track )
238 {
239 mlt_producer producer = NULL;
240
241 if ( this->list != NULL && track < this->count )
242 producer = this->list[ track ]->producer;
243
244 return producer;
245 }
246
247 static int position_compare( const void *p1, const void *p2 )
248 {
249 return *( mlt_position * )p1 - *( mlt_position * )p2;
250 }
251
252 static int add_unique( mlt_position *array, int size, mlt_position position )
253 {
254 int i = 0;
255 for ( i = 0; i < size; i ++ )
256 if ( array[ i ] == position )
257 break;
258 if ( i == size )
259 array[ size ++ ] = position;
260 return size;
261 }
262
263 /** Determine the clip point.
264
265 Special case here: a 'producer' has no concept of multiple clips - only the
266 playlist and multitrack producers have clip functionality. Further to that a
267 multitrack determines clip information from any connected tracks that happen
268 to be playlists.
269
270 Additionally, it must locate clips in the correct order, for example, consider
271 the following track arrangement:
272
273 playlist1 |0.0 |b0.0 |0.1 |0.1 |0.2 |
274 playlist2 |b1.0 |1.0 |b1.1 |1.1 |
275
276 Note - b clips represent blanks. They are also reported as clip positions.
277
278 When extracting clip positions from these playlists, we should get a sequence of:
279
280 0.0, 1.0, b0.0, 0.1, b1.1, 1.1, 0.1, 0.2, [out of playlist2], [out of playlist1]
281 */
282
283 mlt_position mlt_multitrack_clip( mlt_multitrack this, mlt_whence whence, int index )
284 {
285 mlt_position position = 0;
286 int i = 0;
287 int j = 0;
288 mlt_position *map = malloc( 1000 * sizeof( mlt_position ) );
289 int count = 0;
290
291 for ( i = 0; i < this->count; i ++ )
292 {
293 // Get the producer for this track
294 mlt_producer producer = this->list[ i ]->producer;
295
296 // If it's assigned and not a hidden track
297 if ( producer != NULL )
298 {
299 // Get the properties of this producer
300 mlt_properties properties = mlt_producer_properties( producer );
301
302 // Determine if it's a playlist
303 mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL );
304
305 // Special case consideration of playlists
306 if ( playlist != NULL )
307 {
308 for ( j = 0; j < mlt_playlist_count( playlist ); j ++ )
309 count = add_unique( map, count, mlt_playlist_clip( playlist, mlt_whence_relative_start, j ) );
310 count = add_unique( map, count, mlt_producer_get_out( producer ) + 1 );
311 }
312 else
313 {
314 count = add_unique( map, count, 0 );
315 count = add_unique( map, count, mlt_producer_get_out( producer ) + 1 );
316 }
317 }
318 }
319
320 // Now sort the map
321 qsort( map, count, sizeof( mlt_position ), position_compare );
322
323 // Now locate the requested index
324 switch( whence )
325 {
326 case mlt_whence_relative_start:
327 if ( index < count )
328 position = map[ index ];
329 else
330 position = map[ count - 1 ];
331 break;
332
333 case mlt_whence_relative_current:
334 position = mlt_producer_position( mlt_multitrack_producer( this ) );
335 for ( i = 0; i < count - 2; i ++ )
336 if ( position >= map[ i ] && position < map[ i + 1 ] )
337 break;
338 index += i;
339 if ( index >= 0 && index < count )
340 position = map[ index ];
341 else if ( index < 0 )
342 position = map[ 0 ];
343 else
344 position = map[ count - 1 ];
345 break;
346
347 case mlt_whence_relative_end:
348 if ( index < count )
349 position = map[ count - index - 1 ];
350 else
351 position = map[ 0 ];
352 break;
353 }
354
355 // Free the map
356 free( map );
357
358 return position;
359 }
360
361 /** Get frame method.
362
363 Special case here: The multitrack must be used in a conjunction with a downstream
364 tractor-type service, ie:
365
366 Producer1 \
367 Producer2 - multitrack - { filters/transitions } - tractor - consumer
368 Producer3 /
369
370 The get_frame of a tractor pulls frames from it's connected service on all tracks and
371 will terminate as soon as it receives a test card with a last_track property. The
372 important case here is that the mulitrack does not move to the next frame until all
373 tracks have been pulled.
374
375 Reasoning: In order to seek on a network such as above, the multitrack needs to ensure
376 that all producers are positioned on the same frame. It uses the 'last track' logic
377 to determine when to move to the next frame.
378
379 Flaw: if a transition is configured to read from a b-track which happens to trigger
380 the last frame logic (ie: it's configured incorrectly), then things are going to go
381 out of sync.
382
383 See playlist logic too.
384 */
385
386 static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index )
387 {
388 // Get the mutiltrack object
389 mlt_multitrack this = parent->child;
390
391 // Check if we have a track for this index
392 if ( index < this->count && this->list[ index ] != NULL )
393 {
394 // Get the producer for this track
395 mlt_producer producer = this->list[ index ]->producer;
396
397 // Obtain the current position
398 mlt_position position = mlt_producer_frame( parent );
399
400 // Make sure we're at the same point
401 mlt_producer_seek( producer, position );
402
403 // Get the frame from the producer
404 mlt_service_get_frame( mlt_producer_service( producer ), frame, 0 );
405
406 // Indicate speed of this producer
407 mlt_properties producer_properties = mlt_producer_properties( parent );
408 double speed = mlt_properties_get_double( producer_properties, "_speed" );
409 mlt_properties properties = mlt_frame_properties( *frame );
410 mlt_properties_set_double( properties, "_speed", speed );
411 mlt_properties_set_position( properties, "_position", position );
412 mlt_properties_set_int( properties, "hide", mlt_properties_get_int( mlt_producer_properties( producer ), "hide" ) );
413 }
414 else
415 {
416 // Generate a test frame
417 *frame = mlt_frame_init( );
418
419 // Update position on the frame we're creating
420 mlt_frame_set_position( *frame, mlt_producer_position( parent ) );
421
422 // Move on to the next frame
423 if ( index >= this->count )
424 {
425 // Let tractor know if we've reached the end
426 mlt_properties_set_int( mlt_frame_properties( *frame ), "last_track", 1 );
427
428 // Move to the next frame
429 mlt_producer_prepare_next( parent );
430 }
431 }
432
433 return 0;
434 }
435
436 /** Close this instance.
437 */
438
439 void mlt_multitrack_close( mlt_multitrack this )
440 {
441 if ( this != NULL && mlt_properties_dec_ref( mlt_multitrack_properties( this ) ) <= 0 )
442 {
443 int i = 0;
444 for ( i = 0; i < this->count; i ++ )
445 {
446 if ( this->list[ i ] != NULL )
447 {
448 mlt_event_close( this->list[ i ]->event );
449 mlt_producer_close( this->list[ i ]->producer );
450 free( this->list[ i ] );
451 }
452 }
453
454 // Close the producer
455 mlt_producer_close( &this->parent );
456
457 // Free the list
458 free( this->list );
459
460 // Free the object
461 free( this );
462 }
463 }