vlc | branch: master | Francois Cartegnie <[email protected]> | 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 _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
