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