[vlc-devel] [PATCH] access module for BlackMagic SDI cards

Laurent Aimar fenrir at elivagar.org
Sat Oct 2 01:18:58 CEST 2010


Hi,

> +/*****************************************************************************
> + * decklink.cpp: BlackMagic DeckLink SDI input module
> + *****************************************************************************
> + * Copyright (C) 2010 VideoLAN
 Not correct, you can assign it to yourself.

> + *
> + * Authors: Steinar H. Gunderson <steinar+vlc at gunderson.no>
> + *
> + * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
> + *
> + *****************************************************************************/
> +
> +#ifdef HAVE_CONFIG_H
> +# include "config.h"
> +#endif

> +#ifndef INT64_C
> +#define INT64_C(c) c ## LL
> +#endif
Adding
#define __STDC_CONSTANT_MACROS 1
before the config.h include should properly defines it.

> +#include <vlc_common.h>
> +#include <vlc_plugin.h>
> +#include <vlc_input.h>
> +#include <vlc_demux.h>
> +#include <vlc_access.h>
> +#include <vlc_picture.h>
> +#include <vlc_charset.h>
> +#include <vlc_fs.h>
> +#include <vlc_atomic.h>
> +#include <arpa/inet.h>

 Are you sure you need all of thoses headers ? (At quick glance,
 vlc_access, vlc_picture are probably not needed, I haven't check the
others).

> +struct demux_sys_t
> +{
> +    IDeckLink *p_card;
> +    IDeckLinkInput *p_input;
> +    DeckLinkCaptureDelegate *p_delegate;
> +
> +    es_out_id_t *p_video_es;
> +    es_out_id_t *p_audio_es;
> +    bool b_first_frame;
> +    int i_last_pts;
> +
> +    int i_width, i_height, i_fps_num, i_fps_den;
 Do you need them here ?

> +    uint32_t i_dominance_flags;
> +
> +    int i_rate, i_channels;
 Same.

> +
> +    vlc_mutex_t frame_lock;
> +    block_t *p_video_frame;  /* protected by <frame_lock> */
> +    block_t *p_audio_frame;  /* protected by <frame_lock> */
> +    vlc_cond_t has_frame;  /* related to <frame_lock> */
> +};
> +
> +class DeckLinkCaptureDelegate : public IDeckLinkInputCallback
> +{
> +public:
> +    DeckLinkCaptureDelegate( demux_t *p_demux ) : p_demux_(p_demux)
> +    {
> +        vlc_atomic_set( &m_ref_, 1 );
> +    }
> +
> +    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; }
> +
> +    virtual ULONG STDMETHODCALLTYPE AddRef(void)
> +    {
> +        return vlc_atomic_inc( &m_ref_ );
> +    }
> +
> +    virtual ULONG STDMETHODCALLTYPE Release(void)
> +    {
> +        uintptr_t new_ref = vlc_atomic_dec( &m_ref_ );
> +        if ( new_ref == 0 )
> +            delete this;
> +        return new_ref;
> +    }
> +
> +    virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags);
> +    virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame*, IDeckLinkAudioInputPacket*);
> +
> +private:
> +    vlc_atomic_t m_ref_;
> +    demux_t *p_demux_;
 Why the _ suffix ?

> +HRESULT DeckLinkCaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents events, IDeckLinkDisplayMode *mode, BMDDetectedVideoInputFormatFlags)
> +{
> +    msg_Dbg( p_demux_, "Video input format changed" );
> +    return S_OK;
> +}
 Could be moved to the class definition (it's a one line).

> +HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioFrame)
> +{
> +    demux_sys_t *p_sys = p_demux_->p_sys;
> +    block_t *p_video_frame = NULL;
> +    block_t *p_audio_frame = NULL;
> +
> +    if( videoFrame )
> +    {
> +        if( videoFrame->GetFlags() & bmdFrameHasNoInputSource )
> +        {
> +            msg_Warn( p_demux_, "No input signal detected" );
> +            return S_OK;
> +        }
> +
> +        const int i_width = videoFrame->GetWidth();
> +        const int i_height = videoFrame->GetHeight();
> +        const int i_stride = videoFrame->GetRowBytes();
> +        const int i_bpp = 2;
> +
> +        p_video_frame = block_New( p_demux_, i_width * i_height * i_bpp );
> +        if( !p_video_frame )
> +        {
> +            msg_Err( p_demux_, "Could not allocate memory for video frame" );
> +            return S_OK;
> +        }
> +
> +        void *frame_bytes;
> +        videoFrame->GetBytes( &frame_bytes );
> +        for( int y = 0; y < i_height; ++y )
> +        {
> +            const uint8_t *src = (const uint8_t *)frame_bytes + i_stride * y;
> +            uint8_t *dst = (uint8_t *)p_video_frame->p_buffer + i_width * i_bpp * y;
 Useless cast.
> +            memcpy( dst, src, i_width * i_bpp );
> +        }
> +
> +        BMDTimeValue stream_time, frame_duration;
> +        videoFrame->GetStreamTime( &stream_time, &frame_duration, CLOCK_FREQ );
> +        p_video_frame->i_flags = BLOCK_FLAG_TYPE_I | p_sys->i_dominance_flags;

> +        if( p_sys->b_first_frame )
> +        {
> +            p_video_frame->i_flags |= BLOCK_FLAG_DISCONTINUITY;
> +            p_sys->b_first_frame = false;
> +        }
 I don't think you need that.

> +        p_video_frame->i_pts = p_video_frame->i_dts = VLC_TS_0 + stream_time;
> +    }
> +
> +    if( audioFrame )
> +    {
> +        const int i_bytes = audioFrame->GetSampleFrameCount() * sizeof(int16_t) * p_sys->i_channels;
 Using audioFrame->GetBytes() seems simpler.
> +
> +        p_audio_frame = block_New( p_demux_, i_bytes );
> +        if( !p_audio_frame )
> +        {
> +            msg_Err( p_demux_, "Could not allocate memory for audio frame" );
> +            if( p_video_frame )
> +                block_Release( p_video_frame );
> +            return S_OK;
> +        }
> +
> +        void *frame_bytes;
> +        audioFrame->GetBytes( &frame_bytes );
> +        memcpy( p_audio_frame->p_buffer, frame_bytes, i_bytes );
> +
> +        BMDTimeValue packet_time;
> +        audioFrame->GetPacketTime( &packet_time, CLOCK_FREQ );
> +        p_audio_frame->i_pts = p_audio_frame->i_dts = VLC_TS_0 + packet_time;
> +    }

> +    if( p_video_frame || p_audio_frame )
> +    {
> +        vlc_mutex_lock( &p_sys->frame_lock );
> +        if( p_video_frame )
> +        {
> +            if( p_sys->p_video_frame )
> +                block_Release( p_sys->p_video_frame );
> +            p_sys->p_video_frame = p_video_frame;
> +        }
> +        if( p_audio_frame )
> +        {
> +            if( p_sys->p_audio_frame )
> +                block_Release( p_sys->p_audio_frame );
> +            p_sys->p_audio_frame = p_audio_frame;
> +        }
> +        vlc_cond_signal( &p_sys->has_frame );
> +        vlc_mutex_unlock( &p_sys->frame_lock );
 You don't need that part. Simply call ES_OUT_SET_PCR and then
send the data to the es_out here and do not implement the Demux()
function (set demux_t::pf_demux to NULL). It will be simpler and
will avoid useless polling.

> +static int Open( vlc_object_t *p_this )
> +{
> +    demux_t     *p_demux = (demux_t*)p_this;
> +    demux_sys_t *p_sys;
> +    int         ret = VLC_SUCCESS;
 I think that setting it to VLC_EGENERIC by default would simplify
a bit the code.

> +    char        *psz_aspect;
> +    char        *psz_display_mode = NULL;
> +    char        *psz_video_connection = NULL;
> +    char        *psz_audio_connection = NULL;
> +    bool        b_found_mode;
> +    int         i_card_index;
> +
> +    /* Only when selected */
> +    if( *p_demux->psz_access == '\0' )
> +        return VLC_EGENERIC;
> +
> +    /* Set up p_demux */
> +    p_demux->pf_demux = Demux;
> +    p_demux->pf_control = Control;
> +    p_demux->info.i_update = 0;
> +    p_demux->info.i_title = 0;
> +    p_demux->info.i_seekpoint = 0;
> +    p_demux->p_sys = p_sys = (demux_sys_t*)calloc( 1, sizeof( demux_sys_t ) );
> +    if( !p_sys )
> +        return VLC_ENOMEM;
> +
> +    vlc_mutex_init( &p_sys->frame_lock );
> +    vlc_cond_init( &p_sys->has_frame );
> +    p_sys->b_first_frame = true;
> +
> +    IDeckLinkIterator *decklink_iterator = CreateDeckLinkIteratorInstance();
> +    if( !decklink_iterator )
> +    {
> +        msg_Err( p_demux, "DeckLink drivers not found." );
> +        ret = VLC_EGENERIC;
> +        goto finish;
> +    }
> +
> +    HRESULT result;
> +
> +    i_card_index = var_InheritInteger( p_demux, "decklink-card-index" );
> +    for( int i = 0; i <= i_card_index; ++i )
 Nothing prevent i_card_index from being < 0 leading to result uninitialized.
> +    {
> +        if( p_sys->p_card )
> +            p_sys->p_card->Release();
> +        result = decklink_iterator->Next( &p_sys->p_card );
> +        if( result != S_OK )
> +            break;
> +    }
> +
> +    if( result != S_OK )
> +    {
> +        msg_Err( p_demux, "DeckLink PCI card %d not found", i_card_index );
> +        ret = VLC_EGENERIC;
> +        goto finish;
> +    }
> +
> +    const char *psz_model_name;
> +    result = p_sys->p_card->GetModelName( &psz_model_name );
> +
> +    if( result != S_OK )
> +    {
> +        msg_Err( p_demux, "Could not get model name" );
> +        ret = VLC_EGENERIC;
> +        goto finish;
> +    }
> +
> +    msg_Dbg( p_demux, "Opened DeckLink PCI card %d (%s)", i_card_index, psz_model_name );
> +
> +    if( p_sys->p_card->QueryInterface( IID_IDeckLinkInput, (void**)&p_sys->p_input) != S_OK )
> +    {
> +        msg_Err( p_demux, "Card has no inputs" );
> +        ret = VLC_EGENERIC;
> +        goto finish;
> +    }
> +
> +    /* Set up the video and audio sources. */
> +    IDeckLinkConfiguration *p_config;
> +    if( p_sys->p_card->QueryInterface( IID_IDeckLinkConfiguration, (void**)&p_config) != S_OK )
> +    {
> +        msg_Err( p_demux, "Failed to get configuration interface" );
> +        ret = VLC_EGENERIC;
> +        goto finish;
> +    }
> +
> +    psz_video_connection = var_CreateGetNonEmptyString( p_demux, "decklink-video-connection" );
> +    if( psz_video_connection )
> +    {
> +        BMDVideoConnection conn;
> +        if ( !strcmp( psz_video_connection, "sdi" ) )
> +            conn = bmdVideoConnectionSDI;
> +        else if ( !strcmp( psz_video_connection, "hdmi" ) )
> +            conn = bmdVideoConnectionHDMI;
> +        else if ( !strcmp( psz_video_connection, "opticalsdi" ) )
> +            conn = bmdVideoConnectionOpticalSDI;
> +        else if ( !strcmp( psz_video_connection, "component" ) )
> +            conn = bmdVideoConnectionComponent;
> +        else if ( !strcmp( psz_video_connection, "composite" ) )
> +            conn = bmdVideoConnectionComposite;
> +        else if ( !strcmp( psz_video_connection, "svideo" ) )
> +            conn = bmdVideoConnectionSVideo;
> +        else
> +        {
> +            msg_Err( p_demux, "Invalid --decklink-video-connection specified; choose one of " \
> +                              "sdi, hdmi, opticalsdi, component, composite, or svideo." );
> +            ret = VLC_EGENERIC;
> +            goto finish;
> +        }
> +
> +        msg_Dbg( p_demux, "Setting video input format to 0x%x", conn);
> +        result = p_config->SetVideoInputFormat( conn );
> +        if( result != S_OK )
> +        {
> +            msg_Err( p_demux, "Failed to set video input connection" );
> +            ret = VLC_EGENERIC;
> +            goto finish;
> +        }
> +    }
> +
> +    psz_audio_connection = var_CreateGetNonEmptyString( p_demux, "decklink-audio-connection" );
> +    if( psz_audio_connection )
> +    {
> +        BMDAudioConnection conn;
> +        if ( !strcmp( psz_audio_connection, "embedded" ) )
> +            conn = bmdAudioConnectionEmbedded;
> +        else if ( !strcmp( psz_audio_connection, "aesebu" ) )
> +            conn = bmdAudioConnectionAESEBU;
> +        else if ( !strcmp( psz_audio_connection, "analog" ) )
> +            conn = bmdAudioConnectionAnalog;
> +        else
> +        {
> +            msg_Err( p_demux, "Invalid --decklink-audio-connection specified; choose one of " \
> +                              "embedded, aesebu, or analog." );
> +            ret = VLC_EGENERIC;
> +            goto finish;
> +        }
> +
> +        msg_Dbg( p_demux, "Setting audio input format to 0x%x", conn);
> +        result = p_config->SetAudioInputFormat( conn );
> +        if( result != S_OK )
> +        {
> +            msg_Err( p_demux, "Failed to set audio input connection" );
> +            ret = VLC_EGENERIC;
> +            goto finish;
> +        }
> +    }
> +
> +    /* Get the list of display modes. */
> +    IDeckLinkDisplayModeIterator *p_display_iterator;
> +    result = p_sys->p_input->GetDisplayModeIterator( &p_display_iterator );
> +    if( result != S_OK )
> +    {
> +        msg_Err( p_demux, "Failed to enumerate display modes" );
> +        ret = VLC_EGENERIC;
> +        goto finish;
> +    }
> +
> +    psz_display_mode = var_InheritString( p_demux, "decklink-mode" );
var_CreateGetNonEmptyString() will simplify the following test (== 0 is then not possible).
> +    if( !psz_display_mode || strlen( psz_display_mode ) == 0 || strlen( psz_display_mode ) > 4 ) {
> +        msg_Err( p_demux, "Missing or invalid --decklink-mode string" );
> +        ret = VLC_EGENERIC;
> +        goto finish;
> +    }

> +    /*
> +     * Pad the --decklink-mode string to four characters, so the user can specify e.g. "pal"
> +     * without having to add the trailing space.
> +     */
> +    char sz_display_mode_padded[5];
> +    strcpy(sz_display_mode_padded, "    ");
> +    for( int i = 0; i < strlen( psz_display_mode ); ++i )
> +        sz_display_mode_padded[i] = psz_display_mode[i];
 snprintf(sz_display_mode_padded, 5, "%-4s", psz_display_mode)
is simpler.
> +    BMDDisplayMode wanted_mode_id;
> +    memcpy( &wanted_mode_id, &sz_display_mode_padded, sizeof(wanted_mode_id) );
 Are you sure it is actually big/little endian compatible? (Unless the SDK exists
only for little endian hardware)

> +    video_fmt.video.i_sar_num = 1;
> +    video_fmt.video.i_sar_den = 1;
> +    psz_aspect = var_CreateGetNonEmptyString( p_demux, "decklink-aspect-ratio" );
> +    if( psz_aspect )
> +    {
> +        char *psz_denominator = strchr( psz_aspect, ':' );
> +        if( psz_denominator )
> +        {
> +            *psz_denominator++ = '\0';
> +            video_fmt.video.i_sar_num = atoi( psz_aspect )      * video_fmt.video.i_height;
> +            video_fmt.video.i_sar_den = atoi( psz_denominator ) * video_fmt.video.i_width;
> +        }
> +        free( psz_aspect );
> +    }
 Use var_InheritURational() (for an example, you can look at modules/access/imem.c).

> +
> +    /* Update default_pts to a suitable value for access */
> +    var_Create( p_demux, "decklink-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
 You don't need to create it. A var_InheritInteger at the right place will
work.


Regards,

-- 
fenrir




More information about the vlc-devel mailing list