From 3d27aed85f3a65a8ab70724129d8224ff207b924 Mon Sep 17 00:00:00 2001 From: Dan Dennedy Date: Thu, 31 Mar 2011 22:34:24 -0700 Subject: [PATCH] Add decklink producer. --- src/modules/decklink/Makefile | 7 + src/modules/decklink/producer_decklink.cpp | 398 +++++++++++++++++++++ src/modules/decklink/producer_decklink.yml | 64 ++++ 3 files changed, 469 insertions(+) create mode 100644 src/modules/decklink/producer_decklink.cpp create mode 100644 src/modules/decklink/producer_decklink.yml diff --git a/src/modules/decklink/Makefile b/src/modules/decklink/Makefile index bd3b2e85..2f37c4e6 100755 --- a/src/modules/decklink/Makefile +++ b/src/modules/decklink/Makefile @@ -7,6 +7,7 @@ include ../../../config.mak TARGET = ../libmltdecklink$(LIBSUF) OBJS = consumer_decklink.o \ + producer_decklink.o \ DeckLinkAPIDispatch.o SRCS := $(OBJS:.o=.cpp) @@ -31,6 +32,12 @@ clean: install: all install -m 755 $(TARGET) "$(DESTDIR)$(libdir)/mlt" + install -d "$(DESTDIR)$(datadir)/mlt/decklink" + install -m 644 producer_decklink.yml "$(DESTDIR)$(datadir)/mlt/decklink" + +uninstall: + rm "$(DESTDIR)$(libdir)/mlt/libmltdecklink$(LIBSUF)" 2> /dev/null || true + rm -rf "$(DESTDIR)$(datadir)/mlt/decklink" ifneq ($(wildcard .depend),) include .depend diff --git a/src/modules/decklink/producer_decklink.cpp b/src/modules/decklink/producer_decklink.cpp new file mode 100644 index 00000000..5bd6ea32 --- /dev/null +++ b/src/modules/decklink/producer_decklink.cpp @@ -0,0 +1,398 @@ +/* + * producer_decklink.c -- input from Blackmagic Design DeckLink + * Copyright (C) 2011 Dan Dennedy + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include "DeckLinkAPI.h" + +class DeckLinkProducer + : public IDeckLinkInputCallback +{ +private: + mlt_producer_s m_producer; + IDeckLink* m_decklink; + IDeckLinkInput* m_decklinkInput; + mlt_deque m_queue; + pthread_mutex_t m_mutex; + pthread_cond_t m_condition; + bool m_started; + + BMDDisplayMode getDisplayMode( mlt_profile profile ) + { + IDeckLinkDisplayModeIterator* iter; + IDeckLinkDisplayMode* mode; + BMDDisplayMode result = bmdDisplayModeNotSupported; + + if ( m_decklinkInput->GetDisplayModeIterator( &iter ) == S_OK ) + { + while ( !result && iter->Next( &mode ) == S_OK ) + { + int width = mode->GetWidth(); + int height = mode->GetHeight(); + BMDTimeValue duration; + BMDTimeScale timescale; + mode->GetFrameRate( &duration, ×cale ); + double fps = (double) timescale / duration; + int p = mode->GetFieldDominance() == bmdProgressiveFrame; + mlt_log_verbose( getProducer(), "BMD mode %dx%d %.3f fps prog %d\n", width, height, fps, p ); + + if ( width == profile->width && height == profile->height && p == profile->progressive + && fps == mlt_profile_fps( profile ) ) + result = mode->GetDisplayMode(); + } + } + + return result; + } + +public: + + mlt_producer getProducer() + { return &m_producer; } + + ~DeckLinkProducer() + { + if ( m_decklinkInput ) + m_decklinkInput->Release(); + if ( m_decklink ) + m_decklink->Release(); + if ( m_queue ) + { + stop(); + mlt_deque_close( m_queue ); + pthread_mutex_destroy( &m_mutex ); + pthread_cond_destroy( &m_condition ); + } + } + + bool open( mlt_profile profile, unsigned card = 0 ) + { + IDeckLinkIterator* decklinkIterator = CreateDeckLinkIteratorInstance(); + try + { + if ( !decklinkIterator ) + throw "The DeckLink drivers are not installed."; + + // Connect to the Nth DeckLink instance + unsigned i = 0; + do { + if ( decklinkIterator->Next( &m_decklink ) != S_OK ) + throw "DeckLink card not found."; + } while ( ++i <= card ); + decklinkIterator->Release(); + + // Get the input interface + if ( m_decklink->QueryInterface( IID_IDeckLinkInput, (void**) &m_decklinkInput ) != S_OK ) + throw "No DeckLink cards support input."; + + // Provide this class as a delegate to the input callback + m_decklinkInput->SetCallback( this ); + + // Initialize other members + pthread_mutex_init( &m_mutex, NULL ); + pthread_cond_init( &m_condition, NULL ); + m_queue = mlt_deque_init(); + m_started = false; + } + catch ( const char *error ) + { + if ( decklinkIterator ) + decklinkIterator->Release(); + mlt_log_error( getProducer(), "%s\n", error ); + return false; + } + return true; + } + + bool start( mlt_profile profile = 0 ) + { + if ( m_started ) + return false; + try + { + if ( !profile ) + profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) ); + + // Get the display mode + BMDDisplayMode displayMode = getDisplayMode( profile ); + if ( displayMode == bmdDisplayModeNotSupported ) + throw "Profile is not compatible with decklink."; + + // Enable video capture + BMDPixelFormat pixelFormat = bmdFormat8BitYUV; + BMDVideoInputFlags flags = bmdVideoInputFlagDefault; + if ( S_OK != m_decklinkInput->EnableVideoInput( displayMode, pixelFormat, flags ) ) + throw "Failed to enable video capture."; + + // Enable audio capture + BMDAudioSampleRate sampleRate = bmdAudioSampleRate48kHz; + BMDAudioSampleType sampleType = bmdAudioSampleType16bitInteger; + int channels = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "channels" ); + if ( S_OK != m_decklinkInput->EnableAudioInput( sampleRate, sampleType, channels ) ) + throw "Failed to enable audio capture."; + + // Start capture + m_started = m_decklinkInput->StartStreams() == S_OK; + if ( !m_started ) + throw "Failed to start capture."; + } + catch ( const char *error ) + { + m_decklinkInput->DisableVideoInput(); + mlt_log_error( getProducer(), "%s\n", error ); + return false; + } + return true; + } + + void stop() + { + if ( m_started ) + return; + + // Release the wait in getFrame + pthread_mutex_lock( &m_mutex ); + pthread_cond_broadcast( &m_condition ); + pthread_mutex_unlock( &m_mutex ); + + m_decklinkInput->StopStreams(); + + // Cleanup queue + pthread_mutex_lock( &m_mutex ); + while ( mlt_frame frame = (mlt_frame) mlt_deque_pop_back( m_queue ) ) + mlt_frame_close( frame ); + pthread_mutex_unlock( &m_mutex ); + + m_started = false; + } + + mlt_frame getFrame() + { + mlt_frame frame = NULL; + + // Wait if queue is empty + pthread_mutex_lock( &m_mutex ); + while ( mlt_deque_count( m_queue ) < 1 ) + pthread_cond_wait( &m_condition, &m_mutex ); + + // Get the first frame from the queue + frame = ( mlt_frame ) mlt_deque_pop_front( m_queue ); + pthread_mutex_unlock( &m_mutex ); + + // Set frame timestamp and properties + mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) ); + mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); + mlt_properties_set_int( properties, "progressive", profile->progressive ); + mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( profile ) ); + mlt_properties_set_int( properties, "width", profile->width ); + mlt_properties_set_int( properties, "real_width", profile->width ); + mlt_properties_set_int( properties, "height", profile->height ); + mlt_properties_set_int( properties, "real_height", profile->height ); + mlt_properties_set_int( properties, "format", mlt_image_yuv422 ); + mlt_properties_set_int( properties, "audio_frequency", 48000 ); + mlt_properties_set_int( properties, "audio_channels", + mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "channels" ) ); + + return frame; + } + + // *** DeckLink API implementation of IDeckLinkInputCallback *** // + + // IUnknown needs only a dummy implementation + virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID iid, LPVOID *ppv ) + { return E_NOINTERFACE; } + virtual ULONG STDMETHODCALLTYPE AddRef() + { return 1; } + virtual ULONG STDMETHODCALLTYPE Release() + { return 1; } + + /************************* DeckLink API Delegate Methods *****************************/ + + virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( + IDeckLinkVideoInputFrame* video, + IDeckLinkAudioInputPacket* audio ) + { + // Create mlt_frame + mlt_frame frame = mlt_frame_init( MLT_PRODUCER_SERVICE( getProducer() ) ); + + // Copy video + if ( video ) + { + if ( !( video->GetFlags() & bmdFrameHasNoInputSource ) ) + { + int size = video->GetRowBytes() * video->GetHeight(); + void* image = mlt_pool_alloc( size ); + void* buffer = 0; + + video->GetBytes( &buffer ); + if ( image && buffer ) + { + swab( buffer, image, size ); + mlt_frame_set_image( frame, (uint8_t*) image, size, mlt_pool_release ); + } + else if ( image ) + { + mlt_log_verbose( getProducer(), "no video\n" ); + mlt_pool_release( image ); + } + } + else + { + mlt_log_verbose( getProducer(), "no signal\n" ); + mlt_frame_close( frame ); + frame = 0; + } + } + else + { + mlt_log_verbose( getProducer(), "no video\n" ); + mlt_frame_close( frame ); + frame = 0; + } + + // Copy audio + if ( frame && audio ) + { + int channels = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "channels" ); + int size = audio->GetSampleFrameCount() * channels * sizeof(int16_t); + mlt_audio_format format = mlt_audio_s16; + void* pcm = mlt_pool_alloc( size ); + void* buffer = 0; + + audio->GetBytes( &buffer ); + if ( buffer ) + { + memcpy( pcm, buffer, size ); + mlt_frame_set_audio( frame, pcm, format, size, mlt_pool_release ); + mlt_properties_set_int( MLT_FRAME_PROPERTIES(frame), "audio_samples", audio->GetSampleFrameCount() ); + } + else + { + mlt_log_verbose( getProducer(), "no audio\n" ); + mlt_pool_release( pcm ); + } + } + else + { + mlt_log_verbose( getProducer(), "no audio\n" ); + } + + // Put frame in queue + if ( frame ) + { + int queueMax = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "buffer" ); + pthread_mutex_lock( &m_mutex ); + if ( mlt_deque_count( m_queue ) < queueMax ) + { + mlt_deque_push_back( m_queue, frame ); + pthread_cond_broadcast( &m_condition ); + } + pthread_mutex_unlock( &m_mutex ); + } + + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged( + BMDVideoInputFormatChangedEvents events, + IDeckLinkDisplayMode* mode, + BMDDetectedVideoInputFormatFlags flags ) + { + return S_OK; + } +}; + +static int get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples ) +{ + return mlt_frame_get_audio( frame, (void**) buffer, format, frequency, channels, samples ); +} + +static int get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable ) +{ + return mlt_frame_get_image( frame, buffer, format, width, height, writable ); +} + +static int get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) +{ + DeckLinkProducer* decklink = (DeckLinkProducer*) producer->child; + + // Get the next frame from the decklink object + *frame = decklink->getFrame(); + + // Calculate the next timecode + mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); + mlt_producer_prepare_next( producer ); + + // Add audio and video getters + mlt_frame_push_audio( *frame, (void*) get_audio ); + mlt_frame_push_get_image( *frame, get_image ); + + return 0; +} + +static void producer_close( mlt_producer producer ) +{ + producer->close = NULL; + mlt_producer_close( producer ); + delete (DeckLinkProducer*) producer->child; +} + +extern "C" { + +/** Initialise the producer. + */ + +mlt_producer producer_decklink_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ) +{ + // Allocate the producer + DeckLinkProducer* decklink = new DeckLinkProducer(); + mlt_producer producer = NULL; + + // If allocated and initializes + if ( decklink && !mlt_producer_init( decklink->getProducer(), decklink ) ) + { + if ( decklink->open( profile, arg? atoi( arg ) : 0 ) ) + { + producer = decklink->getProducer(); + + // Set callbacks + producer->close = (mlt_destructor) producer_close; + producer->get_frame = get_frame; + + // Set properties + mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "resource", arg ); + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( producer ), "channels", 2 ); + mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( producer ), "buffer", 25 ); + + // Start immediately + if ( !decklink->start( profile ) ) + { + producer_close( producer ); + producer = NULL; + } + } + } + + return producer; +} + +} diff --git a/src/modules/decklink/producer_decklink.yml b/src/modules/decklink/producer_decklink.yml new file mode 100644 index 00000000..cfd5bcd0 --- /dev/null +++ b/src/modules/decklink/producer_decklink.yml @@ -0,0 +1,64 @@ +schema_version: 0.1 +type: producer +identifier: decklink +title: Blackmagic Design DeckLink Capture +version: 1 +copyright: Copytight (C) 2011 Daniel R. Dennedy +license: LGPL +language: en +creator: Dan Dennedy +tags: + - Audio + - Video +description: > + Capture video and audio using Blackmagic Design DeckLink SDI or Intensity + HDMI cards. +notes: > + Please ensure that you use a MLT profile that is compatible with a broadcast + standard which the card you are using supports. If you must use an interlaced + profile but wish to deinterlace or scale the input, then you must use the + consumer producer, e.g.: + melt -profile square_pal consumer:decklink: profile=dv_pal +bugs: + - It is incompatible with the yadif deinterlacer. + - Transport controls such as seeking has no affect. + - External deck control is not implemented. +parameters: + - identifier: argument + title: Card + type: integer + readonly: no + required: no + mutable: no + default: 0 + minimum: 0 + + - identifier: resource + title: Card + type: integer + readonly: yes + + - identifier: channels + title: Audio Channels + type: integer + readonly: no + required: no + mutable: no + default: 2 + minimum: 2 + maximum: 16 + + - identifier: buffer + title: Max Frames to Buffer + description: > + There is a queue of frames between this plugin and its consumer. + If the consumer has a little, intermittent delay then it reduces the + risk of dropping a frame. However, this provides a maximum number of + frames that can be buffered to prevent consuming memory unbounded in + the case of frequent or sustained delays. + type: integer + readonly: no + required: no + mutable: no + default: 25 + minimum: 0 -- 2.39.2