1 /*
2  * Copyright (C) 2011-2021 Meltytech, LLC
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 #include <framework/mlt.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <pthread.h>
24 #include <sys/time.h>
25 
26 // Forward references
27 static int start( mlt_consumer consumer );
28 static int stop( mlt_consumer consumer );
29 static int is_stopped( mlt_consumer consumer );
30 static void *consumer_thread( void *arg );
31 static void consumer_close( mlt_consumer consumer );
32 static void purge( mlt_consumer consumer );
33 
34 static mlt_properties normalisers = NULL;
35 
36 /** Initialise the consumer.
37 */
38 
consumer_multi_init(mlt_profile profile,mlt_service_type type,const char * id,char * arg)39 mlt_consumer consumer_multi_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
40 {
41 	mlt_consumer consumer = mlt_consumer_new( profile );
42 
43 	if ( consumer )
44 	{
45 		mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer);
46 
47 		// Set defaults
48 		mlt_properties_set( properties, "resource", arg );
49 		mlt_properties_set_int( properties, "real_time", -1 );
50 		mlt_properties_set_int( properties, "terminate_on_pause", 1 );
51 
52 		// Init state
53 		mlt_properties_set_int( properties, "joined", 1 );
54 
55 		// Assign callbacks
56 		consumer->close = consumer_close;
57 		consumer->start = start;
58 		consumer->stop = stop;
59 		consumer->is_stopped = is_stopped;
60 		consumer->purge = purge;
61 	}
62 
63 	return consumer;
64 }
65 
create_consumer(mlt_profile profile,char * id,char * arg)66 static mlt_consumer create_consumer( mlt_profile profile, char *id, char *arg )
67 {
68 	char *myid = id ? strdup( id ) : NULL;
69 	char *myarg = ( myid && !arg ) ? strchr( myid, ':' ) : NULL;
70 	if ( myarg )
71 		*myarg ++ = '\0';
72 	else
73 		myarg = arg;
74 	mlt_consumer consumer = mlt_factory_consumer( profile, myid, myarg );
75 	free( myid );
76 	return consumer;
77 }
78 
create_filter(mlt_profile profile,mlt_service service,char * effect,int * created)79 static void create_filter( mlt_profile profile, mlt_service service, char *effect, int *created )
80 {
81 	char *id = strdup( effect );
82 	char *arg = strchr( id, ':' );
83 	if ( arg != NULL )
84 		*arg ++ = '\0';
85 
86 	// We cannot use GLSL-based filters here.
87 	if ( strncmp( effect, "movit.", 6 ) && strncmp( effect, "glsl.", 5 ) )
88 	{
89 		mlt_filter filter;
90 		// The swscale and avcolor_space filters require resolution as arg to test compatibility
91 		if ( strncmp( effect, "swscale", 7 ) == 0 || strncmp( effect, "avcolo", 6 ) == 0 )
92 		{
93 			int width = mlt_properties_get_int( MLT_SERVICE_PROPERTIES( service ), "meta.media.width" );
94 			filter = mlt_factory_filter( profile, id, &width );
95 		}
96 		else
97 		{
98 			filter = mlt_factory_filter( profile, id, arg );
99 		}
100 		if ( filter )
101 		{
102 			mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "_loader", 1 );
103 			mlt_service_attach( service, filter );
104 			mlt_filter_close( filter );
105 			*created = 1;
106 		}
107 	}
108 	free( id );
109 }
110 
attach_normalisers(mlt_profile profile,mlt_service service)111 static void attach_normalisers( mlt_profile profile, mlt_service service )
112 {
113 	// Loop variable
114 	int i;
115 
116 	// Tokeniser
117 	mlt_tokeniser tokeniser = mlt_tokeniser_init( );
118 
119 	// We only need to load the normalising properties once
120 	if ( normalisers == NULL )
121 	{
122 		char temp[ 1024 ];
123 		snprintf( temp, sizeof(temp), "%s/core/loader.ini", mlt_environment( "MLT_DATA" ) );
124 		normalisers = mlt_properties_load( temp );
125 		mlt_factory_register_for_clean_up( normalisers, ( mlt_destructor )mlt_properties_close );
126 	}
127 
128 	// Apply normalisers
129 	for ( i = 0; i < mlt_properties_count( normalisers ); i ++ )
130 	{
131 		int j = 0;
132 		int created = 0;
133 		char *value = mlt_properties_get_value( normalisers, i );
134 		mlt_tokeniser_parse_new( tokeniser, value, "," );
135 		for ( j = 0; !created && j < mlt_tokeniser_count( tokeniser ); j ++ )
136 			create_filter( profile, service, mlt_tokeniser_get_string( tokeniser, j ), &created );
137 	}
138 
139 	// Close the tokeniser
140 	mlt_tokeniser_close( tokeniser );
141 
142 	// Attach the audio and video format converters
143 	int created = 0;
144 	// movit.convert skips setting the frame->convert_image pointer if GLSL cannot be used.
145 	mlt_filter filter = mlt_factory_filter( profile, "movit.convert", NULL );
146 	if ( filter != NULL )
147 	{
148 		mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "_loader", 1 );
149 		mlt_service_attach( service, filter );
150 		mlt_filter_close( filter );
151 		created = 1;
152 	}
153 	// avcolor_space and imageconvert only set frame->convert_image if it has not been set.
154 	create_filter( profile, service, "avcolor_space", &created );
155 	if ( !created )
156 		create_filter( profile, service, "imageconvert", &created );
157 	create_filter( profile, service, "audioconvert", &created );
158 }
159 
on_frame_show(void * dummy,mlt_properties properties,mlt_event_data event_data)160 static void on_frame_show( void *dummy, mlt_properties properties, mlt_event_data event_data )
161 {
162 	mlt_events_fire( properties, "consumer-frame-show", event_data );
163 }
164 
generate_consumer(mlt_consumer consumer,mlt_properties props,int index)165 static mlt_consumer generate_consumer( mlt_consumer consumer, mlt_properties props, int index )
166 {
167 	mlt_profile profile = NULL;
168 	if ( mlt_properties_get( props, "mlt_profile" ) )
169 		profile = mlt_profile_init( mlt_properties_get( props, "mlt_profile" ) );
170 	if ( !profile )
171 		profile = mlt_profile_clone( mlt_service_profile( MLT_CONSUMER_SERVICE(consumer) ) );
172 	mlt_consumer nested = create_consumer( profile, mlt_properties_get( props, "mlt_service" ),
173 		mlt_properties_get( props, "target" ) );
174 
175 	if ( nested )
176 	{
177 		mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer);
178 		mlt_properties nested_props = MLT_CONSUMER_PROPERTIES(nested);
179 		char key[30];
180 
181 		snprintf( key, sizeof(key), "%d.consumer", index );
182 		mlt_properties_set_data( properties, key, nested, 0, (mlt_destructor) mlt_consumer_close, NULL );
183 		snprintf( key, sizeof(key), "%d.profile", index );
184 		mlt_properties_set_data( properties, key, profile, 0, (mlt_destructor) mlt_profile_close, NULL );
185 
186 		mlt_properties_set_int( nested_props, "put_mode", 1 );
187 		mlt_properties_pass_list( nested_props, properties, "terminate_on_pause" );
188 		mlt_properties_set( props, "consumer", NULL );
189 		// set mlt_profile before other properties to facilitate presets
190 		mlt_properties_pass_list( nested_props, props, "mlt_profile" );
191 		mlt_properties_inherit( nested_props, props );
192 
193 		attach_normalisers( profile, MLT_CONSUMER_SERVICE(nested) );
194 
195 		// Relay the first available consumer-frame-show event
196 		mlt_event event = mlt_properties_get_data( properties, "frame-show-event", NULL );
197 		if ( !event )
198 		{
199 			event = mlt_events_listen( nested_props, properties, "consumer-frame-show", (mlt_listener) on_frame_show );
200 			mlt_properties_set_data( properties, "frame-show-event", event, 0, /*mlt_event_close*/ NULL, NULL );
201 		}
202 	}
203 	else
204 	{
205 		mlt_profile_close( profile );
206 	}
207 	return nested;
208 }
209 
foreach_consumer_init(mlt_consumer consumer)210 static void foreach_consumer_init( mlt_consumer consumer )
211 {
212 	const char *resource = mlt_properties_get( MLT_CONSUMER_PROPERTIES(consumer), "resource" );
213 	mlt_properties properties = mlt_properties_parse_yaml( resource );
214 	char key[20];
215 	int index = 0;
216 
217 	if ( mlt_properties_get_data( MLT_CONSUMER_PROPERTIES(consumer), "0", NULL ) )
218 	{
219 		// Properties set directly by application
220 		mlt_properties p;
221 
222 		if ( properties )
223 			mlt_properties_close( properties );
224 		properties = MLT_CONSUMER_PROPERTIES(consumer);
225 		do {
226 			snprintf( key, sizeof(key), "%d", index );
227 			if ( ( p = mlt_properties_get_data( properties, key, NULL ) ) )
228 				generate_consumer( consumer, p, index++ );
229 		} while ( p );
230 	}
231 	else if ( properties && mlt_properties_get_data( properties, "0", NULL ) )
232 	{
233 		// YAML file supplied
234 		mlt_properties p;
235 
236 		do {
237 			snprintf( key, sizeof(key), "%d", index );
238 			if ( ( p = mlt_properties_get_data( properties, key, NULL ) ) )
239 				generate_consumer( consumer, p, index++ );
240 		} while ( p );
241 		mlt_properties_close( properties );
242 	}
243 	else
244 	{
245 		// properties file supplied or properties on this consumer
246 		const char *s;
247 
248 		if ( properties )
249 			mlt_properties_close( properties );
250 		if ( resource )
251 			properties = mlt_properties_load( resource );
252 		else
253 			properties = MLT_CONSUMER_PROPERTIES( consumer );
254 
255 		do {
256 			snprintf( key, sizeof(key), "%d", index );
257 			if ( ( s = mlt_properties_get( properties, key ) ) )
258 			{
259 				mlt_properties p = mlt_properties_new();
260 				if ( !p ) break;
261 
262 				// Terminate mlt_service value at the argument delimiter if supplied.
263 				// Needed here instead of just relying upon create_consumer() so that
264 				// a properties preset is picked up correctly.
265 				char *service = strdup( mlt_properties_get( properties, key ) );
266 				char *arg = strchr( service, ':' );
267 				if ( arg ) {
268 					*arg ++ = '\0';
269 					mlt_properties_set( p, "target", arg );
270 				}
271 				mlt_properties_set( p, "mlt_service", service );
272 				free( service );
273 
274 				snprintf( key, sizeof(key), "%d.", index );
275 
276 				int i, count;
277 				count = mlt_properties_count( properties );
278 				for ( i = 0; i < count; i++ )
279 				{
280 					char *name = mlt_properties_get_name( properties, i );
281 					if ( !strncmp( name, key, strlen(key) ) )
282 						mlt_properties_set( p, name + strlen(key),
283 							mlt_properties_get_value( properties, i ) );
284 				}
285 				generate_consumer( consumer, p, index++ );
286 				mlt_properties_close( p );
287 			}
288 		} while ( s );
289 		if ( resource )
290 			mlt_properties_close( properties );
291 	}
292 }
293 
foreach_consumer_start(mlt_consumer consumer)294 static void foreach_consumer_start( mlt_consumer consumer )
295 {
296 	mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
297 	mlt_consumer nested = NULL;
298 	char key[30];
299 	int index = 0;
300 
301 	do {
302 		snprintf( key, sizeof(key), "%d.consumer", index++ );
303 		nested = mlt_properties_get_data( properties, key, NULL );
304 		if ( nested )
305 		{
306 			mlt_properties nested_props = MLT_CONSUMER_PROPERTIES(nested);
307 			mlt_properties_set_position( nested_props, "_multi_position", mlt_properties_get_position( properties, "in" ) );
308 			mlt_properties_set_data( nested_props, "_multi_audio", NULL, 0, NULL, NULL );
309 			mlt_properties_set_int( nested_props, "_multi_samples", 0 );
310 			mlt_consumer_start( nested );
311 		}
312 	} while ( nested );
313 }
314 
foreach_consumer_refresh(mlt_consumer consumer)315 static void foreach_consumer_refresh( mlt_consumer consumer )
316 {
317 	mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
318 	mlt_consumer nested = NULL;
319 	char key[30];
320 	int index = 0;
321 
322 	do {
323 		snprintf( key, sizeof(key), "%d.consumer", index++ );
324 		nested = mlt_properties_get_data( properties, key, NULL );
325 		if ( nested ) mlt_properties_set_int( MLT_CONSUMER_PROPERTIES(nested), "refresh", 1 );
326 	} while ( nested );
327 }
328 
329 // Update certain properties on this consumer from the child consumers.
foreach_consumer_update(mlt_consumer consumer)330 static void foreach_consumer_update( mlt_consumer consumer )
331 {
332 	mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
333 	mlt_consumer nested = NULL;
334 	char key[30];
335 	int index = 0;
336 
337 	do {
338 		snprintf( key, sizeof(key), "%d.consumer", index++ );
339 		nested = mlt_properties_get_data( properties, key, NULL );
340 		if ( nested )
341 			mlt_properties_pass_list( properties, MLT_CONSUMER_PROPERTIES(nested), "color_trc" );
342 	} while ( nested );
343 }
344 
foreach_consumer_put(mlt_consumer consumer,mlt_frame frame)345 static void foreach_consumer_put( mlt_consumer consumer, mlt_frame frame )
346 {
347 	mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
348 	mlt_consumer nested = NULL;
349 	char key[30];
350 	int index = 0;
351 
352 	do {
353 		snprintf( key, sizeof(key), "%d.consumer", index++ );
354 		nested = mlt_properties_get_data( properties, key, NULL );
355 		if ( nested )
356 		{
357 			mlt_properties nested_props = MLT_CONSUMER_PROPERTIES(nested);
358 			double self_fps = mlt_properties_get_double( properties, "fps" );
359 			double nested_fps = mlt_properties_get_double( nested_props, "fps" );
360 			mlt_position nested_pos = mlt_properties_get_position( nested_props, "_multi_position" );
361 			mlt_position self_pos = mlt_frame_get_position( frame );
362 			double self_time = self_pos / self_fps;
363 			double nested_time = nested_pos / nested_fps;
364 
365 			// get the audio for the current frame
366 			uint8_t *buffer = NULL;
367 			mlt_audio_format format = mlt_audio_s16;
368 			int channels = mlt_properties_get_int( properties, "channels" );
369 			int frequency = mlt_properties_get_int( properties, "frequency" );
370 			int current_samples = mlt_audio_calculate_frame_samples( self_fps, frequency, self_pos );
371 			mlt_frame_get_audio( frame, (void**) &buffer, &format, &frequency, &channels, &current_samples );
372 			int current_size = mlt_audio_format_size( format, current_samples, channels );
373 
374 			// get any leftover audio
375 			int prev_size = 0;
376 			uint8_t *prev_buffer = mlt_properties_get_data( nested_props, "_multi_audio", &prev_size );
377 			uint8_t *new_buffer = NULL;
378 			if ( prev_size > 0 )
379 			{
380 				new_buffer = mlt_pool_alloc( prev_size + current_size );
381 				memcpy( new_buffer, prev_buffer, prev_size );
382 				memcpy( new_buffer + prev_size, buffer, current_size );
383 				buffer = new_buffer;
384 			}
385 			current_size += prev_size;
386 			current_samples += mlt_properties_get_int( nested_props, "_multi_samples" );
387 
388 			while ( nested_time <= self_time )
389 			{
390 				// put ideal number of samples into cloned frame
391 				int deeply = index > 1 ? 1 : 0;
392 				mlt_frame clone_frame = mlt_frame_clone( frame, deeply );
393 				mlt_properties clone_props = MLT_FRAME_PROPERTIES( clone_frame );
394 				int nested_samples = mlt_audio_calculate_frame_samples( nested_fps, frequency, nested_pos );
395 				// -10 is an optimization to avoid tiny amounts of leftover samples
396 				nested_samples = nested_samples > current_samples - 10 ? current_samples : nested_samples;
397 				int nested_size = mlt_audio_format_size( format, nested_samples, channels );
398 				if ( nested_size > 0 )
399 				{
400 					prev_buffer = mlt_pool_alloc( nested_size );
401 					memcpy( prev_buffer, buffer, nested_size );
402 				}
403 				else
404 				{
405 					prev_buffer = NULL;
406 					nested_size = 0;
407 				}
408 				mlt_frame_set_audio( clone_frame, prev_buffer, format, nested_size, mlt_pool_release );
409 				mlt_properties_set_int( clone_props, "audio_samples", nested_samples );
410 				mlt_properties_set_int( clone_props, "audio_frequency", frequency );
411 				mlt_properties_set_int( clone_props, "audio_channels", channels );
412 
413 				// chomp the audio
414 				current_samples -= nested_samples;
415 				current_size -= nested_size;
416 				buffer += nested_size;
417 
418 				// Fix some things
419 				mlt_properties_set_int( clone_props, "meta.media.width",
420 					mlt_properties_get_int( MLT_FRAME_PROPERTIES(frame), "width" ) );
421 				mlt_properties_set_int( clone_props, "meta.media.height",
422 					mlt_properties_get_int( MLT_FRAME_PROPERTIES(frame), "height" ) );
423 
424 				// send frame to nested consumer
425 				mlt_consumer_put_frame( nested, clone_frame );
426 				mlt_properties_set_position( nested_props, "_multi_position", ++nested_pos );
427 				nested_time = nested_pos / nested_fps;
428 			}
429 
430 			// save any remaining audio
431 			if ( current_size > 0 )
432 			{
433 				prev_buffer = mlt_pool_alloc( current_size );
434 				memcpy( prev_buffer, buffer, current_size );
435 			}
436 			else
437 			{
438 				prev_buffer = NULL;
439 				current_size = 0;
440 			}
441 			mlt_pool_release( new_buffer );
442 			mlt_properties_set_data( nested_props, "_multi_audio", prev_buffer, current_size, mlt_pool_release, NULL );
443 			mlt_properties_set_int( nested_props, "_multi_samples", current_samples );
444 		}
445 	} while ( nested );
446 }
447 
foreach_consumer_stop(mlt_consumer consumer)448 static void foreach_consumer_stop( mlt_consumer consumer )
449 {
450 	mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
451 	mlt_consumer nested = NULL;
452 	char key[30];
453 	int index = 0;
454 	struct timespec tm = { 0, 1000 * 1000 };
455 
456 	do {
457 		snprintf( key, sizeof(key), "%d.consumer", index++ );
458 		nested = mlt_properties_get_data( properties, key, NULL );
459 		if ( nested )
460 		{
461 			// Let consumer with terminate_on_pause stop on their own
462 			if ( mlt_properties_get_int( MLT_CONSUMER_PROPERTIES(nested), "terminate_on_pause" ) )
463 			{
464 				// Send additional dummy frame to unlatch nested consumer's threads
465 				mlt_consumer_put_frame( nested, mlt_frame_init( MLT_CONSUMER_SERVICE(consumer) ) );
466 				// wait for stop
467 				while ( !mlt_consumer_is_stopped( nested ) )
468 					nanosleep( &tm, NULL );
469 			}
470 			else
471 			{
472 				mlt_consumer_stop( nested );
473 			}
474 		}
475 	} while ( nested );
476 }
477 
478 /** Start the consumer.
479 */
480 
start(mlt_consumer consumer)481 static int start( mlt_consumer consumer )
482 {
483 	// Check that we're not already running
484 	if ( is_stopped( consumer ) )
485 	{
486 		mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
487 		pthread_t *thread = calloc( 1, sizeof( pthread_t ) );
488 
489 		// Assign the thread to properties with automatic dealloc
490 		mlt_properties_set_data( properties, "thread", thread, sizeof( pthread_t ), free, NULL );
491 
492 		// Set the running state
493 		mlt_properties_set_int( properties, "running", 1 );
494 		mlt_properties_set_int( properties, "joined", 0 );
495 
496 		// Construct and start nested consumers
497 		if ( !mlt_properties_get_data( properties, "0.consumer", NULL ) )
498 			foreach_consumer_init( consumer );
499 		foreach_consumer_start( consumer );
500 
501 		// Create the thread
502 		pthread_create( thread, NULL, consumer_thread, consumer );
503 	}
504 	return 0;
505 }
506 
507 /** Stop the consumer.
508 */
509 
stop(mlt_consumer consumer)510 static int stop( mlt_consumer consumer )
511 {
512 	// Check that we're running
513 	if ( !mlt_properties_get_int( MLT_CONSUMER_PROPERTIES(consumer), "joined" ) )
514 	{
515 		mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
516 		pthread_t *thread = mlt_properties_get_data( properties, "thread", NULL );
517 
518 		// Stop the thread
519 		mlt_properties_set_int( properties, "running", 0 );
520 
521 		// Wait for termination
522 		if ( thread )
523 		{
524 			foreach_consumer_refresh( consumer );
525 			pthread_join( *thread, NULL );
526 		}
527 		mlt_properties_set_int( properties, "joined", 1 );
528 
529 		// Stop nested consumers
530 		foreach_consumer_stop( consumer );
531 	}
532 
533 	return 0;
534 }
535 
536 /** Determine if the consumer is stopped.
537 */
538 
is_stopped(mlt_consumer consumer)539 static int is_stopped( mlt_consumer consumer )
540 {
541 	return !mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "running" );
542 }
543 
544 /** Purge each of the child consumers.
545 */
546 
purge(mlt_consumer consumer)547 static void purge( mlt_consumer consumer )
548 {
549 	mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
550 	if ( mlt_properties_get_int( properties, "running" ) )
551 	{
552 		mlt_consumer nested = NULL;
553 		char key[30];
554 		int index = 0;
555 
556 		do {
557 			snprintf( key, sizeof(key), "%d.consumer", index++ );
558 			nested = mlt_properties_get_data( properties, key, NULL );
559 			mlt_consumer_purge( nested );
560 		} while ( nested );
561 	}
562 }
563 
564 /** The main thread - the argument is simply the consumer.
565 */
566 
consumer_thread(void * arg)567 static void *consumer_thread( void *arg )
568 {
569 	mlt_consumer consumer = arg;
570 	mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
571 	mlt_frame frame = NULL;
572 
573 	// Determine whether to stop at end-of-media
574 	int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" );
575 	int terminated = 0;
576 
577 	foreach_consumer_update( consumer );
578 
579 	// Loop while running
580 	while ( !terminated && !is_stopped( consumer ) )
581 	{
582 		// Get the next frame
583 		frame = mlt_consumer_rt_frame( consumer );
584 
585 		// Check for termination
586 		if ( terminate_on_pause && frame )
587 			terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0;
588 
589 		// Check that we have a frame to work with
590 		if ( frame && !terminated && !is_stopped( consumer ) )
591 		{
592 			if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES(frame), "rendered" ) )
593 			{
594 				if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES(frame), "_speed" ) == 0 )
595 					foreach_consumer_refresh( consumer );
596 				foreach_consumer_put( consumer, frame );
597 			}
598 			else
599 			{
600 				int dropped = mlt_properties_get_int( properties, "_dropped" );
601 				mlt_log_info( MLT_CONSUMER_SERVICE(consumer), "dropped frame %d\n", ++dropped );
602 				mlt_properties_set_int( properties, "_dropped", dropped );
603 			}
604 			mlt_frame_close( frame );
605 		}
606 		else
607 		{
608 			if ( frame && terminated )
609 			{
610 				// Send this termination frame to nested consumers for their cancellation
611 				foreach_consumer_put( consumer, frame );
612 			}
613 			if ( frame )
614 				mlt_frame_close( frame );
615 			terminated = 1;
616 		}
617 	}
618 
619 	// Indicate that the consumer is stopped
620 	mlt_consumer_stopped( consumer );
621 
622 	return NULL;
623 }
624 
625 /** Close the consumer.
626 */
627 
consumer_close(mlt_consumer consumer)628 static void consumer_close( mlt_consumer consumer )
629 {
630 	mlt_consumer_stop( consumer );
631 
632 	// Close the parent
633 	mlt_consumer_close( consumer );
634 	free( consumer );
635 }
636