sout: add SDI stream output

Decklink vout backport, so
this is the currently the only support
This commit is contained in:
Francois Cartegnie 2016-10-18 20:08:06 +02:00
parent 4be7fdc4f4
commit 752df69bf9
17 changed files with 2203 additions and 1 deletions

4
NEWS
View File

@ -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

View File

@ -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}"

View File

@ -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

View File

@ -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 = \

View File

@ -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));
}
}

View File

@ -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

View File

@ -0,0 +1,703 @@
/*****************************************************************************
* DBMSDIOutput.cpp: Decklink SDI Output
*****************************************************************************
* Copyright © 2014-2016 VideoLAN and VideoLAN Authors
* 2018 VideoLabs
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "DBMSDIOutput.hpp"
#include "SDIStream.hpp"
#include "Ancillary.hpp"
#include "V210.hpp"
#include <DeckLinkAPIDispatch.cpp>
#include "sdiout.hpp"
#include <vlc_es.h>
#include <vlc_picture.h>
#include <vlc_interrupt.h>
#include <vlc_image.h>
#include <arpa/inet.h>
using namespace sdi_sout;
DBMSDIOutput::DBMSDIOutput(sout_stream_t *p_stream) :
SDIOutput(p_stream)
{
p_card = NULL;
p_output = NULL;
offset = 0;
lasttimestamp = 0;
b_running = false;
}
DBMSDIOutput::~DBMSDIOutput()
{
if(p_output)
{
BMDTimeValue out;
p_output->StopScheduledPlayback(lasttimestamp, &out, timescale);
p_output->DisableVideoOutput();
p_output->DisableAudioOutput();
p_output->Release();
}
if(p_card)
p_card->Release();
}
AbstractStream *DBMSDIOutput::Add(const es_format_t *fmt)
{
AbstractStream *s = SDIOutput::Add(fmt);
if(s)
{
msg_Dbg(p_stream, "accepted %s %4.4s",
s->getID().toString().c_str(), (const char *) &fmt->i_codec);
if( videoStream && (audioStream || audio.i_channels == 0) )
Start();
}
else
{
msg_Err(p_stream, "rejected es id %d %4.4s",
fmt->i_id, (const char *) &fmt->i_codec);
}
return s;
}
IDeckLinkDisplayMode * DBMSDIOutput::MatchDisplayMode(const video_format_t *fmt, BMDDisplayMode forcedmode)
{
HRESULT result;
IDeckLinkDisplayMode *p_selected = NULL;
IDeckLinkDisplayModeIterator *p_iterator = NULL;
for(int i=0; i<4 && p_selected==NULL; i++)
{
int i_width = (i % 2 == 0) ? fmt->i_width : fmt->i_visible_width;
int i_height = (i % 2 == 0) ? fmt->i_height : fmt->i_visible_height;
int i_div = (i > 2) ? 4 : 0;
result = p_output->GetDisplayModeIterator(&p_iterator);
if(result == S_OK)
{
IDeckLinkDisplayMode *p_mode = NULL;
while(p_iterator->Next(&p_mode) == S_OK)
{
BMDDisplayMode mode_id = p_mode->GetDisplayMode();
BMDTimeValue frameduration;
BMDTimeScale timescale;
const char *psz_mode_name;
if(p_mode->GetFrameRate(&frameduration, &timescale) == S_OK &&
p_mode->GetName(&psz_mode_name) == S_OK)
{
BMDDisplayMode modenl = htonl(mode_id);
if(i==0)
{
BMDFieldDominance field = htonl(p_mode->GetFieldDominance());
msg_Dbg(p_stream, "Found mode '%4.4s': %s (%ldx%ld, %4.4s, %.3f fps, scale %ld dur %ld)",
(const char*)&modenl, psz_mode_name,
p_mode->GetWidth(), p_mode->GetHeight(),
(const char *)&field,
double(timescale) / frameduration,
timescale, frameduration);
}
}
else
{
p_mode->Release();
continue;
}
if(forcedmode != bmdDisplayModeNotSupported && unlikely(!p_selected))
{
BMDDisplayMode modenl = htonl(forcedmode);
msg_Dbg(p_stream, "Forced mode '%4.4s'", (char *)&modenl);
if(forcedmode == mode_id)
p_selected = p_mode;
else
p_mode->Release();
continue;
}
if(p_selected == NULL && forcedmode == bmdDisplayModeNotSupported)
{
if(i_width >> i_div == p_mode->GetWidth() >> i_div &&
i_height >> i_div == p_mode->GetHeight() >> i_div)
{
unsigned int num_deck, den_deck;
unsigned int num_stream, den_stream;
vlc_ureduce(&num_deck, &den_deck, timescale, frameduration, 0);
vlc_ureduce(&num_stream, &den_stream,
fmt->i_frame_rate, fmt->i_frame_rate_base, 0);
if (num_deck == num_stream && den_deck == den_stream)
{
msg_Info(p_stream, "Matches incoming stream");
p_selected = p_mode;
continue;
}
}
}
p_mode->Release();
}
p_iterator->Release();
}
}
return p_selected;
}
const char * DBMSDIOutput::ErrorToString(long i_code)
{
static struct
{
long i_return_code;
const char * const psz_string;
} const errors_to_string[] = {
{ E_UNEXPECTED, "Unexpected error" },
{ E_NOTIMPL, "Not implemented" },
{ E_OUTOFMEMORY, "Out of memory" },
{ E_INVALIDARG, "Invalid argument" },
{ E_NOINTERFACE, "No interface" },
{ E_POINTER, "Invalid pointer" },
{ E_HANDLE, "Invalid handle" },
{ E_ABORT, "Aborted" },
{ E_FAIL, "Failed" },
{ E_ACCESSDENIED,"Access denied" }
};
for(size_t i=0; i<ARRAY_SIZE(errors_to_string); i++)
{
if(errors_to_string[i].i_return_code == i_code)
return errors_to_string[i].psz_string;
}
return NULL;
}
#define CHECK(message) do { \
if (result != S_OK) \
{ \
const char *psz_err = ErrorToString(result); \
if(psz_err)\
msg_Err(p_stream, message ": %s", psz_err); \
else \
msg_Err(p_stream, message ": 0x%X", result); \
goto error; \
} \
} while(0)
int DBMSDIOutput::Open()
{
HRESULT result;
IDeckLinkIterator *decklink_iterator = NULL;
int i_card_index = var_InheritInteger(p_stream, CFG_PREFIX "card-index");
if (i_card_index < 0)
{
msg_Err(p_stream, "Invalid card index %d", i_card_index);
goto error;
}
decklink_iterator = CreateDeckLinkIteratorInstance();
if (!decklink_iterator)
{
msg_Err(p_stream, "DeckLink drivers not found.");
goto error;
}
for(int i = 0; i <= i_card_index; ++i)
{
if (p_card)
{
p_card->Release();
p_card = NULL;
}
result = decklink_iterator->Next(&p_card);
CHECK("Card not found");
}
const char *psz_model_name;
result = p_card->GetModelName(&psz_model_name);
CHECK("Unknown model name");
msg_Dbg(p_stream, "Opened DeckLink PCI card %s", psz_model_name);
result = p_card->QueryInterface(IID_IDeckLinkOutput, (void**)&p_output);
CHECK("No outputs");
decklink_iterator->Release();
return VLC_SUCCESS;
error:
if (p_output)
{
p_output->Release();
p_output = NULL;
}
if (p_card)
{
p_card->Release();
p_output = NULL;
}
if (decklink_iterator)
decklink_iterator->Release();
return VLC_EGENERIC;
}
int DBMSDIOutput::ConfigureAudio(const audio_format_t *)
{
HRESULT result;
if(FAKE_DRIVER)
return VLC_SUCCESS;
if(!p_output)
return VLC_EGENERIC;
if(!video.configuredfmt.i_codec && b_running)
return VLC_EGENERIC;
if (audio.i_channels > 0)
{
audio.configuredfmt.i_codec =
audio.configuredfmt.audio.i_format = VLC_CODEC_S16N;
audio.configuredfmt.audio.i_channels = 2;
audio.configuredfmt.audio.i_physical_channels = AOUT_CHANS_STEREO;
audio.configuredfmt.audio.i_rate = 48000;
audio.configuredfmt.audio.i_bitspersample = 16;
audio.configuredfmt.audio.i_blockalign = 2 * 16 / 8;
audio.configuredfmt.audio.i_frame_length = FRAME_SIZE;
result = p_output->EnableAudioOutput(
bmdAudioSampleRate48kHz,
bmdAudioSampleType16bitInteger,
2,
bmdAudioOutputStreamTimestamped);
CHECK("Could not start audio output");
}
return VLC_SUCCESS;
error:
return VLC_EGENERIC;
}
static BMDVideoConnection getVConn(const char *psz)
{
BMDVideoConnection conn = bmdVideoConnectionSDI;
if(!psz)
return conn;
if (!strcmp(psz, "sdi"))
conn = bmdVideoConnectionSDI;
else if (!strcmp(psz, "hdmi"))
conn = bmdVideoConnectionHDMI;
else if (!strcmp(psz, "opticalsdi"))
conn = bmdVideoConnectionOpticalSDI;
else if (!strcmp(psz, "component"))
conn = bmdVideoConnectionComponent;
else if (!strcmp(psz, "composite"))
conn = bmdVideoConnectionComposite;
else if (!strcmp(psz, "svideo"))
conn = bmdVideoConnectionSVideo;
return conn;
}
int DBMSDIOutput::ConfigureVideo(const video_format_t *vfmt)
{
HRESULT result;
BMDDisplayMode wanted_mode_id = bmdDisplayModeNotSupported;
IDeckLinkConfiguration *p_config = NULL;
IDeckLinkAttributes *p_attributes = NULL;
IDeckLinkDisplayMode *p_display_mode = NULL;
char *psz_string = NULL;
video_format_t *fmt = &video.configuredfmt.video;
if(FAKE_DRIVER)
{
video_format_Copy(fmt, vfmt);
fmt->i_chroma = !video.tenbits ? VLC_CODEC_UYVY : VLC_CODEC_I422_10L;
fmt->i_frame_rate = (unsigned) frameduration;
fmt->i_frame_rate_base = (unsigned) timescale;
video.configuredfmt.i_codec = fmt->i_chroma;
return VLC_SUCCESS;
}
if(!p_output)
return VLC_EGENERIC;
if(!video.configuredfmt.i_codec && b_running)
return VLC_EGENERIC;
/* Now configure card */
if(!p_output)
return VLC_EGENERIC;
result = p_card->QueryInterface(IID_IDeckLinkConfiguration, (void**)&p_config);
CHECK("Could not get config interface");
psz_string = var_InheritString(p_stream, CFG_PREFIX "mode");
if(psz_string)
{
size_t len = strlen(psz_string);
if (len > 4)
{
free(psz_string);
msg_Err(p_stream, "Invalid mode %s", psz_string);
goto error;
}
memset(&wanted_mode_id, ' ', 4);
strncpy((char*)&wanted_mode_id, psz_string, 4);
wanted_mode_id = ntohl(wanted_mode_id);
free(psz_string);
}
/* Read attributes */
result = p_card->QueryInterface(IID_IDeckLinkAttributes, (void**)&p_attributes);
CHECK("Could not get IDeckLinkAttributes");
int64_t vconn;
result = p_attributes->GetInt(BMDDeckLinkVideoOutputConnections, &vconn); /* reads mask */
CHECK("Could not get BMDDeckLinkVideoOutputConnections");
psz_string = var_InheritString(p_stream, CFG_PREFIX "video-connection");
vconn = getVConn(psz_string);
free(psz_string);
if (vconn == 0)
{
msg_Err(p_stream, "Invalid video connection specified");
goto error;
}
result = p_config->SetInt(bmdDeckLinkConfigVideoOutputConnection, vconn);
CHECK("Could not set video output connection");
p_display_mode = MatchDisplayMode(vfmt, wanted_mode_id);
if(p_display_mode == NULL)
{
msg_Err(p_stream, "Could not negociate a compatible display mode");
goto error;
}
else
{
BMDDisplayMode mode_id = p_display_mode->GetDisplayMode();
BMDDisplayMode modenl = htonl(mode_id);
msg_Dbg(p_stream, "Selected mode '%4.4s'", (char *) &modenl);
BMDVideoOutputFlags flags = bmdVideoOutputVANC;
if (mode_id == bmdModeNTSC ||
mode_id == bmdModeNTSC2398 ||
mode_id == bmdModePAL)
{
flags = bmdVideoOutputVITC;
}
BMDDisplayModeSupport support;
IDeckLinkDisplayMode *resultMode;
result = p_output->DoesSupportVideoMode(mode_id,
video.tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV,
flags, &support, &resultMode);
CHECK("Does not support video mode");
if (support == bmdDisplayModeNotSupported)
{
msg_Err(p_stream, "Video mode not supported");
goto error;
}
if (p_display_mode->GetWidth() <= 0 || p_display_mode->GetWidth() & 1)
{
msg_Err(p_stream, "Unknown video mode specified.");
goto error;
}
result = p_display_mode->GetFrameRate(&frameduration,
&timescale);
CHECK("Could not read frame rate");
result = p_output->EnableVideoOutput(mode_id, flags);
CHECK("Could not enable video output");
video_format_Copy(fmt, vfmt);
fmt->i_width = fmt->i_visible_width = p_display_mode->GetWidth();
fmt->i_height = fmt->i_visible_height = p_display_mode->GetHeight();
fmt->i_x_offset = 0;
fmt->i_y_offset = 0;
fmt->i_sar_num = 0;
fmt->i_sar_den = 0;
fmt->i_chroma = !video.tenbits ? VLC_CODEC_UYVY : VLC_CODEC_I422_10L; /* we will convert to v210 */
fmt->i_frame_rate = (unsigned) frameduration;
fmt->i_frame_rate_base = (unsigned) timescale;
video.configuredfmt.i_codec = fmt->i_chroma;
char *psz_file = var_InheritString(p_stream, CFG_PREFIX "nosignal-image");
if(psz_file)
{
video.pic_nosignal = CreateNoSignalPicture(psz_file, fmt);
if (!video.pic_nosignal)
msg_Err(p_stream, "Could not create no signal picture");
free(psz_file);
}
}
p_display_mode->Release();
p_attributes->Release();
p_config->Release();
return VLC_SUCCESS;
error:
if (p_display_mode)
p_display_mode->Release();
if(p_attributes)
p_attributes->Release();
if (p_config)
p_config->Release();
return VLC_EGENERIC;
}
int DBMSDIOutput::Start()
{
HRESULT result;
if(FAKE_DRIVER && !b_running)
{
b_running = true;
return VLC_SUCCESS;
}
if(b_running)
return VLC_EGENERIC;
result = p_output->StartScheduledPlayback(
SEC_FROM_VLC_TICK(vlc_tick_now() * timescale), timescale, 1.0);
CHECK("Could not start playback");
b_running = true;
return VLC_SUCCESS;
error:
return VLC_EGENERIC;
}
int DBMSDIOutput::Process()
{
if((!p_output && !FAKE_DRIVER) || !b_running)
return VLC_EGENERIC;
picture_t *p;
while((p = reinterpret_cast<picture_t *>(videoBuffer.Dequeue())))
ProcessVideo(p);
block_t *b;
while((b = reinterpret_cast<block_t *>(audioBuffer.Dequeue())))
ProcessAudio(b);
return VLC_SUCCESS;
}
int DBMSDIOutput::ProcessAudio(block_t *p_block)
{
if(FAKE_DRIVER)
{
block_Release(p_block);
return VLC_SUCCESS;
}
if (!p_output)
{
block_Release(p_block);
return VLC_EGENERIC;
}
p_block->i_pts -= offset;
uint32_t sampleFrameCount = p_block->i_nb_samples;
uint32_t written;
HRESULT result = p_output->ScheduleAudioSamples(
p_block->p_buffer, p_block->i_nb_samples, p_block->i_pts, CLOCK_FREQ, &written);
if (result != S_OK)
msg_Err(p_stream, "Failed to schedule audio sample: 0x%X", result);
else
{
lasttimestamp = __MAX(p_block->i_pts, lasttimestamp);
if (sampleFrameCount != written)
msg_Err(p_stream, "Written only %d samples out of %d", written, sampleFrameCount);
}
block_Release(p_block);
return result != S_OK ? VLC_EGENERIC : VLC_SUCCESS;
}
int DBMSDIOutput::ProcessVideo(picture_t *picture)
{
mtime_t now = vlc_tick_now();
if (!picture)
return VLC_EGENERIC;
if(picture->date - now > 5000)
vlc_msleep_i11e(picture->date - now);
if (video.pic_nosignal &&
now - picture->date > vlc_tick_from_sec(video.nosignal_delay))
{
msg_Dbg(p_stream, "no signal");
picture_Hold(video.pic_nosignal);
video.pic_nosignal->date = now;
doProcessVideo(picture);
}
return doProcessVideo(picture);
}
int DBMSDIOutput::doProcessVideo(picture_t *picture)
{
HRESULT result;
int w, h, stride, length, ret = VLC_EGENERIC;
mtime_t now;
IDeckLinkMutableVideoFrame *pDLVideoFrame = NULL;
w = video.configuredfmt.video.i_visible_width;
h = video.configuredfmt.video.i_visible_height;
if(FAKE_DRIVER)
goto end;
result = p_output->CreateVideoFrame(w, h, w*3,
video.tenbits ? bmdFormat10BitYUV : bmdFormat8BitYUV,
bmdFrameFlagDefault, &pDLVideoFrame);
if (result != S_OK) {
msg_Err(p_stream, "Failed to create video frame: 0x%X", result);
goto error;
}
void *frame_bytes;
pDLVideoFrame->GetBytes((void**)&frame_bytes);
stride = pDLVideoFrame->GetRowBytes();
if (video.tenbits)
{
IDeckLinkVideoFrameAncillary *vanc;
void *buf;
result = p_output->CreateAncillaryData(bmdFormat10BitYUV, &vanc);
if (result != S_OK) {
msg_Err(p_stream, "Failed to create vanc: %d", result);
goto error;
}
result = vanc->GetBufferForVerticalBlankingLine(ancillary.afd_line, &buf);
if (result != S_OK) {
msg_Err(p_stream, "Failed to get VBI line %u: %d", ancillary.afd_line, result);
goto error;
}
sdi::AFD afd(ancillary.afd, ancillary.ar);
afd.FillBuffer(reinterpret_cast<uint8_t*>(buf), stride);
sdi::V210::Convert(picture, stride, frame_bytes);
result = pDLVideoFrame->SetAncillaryData(vanc);
vanc->Release();
if (result != S_OK) {
msg_Err(p_stream, "Failed to set vanc: %d", result);
goto error;
}
}
else for(int y = 0; y < h; ++y) {
uint8_t *dst = (uint8_t *)frame_bytes + stride * y;
const uint8_t *src = (const uint8_t *)picture->p[0].p_pixels +
picture->p[0].i_pitch * y;
memcpy(dst, src, w * 2 /* bpp */);
}
// compute frame duration in CLOCK_FREQ units
length = (frameduration * CLOCK_FREQ) / timescale;
picture->date -= offset;
result = p_output->ScheduleVideoFrame(pDLVideoFrame,
picture->date, length, CLOCK_FREQ);
if (result != S_OK) {
msg_Err(p_stream, "Dropped Video frame %" PRId64 ": 0x%x",
picture->date, result);
goto error;
}
lasttimestamp = __MAX(picture->date, lasttimestamp);
now = vlc_tick_now() - offset;
BMDTimeValue decklink_now;
double speed;
p_output->GetScheduledStreamTime (CLOCK_FREQ, &decklink_now, &speed);
if ((now - decklink_now) > 400000) {
/* XXX: workaround card clock drift */
offset += 50000;
msg_Err(p_stream, "Delaying: offset now %" PRId64, offset);
}
end:
ret = VLC_SUCCESS;
error:
picture_Release(picture);
if (pDLVideoFrame)
pDLVideoFrame->Release();
return ret;
}
picture_t * DBMSDIOutput::CreateNoSignalPicture(const char *psz_file, const video_format_t *fmt)
{
picture_t *p_pic = NULL;
image_handler_t *img = image_HandlerCreate(p_stream);
if (img)
{
video_format_t in, dummy;
video_format_Init(&dummy, 0);
video_format_Init(&in, 0);
video_format_Setup(&in, 0,
fmt->i_width, fmt->i_height,
fmt->i_width, fmt->i_height, 1, 1);
picture_t *png = image_ReadUrl(img, psz_file, &dummy, &in);
if (png)
{
video_format_Clean(&dummy);
video_format_Copy(&dummy, fmt);
p_pic = image_Convert(img, png, &in, &dummy);
if(!video_format_IsSimilar(&dummy, fmt))
{
picture_Release(p_pic);
p_pic = NULL;
}
picture_Release(png);
}
image_HandlerDelete(img);
video_format_Clean(&in);
video_format_Clean(&dummy);
}
return p_pic;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 ()

View File

@ -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

View File

@ -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