]> git.sesse.net Git - mlt/commitdiff
Add new avsync module
authorBrian Matherly <pez4brian@yahoo.com>
Tue, 26 Feb 2013 04:38:06 +0000 (22:38 -0600)
committerBrian Matherly <pez4brian@yahoo.com>
Tue, 26 Feb 2013 04:41:46 +0000 (22:41 -0600)
src/modules/avsync/Makefile [new file with mode: 0644]
src/modules/avsync/consumer_blipflash.c [new file with mode: 0644]
src/modules/avsync/consumer_blipflash.yml [new file with mode: 0644]
src/modules/avsync/factory.c [new file with mode: 0644]
src/modules/avsync/producer_blipflash.c [new file with mode: 0644]
src/modules/avsync/producer_blipflash.yml [new file with mode: 0644]

diff --git a/src/modules/avsync/Makefile b/src/modules/avsync/Makefile
new file mode 100644 (file)
index 0000000..c036290
--- /dev/null
@@ -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 (file)
index 0000000..5de8a55
--- /dev/null
@@ -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 <pez4brian@yahoo.com>
+ *
+ * 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 <framework/mlt_consumer.h>
+#include <framework/mlt_frame.h>
+#include <framework/mlt_deque.h>
+
+// System header files
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+// 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 (file)
index 0000000..5c8f40e
--- /dev/null
@@ -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 (file)
index 0000000..79687fe
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * factory.c -- the factory method interfaces
+ * Copyright (C) 2013 Brian Matherly
+ * Author: Brian Matherly <pez4brian@yahoo.com>
+ *
+ * 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 <framework/mlt.h>
+#include <string.h>
+#include <limits.h>
+
+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 (file)
index 0000000..e677445
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * producer_blipflash.c -- blip/flash generating producer
+ * Copyright (C) 2013 Brian Matherly
+ * Author: Brian Matherly <pez4brian@yahoo.com>
+ *
+ * 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 <framework/mlt.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/** 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 (file)
index 0000000..8bec5a3
--- /dev/null
@@ -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