[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, ×cale) == 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,
+ ×cale);
+ 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