X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fframework%2Fmlt_transition.c;h=86a57d4ea4cf30ab5b4ce444986940e7cf0f07a9;hb=6f8293d53fc21742d9d43f94f0e590e1bfcb8ee0;hp=dd4f2f64dfe2069a5c921a314bf71e7072c8f9d0;hpb=661165812e3410fe2f6f49d7af882b36a0efcf82;p=mlt diff --git a/src/framework/mlt_transition.c b/src/framework/mlt_transition.c index dd4f2f64..86a57d4e 100644 --- a/src/framework/mlt_transition.c +++ b/src/framework/mlt_transition.c @@ -1,232 +1,538 @@ -/* - * mlt_transition.c -- abstraction for all transition services - * Copyright (C) 2003-2004 Ushodaya Enterprises Limited - * Author: Charles Yates +/** + * \file mlt_transition.c + * \brief abstraction for all transition services + * \see mlt_transition_s * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * Copyright (C) 2003-2009 Ushodaya Enterprises Limited + * \author Charles Yates * - * This program is distributed in the hope that it will be useful, + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "config.h" - #include "mlt_transition.h" #include "mlt_frame.h" +#include "mlt_log.h" +#include "mlt_producer.h" #include #include #include -/** Forward references. -*/ +/* Forward references */ -static int transition_get_frame( mlt_service this, mlt_frame_ptr frame, int index ); +static int transition_get_frame( mlt_service self, mlt_frame_ptr frame, int index ); -/** Constructor. -*/ +/** Initialize a new transition. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \param child the object of a subclass + * \return true on error + */ -int mlt_transition_init( mlt_transition this, void *child ) +int mlt_transition_init( mlt_transition self, void *child ) { - mlt_service service = &this->parent; - memset( this, 0, sizeof( struct mlt_transition_s ) ); - this->child = child; - if ( mlt_service_init( service, this ) == 0 ) + mlt_service service = &self->parent; + memset( self, 0, sizeof( struct mlt_transition_s ) ); + self->child = child; + if ( mlt_service_init( service, self ) == 0 ) { + mlt_properties properties = MLT_TRANSITION_PROPERTIES( self ); + service->get_frame = transition_get_frame; + service->close = ( mlt_destructor )mlt_transition_close; + service->close_object = self; + + mlt_properties_set_position( properties, "in", 0 ); + mlt_properties_set_position( properties, "out", 0 ); + mlt_properties_set_int( properties, "a_track", 0 ); + mlt_properties_set_int( properties, "b_track", 1 ); + return 0; } return 1; } -/** Get the service associated to the transition. -*/ +/** Create and initialize a new transition. + * + * \public \memberof mlt_transition_s + * \return a new transition + */ -mlt_service mlt_transition_service( mlt_transition this ) +mlt_transition mlt_transition_new( ) { - return &this->parent; + mlt_transition self = calloc( 1, sizeof( struct mlt_transition_s ) ); + if ( self != NULL ) + mlt_transition_init( self, NULL ); + return self; } -/** Connect this transition with a producers a and b tracks. -*/ +/** Get the service class interface. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \return the service class + * \see MLT_TRANSITION_SERVICE + */ + +mlt_service mlt_transition_service( mlt_transition self ) +{ + return self != NULL ? &self->parent : NULL; +} + +/** Get the properties interface. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \return the transition's properties + * \see MLT_TRANSITION_PROPERTIES + */ -int mlt_transition_connect( mlt_transition this, mlt_service producer, int a_track, int b_track ) +mlt_properties mlt_transition_properties( mlt_transition self ) { - int ret = mlt_service_connect_producer( &this->parent, producer, a_track ); + return MLT_TRANSITION_PROPERTIES( self ); +} + +/** Connect a transition with a producer's a and b tracks. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \param producer a producer + * \param a_track the track index of the first input + * \param b_track the track index of the second index + * \return true on error + */ + +int mlt_transition_connect( mlt_transition self, mlt_service producer, int a_track, int b_track ) +{ + int ret = mlt_service_connect_producer( &self->parent, producer, a_track ); if ( ret == 0 ) { - this->producer = producer; - this->a_track = a_track; - this->b_track = b_track; - this->in = 0; - this->out = 0; + mlt_properties properties = MLT_TRANSITION_PROPERTIES( self ); + self->producer = producer; + mlt_properties_set_int( properties, "a_track", a_track ); + mlt_properties_set_int( properties, "b_track", b_track ); } return ret; } -/** Set the in and out points. -*/ +/** Set the starting and ending time for when the transition is active. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \param in the starting time + * \param out the ending time + */ -void mlt_transition_set_in_and_out( mlt_transition this, mlt_timecode in, mlt_timecode out ) +void mlt_transition_set_in_and_out( mlt_transition self, mlt_position in, mlt_position out ) { - this->in = in; - this->out = out; + mlt_properties properties = MLT_TRANSITION_PROPERTIES( self ); + mlt_properties_set_position( properties, "in", in ); + mlt_properties_set_position( properties, "out", out ); } /** Get the index of the a track. -*/ + * + * \public \memberof mlt_transition_s + * \param self a transition + * \return the 0-based index of the track of the first producer + */ -int mlt_transition_get_a_track( mlt_transition this ) +int mlt_transition_get_a_track( mlt_transition self ) { - return this->a_track; + return mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( self ), "a_track" ); } /** Get the index of the b track. -*/ + * + * \public \memberof mlt_transition_s + * \param self a transition + * \return the 0-based index of the track of the second producer + */ -int mlt_transition_get_b_track( mlt_transition this ) +int mlt_transition_get_b_track( mlt_transition self ) { - return this->b_track; + return mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( self ), "b_track" ); } /** Get the in point. -*/ + * + * \public \memberof mlt_transition_s + * \param self a transition + * \return the starting time + */ -mlt_timecode mlt_transition_get_in( mlt_transition this ) +mlt_position mlt_transition_get_in( mlt_transition self ) { - return this->in; + return mlt_properties_get_position( MLT_TRANSITION_PROPERTIES( self ), "in" ); } /** Get the out point. -*/ + * + * \public \memberof mlt_transition_s + * \param self a transition + * \return the ending time + */ -mlt_timecode mlt_transition_get_out( mlt_transition this ) +mlt_position mlt_transition_get_out( mlt_transition self ) { - return this->out; + return mlt_properties_get_position( MLT_TRANSITION_PROPERTIES( self ), "out" ); } -/** Process the frame. -*/ +/** Get the duration. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \return the duration or zero if unlimited + */ + +mlt_position mlt_transition_get_length( mlt_transition self ) +{ + mlt_properties properties = MLT_SERVICE_PROPERTIES( &self->parent ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + return ( out > 0 ) ? ( out - in + 1 ) : 0; +} + +/** Get the position within the transition. + * + * The position is relative to the in point. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \param frame a frame + * \return the position + */ + +mlt_position mlt_transition_get_position( mlt_transition self, mlt_frame frame ) +{ + mlt_position in = mlt_transition_get_in( self ); + mlt_position position = mlt_frame_get_position( frame ); + return position - in; +} + +/** Get the percent complete. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \param frame a frame + * \return the progress in the range 0.0 to 1.0 + */ -static mlt_frame transition_process( mlt_transition this, mlt_frame a_frame, mlt_frame b_frame ) +double mlt_transition_get_progress( mlt_transition self, mlt_frame frame ) { - if ( this->process == NULL ) + double progress = 0; + mlt_position in = mlt_transition_get_in( self ); + mlt_position out = mlt_transition_get_out( self ); + + if ( out == 0 ) { - if ( !mlt_frame_is_test_card( a_frame ) ) + // If always active, use the frame's producer + mlt_producer producer = mlt_frame_get_original_producer( frame ); + if ( producer ) { - mlt_frame_close( b_frame ); - return a_frame; + in = mlt_producer_get_in( producer ); + out = mlt_producer_get_out( producer ); } - else + } + if ( out != 0 ) + { + mlt_position position = mlt_frame_get_position( frame ); + progress = ( double ) ( position - in ) / ( double ) ( out - in + 1 ); + } + return progress; +} + +/** Get the second field incremental progress. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \param frame a frame + * \return the progress increment in the range 0.0 to 1.0 + */ + +double mlt_transition_get_progress_delta( mlt_transition self, mlt_frame frame ) +{ + double progress = 0; + mlt_position in = mlt_transition_get_in( self ); + mlt_position out = mlt_transition_get_out( self ); + + if ( out == 0 ) + { + // If always active, use the frame's producer + mlt_producer producer = mlt_frame_get_original_producer( frame ); + if ( producer ) { - mlt_frame_close( a_frame ); - return b_frame; + in = mlt_producer_get_in( producer ); + out = mlt_producer_get_out( producer ); } } + if ( out != 0 ) + { + mlt_position position = mlt_frame_get_position( frame ); + double length = out - in + 1; + double x = ( double ) ( position - in ) / length; + double y = ( double ) ( position + 1 - in ) / length; + progress = ( y - x ) / 2.0; + } + return progress; +} + +/** Process the frame. + * + * If we have no process method (unlikely), we simply return the a_frame unmolested. + * + * \public \memberof mlt_transition_s + * \param self a transition + * \param a_frame a frame from the first producer + * \param b_frame a frame from the second producer + * \return a frame + */ + +mlt_frame mlt_transition_process( mlt_transition self, mlt_frame a_frame, mlt_frame b_frame ) +{ + if ( self->process == NULL ) + return a_frame; else + return self->process( self, a_frame, b_frame ); +} + +static int get_image_a( mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_transition self = mlt_frame_pop_service( a_frame ); + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + + // All transitions get scaling + const char *rescale = mlt_properties_get( a_props, "rescale.interp" ); + if ( !rescale || !strcmp( rescale, "none" ) ) + mlt_properties_set( a_props, "rescale.interp", "nearest" ); + + // Ensure sane aspect ratio + if ( mlt_frame_get_aspect_ratio( a_frame ) == 0.0 ) + mlt_frame_set_aspect_ratio( a_frame, mlt_profile_sar( mlt_service_profile( MLT_TRANSITION_SERVICE(self) ) ) ); + + return mlt_frame_get_image( a_frame, image, format, width, height, writable ); +} + +static int get_image_b( mlt_frame b_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + mlt_transition self = mlt_frame_pop_service( b_frame ); + mlt_frame a_frame = mlt_frame_pop_frame( b_frame ); + mlt_properties a_props = MLT_FRAME_PROPERTIES( a_frame ); + mlt_properties b_props = MLT_FRAME_PROPERTIES( b_frame ); + + // Set scaling from A frame if not already provided. + if ( !mlt_properties_get( b_props, "rescale.interp" ) ) { - return this->process( this, a_frame, b_frame ); + const char *rescale = mlt_properties_get( a_props, "rescale.interp" ); + if ( !rescale || !strcmp( rescale, "none" ) ) + rescale = "nearest"; + mlt_properties_set( b_props, "rescale.interp", rescale ); } + + // Ensure sane aspect ratio + if ( mlt_frame_get_aspect_ratio( b_frame ) == 0.0 ) + mlt_frame_set_aspect_ratio( b_frame, mlt_profile_sar( mlt_service_profile( MLT_TRANSITION_SERVICE(self) ) ) ); + + mlt_properties_pass_list( b_props, a_props, + "consumer_deinterlace, deinterlace_method, consumer_tff" ); + + return mlt_frame_get_image( b_frame, image, format, width, height, writable ); } -/** Get a frame from this filter. +/** Get a frame from a transition. + + The logic is complex here. A transition is typically applied to frames on the a and + b tracks specified in the connect method above and only if both contain valid info + for the transition type (this is either audio or image). - The logic is complex here. A transition is applied to frames on the a and b tracks - specified in the connect method above. Since all frames are obtained via this - method for all tracks, we have to take special care that we only obtain the a and - b frames once - we do this on the first call to get a frame from either a or b. - - After that, we have 3 cases to resolve: - - 1) if the track is the a_track and we're in the time zone, then we need to call the - process method to do the effect on the frame (we assign NULL to the a_frame and - b_frames here) otherwise, we pass on the a_frame unmolested; - 2) if the track is the b_track and we're the in the time zone OR the b_frame is NULL, - then we generate a test card frame, otherwise we pass on the b frame unmolested; - 3) For all other tracks, we get the frames on demand. -*/ + However, the fixed a_track may not always contain data of the correct type, eg: +
+	+---------+                               +-------+
+	|c1       |                               |c5     | <-- A(0,1) <-- B(0,2) <-- get frame
+	+---------+                     +---------+-+-----+        |          |
+	                                |c4         |       <------+          |
+	         +----------+-----------+-+---------+                         |
+	         |c2        |c3           |                 <-----------------+
+	         +----------+-------------+
+
+ During the overlap of c1 and c2, there is nothing for the A transition to do, so this + results in a no operation, but B is triggered. During the overlap of c2 and c3, again, + the A transition is inactive and because the B transition is pointing at track 0, + it too would be inactive. This isn't an ideal situation - it's better if the B + transition simply treats the frames from c3 as though they're the a track. + + For this to work, we cache all frames coming from all tracks between the a and b + tracks. Before we process, we determine that the b frame contains someting of the + right type and then we determine which frame to use as the a frame (selecting a + matching frame from a_track to b_track - 1). If both frames contain data of the + correct type, we process the transition. + + This method is invoked for each track and we return the cached frames as needed. + We clear the cache only when the requested frame is flagged as a 'last_track' frame. + + * \private \memberof mlt_transition_s + * \param service a service + * \param[out] frame a frame by reference + * \param index 0-based track index + * \return true on error + */ static int transition_get_frame( mlt_service service, mlt_frame_ptr frame, int index ) { - mlt_transition this = service->child; + int error = 0; + mlt_transition self = service->child; - // Fetch a and b frames together... - if ( ( index == this->a_track || index == this->b_track ) && - ( this->a_frame == NULL && this->b_frame == NULL ) ) + mlt_properties properties = MLT_TRANSITION_PROPERTIES( self ); + + int accepts_blanks = mlt_properties_get_int( properties, "accepts_blanks" ); + int a_track = mlt_properties_get_int( properties, "a_track" ); + int b_track = mlt_properties_get_int( properties, "b_track" ); + mlt_position in = mlt_properties_get_position( properties, "in" ); + mlt_position out = mlt_properties_get_position( properties, "out" ); + int always_active = mlt_properties_get_int( properties, "always_active" ); + int type = mlt_properties_get_int( properties, "_transition_type" ); + int reverse_order = 0; + + // Ensure that we have the correct order + if ( a_track > b_track ) { - mlt_service_get_frame( this->producer, &this->a_frame, this->a_track ); - mlt_service_get_frame( this->producer, &this->b_frame, this->b_track ); + reverse_order = 1; + a_track = b_track; + b_track = mlt_properties_get_int( properties, "a_track" ); } - - // Special case track processing - if ( index == this->a_track ) + + // Only act on this operation once per multitrack iteration from the tractor + if ( !self->held ) { - // Determine if we're in the right time zone - mlt_timecode timecode = mlt_frame_get_timecode( this->a_frame ); - if ( timecode >= this->in && timecode < this->out ) + int active = 0; + int i = 0; + int a_frame = a_track; + int b_frame = b_track; + mlt_position position; + int ( *invalid )( mlt_frame ) = type == 1 ? mlt_frame_is_test_card : mlt_frame_is_test_audio; + + // Initialise temporary store + if ( self->frames == NULL ) + self->frames = calloc( sizeof( mlt_frame ), b_track + 1 ); + + // Get all frames between a and b + for( i = a_track; i <= b_track; i ++ ) + mlt_service_get_frame( self->producer, &self->frames[ i ], i ); + + // We're holding these frames until the last_track frame property is received + self->held = 1; + + // When we need to locate the a_frame + switch( type ) { - // Process the transition - *frame = transition_process( this, this->a_frame, this->b_frame ); - - // Important - NULL both frames now so that we know they're done... - this->a_frame = NULL; - this->b_frame = NULL; + case 1: + case 2: + // Some transitions (esp. audio) may accept blank frames + active = accepts_blanks; + + // If we're not active then... + if ( !active ) + { + // Hunt for the a_frame + while( a_frame <= b_frame && invalid( self->frames[ a_frame ] ) ) + a_frame ++; + + // Determine if we're active now + active = a_frame != b_frame && !invalid( self->frames[ b_frame ] ); + } + break; + + default: + mlt_log( service, MLT_LOG_ERROR, "invalid transition type\n" ); + break; } - else - { - // Pass on the 'a frame' and remember that we've done it - *frame = this->a_frame; - this->a_frame = NULL; - } - return 0; - } - if ( index == this->b_track ) - { - if ( this->b_frame == NULL ) + + // Now handle the non-always active case + if ( active && !always_active ) { - // We're *probably* in the zone and the a frame has been requested - *frame = mlt_frame_init( ); + // For non-always-active transitions, we need the current position of the a frame + position = mlt_frame_get_position( self->frames[ a_frame ] ); + + // If a is in range, we're active + active = position >= in && ( out == 0 || position <= out ); } - else + + // Finally, process the a and b frames + if ( active && !mlt_properties_get_int( MLT_TRANSITION_PROPERTIES( self ), "disable" ) ) { - mlt_timecode timecode = mlt_frame_get_timecode( this->b_frame ); - if ( timecode >= this->in && timecode < this->out ) - { - // We're in the zone, but the 'a frame' has not been requested yet - *frame = mlt_frame_init( ); - } - else + mlt_frame a_frame_ptr = self->frames[ !reverse_order ? a_frame : b_frame ]; + mlt_frame b_frame_ptr = self->frames[ !reverse_order ? b_frame : a_frame ]; + int a_hide = mlt_properties_get_int( MLT_FRAME_PROPERTIES( a_frame_ptr ), "hide" ); + int b_hide = mlt_properties_get_int( MLT_FRAME_PROPERTIES( b_frame_ptr ), "hide" ); + if ( !( a_hide & type ) && !( b_hide & type ) ) { - // We're out of the zone, pass on b and remember that we've done it - *frame = this->b_frame; - this->b_frame = NULL; + // Add hooks for pre-processing frames + mlt_frame_push_service( a_frame_ptr, self ); + mlt_frame_push_get_image( a_frame_ptr, get_image_a ); + mlt_frame_push_frame( b_frame_ptr, a_frame_ptr ); + mlt_frame_push_service( b_frame_ptr, self ); + mlt_frame_push_get_image( b_frame_ptr, get_image_b ); + + // Process the transition + *frame = mlt_transition_process( self, a_frame_ptr, b_frame_ptr ); + + // We need to ensure that the tractor doesn't consider this frame for output + if ( *frame == a_frame_ptr ) + b_hide |= type; + else + a_hide |= type; + + mlt_properties_set_int( MLT_FRAME_PROPERTIES( a_frame_ptr ), "hide", a_hide ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES( b_frame_ptr ), "hide", b_hide ); } } - return 0; } + + // Obtain the frame from the cache or the producer we're attached to + if ( index >= a_track && index <= b_track ) + *frame = self->frames[ index ]; else - { - // Pass through - return mlt_service_get_frame( this->producer, frame, index ); - } + error = mlt_service_get_frame( self->producer, frame, index ); + + // Determine if that was the last track + self->held = !mlt_properties_get_int( MLT_FRAME_PROPERTIES( *frame ), "last_track" ); + + return error; } -/** Close the transition. -*/ +/** Close and destroy the transition. + * + * \public \memberof mlt_transition_s + * \param self a transition + */ -void mlt_transitition_close( mlt_transition this ) +void mlt_transition_close( mlt_transition self ) { - if ( this->close != NULL ) - this->close( this ); - else - mlt_service_close( &this->parent ); + if ( self != NULL && mlt_properties_dec_ref( MLT_TRANSITION_PROPERTIES( self ) ) <= 0 ) + { + self->parent.close = NULL; + if ( self->close != NULL ) + { + self->close( self ); + } + else + { + mlt_service_close( &self->parent ); + free( self->frames ); + free( self ); + } + } }