mirror of
https://github.com/videolan/vlc.git
synced 2024-12-06 08:15:52 +08:00
sout: add SDI stream output
Decklink vout backport, so this is the currently the only support
This commit is contained in:
parent
4be7fdc4f4
commit
752df69bf9
4
NEWS
4
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
|
||||
|
||||
|
@ -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}"
|
||||
|
@ -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
|
||||
|
@ -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 = \
|
||||
|
100
modules/stream_out/sdi/Ancillary.cpp
Normal file
100
modules/stream_out/sdi/Ancillary.cpp
Normal 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));
|
||||
}
|
||||
}
|
49
modules/stream_out/sdi/Ancillary.hpp
Normal file
49
modules/stream_out/sdi/Ancillary.hpp
Normal 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
|
703
modules/stream_out/sdi/DBMSDIOutput.cpp
Normal file
703
modules/stream_out/sdi/DBMSDIOutput.cpp
Normal 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, ×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;
|
||||
}
|
67
modules/stream_out/sdi/DBMSDIOutput.hpp
Normal file
67
modules/stream_out/sdi/DBMSDIOutput.hpp
Normal 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
|
157
modules/stream_out/sdi/SDIOutput.cpp
Normal file
157
modules/stream_out/sdi/SDIOutput.cpp
Normal 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();
|
||||
}
|
81
modules/stream_out/sdi/SDIOutput.hpp
Normal file
81
modules/stream_out/sdi/SDIOutput.hpp
Normal 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
|
511
modules/stream_out/sdi/SDIStream.cpp
Normal file
511
modules/stream_out/sdi/SDIStream.cpp
Normal 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;
|
||||
}
|
149
modules/stream_out/sdi/SDIStream.hpp
Normal file
149
modules/stream_out/sdi/SDIStream.hpp
Normal 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
|
96
modules/stream_out/sdi/V210.cpp
Normal file
96
modules/stream_out/sdi/V210.cpp
Normal 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;
|
||||
}
|
||||
}
|
37
modules/stream_out/sdi/V210.hpp
Normal file
37
modules/stream_out/sdi/V210.hpp
Normal 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
|
205
modules/stream_out/sdi/sdiout.cpp
Normal file
205
modules/stream_out/sdi/sdiout.cpp
Normal 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 ()
|
23
modules/stream_out/sdi/sdiout.hpp
Normal file
23
modules/stream_out/sdi/sdiout.hpp
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user