From 928ea3c5e79d2ca150f1c9d55e720ccb595da492 Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Mon, 25 Feb 2013 22:38:06 -0600 Subject: [PATCH] Add new avsync module --- src/modules/avsync/Makefile | 38 ++ src/modules/avsync/consumer_blipflash.c | 427 ++++++++++++++++++++++ src/modules/avsync/consumer_blipflash.yml | 34 ++ src/modules/avsync/factory.c | 42 +++ src/modules/avsync/producer_blipflash.c | 305 ++++++++++++++++ src/modules/avsync/producer_blipflash.yml | 36 ++ 6 files changed, 882 insertions(+) create mode 100644 src/modules/avsync/Makefile create mode 100644 src/modules/avsync/consumer_blipflash.c create mode 100644 src/modules/avsync/consumer_blipflash.yml create mode 100644 src/modules/avsync/factory.c create mode 100644 src/modules/avsync/producer_blipflash.c create mode 100644 src/modules/avsync/producer_blipflash.yml diff --git a/src/modules/avsync/Makefile b/src/modules/avsync/Makefile new file mode 100644 index 00000000..c0362901 --- /dev/null +++ b/src/modules/avsync/Makefile @@ -0,0 +1,38 @@ +CFLAGS += -I../.. + +LDFLAGS += -L../../framework -lmlt -lm -lpthread + +include ../../../config.mak + +TARGET = ../libmltavsync$(LIBSUF) + +OBJS = factory.o \ + producer_blipflash.o \ + consumer_blipflash.o + +ASM_OBJS = + +SRCS := $(OBJS:.o=.c) + +all: $(TARGET) + +$(TARGET): $(OBJS) $(ASM_OBJS) + $(CC) $(SHFLAGS) -o $@ $(OBJS) $(ASM_OBJS) $(LDFLAGS) + +depend: $(SRCS) + $(CC) -MM $(CFLAGS) $^ 1>.depend + +distclean: clean + rm -f .depend + +clean: + rm -f $(OBJS) $(ASM_OBJS) $(TARGET) + +install: all + install -m 755 $(TARGET) "$(DESTDIR)$(moduledir)" + install -d "$(DESTDIR)$(mltdatadir)/avsync" + install -m 644 *.yml "$(DESTDIR)$(mltdatadir)/avsync" + +ifneq ($(wildcard .depend),) +include .depend +endif diff --git a/src/modules/avsync/consumer_blipflash.c b/src/modules/avsync/consumer_blipflash.c new file mode 100644 index 00000000..5de8a559 --- /dev/null +++ b/src/modules/avsync/consumer_blipflash.c @@ -0,0 +1,427 @@ +/* + * consumer_blipflash.c -- a consumer to measure A/V sync from a blip/flash + * source + * Copyright (C) 2013 Brian Matherly + * Author: Brian Matherly + * + * 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 + * Lesser General Public License for more details. + * + * 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 + */ + +// mlt Header files +#include +#include +#include + +// System header files +#include +#include +#include +#include +#include + +// Private constants +#define SAMPLE_FREQ 48000 +#define FLASH_LUMA_THRESHOLD 150 +#define BLIP_THRESHOLD 0.5 + +// Private types +typedef struct +{ + int64_t flash_history[2]; + int flash_history_count; + int64_t blip_history[2]; + int blip_history_count; + int blip_in_progress; + int samples_since_blip; + int blip; + int flash; + int sample_offset; + FILE* out_file; + int report_frames; +} avsync_stats; + +// Forward references. +static int consumer_start( mlt_consumer consumer ); +static int consumer_stop( mlt_consumer consumer ); +static int consumer_is_stopped( mlt_consumer consumer ); +static void *consumer_thread( void *arg ); +static void consumer_close( mlt_consumer consumer ); + +/** Initialize the consumer. +*/ + +mlt_consumer consumer_blipflash_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ) +{ + // Allocate the consumer + mlt_consumer consumer = mlt_consumer_new( profile ); + mlt_properties consumer_properties = MLT_CONSUMER_PROPERTIES( consumer ); + avsync_stats* stats = NULL; + + // If memory allocated and initializes without error + if ( consumer != NULL ) + { + // Set up start/stop/terminated callbacks + consumer->close = consumer_close; + consumer->start = consumer_start; + consumer->stop = consumer_stop; + consumer->is_stopped = consumer_is_stopped; + + stats = mlt_pool_alloc( sizeof( avsync_stats ) ); + stats->flash_history_count = 0; + stats->blip_history_count = 0; + stats->blip_in_progress = 0; + stats->samples_since_blip = 0; + stats->blip = 0; + stats->flash = 0; + stats->sample_offset = INT_MAX; + stats->report_frames = 0; + stats->out_file = stdout; + if ( arg != NULL ) + { + FILE* out_file = fopen( arg, "w" ); + if ( out_file != NULL ) + stats->out_file = out_file; + } + mlt_properties_set_data( consumer_properties, "_stats", stats, 0, NULL, NULL ); + + mlt_properties_set( consumer_properties, "report", "blip" ); + } + + // Return this + return consumer; +} + +/** Start the consumer. +*/ + +static int consumer_start( mlt_consumer consumer ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + + // Check that we're not already running + if ( !mlt_properties_get_int( properties, "_running" ) ) + { + // Allocate a thread + pthread_t *thread = calloc( 1, sizeof( pthread_t ) ); + + // Assign the thread to properties + mlt_properties_set_data( properties, "_thread", thread, sizeof( pthread_t ), free, NULL ); + + // Set the running state + mlt_properties_set_int( properties, "_running", 1 ); + + // Create the thread + pthread_create( thread, NULL, consumer_thread, consumer ); + } + return 0; +} + +/** Stop the consumer. +*/ + +static int consumer_stop( mlt_consumer consumer ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + + // Check that we're running + if ( mlt_properties_get_int( properties, "_running" ) ) + { + // Get the thread + pthread_t *thread = mlt_properties_get_data( properties, "_thread", NULL ); + + // Stop the thread + mlt_properties_set_int( properties, "_running", 0 ); + + // Wait for termination + if ( thread ) + pthread_join( *thread, NULL ); + } + + return 0; +} + +/** Determine if the consumer is stopped. +*/ + +static int consumer_is_stopped( mlt_consumer consumer ) +{ + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + return !mlt_properties_get_int( properties, "_running" ); +} + +static void detect_flash( mlt_frame frame, mlt_position pos, double fps, avsync_stats* stats ) +{ + int width = 0; + int height = 0; + mlt_image_format format = mlt_image_yuv422; + uint8_t* image = NULL; + int error = mlt_frame_get_image( frame, &image, &format, &width, &height, 0 ); + + if ( !error && format == mlt_image_yuv422 && image != NULL ) + { + int i, j = 0; + int y_accumulator = 0; + + // Add up the luma values from 4 samples in 4 different quadrants. + for( i = 1; i < 3; i++ ) + { + int x = ( width / 3 ) * i; + x = x - x % 2; // Make sure this is a luma sample + for( j = 1; j < 3; j++ ) + { + int y = ( height / 3 ) * j; + y_accumulator += image[ y * height * 2 + x * 2 ]; + } + } + // If the average luma value is > 150, assume it is a flash. + stats->flash = ( y_accumulator / 4 ) > FLASH_LUMA_THRESHOLD; + } + + if( stats->flash ) + { + stats->flash_history[1] = stats->flash_history[0]; + stats->flash_history[0] = mlt_sample_calculator_to_now( fps, SAMPLE_FREQ, pos ); + if( stats->flash_history_count < 2 ) + { + stats->flash_history_count++; + } + } +} + +static void detect_blip( mlt_frame frame, mlt_position pos, double fps, avsync_stats* stats ) +{ + int frequency = SAMPLE_FREQ; + int channels = 1; + int samples = mlt_sample_calculator( fps, frequency, pos ); + mlt_audio_format format = mlt_audio_float; + float* buffer = NULL; + int error = mlt_frame_get_audio( frame, (void**) &buffer, &format, &frequency, &channels, &samples ); + + if ( !error && format == mlt_audio_float && buffer != NULL ) + { + int i = 0; + + for( i = 0; i < samples; i++ ) + { + if( !stats->blip_in_progress ) + { + if( buffer[i] > BLIP_THRESHOLD || buffer[i] < -BLIP_THRESHOLD ) + { + // This sample must start a blip + stats->blip_in_progress = 1; + stats->samples_since_blip = 0; + + stats->blip_history[1] = stats->blip_history[0]; + stats->blip_history[0] = mlt_sample_calculator_to_now( fps, SAMPLE_FREQ, pos ); + stats->blip_history[0] += i; + if( stats->blip_history_count < 2 ) + { + stats->blip_history_count++; + } + stats->blip = 1; + } + } + else + { + if( buffer[i] > -BLIP_THRESHOLD && buffer[i] < BLIP_THRESHOLD ) + { + if( ++stats->samples_since_blip > frequency / 1000 ) + { + // One ms of silence means the blip is over + stats->blip_in_progress = 0; + stats->samples_since_blip = 0; + } + } + else + { + stats->samples_since_blip = 0; + } + } + } + } +} + +static void calculate_sync( avsync_stats* stats ) +{ + if( stats->blip || stats->flash ) + { + if( stats->flash_history_count > 0 && + stats->blip_history_count > 0 && + stats->blip_history[0] == stats->flash_history[0] ) + { + // The flash and blip occurred at the same time. + stats->sample_offset = 0; + } + if( stats->flash_history_count > 1 && + stats->blip_history_count > 0 && + stats->blip_history[0] <= stats->flash_history[0] && + stats->blip_history[0] >= stats->flash_history[1] ) + { + // The latest blip occurred between two flashes + if( stats->flash_history[0] - stats->blip_history[0] > + stats->blip_history[0] - stats->flash_history[1] ) + { + // Blip is closer to the previous flash. + // F1---B0--------F0 + // ^----^ + // Video leads audio (negative number). + stats->sample_offset = (int)(stats->flash_history[1] - stats->blip_history[0] ); + } + else + { + // Blip is closer to the current flash. + // F1--------B0---F0 + // ^----^ + // Audio leads video (positive number). + stats->sample_offset = (int)(stats->flash_history[0] - stats->blip_history[0]); + } + } + else if( stats->blip_history_count > 1 && + stats->flash_history_count > 0 && + stats->flash_history[0] <= stats->blip_history[0] && + stats->flash_history[0] >= stats->blip_history[1] ) + { + // The latest flash occurred between two blips + if( stats->blip_history[0] - stats->flash_history[0] > + stats->flash_history[0] - stats->blip_history[1] ) + { + // Flash is closer to the previous blip. + // B1---F0--------B0 + // ^----^ + // Audio leads video (positive number). + stats->sample_offset = (int)(stats->flash_history[0] - stats->blip_history[1]); + } + else + { + // Flash is closer to the latest blip. + // B1--------F0---B0 + // ^----^ + // Video leads audio (negative number). + stats->sample_offset = (int)(stats->flash_history[0] - stats->blip_history[0] ); + } + } + } +} + +static void report_results( avsync_stats* stats, mlt_position pos ) +{ + if( stats->report_frames || stats->blip ) + { + if( stats->sample_offset == INT_MAX ) + { + fprintf( stats->out_file, "%d\t??\n", pos ); + } + else + { + // Convert to milliseconds. + double ms_offset = (double)stats->sample_offset * 1000.0 / (double)SAMPLE_FREQ; + fprintf( stats->out_file, "%d\t%02.02f\n", pos, ms_offset ); + } + } + stats->blip = 0; + stats->flash = 0; +} + +/** The main thread - the argument is simply the consumer. +*/ + +static void *consumer_thread( void *arg ) +{ + // Map the argument to the object + mlt_consumer consumer = arg; + + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + + // Convenience functionality + int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" ); + int terminated = 0; + + // Frame and size + mlt_frame frame = NULL; + + // Loop while running + while( !terminated && mlt_properties_get_int( properties, "_running" ) ) + { + // Get the frame + frame = mlt_consumer_rt_frame( consumer ); + + // Check for termination + if ( terminate_on_pause && frame != NULL ) + terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0; + + // Check that we have a frame to work with + if ( frame ) + { + avsync_stats* stats = mlt_properties_get_data( properties, "_stats", NULL ); + double fps = mlt_properties_get_double( properties, "fps" ); + mlt_position pos = mlt_frame_get_position( frame ); + + if( !strcmp( mlt_properties_get( properties, "report" ), "frame" ) ) + { + stats->report_frames = 1; + } + else + { + stats->report_frames = 0; + } + + detect_flash( frame, pos, fps, stats ); + detect_blip( frame, pos, fps, stats ); + calculate_sync( stats ); + report_results( stats, pos ); + + // Close the frame + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); + mlt_frame_close( frame ); + } + } + + // Indicate that the consumer is stopped + mlt_properties_set_int( properties, "_running", 0 ); + mlt_consumer_stopped( consumer ); + + return NULL; +} + +/** Close the consumer. +*/ + +static void consumer_close( mlt_consumer consumer ) +{ + mlt_properties consumer_properties = MLT_CONSUMER_PROPERTIES( consumer ); + avsync_stats* stats = mlt_properties_get_data( consumer_properties, "_stats", NULL ); + + // Stop the consumer + mlt_consumer_stop( consumer ); + + // Close the file + if( stats->out_file != stdout ) + { + fclose( stats->out_file ); + } + + // Clean up memory + mlt_pool_release( stats ); + + // Close the parent + mlt_consumer_close( consumer ); + + // Free the memory + free( consumer ); +} diff --git a/src/modules/avsync/consumer_blipflash.yml b/src/modules/avsync/consumer_blipflash.yml new file mode 100644 index 00000000..5c8f40e5 --- /dev/null +++ b/src/modules/avsync/consumer_blipflash.yml @@ -0,0 +1,34 @@ +schema_version: 0.1 +type: consumer +identifier: blipflash +title: Blip Flash +version: 1 +copyright: Brian Matherly +creator: Brian Matherly +license: LGPLv2.1 +language: en +tags: + - Video + - Audio +description: > + Calculate the A/V sync for a blip flash source. + Sync can be recalculated whenever a blip or a flash is detected. +parameters: + - identifier: argument + title: Report File + type: string + description: > + The file to report the results to. If empty, the results will be reported to standard out. + required: no + widget: filesave + - identifier: report + title: Report Style + type: string + description: > + When to report sync - every frame or only when blips occur. + default: blip + values: + - blip + - frame + mutable: yes + widget: combo diff --git a/src/modules/avsync/factory.c b/src/modules/avsync/factory.c new file mode 100644 index 00000000..79687fe1 --- /dev/null +++ b/src/modules/avsync/factory.c @@ -0,0 +1,42 @@ +/* + * factory.c -- the factory method interfaces + * Copyright (C) 2013 Brian Matherly + * Author: Brian Matherly + * + * 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 + * Lesser General Public License for more details. + * + * 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 +#include +#include + +extern mlt_consumer consumer_blipflash_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); +extern mlt_producer producer_blipflash_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); + +static mlt_properties metadata( mlt_service_type type, const char *id, void *data ) +{ + char file[ PATH_MAX ]; + snprintf( file, PATH_MAX, "%s/avsync/%s", mlt_environment( "MLT_DATA" ), (char*) data ); + return mlt_properties_parse_yaml( file ); +} + +MLT_REPOSITORY +{ + MLT_REGISTER( consumer_type, "blipflash", consumer_blipflash_init ); + MLT_REGISTER( producer_type, "blipflash", producer_blipflash_init ); + + MLT_REGISTER_METADATA( consumer_type, "blipflash", metadata, "consumer_blipflash.yml" ); + MLT_REGISTER_METADATA( producer_type, "blipflash", metadata, "producer_blipflash.yml" ); +} diff --git a/src/modules/avsync/producer_blipflash.c b/src/modules/avsync/producer_blipflash.c new file mode 100644 index 00000000..e677445e --- /dev/null +++ b/src/modules/avsync/producer_blipflash.c @@ -0,0 +1,305 @@ +/* + * producer_blipflash.c -- blip/flash generating producer + * Copyright (C) 2013 Brian Matherly + * Author: Brian Matherly + * + * 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 + * Lesser General Public License for more details. + * + * 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 +#include +#include +#include +#include + +/** Fill an audio buffer with 1kHz "blip" samples. +*/ + +static void fill_blip( mlt_properties producer_properties, float* buffer, int frequency, int channels, int samples ) +{ + int new_size = samples * channels * sizeof( float ); + int old_size = 0; + float* blip = mlt_properties_get_data( producer_properties, "_blip", &old_size ); + + if( !blip || new_size > old_size ) + { + blip = mlt_pool_alloc( new_size ); + + // Fill the blip buffer + if ( blip != NULL ) + { + int s = 0; + int c = 0; + + for( s = 0; s < samples; s++ ) + { + float f = 1000.0; + float t = (float)s/(float)frequency; + // Add 90 deg so the blip always starts at 1 for easy detection. + float phase = M_PI / 2; + float value = sin( 2*M_PI*f*t + phase ); + + for( c = 0; c < channels; c++ ) + { + float* sample_ptr = ((float*) blip) + (c * samples) + s; + *sample_ptr = value; + } + } + } + // Cache the audio blip to save from regenerating it with every blip. + mlt_properties_set_data( producer_properties, "_blip", blip, new_size, mlt_pool_release, NULL ); + }; + + memcpy( buffer, blip, new_size ); +} + +static int producer_get_audio( mlt_frame frame, int16_t** buffer, mlt_audio_format* format, int* frequency, int* channels, int* samples ) +{ + mlt_producer producer = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "_producer_blipflash", NULL ); + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + int size = *samples * *channels * sizeof( float ); + double fps = mlt_producer_get_fps( producer ); + int frames = mlt_frame_get_position( frame ) + mlt_properties_get_int( producer_properties, "offset" ); + int seconds = frames / fps; + + // Correct the returns if necessary + *format = mlt_audio_float; + *frequency = *frequency <= 0 ? 48000 : *frequency; + *channels = *channels <= 0 ? 2 : *channels; + *samples = *samples <= 0 ? mlt_sample_calculator( fps, *frequency, frames ) : *samples; + + // Allocate the buffer + *buffer = mlt_pool_alloc( size ); + + // Determine if this should be a blip or silence. + frames = frames % lrint( fps ); + seconds = seconds % mlt_properties_get_int( producer_properties, "period" ); + if( seconds == 0 && frames == 0 ) + { + fill_blip( producer_properties, (float*)*buffer, *frequency, *channels, *samples ); + } + else + { + // Fill silence. + memset( *buffer, 0, size ); + } + + // Set the buffer for destruction + mlt_frame_set_audio( frame, *buffer, *format, size, mlt_pool_release ); + + return 0; +} + +/** Fill an image buffer with either white (flash) or black as requested. +*/ + +static void fill_image( mlt_properties producer_properties, char* color, uint8_t* buffer, mlt_image_format format, int width, int height ) +{ + + int new_size = mlt_image_format_size( format, width, height, NULL ); + int old_size = 0; + uint8_t* image = image = mlt_properties_get_data( producer_properties, color, &old_size ); + + if( !image || new_size > old_size ) + { + // Need to create a new cached image. + image = mlt_pool_alloc( new_size ); + + if ( image != NULL ) + { + uint8_t r, g, b; + uint8_t* p = image; + + if( !strcmp( color, "_flash" ) ) + { + r = g = b = 255; // White + } + else + { + r = g = b = 0; // Black + } + + switch( format ) + { + default: + case mlt_image_yuv422: + { + int uneven = width % 2; + int count = ( width - uneven ) / 2 + 1; + uint8_t y, u, v; + + RGB2YUV_601_SCALED( r, g, b, y, u, v ); + int i = height + 1; + while ( --i ) + { + int j = count; + while ( --j ) + { + *p ++ = y; + *p ++ = u; + *p ++ = y; + *p ++ = v; + } + if ( uneven ) + { + *p ++ = y; + *p ++ = u; + } + } + break; + } + case mlt_image_rgb24: + { + int i = width * height + 1; + while ( --i ) + { + *p ++ = r; + *p ++ = g; + *p ++ = b; + } + break; + } + case mlt_image_rgb24a: + { + int i = width * height + 1; + while ( --i ) + { + *p ++ = r; + *p ++ = g; + *p ++ = b; + *p ++ = 255; // alpha + } + break; + } + } + // Cache the image to save from regenerating it with every frame. + mlt_properties_set_data( producer_properties, color, image, new_size, mlt_pool_release, NULL ); + } + } + + memcpy( buffer, image, new_size ); +} + +static int producer_get_image( mlt_frame frame, uint8_t** buffer, mlt_image_format* format, int* width, int* height, int writable ) +{ + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + mlt_producer producer = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "_producer_blipflash", NULL ); + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + int size = 0; + double fps = mlt_producer_get_fps( producer ); + int frames = mlt_frame_get_position( frame ); + int seconds = frames / fps; + + mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) ); + + // Correct the returns if necessary + if( *format != mlt_image_yuv422 || *format != mlt_image_rgb24 || *format != mlt_image_rgb24a ) + *format = mlt_image_yuv422; + if( *width <= 0 ) + *width = mlt_service_profile( MLT_PRODUCER_SERVICE(producer) )->width; + if ( *height <= 0 ) + *height = mlt_service_profile( MLT_PRODUCER_SERVICE(producer) )->height; + + // Allocate the buffer + size = mlt_image_format_size( *format, *width, *height, NULL ); + *buffer = mlt_pool_alloc( size ); + + // Determine if this should be a flash or black. + frames = frames % lrint( fps ); + seconds = seconds % mlt_properties_get_int( producer_properties, "period" ); + if( seconds == 0 && frames == 0 ) + { + fill_image( producer_properties, "_flash", *buffer, *format, *width, *height ); + } + else + { + fill_image( producer_properties, "_black", *buffer, *format, *width, *height ); + } + + mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) ); + + // Create the alpha channel + int alpha_size = *width * *height; + uint8_t *alpha = mlt_pool_alloc( alpha_size ); + if ( alpha ) + memset( alpha, 255, alpha_size ); + + // Update the frame + mlt_frame_set_image( frame, *buffer, size, mlt_pool_release ); + mlt_frame_set_alpha( frame, alpha, alpha_size, mlt_pool_release ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( producer_properties, "aspect_ratio" ) ); + mlt_properties_set_int( properties, "progressive", 1 ); + mlt_properties_set_int( properties, "meta.media.width", *width ); + mlt_properties_set_int( properties, "meta.media.height", *height ); + + return 0; +} + +static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + // Generate a frame + *frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) ); + + if ( *frame != NULL ) + { + // Obtain properties of frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( *frame ); + + // Save the producer to be used later + mlt_properties_set_data( frame_properties, "_producer_blipflash", producer, 0, NULL, NULL ); + + // Update time code on the frame + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + + // Configure callbacks + mlt_frame_push_get_image( *frame, producer_get_image ); + mlt_frame_push_audio( *frame, producer_get_audio ); + } + + // Calculate the next time code + mlt_producer_prepare_next( producer ); + + return 0; +} + +static void producer_close( mlt_producer this ) +{ + this->close = NULL; + mlt_producer_close( this ); + free( this ); +} + +/** Initialize. +*/ + +mlt_producer producer_blipflash_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ) +{ + // Create a new producer object + mlt_producer producer = mlt_producer_new( profile ); + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer ); + + // Initialize the producer + if ( producer ) + { + mlt_properties_set_int( producer_properties, "period", 1 ); + mlt_properties_set_int( producer_properties, "offset", 0 ); + + // Callback registration + producer->get_frame = producer_get_frame; + producer->close = ( mlt_destructor )producer_close; + } + + return producer; +} diff --git a/src/modules/avsync/producer_blipflash.yml b/src/modules/avsync/producer_blipflash.yml new file mode 100644 index 00000000..8bec5a3f --- /dev/null +++ b/src/modules/avsync/producer_blipflash.yml @@ -0,0 +1,36 @@ +schema_version: 0.1 +type: producer +identifier: blipflash +title: Blip Flash +version: 1 +copyright: Brian Matherly +creator: Brian Matherly +license: LGPLv2.1 +language: en +tags: + - Audio + - Video +description: > + Generate periodic synchronized audio blips and video flashes. + Blips are a 1kHz tone and last the duration of the flash frame. +parameters: + - identifier: period + title: Flash Period + type: integer + description: > + The period between flashes in seconds. + default: 1 + readonly: no + mutable: yes + widget: spinner + - identifier: offset + title: Audio Offset + type: integer + description: > + The number of frames to offset the audio. + A positive number results in audio earlier than video. + An negative number results in audio later than video. + default: 0 + readonly: no + mutable: yes + widget: spinner -- 2.39.2