more complete next/prev clip behaviour
[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 /** Private definition.
31 */
32
33 struct mlt_multitrack_s
34 {
35 // We're extending producer here
36 struct mlt_producer_s parent;
37 mlt_producer *list;
38 int size;
39 int count;
40 };
41
42 /** Forward reference.
43 */
44
45 static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index );
46
47 /** Constructor.
48 */
49
50 mlt_multitrack mlt_multitrack_init( )
51 {
52 // Allocate the multitrack object
53 mlt_multitrack this = calloc( sizeof( struct mlt_multitrack_s ), 1 );
54
55 if ( this != NULL )
56 {
57 mlt_producer producer = &this->parent;
58 if ( mlt_producer_init( producer, this ) == 0 )
59 {
60 mlt_properties properties = mlt_multitrack_properties( this );
61 producer->get_frame = producer_get_frame;
62 mlt_properties_set_data( properties, "multitrack", this, 0, NULL, NULL );
63 mlt_properties_set( properties, "log_id", "multitrack" );
64 }
65 else
66 {
67 free( this );
68 this = NULL;
69 }
70 }
71
72 return this;
73 }
74
75 /** Get the producer associated to this multitrack.
76 */
77
78 mlt_producer mlt_multitrack_producer( mlt_multitrack this )
79 {
80 return &this->parent;
81 }
82
83 /** Get the service associated this multitrack.
84 */
85
86 mlt_service mlt_multitrack_service( mlt_multitrack this )
87 {
88 return mlt_producer_service( mlt_multitrack_producer( this ) );
89 }
90
91 /** Get the properties associated this multitrack.
92 */
93
94 mlt_properties mlt_multitrack_properties( mlt_multitrack this )
95 {
96 return mlt_service_properties( mlt_multitrack_service( this ) );
97 }
98
99 /** Initialise timecode related information.
100 */
101
102 void mlt_multitrack_refresh( mlt_multitrack this )
103 {
104 int i = 0;
105
106 // Obtain the properties of this multitrack
107 mlt_properties properties = mlt_multitrack_properties( this );
108
109 // We need to ensure that the multitrack reports the longest track as its length
110 mlt_timecode length = 0;
111
112 // We need to ensure that fps are the same on all services
113 double fps = 0;
114
115 // Obtain stats on all connected services
116 for ( i = 0; i < this->count; i ++ )
117 {
118 // Get the producer from this index
119 mlt_producer producer = this->list[ i ];
120
121 // If it's allocated then, update our stats
122 if ( producer != NULL )
123 {
124 // Determine the longest length
125 length = mlt_producer_get_playtime( producer ) > length ? mlt_producer_get_playtime( producer ) : length;
126
127 // Handle fps
128 if ( fps == 0 )
129 {
130 // This is the first producer, so it controls the fps
131 fps = mlt_producer_get_fps( producer );
132 }
133 else if ( fps != mlt_producer_get_fps( producer ) )
134 {
135 // Generate a warning for now - the following attempt to fix may fail
136 fprintf( stderr, "Warning: fps mismatch on track %d\n", i );
137
138 // It should be safe to impose fps on an image producer, but not necessarily safe for video
139 mlt_properties_set_double( mlt_producer_properties( producer ), "fps", fps );
140 }
141 }
142 }
143
144 // Update multitrack properties now - we'll not destroy the in point here
145 mlt_properties_set_timecode( properties, "length", length );
146 mlt_properties_set_timecode( properties, "out", length );
147 mlt_properties_set_double( properties, "fps", fps );
148 }
149
150 /** Connect a producer to a given track.
151
152 Note that any producer can be connected here, but see special case treatment
153 of playlist in clip point determination below.
154 */
155
156 int mlt_multitrack_connect( mlt_multitrack this, mlt_producer producer, int track )
157 {
158 // Connect to the producer to ourselves at the specified track
159 int result = mlt_service_connect_producer( mlt_multitrack_service( this ), mlt_producer_service( producer ), track );
160
161 if ( result == 0 )
162 {
163 // Resize the producer list if need be
164 if ( track >= this->size )
165 {
166 int i;
167 this->list = realloc( this->list, ( track + 10 ) * sizeof( mlt_producer ) );
168 for ( i = this->size; i < track + 10; i ++ )
169 this->list[ i ] = NULL;
170 this->size = track + 10;
171 }
172
173 // Assign the track in our list here
174 this->list[ track ] = producer;
175
176 // Increment the track count if need be
177 if ( track >= this->count )
178 this->count = track + 1;
179
180 // Refresh our stats
181 mlt_multitrack_refresh( this );
182 }
183
184 return result;
185 }
186
187 /** Determine the clip point.
188
189 Special case here: a 'producer' has no concept of multiple clips - only the
190 playlist and multitrack producers have clip functionality. Further to that a
191 multitrack determines clip information from any connected tracks that happen
192 to be playlists.
193
194 Additionally, it must locate clips in the correct order, for example, consider
195 the following track arrangement:
196
197 playlist1 |0.0 |b0.0 |0.1 |0.1 |0.2 |
198 playlist2 |b1.0 |1.0 |b1.1 |1.1 |
199
200 Note - b clips represent blanks. They are also reported as clip positions.
201
202 When extracting clip positions from these playlists, we should get a sequence of:
203
204 0.0, 1.0, b0.0, 0.1, b1.1, 1.1, 0.1, 0.2, [out of playlist2], [out of playlist1]
205 */
206
207 mlt_timecode mlt_multitrack_clip( mlt_multitrack this, mlt_whence whence, int index )
208 {
209 int first = 1;
210 mlt_timecode position = 0;
211 int i = 0;
212
213 // Loop through each of the tracks
214 for ( i = 0; i < this->count; i ++ )
215 {
216 // Get the producer for this track
217 mlt_producer producer = this->list[ i ];
218
219 // If it's assigned...
220 if ( producer != NULL )
221 {
222 // Get the properties of this producer
223 mlt_properties properties = mlt_producer_properties( producer );
224
225 // Determine if it's a playlist
226 mlt_playlist playlist = mlt_properties_get_data( properties, "playlist", NULL );
227
228 // We only consider playlists
229 if ( playlist != NULL )
230 {
231 // Locate the smallest timecode
232 if ( first )
233 {
234 // First position found
235 position = mlt_playlist_clip( playlist, whence, index );
236
237 // We're no longer first
238 first = 0;
239 }
240 else
241 {
242 // Obtain the clip position in this playlist
243 mlt_timecode position2 = mlt_playlist_clip( playlist, whence, index );
244
245 // If this position is prior to the first, then use it
246 if ( position2 < position )
247 position = position2;
248 }
249 }
250 }
251 }
252
253 return position;
254 }
255
256 /** Get frame method.
257
258 Special case here: The multitrack must be used in a conjunction with a downstream
259 tractor-type service, ie:
260
261 Producer1 \
262 Producer2 - multitrack - { filters/transitions } - tractor - consumer
263 Producer3 /
264
265 The get_frame of a tractor pulls frames from it's connected service on all tracks and
266 will terminate as soon as it receives a test card with a last_track property. The
267 important case here is that the mulitrack does not move to the next frame until all
268 tracks have been pulled.
269
270 Reasoning: In order to seek on a network such as above, the multitrack needs to ensure
271 that all producers are positioned on the same frame. It uses the 'last track' logic
272 to determine when to move to the next frame.
273
274 Flaw: if a transition is configured to read from a b-track which happens to trigger
275 the last frame logic (ie: it's configured incorrectly), then things are going to go
276 out of sync.
277
278 See playlist logic too.
279 */
280
281 static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index )
282 {
283 // Get the mutiltrack object
284 mlt_multitrack this = parent->child;
285
286 // Check if we have a track for this index
287 if ( index < this->count && this->list[ index ] != NULL )
288 {
289 // Get the producer for this track
290 mlt_producer producer = this->list[ index ];
291
292 // Obtain the current timecode
293 uint64_t position = mlt_producer_frame( parent );
294
295 // Make sure we're at the same point
296 mlt_producer_seek_frame( producer, position );
297
298 // Get the frame from the producer
299 mlt_service_get_frame( mlt_producer_service( producer ), frame, 0 );
300
301 // Indicate speed of this producer
302 mlt_properties producer_properties = mlt_producer_properties( parent );
303 double speed = mlt_properties_get_double( producer_properties, "speed" );
304 mlt_properties properties = mlt_frame_properties( *frame );
305 mlt_properties_set_double( properties, "speed", speed );
306 }
307 else
308 {
309 // Generate a test frame
310 *frame = mlt_frame_init( );
311
312 // Update timecode on the frame we're creating
313 mlt_frame_set_timecode( *frame, mlt_producer_position( parent ) );
314
315 // Move on to the next frame
316 if ( index >= this->count )
317 {
318 // Let tractor know if we've reached the end
319 mlt_properties_set_int( mlt_frame_properties( *frame ), "last_track", 1 );
320
321 // Move to the next frame
322 mlt_producer_prepare_next( parent );
323 }
324
325 // Refresh our stats
326 mlt_multitrack_refresh( this );
327 }
328
329 return 0;
330 }
331
332 /** Close this instance.
333 */
334
335 void mlt_multitrack_close( mlt_multitrack this )
336 {
337 // Close the producer
338 mlt_producer_close( &this->parent );
339
340 // Free the list
341 free( this->list );
342
343 // Free the object
344 free( this );
345 }