int colorspace;
pthread_mutex_t video_mutex;
pthread_mutex_t audio_mutex;
+ mlt_deque apackets;
+ mlt_deque vpackets;
+ pthread_mutex_t packets_mutex;
#ifdef VDPAU
struct
{
typedef struct producer_avformat_s *producer_avformat;
// Forward references.
-static int producer_open( producer_avformat self, mlt_profile profile, char *file );
+static int list_components( char* file );
+static int producer_open( producer_avformat self, mlt_profile profile, const char *URL );
static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index );
static void producer_avformat_close( producer_avformat );
static void producer_close( mlt_producer parent );
static void producer_set_up_audio( producer_avformat self, mlt_frame frame );
static void apply_properties( void *obj, mlt_properties properties, int flags );
static int video_codec_init( producer_avformat self, int index, mlt_properties properties );
+static void get_audio_streams_info( producer_avformat self );
#ifdef VDPAU
#include "vdpau.c"
mlt_producer producer_avformat_init( mlt_profile profile, const char *service, char *file )
{
- int skip = 0;
+ if ( list_components( file ) )
+ return NULL;
- // Report information about available demuxers and codecs as YAML Tiny
- if ( file && strstr( file, "f-list" ) )
- {
- fprintf( stderr, "---\nformats:\n" );
- AVInputFormat *format = NULL;
- while ( ( format = av_iformat_next( format ) ) )
- fprintf( stderr, " - %s\n", format->name );
- fprintf( stderr, "...\n" );
- skip = 1;
- }
- if ( file && strstr( file, "acodec-list" ) )
- {
- fprintf( stderr, "---\naudio_codecs:\n" );
- AVCodec *codec = NULL;
- while ( ( codec = av_codec_next( codec ) ) )
- if ( codec->decode && codec->type == CODEC_TYPE_AUDIO )
- fprintf( stderr, " - %s\n", codec->name );
- fprintf( stderr, "...\n" );
- skip = 1;
- }
- if ( file && strstr( file, "vcodec-list" ) )
- {
- fprintf( stderr, "---\nvideo_codecs:\n" );
- AVCodec *codec = NULL;
- while ( ( codec = av_codec_next( codec ) ) )
- if ( codec->decode && codec->type == CODEC_TYPE_VIDEO )
- fprintf( stderr, " - %s\n", codec->name );
- fprintf( stderr, "...\n" );
- skip = 1;
- }
+ mlt_producer producer = NULL;
// Check that we have a non-NULL argument
- if ( !skip && file )
+ if ( file )
{
// Construct the producer
- mlt_producer producer = calloc( 1, sizeof( struct mlt_producer_s ) );
producer_avformat self = calloc( 1, sizeof( struct producer_avformat_s ) );
+ producer = calloc( 1, sizeof( struct mlt_producer_s ) );
// Initialise it
if ( mlt_producer_init( producer, self ) == 0 )
// Register our get_frame implementation
producer->get_frame = producer_get_frame;
-
+
if ( strcmp( service, "avformat-novalidate" ) )
{
// Open the file
mlt_producer_close( producer );
producer = NULL;
}
- else
+ else if ( self->seekable )
{
// Close the file to release resources for large playlists - reopen later as needed
avformat_lock();
- if ( self->dummy_context )
- av_close_input_file( self->dummy_context );
- self->dummy_context = NULL;
if ( self->audio_format )
av_close_input_file( self->audio_format );
self->audio_format = NULL;
av_close_input_file( self->video_format );
self->video_format = NULL;
avformat_unlock();
-
- // Default the user-selectable indices from the auto-detected indices
- mlt_properties_set_int( properties, "audio_index", self->audio_index );
- mlt_properties_set_int( properties, "video_index", self->video_index );
-
-#ifdef VDPAU
- mlt_service_cache_set_size( MLT_PRODUCER_SERVICE(producer), "producer_avformat", 5 );
-#endif
- mlt_service_cache_put( MLT_PRODUCER_SERVICE(producer), "producer_avformat", self, 0, (mlt_destructor) producer_avformat_close );
}
}
- else
+ if ( producer )
{
+ // Default the user-selectable indices from the auto-detected indices
+ mlt_properties_set_int( properties, "audio_index", self->audio_index );
+ mlt_properties_set_int( properties, "video_index", self->video_index );
#ifdef VDPAU
mlt_service_cache_set_size( MLT_PRODUCER_SERVICE(producer), "producer_avformat", 5 );
#endif
mlt_service_cache_put( MLT_PRODUCER_SERVICE(producer), "producer_avformat", self, 0, (mlt_destructor) producer_avformat_close );
}
- return producer;
}
}
- return NULL;
+ return producer;
+}
+
+int list_components( char* file )
+{
+ int skip = 0;
+
+ // Report information about available demuxers and codecs as YAML Tiny
+ if ( file && strstr( file, "f-list" ) )
+ {
+ fprintf( stderr, "---\nformats:\n" );
+ AVInputFormat *format = NULL;
+ while ( ( format = av_iformat_next( format ) ) )
+ fprintf( stderr, " - %s\n", format->name );
+ fprintf( stderr, "...\n" );
+ skip = 1;
+ }
+ if ( file && strstr( file, "acodec-list" ) )
+ {
+ fprintf( stderr, "---\naudio_codecs:\n" );
+ AVCodec *codec = NULL;
+ while ( ( codec = av_codec_next( codec ) ) )
+ if ( codec->decode && codec->type == CODEC_TYPE_AUDIO )
+ fprintf( stderr, " - %s\n", codec->name );
+ fprintf( stderr, "...\n" );
+ skip = 1;
+ }
+ if ( file && strstr( file, "vcodec-list" ) )
+ {
+ fprintf( stderr, "---\nvideo_codecs:\n" );
+ AVCodec *codec = NULL;
+ while ( ( codec = av_codec_next( codec ) ) )
+ if ( codec->decode && codec->type == CODEC_TYPE_VIDEO )
+ fprintf( stderr, " - %s\n", codec->name );
+ fprintf( stderr, "...\n" );
+ skip = 1;
+ }
+
+ return skip;
}
/** Find the default streams.
*/
-static mlt_properties find_default_streams( mlt_properties meta_media, AVFormatContext *context, int *audio_index, int *video_index )
+static mlt_properties find_default_streams( producer_avformat self )
{
int i;
char key[200];
AVMetadataTag *tag = NULL;
+ AVFormatContext *context = self->video_format;
+ mlt_properties meta_media = MLT_PRODUCER_PROPERTIES( self->parent );
+
+ // Default to the first audio and video streams found
+ self->audio_index = -1;
+ self->video_index = -1;
mlt_properties_set_int( meta_media, "meta.media.nb_streams", context->nb_streams );
switch( codec_context->codec_type )
{
case CODEC_TYPE_VIDEO:
- if ( *video_index < 0 )
- *video_index = i;
+ // Use first video stream
+ if ( self->video_index < 0 )
+ self->video_index = i;
mlt_properties_set( meta_media, key, "video" );
snprintf( key, sizeof(key), "meta.media.%d.stream.frame_rate", i );
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(42<<8)+0)
#endif
break;
case CODEC_TYPE_AUDIO:
- if ( *audio_index < 0 )
- *audio_index = i;
+ // Use first audio stream
+ if ( self->audio_index < 0 )
+ self->audio_index = i;
mlt_properties_set( meta_media, key, "audio" );
#if LIBAVCODEC_VERSION_MAJOR > 52
snprintf( key, sizeof(key), "meta.media.%d.codec.sample_fmt", i );
return aspect_ratio;
}
-/** Open the file.
-*/
-
-static int producer_open( producer_avformat self, mlt_profile profile, char *file )
+static char* parse_url( mlt_profile profile, const char* URL, AVInputFormat **format, AVFormatParameters *params )
{
- // Return an error code (0 == no error)
- int error = 0;
+ if ( !URL ) return NULL;
- // Context for avformat
- AVFormatContext *context = NULL;
-
- // Get the properties
- mlt_properties properties = MLT_PRODUCER_PROPERTIES( self->parent );
-
- // We will treat everything with the producer fps
- double fps = mlt_profile_fps( profile );
-
- // Lock the service
- pthread_mutex_init( &self->audio_mutex, NULL );
- pthread_mutex_init( &self->video_mutex, NULL );
- pthread_mutex_lock( &self->audio_mutex );
- pthread_mutex_lock( &self->video_mutex );
-
- // If "MRL", then create AVInputFormat
- AVInputFormat *format = NULL;
- AVFormatParameters *params = NULL;
- char *standard = NULL;
- char *mrl = strchr( file, ':' );
-
- // AV option (0 = both, 1 = video, 2 = audio)
- int av = 0;
+ const char *result = URL;
+ char *protocol = strdup( URL );
+ char *url = strchr( protocol, ':' );
// Only if there is not a protocol specification that avformat can handle
#if LIBAVFORMAT_VERSION_MAJOR > 52
- if ( mrl && !avio_check( file, 0 ) )
+ if ( url && avio_check( URL, 0 ) < 0 )
#else
- if ( mrl && !url_exist( file ) )
+ if ( url && !url_exist( URL ) )
#endif
{
- // 'file' becomes format abbreviation
- mrl[0] = 0;
+ // Truncate protocol string
+ url[0] = 0;
+ mlt_log_debug( NULL, "%s: protocol=%s resource=%s\n", __FUNCTION__, protocol, url + 1 );
// Lookup the format
- format = av_find_input_format( file );
+ *format = av_find_input_format( protocol );
// Eat the format designator
- file = ++mrl;
+ result = ++url;
- if ( format )
+ if ( *format )
{
- // Allocate params
- params = calloc( sizeof( AVFormatParameters ), 1 );
-
- // These are required by video4linux (defaults)
- params->width = 640;
- params->height = 480;
- params->time_base= (AVRational){1,25};
+ // These are required by video4linux2 (defaults)
+ params->width = profile->width;
+ params->height = profile->height;
+ params->time_base= (AVRational){ profile->frame_rate_den, profile->frame_rate_num };
params->channels = 2;
params->sample_rate = 48000;
- }
- // Parse out params
- mrl = strchr( file, '?' );
- while ( mrl )
- {
- mrl[0] = 0;
- char *name = strdup( ++mrl );
- char *value = strchr( name, ':' );
- if ( value )
+ // Parse out params
+ url = strchr( url, '?' );
+ while ( url )
{
- value[0] = 0;
- value++;
- char *t = strchr( value, '&' );
- if ( t )
- t[0] = 0;
- if ( !strcmp( name, "frame_rate" ) )
- params->time_base.den = atoi( value );
- else if ( !strcmp( name, "frame_rate_base" ) )
- params->time_base.num = atoi( value );
- else if ( !strcmp( name, "sample_rate" ) )
- params->sample_rate = atoi( value );
- else if ( !strcmp( name, "channel" ) )
- params->channel = atoi( value );
- else if ( !strcmp( name, "channels" ) )
- params->channels = atoi( value );
+ url[0] = 0;
+ char *name = strdup( ++url );
+ char *value = strchr( name, ':' );
+ if ( value )
+ {
+ value[0] = 0;
+ value++;
+ char *t = strchr( value, '&' );
+ if ( t )
+ t[0] = 0;
+ if ( !strcmp( name, "frame_rate" ) )
+ params->time_base.den = atoi( value );
+ else if ( !strcmp( name, "frame_rate_base" ) )
+ params->time_base.num = atoi( value );
+ else if ( !strcmp( name, "sample_rate" ) )
+ params->sample_rate = atoi( value );
+ else if ( !strcmp( name, "channel" ) )
+ params->channel = atoi( value );
+ else if ( !strcmp( name, "channels" ) )
+ params->channels = atoi( value );
#if (LIBAVUTIL_VERSION_INT > ((50<<16)+(7<<8)+0))
- else if ( !strcmp( name, "pix_fmt" ) )
- params->pix_fmt = av_get_pix_fmt( value );
+ else if ( !strcmp( name, "pix_fmt" ) )
+ params->pix_fmt = av_get_pix_fmt( value );
+#endif
+ else if ( !strcmp( name, "width" ) )
+ params->width = atoi( value );
+ else if ( !strcmp( name, "height" ) )
+ params->height = atoi( value );
+ else if ( !strcmp( name, "standard" ) )
+ params->standard = strdup( value );
+ }
+ free( name );
+ url = strchr( url, '&' );
+ }
+ }
+ }
+ free( protocol );
+ return strdup( result );
+}
+
+static int get_basic_info( producer_avformat self, mlt_profile profile, const char *filename )
+{
+ int error = 0;
+
+ // Get the properties
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( self->parent );
+
+ AVFormatContext *format = self->video_format;
+
+ // We will treat everything with the producer fps.
+ // TODO: make this more flexible.
+ double fps = mlt_profile_fps( profile );
+
+ // Get the duration
+ if ( !mlt_properties_get_int( properties, "_length_computed" ) )
+ {
+ // The _length_computed flag prevents overwriting explicity set length/out/eof properties
+ // when producer_open is called after initial call when restoring or reseting the producer.
+ if ( format->duration != AV_NOPTS_VALUE )
+ {
+ // This isn't going to be accurate for all formats
+ mlt_position frames = ( mlt_position )( ( ( double )format->duration / ( double )AV_TIME_BASE ) * fps );
+ mlt_properties_set_position( properties, "out", frames - 1 );
+ mlt_properties_set_position( properties, "length", frames );
+ mlt_properties_set_int( properties, "_length_computed", 1 );
+ }
+ else
+ {
+ // Set live sources to run forever
+ mlt_properties_set_position( properties, "length", INT_MAX );
+ mlt_properties_set_position( properties, "out", INT_MAX - 1 );
+ mlt_properties_set( properties, "eof", "loop" );
+ mlt_properties_set_int( properties, "_length_computed", 1 );
+ }
+ }
+
+ if ( format->start_time != AV_NOPTS_VALUE )
+ self->start_time = format->start_time;
+
+ // Check if we're seekable
+ // avdevices are typically AVFMT_NOFILE and not seekable
+ self->seekable = !format->iformat || !( format->iformat->flags & AVFMT_NOFILE );
+ if ( format->pb )
+ {
+ // protocols can indicate if they support seeking
+#if LIBAVFORMAT_VERSION_MAJOR > 52
+ self->seekable = format->pb->seekable;
+#else
+ URLContext *uc = url_fileno( format->pb );
+ if ( uc )
+ self->seekable = !uc->is_streamed;
#endif
- else if ( !strcmp( name, "width" ) )
- params->width = atoi( value );
- else if ( !strcmp( name, "height" ) )
- params->height = atoi( value );
- else if ( !strcmp( name, "standard" ) )
+ }
+ if ( self->seekable )
+ {
+ // Do a more rigourous test of seekable on a disposable context
+ self->seekable = av_seek_frame( format, -1, self->start_time, AVSEEK_FLAG_BACKWARD ) >= 0;
+ mlt_properties_set_int( properties, "seekable", self->seekable );
+ self->dummy_context = format;
+ av_open_input_file( &self->video_format, filename, NULL, 0, NULL );
+ format = self->video_format;
+ av_find_stream_info( format );
+ }
+
+ // Fetch the width, height and aspect ratio
+ if ( self->video_index != -1 )
+ {
+ AVCodecContext *codec_context = format->streams[ self->video_index ]->codec;
+ mlt_properties_set_int( properties, "width", codec_context->width );
+ mlt_properties_set_int( properties, "height", codec_context->height );
+
+ if ( codec_context->codec_id == CODEC_ID_DVVIDEO )
+ {
+ // Fetch the first frame of DV so we can read it directly
+ AVPacket pkt;
+ int ret = 0;
+ while ( ret >= 0 )
+ {
+ ret = av_read_frame( format, &pkt );
+ if ( ret >= 0 && pkt.stream_index == self->video_index && pkt.size > 0 )
{
- standard = strdup( value );
- params->standard = standard;
+ get_aspect_ratio( properties, format->streams[ self->video_index ], codec_context, &pkt );
+ break;
}
- else if ( !strcmp( name, "av" ) )
- av = atoi( value );
}
- free( name );
- mrl = strchr( mrl, '&' );
}
+ else
+ {
+ get_aspect_ratio( properties, format->streams[ self->video_index ], codec_context, NULL );
+ }
+
+#ifdef SWSCALE
+ // Verify that we can convert this to YUV 4:2:2
+ // TODO: we can now also return RGB and RGBA and quite possibly more in the future.
+ struct SwsContext *context = sws_getContext( codec_context->width, codec_context->height, codec_context->pix_fmt,
+ codec_context->width, codec_context->height, PIX_FMT_YUYV422, SWS_BILINEAR, NULL, NULL, NULL);
+ if ( context )
+ sws_freeContext( context );
+ else
+ error = 1;
+#endif
}
+ return error;
+}
- // Now attempt to open the file
- error = av_open_input_file( &context, file, format, 0, params ) < 0;
+/** Open the file.
+*/
+
+static int producer_open( producer_avformat self, mlt_profile profile, const char *URL )
+{
+ // Return an error code (0 == no error)
+ int error = 0;
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( self->parent );
+
+ // Lock the service
+ pthread_mutex_init( &self->audio_mutex, NULL );
+ pthread_mutex_init( &self->video_mutex, NULL );
+ pthread_mutex_init( &self->packets_mutex, NULL );
+ mlt_events_block( properties, self->parent );
+ pthread_mutex_lock( &self->audio_mutex );
+ pthread_mutex_lock( &self->video_mutex );
+
+ // Parse URL
+ AVInputFormat *format = NULL;
+ AVFormatParameters params;
+ memset( ¶ms, 0, sizeof(params) );
+ char *filename = parse_url( profile, URL, &format, ¶ms );
+
+ // Now attempt to open the file or device with filename
+ error = av_open_input_file( &self->video_format, filename, format, 0, ¶ms ) < 0;
+ if ( error )
+ // If the URL is a network stream URL, then we probably need to open with full URL
+ error = av_open_input_file( &self->video_format, URL, format, 0, ¶ms ) < 0;
+
+ // Set MLT properties onto video AVFormatContext
+ if ( !error && self->video_format )
+ {
+ apply_properties( self->video_format, properties, AV_OPT_FLAG_DECODING_PARAM );
+#if LIBAVFORMAT_VERSION_MAJOR > 52
+ if ( self->video_format->iformat && self->video_format->iformat->priv_class && self->video_format->priv_data )
+ apply_properties( self->video_format->priv_data, properties, AV_OPT_FLAG_DECODING_PARAM );
+#endif
+ }
// Cleanup AVFormatParameters
- free( standard );
- free( params );
+ if ( params.standard )
+ free( (void*) params.standard );
// If successful, then try to get additional info
if ( !error )
{
// Get the stream info
- error = av_find_stream_info( context ) < 0;
+ error = av_find_stream_info( self->video_format ) < 0;
// Continue if no error
if ( !error )
{
- // We will default to the first audio and video streams found
- int audio_index = -1;
- int video_index = -1;
-
// Find default audio and video streams
- find_default_streams( properties, context, &audio_index, &video_index );
+ find_default_streams( self );
+ error = get_basic_info( self, profile, filename );
- // Now set properties where we can (use default unknowns if required)
- if ( context->duration != AV_NOPTS_VALUE )
- {
- // This isn't going to be accurate for all formats
- // Workaround some clips whose estimated duration cause problems:
- // http://www.kdenlive.org/mantis/view.php?id=2003
- int adjust = -3;
- if ( mlt_properties_get( properties, "adjust_length" ) )
- adjust = mlt_properties_get_int( properties, "adjust_length" );
- mlt_position frames = ( mlt_position )( ( ( double )context->duration / ( double )AV_TIME_BASE ) * fps + adjust );
- if ( mlt_properties_get_position( properties, "force_length" ) > 0 )
- frames = mlt_properties_get_position( properties, "force_length" );
- mlt_properties_set_position( properties, "out", frames - 1 );
- mlt_properties_set_position( properties, "length", frames );
- }
-
- if ( context->start_time != AV_NOPTS_VALUE )
- self->start_time = context->start_time;
-
- // Check if we're seekable
- // avdevices are typically AVFMT_NOFILE and not seekable
- self->seekable = !format || !( format->flags & AVFMT_NOFILE );
- if ( context->pb )
- {
- // protocols can indicate if they support seeking
-#if LIBAVFORMAT_VERSION_MAJOR > 52
- self->seekable = context->pb->seekable;
-#else
- URLContext *uc = url_fileno( context->pb );
- if ( uc )
- self->seekable = !uc->is_streamed;
-#endif
- }
- if ( self->seekable )
- {
- self->seekable = av_seek_frame( context, -1, self->start_time, AVSEEK_FLAG_BACKWARD ) >= 0;
- mlt_properties_set_int( properties, "seekable", self->seekable );
- self->dummy_context = context;
- av_open_input_file( &context, file, NULL, 0, NULL );
- av_find_stream_info( context );
- }
-
- // Store selected audio and video indexes on properties
- self->audio_index = audio_index;
- self->video_index = video_index;
+ // Initialize position info
self->first_pts = -1;
self->last_position = POSITION_INITIAL;
- // Fetch the width, height and aspect ratio
- if ( video_index != -1 )
+ // We're going to cheat here - for seekable A/V files, we will have separate contexts
+ // to support independent seeking of audio from video.
+ // TODO: Is this really necessary?
+ if ( self->audio_index != -1 && self->video_index != -1 )
{
- AVCodecContext *codec_context = context->streams[ video_index ]->codec;
- mlt_properties_set_int( properties, "width", codec_context->width );
- mlt_properties_set_int( properties, "height", codec_context->height );
-
- if ( codec_context->codec_id == CODEC_ID_DVVIDEO )
+ if ( self->seekable )
{
- // Fetch the first frame of DV so we can read it directly
- AVPacket pkt;
- int ret = 0;
- while ( ret >= 0 )
- {
- ret = av_read_frame( context, &pkt );
- if ( ret >= 0 && pkt.stream_index == video_index && pkt.size > 0 )
- {
- get_aspect_ratio( properties, context->streams[ video_index ], codec_context, &pkt );
- break;
- }
- }
+ // And open again for our audio context
+ av_open_input_file( &self->audio_format, filename, NULL, 0, NULL );
+ apply_properties( self->audio_format, properties, AV_OPT_FLAG_DECODING_PARAM );
+#if LIBAVFORMAT_VERSION_MAJOR > 52
+ if ( self->audio_format->iformat && self->audio_format->iformat->priv_class && self->audio_format->priv_data )
+ apply_properties( self->audio_format->priv_data, properties, AV_OPT_FLAG_DECODING_PARAM );
+#endif
+ av_find_stream_info( self->audio_format );
}
else
{
- get_aspect_ratio( properties, context->streams[ video_index ], codec_context, NULL );
+ self->audio_format = self->video_format;
}
-#ifdef SWSCALE
- struct SwsContext *context = sws_getContext( codec_context->width, codec_context->height, codec_context->pix_fmt,
- codec_context->width, codec_context->height, PIX_FMT_YUYV422, SWS_BILINEAR, NULL, NULL, NULL);
- if ( context )
- sws_freeContext( context );
- else
- error = 1;
-#endif
- }
-
- // We're going to cheat here - for a/v files, we will have two contexts (reasoning will be clear later)
- if ( av == 0 && audio_index != -1 && video_index != -1 )
- {
- // We'll use the open one as our video_format
- self->video_format = context;
-
- // And open again for our audio context
- av_open_input_file( &context, file, NULL, 0, NULL );
- av_find_stream_info( context );
-
- // Audio context
- self->audio_format = context;
}
- else if ( av != 2 && video_index != -1 )
- {
- // We only have a video context
- self->video_format = context;
- }
- else if ( audio_index != -1 )
+ else if ( self->audio_index != -1 )
{
// We only have an audio context
- self->audio_format = context;
+ self->audio_format = self->video_format;
+ self->video_format = NULL;
}
- else
+ else if ( self->video_index == -1 )
{
// Something has gone wrong
error = -1;
}
+ if ( self->audio_format && !self->audio_streams )
+ get_audio_streams_info( self );
}
}
+ if ( filename )
+ free( filename );
+ if ( !error )
+ {
+ self->apackets = mlt_deque_init();
+ self->vpackets = mlt_deque_init();
+ }
+
+ if ( self->dummy_context )
+ {
+ av_close_input_file( self->dummy_context );
+ self->dummy_context = NULL;
+ }
// Unlock the service
pthread_mutex_unlock( &self->audio_mutex );
pthread_mutex_unlock( &self->video_mutex );
+ mlt_events_unblock( properties, self->parent );
return error;
}
-void reopen_video( producer_avformat self, mlt_producer producer, mlt_properties properties )
+static void reopen_video( producer_avformat self, mlt_producer producer )
{
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer );
mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) );
pthread_mutex_lock( &self->audio_mutex );
int audio_index = self->audio_index;
int video_index = self->video_index;
- mlt_events_block( properties, producer );
pthread_mutex_unlock( &self->audio_mutex );
pthread_mutex_unlock( &self->video_mutex );
producer_open( self, mlt_service_profile( MLT_PRODUCER_SERVICE(producer) ),
mlt_properties_get( properties, "resource" ) );
pthread_mutex_lock( &self->video_mutex );
pthread_mutex_lock( &self->audio_mutex );
- if ( self->dummy_context )
- {
- av_close_input_file( self->dummy_context );
- self->dummy_context = NULL;
- }
- mlt_events_unblock( properties, producer );
- apply_properties( self->video_format, properties, AV_OPT_FLAG_DECODING_PARAM );
-#if LIBAVFORMAT_VERSION_MAJOR > 52
- if ( self->video_format->iformat && self->video_format->iformat->priv_class && self->video_format->priv_data )
- apply_properties( self->video_format->priv_data, properties, AV_OPT_FLAG_DECODING_PARAM );
-#endif
self->audio_index = audio_index;
if ( self->video_format && video_index > -1 )
mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) );
}
+static int seek_video( producer_avformat self, mlt_position position,
+ int req_position, int must_decode, int use_new_seek, int *ignore )
+{
+ mlt_producer producer = self->parent;
+ int paused = 0;
+
+ if ( self->seekable && ( position != self->video_expected || self->last_position < 0 ) )
+ {
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer );
+
+ // Fetch the video format context
+ AVFormatContext *context = self->video_format;
+
+ // Get the video stream
+ AVStream *stream = context->streams[ self->video_index ];
+
+ // Get codec context
+ AVCodecContext *codec_context = stream->codec;
+
+ // We may want to use the source fps if available
+ double source_fps = mlt_properties_get_double( properties, "meta.media.frame_rate_num" ) /
+ mlt_properties_get_double( properties, "meta.media.frame_rate_den" );
+
+ if ( self->av_frame && position + 1 == self->video_expected )
+ {
+ // We're paused - use last image
+ paused = 1;
+ }
+ else if ( !self->seekable && position > self->video_expected && ( position - self->video_expected ) < 250 )
+ {
+ // Fast forward - seeking is inefficient for small distances - just ignore following frames
+ *ignore = ( int )( ( position - self->video_expected ) / mlt_producer_get_fps( producer ) * source_fps );
+ codec_context->skip_loop_filter = AVDISCARD_NONREF;
+ }
+ else if ( self->seekable && ( position < self->video_expected || position - self->video_expected >= 12 || self->last_position < 0 ) )
+ {
+ if ( use_new_seek && self->last_position == POSITION_INITIAL )
+ {
+ // find first key frame
+ int ret = 0;
+ int toscan = 100;
+ AVPacket pkt;
+
+ while ( ret >= 0 && toscan-- > 0 )
+ {
+ ret = av_read_frame( context, &pkt );
+ if ( ret >= 0 && ( pkt.flags & PKT_FLAG_KEY ) && pkt.stream_index == self->video_index )
+ {
+ mlt_log_verbose( MLT_PRODUCER_SERVICE(producer), "first_pts %"PRId64" dts %"PRId64" pts_dts_delta %d\n", pkt.pts, pkt.dts, (int)(pkt.pts - pkt.dts) );
+ self->first_pts = pkt.pts;
+ toscan = 0;
+ }
+ av_free_packet( &pkt );
+ }
+ // Rewind
+ av_seek_frame( context, -1, 0, AVSEEK_FLAG_BACKWARD );
+ }
+
+ // Calculate the timestamp for the requested frame
+ int64_t timestamp;
+ if ( use_new_seek )
+ {
+ timestamp = ( req_position - 0.1 / source_fps ) /
+ ( av_q2d( stream->time_base ) * source_fps );
+ mlt_log_verbose( MLT_PRODUCER_SERVICE(producer), "pos %d pts %"PRId64" ", req_position, timestamp );
+ if ( self->first_pts > 0 )
+ timestamp += self->first_pts;
+ else if ( context->start_time != AV_NOPTS_VALUE )
+ timestamp += context->start_time;
+ }
+ else
+ {
+ timestamp = ( int64_t )( ( double )req_position / source_fps * AV_TIME_BASE + 0.5 );
+ if ( context->start_time != AV_NOPTS_VALUE )
+ timestamp += context->start_time;
+ }
+ if ( must_decode )
+ timestamp -= AV_TIME_BASE;
+ if ( timestamp < 0 )
+ timestamp = 0;
+ mlt_log_debug( MLT_PRODUCER_SERVICE(producer), "seeking timestamp %"PRId64" position %d expected %d last_pos %d\n",
+ timestamp, position, self->video_expected, self->last_position );
+
+ // Seek to the timestamp
+ if ( use_new_seek )
+ {
+ codec_context->skip_loop_filter = AVDISCARD_NONREF;
+ av_seek_frame( context, self->video_index, timestamp, AVSEEK_FLAG_BACKWARD );
+ }
+ else if ( req_position > 0 || self->last_position <= 0 )
+ {
+ av_seek_frame( context, -1, timestamp, AVSEEK_FLAG_BACKWARD );
+ }
+ else
+ {
+ // Re-open video stream when rewinding to beginning from somewhere else.
+ // This is rather ugly, and I prefer not to do it this way, but ffmpeg is
+ // not reliably seeking to the first frame across formats.
+ reopen_video( self, producer );
+ }
+
+ // Remove the cached info relating to the previous position
+ self->current_position = POSITION_INVALID;
+ self->last_position = POSITION_INVALID;
+ av_freep( &self->av_frame );
+
+ if ( use_new_seek )
+ {
+ // flush any pictures still in decode buffer
+ avcodec_flush_buffers( codec_context );
+ }
+ }
+ }
+ return paused;
+}
+
/** Convert a frame position to a time code.
*/
// Packet
AVPacket pkt;
- // Special case pause handling flag
- int paused = 0;
-
// Special case ffwd handling
int ignore = 0;
// We may want to use the source fps if available
double source_fps = mlt_properties_get_double( properties, "meta.media.frame_rate_num" ) /
mlt_properties_get_double( properties, "meta.media.frame_rate_den" );
- double fps = mlt_producer_get_fps( producer );
// This is the physical frame position in the source
- int req_position = ( int )( position / fps * source_fps + 0.5 );
+ int req_position = ( int )( position / mlt_producer_get_fps( producer ) * source_fps + 0.5 );
// Determines if we have to decode all frames in a sequence
// Temporary hack to improve intra frame only
strcmp( codec_context->codec->name, "mjpeg" ) &&
strcmp( codec_context->codec->name, "rawvideo" );
- int last_position = self->last_position;
-
// Turn on usage of new seek API and PTS for seeking
int use_new_seek = codec_context->codec_id == CODEC_ID_H264 && !strcmp( context->iformat->name, "mpegts" );
if ( mlt_properties_get( properties, "new_seek" ) )
use_new_seek = mlt_properties_get_int( properties, "new_seek" );
// Seek if necessary
- if ( position != self->video_expected || last_position < 0 )
- {
- if ( self->av_frame && position + 1 == self->video_expected )
- {
- // We're paused - use last image
- paused = 1;
- }
- else if ( !self->seekable && position > self->video_expected && ( position - self->video_expected ) < 250 )
- {
- // Fast forward - seeking is inefficient for small distances - just ignore following frames
- ignore = ( int )( ( position - self->video_expected ) / fps * source_fps );
- codec_context->skip_loop_filter = AVDISCARD_NONREF;
- }
- else if ( self->seekable && ( position < self->video_expected || position - self->video_expected >= 12 || last_position < 0 ) )
- {
- if ( use_new_seek && last_position == POSITION_INITIAL )
- {
- // find first key frame
- int ret = 0;
- int toscan = 100;
-
- while ( ret >= 0 && toscan-- > 0 )
- {
- ret = av_read_frame( context, &pkt );
- if ( ret >= 0 && ( pkt.flags & PKT_FLAG_KEY ) && pkt.stream_index == self->video_index )
- {
- mlt_log_verbose( MLT_PRODUCER_SERVICE(producer), "first_pts %"PRId64" dts %"PRId64" pts_dts_delta %d\n", pkt.pts, pkt.dts, (int)(pkt.pts - pkt.dts) );
- self->first_pts = pkt.pts;
- toscan = 0;
- }
- av_free_packet( &pkt );
- }
- // Rewind
- av_seek_frame( context, -1, 0, AVSEEK_FLAG_BACKWARD );
- }
-
- // Calculate the timestamp for the requested frame
- int64_t timestamp;
- if ( use_new_seek )
- {
- timestamp = ( req_position - 0.1 / source_fps ) /
- ( av_q2d( stream->time_base ) * source_fps );
- mlt_log_verbose( MLT_PRODUCER_SERVICE(producer), "pos %d pts %"PRId64" ", req_position, timestamp );
- if ( self->first_pts > 0 )
- timestamp += self->first_pts;
- else if ( context->start_time != AV_NOPTS_VALUE )
- timestamp += context->start_time;
- }
- else
- {
- timestamp = ( int64_t )( ( double )req_position / source_fps * AV_TIME_BASE + 0.5 );
- if ( context->start_time != AV_NOPTS_VALUE )
- timestamp += context->start_time;
- }
- if ( must_decode )
- timestamp -= AV_TIME_BASE;
- if ( timestamp < 0 )
- timestamp = 0;
- mlt_log_debug( MLT_PRODUCER_SERVICE(producer), "seeking timestamp %"PRId64" position %d expected %d last_pos %d\n",
- timestamp, position, self->video_expected, last_position );
-
- // Seek to the timestamp
- if ( use_new_seek )
- {
- codec_context->skip_loop_filter = AVDISCARD_NONREF;
- av_seek_frame( context, self->video_index, timestamp, AVSEEK_FLAG_BACKWARD );
- }
- else if ( req_position > 0 || last_position <= 0 )
- {
- av_seek_frame( context, -1, timestamp, AVSEEK_FLAG_BACKWARD );
- }
- else
- {
- // Re-open video stream when rewinding to beginning from somewhere else.
- // This is rather ugly, and I prefer not to do it this way, but ffmpeg is
- // not reliably seeking to the first frame across formats.
- reopen_video( self, producer, properties );
- context = self->video_format;
- stream = context->streams[ self->video_index ];
- codec_context = stream->codec;
- }
+ int paused = seek_video( self, position, req_position, must_decode, use_new_seek, &ignore );
- // Remove the cached info relating to the previous position
- self->current_position = POSITION_INVALID;
- self->last_position = POSITION_INVALID;
- av_freep( &self->av_frame );
-
- if ( use_new_seek )
- {
- // flush any pictures still in decode buffer
- avcodec_flush_buffers( codec_context );
- }
- }
- }
+ // Seek might have reopened the file
+ context = self->video_format;
+ stream = context->streams[ self->video_index ];
+ codec_context = stream->codec;
// Duplicate the last image if necessary
if ( self->av_frame && self->av_frame->linesize[0] && self->got_picture && self->seekable
while( ret >= 0 && !got_picture )
{
// Read a packet
- ret = av_read_frame( context, &pkt );
+ pthread_mutex_lock( &self->packets_mutex );
+ if ( mlt_deque_count( self->vpackets ) )
+ {
+ AVPacket *tmp = (AVPacket*) mlt_deque_pop_front( self->vpackets );
+ pkt = *tmp;
+ free( tmp );
+ }
+ else
+ {
+ ret = av_read_frame( context, &pkt );
+ if ( ret >= 0 && !self->seekable && pkt.stream_index == self->audio_index )
+ {
+ if ( !av_dup_packet( &pkt ) )
+ {
+ AVPacket *tmp = malloc( sizeof(AVPacket) );
+ *tmp = pkt;
+ mlt_deque_push_back( self->apackets, tmp );
+ }
+ }
+ }
+ pthread_mutex_unlock( &self->packets_mutex );
// We only deal with video from the selected video_index
if ( ret >= 0 && pkt.stream_index == self->video_index && pkt.size > 0 )
int_position = ( int )( av_q2d( stream->time_base ) * pkt.dts * source_fps + 0.5 );
if ( context->start_time != AV_NOPTS_VALUE )
int_position -= ( int )( context->start_time * source_fps / AV_TIME_BASE + 0.5 );
- last_position = self->last_position;
- if ( int_position == last_position )
- int_position = last_position + 1;
+ if ( int_position == self->last_position )
+ int_position = self->last_position + 1;
}
else
{
got_picture = 0;
}
}
- if ( ret >= 0 )
+ if ( self->seekable || pkt.stream_index != self->audio_index )
av_free_packet( &pkt );
}
}
// Reopen the file if necessary
if ( !context && index > -1 )
{
- mlt_events_block( properties, producer );
producer_open( self, mlt_service_profile( MLT_PRODUCER_SERVICE(producer) ),
mlt_properties_get( properties, "resource" ) );
context = self->video_format;
- if ( self->dummy_context )
- {
- av_close_input_file( self->dummy_context );
- self->dummy_context = NULL;
- }
- mlt_events_unblock( properties, producer );
- if ( self->audio_format && !self->audio_streams )
- get_audio_streams_info( self );
-
- // Process properties as AVOptions
- if ( context )
- {
- apply_properties( context, properties, AV_OPT_FLAG_DECODING_PARAM );
-#if LIBAVFORMAT_VERSION_MAJOR > 52
- if ( context->iformat && context->iformat->priv_class && context->priv_data )
- apply_properties( context->priv_data, properties, AV_OPT_FLAG_DECODING_PARAM );
-#endif
- }
}
// Exception handling for video_index
int paused = 0;
// Seek if necessary
- if ( position != self->audio_expected )
+ if ( self->seekable && position != self->audio_expected )
{
if ( position + 1 == self->audio_expected )
{
}
// Read a packet
- ret = av_read_frame( context, &pkt );
+ pthread_mutex_lock( &self->packets_mutex );
+ if ( mlt_deque_count( self->apackets ) )
+ {
+ AVPacket *tmp = (AVPacket*) mlt_deque_pop_front( self->apackets );
+ pkt = *tmp;
+ free( tmp );
+ }
+ else
+ {
+ ret = av_read_frame( context, &pkt );
+ if ( ret >= 0 && !self->seekable && pkt.stream_index == self->video_index )
+ {
+ if ( !av_dup_packet( &pkt ) )
+ {
+ AVPacket *tmp = malloc( sizeof(AVPacket) );
+ *tmp = pkt;
+ mlt_deque_push_back( self->vpackets, tmp );
+ }
+ }
+ }
+ pthread_mutex_unlock( &self->packets_mutex );
// We only deal with audio from the selected audio index
index = pkt.stream_index;
self->audio_codec[index]->channels : *channels;
ret = decode_audio( self, &ignore, pkt, channels2, *samples, real_timecode, fps );
}
- av_free_packet( &pkt );
+
+ if ( self->seekable || index != self->video_index )
+ av_free_packet( &pkt );
if ( self->audio_index == INT_MAX && ret >= 0 )
{
// Reopen the file if necessary
if ( !context && self->audio_index > -1 && index > -1 )
{
- mlt_events_block( properties, producer );
producer_open( self, mlt_service_profile( MLT_PRODUCER_SERVICE(producer) ),
mlt_properties_get( properties, "resource" ) );
context = self->audio_format;
- if ( self->dummy_context )
- {
- av_close_input_file( self->dummy_context );
- self->dummy_context = NULL;
- }
- mlt_events_unblock( properties, producer );
- if ( self->audio_format )
- get_audio_streams_info( self );
}
// Exception handling for audio_index
static void producer_avformat_close( producer_avformat self )
{
mlt_log_debug( NULL, "producer_avformat_close\n" );
- // Close the file
+
+ // Cleanup av contexts
av_free( self->av_frame );
avformat_lock();
int i;
}
if ( self->video_codec )
avcodec_close( self->video_codec );
+ // Close the file
if ( self->dummy_context )
av_close_input_file( self->dummy_context );
- if ( self->audio_format )
+ if ( self->seekable && self->audio_format )
av_close_input_file( self->audio_format );
if ( self->video_format )
av_close_input_file( self->video_format );
#endif
if ( self->image_cache )
mlt_cache_close( self->image_cache );
+
+ // Cleanup the mutexes
pthread_mutex_destroy( &self->audio_mutex );
pthread_mutex_destroy( &self->video_mutex );
+ pthread_mutex_destroy( &self->packets_mutex );
+
+ // Cleanup the packet queues
+ AVPacket *pkt;
+ while ( ( pkt = mlt_deque_pop_back( self->apackets ) ) )
+ {
+ av_free_packet( pkt );
+ free( pkt );
+ }
+ while ( ( pkt = mlt_deque_pop_back( self->vpackets ) ) )
+ {
+ av_free_packet( pkt );
+ free( pkt );
+ }
+
free( self );
}
static void producer_close( mlt_producer parent )
{
+ // Remove this instance from the cache
+ mlt_service_cache_purge( MLT_PRODUCER_SERVICE(parent) );
+
// Close the parent
parent->close = NULL;
mlt_producer_close( parent );