[vlc-commits] sout: add SDI stream output

Francois Cartegnie git at videolan.org
Wed Sep 5 10:36:41 CEST 2018


vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | Tue Oct 18 20:08:06 2016 +0200| [752df69bf96946d2aa03f470cf2dba65732fcc04] | committer: Francois Cartegnie

sout: add SDI stream output

Decklink vout backport, so
this is the currently the only support

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=752df69bf96946d2aa03f470cf2dba65732fcc04
---

 NEWS                                    |   4 +
 configure.ac                            |   2 +-
 modules/MODULES_LIST                    |   1 +
 modules/stream_out/Makefile.am          |  18 +
 modules/stream_out/sdi/Ancillary.cpp    | 100 +++++
 modules/stream_out/sdi/Ancillary.hpp    |  49 +++
 modules/stream_out/sdi/DBMSDIOutput.cpp | 703 ++++++++++++++++++++++++++++++++
 modules/stream_out/sdi/DBMSDIOutput.hpp |  67 +++
 modules/stream_out/sdi/SDIOutput.cpp    | 157 +++++++
 modules/stream_out/sdi/SDIOutput.hpp    |  81 ++++
 modules/stream_out/sdi/SDIStream.cpp    | 511 +++++++++++++++++++++++
 modules/stream_out/sdi/SDIStream.hpp    | 149 +++++++
 modules/stream_out/sdi/V210.cpp         |  96 +++++
 modules/stream_out/sdi/V210.hpp         |  37 ++
 modules/stream_out/sdi/sdiout.cpp       | 205 ++++++++++
 modules/stream_out/sdi/sdiout.hpp       |  23 ++
 po/POTFILES.in                          |   1 +
 17 files changed, 2203 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 6bc94096de..b4fac69e9b 100644
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,10 @@ Video output:
  * Remove RealRTSP plugin
  * Remove Real demuxer plugin
 
+Stream output:
+ * New SDI output with improved audio and ancillary support.
+   Candidate for deprecation of decklink vout/aout modules.
+
 macOS:
  * Remove Growl notification support
 
diff --git a/configure.ac b/configure.ac
index 1627c12b79..9700ec121e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1896,7 +1896,7 @@ if test "${enable_decklink}" != "no"
 then
   if test "${with_decklink_sdk}" != "no" -a -n "${with_decklink_sdk}"
   then
-    VLC_ADD_CPPFLAGS([decklink decklinkoutput],[-I${with_decklink_sdk}/include])
+    VLC_ADD_CPPFLAGS([decklink decklinkoutput stream_out_sdi],[-I${with_decklink_sdk}/include])
   fi
   VLC_SAVE_FLAGS
   CPPFLAGS="${CPPFLAGS} ${CPPFLAGS_decklink}"
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index e1978cc6e4..73e73ed545 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -340,6 +340,7 @@ $Id$
  * scte18: SCTE-18/Emergency Alert Messaging for Cable decoder
  * scte27: SCTE-27/Digicipher subtitles decoder
  * sd_journal: logger output to SystemD journal
+ * sdiout: SDI stream output
  * sdl_image: SDL-based image decoder
  * sdp: SDP fake access
  * secret: store secrets via Gnome libsecret
diff --git a/modules/stream_out/Makefile.am b/modules/stream_out/Makefile.am
index 24a7a6db82..f27f3e066e 100644
--- a/modules/stream_out/Makefile.am
+++ b/modules/stream_out/Makefile.am
@@ -50,6 +50,24 @@ sout_LTLIBRARIES = \
 	libstream_out_setid_plugin.la \
 	libstream_out_transcode_plugin.la
 
+if HAVE_DECKLINK
+libstream_out_sdi_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(CPPFLAGS_decklinkoutput)
+libstream_out_sdi_plugin_la_LIBADD = $(LIBS_decklink) $(LIBDL) -lpthread
+libstream_out_sdi_plugin_la_SOURCES = stream_out/sdi/sdiout.cpp \
+        stream_out/sdi/sdiout.hpp \
+        stream_out/sdi/Ancillary.cpp \
+        stream_out/sdi/Ancillary.hpp \
+        stream_out/sdi/DBMSDIOutput.cpp \
+        stream_out/sdi/DBMSDIOutput.hpp \
+        stream_out/sdi/SDIOutput.cpp \
+        stream_out/sdi/SDIOutput.hpp \
+        stream_out/sdi/SDIStream.cpp \
+        stream_out/sdi/SDIStream.hpp \
+        stream_out/sdi/V210.cpp \
+        stream_out/sdi/V210.hpp
+sout_LTLIBRARIES += libstream_out_sdi_plugin.la
+endif
+
 # RTP plugin
 sout_LTLIBRARIES += libstream_out_rtp_plugin.la
 libstream_out_rtp_plugin_la_SOURCES = \
diff --git a/modules/stream_out/sdi/Ancillary.cpp b/modules/stream_out/sdi/Ancillary.cpp
new file mode 100644
index 0000000000..67f7acba2e
--- /dev/null
+++ b/modules/stream_out/sdi/Ancillary.cpp
@@ -0,0 +1,100 @@
+/*****************************************************************************
+ * Ancillary.cpp: SDI Ancillary
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; 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
+
+#include "Ancillary.hpp"
+
+using namespace sdi;
+
+AFD::AFD(uint8_t afdcode, uint8_t ar)
+{
+    this->afdcode = afdcode;
+    this->ar = ar;
+}
+
+AFD::~AFD()
+{
+
+}
+
+static inline void put_le32(uint8_t **p, uint32_t d)
+{
+    SetDWLE(*p, d);
+    (*p) += 4;
+}
+
+void AFD::FillBuffer(uint8_t *p_buf, size_t i_buf)
+{
+    const size_t len = 6 /* vanc header */ + 8 /* AFD data */ + 1 /* csum */;
+    const size_t s = ((len + 5) / 6) * 6; // align for v210
+
+    if(s * 6 >= i_buf / 16)
+        return;
+
+    uint16_t afd[s];
+
+    afd[0] = 0x000;
+    afd[1] = 0x3ff;
+    afd[2] = 0x3ff;
+    afd[3] = 0x41; // DID
+    afd[4] = 0x05; // SDID
+    afd[5] = 8; // Data Count
+
+    int bar_data_flags = 0;
+    int bar_data_val1 = 0;
+    int bar_data_val2 = 0;
+
+    afd[ 6] = ((afdcode & 0x0F) << 3) | ((ar & 0x01) << 2); /* SMPTE 2016-1 */
+    afd[ 7] = 0; // reserved
+    afd[ 8] = 0; // reserved
+    afd[ 9] = bar_data_flags << 4;
+    afd[10] = bar_data_val1 << 8;
+    afd[11] = bar_data_val1 & 0xff;
+    afd[12] = bar_data_val2 << 8;
+    afd[13] = bar_data_val2 & 0xff;
+
+    /* parity bit */
+    for (size_t i = 3; i < len - 1; i++)
+        afd[i] |= vlc_parity((unsigned)afd[i]) ? 0x100 : 0x200;
+
+    /* vanc checksum */
+    uint16_t vanc_sum = 0;
+    for (size_t i = 3; i < len - 1; i++) {
+        vanc_sum += afd[i];
+        vanc_sum &= 0x1ff;
+    }
+
+    afd[len - 1] = vanc_sum | ((~vanc_sum & 0x100) << 1);
+
+    /* pad */
+    for (size_t i = len; i < s; i++)
+        afd[i] = 0x040;
+
+    /* convert to v210 and write into VANC */
+    for (size_t w = 0; w < s / 6 ; w++) {
+        put_le32(&p_buf, afd[w*6+0] << 10);
+        put_le32(&p_buf, afd[w*6+1] | (afd[w*6+2] << 20));
+        put_le32(&p_buf, afd[w*6+3] << 10);
+        put_le32(&p_buf, afd[w*6+4] | (afd[w*6+5] << 20));
+    }
+}
diff --git a/modules/stream_out/sdi/Ancillary.hpp b/modules/stream_out/sdi/Ancillary.hpp
new file mode 100644
index 0000000000..56e095f2b1
--- /dev/null
+++ b/modules/stream_out/sdi/Ancillary.hpp
@@ -0,0 +1,49 @@
+/*****************************************************************************
+ * Ancillary.hpp: SDI Ancillary
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef ANCILLARY_HPP
+#define ANCILLARY_HPP
+
+#include <vlc_common.h>
+
+namespace sdi
+{
+
+    class Ancillary
+    {
+        public:
+            virtual void FillBuffer(uint8_t *, size_t) = 0;
+    };
+
+    class AFD : public Ancillary
+    {
+        public:
+            AFD(uint8_t afdcode, uint8_t ar);
+            virtual ~AFD();
+            virtual void FillBuffer(uint8_t *, size_t);
+
+        private:
+            uint8_t afdcode;
+            uint8_t ar;
+    };
+
+}
+
+#endif // ANCILLARY_HPP
diff --git a/modules/stream_out/sdi/DBMSDIOutput.cpp b/modules/stream_out/sdi/DBMSDIOutput.cpp
new file mode 100644
index 0000000000..57f48a4a0d
--- /dev/null
+++ b/modules/stream_out/sdi/DBMSDIOutput.cpp
@@ -0,0 +1,703 @@
+/*****************************************************************************
+ * DBMSDIOutput.cpp: Decklink SDI Output
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; 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
+
+#include "DBMSDIOutput.hpp"
+#include "SDIStream.hpp"
+#include "Ancillary.hpp"
+#include "V210.hpp"
+
+#include <DeckLinkAPIDispatch.cpp>
+#include "sdiout.hpp"
+
+#include <vlc_es.h>
+#include <vlc_picture.h>
+#include <vlc_interrupt.h>
+#include <vlc_image.h>
+#include <arpa/inet.h>
+
+using namespace sdi_sout;
+
+DBMSDIOutput::DBMSDIOutput(sout_stream_t *p_stream) :
+    SDIOutput(p_stream)
+{
+    p_card = NULL;
+    p_output = NULL;
+    offset = 0;
+    lasttimestamp = 0;
+    b_running = false;
+}
+
+DBMSDIOutput::~DBMSDIOutput()
+{
+    if(p_output)
+    {
+        BMDTimeValue out;
+        p_output->StopScheduledPlayback(lasttimestamp, &out, timescale);
+        p_output->DisableVideoOutput();
+        p_output->DisableAudioOutput();
+        p_output->Release();
+    }
+    if(p_card)
+        p_card->Release();
+}
+
+AbstractStream *DBMSDIOutput::Add(const es_format_t *fmt)
+{
+    AbstractStream *s = SDIOutput::Add(fmt);
+    if(s)
+    {
+        msg_Dbg(p_stream, "accepted %s %4.4s",
+                          s->getID().toString().c_str(), (const char *) &fmt->i_codec);
+        if( videoStream && (audioStream || audio.i_channels == 0) )
+            Start();
+    }
+    else
+    {
+        msg_Err(p_stream, "rejected es id %d %4.4s",
+                          fmt->i_id, (const char *) &fmt->i_codec);
+    }
+
+    return s;
+}
+
+IDeckLinkDisplayMode * DBMSDIOutput::MatchDisplayMode(const video_format_t *fmt, BMDDisplayMode forcedmode)
+{
+    HRESULT result;
+    IDeckLinkDisplayMode *p_selected = NULL;
+    IDeckLinkDisplayModeIterator *p_iterator = NULL;
+
+    for(int i=0; i<4 && p_selected==NULL; i++)
+    {
+        int i_width = (i % 2 == 0) ? fmt->i_width : fmt->i_visible_width;
+        int i_height = (i % 2 == 0) ? fmt->i_height : fmt->i_visible_height;
+        int i_div = (i > 2) ? 4 : 0;
+
+        result = p_output->GetDisplayModeIterator(&p_iterator);
+        if(result == S_OK)
+        {
+            IDeckLinkDisplayMode *p_mode = NULL;
+            while(p_iterator->Next(&p_mode) == S_OK)
+            {
+                BMDDisplayMode mode_id = p_mode->GetDisplayMode();
+                BMDTimeValue frameduration;
+                BMDTimeScale timescale;
+                const char *psz_mode_name;
+
+                if(p_mode->GetFrameRate(&frameduration, &timescale) == S_OK &&
+                        p_mode->GetName(&psz_mode_name) == S_OK)
+                {
+                    BMDDisplayMode modenl = htonl(mode_id);
+                    if(i==0)
+                    {
+                        BMDFieldDominance field = htonl(p_mode->GetFieldDominance());
+                        msg_Dbg(p_stream, "Found mode '%4.4s': %s (%ldx%ld, %4.4s, %.3f fps, scale %ld dur %ld)",
+                                (const char*)&modenl, psz_mode_name,
+                                p_mode->GetWidth(), p_mode->GetHeight(),
+                                (const char *)&field,
+                                double(timescale) / frameduration,
+                                timescale, frameduration);
+                    }
+                }
+                else
+                {
+                    p_mode->Release();
+                    continue;
+                }
+
+                if(forcedmode != bmdDisplayModeNotSupported && unlikely(!p_selected))
+                {
+                    BMDDisplayMode modenl = htonl(forcedmode);
+                    msg_Dbg(p_stream, "Forced mode '%4.4s'", (char *)&modenl);
+                    if(forcedmode == mode_id)
+                        p_selected = p_mode;
+                    else
+                        p_mode->Release();
+                    continue;
+                }
+
+                if(p_selected == NULL && forcedmode == bmdDisplayModeNotSupported)
+                {
+                    if(i_width >> i_div == p_mode->GetWidth() >> i_div &&
+                       i_height >> i_div == p_mode->GetHeight() >> i_div)
+                    {
+                        unsigned int num_deck, den_deck;
+                        unsigned int num_stream, den_stream;
+                        vlc_ureduce(&num_deck, &den_deck, timescale, frameduration, 0);
+                        vlc_ureduce(&num_stream, &den_stream,
+                                    fmt->i_frame_rate, fmt->i_frame_rate_base, 0);
+
+                        if (num_deck == num_stream && den_deck == den_stream)
+                        {
+                            msg_Info(p_stream, "Matches incoming stream");
+                            p_selected = p_mode;
+                            continue;
+                        }
+                    }
+                }
+
+                p_mode->Release();
+            }
+            p_iterator->Release();
+        }
+    }
+    return p_selected;
+}
+
+
+const char * DBMSDIOutput::ErrorToString(long i_code)
+{
+    static struct
+    {
+        long i_return_code;
+        const char * const psz_string;
+    } const errors_to_string[] = {
+        { E_UNEXPECTED,  "Unexpected error" },
+        { E_NOTIMPL,     "Not implemented" },
+        { E_OUTOFMEMORY, "Out of memory" },
+        { E_INVALIDARG,  "Invalid argument" },
+        { E_NOINTERFACE, "No interface" },
+        { E_POINTER,     "Invalid pointer" },
+        { E_HANDLE,      "Invalid handle" },
+        { E_ABORT,       "Aborted" },
+        { E_FAIL,        "Failed" },
+        { E_ACCESSDENIED,"Access denied" }
+    };
+
+    for(size_t i=0; i<ARRAY_SIZE(errors_to_string); i++)
+    {
+        if(errors_to_string[i].i_return_code == i_code)
+            return errors_to_string[i].psz_string;
+    }
+    return NULL;
+}
+
+#define CHECK(message) do { \
+    if (result != S_OK) \
+    { \
+    const char *psz_err = ErrorToString(result); \
+    if(psz_err)\
+    msg_Err(p_stream, message ": %s", psz_err); \
+    else \
+    msg_Err(p_stream, message ": 0x%X", result); \
+    goto error; \
+} \
+} while(0)
+
+int DBMSDIOutput::Open()
+{
+    HRESULT result;
+    IDeckLinkIterator *decklink_iterator = NULL;
+
+    int i_card_index = var_InheritInteger(p_stream, CFG_PREFIX "card-index");
+
+    if (i_card_index < 0)
+    {
+        msg_Err(p_stream, "Invalid card index %d", i_card_index);
+        goto error;
+    }
+
+    decklink_iterator = CreateDeckLinkIteratorInstance();
+    if (!decklink_iterator)
+    {
+        msg_Err(p_stream, "DeckLink drivers not found.");
+        goto error;
+    }
+
+    for(int i = 0; i <= i_card_index; ++i)
+    {
+        if (p_card)
+        {
+            p_card->Release();
+            p_card = NULL;
+        }
+        result = decklink_iterator->Next(&p_card);
+        CHECK("Card not found");
+    }
+
+    const char *psz_model_name;
+    result = p_card->GetModelName(&psz_model_name);
+    CHECK("Unknown model name");
+
+    msg_Dbg(p_stream, "Opened DeckLink PCI card %s", psz_model_name);
+
+    result = p_card->QueryInterface(IID_IDeckLinkOutput, (void**)&p_output);
+    CHECK("No outputs");
+
+    decklink_iterator->Release();
+
+    return VLC_SUCCESS;
+
+error:
+    if (p_output)
+    {
+        p_output->Release();
+        p_output = NULL;
+    }
+    if (p_card)
+    {
+        p_card->Release();
+        p_output = NULL;
+    }
+    if (decklink_iterator)
+        decklink_iterator->Release();
+
+    return VLC_EGENERIC;
+}
+
+int DBMSDIOutput::ConfigureAudio(const audio_format_t *)
+{
+    HRESULT result;
+
+    if(FAKE_DRIVER)
+        return VLC_SUCCESS;
+
+    if(!p_output)
+        return VLC_EGENERIC;
+
+    if(!video.configuredfmt.i_codec && b_running)
+        return VLC_EGENERIC;
+
+    if (audio.i_channels > 0)
+    {
+        audio.configuredfmt.i_codec =
+        audio.configuredfmt.audio.i_format = VLC_CODEC_S16N;
+        audio.configuredfmt.audio.i_channels = 2;
+        audio.configuredfmt.audio.i_physical_channels = AOUT_CHANS_STEREO;
+        audio.configuredfmt.audio.i_rate = 48000;
+        audio.configuredfmt.audio.i_bitspersample = 16;
+        audio.configuredfmt.audio.i_blockalign = 2 * 16 / 8;
+        audio.configuredfmt.audio.i_frame_length = FRAME_SIZE;
+
+        result = p_output->EnableAudioOutput(
+                    bmdAudioSampleRate48kHz,
+                    bmdAudioSampleType16bitInteger,
+                    2,
+                    bmdAudioOutputStreamTimestamped);
+        CHECK("Could not start audio output");
+    }
+    return VLC_SUCCESS;
+
+error:
+    return VLC_EGENERIC;
+}
+
+static BMDVideoConnection getVConn(const char *psz)
+{
+    BMDVideoConnection conn = bmdVideoConnectionSDI;
+
+    if(!psz)
+        return conn;
+
+    if (!strcmp(psz, "sdi"))
+        conn = bmdVideoConnectionSDI;
+    else if (!strcmp(psz, "hdmi"))
+        conn = bmdVideoConnectionHDMI;
+    else if (!strcmp(psz, "opticalsdi"))
+        conn = bmdVideoConnectionOpticalSDI;
+    else if (!strcmp(psz, "component"))
+        conn = bmdVideoConnectionComponent;
+    else if (!strcmp(psz, "composite"))
+        conn = bmdVideoConnectionComposite;
+    else if (!strcmp(psz, "svideo"))
+        conn = bmdVideoConnectionSVideo;
+
+    return conn;
+}
+
+int DBMSDIOutput::ConfigureVideo(const video_format_t *vfmt)
+{
+    HRESULT result;
+    BMDDisplayMode wanted_mode_id = bmdDisplayModeNotSupported;
+    IDeckLinkConfiguration *p_config = NULL;
+    IDeckLinkAttributes *p_attributes = NULL;
+    IDeckLinkDisplayMode *p_display_mode = NULL;
+    char *psz_string = NULL;
+    video_format_t *fmt = &video.configuredfmt.video;
+
+    if(FAKE_DRIVER)
+    {
+        video_format_Copy(fmt, vfmt);
+        fmt->i_chroma = !video.tenbits ? VLC_CODEC_UYVY : VLC_CODEC_I422_10L;
+        fmt->i_frame_rate = (unsigned) frameduration;
+        fmt->i_frame_rate_base = (unsigned) timescale;
+        video.configuredfmt.i_codec = fmt->i_chroma;
+        return VLC_SUCCESS;
+    }
+
+    if(!p_output)
+        return VLC_EGENERIC;
+
+    if(!video.configuredfmt.i_codec && b_running)
+        return VLC_EGENERIC;
+
+    /* Now configure card */
+    if(!p_output)
+        return VLC_EGENERIC;
+
+    result = p_card->QueryInterface(IID_IDeckLinkConfiguration, (void**)&p_config);
+    CHECK("Could not get config interface");
+
+    psz_string = var_InheritString(p_stream, CFG_PREFIX "mode");
+    if(psz_string)
+    {
+        size_t len = strlen(psz_string);
+        if (len > 4)
+        {
+            free(psz_string);
+            msg_Err(p_stream, "Invalid mode %s", psz_string);
+            goto error;
+        }
+        memset(&wanted_mode_id, ' ', 4);
+        strncpy((char*)&wanted_mode_id, psz_string, 4);
+        wanted_mode_id = ntohl(wanted_mode_id);
+        free(psz_string);
+    }
+
+    /* Read attributes */
+    result = p_card->QueryInterface(IID_IDeckLinkAttributes, (void**)&p_attributes);
+    CHECK("Could not get IDeckLinkAttributes");
+
+    int64_t vconn;
+    result = p_attributes->GetInt(BMDDeckLinkVideoOutputConnections, &vconn); /* reads mask */
+    CHECK("Could not get BMDDeckLinkVideoOutputConnections");
+
+    psz_string = var_InheritString(p_stream, CFG_PREFIX "video-connection");
+    vconn = getVConn(psz_string);
+    free(psz_string);
+    if (vconn == 0)
+    {
+        msg_Err(p_stream, "Invalid video connection specified");
+        goto error;
+    }
+
+    result = p_config->SetInt(bmdDeckLinkConfigVideoOutputConnection, vconn);
+    CHECK("Could not set video output connection");
+
+    p_display_mode = MatchDisplayMode(vfmt, wanted_mode_id);
+    if(p_display_mode == NULL)
+    {
+        msg_Err(p_stream, "Could not negociate a compatible display mode");
+        goto error;
+    }
+    else
+    {
+        BMDDisplayMode mode_id = p_display_mode->GetDisplayMode();
+        BMDDisplayMode modenl = htonl(mode_id);
+        msg_Dbg(p_stream, "Selected mode '%4.4s'", (char *) &modenl);
+
+        BMDVideoOutputFlags flags = bmdVideoOutputVANC;
+        if (mode_id == bmdModeNTSC ||
+                mode_id == bmdModeNTSC2398 ||
+                mode_id == bmdModePAL)
+        {
+            flags = bmdVideoOutputVITC;
+        }
+
+        BMDDisplayModeSupport support;
+        IDeckLinkDisplayMode *resultMode;
+
+        result = p_output->DoesSupportVideoMode(mode_id,
+                                                video.tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV,
+                                                flags, &support, &resultMode);
+        CHECK("Does not support video mode");
+        if (support == bmdDisplayModeNotSupported)
+        {
+            msg_Err(p_stream, "Video mode not supported");
+            goto error;
+        }
+
+        if (p_display_mode->GetWidth() <= 0 || p_display_mode->GetWidth() & 1)
+        {
+            msg_Err(p_stream, "Unknown video mode specified.");
+            goto error;
+        }
+
+        result = p_display_mode->GetFrameRate(&frameduration,
+                                              &timescale);
+        CHECK("Could not read frame rate");
+
+        result = p_output->EnableVideoOutput(mode_id, flags);
+        CHECK("Could not enable video output");
+
+        video_format_Copy(fmt, vfmt);
+        fmt->i_width = fmt->i_visible_width = p_display_mode->GetWidth();
+        fmt->i_height = fmt->i_visible_height = p_display_mode->GetHeight();
+        fmt->i_x_offset = 0;
+        fmt->i_y_offset = 0;
+        fmt->i_sar_num = 0;
+        fmt->i_sar_den = 0;
+        fmt->i_chroma = !video.tenbits ? VLC_CODEC_UYVY : VLC_CODEC_I422_10L; /* we will convert to v210 */
+        fmt->i_frame_rate = (unsigned) frameduration;
+        fmt->i_frame_rate_base = (unsigned) timescale;
+        video.configuredfmt.i_codec = fmt->i_chroma;
+
+        char *psz_file = var_InheritString(p_stream, CFG_PREFIX "nosignal-image");
+        if(psz_file)
+        {
+            video.pic_nosignal = CreateNoSignalPicture(psz_file, fmt);
+            if (!video.pic_nosignal)
+                msg_Err(p_stream, "Could not create no signal picture");
+            free(psz_file);
+        }
+    }
+
+    p_display_mode->Release();
+    p_attributes->Release();
+    p_config->Release();
+
+    return VLC_SUCCESS;
+
+error:
+    if (p_display_mode)
+        p_display_mode->Release();
+    if(p_attributes)
+        p_attributes->Release();
+    if (p_config)
+        p_config->Release();
+    return VLC_EGENERIC;
+}
+
+int DBMSDIOutput::Start()
+{
+    HRESULT result;
+    if(FAKE_DRIVER && !b_running)
+    {
+        b_running = true;
+        return VLC_SUCCESS;
+    }
+    if(b_running)
+        return VLC_EGENERIC;
+    result = p_output->StartScheduledPlayback(
+                     SEC_FROM_VLC_TICK(vlc_tick_now() * timescale), timescale, 1.0);
+    CHECK("Could not start playback");
+    b_running = true;
+    return VLC_SUCCESS;
+
+error:
+    return VLC_EGENERIC;
+}
+
+int DBMSDIOutput::Process()
+{
+    if((!p_output && !FAKE_DRIVER) || !b_running)
+        return VLC_EGENERIC;
+
+    picture_t *p;
+    while((p = reinterpret_cast<picture_t *>(videoBuffer.Dequeue())))
+        ProcessVideo(p);
+
+    block_t *b;
+    while((b = reinterpret_cast<block_t *>(audioBuffer.Dequeue())))
+        ProcessAudio(b);
+
+    return VLC_SUCCESS;
+}
+
+int DBMSDIOutput::ProcessAudio(block_t *p_block)
+{
+    if(FAKE_DRIVER)
+    {
+        block_Release(p_block);
+        return VLC_SUCCESS;
+    }
+
+    if (!p_output)
+    {
+        block_Release(p_block);
+        return VLC_EGENERIC;
+    }
+
+    p_block->i_pts -= offset;
+
+    uint32_t sampleFrameCount = p_block->i_nb_samples;
+    uint32_t written;
+    HRESULT result = p_output->ScheduleAudioSamples(
+            p_block->p_buffer, p_block->i_nb_samples, p_block->i_pts, CLOCK_FREQ, &written);
+
+    if (result != S_OK)
+        msg_Err(p_stream, "Failed to schedule audio sample: 0x%X", result);
+    else
+    {
+        lasttimestamp = __MAX(p_block->i_pts, lasttimestamp);
+        if (sampleFrameCount != written)
+            msg_Err(p_stream, "Written only %d samples out of %d", written, sampleFrameCount);
+    }
+
+    block_Release(p_block);
+
+    return result != S_OK ? VLC_EGENERIC : VLC_SUCCESS;
+}
+
+int DBMSDIOutput::ProcessVideo(picture_t *picture)
+{
+    mtime_t now = vlc_tick_now();
+
+    if (!picture)
+        return VLC_EGENERIC;
+
+    if(picture->date - now >  5000)
+        vlc_msleep_i11e(picture->date - now);
+
+    if (video.pic_nosignal &&
+        now - picture->date > vlc_tick_from_sec(video.nosignal_delay))
+    {
+        msg_Dbg(p_stream, "no signal");
+
+        picture_Hold(video.pic_nosignal);
+        video.pic_nosignal->date = now;
+        doProcessVideo(picture);
+    }
+
+    return doProcessVideo(picture);
+}
+
+int DBMSDIOutput::doProcessVideo(picture_t *picture)
+{
+    HRESULT result;
+    int w, h, stride, length, ret = VLC_EGENERIC;
+    mtime_t now;
+    IDeckLinkMutableVideoFrame *pDLVideoFrame = NULL;
+    w = video.configuredfmt.video.i_visible_width;
+    h = video.configuredfmt.video.i_visible_height;
+
+    if(FAKE_DRIVER)
+        goto end;
+
+    result = p_output->CreateVideoFrame(w, h, w*3,
+                                        video.tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV,
+                                        bmdFrameFlagDefault, &pDLVideoFrame);
+    if (result != S_OK) {
+        msg_Err(p_stream, "Failed to create video frame: 0x%X", result);
+        goto error;
+    }
+
+    void *frame_bytes;
+    pDLVideoFrame->GetBytes((void**)&frame_bytes);
+    stride = pDLVideoFrame->GetRowBytes();
+
+    if (video.tenbits)
+    {
+        IDeckLinkVideoFrameAncillary *vanc;
+        void *buf;
+
+        result = p_output->CreateAncillaryData(bmdFormat10BitYUV, &vanc);
+        if (result != S_OK) {
+            msg_Err(p_stream, "Failed to create vanc: %d", result);
+            goto error;
+        }
+
+        result = vanc->GetBufferForVerticalBlankingLine(ancillary.afd_line, &buf);
+        if (result != S_OK) {
+            msg_Err(p_stream, "Failed to get VBI line %u: %d", ancillary.afd_line, result);
+            goto error;
+        }
+
+        sdi::AFD afd(ancillary.afd, ancillary.ar);
+        afd.FillBuffer(reinterpret_cast<uint8_t*>(buf), stride);
+
+        sdi::V210::Convert(picture, stride, frame_bytes);
+
+        result = pDLVideoFrame->SetAncillaryData(vanc);
+        vanc->Release();
+        if (result != S_OK) {
+            msg_Err(p_stream, "Failed to set vanc: %d", result);
+            goto error;
+        }
+    }
+    else for(int y = 0; y < h; ++y) {
+        uint8_t *dst = (uint8_t *)frame_bytes + stride * y;
+        const uint8_t *src = (const uint8_t *)picture->p[0].p_pixels +
+                picture->p[0].i_pitch * y;
+        memcpy(dst, src, w * 2 /* bpp */);
+    }
+
+
+    // compute frame duration in CLOCK_FREQ units
+    length = (frameduration * CLOCK_FREQ) / timescale;
+
+    picture->date -= offset;
+    result = p_output->ScheduleVideoFrame(pDLVideoFrame,
+                                          picture->date, length, CLOCK_FREQ);
+    if (result != S_OK) {
+        msg_Err(p_stream, "Dropped Video frame %" PRId64 ": 0x%x",
+                picture->date, result);
+        goto error;
+    }
+    lasttimestamp = __MAX(picture->date, lasttimestamp);
+
+    now = vlc_tick_now() - offset;
+
+    BMDTimeValue decklink_now;
+    double speed;
+    p_output->GetScheduledStreamTime (CLOCK_FREQ, &decklink_now, &speed);
+
+    if ((now - decklink_now) > 400000) {
+        /* XXX: workaround card clock drift */
+        offset += 50000;
+        msg_Err(p_stream, "Delaying: offset now %" PRId64, offset);
+    }
+
+end:
+    ret = VLC_SUCCESS;
+
+error:
+    picture_Release(picture);
+    if (pDLVideoFrame)
+        pDLVideoFrame->Release();
+
+    return ret;
+}
+
+picture_t * DBMSDIOutput::CreateNoSignalPicture(const char *psz_file, const video_format_t *fmt)
+{
+    picture_t *p_pic = NULL;
+    image_handler_t *img = image_HandlerCreate(p_stream);
+    if (img)
+    {
+        video_format_t in, dummy;
+        video_format_Init(&dummy, 0);
+        video_format_Init(&in, 0);
+        video_format_Setup(&in, 0,
+                           fmt->i_width, fmt->i_height,
+                           fmt->i_width, fmt->i_height, 1, 1);
+
+        picture_t *png = image_ReadUrl(img, psz_file, &dummy, &in);
+        if (png)
+        {
+            video_format_Clean(&dummy);
+            video_format_Copy(&dummy, fmt);
+            p_pic = image_Convert(img, png, &in, &dummy);
+            if(!video_format_IsSimilar(&dummy, fmt))
+            {
+                picture_Release(p_pic);
+                p_pic = NULL;
+            }
+            picture_Release(png);
+        }
+        image_HandlerDelete(img);
+        video_format_Clean(&in);
+        video_format_Clean(&dummy);
+    }
+    return p_pic;
+}
diff --git a/modules/stream_out/sdi/DBMSDIOutput.hpp b/modules/stream_out/sdi/DBMSDIOutput.hpp
new file mode 100644
index 0000000000..915ac335a7
--- /dev/null
+++ b/modules/stream_out/sdi/DBMSDIOutput.hpp
@@ -0,0 +1,67 @@
+/*****************************************************************************
+ * DBMSDIOutput.hpp: Decklink SDI Output
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef DBMSDIOUTPUT_HPP
+#define DBMSDIOUTPUT_HPP
+
+#include "SDIOutput.hpp"
+
+#include <vlc_es.h>
+
+#include <DeckLinkAPI.h>
+
+namespace sdi_sout
+{
+    class DBMSDIOutput : public SDIOutput
+    {
+        public:
+            DBMSDIOutput(sout_stream_t *);
+            ~DBMSDIOutput();
+            virtual AbstractStream *Add(const es_format_t *); /* reimpl */
+            virtual int Open(); /* impl */
+            virtual int Process(); /* impl */
+
+        protected:
+            int ProcessVideo(picture_t *);
+            int ProcessAudio(block_t *);
+            virtual int ConfigureVideo(const video_format_t *); /* impl */
+            virtual int ConfigureAudio(const audio_format_t *); /* impl */
+
+        private:
+            IDeckLink *p_card;
+            IDeckLinkOutput *p_output;
+
+            BMDTimeScale timescale;
+            BMDTimeValue frameduration;
+            vlc_tick_t lasttimestamp;
+            /* XXX: workaround card clock drift */
+            vlc_tick_t offset;
+
+            bool b_running;
+            int Start();
+            const char *ErrorToString(long i_code);
+            IDeckLinkDisplayMode * MatchDisplayMode(const video_format_t *,
+                                                    BMDDisplayMode = bmdDisplayModeNotSupported);
+            int doProcessVideo(picture_t *);
+            picture_t * CreateNoSignalPicture(const char*, const video_format_t *);
+    };
+}
+
+#endif // DBMSDIOUTPUT_HPP
diff --git a/modules/stream_out/sdi/SDIOutput.cpp b/modules/stream_out/sdi/SDIOutput.cpp
new file mode 100644
index 0000000000..d71e4dd5f3
--- /dev/null
+++ b/modules/stream_out/sdi/SDIOutput.cpp
@@ -0,0 +1,157 @@
+/*****************************************************************************
+ * SDIOutput.cpp: SDI sout module for vlc
+ *****************************************************************************
+ * Copyright © 2018 VideoLabs, VideoLAN and VideoLAN Authors
+ *
+ * This program 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 program 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 program; 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
+
+#include "SDIOutput.hpp"
+#include "SDIStream.hpp"
+#include "sdiout.hpp"
+
+#include <vlc_sout.h>
+#include <vlc_picture.h>
+
+using namespace sdi_sout;
+
+SDIOutput::SDIOutput(sout_stream_t *p_stream_)
+{
+    p_stream = p_stream_;
+    p_stream->pf_add     = SoutCallback_Add;
+    p_stream->pf_del     = SoutCallback_Del;
+    p_stream->pf_send    = SoutCallback_Send;
+    p_stream->pf_flush   = SoutCallback_Flush;
+    p_stream->pf_control = SoutCallback_Control;
+    p_stream->pace_nocontrol = true;
+
+    es_format_Init(&video.configuredfmt, VIDEO_ES, 0);
+    es_format_Init(&audio.configuredfmt, AUDIO_ES, 0);
+    video.tenbits = var_InheritBool(p_stream, CFG_PREFIX "tenbits");
+    video.nosignal_delay = var_InheritInteger(p_stream, CFG_PREFIX "nosignal-delay");
+    video.pic_nosignal = NULL;
+    audio.i_channels = var_InheritInteger(p_stream, CFG_PREFIX "channels");;
+    ancillary.afd = var_InheritInteger(p_stream, CFG_PREFIX "afd");
+    ancillary.ar = var_InheritInteger(p_stream, CFG_PREFIX "ar");
+    ancillary.afd_line = var_InheritInteger(p_stream, CFG_PREFIX "afd-line");
+    videoStream = NULL;
+    audioStream = NULL;
+}
+
+SDIOutput::~SDIOutput()
+{
+    videoBuffer.FlushQueued();
+    audioBuffer.FlushQueued();
+    if(video.pic_nosignal)
+        picture_Release(video.pic_nosignal);
+    es_format_Clean(&video.configuredfmt);
+    es_format_Clean(&audio.configuredfmt);
+}
+
+AbstractStream *SDIOutput::Add(const es_format_t *fmt)
+{
+    AbstractStream *s = NULL;
+    StreamID id(fmt->i_id);
+    if(fmt->i_cat == VIDEO_ES && !videoStream)
+    {
+        if(ConfigureVideo(&fmt->video) == VLC_SUCCESS)
+            s = videoStream = dynamic_cast<VideoDecodedStream *>(createStream(id, fmt, &videoBuffer));
+        if(videoStream)
+            videoStream->setOutputFormat(&video.configuredfmt);
+    }
+    else if(fmt->i_cat == AUDIO_ES && audio.i_channels && !audioStream)
+    {
+        if(ConfigureAudio(&fmt->audio) == VLC_SUCCESS)
+            s = audioStream = dynamic_cast<AudioDecodedStream *>(createStream(id, fmt, &audioBuffer));
+        if(audioStream)
+            audioStream->setOutputFormat(&audio.configuredfmt);
+    }
+    return s;
+}
+
+int SDIOutput::Send(AbstractStream *id, block_t *p)
+{
+    int ret = id->Send(p);
+    Process();
+    return ret;
+}
+
+void SDIOutput::Del(AbstractStream *s)
+{
+    s->Drain();
+    Process();
+    if(videoStream == s)
+        videoStream = NULL;
+    else if(audioStream == s)
+        audioStream = NULL;
+    delete s;
+}
+
+int SDIOutput::Control(int, va_list)
+{
+    return VLC_EGENERIC;
+}
+
+AbstractStream *SDIOutput::createStream(const StreamID &id,
+                                        const es_format_t *fmt,
+                                        AbstractStreamOutputBuffer *buffer)
+{
+    AbstractStream *s;
+    if(fmt->i_cat == VIDEO_ES)
+        s = new VideoDecodedStream(VLC_OBJECT(p_stream), id, buffer);
+    else if(fmt->i_cat == AUDIO_ES)
+        s = new AudioDecodedStream(VLC_OBJECT(p_stream), id, buffer);
+    else
+        s = NULL;
+
+     if(s && !s->init(fmt))
+     {
+         delete s;
+         return NULL;
+     }
+     return s;
+}
+
+void *SDIOutput::SoutCallback_Add(sout_stream_t *p_stream, const es_format_t *fmt)
+{
+    SDIOutput *me = reinterpret_cast<SDIOutput *>(p_stream->p_sys);
+    return me->Add(fmt);
+}
+
+void SDIOutput::SoutCallback_Del(sout_stream_t *p_stream, void *id)
+{
+    SDIOutput *me = reinterpret_cast<SDIOutput *>(p_stream->p_sys);
+    me->Del(reinterpret_cast<AbstractStream *>(id));
+}
+
+int SDIOutput::SoutCallback_Send(sout_stream_t *p_stream, void *id, block_t *p_block)
+{
+    SDIOutput *me = reinterpret_cast<SDIOutput *>(p_stream->p_sys);
+    return me->Send(reinterpret_cast<AbstractStream *>(id), p_block);
+}
+
+int SDIOutput::SoutCallback_Control(sout_stream_t *p_stream, int query, va_list args)
+{
+    SDIOutput *me = reinterpret_cast<SDIOutput *>(p_stream->p_sys);
+    return me->Control(query, args);
+}
+
+void SDIOutput::SoutCallback_Flush(sout_stream_t *, void *id)
+{
+    reinterpret_cast<AbstractStream *>(id)->Flush();
+}
diff --git a/modules/stream_out/sdi/SDIOutput.hpp b/modules/stream_out/sdi/SDIOutput.hpp
new file mode 100644
index 0000000000..4d348f9cd5
--- /dev/null
+++ b/modules/stream_out/sdi/SDIOutput.hpp
@@ -0,0 +1,81 @@
+/*****************************************************************************
+ * SDIOutput.hpp: SDI sout module for vlc
+ *****************************************************************************
+ * Copyright © 2018 VideoLabs, VideoLAN and VideoLAN Authors
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef SDIOUTPUT_HPP
+#define SDIOUTPUT_HPP
+
+#include "SDIStream.hpp"
+#include <vlc_common.h>
+
+namespace sdi_sout
+{
+    class SDIOutput
+    {
+        public:
+            SDIOutput(sout_stream_t *);
+            virtual ~SDIOutput();
+            virtual int Open() = 0;
+            virtual int Process() = 0;
+            virtual AbstractStream *Add(const es_format_t *);
+            virtual int   Send(AbstractStream *, block_t *);
+            virtual void  Del(AbstractStream *);
+            virtual int   Control(int, va_list);
+
+        protected:
+            virtual AbstractStream * createStream(const StreamID &,
+                                                  const es_format_t *,
+                                                  AbstractStreamOutputBuffer *);
+            virtual int ConfigureVideo(const video_format_t *) = 0;
+            virtual int ConfigureAudio(const audio_format_t *) = 0;
+            sout_stream_t *p_stream;
+            VideoDecodedStream *videoStream;
+            AudioDecodedStream *audioStream;
+            PictureStreamOutputBuffer videoBuffer;
+            BlockStreamOutputBuffer audioBuffer;
+
+            struct
+            {
+                es_format_t configuredfmt;
+                bool tenbits;
+                int nosignal_delay;
+                picture_t *pic_nosignal;
+            } video;
+
+            struct
+            {
+                es_format_t configuredfmt;
+                uint8_t i_channels;
+            } audio;
+
+            struct
+            {
+                uint8_t afd, ar;
+                unsigned afd_line;
+            } ancillary;
+
+        private:
+            static void *SoutCallback_Add(sout_stream_t *, const es_format_t *);
+            static void  SoutCallback_Del(sout_stream_t *, void *);
+            static int   SoutCallback_Send(sout_stream_t *, void *, block_t*);
+            static int   SoutCallback_Control(sout_stream_t *, int, va_list);
+            static void  SoutCallback_Flush(sout_stream_t *, void *);
+    };
+}
+
+#endif
diff --git a/modules/stream_out/sdi/SDIStream.cpp b/modules/stream_out/sdi/SDIStream.cpp
new file mode 100644
index 0000000000..749c6b5a4d
--- /dev/null
+++ b/modules/stream_out/sdi/SDIStream.cpp
@@ -0,0 +1,511 @@
+/*****************************************************************************
+ * SDIStream.cpp: SDI sout module for vlc
+ *****************************************************************************
+ * Copyright © 2018 VideoLabs, VideoLAN and VideoLAN Authors
+ *
+ * This program 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 program 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 program; 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
+
+#include "SDIStream.hpp"
+
+#include "sdiout.hpp"
+
+#include <vlc_modules.h>
+#include <vlc_codec.h>
+#include <vlc_meta.h>
+#include <vlc_block.h>
+
+#include <sstream>
+
+using namespace sdi_sout;
+
+AbstractStreamOutputBuffer::AbstractStreamOutputBuffer()
+{
+}
+
+AbstractStreamOutputBuffer::~AbstractStreamOutputBuffer()
+{
+}
+
+void AbstractStreamOutputBuffer::Enqueue(void *p)
+{
+    queue_mutex.lock();
+    queued.push(p);
+    queue_mutex.unlock();
+}
+
+void *AbstractStreamOutputBuffer::Dequeue()
+{
+    void *p = NULL;
+    queue_mutex.lock();
+    if(!queued.empty())
+    {
+        p = queued.front();
+        queued.pop();
+    }
+    queue_mutex.unlock();
+    return p;
+}
+
+BlockStreamOutputBuffer::BlockStreamOutputBuffer()
+    : AbstractStreamOutputBuffer()
+{
+
+}
+
+BlockStreamOutputBuffer::~BlockStreamOutputBuffer()
+{
+
+}
+
+void BlockStreamOutputBuffer::FlushQueued()
+{
+    block_t *p;
+    while((p = reinterpret_cast<block_t *>(Dequeue())))
+        block_Release(p);
+}
+
+
+PictureStreamOutputBuffer::PictureStreamOutputBuffer()
+    : AbstractStreamOutputBuffer()
+{
+
+}
+
+PictureStreamOutputBuffer::~PictureStreamOutputBuffer()
+{
+
+}
+
+void PictureStreamOutputBuffer::FlushQueued()
+{
+    picture_t *p;
+    while((p = reinterpret_cast<picture_t *>(Dequeue())))
+        picture_Release(p);
+}
+
+unsigned StreamID::i_next_sequence_id = 0;
+
+StreamID::StreamID(int i_stream_id)
+{
+    stream_id = i_stream_id;
+    sequence_id = i_next_sequence_id++;
+}
+
+StreamID::StreamID(int i_stream_id, int i_sequence)
+{
+    stream_id = i_stream_id;
+    sequence_id = i_sequence;
+}
+
+std::string StreamID::toString() const
+{
+    std::stringstream ss;
+    ss.imbue(std::locale("C"));
+    ss << "Stream(";
+    if(stream_id > -1)
+        ss << "id #" << stream_id << ", ";
+    ss << "seq " << sequence_id << ")";
+    return ss.str();
+}
+
+StreamID& StreamID::operator=(const StreamID &other)
+{
+    stream_id = other.stream_id;
+    sequence_id = other.sequence_id;
+    return *this;
+}
+
+bool StreamID::operator==(const StreamID &other)
+{
+    if(stream_id == -1 || other.stream_id == -1)
+        return sequence_id == other.sequence_id;
+    else
+        return stream_id == other.stream_id;
+}
+
+AbstractStream::AbstractStream(vlc_object_t *p_obj,
+                               const StreamID &id,
+                               AbstractStreamOutputBuffer *buffer)
+    : id(id)
+{
+    p_stream = p_obj;
+    outputbuffer = buffer;
+}
+
+AbstractStream::~AbstractStream()
+{
+
+}
+
+const StreamID & AbstractStream::getID() const
+{
+    return id;
+}
+
+struct decoder_owner
+{
+    decoder_t dec;
+    AbstractDecodedStream *id;
+    bool b_error;
+    es_format_t last_fmt_update;
+    es_format_t decoder_out;
+};
+
+AbstractDecodedStream::AbstractDecodedStream(vlc_object_t *p_obj,
+                                             const StreamID &id,
+                                             AbstractStreamOutputBuffer *buffer)
+    : AbstractStream(p_obj, id, buffer)
+{
+    p_decoder = NULL;
+    es_format_Init(&requestedoutput, 0, 0);
+}
+
+AbstractDecodedStream::~AbstractDecodedStream()
+{
+    es_format_Clean(&requestedoutput);
+
+    if(!p_decoder)
+        return;
+
+    struct decoder_owner *p_owner;
+    p_owner = container_of(p_decoder, struct decoder_owner, dec);
+    if(p_decoder->p_module)
+        module_unneed(p_decoder, p_decoder->p_module);
+    es_format_Clean(&p_owner->dec.fmt_in);
+    es_format_Clean(&p_owner->dec.fmt_out);
+    es_format_Clean(&p_owner->decoder_out);
+    es_format_Clean(&p_owner->last_fmt_update);
+    if(p_decoder->p_description)
+        vlc_meta_Delete(p_decoder->p_description);
+    vlc_object_release(p_decoder);
+}
+
+bool AbstractDecodedStream::init(const es_format_t *p_fmt)
+{
+    const char *category;
+    if(p_fmt->i_cat == VIDEO_ES)
+        category = "video decoder";
+    else if(p_fmt->i_cat == AUDIO_ES)
+        category = "audio decoder";
+    else
+        return false;
+
+    /* Create decoder object */
+    struct decoder_owner * p_owner =
+            reinterpret_cast<struct decoder_owner *>(
+                vlc_object_create(p_stream, sizeof(*p_owner)));
+    if(!p_owner)
+        return false;
+
+    es_format_Init(&p_owner->decoder_out, p_fmt->i_cat, 0);
+    es_format_Init(&p_owner->last_fmt_update, p_fmt->i_cat, 0);
+    p_owner->b_error = false;
+    p_owner->id = this;
+
+    p_decoder = &p_owner->dec;
+    p_decoder->p_module = NULL;
+    es_format_Init(&p_decoder->fmt_out, p_fmt->i_cat, 0);
+    es_format_Copy(&p_decoder->fmt_in, p_fmt);
+    p_decoder->b_frame_drop_allowed = false;
+
+    setCallbacks();
+
+    p_decoder->pf_decode = NULL;
+    p_decoder->pf_get_cc = NULL;
+
+    p_decoder->p_module = module_need_var(p_decoder, category, "codec");
+    if(!p_decoder->p_module)
+    {
+        msg_Err(p_stream, "cannot find %s for %4.4s", category, (char *)&p_fmt->i_codec);
+        es_format_Clean(&p_decoder->fmt_in);
+        es_format_Clean(&p_decoder->fmt_out);
+        es_format_Clean(&p_owner->decoder_out);
+        es_format_Clean(&p_owner->last_fmt_update);
+        vlc_object_release(p_decoder);
+        p_decoder = NULL;
+        return false;
+    }
+
+    return true;
+}
+
+int AbstractDecodedStream::Send(block_t *p_block)
+{
+    assert(p_decoder);
+
+    struct decoder_owner *p_owner =
+            container_of(p_decoder, struct decoder_owner, dec);
+
+     if(!p_owner->b_error)
+    {
+        int ret = p_decoder->pf_decode(p_decoder, p_block);
+        switch(ret)
+        {
+            case VLCDEC_SUCCESS:
+                break;
+            case VLCDEC_ECRITICAL:
+                p_owner->b_error = true;
+                break;
+            case VLCDEC_RELOAD:
+                p_owner->b_error = true;
+                if(p_block)
+                    block_Release(p_block);
+                break;
+            default:
+                vlc_assert_unreachable();
+        }
+    }
+
+    return p_owner->b_error ? VLC_EGENERIC : VLC_SUCCESS;
+}
+
+void AbstractDecodedStream::Flush()
+{
+}
+
+void AbstractDecodedStream::Drain()
+{
+    Send(NULL);
+}
+
+void AbstractDecodedStream::setOutputFormat(const es_format_t *p_fmt)
+{
+    es_format_Clean(&requestedoutput);
+    es_format_Copy(&requestedoutput, p_fmt);
+}
+
+VideoDecodedStream::VideoDecodedStream(vlc_object_t *p_obj,
+                                       const StreamID &id,
+                                       AbstractStreamOutputBuffer *buffer)
+    :AbstractDecodedStream(p_obj, id, buffer)
+{
+    p_filters_chain = NULL;
+}
+
+VideoDecodedStream::~VideoDecodedStream()
+{
+    if(p_filters_chain)
+        filter_chain_Delete(p_filters_chain);
+}
+
+void VideoDecodedStream::setCallbacks()
+{
+    static struct decoder_owner_callbacks dec_cbs;
+    memset(&dec_cbs, 0, sizeof(dec_cbs));
+    dec_cbs.video.format_update = VideoDecCallback_update_format;
+    dec_cbs.video.buffer_new = VideoDecCallback_new_buffer;
+    dec_cbs.video.queue = VideoDecCallback_queue;
+
+    p_decoder->cbs = &dec_cbs;
+}
+
+void VideoDecodedStream::VideoDecCallback_queue(decoder_t *p_dec, picture_t *p_pic)
+{
+    struct decoder_owner *p_owner;
+    p_owner = container_of(p_dec, struct decoder_owner, dec);
+    static_cast<VideoDecodedStream *>(p_owner->id)->Output(p_pic);
+}
+
+int VideoDecodedStream::VideoDecCallback_update_format(decoder_t *p_dec)
+{
+    struct decoder_owner *p_owner;
+    p_owner = container_of(p_dec, struct decoder_owner, dec);
+
+    /* fixup */
+    p_dec->fmt_out.video.i_chroma = p_dec->fmt_out.i_codec;
+
+    es_format_Clean(&p_owner->last_fmt_update);
+    es_format_Copy(&p_owner->last_fmt_update, &p_dec->fmt_out);
+
+    return VLC_SUCCESS;
+}
+
+picture_t *VideoDecodedStream::VideoDecCallback_new_buffer(decoder_t *p_dec)
+{
+    return picture_NewFromFormat(&p_dec->fmt_out.video);
+}
+
+
+static picture_t *transcode_video_filter_buffer_new(filter_t *p_filter)
+{
+    p_filter->fmt_out.video.i_chroma = p_filter->fmt_out.i_codec;
+    return picture_NewFromFormat(&p_filter->fmt_out.video);
+}
+
+static const struct filter_video_callbacks transcode_filter_video_cbs =
+{
+    .buffer_new = transcode_video_filter_buffer_new,
+};
+
+filter_chain_t * VideoDecodedStream::VideoFilterCreate(const es_format_t *p_srcfmt)
+{
+    filter_chain_t *p_chain;
+    filter_owner_t owner;
+    memset(&owner, 0, sizeof(owner));
+    owner.video = &transcode_filter_video_cbs;
+
+    p_chain = filter_chain_NewVideo(p_stream, false, &owner);
+    if(!p_chain)
+        return NULL;
+    filter_chain_Reset(p_chain, p_srcfmt, &requestedoutput);
+
+    if(p_srcfmt->video.i_chroma != requestedoutput.video.i_chroma)
+    {
+        if(filter_chain_AppendConverter(p_chain, p_srcfmt, &requestedoutput) != VLC_SUCCESS)
+        {
+            filter_chain_Delete(p_chain);
+            return NULL;
+        }
+    }
+
+    const es_format_t *p_fmt_out = filter_chain_GetFmtOut(p_chain);
+    if(!es_format_IsSimilar(&requestedoutput, p_fmt_out))
+    {
+        filter_chain_Delete(p_chain);
+        return NULL;
+    }
+
+    return p_chain;
+}
+
+void VideoDecodedStream::Output(picture_t *p_pic)
+{
+    struct decoder_owner *p_owner;
+    p_owner = container_of(p_decoder, struct decoder_owner, dec);
+
+    if(!es_format_IsSimilar(&p_owner->last_fmt_update, &p_owner->decoder_out))
+    {
+
+        msg_Dbg(p_stream, "decoder output format now %4.4s",
+                (char*)&p_owner->last_fmt_update.i_codec);
+
+        if(p_filters_chain)
+            filter_chain_Delete(p_filters_chain);
+        p_filters_chain = VideoFilterCreate(&p_owner->last_fmt_update);
+        if(!p_filters_chain)
+        {
+            picture_Release(p_pic);
+            return;
+        }
+
+        es_format_Clean(&p_owner->decoder_out);
+        es_format_Copy(&p_owner->decoder_out, &p_owner->last_fmt_update);
+    }
+
+    if(p_filters_chain)
+        p_pic = filter_chain_VideoFilter(p_filters_chain, p_pic);
+
+    if(p_pic)
+        outputbuffer->Enqueue(p_pic);
+}
+
+AudioDecodedStream::AudioDecodedStream(vlc_object_t *p_obj,
+                                       const StreamID &id,
+                                       AbstractStreamOutputBuffer *buffer)
+    :AbstractDecodedStream(p_obj, id, buffer)
+{
+    p_filters = NULL;
+}
+
+AudioDecodedStream::~AudioDecodedStream()
+{
+    if(p_filters)
+        aout_FiltersDelete(p_stream, p_filters);
+}
+
+void AudioDecodedStream::AudioDecCallback_queue(decoder_t *p_dec, block_t *p_block)
+{
+    struct decoder_owner *p_owner;
+    p_owner = container_of(p_dec, struct decoder_owner, dec);
+    static_cast<AudioDecodedStream *>(p_owner->id)->Output(p_block);
+}
+
+void AudioDecodedStream::Output(block_t *p_block)
+{
+    struct decoder_owner *p_owner;
+    p_owner = container_of(p_decoder, struct decoder_owner, dec);
+
+    if(!es_format_IsSimilar(&p_owner->last_fmt_update, &p_owner->decoder_out))
+    {
+        msg_Dbg(p_stream, "decoder output format now %4.4s %u channels",
+                (char*)&p_owner->last_fmt_update.i_codec,
+                p_owner->last_fmt_update.audio.i_channels);
+
+        if(p_filters)
+            aout_FiltersDelete(p_stream, p_filters);
+        p_filters = AudioFiltersCreate(&p_owner->last_fmt_update);
+        if(!p_filters)
+        {
+            msg_Err(p_stream, "filter creation failed");
+            block_Release(p_block);
+            return;
+        }
+
+        es_format_Clean(&p_owner->decoder_out);
+        es_format_Copy(&p_owner->decoder_out, &p_owner->last_fmt_update);
+    }
+
+    /* Run filter chain */
+    if(p_filters)
+        p_block = aout_FiltersPlay(p_filters, p_block, 1.f);
+
+    if(p_block && !p_block->i_nb_samples &&
+       p_owner->last_fmt_update.audio.i_bytes_per_frame )
+    {
+        p_block->i_nb_samples = p_block->i_buffer /
+                p_owner->last_fmt_update.audio.i_bytes_per_frame;
+    }
+
+    if(p_block)
+        outputbuffer->Enqueue(p_block);
+}
+
+aout_filters_t * AudioDecodedStream::AudioFiltersCreate(const es_format_t *afmt)
+{
+    return aout_FiltersNew(p_stream, &afmt->audio, &requestedoutput.audio, NULL, NULL);
+}
+
+int AudioDecodedStream::AudioDecCallback_update_format(decoder_t *p_dec)
+{
+    struct decoder_owner *p_owner;
+    p_owner = container_of(p_dec, struct decoder_owner, dec);
+
+    if( !AOUT_FMT_LINEAR(&p_dec->fmt_out.audio) )
+        return VLC_EGENERIC;
+
+    /* fixup */
+    p_dec->fmt_out.audio.i_format = p_dec->fmt_out.i_codec;
+    aout_FormatPrepare(&p_dec->fmt_out.audio);
+
+    es_format_Clean(&p_owner->last_fmt_update);
+    es_format_Copy(&p_owner->last_fmt_update, &p_dec->fmt_out);
+
+    p_owner->last_fmt_update.audio.i_format = p_owner->last_fmt_update.i_codec;
+
+    return VLC_SUCCESS;
+}
+
+void AudioDecodedStream::setCallbacks()
+{
+    static struct decoder_owner_callbacks dec_cbs;
+    memset(&dec_cbs, 0, sizeof(dec_cbs));
+    dec_cbs.audio.format_update = AudioDecCallback_update_format;
+    dec_cbs.audio.queue = AudioDecCallback_queue;
+    p_decoder->cbs = &dec_cbs;
+}
diff --git a/modules/stream_out/sdi/SDIStream.hpp b/modules/stream_out/sdi/SDIStream.hpp
new file mode 100644
index 0000000000..c3c0b979bd
--- /dev/null
+++ b/modules/stream_out/sdi/SDIStream.hpp
@@ -0,0 +1,149 @@
+/*****************************************************************************
+ * SDIStream.hpp: SDI sout module for vlc
+ *****************************************************************************
+ * Copyright © 2018 VideoLabs, VideoLAN and VideoLAN Authors
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef SDISTREAM_HPP
+#define SDISTREAM_HPP
+
+#include <vlc_common.h>
+#include <vlc_filter.h>
+#include <vlc_aout.h>
+#include <queue>
+#include <mutex>
+
+namespace sdi_sout
+{
+    class AbstractStreamOutputBuffer
+    {
+        public:
+            AbstractStreamOutputBuffer();
+            virtual ~AbstractStreamOutputBuffer();
+            virtual void FlushQueued() = 0;
+            void Enqueue(void *);
+            void * Dequeue();
+
+        private:
+            std::mutex queue_mutex;
+            std::queue<void *> queued;
+    };
+
+    class BlockStreamOutputBuffer : public AbstractStreamOutputBuffer
+    {
+        public:
+            BlockStreamOutputBuffer();
+            virtual ~BlockStreamOutputBuffer();
+            virtual void FlushQueued();
+    };
+
+    class PictureStreamOutputBuffer : public AbstractStreamOutputBuffer
+    {
+        public:
+            PictureStreamOutputBuffer();
+            virtual ~PictureStreamOutputBuffer();
+            virtual void FlushQueued();
+    };
+
+    class StreamID
+    {
+        public:
+            StreamID(int);
+            StreamID(int, int);
+            StreamID& operator=(const StreamID &);
+            bool      operator==(const StreamID &);
+            std::string toString() const;
+
+        private:
+            int stream_id;
+            unsigned sequence_id;
+            static unsigned i_next_sequence_id;
+    };
+
+    class AbstractStream
+    {
+        public:
+            AbstractStream(vlc_object_t *, const StreamID &,
+                           AbstractStreamOutputBuffer *);
+            virtual ~AbstractStream();
+            virtual bool init(const es_format_t *) = 0;
+            virtual int Send(block_t*) = 0;
+            virtual void Drain() = 0;
+            virtual void Flush() = 0;
+            const StreamID & getID() const;
+
+        protected:
+            vlc_object_t *p_stream;
+            AbstractStreamOutputBuffer *outputbuffer;
+
+        private:
+            StreamID id;
+    };
+
+    class AbstractDecodedStream : public AbstractStream
+    {
+        public:
+            AbstractDecodedStream(vlc_object_t *, const StreamID &,
+                                  AbstractStreamOutputBuffer *);
+            virtual ~AbstractDecodedStream();
+            virtual bool init(const es_format_t *); /* impl */
+            virtual int Send(block_t*);
+            virtual void Flush();
+            virtual void Drain();
+            void setOutputFormat(const es_format_t *);
+
+        protected:
+            decoder_t *p_decoder;
+            virtual void setCallbacks() = 0;
+            es_format_t requestedoutput;
+    };
+
+    class VideoDecodedStream : public AbstractDecodedStream
+    {
+        public:
+            VideoDecodedStream(vlc_object_t *, const StreamID &,
+                               AbstractStreamOutputBuffer *);
+            virtual ~VideoDecodedStream();
+            virtual void setCallbacks();
+
+        private:
+            static void VideoDecCallback_queue(decoder_t *, picture_t *);
+            static int VideoDecCallback_update_format(decoder_t *);
+            static picture_t *VideoDecCallback_new_buffer(decoder_t *);
+            filter_chain_t * VideoFilterCreate(const es_format_t *);
+            void Output(picture_t *);
+            filter_chain_t *p_filters_chain;
+    };
+
+#   define FRAME_SIZE 1920
+    class AudioDecodedStream : public AbstractDecodedStream
+    {
+        public:
+            AudioDecodedStream(vlc_object_t *, const StreamID &,
+                               AbstractStreamOutputBuffer *);
+            virtual ~AudioDecodedStream();
+            virtual void setCallbacks();
+
+        private:
+            static void AudioDecCallback_queue(decoder_t *, block_t *);
+            static int AudioDecCallback_update_format(decoder_t *);
+            aout_filters_t *AudioFiltersCreate(const es_format_t *);
+            void Output(block_t *);
+            aout_filters_t *p_filters;
+    };
+}
+
+#endif
diff --git a/modules/stream_out/sdi/V210.cpp b/modules/stream_out/sdi/V210.cpp
new file mode 100644
index 0000000000..1cc6c90d5c
--- /dev/null
+++ b/modules/stream_out/sdi/V210.cpp
@@ -0,0 +1,96 @@
+/*****************************************************************************
+ * V210.cpp: V210 picture conversion
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; 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
+
+#include "V210.hpp"
+
+#include <vlc_picture.h>
+
+using namespace sdi;
+
+static inline unsigned clip(unsigned a)
+{
+    if      (a < 4) return 4;
+    else if (a > 1019) return 1019;
+    else               return a;
+}
+
+static inline void put_le32(uint8_t **p, uint32_t d)
+{
+    SetDWLE(*p, d);
+    (*p) += 4;
+}
+
+void V210::Convert(const picture_t *pic, unsigned dst_stride, void *frame_bytes)
+{
+    unsigned width = pic->format.i_width;
+    unsigned height = pic->format.i_height;
+    unsigned payload_size = ((width * 8 + 11) / 12) * 4;
+    unsigned line_padding = (payload_size < dst_stride) ? dst_stride - payload_size : 0;
+    unsigned h, w;
+    uint8_t *dst = (uint8_t*)frame_bytes;
+
+    const uint16_t *y = (const uint16_t*)pic->p[0].p_pixels;
+    const uint16_t *u = (const uint16_t*)pic->p[1].p_pixels;
+    const uint16_t *v = (const uint16_t*)pic->p[2].p_pixels;
+
+#define WRITE_PIXELS(a, b, c)           \
+    do {                                \
+        val =   clip(*a++);             \
+        val |= (clip(*b++) << 10) |     \
+               (clip(*c++) << 20);      \
+        put_le32(&dst, val);           \
+    } while (0)
+
+    for (h = 0; h < height; h++) {
+        uint32_t val = 0;
+        for (w = 0; w + 5 < width; w += 6) {
+            WRITE_PIXELS(u, y, v);
+            WRITE_PIXELS(y, u, y);
+            WRITE_PIXELS(v, y, u);
+            WRITE_PIXELS(y, v, y);
+        }
+        if (w + 1 < width) {
+            WRITE_PIXELS(u, y, v);
+
+            val = clip(*y++);
+            if (w + 2 == width)
+                put_le32(&dst, val);
+#undef WRITE_PIXELS
+        }
+        if (w + 3 < width) {
+            val |= (clip(*u++) << 10) | (clip(*y++) << 20);
+            put_le32(&dst, val);
+
+            val = clip(*v++) | (clip(*y++) << 10);
+            put_le32(&dst, val);
+        }
+
+        memset(dst, 0, line_padding);
+        dst += line_padding;
+
+        y += pic->p[0].i_pitch / 2 - width;
+        u += pic->p[1].i_pitch / 2 - width / 2;
+        v += pic->p[2].i_pitch / 2 - width / 2;
+    }
+}
diff --git a/modules/stream_out/sdi/V210.hpp b/modules/stream_out/sdi/V210.hpp
new file mode 100644
index 0000000000..5a192cbd41
--- /dev/null
+++ b/modules/stream_out/sdi/V210.hpp
@@ -0,0 +1,37 @@
+/*****************************************************************************
+ * V210.hpp: V210 picture conversion
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#ifndef V210_HPP
+#define V210_HPP
+
+#include <vlc_common.h>
+
+namespace sdi
+{
+
+    class V210
+    {
+        public:
+            static void Convert(const picture_t *, unsigned, void *);
+    };
+
+}
+
+#endif // V210_HPP
diff --git a/modules/stream_out/sdi/sdiout.cpp b/modules/stream_out/sdi/sdiout.cpp
new file mode 100644
index 0000000000..eb450cfb10
--- /dev/null
+++ b/modules/stream_out/sdi/sdiout.cpp
@@ -0,0 +1,205 @@
+/*****************************************************************************
+ * sdiout.cpp: SDI sout module for vlc
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; 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
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#include "sdiout.hpp"
+
+#include "DBMSDIOutput.hpp"
+
+#include <vlc_common.h>
+#include <vlc_sout.h>
+#include <vlc_plugin.h>
+
+#define NOSIGNAL_INDEX_TEXT N_("Timelength after which we assume there is no signal.")
+#define NOSIGNAL_INDEX_LONGTEXT N_(\
+    "Timelength after which we assume there is no signal.\n"\
+    "After this delay we black out the video."\
+    )
+
+#define AFD_INDEX_TEXT N_("Active Format Descriptor value")
+
+#define AR_INDEX_TEXT N_("Aspect Ratio")
+#define AR_INDEX_LONGTEXT N_("Aspect Ratio of the source picture.")
+
+#define AFDLINE_INDEX_TEXT N_("Active Format Descriptor line")
+#define AFDLINE_INDEX_LONGTEXT N_("VBI line on which to output Active Format Descriptor.")
+
+#define NOSIGNAL_IMAGE_TEXT N_("Picture to display on input signal loss")
+#define NOSIGNAL_IMAGE_LONGTEXT NOSIGNAL_IMAGE_TEXT
+
+#define CARD_INDEX_TEXT N_("Output card")
+#define CARD_INDEX_LONGTEXT N_(\
+    "DeckLink output card, if multiple exist. " \
+    "The cards are numbered from 0.")
+
+#define MODE_TEXT N_("Desired output mode")
+#define MODE_LONGTEXT N_(\
+    "Desired output mode for DeckLink output. " \
+    "This value should be a FOURCC code in textual " \
+    "form, e.g. \"ntsc\".")
+
+#define CHANNELS_TEXT N_("Number of audio channels")
+#define CHANNELS_LONGTEXT N_(\
+    "Number of output channels for DeckLink output. " \
+"Must be 2, 8 or 16. 0 disables audio output.")
+
+#define VIDEO_CONNECTION_TEXT N_("Video connection")
+#define VIDEO_CONNECTION_LONGTEXT N_(\
+    "Video connection for DeckLink output.")
+
+#define VIDEO_TENBITS_TEXT N_("10 bits")
+#define VIDEO_TENBITS_LONGTEXT N_(\
+    "Use 10 bits per pixel for video frames.")
+
+/* Video Connections */
+static const char *const ppsz_videoconns[] = {
+    "sdi",
+    "hdmi",
+    "opticalsdi",
+    "component",
+    "composite",
+    "svideo"
+};
+static const char *const ppsz_videoconns_text[] = {
+    "SDI",
+    "HDMI",
+    "Optical SDI",
+    "Component",
+    "Composite",
+    "S-video",
+};
+static const BMDVideoConnection rgbmd_videoconns[] =
+{
+    bmdVideoConnectionSDI,
+    bmdVideoConnectionHDMI,
+    bmdVideoConnectionOpticalSDI,
+    bmdVideoConnectionComponent,
+    bmdVideoConnectionComposite,
+    bmdVideoConnectionSVideo,
+};
+static_assert(ARRAY_SIZE(rgbmd_videoconns) == ARRAY_SIZE(ppsz_videoconns), "videoconn arrays messed up");
+static_assert(ARRAY_SIZE(rgbmd_videoconns) == ARRAY_SIZE(ppsz_videoconns_text), "videoconn arrays messed up");
+
+static const int rgi_afd_values[] = {
+    0, 2, 3, 4, 8, 9, 10, 11, 13, 14, 15,
+};
+static const char * const rgsz_afd_text[] = {
+    "Undefined",
+    "Box 16:9 (top aligned)",
+    "Box 14:9 (top aligned)",
+    "Box > 16:9 (centre aligned)",
+    "Same as coded frame (full frame)",
+    "4:3  (centre aligned)",
+    "16:9 (centre aligned)",
+    "14:9 (centre aligned)",
+    "4:3  (with shoot and protect 14:9 centre)",
+    "16:9 (with shoot and protect 14:9 centre)",
+    "16:9 (with shoot and protect  4:3 centre)",
+};
+static_assert(ARRAY_SIZE(rgi_afd_values) == ARRAY_SIZE(rgsz_afd_text), "afd arrays messed up");
+
+static const int rgi_ar_values[] = {
+    0, 1,
+};
+static const char * const rgsz_ar_text[] = {
+    "0:   4:3",
+    "1:  16:9",
+};
+static_assert(ARRAY_SIZE(rgi_ar_values) == ARRAY_SIZE(rgsz_ar_text), "afd arrays messed up");
+
+
+/*****************************************************************************
+ * Sout callbacks
+ *****************************************************************************/
+
+static void CloseSDIOutput(vlc_object_t *p_this)
+{
+    sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
+    sdi_sout::DBMSDIOutput *sdi =
+            reinterpret_cast<sdi_sout::DBMSDIOutput *>(p_stream->p_sys);
+    sdi->Process(); /* Drain */
+    delete sdi;
+}
+
+static int OpenSDIOutput(vlc_object_t *p_this)
+{
+    sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
+    sdi_sout::DBMSDIOutput *output = new sdi_sout::DBMSDIOutput(p_stream);
+
+    if(output->Open() != VLC_SUCCESS && !FAKE_DRIVER)
+    {
+        delete output;
+        return VLC_EGENERIC;
+    }
+    p_stream->p_sys = output;
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+vlc_module_begin ()
+
+    set_shortname(N_("SDI output"))
+    set_description(N_("SDI stream output"))
+    set_capability("sout stream", 0)
+    add_shortcut("sdiout")
+    set_category(CAT_SOUT)
+    set_subcategory(SUBCAT_SOUT_STREAM)
+    set_callbacks(OpenSDIOutput, CloseSDIOutput)
+
+    set_section(N_("DeckLink General Options"), NULL)
+    add_integer(CFG_PREFIX "card-index", 0,
+                CARD_INDEX_TEXT, CARD_INDEX_LONGTEXT, true)
+
+    set_section(N_("DeckLink Video Options"), NULL)
+    add_string(CFG_PREFIX "video-connection", "sdi",
+                VIDEO_CONNECTION_TEXT, VIDEO_CONNECTION_LONGTEXT, true)
+                change_string_list(ppsz_videoconns, ppsz_videoconns_text)
+    add_string(CFG_PREFIX "mode", "",
+                MODE_TEXT, MODE_LONGTEXT, true)
+    add_bool(CFG_PREFIX "tenbits", true,
+                VIDEO_TENBITS_TEXT, VIDEO_TENBITS_LONGTEXT, true)
+    add_integer(CFG_PREFIX "nosignal-delay", 5,
+                NOSIGNAL_INDEX_TEXT, NOSIGNAL_INDEX_LONGTEXT, true)
+    add_integer(CFG_PREFIX "afd-line", 16,
+                AFDLINE_INDEX_TEXT, AFDLINE_INDEX_LONGTEXT, true)
+    add_integer_with_range(CFG_PREFIX "afd", 8, 0, 16,
+                AFD_INDEX_TEXT, AFD_INDEX_TEXT, true)
+                change_integer_list(rgi_afd_values, rgsz_afd_text)
+    add_integer_with_range(CFG_PREFIX "ar", 1, 0, 1,
+                AR_INDEX_TEXT, AR_INDEX_LONGTEXT, true)
+                change_integer_list(rgi_ar_values, rgsz_ar_text)
+    add_loadfile(CFG_PREFIX "nosignal-image", NULL,
+                 NOSIGNAL_IMAGE_TEXT, NOSIGNAL_IMAGE_LONGTEXT)
+
+    set_section(N_("DeckLink Audio Options"), NULL)
+    add_integer_with_range(CFG_PREFIX "channels", 2, 0, 16,
+                CHANNELS_TEXT, CHANNELS_LONGTEXT, true)
+
+vlc_module_end ()
diff --git a/modules/stream_out/sdi/sdiout.hpp b/modules/stream_out/sdi/sdiout.hpp
new file mode 100644
index 0000000000..9444ba35cf
--- /dev/null
+++ b/modules/stream_out/sdi/sdiout.hpp
@@ -0,0 +1,23 @@
+/*****************************************************************************
+ * sdiout.hpp: SDI sout module for vlc
+ *****************************************************************************
+ * Copyright © 2014-2016 VideoLAN and VideoLAN Authors
+ *                  2018 VideoLabs
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+#define CFG_PREFIX "sdiout-"
+
+#define FAKE_DRIVER 0
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ab257a3402..0ab45843b2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1076,6 +1076,7 @@ modules/stream_out/rtp.c
 modules/stream_out/rtpfmt.c
 modules/stream_out/rtp.h
 modules/stream_out/rtsp.c
+modules/stream_out/sdi/sdiout.cpp
 modules/stream_out/setid.c
 modules/stream_out/smem.c
 modules/stream_out/stats.c



More information about the vlc-commits mailing list