Hi

attached patch implements NewTek NDI protocol support for consumer. Producer is not implemented yet.

i tested it against their tools provided for monitoring.

--
Maksym Veremeyenko

From f4b77cd16bc77e704f3d166790fa42972b790b3e Mon Sep 17 00:00:00 2001
From: Maksym Veremeyenko <ve...@m1.tv>
Date: Thu, 29 Sep 2016 15:12:18 +0300
Subject: [PATCH] NewTek NDI consumer, draft version

---
 src/modules/ndi/Makefile  |   40 ++++
 src/modules/ndi/configure |   46 +++++
 src/modules/ndi/ndi.c     |  441 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 527 insertions(+), 0 deletions(-)
 create mode 100644 src/modules/ndi/Makefile
 create mode 100755 src/modules/ndi/configure
 create mode 100644 src/modules/ndi/ndi.c

diff --git a/src/modules/ndi/Makefile b/src/modules/ndi/Makefile
new file mode 100644
index 0000000..ceede46
--- /dev/null
+++ b/src/modules/ndi/Makefile
@@ -0,0 +1,40 @@
+CFLAGS += -fPIC -I../.. -Wno-deprecated -Wno-multichar
+LDFLAGS += -L../../framework -lmlt -lpthread
+
+include ../../../config.mak
+include config.mak
+
+TARGET = ../libmltndi$(LIBSUF)
+
+OBJS = ndi.o
+
+SRCS := $(OBJS:.o=.c)
+
+LDFLAGS += $(LIBDL)
+
+all:   $(TARGET)
+       
+$(TARGET): $(OBJS)
+               $(CC) $(SHFLAGS) -o $@ $(OBJS) $(LDFLAGS)
+
+depend:        $(SRCS)
+               $(CC) -MM $(CFLAGS) $^ 1>.depend
+
+distclean:     clean
+               rm -f .depend
+
+clean:
+               rm -f $(OBJS) $(TARGET)
+
+install: all
+       install -m 755 $(TARGET) "$(DESTDIR)$(moduledir)"
+       install -d "$(DESTDIR)$(mltdatadir)/ndi"
+#      install -m 644 *.yml "$(DESTDIR)$(mltdatadir)/ndi"
+
+uninstall:
+       rm "$(DESTDIR)$(moduledir)/libmltndi$(LIBSUF)" 2> /dev/null || true
+       rm -rf "$(DESTDIR)$(mltdatadir)/ndi"
+
+ifneq ($(wildcard .depend),)
+include .depend
+endif
diff --git a/src/modules/ndi/configure b/src/modules/ndi/configure
new file mode 100755
index 0000000..04f6e0a
--- /dev/null
+++ b/src/modules/ndi/configure
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+if [ "$help" = "1" ]
+then
+       cat << EOF
+NDI SDK paths:
+
+    --ndi-sdk-include=<path>  - Path to include directory
+    --ndi-sdk-lib=<path>      - Path to library directory
+
+EOF
+
+else
+
+       echo > config.mak
+
+       export ndi_sdk_include=
+       export ndi_sdk_lib=
+
+       for i in "$@"
+       do
+               case $i in
+                       --ndi-sdk-include=* )   
ndi_sdk_include="${i#--ndi-sdk-include=}" ;;
+                       --ndi-sdk-lib=* )       
ndi_sdk_lib="${i#--ndi-sdk-lib=}" ;;
+               esac
+       done
+
+       if \
+               [ "$ndi_sdk_include" != "" ] &&
+               [ -f "$ndi_sdk_include/Processing.NDI.Lib.h" ] && \
+               [ "$ndi_sdk_lib" != "" ] && \
+               [ -e "$ndi_sdk_lib/libndi.so" ]
+       then
+               echo "- NDI SDK found: enabling it"
+               rm -f ../disable-ndi
+
+               echo "CFLAGS+=-I$ndi_sdk_include " >> config.mak
+               echo "LDFLAGS+=-L$ndi_sdk_lib -lndi " >> config.mak
+       else
+               echo "- NDI SDK not found: disabling"
+               touch ../disable-ndi
+       fi
+
+fi
+
+exit 0
diff --git a/src/modules/ndi/ndi.c b/src/modules/ndi/ndi.c
new file mode 100644
index 0000000..bd8e84a
--- /dev/null
+++ b/src/modules/ndi/ndi.c
@@ -0,0 +1,441 @@
+/*
+ * ndi.c -- output through NewTek NDI
+ * Copyright (C) 2010-2016 Maksym Veremeyenko <ve...@m1stereo.tv>
+ *
+ * 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 consumer library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
+ */
+
+#define __STDC_FORMAT_MACROS  /* see inttypes.h */
+#define __STDC_LIMIT_MACROS
+#define __STDC_CONSTANT_MACROS
+#define _XOPEN_SOURCE
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <limits.h>
+#include <pthread.h>
+
+#include <framework/mlt.h>
+
+#include "Processing.NDI.Lib.h"
+
+#define NDI_CON_STR_MAX 32768
+
+static void swab2( const void *from, void *to, int n )
+{
+#if defined(USE_SSE)
+#define SWAB_STEP 16
+       __asm__ volatile
+       (
+               "loop_start:                            \n\t"
+
+               /* load */
+               "movdqa         0(%[from]), %%xmm0      \n\t"
+               "add            $0x10, %[from]          \n\t"
+
+               /* duplicate to temp registers */
+               "movdqa         %%xmm0, %%xmm1          \n\t"
+
+               /* shift right temp register */
+               "psrlw          $8, %%xmm1              \n\t"
+
+               /* shift left main register */
+               "psllw          $8, %%xmm0              \n\t"
+
+               /* compose them back */
+               "por           %%xmm0, %%xmm1           \n\t"
+
+               /* save */
+               "movdqa         %%xmm1, 0(%[to])        \n\t"
+               "add            $0x10, %[to]            \n\t"
+
+               "dec            %[cnt]                  \n\t"
+               "jnz            loop_start              \n\t"
+
+               :
+               : [from]"r"(from), [to]"r"(to), [cnt]"r"(n / SWAB_STEP)
+               //: "xmm0", "xmm1"
+       );
+
+       from = (unsigned char*) from + n - (n % SWAB_STEP);
+       to = (unsigned char*) to + n - (n % SWAB_STEP);
+       n = (n % SWAB_STEP);
+#endif
+       swab((char*) from, (char*) to, n);
+};
+
+typedef struct
+{
+       struct mlt_consumer_s parent;
+       int f_running, f_exit;
+       char* arg;
+       pthread_t th;
+       int count;
+} consumer_ndi;
+
+static void* consumer_ndi_feeder( void* p )
+{
+       int i;
+       mlt_frame last = NULL;
+       mlt_consumer consumer = p;
+       mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
+       consumer_ndi* self = ( consumer_ndi* )consumer->child;
+
+       mlt_log_debug( MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", 
__FUNCTION__ );
+
+       const NDIlib_send_create_t ndi_send_desc = { self->arg, NULL, true, 
false };
+
+       NDIlib_send_instance_t ndi_send = NDIlib_send_create( &ndi_send_desc );
+       if ( !ndi_send )
+       {
+               mlt_log_error( MLT_CONSUMER_SERVICE( consumer ), "%s: 
NDIlib_send_create failed\n", __FUNCTION__ );
+
+               return NULL;
+       }
+
+       char* ndi_con_str = malloc(NDI_CON_STR_MAX);
+       strncpy(ndi_con_str, "<ndi_product", NDI_CON_STR_MAX);
+       for ( i = 0; i < mlt_properties_count( properties ); i++ )
+       {
+               char *name = mlt_properties_get_name( properties, i );
+               if ( !name || strncmp( name, "meta.attr.", 10 ) )
+                       continue;
+               strncat(ndi_con_str, " \"", NDI_CON_STR_MAX);
+               strncat(ndi_con_str, name + 10, NDI_CON_STR_MAX);
+               strncat(ndi_con_str, "\"=\"", NDI_CON_STR_MAX);
+               strncat(ndi_con_str, mlt_properties_get_value( properties, i ), 
NDI_CON_STR_MAX);
+               strncat(ndi_con_str, "\"", NDI_CON_STR_MAX);
+       }
+       strncat(ndi_con_str, ">", NDI_CON_STR_MAX);
+
+       const NDIlib_metadata_frame_t ndi_con_type =
+       {
+               // The length
+               (int)strlen(ndi_con_str),
+               // Timecode (synthesized for us !)
+               NDIlib_send_timecode_synthesize,
+               // The string
+               (char*)ndi_con_str
+       };
+
+       NDIlib_send_add_connection_metadata(ndi_send, &ndi_con_type);
+
+       while ( !self->f_exit )
+       {
+               mlt_frame frame = mlt_consumer_rt_frame( consumer );
+
+               if ( frame || last )
+               {
+                       mlt_profile profile = mlt_service_profile( 
MLT_CONSUMER_SERVICE( consumer ) );
+                       int m_isKeyer = 0, width = profile->width, height = 
profile->height;
+                       uint8_t* image = 0;
+                       mlt_frame frm = m_isKeyer ? frame : last;
+                       mlt_image_format format = 0 ? mlt_image_rgb24a : 
mlt_image_yuv422;
+                       int rendered = mlt_properties_get_int( 
MLT_FRAME_PROPERTIES( frm ), "rendered" );
+
+                       if ( rendered && !mlt_frame_get_image( frm, &image, 
&format, &width, &height, 0 ) )
+                       {
+//                             mlt_log_debug( MLT_CONSUMER_SERVICE(consumer), 
"%s:%d: width=%d, height=%d\n",
+//                                     __FUNCTION__, __LINE__, width, height );
+
+                               uint8_t* buffer = 0;
+                               int stride = width * ( m_isKeyer? 4 : 2 );
+                               int progressive = mlt_properties_get_int( 
MLT_FRAME_PROPERTIES( frame ), "progressive" );
+
+                               const NDIlib_video_frame_t ndi_video_frame =
+                               {
+                                       // Resolution
+                                       width, height,
+
+                                       // Use YCbCr video
+                                       m_isKeyer
+                                               ? NDIlib_FourCC_type_BGRA
+                                               : NDIlib_FourCC_type_UYVY,
+
+                                       // The frame-eate
+                                       profile->frame_rate_num, 
profile->frame_rate_den,
+
+                                       // The aspect ratio
+                                       (double)profile->display_aspect_num / 
profile->display_aspect_den,
+
+                                       // This is a progressive frame
+                                       progressive
+                                               ? 
NDIlib_frame_format_type_progressive
+                                               : 
NDIlib_frame_format_type_interleaved,
+
+                                       // Timecode (synthesized for us !)
+                                       NDIlib_send_timecode_synthesize,
+
+                                       // The video memory used for this frame
+                                       buffer = (uint8_t*)malloc( height * 
stride ),
+
+                                       // The line to line stride of this image
+                                       stride
+                               };
+
+                               if ( !m_isKeyer )
+                               {
+                                       // Normal non-keyer playout - needs 
byte swapping
+                                       if ( !progressive )
+                                               // convert lower field first to 
top field first
+                                               swab2( (char*) image, (char*) 
buffer + stride, stride * ( height - 1 ) );
+                                       else
+                                               swab2( (char*) image, (char*) 
buffer, stride * height );
+                               }
+                               else if ( !mlt_properties_get_int( 
MLT_FRAME_PROPERTIES( frame ), "test_image" ) )
+                               {
+                                       // Normal keyer output
+                                       int y = height + 1;
+                                       uint32_t* s = (uint32_t*) image;
+                                       uint32_t* d = (uint32_t*) buffer;
+
+                                       if ( !progressive )
+                                       {
+                                               // Correct field order
+                                               height--;
+                                               y--;
+                                               d += width;
+                                       }
+
+                                       // Need to relocate alpha channel RGBA 
=> ARGB
+                                       while ( --y )
+                                       {
+                                               int x = width + 1;
+                                               while ( --x )
+                                               {
+                                                       *d++ = ( *s << 8 ) | ( 
*s >> 24 );
+                                                       s++;
+                                               }
+                                       }
+                               }
+                               else
+                               {
+                                       // Keying blank frames - nullify alpha
+                                       memset( buffer, 0, stride * height );
+                               }
+
+                               mlt_audio_format aformat = mlt_audio_s16;
+                               int frequency = 48000;
+                               int m_channels = 2;
+                               int samples = mlt_sample_calculator( 
mlt_profile_fps( profile ), frequency, self->count );
+                               int16_t *pcm = 0;
+
+                               if ( !mlt_frame_get_audio( frm, (void**) &pcm, 
&aformat, &frequency, &m_channels, &samples ) )
+                               {
+                                       // Create an audio buffer
+                                       const 
NDIlib_audio_frame_interleaved_16s_t audio_data =
+                                       {
+                                               // 48kHz
+                                               frequency,
+
+                                               // Lets submit stereo although 
there is nothing limiting us
+                                               m_channels,
+
+                                               //
+                                               samples,
+
+                                               // timecode
+                                               NDIlib_send_timecode_synthesize,
+
+                                               // reference_level
+                                               0,
+
+                                               // The audio data, interleaved 
16bpp
+                                               pcm
+                                       };
+
+                                       
NDIlib_util_send_send_audio_interleaved_16s(ndi_send, &audio_data);
+                               }
+
+                               // We now submit the frame.
+                               NDIlib_send_send_video(ndi_send, 
&ndi_video_frame);
+
+                               // free buf
+                               free( ndi_video_frame.p_data );
+
+                               self->count++;
+                       }
+               }
+
+               if ( frame )
+               {
+                       if ( last )
+                               mlt_frame_close( last );
+
+                       last = frame;
+               }
+
+               mlt_events_fire( properties, "consumer-frame-show", frame, NULL 
);
+       }
+
+       NDIlib_send_destroy( ndi_send );
+
+       mlt_log_debug( MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", 
__FUNCTION__ );
+
+       if ( last )
+               mlt_frame_close( last );
+
+       return NULL;
+}
+
+static int consumer_ndi_start( mlt_consumer consumer )
+{
+       int r = 0;
+
+       mlt_log_debug( MLT_CONSUMER_SERVICE( consumer ), "%s: entering\n", 
__FUNCTION__ );
+
+       consumer_ndi* self = ( consumer_ndi* )consumer->child;
+
+       if ( !self->f_running )
+       {
+               // set flags
+               self->f_exit = 0;
+
+               pthread_create( &self->th, NULL, consumer_ndi_feeder, consumer 
);
+
+               // set flags
+               self->f_running = 1;
+       }
+
+       mlt_log_debug( MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", 
__FUNCTION__ );
+
+       return r;
+}
+
+static int consumer_ndi_stop( mlt_consumer consumer )
+{
+       int r = 0;
+
+       mlt_log_debug( MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", 
__FUNCTION__ );
+
+       consumer_ndi* self = ( consumer_ndi* )consumer->child;
+
+       if ( self->f_running )
+       {
+               // rise flags
+               self->f_exit = 1;
+
+               // wait for thread
+               pthread_join( self->th, NULL );
+
+               // hide flags
+               self->f_running = 0;
+       }
+
+       mlt_log_debug( MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", 
__FUNCTION__ );
+
+       return r;
+}
+
+static int consumer_ndi_is_stopped( mlt_consumer consumer )
+{
+       consumer_ndi* self = ( consumer_ndi* )consumer->child;
+       return !self->f_running;
+}
+
+static void consumer_ndi_close( mlt_consumer consumer )
+{
+       consumer_ndi* self = ( consumer_ndi* )consumer->child;
+
+       mlt_log_debug( MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", 
__FUNCTION__ );
+
+       // Stop the consumer
+       mlt_consumer_stop( consumer );
+
+       // Close the parent
+       consumer->close = NULL;
+       mlt_consumer_close( consumer );
+
+       // free context
+       if ( self->arg )
+               free( self->arg );
+       free( self );
+
+       mlt_log_debug( NULL, "%s: exiting\n", __FUNCTION__ );
+}
+
+/** Initialise the producer.
+ */
+static mlt_consumer producer_ndi_init( mlt_profile profile, mlt_service_type 
type, const char *id, char *arg )
+{
+       return NULL;
+}
+
+/** Initialise the consumer.
+ */
+static mlt_consumer consumer_ndi_init( mlt_profile profile, mlt_service_type 
type, const char *id, char *arg )
+{
+       // Allocate the consumer
+       consumer_ndi* self = ( consumer_ndi* )calloc( 1, sizeof( consumer_ndi ) 
);
+
+       mlt_log_debug( NULL, "%s: entering id=[%s], arg=[%s]\n", __FUNCTION__, 
id, arg );
+
+       if ( self )
+               memset( self, 0, sizeof( consumer_ndi ) );
+
+       // If allocated
+       if ( self && !mlt_consumer_init( &self->parent, self, profile ) )
+       {
+               mlt_consumer parent = &self->parent;
+
+               // Setup context
+               if( arg )
+                       self->arg = strdup( arg );
+
+               // Setup callbacks
+               parent->close = consumer_ndi_close;
+               parent->start = consumer_ndi_start;
+               parent->stop = consumer_ndi_stop;
+               parent->is_stopped = consumer_ndi_is_stopped;
+
+               mlt_properties properties = MLT_CONSUMER_PROPERTIES( parent );
+               mlt_properties_set( properties, "deinterlace_method", 
"onefield" );
+
+               return parent;
+       }
+
+       free( self );
+
+       return NULL;
+}
+
+static void *create_service( mlt_profile profile, mlt_service_type type, const 
char *id, void *arg )
+{
+       mlt_log_debug( NULL, "%s: entering id=[%s]\n", __FUNCTION__, id );
+
+       if ( !NDIlib_initialize() )
+       {
+               mlt_log_error( NULL, "%s: NDIlib_initialize failed\n", 
__FUNCTION__ );
+               return NULL;
+       }
+
+       if ( type == producer_type )
+               return producer_ndi_init( profile, type, id, (char*)arg );
+       else if ( type == consumer_type )
+               return consumer_ndi_init( profile, type, id, (char*)arg );
+
+       return NULL;
+}
+
+MLT_REPOSITORY
+{
+       MLT_REGISTER( consumer_type, "ndi", create_service );
+       MLT_REGISTER( producer_type, "ndi", create_service );
+}
-- 
1.7.7.6

------------------------------------------------------------------------------
_______________________________________________
Mlt-devel mailing list
Mlt-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mlt-devel

Reply via email to