[vlmc-devel] [PATCH] Add libVLC producer (WIP) to repo
Pawel Golinski
golpaw1 at gmail.com
Thu Jul 21 20:17:22 CEST 2016
---
src/Backend/MLT/modules/libvlc/producer_libvlc.c | 892 +++++++++++++++++++++++
1 file changed, 892 insertions(+)
create mode 100644 src/Backend/MLT/modules/libvlc/producer_libvlc.c
diff --git a/src/Backend/MLT/modules/libvlc/producer_libvlc.c b/src/Backend/MLT/modules/libvlc/producer_libvlc.c
new file mode 100644
index 0000000..35a887b
--- /dev/null
+++ b/src/Backend/MLT/modules/libvlc/producer_libvlc.c
@@ -0,0 +1,892 @@
+/*****************************************************************************
+ * producer_libvlc.c: libVLC producer plugin for MLT (WIP)
+ *****************************************************************************
+ * Copyright (C) 2008-2016 VideoLAN
+ *
+ * Authors: Pawel Golinski <golpaw1 at gmail.com>
+ *
+ * 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.
+ *
+ * This program 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.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#include <framework/mlt.h>
+
+#include <vlc/vlc.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <string.h>
+#include <assert.h>
+#include <locale.h>
+
+#define MAX_CACHE_SIZE 500
+#define AUDIO_SAMPLE_SIZE sizeof( int16_t )
+#define NO_PTS_VALUE (-1)
+#define SEEK_threshold 200
+#define SEEK_AHEAD (-1)
+#define SEEK_BEHIND (-1)
+#define FRAMES_FOR_DATA_COLLECTION 5
+
+// To the reviewer:
+// Assertions in code will be deleted later,
+// these are here to assure that I have proper
+// understanding of libVLC (and should be reviewed as well).
+
+pthread_mutex_t log_mutex;
+
+static void log_cb(void *data, int level, const libvlc_log_t *ctx, const char *fmt, va_list args)
+{
+ pthread_mutex_lock(&log_mutex);
+ printf("VLC LOG: ");
+ vprintf(fmt, args);
+ printf("\n");
+ pthread_mutex_unlock(&log_mutex);
+}
+
+typedef struct producer_libvlc_s *producer_libvlc;
+typedef struct a_cache_item_s *a_cache_item;
+typedef struct v_cache_item_s *v_cache_item;
+
+// Video cache item
+struct v_cache_item_s
+{
+ uint8_t *buffer;
+ size_t size;
+ int bpp;
+ int width;
+ int height;
+ int64_t pts;
+};
+
+// Audio cache item
+struct a_cache_item_s
+{
+ // buffer of samples
+ void *buffer;
+ // number of samples per channel
+ size_t samples;
+ // number of channels
+ int channels;
+ // sample rate
+ int sample_rate;
+ // bits per sample
+ int bps;
+ // pts
+ int64_t pts;
+};
+
+struct producer_libvlc_s
+{
+ mlt_producer parent;
+ libvlc_instance_t *libvlc_instance;
+ libvlc_media_t *media;
+ libvlc_media_player_t *media_player;
+ mlt_deque v_cache;
+ int v_cache_producers;
+ int v_cache_consumers;
+ pthread_mutex_t v_cache_mutex;
+ pthread_cond_t v_cache_producer_cond;
+ pthread_cond_t v_cache_consumer_cond;
+ mlt_deque a_cache;
+ mlt_position last_frame_requested;
+ int a_cache_producers;
+ int a_cache_consumers;
+ pthread_mutex_t a_cache_mutex;
+ pthread_cond_t a_cache_producer_cond;
+ pthread_cond_t a_cache_consumer_cond;
+ // temporary audio buffer for samples
+ void *ta_buffer;
+ // size of ta_buffer
+ size_t ta_buffer_size;
+ mlt_position current_audio_frame;
+ // Flag for cleanup
+ int terminating;
+ // Seeking
+ int64_t first_pts;
+ pthread_mutex_t seek_mutex;
+ pthread_cond_t seek_cond;
+ int seek_pending;
+ int seek_video_report;
+ int seek_audio_report;
+ int collecting_pts_data;
+ int waiting_for_pts_data;
+ int64_t video_pts[ FRAMES_FOR_DATA_COLLECTION ];
+ int64_t audio_pts[ FRAMES_FOR_DATA_COLLECTION ];
+ int collected_video_pts;
+ int collected_audio_pts;
+ pthread_mutex_t pts_data_mutex;
+ pthread_cond_t pts_data_cond;
+ int64_t average_video_pts_diff;
+ int64_t average_audio_pts_diff;
+ // debug
+ int audio_locks;
+ int audio_unlocks;
+ int image_gots;
+ int audio_gots;
+};
+
+// Forward references
+static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index );
+static void set_properties( producer_libvlc self, mlt_profile profile );
+static void collect_stream_data( producer_libvlc self, char *file );
+static void setup_smem( producer_libvlc self );
+static void producer_close( mlt_producer parent );
+static void audio_prerender_callback( void* p_audio_data, uint8_t** pp_pcm_buffer, size_t size );
+static void audio_postrender_callback( void* p_audio_data, uint8_t* p_pcm_buffer, unsigned int channels,
+ unsigned int rate, unsigned int nb_samples, unsigned int bits_per_sample,
+ size_t size, int64_t pts );
+static void video_prerender_callback( void *data, uint8_t **p_buffer, size_t size );
+static void video_postrender_callback( void *data, uint8_t *buffer, int width, int height,
+ int bpp, size_t size, int64_t pts );
+static void seek_smem( producer_libvlc self, mlt_position position );
+static void collect_timestamp_data( producer_libvlc self );
+
+mlt_producer producer_libvlc_init( mlt_profile profile, mlt_service_type type, const char *id, char *file )
+{
+ // If we receive a NULL file argument, we've got nothing to do
+ if ( file == NULL )
+ return NULL;
+
+ mlt_producer producer = NULL;
+ producer_libvlc self = NULL;
+ int libvlc_initialized = 0;
+ int media_initialized = 0;
+ int media_player_initialized = 0;
+
+ // Construct the producer
+ self = calloc( 1, sizeof( struct producer_libvlc_s ) );
+ producer = calloc( 1, sizeof( struct mlt_producer_s ) );
+
+ if ( self == NULL || producer == NULL ) goto cleanup;
+ if ( mlt_producer_init( producer, self ) != 0 ) goto cleanup;
+
+ // Set libVLC's producer parent
+ self->parent = producer;
+
+ // Set destructor
+ producer->close = producer_close;
+
+ // Initialize libVLC
+ self->libvlc_instance = libvlc_new( 0, NULL );
+ if ( self->libvlc_instance == NULL ) goto cleanup;
+ libvlc_initialized = 1;
+
+ // Left for debugging
+ libvlc_log_set(self->libvlc_instance, log_cb, NULL);
+ pthread_mutex_init(&log_mutex, NULL);
+
+ // Initialize media
+ self->media = libvlc_media_new_path( self->libvlc_instance, file );
+ if ( self->media == NULL ) goto cleanup;
+ media_initialized = 1;
+
+ // Override virtual function for getting frames
+ producer->get_frame = producer_get_frame;
+
+ // Initialize cache and its mutexes
+ self->v_cache = mlt_deque_init( );
+ self->a_cache = mlt_deque_init( );
+
+ pthread_mutex_init( &self->v_cache_mutex, NULL );
+ pthread_cond_init( &self->v_cache_producer_cond, NULL );
+ pthread_cond_init( &self->v_cache_consumer_cond, NULL );
+ pthread_mutex_init( &self->a_cache_mutex, NULL );
+ pthread_cond_init( &self->a_cache_producer_cond, NULL );
+ pthread_cond_init( &self->a_cache_consumer_cond, NULL );
+ pthread_mutex_init( &self->seek_mutex, NULL );
+ pthread_cond_init( &self->seek_cond, NULL );
+ pthread_mutex_init( &self->pts_data_mutex, NULL );
+ pthread_cond_init( &self->pts_data_cond, NULL );
+
+ // Collect stream metadata
+ collect_stream_data( self, file );
+
+ // Set properties to make all profile info available to all functions thorugh them
+ set_properties( self, profile );
+
+ // Setup libVLC smem
+ setup_smem( self );
+
+ // Create smem media player
+ self->media_player = libvlc_media_player_new_from_media( self->media );
+ if ( self->media_player == NULL ) goto cleanup;
+ media_player_initialized = 1;
+
+ collect_timestamp_data( self );
+
+ self->first_pts = NO_PTS_VALUE;
+
+ // TODO: Should this go somewhere else?
+ libvlc_media_player_play( self->media_player );
+
+ return producer;
+
+cleanup:
+ if ( libvlc_initialized ) libvlc_release( self->libvlc_instance );
+ if ( media_initialized ) libvlc_media_release( self->media );
+ if ( media_player_initialized ) libvlc_media_player_release( self->media_player );
+ free( self );
+ free( producer );
+ return NULL;
+}
+
+static void collect_stream_data( producer_libvlc self, char *file )
+{
+ if ( self->media == NULL )
+ {
+ return;
+ }
+
+ int track_i;
+ unsigned int nb_tracks;
+ libvlc_media_track_t *track;
+ libvlc_video_track_t *v_track;
+
+ // Get handles to necessary objects
+ mlt_properties p = MLT_PRODUCER_PROPERTIES( self->parent );
+ libvlc_media_t *media = self->media;
+
+ // Collect media metadata
+ libvlc_media_parse( media );
+
+ // Read and save media metadata to properties
+ libvlc_media_track_t **tracks;
+ nb_tracks = libvlc_media_tracks_get( media, &tracks );
+
+ // Search for default video track to fetch metadata
+ for ( track_i = 0; track_i < nb_tracks; track_i++ ) {
+ track = tracks[ track_i ];
+
+ // We pick first video track as the default one
+ if ( track->i_type == libvlc_track_video ) {
+ v_track = track->video;
+ // This sets metadata, which can be useful for creating auto-profile
+ mlt_properties_set_int( p, "meta.media.width", v_track->i_width );
+ mlt_properties_set_int( p, "meta.media.height", v_track->i_height );
+ mlt_properties_set_int( p, "meta.media.frame_rate_num", v_track->i_frame_rate_num );
+ mlt_properties_set_int( p, "meta.media.frame_rate_den", v_track->i_frame_rate_den );
+ mlt_properties_set_int( p, "meta.media.sample_aspect_num", v_track->i_sar_num );
+ mlt_properties_set_int( p, "meta.media.sample_aspect_den", v_track->i_sar_den );
+ break;
+ }
+ }
+ libvlc_media_tracks_release( tracks, nb_tracks );
+}
+
+static void collect_timestamp_data( producer_libvlc self )
+{
+ // TODO: Fix deadlock if < FRAMES_FOR_DATA_COLLECTION frames in video/audio source
+ // Possible solution: Add listener for EOF event, that signals cond
+ int i;
+ self->collecting_pts_data = 1;
+ self->waiting_for_pts_data = 1;
+ libvlc_media_player_t *media_player = self->media_player;
+ libvlc_media_player_play( media_player );
+ pthread_mutex_lock( &self->pts_data_mutex );
+ while ( !( self->collected_video_pts >= FRAMES_FOR_DATA_COLLECTION && self->collected_audio_pts >= FRAMES_FOR_DATA_COLLECTION ) )
+ {
+ pthread_cond_wait( &self->pts_data_cond, &self->pts_data_mutex );
+ }
+ libvlc_media_player_stop( media_player );
+ for ( i = 1; i < FRAMES_FOR_DATA_COLLECTION; i++ )
+ {
+ self->average_audio_pts_diff += self->audio_pts[ i ] - self->audio_pts[ i - 1 ];
+ self->average_video_pts_diff += self->video_pts[ i ] - self->video_pts[ i - 1 ];
+ }
+ // 0.5 added for rounding
+ self->average_audio_pts_diff = ( double )self->average_audio_pts_diff / ( FRAMES_FOR_DATA_COLLECTION - 1 ) + 0.5;
+ self->average_video_pts_diff = ( double )self->average_video_pts_diff / ( FRAMES_FOR_DATA_COLLECTION - 1 ) + 0.5;
+ printf("MLT LOG average audio diff %" PRId64 "\n", self->average_audio_pts_diff);
+ printf("MLT LOG average video diff %" PRId64 "\n", self->average_video_pts_diff);
+ self->collecting_pts_data = 0;
+ pthread_mutex_unlock( &self->pts_data_mutex );
+}
+
+static void set_properties( producer_libvlc self, mlt_profile profile )
+{
+ // TODO: Don't know what to do about that yet
+ assert( profile != NULL );
+
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( self->parent );
+
+ // Smem uses comma as floating point separator
+ mlt_properties_set_lcnumeric( properties, "C" );
+
+ mlt_properties_set_int( properties, "width", profile->width );
+
+ mlt_properties_set_int( properties, "height", profile->height );
+ mlt_properties_set_double( properties, "fps", mlt_profile_fps( profile ) );
+
+ // Default audio settings
+ mlt_properties_set_int( properties, "channels", 2 );
+ mlt_properties_set_int( properties, "frequency", 48000 );
+}
+
+static void setup_smem( producer_libvlc self )
+{
+ char vcodec[] = "RV24";
+ char acodec[] = "s16l";
+
+ mlt_properties p = MLT_PRODUCER_PROPERTIES( self->parent );
+
+ // Build smem options string
+ char smem_options[ 1000 ];
+ sprintf( smem_options,
+ ":sout=#transcode{"
+ "vcodec=%s,"
+ "fps=%s,"
+ "width=%d,"
+ "height=%d,"
+ "acodec=%s,"
+ "channels=%d,"
+ "samplerate=%d,"
+ "}:smem{"
+ "no-time-sync,"
+ "audio-prerender-callback=%" PRIdPTR ","
+ "audio-postrender-callback=%" PRIdPTR ","
+ "video-prerender-callback=%" PRIdPTR ","
+ "video-postrender-callback=%" PRIdPTR ","
+ "audio-data=%" PRIdPTR ","
+ "video-data=%" PRIdPTR ","
+ "}",
+ vcodec,
+ mlt_properties_get( p, "fps" ),
+ mlt_properties_get_int( p, "width" ),
+ mlt_properties_get_int( p, "height" ),
+ acodec,
+ mlt_properties_get_int( p, "channels" ),
+ mlt_properties_get_int( p, "samplerate" ),
+ (intptr_t)(void*)&audio_prerender_callback,
+ (intptr_t)(void*)&audio_postrender_callback,
+ (intptr_t)(void*)&video_prerender_callback,
+ (intptr_t)(void*)&video_postrender_callback,
+ (intptr_t)(void*)self,
+ (intptr_t)(void*)self );
+
+ // Supply smem options to libVLC
+ libvlc_media_add_option( self->media, smem_options );
+}
+
+// Work in progress - not being used yet
+static void seek_smem( producer_libvlc self, mlt_position position )
+{
+ libvlc_media_player_t *media_player = self->media_player;
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( self->parent );
+ double fps = mlt_properties_get_double( properties, "fps");
+
+ int64_t current_timestamp = libvlc_media_player_get_time( self->media_player );
+ // Seek timestamp is in milliseconds
+ int64_t seek_timestamp = ( double )position / fps * 1000.0;
+ // If we're behind by less than seek_threshold, we just wait for appropriate frame instead of seek
+ int seek_threshold = 500;
+ if ( seek_timestamp < current_timestamp || seek_timestamp - current_timestamp > seek_threshold )
+ {
+ // We are seeking...
+ pthread_mutex_lock( &self->seek_mutex );
+ printf("seeking to time %" PRId64 "\n", seek_timestamp);
+ self->seek_pending = 1;
+ libvlc_media_player_set_time( media_player, seek_timestamp );
+ printf("seeking pending\n");
+ while ( self->seek_pending )
+ {
+ pthread_cond_wait( &self->seek_cond, &self->seek_mutex );
+ }
+ printf("seeking complete\n");
+ }
+}
+
+static void audio_prerender_callback( void* p_audio_data, uint8_t** pp_pcm_buffer, size_t size )
+{
+ // Flag for termination purposes
+ int mutex_locked = 0;
+
+ producer_libvlc self = p_audio_data;
+
+ // If we're collecting metadata, we are not moving data to cache
+ if ( self->collecting_pts_data )
+ {
+ // We haven't collected necessary information yet
+ if ( !( self->collected_audio_pts == FRAMES_FOR_DATA_COLLECTION ) )
+ {
+ // Just allocate space for buffers and go on
+ uint8_t *buffer = mlt_pool_alloc( size * sizeof( uint8_t ) );
+ *pp_pcm_buffer = buffer;
+ }
+ // We already have pts data from first FRAMES_FOR_DATA_COLLECTION frames
+ else
+ {
+ // Cancel rendering
+ *pp_pcm_buffer = NULL;
+ }
+ }
+ else
+ {
+ // If we're terminating, we need to abort render
+ if ( self->terminating )
+ goto terminate;
+
+ // Lock cache mutex
+ pthread_mutex_lock( &self->a_cache_mutex );
+ mutex_locked = 1;
+
+ // Wait for space in cache
+ self->a_cache_producers++;
+ while ( mlt_deque_count( self->a_cache ) == MAX_CACHE_SIZE )
+ {
+ pthread_cond_wait( &self->a_cache_producer_cond, &self->a_cache_mutex );
+
+ // Terminate function could have woken us up
+ if ( self->terminating )
+ goto terminate;
+ }
+ self->a_cache_producers--;
+
+ // Allocate space for buffer and proceed with rendering
+ uint8_t *buffer = mlt_pool_alloc( size * sizeof( uint8_t ) );
+ *pp_pcm_buffer = buffer;
+ }
+
+ return;
+
+terminate:
+ *pp_pcm_buffer = NULL;
+ // If we own the mutex, we need to release it for destruction
+ if ( mutex_locked )
+ pthread_mutex_unlock( &self->a_cache_mutex );
+ return;
+}
+
+static void audio_postrender_callback( void* p_audio_data, uint8_t* p_pcm_buffer, unsigned int channels,
+ unsigned int rate, unsigned int nb_samples, unsigned int bits_per_sample,
+ size_t size, int64_t pts )
+{
+ assert( ( nb_samples * AUDIO_SAMPLE_SIZE * channels ) <= size );
+
+ producer_libvlc self = p_audio_data;
+
+ // We're just collecting pts timestamps now
+ if ( self->collecting_pts_data )
+ {
+ // We need those mutexes in order not to lose a cond wakeup
+ pthread_mutex_lock( &self->pts_data_mutex );
+ if ( self->collected_audio_pts < FRAMES_FOR_DATA_COLLECTION )
+ {
+ self->audio_pts[ self->collected_audio_pts ] = pts;
+ self->collected_audio_pts += 1;
+ if ( self->collected_audio_pts == FRAMES_FOR_DATA_COLLECTION
+ && self->collected_video_pts == FRAMES_FOR_DATA_COLLECTION && self->waiting_for_pts_data )
+ {
+ self->waiting_for_pts_data = 0;
+ pthread_cond_signal( &self->pts_data_cond );
+ }
+ }
+ pthread_mutex_unlock( &self->pts_data_mutex );
+ }
+ // Normal "move to cache" procedure
+ else
+ {
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( self->parent );
+
+ double fps = mlt_properties_get_double( properties, "fps" );
+
+ // This is how many samples per channel we need for the current audio frame
+ int needed_samples = mlt_sample_calculator( fps, rate, self->current_audio_frame );
+ // Size of a complete single sample is equal to AUDIO_SAMPLE_SIZE * channels
+ int buffered_samples = self->ta_buffer_size / ( AUDIO_SAMPLE_SIZE * channels );
+ // How many samples have we computed
+ int calculated_samples = nb_samples + buffered_samples;
+ // We have all data needed to complete a frame
+ if ( calculated_samples >= needed_samples )
+ {
+ size_t samples_to_buffer = calculated_samples - needed_samples;
+ size_t new_ta_buffer_size = samples_to_buffer * AUDIO_SAMPLE_SIZE * channels;
+ size_t frame_samples_size = needed_samples * AUDIO_SAMPLE_SIZE * channels;
+ // Allocate buffer for frame samples
+ uint8_t *frame_samples = mlt_pool_alloc( frame_samples_size * sizeof( uint8_t ) );
+ // And for the new temp audio buffer
+ uint8_t *new_ta_buffer = mlt_pool_alloc( new_ta_buffer_size * sizeof( uint8_t ) );
+ // Copy frame samples to buffer and initialize new temp buffer
+ if ( buffered_samples < needed_samples ) // We need to use both buffers
+ {
+ // Copy entire temp audio buffer contents
+ memcpy( frame_samples, self->ta_buffer, self->ta_buffer_size );
+ // Copy samples from pcm buffer that we still need
+ memcpy( frame_samples + self->ta_buffer_size, p_pcm_buffer,
+ ( needed_samples - buffered_samples ) * AUDIO_SAMPLE_SIZE * channels );
+ // Copy remaining samples to new temp audio buffer
+ memcpy( new_ta_buffer,
+ p_pcm_buffer + ( needed_samples - buffered_samples ) * AUDIO_SAMPLE_SIZE * channels,
+ new_ta_buffer_size );
+ }
+ else // We need to use samples from temp audio buffer only
+ {
+ // Copy all needed samples from temp audio buffer
+ memcpy( frame_samples, self->ta_buffer, frame_samples_size );
+ // Copy remaining samples from both buffers to new temp buffer
+ memcpy( new_ta_buffer, self->ta_buffer + frame_samples_size,
+ self->ta_buffer_size - frame_samples_size );
+ memcpy( new_ta_buffer + ( self->ta_buffer_size - frame_samples_size ), p_pcm_buffer,
+ nb_samples * AUDIO_SAMPLE_SIZE * channels );
+ }
+
+ // We don't need those buffers anymore
+ mlt_pool_release( self->ta_buffer );
+ mlt_pool_release( p_pcm_buffer );
+
+ // Move our new temp audio buffer to producer
+ self->ta_buffer = new_ta_buffer;
+ self->ta_buffer_size = new_ta_buffer_size;
+
+ // Move samples to cache
+ a_cache_item a_item = calloc( 1, sizeof( struct a_cache_item_s ) );
+ a_item->buffer = frame_samples;
+ a_item->samples = needed_samples;
+ a_item->sample_rate = rate;
+ a_item->channels = channels;
+ a_item->pts = pts;
+
+ mlt_deque_push_back( self->a_cache, a_item );
+
+ // We will be computing next frame now
+ self->current_audio_frame++;
+
+ // If there are consumers waiting, signal them
+ if ( self->a_cache_consumers )
+ {
+ pthread_cond_signal( &self->a_cache_consumer_cond );
+ }
+ }
+ // We need to cache the data for later
+ else
+ {
+ // Make temp audio buffer bigger to get all the samples
+ self->ta_buffer =
+ mlt_pool_realloc( self->ta_buffer, self->ta_buffer_size + ( nb_samples * AUDIO_SAMPLE_SIZE * channels ) );
+
+ // TODO: What now? Cleanup and terminate?
+ assert( self->ta_buffer != NULL );
+
+ memcpy( self->ta_buffer + self->ta_buffer_size,
+ p_pcm_buffer, ( nb_samples * AUDIO_SAMPLE_SIZE * channels ) );
+ self->ta_buffer_size += ( nb_samples * AUDIO_SAMPLE_SIZE * channels );
+ mlt_pool_release( p_pcm_buffer );
+ }
+ pthread_mutex_unlock( &self->a_cache_mutex );
+ }
+}
+
+static void video_prerender_callback( void *data, uint8_t **p_buffer, size_t size )
+{
+ // Flag for termination purposes
+ int mutex_locked = 0;
+
+ producer_libvlc self = data;
+
+ // Same logic as in audio callbacks (check comments there)
+ if ( self->collecting_pts_data )
+ {
+ if ( !( self->collected_video_pts == FRAMES_FOR_DATA_COLLECTION ) )
+ {
+ uint8_t *buffer = mlt_pool_alloc( size * sizeof( uint8_t ) );
+ *p_buffer = buffer;
+ }
+ else
+ {
+ *p_buffer = NULL;
+ }
+ }
+ else
+ {
+ // If we're terminating, we need to abort render
+ if ( self->terminating )
+ goto terminate;
+
+ // Aquire cache mutex
+ pthread_mutex_lock( &self->v_cache_mutex );
+ mutex_locked = 1;
+
+ // Inform all threads we are waiting on cond
+ self->v_cache_producers++;
+
+ // Wait until there is free space in cache
+ while( mlt_deque_count( self->v_cache ) == MAX_CACHE_SIZE )
+ {
+ pthread_cond_wait( &self->v_cache_producer_cond, &self->v_cache_mutex );
+
+ // Terminate function could have woken us up
+ if ( self->terminating )
+ goto terminate;
+ }
+
+ // We're not waiting on cond anymore
+ self->v_cache_producers--;
+
+ // Allocate memory and proceed with rendering
+ uint8_t *buffer = mlt_pool_alloc( size * sizeof( uint8_t ) );
+ *p_buffer = buffer;
+ }
+
+ return;
+
+terminate:
+ *p_buffer = NULL;
+ // Release mutex for destruction
+ if ( mutex_locked )
+ pthread_mutex_unlock( &self->v_cache_mutex );
+ return;
+}
+
+static void video_postrender_callback( void *data, uint8_t *buffer, int width, int height,
+ int bpp, size_t size, int64_t pts )
+{
+ size_t i;
+
+ producer_libvlc self = data;
+ mlt_properties properties = MLT_PRODUCER_PROPERTIES( self->parent );
+
+ // Same logic as in audio callbacks (check comments there)
+ if ( self->collecting_pts_data )
+ {
+ pthread_mutex_lock( &self->pts_data_mutex );
+ if ( self->collected_video_pts < FRAMES_FOR_DATA_COLLECTION )
+ {
+ self->video_pts[ self->collected_video_pts ] = pts;
+ self->collected_video_pts += 1;
+ if ( self->collected_audio_pts == FRAMES_FOR_DATA_COLLECTION
+ && self->collected_video_pts == FRAMES_FOR_DATA_COLLECTION && self->waiting_for_pts_data )
+ {
+ self->waiting_for_pts_data = 0;
+ pthread_cond_signal( &self->pts_data_cond );
+ }
+ }
+ pthread_mutex_unlock( &self->pts_data_mutex );
+ }
+ else
+ {
+ // Get first pts as a frame of reference
+ if ( self->first_pts == NO_PTS_VALUE )
+ self->first_pts = pts;
+
+ printf("VIDEO PTS %" PRId64 "\n", pts);
+ printf("VIDEO timestamp %" PRId64 "\n", (pts - self->first_pts)/1000);
+
+ // For some reason width and height returned by smem
+ // seems bogus (when using width * height * bpp / 8 as size
+ // of returned buffer, accessing last bytes causes segfault),
+ // so we're using width and height that we've inputted into
+ // transcoder when constructing smem
+ width = mlt_properties_get_int( properties, "width" );
+ height = mlt_properties_get_int( properties, "height" );
+ size_t buffer_size = width * height * bpp / 8;
+
+ // Allocate space for cache data
+ v_cache_item v_item = calloc( 1, sizeof( struct v_cache_item_s ) );
+
+ // Exchange red with blue (for some reason libVLC has
+ // has different representation of RGB24 than MLT)
+ for (i = 0; i+2 < buffer_size; i += 3)
+ {
+ uint8_t temp = buffer[ i ];
+ buffer[ i ] = buffer[ i + 2 ];
+ buffer[ i + 2 ] = temp;
+ }
+
+ // Move stuff to cache
+ v_item->buffer = buffer;
+ v_item->size = size;
+ v_item->bpp = bpp;
+ v_item->width = width;
+ v_item->height = height;
+ v_item->pts = pts;
+
+ // Push cache item into cache
+ mlt_deque_push_back( self->v_cache, v_item );
+
+ // Signal waiting consumers, if any
+ if ( self->v_cache_consumers )
+ {
+ pthread_cond_signal( &self->v_cache_consumer_cond );
+ }
+
+ // Unlock cache access
+ pthread_mutex_unlock( &self->v_cache_mutex );
+ }
+}
+
+static int producer_get_audio( mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples )
+{
+ // Get the producer
+ producer_libvlc self = mlt_frame_pop_audio( frame );
+
+ pthread_mutex_lock( &self->a_cache_mutex );
+ self->a_cache_consumers++;
+ // TODO: This logic will have to be rewritten when we implement seeking,
+ // if we manipulate the video's position, we have no guarantee that
+ // data in cache is actually relevant to us (same for video)
+ while ( mlt_deque_count( self->a_cache ) == 0 ) {
+ pthread_cond_wait( &self->a_cache_consumer_cond, &self->a_cache_mutex );
+ }
+ self->a_cache_consumers--;
+
+ a_cache_item a_item = mlt_deque_pop_front( self->a_cache );
+ *buffer = a_item->buffer;
+ // We set libVLC's smem to use this format
+ *format = mlt_audio_s16;
+ *frequency = a_item->sample_rate;
+ *channels = a_item->channels;
+ *samples = a_item->samples;
+
+ free( a_item );
+
+ // Sets audio buffer destructor
+ mlt_frame_set_audio( frame, *buffer, *format, *samples * *channels * AUDIO_SAMPLE_SIZE, mlt_pool_release );
+
+ if ( self->a_cache_producers )
+ {
+ pthread_cond_signal( &self->a_cache_producer_cond );
+ }
+ pthread_mutex_unlock( &self->a_cache_mutex );
+ return 0;
+}
+
+static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable )
+{
+ // Get the producer
+ producer_libvlc self = mlt_frame_pop_service( frame );
+
+ mlt_producer producer = self->parent;
+
+ // libVLC produces frames in this format
+ *format = mlt_image_rgb24;
+
+ // Lock the cache mutex
+ pthread_mutex_lock( &self->v_cache_mutex );
+
+ // Inform other threads that you're waiting for data
+ self->v_cache_consumers++;
+
+ // Wait for data
+ while ( mlt_deque_count( self->v_cache ) == 0 )
+ {
+ pthread_cond_wait( &self->v_cache_consumer_cond, &self->v_cache_mutex );
+ }
+
+ // Not waiting for data anymore
+ self->v_cache_consumers--;
+
+ // Get data, rewrite it, and free cache item
+ v_cache_item v_item = mlt_deque_pop_front( self->v_cache );
+ *width = v_item->width;
+ *height = v_item->height;
+ *buffer = v_item->buffer;
+ free( v_item );
+
+ // Sets image buffer destructor
+ mlt_frame_set_image( frame, *buffer, *width * *height * 3, mlt_pool_release );
+
+ // Signal waiting producers, if any
+ if ( self->v_cache_producers )
+ {
+ pthread_cond_signal( &self->v_cache_producer_cond );
+ }
+
+ // We're finished with cache
+ pthread_mutex_unlock( &self->v_cache_mutex );
+
+ return 0;
+}
+
+static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
+{
+ // Get handle to libVLC's producer
+ producer_libvlc self = producer->child;
+
+ // Aquire current position
+ mlt_position current_position = mlt_producer_position( producer );
+
+ // Seek to current position
+ // seek_smem( self, current_position );
+
+ // Access the private data (producer is needed to get profile)
+ mlt_service service = MLT_PRODUCER_SERVICE( producer );
+
+ // Initialize frame with producer's profile and get its properties
+ *frame = mlt_frame_init( service );
+ mlt_properties frame_properties = mlt_frame_properties( *frame );
+
+ // Push get_image and its argument to video stack of frame
+ mlt_frame_push_service( *frame, self );
+ mlt_frame_push_get_image( *frame, producer_get_image );
+
+ mlt_frame_push_audio( *frame, self );
+ mlt_frame_push_audio( *frame, producer_get_audio );
+
+ // Prepare next frame
+ mlt_producer_prepare_next( producer );
+
+ return 0;
+}
+
+static void producer_close( mlt_producer parent )
+{
+ if ( parent != NULL ) {
+ producer_libvlc self = parent->child;
+
+ // Stop smem threads
+ self->terminating = 1;
+ libvlc_media_player_stop( self->media_player );
+
+ // Release libVLC objects
+ libvlc_media_player_release( self->media_player );
+ libvlc_media_release( self->media );
+ libvlc_release( self->libvlc_instance );
+
+ // Clear video cache
+ v_cache_item v_item;
+ while ( v_item = mlt_deque_pop_front( self->v_cache ) ) {
+ mlt_pool_release( v_item->buffer );
+ free( v_item );
+ }
+
+ // Clear audio cache
+ a_cache_item a_item;
+ while ( a_item = mlt_deque_pop_front( self->a_cache ) ) {
+ mlt_pool_release( a_item->buffer );
+ free( a_item );
+ }
+
+ // Clear ta_buffer
+ mlt_pool_release( self->ta_buffer );
+
+ // Clear mutexes and conds
+ pthread_mutex_destroy( &self->v_cache_mutex );
+ pthread_cond_destroy( &self->v_cache_producer_cond );
+ pthread_cond_destroy( &self->v_cache_consumer_cond );
+ pthread_mutex_destroy( &self->a_cache_mutex );
+ pthread_cond_destroy( &self->a_cache_producer_cond );
+ pthread_cond_destroy( &self->a_cache_consumer_cond );
+ pthread_mutex_destroy( &self->seek_mutex );
+ pthread_cond_destroy( &self->seek_cond );
+ pthread_mutex_destroy( &self->pts_data_mutex );
+ pthread_cond_destroy( &self->pts_data_cond );
+
+ // Free allocated memory for libvlc_producer
+ free( self );
+
+ // Call overriden destructor
+ parent->close = NULL;
+ mlt_producer_close( parent );
+ }
+}
--
2.7.4 (Apple Git-66)
More information about the Vlmc-devel
mailing list