Files
UltraGrid/src/video_capture/decklink.cpp
Martin Pulec 435efcb63f DeckLink cap.: explicitly initialize state attrs
Now it is value-initialized but explicit initialization is more clear
and prevents vars becoming uninitialized if someone happens to write a
constructor.
2022-04-01 10:49:13 +02:00

1834 lines
82 KiB
C++

/*
* FILE: video_capture/decklink.cpp
* AUTHORS: Martin Benes <martinbenesh@gmail.com>
* Lukas Hejtmanek <xhejtman@ics.muni.cz>
* Petr Holub <hopet@ics.muni.cz>
* Milos Liska <xliska@fi.muni.cz>
* Martin Pulec <martin.pulec@cesnet.cz>
* Jiri Matela <matela@ics.muni.cz>
* Dalibor Matura <255899@mail.muni.cz>
* Ian Wesley-Smith <iwsmith@cct.lsu.edu>
*
* Copyright (c) 2005-2021 CESNET z.s.p.o.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
*
* This product includes software developed by CESNET z.s.p.o.
*
* 4. Neither the name of the CESNET nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#define MODULE_NAME "[Decklink capture] "
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#include <algorithm>
#include <cassert>
#include <condition_variable>
#include <chrono>
#include <iostream>
#include <iomanip>
#include <list>
#include <mutex>
#include <queue>
#include <string>
#include <vector>
#include "blackmagic_common.h"
#include "audio/types.h"
#include "audio/utils.h"
#include "debug.h"
#include "host.h"
#include "lib_common.h"
#include "rang.hpp"
#include "tv.h"
#include "utils/color_out.h"
#include "video.h"
#include "video_capture.h"
constexpr const int DEFAULT_AUDIO_BPS = 4;
constexpr const size_t MAX_AUDIO_PACKETS = 10;
#define MOD_NAME "[DeckLink capture] "
#ifndef WIN32
#define STDMETHODCALLTYPE
#endif
#define RELEASE_IF_NOT_NULL(x) if (x != nullptr) { x->Release(); x = nullptr; }
using namespace std;
using namespace std::chrono;
using rang::fg;
using rang::style;
using std::mutex;
// static int device = 0; // use first BlackMagic device
// static int mode = 5; // for Intensity
// static int mode = 6; // for Decklink 6) HD 1080i 59.94; 1920 x 1080; 29.97 FPS 7) HD 1080i 60; 1920 x 1080; 30 FPS
//static int connection = 0; // the choice of BMDVideoConnection // It should be 0 .... bmdVideoConnectionSDI
// performs command, if failed, displays error and jumps to error label
#define EXIT_IF_FAILED(cmd, name) \
do {\
HRESULT result = cmd;\
if (FAILED(result)) {;\
LOG(LOG_LEVEL_ERROR) << MOD_NAME << name << ": " << bmd_hresult_to_string(result) << "\n";\
goto error;\
}\
} while (0)
// similar as above, but only displays warning
#define CALL_AND_CHECK_2(cmd, name) \
do {\
HRESULT result = cmd;\
if (FAILED(result)) {;\
LOG(LOG_LEVEL_WARNING) << MOD_NAME << name << ": " << bmd_hresult_to_string(result) << "\n";\
}\
} while (0)
#define CALL_AND_CHECK_3(cmd, name, msg) \
do {\
HRESULT result = cmd;\
if (FAILED(result)) {;\
LOG(LOG_LEVEL_WARNING) << MOD_NAME << name << ": " << bmd_hresult_to_string(result) << "\n";\
} else {\
LOG(LOG_LEVEL_INFO) << MOD_NAME << name << ": " << msg << "\n";\
}\
} while (0)
#define GET_3TH_ARG(arg1, arg2, arg3, ...) arg3
#define CALL_AND_CHECK_CHOOSER(...) \
GET_3TH_ARG(__VA_ARGS__, CALL_AND_CHECK_3, CALL_AND_CHECK_2, )
#define CALL_AND_CHECK(cmd, ...) CALL_AND_CHECK_CHOOSER(__VA_ARGS__)(cmd, __VA_ARGS__)
#define BMD_CONFIG_SET_INT(key, val) do {\
if (val != (decltype(val)) BMD_OPT_DEFAULT) {\
HRESULT result = deckLinkConfiguration->SetInt(key, val);\
if (result != S_OK) {\
LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Unable to set " #key ": " << bmd_hresult_to_string(result) << "\n";\
goto error;\
} else { \
LOG(LOG_LEVEL_INFO) << MOD_NAME << #key << " set to: " << val << "\n";\
} \
}\
} while (0)
class VideoDelegate;
struct device_state {
IDeckLink *deckLink = nullptr;
IDeckLinkInput *deckLinkInput = nullptr;
VideoDelegate *delegate = nullptr;
IDeckLinkProfileAttributes *deckLinkAttributes = nullptr;
IDeckLinkConfiguration *deckLinkConfiguration = nullptr;
string device_id = "0"; // either numeric value or device name
};
struct vidcap_decklink_state {
vector <struct device_state> state{vector <struct device_state>(1)};
int devices_cnt = 1;
string mode;
unsigned int next_frame_time = 0; // avarege time between frames
struct video_frame *frame{nullptr};
struct audio_frame audio{};
queue<IDeckLinkAudioInputPacket *> audioPackets;
codec_t codec{VIDEO_CODEC_NONE};
BMDVideoInputFlags enable_flags{0};
BMDSupportedVideoModeFlags supported_flags = bmdSupportedVideoModeDefault;
mutex lock;
condition_variable boss_cv;
int frames = 0;
bool grab_audio{false}; /* wheather we process audio or not */
bool stereo{false}; /* for eg. DeckLink HD Extreme, Quad doesn't set this !!! */
bool sync_timecode{false}; /* use timecode when grabbing from multiple inputs */
static_assert(bmdVideoConnectionUnspecified == BMD_OPT_DEFAULT, "Connection unspecified is not 0!");
BMDVideoConnection connection{bmdVideoConnectionUnspecified};
int audio_consumer_levels{-1}; ///< 0 false, 1 true, -1 default
BMDVideoInputConversionMode conversion_mode{};
BMDDeckLinkCapturePassthroughMode passthrough{}; // 0 means don't set
struct timeval t0;
bool detect_format = false;
unsigned int requested_bit_depth = 0; // 0, bmdDetectedVideoInput8BitDepth, bmdDetectedVideoInput10BitDepth or bmdDetectedVideoInput12BitDepth
bool p_not_i = false;
int use1080psf = BMD_OPT_KEEP; // capture PsF instead of progressive
uint32_t profile{}; // BMD_OPT_DEFAULT, BMD_OPT_KEEP, bmdDuplexHalf or one of BMDProfileID
uint32_t link = 0;
bool nosig_send = false; ///< send video even when no signal detected
};
static HRESULT set_display_mode_properties(struct vidcap_decklink_state *s, struct tile *tile, IDeckLinkDisplayMode* displayMode, /* out */ BMDPixelFormat *pf);
static void cleanup_common(struct vidcap_decklink_state *s);
static list<tuple<int, string, string, string>> get_input_modes (IDeckLink* deckLink);
static void print_input_modes (IDeckLink* deckLink);
class VideoDelegate : public IDeckLinkInputCallback {
private:
int32_t mRefCount{};
static constexpr BMDDetectedVideoInputFormatFlags csMask{bmdDetectedVideoInputYCbCr422 | bmdDetectedVideoInputRGB444};
static constexpr BMDDetectedVideoInputFormatFlags bitDepthMask{bmdDetectedVideoInput8BitDepth | bmdDetectedVideoInput10BitDepth | bmdDetectedVideoInput12BitDepth};
BMDDetectedVideoInputFormatFlags configuredCsBitDepth{};
public:
int newFrameReady{};
IDeckLinkVideoFrame *rightEyeFrame{};
void *pixelFrame{};
void *pixelFrameRight{};
IDeckLinkVideoInputFrame *lastFrame{nullptr};
uint32_t timecode{};
struct vidcap_decklink_state *s;
int i; ///< index of the device
VideoDelegate(struct vidcap_decklink_state *state, int index) : s(state), i(index) {
}
virtual ~VideoDelegate () {
if(rightEyeFrame)
rightEyeFrame->Release();
if (lastFrame) {
lastFrame->Release();
}
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *) override { return E_NOINTERFACE; }
virtual ULONG STDMETHODCALLTYPE AddRef(void) override {
return mRefCount++;
}
virtual ULONG STDMETHODCALLTYPE Release(void) override {
int32_t newRefValue;
newRefValue = mRefCount--;
if (newRefValue == 0)
{
delete this;
return 0;
}
return newRefValue;
}
static auto getNotificationEventsStr(BMDVideoInputFormatChangedEvents notificationEvents) noexcept {
string reason{};
map<BMDDetectedVideoInputFormatFlags, string> m{
{ bmdVideoInputDisplayModeChanged, "display mode"s },
{ bmdVideoInputFieldDominanceChanged, "field dominance"s },
{ bmdVideoInputColorspaceChanged, "color space"s },
};
for (auto &i : m) {
if ((notificationEvents & i.first) != 0U) {
if (!reason.empty()) {
reason += ", "s;
}
reason += i.second;
}
}
return reason.empty() ? "unknown"s : reason;
}
virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(
BMDVideoInputFormatChangedEvents notificationEvents,
IDeckLinkDisplayMode* mode,
BMDDetectedVideoInputFormatFlags flags) noexcept override {
LOG(LOG_LEVEL_NOTICE) << MODULE_NAME << "Format change detected (" << getNotificationEventsStr(notificationEvents) << ").\n";
notificationEvents &= ~bmdVideoInputFieldDominanceChanged; // ignore field dominance change
if (notificationEvents == 0U) {
return S_OK;
}
if ((flags & bmdDetectedVideoInputDualStream3D) != 0u && !s->stereo) {
LOG(LOG_LEVEL_ERROR) << MODULE_NAME << "Stereoscopic 3D detected but not enabled! Please supply a \"3D\" parameter.\n";
return E_FAIL;
}
BMDDetectedVideoInputFormatFlags csBitDepth = flags & (csMask | bitDepthMask);
if ((csBitDepth & bitDepthMask) == 0U) { // if no bit depth, assume 8-bit
csBitDepth |= bmdDetectedVideoInput8BitDepth;
}
if (s->requested_bit_depth != 0) {
csBitDepth = (flags & csMask) | s->requested_bit_depth;
}
unordered_map<BMDDetectedVideoInputFormatFlags, codec_t> m = {
{bmdDetectedVideoInputYCbCr422 | bmdDetectedVideoInput8BitDepth, UYVY},
{bmdDetectedVideoInputYCbCr422 | bmdDetectedVideoInput10BitDepth, v210},
{bmdDetectedVideoInputYCbCr422 | bmdDetectedVideoInput12BitDepth, v210}, // weird
{bmdDetectedVideoInputRGB444 | bmdDetectedVideoInput8BitDepth, RGBA},
{bmdDetectedVideoInputRGB444 | bmdDetectedVideoInput10BitDepth, R10k},
{bmdDetectedVideoInputRGB444 | bmdDetectedVideoInput12BitDepth, R12L},
};
if (notificationEvents == bmdVideoInputColorspaceChanged && csBitDepth == configuredCsBitDepth) { // only CS change which was already performed
return S_OK;
}
if (s->requested_bit_depth == 0 && (flags & bmdDetectedVideoInput8BitDepth) == 0) {
const string & depth = (flags & bmdDetectedVideoInput10BitDepth) != 0U ? "10"s : "12"s;
LOG(LOG_LEVEL_WARNING) << MODULE_NAME << "Capturing detected " << depth << "-bit signal, use \":codec=UYVY\" to enforce 8-bit capture (old behavior).\n";
}
unique_lock<mutex> lk(s->lock);
s->codec = m.at(csBitDepth);
configuredCsBitDepth = csBitDepth;
LOG(LOG_LEVEL_INFO) << MODULE_NAME "Using codec: " << get_codec_name(s->codec) << "\n";
IDeckLinkInput *deckLinkInput = s->state[this->i].deckLinkInput;
deckLinkInput->PauseStreams();
BMDPixelFormat pf{};
HRESULT result = set_display_mode_properties(s, vf_get_tile(s->frame, this->i), mode, /* out */ &pf);
if(result == S_OK) {
CALL_AND_CHECK(deckLinkInput->EnableVideoInput(mode->GetDisplayMode(), pf, s->enable_flags), "EnableVideoInput");
if (!s->grab_audio ||
this->i != 0) { //TODO: figure out output from multiple streams
deckLinkInput->DisableAudioInput();
} else {
deckLinkInput->EnableAudioInput(
bmdAudioSampleRate48kHz,
s->audio.bps == 2 ? bmdAudioSampleType16bitInteger :
bmdAudioSampleType32bitInteger,
max(s->audio.ch_count, 2)); // BMD isn't able to grab single channel
}
//deckLinkInput->SetCallback(s->state[i].delegate);
deckLinkInput->FlushStreams();
deckLinkInput->StartStreams();
} else {
LOG(LOG_LEVEL_ERROR) << MOD_NAME << "set_display_mode_properties: " << bmd_hresult_to_string(result) << "\n";\
}
return result;
}
virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame*, IDeckLinkAudioInputPacket*) override;
};
HRESULT
VideoDelegate::VideoInputFrameArrived (IDeckLinkVideoInputFrame *videoFrame, IDeckLinkAudioInputPacket *audioPacket)
{
bool nosig = false;
unique_lock<mutex> lk(s->lock);
// LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK //
// Video
if (videoFrame)
{
if (videoFrame->GetFlags() & bmdFrameHasNoInputSource) {
nosig = true;
log_msg(LOG_LEVEL_INFO, "Frame received (#%d) - No input signal detected\n", s->frames);
if (s->nosig_send) {
newFrameReady = 1;
}
} else {
newFrameReady = 1; // The new frame is ready to grab
// printf("Frame received (#%lu) - Valid Frame (Size: %li bytes)\n", framecount, videoFrame->GetRowBytes() * videoFrame->GetHeight());
}
}
if (audioPacket) {
if (s->audioPackets.size() < MAX_AUDIO_PACKETS) {
audioPacket->AddRef();
s->audioPackets.push(audioPacket);
} else {
LOG(LOG_LEVEL_WARNING) << MOD_NAME "Dropping audio packet, queue full.\n";
}
}
if (videoFrame && newFrameReady && (!nosig || !lastFrame)) {
/// @todo videoFrame should be actually retained until the data are processed
videoFrame->GetBytes(&pixelFrame);
if (lastFrame) {
lastFrame->Release();
}
lastFrame = videoFrame;
lastFrame->AddRef();
IDeckLinkTimecode *tc = NULL;
if (videoFrame->GetTimecode(bmdTimecodeRP188Any, &tc) == S_OK) {
timecode = tc->GetBCD();
} else {
timecode = 0;
if (s->sync_timecode) {
log_msg(LOG_LEVEL_ERROR, "Failed to acquire timecode from stream. Disabling sync.\n");
s->sync_timecode = FALSE;
}
}
if(rightEyeFrame)
rightEyeFrame->Release();
pixelFrameRight = NULL;
rightEyeFrame = NULL;
if(s->stereo) {
IDeckLinkVideoFrame3DExtensions *rightEye;
HRESULT result;
result = videoFrame->QueryInterface(IID_IDeckLinkVideoFrame3DExtensions, (void **)&rightEye);
if (result == S_OK) {
result = rightEye->GetFrameForRightEye(&rightEyeFrame);
if(result == S_OK) {
if (rightEyeFrame->GetFlags() & bmdFrameHasNoInputSource)
{
fprintf(stderr, "Right Eye Frame received (#%d) - No input signal detected\n", s->frames);
}
rightEyeFrame->GetBytes(&pixelFrameRight);
}
}
rightEye->Release();
if(!pixelFrameRight) {
fprintf(stderr, "[DeckLink] Sending right eye error.\n");
}
}
}
lk.unlock();
s->boss_cv.notify_one();
// UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UN //
debug_msg("VideoInputFrameArrived - END\n"); /* TOREMOVE */
return S_OK;
}
static map<BMDVideoConnection, string> connection_string_map = {
{ bmdVideoConnectionSDI, "SDI" },
{ bmdVideoConnectionHDMI, "HDMI"},
{ bmdVideoConnectionOpticalSDI, "OpticalSDI"},
{ bmdVideoConnectionComponent, "Component"},
{ bmdVideoConnectionComposite, "Composite"},
{ bmdVideoConnectionSVideo, "SVideo"}
};
static void vidcap_decklink_print_card_info(IDeckLink *deckLink) {
// ** List the video input display modes supported by the card
print_input_modes(deckLink);
IDeckLinkProfileAttributes *deckLinkAttributes = nullptr;
HRESULT result = deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes);
if (result != S_OK) {
cout << "Could not query device attributes.\n\n";
return;
}
int64_t connections = 0;
if (deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections, &connections) != S_OK) {
LOG(LOG_LEVEL_ERROR) << MOD_NAME "Could not get connections.\n";
} else {
cout << "\n\tConnection can be one of following:\n";
for (auto it : connection_string_map) {
if (connections & it.first) {
cout << style::bold << "\t\t" <<
it.second << style::reset << "\n";
}
}
}
cout << "\n";
// Release the IDeckLink instance when we've finished with it to prevent leaks
RELEASE_IF_NOT_NULL(deckLinkAttributes);
}
/* HELP */
static int
decklink_help(bool full)
{
IDeckLinkIterator* deckLinkIterator;
IDeckLink* deckLink;
int numDevices = 0;
printf("\nDecklink options:\n");
cout << style::bold << fg::red << "\t-t decklink" << fg::reset << "[:<device_index(indices)>[:<mode>:<colorspace>[:3D][:sync_timecode][:connection=<input>][:audio_level={line|mic}][:detect-format][:conversion=<conv_mode>]]\n" << style::reset;
printf("\t\tor\n");
cout << style::bold << fg::red << "\t-t decklink" << fg::reset << "{:mode=<mode>|:device=<device_index>|:codec=<colorspace>...<key>=<val>}*|[full]help\n" << style::reset;
printf("\t(Mode specification is mandatory if your card does not support format autodetection.)\n");
printf("\n");
cout << style::bold << "3D" << style::reset << "\n";
printf("\tUse this to capture 3D from supported card (eg. DeckLink HD 3D Extreme).\n");
printf("\tDo not use it for eg. Quad or Duo. Availability of the mode is indicated\n");
printf("\tin video format listing above (\"supports 3D\").\n");
printf("\n");
cout << style::bold << "audio_level\n" << style::reset;
cout << style::bold << "\tline" << style::reset << " - the selected analog input gain levels are used\n";
cout << style::bold << "\tmic" << style::reset << " - analog audio levels are set to maximum gain on audio input.\n";
printf("\n");
if (full) {
cout << style::bold << "conversion\n" << style::reset;
cout << style::bold << "\tnone" << style::reset << " - No video input conversion\n";
cout << style::bold << "\t10lb" << style::reset << " - HD1080 to SD video input down conversion\n";
cout << style::bold << "\t10am" << style::reset << " - Anamorphic from HD1080 to SD video input down conversion\n";
cout << style::bold << "\t72lb" << style::reset << " - Letter box from HD720 to SD video input down conversion\n";
cout << style::bold << "\t72ab" << style::reset << " - Letterbox video input up conversion\n";
cout << style::bold << "\tamup" << style::reset << " - Anamorphic video input up conversion\n";
cout << "\tThen use the set the resulting mode (!) for capture, eg. for 1080p to PAL conversion:\n"
"\t\t-t decklink:mode=pal:conversion=10lb\n";
cout << "\n";
}
cout << style::bold << "detect-format\n" << style::reset;
cout << "\tTry to detect input video format even if the device doesn't support\n"
"\tautodetect, eg. \"-t decklink:connection=HDMI:detect-format\".\n";
cout << "\n";
cout << style::bold << "fullhelp\n" << style::reset;
cout << "\tPrint description of all available options.\n";
cout << "\n";
cout << style::bold << "half-duplex\n" << style::reset;
cout << "\tSet a profile that allows maximal number of simultaneous IOs.\n";
cout << "\n";
if (full) {
cout << style::bold << "p_not_i\n" << style::reset;
cout << "\tIncoming signal should be treated as progressive even if detected as interlaced (PsF).\n";
cout << "\n";
cout << style::bold << "Use1080PsF[=true|false]\n" << style::reset;
cout << "\tIncoming signal should be treated as PsF instead of progressive.\n";
cout << "\n";
cout << style::bold << "nosig-send\n" << style::reset;
cout << "\tSend video even if no signal was detected (useful when video interrupts\n"
"\tbut the video stream needs to be preserved, eg. to keep sync with audio).\n";
cout << "\n";
cout << style::bold << "[no]passthrough\n" << style::reset;
cout << "\tEnable/disable capture passthrough.\n";
cout << "\n";
cout << style::bold << "profile=<FourCC>|profile=keep\n" << style::reset;
cout << "\tUse desired device profile: " << style::bold << "1dfd" << style::reset << ", "
<< style::bold << "1dhd" << style::reset << ", "
<< style::bold << "2dfd" << style::reset << ", "
<< style::bold << "2dhd" << style::reset << " or "
<< style::bold << "4dhd" << style::reset << ". See SDK manual for details. Use "
<< style::bold << "keep" << style::reset << " to disable automatic selection.\n";
cout << "\n";
}
cout << style::bold << "single-/dual-/quad-link\n" << style::reset;
cout << "\tUse single-/dual-/quad-link.\n";
cout << "\n";
if (full) {
cout << style::bold << "sync_timecode" << style::reset << "\n";
cout << "\tTry to synchronize inputs based on timecode (for multiple inputs, eg. tiled 4K)\n";
cout << "\n";
}
cout << "Available color spaces:";
for (auto & i : uv_to_bmd_codec_map) {
if (i != *uv_to_bmd_codec_map.begin()) {
cout << ",";
}
cout << " " << style::bold << get_codec_name(i.first)
<< style::reset;
}
cout << "\n";
if (!full) {
cout << "Possible connections: " << BOLD("SDI") << ", " << BOLD("HDMI") << ", " << BOLD("OpticalSDI") << ", " << BOLD("Component") << ", " << BOLD("Composite") << ", " << BOLD("SVideo") << "\n";
}
cout << "\n";
// Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system
deckLinkIterator = create_decklink_iterator();
if (deckLinkIterator == NULL) {
return 0;
}
cout << "Devices:\n";
// Enumerate all cards in this system
while (deckLinkIterator->Next(&deckLink) == S_OK)
{
string deviceName = bmd_get_device_name(deckLink);
if (deviceName.empty()) {
deviceName = "(unable to get name)";
}
// *** Print the model name of the DeckLink card
cout << "\t" << BOLD(numDevices) << ") " << BOLD(GREEN(deviceName)) << "\n";
// Increment the total number of DeckLink cards found
numDevices++;
if (full) {
vidcap_decklink_print_card_info(deckLink);
}
deckLink->Release();
}
if (!full) {
cout << "(use \"-t decklink:fullhelp\" to see full list of device modes and available connections)\n\n";
}
deckLinkIterator->Release();
decklink_uninitialize();
// If no DeckLink cards were found in the system, inform the user
if (numDevices == 0)
{
log_msg(LOG_LEVEL_ERROR, "No Blackmagic Design devices were found.\n");
}
printf("Examples:\n");
cout << "\t" << style::bold << uv_argv[0] << " -t decklink" << style::reset << " # captures autodetected video from first DeckLink in system\n";
cout << "\t" << style::bold << uv_argv[0] << " -t decklink:0:Hi50:UYVY" << style::reset << " # captures 1080i50, 8-bit yuv\n";
cout << "\t" << style::bold << uv_argv[0] << " -t decklink:0:10:v210:connection=HDMI" << style::reset << " # captures 10th format from a card (alternative syntax), 10-bit YUV, from HDMI\n";
cout << "\t" << style::bold << uv_argv[0] << " -t decklink:mode=23ps" << style::reset << " # captures 1080p24, 8-bit yuv from first device\n";
cout << "\t" << style::bold << uv_argv[0] << " -t \"decklink:mode=Hp30:codec=v210:device=DeckLink HD Extreme 3D+\"" << style::reset << " # captures 1080p30, 10-bit yuv from DeckLink HD Extreme 3D+\n";
printf("\n");
print_decklink_version();
printf("\n");
return 1;
}
/* SETTINGS */
static void parse_devices(struct vidcap_decklink_state *s, const char *devs)
{
assert(devs != NULL && strlen(devs) > 0);
char *devices = strdup(devs);
char *ptr;
char *save_ptr_dev;
char *tmp = devices;
s->devices_cnt = 0;
while ((ptr = strtok_r(tmp, ",", &save_ptr_dev))) {
s->devices_cnt += 1;
s->state.resize(s->devices_cnt);
s->state[s->devices_cnt - 1].device_id = ptr;
tmp = NULL;
}
free (devices);
}
/* Parses option in format key=value */
static bool parse_option(struct vidcap_decklink_state *s, const char *opt)
{
if(strcasecmp(opt, "3D") == 0) {
s->stereo = true;
} else if(strcasecmp(opt, "timecode") == 0) {
s->sync_timecode = TRUE;
} else if(strncasecmp(opt, "connection=", strlen("connection=")) == 0) {
const char *connection = opt + strlen("connection=");
bool found = false;
for (auto it : connection_string_map) {
if (strcasecmp(connection, it.second.c_str()) == 0) {
s->connection = it.first;
found = true;
}
}
if (!found) {
fprintf(stderr, "[DeckLink] Unrecognized connection %s.\n", connection);
return false;
}
} else if(strncasecmp(opt, "audio_level=",
strlen("audio_level=")) == 0) {
const char *levels = opt + strlen("audio_level=");
if (strcasecmp(levels, "false") == 0) {
s->audio_consumer_levels = 0;
} else {
s->audio_consumer_levels = 1;
}
} else if(strncasecmp(opt, "conversion=",
strlen("conversion=")) == 0) {
s->conversion_mode = (BMDVideoInputConversionMode) bmd_read_fourcc(opt + strlen("conversion="));
} else if(strncasecmp(opt, "device=",
strlen("device=")) == 0) {
const char *devices = opt + strlen("device=");
parse_devices(s, devices);
} else if(strncasecmp(opt, "mode=",
strlen("mode=")) == 0) {
s->mode = opt + strlen("mode=");
} else if(strncasecmp(opt, "codec=",
strlen("codec=")) == 0) {
const char *codec = opt + strlen("codec=");
s->codec = get_codec_from_name(codec);
if(s->codec == VIDEO_CODEC_NONE) {
fprintf(stderr, "Wrong config. Unknown color space %s\n", codec);
return false;
}
} else if (strcasecmp(opt, "detect-format") == 0) {
s->detect_format = true;
} else if (strcasecmp(opt, "p_not_i") == 0) {
s->p_not_i = true;
} else if (strstr(opt, "Use1080PsF") != nullptr) {
s->use1080psf = 1;
if (strcasecmp(opt + strlen("Use1080PsF"), "=false") == 0) {
s->use1080psf = 0;
}
} else if (strcasecmp(opt, "passthrough") == 0 || strcasecmp(opt, "nopassthrough") == 0) {
s->passthrough = opt[0] == 'n' ? bmdDeckLinkCapturePassthroughModeDisabled
: bmdDeckLinkCapturePassthroughModeCleanSwitch;
} else if (strstr(opt, "profile=") == opt) {
const char *mode = opt + strlen("profile=");
if (strcmp(mode, "keep") == 0) {
s->profile = BMD_OPT_KEEP;
} else {
s->profile = (BMDProfileID) bmd_read_fourcc(mode);
}
} else if (strstr(opt, "half-duplex") == opt) {
s->profile = bmdDuplexHalf;
} else if (strcasecmp(opt, "single-link") == 0) {
s->link = bmdLinkConfigurationSingleLink;
} else if (strcasecmp(opt, "dual-link") == 0) {
s->link = bmdLinkConfigurationDualLink;
} else if (strcasecmp(opt, "quad-link") == 0) {
s->link = bmdLinkConfigurationQuadLink;
} else if (strcasecmp(opt, "nosig-send") == 0) {
s->nosig_send = true;
} else {
log_msg(LOG_LEVEL_WARNING, "[DeckLink] Warning, unrecognized trailing options in init string: %s\n", opt);
return false;
}
return true;
}
static bool settings_init_key_val(struct vidcap_decklink_state *s, char **save_ptr)
{
char *tmp;
while((tmp = strtok_r(NULL, ":", save_ptr))) {
if (!parse_option(s, tmp)) {
return false;
}
}
return true;
}
static int settings_init(struct vidcap_decklink_state *s, char *fmt)
{
char *tmp;
char *save_ptr = NULL;
if (!fmt || (tmp = strtok_r(fmt, ":", &save_ptr)) == NULL) {
printf("[DeckLink] Auto-choosen device 0.\n");
return 1;
}
// options are in format <device>:<mode>:<codec>[:other_opts]
if (isdigit(tmp[0]) && strcasecmp(tmp, "3D") != 0) {
LOG(LOG_LEVEL_WARNING) << MODULE_NAME "Deprecated syntax used, please use options in format \"key=value\"\n";
// choose device
parse_devices(s, tmp);
// choose mode
tmp = strtok_r(NULL, ":", &save_ptr);
if(tmp) {
s->mode = tmp;
tmp = strtok_r(NULL, ":", &save_ptr);
if (tmp) {
s->codec = get_codec_from_name(tmp);
if(s->codec == VIDEO_CODEC_NONE) {
fprintf(stderr, "Wrong config. Unknown color space %s\n", tmp);
return 0;
}
}
if (!settings_init_key_val(s, &save_ptr)) {
return 0;
}
}
} else { // options are in format key=val
if (!parse_option(s, tmp)) {
return 0;
}
if (!settings_init_key_val(s, &save_ptr)) {
return 0;
}
}
return 1;
}
/* External API ***************************************************************/
static struct vidcap_type *vidcap_decklink_probe(bool verbose, void (**deleter)(void *))
{
*deleter = free;
auto vt = static_cast<struct vidcap_type *>(calloc(1, sizeof(struct vidcap_type)));
if (vt == nullptr) {
return nullptr;
}
vt->name = "decklink";
vt->description = "Blackmagic DeckLink card";
if (!verbose) {
return vt;
}
IDeckLinkIterator* deckLinkIterator;
IDeckLink* deckLink;
int numDevices = 0;
// Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system
deckLinkIterator = create_decklink_iterator(false);
if (deckLinkIterator == nullptr) {
return vt;
}
// Enumerate all cards in this system
while (deckLinkIterator->Next(&deckLink) == S_OK) {
HRESULT result;
IDeckLinkProfileAttributes *deckLinkAttributes;
result = deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes);
if (result != S_OK) {
continue;
}
int64_t connections_bitmap;
if(deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections, &connections_bitmap) != S_OK) {
fprintf(stderr, "[DeckLink] Could not get connections.\n");
continue;
}
string deviceName = bmd_get_device_name(deckLink);
if (deviceName.empty()) {
deviceName = "(unknown)";
}
vt->card_count += 1;
vt->cards = (struct device_info *)
realloc(vt->cards, vt->card_count * sizeof(struct device_info));
memset(&vt->cards[vt->card_count - 1], 0, sizeof(struct device_info));
snprintf(vt->cards[vt->card_count - 1].dev, sizeof vt->cards[vt->card_count - 1].dev,
":device=%d", numDevices);
snprintf(vt->cards[vt->card_count - 1].name, sizeof vt->cards[vt->card_count - 1].name,
"%s #%d", deviceName.c_str(), numDevices);
snprintf(vt->cards[vt->card_count - 1].extra, sizeof vt->cards[vt->card_count - 1].extra,
"\"embeddedAudioAvailable\":\"t\"");
list<string> connections;
for (auto it : connection_string_map) {
if (connections_bitmap & it.first) {
connections.push_back(it.second);
}
}
list<tuple<int, string, string, string>> modes = get_input_modes (deckLink);
int i = 0;
const int mode_count = sizeof vt->cards[vt->card_count - 1].modes /
sizeof vt->cards[vt->card_count - 1].modes[0];
for (auto &m : modes) {
for (auto &c : connections) {
if (i >= mode_count) { // no space
break;
}
snprintf(vt->cards[vt->card_count - 1].modes[i].id,
sizeof vt->cards[vt->card_count - 1].modes[i].id,
R"({"modeOpt":"connection=%s:mode=%s:codec=UYVY"})",
c.c_str(), get<1>(m).c_str());
snprintf(vt->cards[vt->card_count - 1].modes[i].name,
sizeof vt->cards[vt->card_count - 1].modes[i].name,
"%s (%s)", get<2>(m).c_str(), c.c_str());
i++;
}
}
for (auto &c : connections) {
if (i >= mode_count) {
break;
}
snprintf(vt->cards[vt->card_count - 1].modes[i].id,
sizeof vt->cards[vt->card_count - 1].modes[i].id,
"{\"modeOpt\":\"connection=%s\"}",
c.c_str());
snprintf(vt->cards[vt->card_count - 1].modes[i].name,
sizeof vt->cards[vt->card_count - 1].modes[i].name,
"Decklink Auto (%s)", c.c_str());
if (++i >= mode_count) {
break;
}
snprintf(vt->cards[vt->card_count - 1].modes[i].id,
sizeof vt->cards[vt->card_count - 1].modes[i].id,
"{\"modeOpt\":\"detect-format:connection=%s\"}",
c.c_str());
snprintf(vt->cards[vt->card_count - 1].modes[i].name,
sizeof vt->cards[vt->card_count - 1].modes[i].name,
"UltraGrid auto-detect (%s)", c.c_str());
i++;
}
// Increment the total number of DeckLink cards found
numDevices++;
RELEASE_IF_NOT_NULL(deckLinkAttributes);
// Release the IDeckLink instance when we've finished with it to prevent leaks
deckLink->Release();
}
deckLinkIterator->Release();
decklink_uninitialize();
return vt;
}
static HRESULT set_display_mode_properties(struct vidcap_decklink_state *s, struct tile *tile, IDeckLinkDisplayMode* displayMode, /* out */ BMDPixelFormat *pf)
{
BMD_STR displayModeString = NULL;
HRESULT result;
result = displayMode->GetName(&displayModeString);
if (FAILED(result)) {
return result;
}
auto it = std::find_if(uv_to_bmd_codec_map.begin(),
uv_to_bmd_codec_map.end(),
[&s](const std::pair<codec_t, BMDPixelFormat>& el){ return el.first == s->codec; });
if (it == uv_to_bmd_codec_map.end()) {
LOG(LOG_LEVEL_ERROR) << "Unsupported codec: " << get_codec_name(s->codec) << "!\n";
return E_FAIL;
}
*pf = it->second;
// get avarage time between frames
BMDTimeValue frameRateDuration = 0;
BMDTimeScale frameRateScale = 0;
tile->width = displayMode->GetWidth();
tile->height = displayMode->GetHeight();
s->frame->color_spec = s->codec;
displayMode->GetFrameRate(&frameRateDuration, &frameRateScale);
s->frame->fps = static_cast<double>(frameRateScale) / frameRateDuration;
s->next_frame_time = static_cast<int>(std::chrono::microseconds::period::den / s->frame->fps); // in microseconds
switch(displayMode->GetFieldDominance()) {
case bmdLowerFieldFirst:
case bmdUpperFieldFirst:
s->frame->interlacing = INTERLACED_MERGED;
break;
case bmdProgressiveFrame:
case bmdProgressiveSegmentedFrame:
s->frame->interlacing = PROGRESSIVE;
break;
case bmdUnknownFieldDominance:
LOG(LOG_LEVEL_WARNING) << "[DeckLink cap.] Unknown field dominance!\n";
s->frame->interlacing = PROGRESSIVE;
break;
}
if (s->p_not_i) {
s->frame->interlacing = PROGRESSIVE;
}
char *displayModeCString = get_cstr_from_bmd_api_str(displayModeString);
LOG(LOG_LEVEL_DEBUG) << displayModeCString << " \t " << tile->width << " x " << tile->height << " \t " <<
s->frame->fps << " FPS \t " << s->next_frame_time << " AVAREGE TIME BETWEEN FRAMES\n";
cout << "Enable video input: " << displayModeCString << "\n";
release_bmd_api_str(displayModeString);
free(displayModeCString);
tile->data_len =
vc_get_linesize(tile->width, s->frame->color_spec) * tile->height;
if(s->stereo) {
s->frame->tiles[1].width = s->frame->tiles[0].width;
s->frame->tiles[1].height = s->frame->tiles[0].height;
s->frame->tiles[1].data_len = s->frame->tiles[0].data_len;
}
return result;
}
/**
* This function is used when device does not support autodetection and user
* request explicitly to detect the format (:detect-format)
*/
static bool detect_format(struct vidcap_decklink_state *s, BMDDisplayMode *outDisplayMode, int card_idx)
{
IDeckLinkDisplayMode *displayMode;
HRESULT result;
IDeckLinkDisplayModeIterator* displayModeIterator = NULL;
result = s->state[card_idx].deckLinkInput->GetDisplayModeIterator(&displayModeIterator);
if (result != S_OK) {
return false;
}
vector<BMDPixelFormat> pfs = {bmdFormat8BitYUV, bmdFormat8BitBGRA};
while (displayModeIterator->Next(&displayMode) == S_OK) {
for (BMDPixelFormat pf : pfs) {
uint32_t mode = ntohl(displayMode->GetDisplayMode());
log_msg(LOG_LEVEL_NOTICE, "DeckLink: trying mode %.4s, pixel format %.4s\n", (const char *) &mode, (const char *) &pf);
result = s->state[card_idx].deckLinkInput->EnableVideoInput(displayMode->GetDisplayMode(), pf, 0);
if (result == S_OK) {
s->state[card_idx].deckLinkInput->StartStreams();
unique_lock<mutex> lk(s->lock);
s->boss_cv.wait_for(lk, chrono::milliseconds(1200), [s, card_idx]{return s->state[card_idx].delegate->newFrameReady;});
lk.unlock();
s->state[card_idx].deckLinkInput->StopStreams();
s->state[card_idx].deckLinkInput->DisableVideoInput();
if (s->state[card_idx].delegate->newFrameReady) {
*outDisplayMode = displayMode->GetDisplayMode();
// set also detected codec (!)
s->codec = pf == bmdFormat8BitYUV ? UYVY : RGBA;
displayMode->Release();
displayModeIterator->Release();
return true;
}
}
}
displayMode->Release();
}
displayModeIterator->Release();
return false;
}
static bool decklink_cap_configure_audio(struct vidcap_decklink_state *s, unsigned int audio_src_flag, BMDAudioConnection *audioConnection) {
if (audio_src_flag == 0U) {
s->grab_audio = false;
return true;
}
s->grab_audio = true;
switch (audio_src_flag) {
case VIDCAP_FLAG_AUDIO_EMBEDDED:
*audioConnection = bmdAudioConnectionEmbedded;
break;
case VIDCAP_FLAG_AUDIO_AESEBU:
*audioConnection = bmdAudioConnectionAESEBU;
break;
case VIDCAP_FLAG_AUDIO_ANALOG:
*audioConnection = bmdAudioConnectionAnalog;
break;
default:
LOG(LOG_LEVEL_FATAL) << MOD_NAME << "Unexpected audio flag " << audio_src_flag << " encountered.\n";
abort();
}
s->audio.bps = audio_capture_bps == 0 ? DEFAULT_AUDIO_BPS : audio_capture_bps;
if (s->audio.bps != 2 && s->audio.bps != 4) {
LOG(LOG_LEVEL_ERROR) << MOD_NAME << "[Decklink] Unsupported audio Bps " << audio_capture_bps << "! Supported is 2 or 4 bytes only!\n";
return false;
}
if (audio_capture_sample_rate != 0 && audio_capture_sample_rate != bmdAudioSampleRate48kHz) {
LOG(LOG_LEVEL_ERROR) << MOD_NAME "Unsupported sample rate " << audio_capture_sample_rate << "! Only " << bmdAudioSampleRate48kHz << " is supported.\n";
return false;
}
s->audio.sample_rate = bmdAudioSampleRate48kHz;
s->audio.ch_count = audio_capture_channels > 0 ? audio_capture_channels : DEFAULT_AUDIO_CAPTURE_CHANNELS;
s->audio.max_size = (s->audio.sample_rate / 10) * s->audio.ch_count * s->audio.bps;
s->audio.data = (char *) malloc(s->audio.max_size);
return true;
}
static int
vidcap_decklink_init(struct vidcap_params *params, void **state)
{
debug_msg("vidcap_decklink_init\n"); /* TOREMOVE */
const char *fmt = vidcap_params_get_fmt(params);
int dnum, mnum;
IDeckLinkIterator* deckLinkIterator;
IDeckLink* deckLink;
HRESULT result;
IDeckLinkInput* deckLinkInput = NULL;
IDeckLinkDisplayModeIterator* displayModeIterator = NULL;
IDeckLinkDisplayMode* displayMode = NULL;
IDeckLinkConfiguration* deckLinkConfiguration = NULL;
BMDAudioConnection audioConnection = bmdAudioConnectionEmbedded;
if (strcmp(fmt, "help") == 0 || strcmp(fmt, "fullhelp") == 0) {
decklink_help(strcmp(fmt, "fullhelp") == 0);
return VIDCAP_INIT_NOERR;
}
if (!blackmagic_api_version_check()) {
return VIDCAP_INIT_FAIL;
}
struct vidcap_decklink_state *s = new vidcap_decklink_state();
if (s == NULL) {
LOG(LOG_LEVEL_ERROR) << "Unable to allocate DeckLink state\n";
return VIDCAP_INIT_FAIL;
}
gettimeofday(&s->t0, NULL);
// SET UP device and mode
char *tmp_fmt = strdup(fmt);
int ret = settings_init(s, tmp_fmt);
free(tmp_fmt);
if (!ret) {
delete s;
return VIDCAP_INIT_FAIL;
}
if (s->link == bmdLinkConfigurationQuadLink) {
if (s->profile == BMD_OPT_DEFAULT) {
LOG(LOG_LEVEL_WARNING) << MOD_NAME "Quad-link detected - setting 1-subdevice-1/2-duplex profile automatically, use 'profile=keep' to override.\n";
s->profile = bmdProfileOneSubDeviceHalfDuplex;
} else if (s->profile != BMD_OPT_KEEP && s->profile != bmdProfileOneSubDeviceHalfDuplex) {
LOG(LOG_LEVEL_WARNING) << MOD_NAME "Setting quad-link and profile other than 1-subdevice-1/2-duplex may not be supported!\n";
}
}
switch (get_bits_per_component(s->codec)) {
case 0: s->requested_bit_depth = 0; break;
case 8: s->requested_bit_depth = bmdDetectedVideoInput8BitDepth; break;
case 10: s->requested_bit_depth = bmdDetectedVideoInput10BitDepth; break;
case 12: s->requested_bit_depth = bmdDetectedVideoInput12BitDepth; break;
default: abort();
}
if (s->codec == VIDEO_CODEC_NONE) {
s->codec = UYVY; // default one
}
if (!decklink_cap_configure_audio(s, vidcap_params_get_flags(params) & VIDCAP_FLAG_AUDIO_ANY, &audioConnection)) {
delete s;
return VIDCAP_INIT_FAIL;
}
if(s->stereo) {
s->enable_flags |= bmdVideoInputDualStream3D;
s->supported_flags = (BMDSupportedVideoModeFlags) (s->supported_flags | bmdSupportedVideoModeDualStream3D);
if (s->devices_cnt > 1) {
fprintf(stderr, "[DeckLink] Passed more than one device while setting 3D mode. "
"In this mode, only one device needs to be passed.");
delete s;
return VIDCAP_INIT_FAIL;
}
s->frame = vf_alloc(2);
} else {
s->frame = vf_alloc(s->devices_cnt);
}
/* TODO: make sure that all devices are have compatible properties */
for (int i = 0; i < s->devices_cnt; ++i) {
struct tile * tile = vf_get_tile(s->frame, i);
dnum = 0;
deckLink = NULL;
// Create an IDeckLinkIterator object to enumerate all DeckLink cards in the system
deckLinkIterator = create_decklink_iterator(true, i == 0 ? true : false);
if (deckLinkIterator == NULL) {
vf_free(s->frame);
delete s;
return VIDCAP_INIT_FAIL;
}
bool found = false;
while (deckLinkIterator->Next(&deckLink) == S_OK) {
string deviceName = bmd_get_device_name(deckLink);
if (!deviceName.empty() && deviceName == s->state[i].device_id.c_str()) {
found = true;
}
if (isdigit(s->state[i].device_id.c_str()[0]) && atoi(s->state[i].device_id.c_str()) == dnum) {
found = true;
}
if (found) {
break;
}
dnum++;
// Release the IDeckLink instance when we've finished with it to prevent leaks
deckLink->Release();
deckLink = NULL;
}
deckLinkIterator->Release();
deckLinkIterator = NULL;
if (!found) {
LOG(LOG_LEVEL_ERROR) << "Device " << s->state[i].device_id << " was not found.\n";
goto error;
}
s->state[i].deckLink = deckLink;
// Print the model name of the DeckLink card
string deviceName = bmd_get_device_name(deckLink);
if (!deviceName.empty()) {
LOG(LOG_LEVEL_INFO) << "Using device " << deviceName << "\n";
}
if (s->profile != BMD_OPT_DEFAULT && s->profile != BMD_OPT_KEEP) {
decklink_set_duplex(s->state[i].deckLink, s->profile);
}
// Query the DeckLink for its configuration interface
EXIT_IF_FAILED(deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput), "Could not obtain the IDeckLinkInput interface");
s->state[i].deckLinkInput = deckLinkInput;
// Query the DeckLink for its configuration interface
EXIT_IF_FAILED(deckLinkInput->QueryInterface(IID_IDeckLinkConfiguration, (void**)&deckLinkConfiguration), "Could not obtain the IDeckLinkConfiguration interface");
s->state[i].deckLinkConfiguration = deckLinkConfiguration;
IDeckLinkProfileAttributes *deckLinkAttributes;
EXIT_IF_FAILED(deckLinkInput->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes), "Could not query device attributes");
s->state[i].deckLinkAttributes = deckLinkAttributes;
BMD_CONFIG_SET_INT(bmdDeckLinkConfigVideoInputConnection, s->connection);
BMD_CONFIG_SET_INT(bmdDeckLinkConfigVideoInputConversionMode, s->conversion_mode);
BMDVideoInputConversionMode supported_conversion_mode = s->conversion_mode ? s->conversion_mode : (BMDVideoInputConversionMode) bmdNoVideoInputConversion;
BMD_CONFIG_SET_INT(bmdDeckLinkConfigCapturePassThroughMode, s->passthrough);
if (s->link == 0) {
LOG(LOG_LEVEL_NOTICE) << MOD_NAME "Setting single link by default.\n";
s->link = bmdLinkConfigurationSingleLink;
}
CALL_AND_CHECK( deckLinkConfiguration->SetInt(bmdDeckLinkConfigSDIOutputLinkConfiguration, s->link), "Unable set output SDI link mode");
if (s->use1080psf != BMD_OPT_KEEP) {
CALL_AND_CHECK(deckLinkConfiguration->SetFlag(bmdDeckLinkConfigCapture1080pAsPsF, s->use1080psf != 0), "Unable to set output as PsF");
}
// set Callback which returns frames
s->state[i].delegate = new VideoDelegate(s, i);
deckLinkInput->SetCallback(s->state[i].delegate);
BMDDisplayMode detectedDisplayMode = bmdModeUnknown;
if (s->detect_format) {
if (!detect_format(s, &detectedDisplayMode, i)) {
LOG(LOG_LEVEL_WARNING) << "Signal could have not been detected!\n";
goto error;
}
}
// Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on input
EXIT_IF_FAILED(deckLinkInput->GetDisplayModeIterator(&displayModeIterator),
"Could not obtain the video input display mode iterator:");
mnum = 0;
#define MODE_SPEC_AUTODETECT -1
#define MODE_SPEC_FOURCC -2
#define MODE_SPEC_DETECTED -3
int mode_idx = MODE_SPEC_AUTODETECT;
// mode selected manually - either by index or FourCC
if (s->mode.length() > 0) {
if (s->mode.length() <= 2) {
mode_idx = atoi(s->mode.c_str());
} else {
mode_idx = MODE_SPEC_FOURCC;
}
}
if (s->detect_format) { // format already detected manually
mode_idx = MODE_SPEC_DETECTED;
}
while (displayModeIterator->Next(&displayMode) == S_OK) {
if (mode_idx == MODE_SPEC_DETECTED) { // format already detected manually
if (detectedDisplayMode == displayMode->GetDisplayMode()) {
break;
} else {
displayMode->Release();
}
} else if (mode_idx == MODE_SPEC_AUTODETECT) { // autodetect, pick first eligible mode and let device autodetect
if (s->stereo && (displayMode->GetFlags() & bmdDisplayModeSupports3D) == 0u) {
displayMode->Release();
continue;
}
auto it = std::find_if(uv_to_bmd_codec_map.begin(),
uv_to_bmd_codec_map.end(),
[&s](const std::pair<codec_t, BMDPixelFormat>& el){ return el.first == s->codec; });
if (it == uv_to_bmd_codec_map.end()) {
LOG(LOG_LEVEL_ERROR) << "Unsupported codec: " << get_codec_name(s->codec) << "!\n";
goto error;
}
BMDPixelFormat pf = it->second;
BMD_BOOL supported = 0;
EXIT_IF_FAILED(deckLinkInput->DoesSupportVideoMode(s->connection, displayMode->GetDisplayMode(), pf, supported_conversion_mode, s->supported_flags, nullptr, &supported), "DoesSupportVideoMode");
if (supported) {
break;
}
} else if (mode_idx != MODE_SPEC_FOURCC) { // manually given idx
if (mode_idx != mnum) {
mnum++;
// Release the IDeckLinkDisplayMode object to prevent a leak
displayMode->Release();
continue;
}
mnum++;
break;
} else { // manually given FourCC
if (displayMode->GetDisplayMode() == bmd_read_fourcc(s->mode.c_str())) {
break;
}
displayMode->Release();
}
}
if (displayMode) {
BMD_STR displayModeString = NULL;
result = displayMode->GetName(&displayModeString);
if (result == S_OK) {
char *displayModeCString = get_cstr_from_bmd_api_str(displayModeString);
LOG(LOG_LEVEL_INFO) << "The desired display mode is supported: " << displayModeCString << "\n";
release_bmd_api_str(displayModeString);
free(displayModeCString);
}
} else {
if (mode_idx == MODE_SPEC_FOURCC) {
log_msg(LOG_LEVEL_ERROR, "Desired mode \"%s\" is invalid or not supported.\n", s->mode.c_str());
} else if (mode_idx >= 0) {
log_msg(LOG_LEVEL_ERROR, "Desired mode index %s is out of bounds.\n", s->mode.c_str());
} else if (mode_idx == MODE_SPEC_AUTODETECT) {
log_msg(LOG_LEVEL_ERROR, MODULE_NAME "Cannot set initial format for autodetection - perhaps imposible combinations of parameters were set.\n");
} else {
assert("Invalid mode spec." && 0);
}
goto error;
}
BMDPixelFormat pf;
EXIT_IF_FAILED(set_display_mode_properties(s, tile, displayMode, &pf),
"Could not set display mode properties");
deckLinkInput->StopStreams();
if (mode_idx == MODE_SPEC_AUTODETECT) {
log_msg(LOG_LEVEL_INFO, "[DeckLink] Trying to autodetect format.\n");
BMD_BOOL autodetection;
EXIT_IF_FAILED(deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, &autodetection), "Could not verify if device supports autodetection");
if (autodetection == BMD_FALSE) {
log_msg(LOG_LEVEL_ERROR, "[DeckLink] Device doesn't support format autodetection, you must set it manually or try \"-t decklink:detect-format[:connection=<in>]\"\n");
goto error;
}
s->enable_flags |= bmdVideoInputEnableFormatDetection;
}
BMD_BOOL supported = 0;
EXIT_IF_FAILED(deckLinkInput->DoesSupportVideoMode(s->connection, displayMode->GetDisplayMode(), pf, supported_conversion_mode, s->supported_flags, nullptr, &supported), "DoesSupportVideoMode");
if (!supported) {
LOG(LOG_LEVEL_ERROR) << MOD_NAME "Requested display mode not supported with the selected pixel format\n";
goto error;
}
result = deckLinkInput->EnableVideoInput(displayMode->GetDisplayMode(), pf, s->enable_flags);
if (result != S_OK) {
switch (result) {
case E_INVALIDARG:
fprintf(stderr, "You have required invalid video mode and pixel format combination.\n");
break;
case E_ACCESSDENIED:
fprintf(stderr, "Unable to access the hardware or input "
"stream currently active (another application using it?).\n");
break;
}
string err_msg = bmd_hresult_to_string(result);
fprintf(stderr, "Could not enable video input: %s\n",
err_msg.c_str());
goto error;
}
if (!s->grab_audio ||
i != 0) { //TODO: figure out output from multiple streams
deckLinkInput->DisableAudioInput();
} else {
if (deckLinkConfiguration->SetInt(bmdDeckLinkConfigAudioInputConnection,
audioConnection) == S_OK) {
const map<BMDAudioConnection, string> mapping = {
{ bmdAudioConnectionEmbedded, "embedded" },
{ bmdAudioConnectionAESEBU, "AES/EBU" },
{ bmdAudioConnectionAnalog, "analog" },
{ bmdAudioConnectionAnalogXLR, "analogXLR" },
{ bmdAudioConnectionAnalogRCA, "analogRCA" },
{ bmdAudioConnectionMicrophone, "microphone" },
{ bmdAudioConnectionHeadphones, "headphones" },
};
printf("[Decklink capture] Audio input set to: %s\n", mapping.find(audioConnection) != mapping.end() ? mapping.at(audioConnection).c_str() : "unknown");
} else {
fprintf(stderr, "[Decklink capture] Unable to set audio input!!! Please check if it is OK. Continuing anyway.\n");
}
if (s->audio.ch_count != 1 &&
s->audio.ch_count != 2 &&
s->audio.ch_count != 8 &&
s->audio.ch_count != 16) {
fprintf(stderr, "[DeckLink] Decklink cannot grab %d audio channels. "
"Only 1, 2, 8 or 16 are possible.", s->audio.ch_count);
goto error;
}
if (s->audio_consumer_levels != -1) {
result = deckLinkConfiguration->SetFlag(bmdDeckLinkConfigAnalogAudioConsumerLevels,
s->audio_consumer_levels == 1 ? true : false);
if(result != S_OK) {
fprintf(stderr, "[DeckLink capture] Unable set input audio consumer levels.\n");
}
}
CALL_AND_CHECK(deckLinkInput->EnableAudioInput(
bmdAudioSampleRate48kHz,
s->audio.bps == 2 ? bmdAudioSampleType16bitInteger : bmdAudioSampleType32bitInteger,
max(s->audio.ch_count, 2)), // capture at least 2
"EnableAudioInput",
"Decklink audio capture initialized sucessfully: " << audio_desc_from_frame(&s->audio));
}
// Start streaming
printf("Start capture\n");
EXIT_IF_FAILED(deckLinkInput->StartStreams(), "Could not start stream");
displayMode->Release();
displayMode = NULL;
displayModeIterator->Release();
displayModeIterator = NULL;
}
printf("DeckLink capture device enabled\n");
debug_msg("vidcap_decklink_init - END\n"); /* TOREMOVE */
*state = s;
return VIDCAP_INIT_OK;
error:
if(displayMode != NULL) {
displayMode->Release();
displayMode = NULL;
}
if (displayModeIterator != NULL){
displayModeIterator->Release();
displayModeIterator = NULL;
}
if (s) {
cleanup_common(s);
}
return VIDCAP_INIT_FAIL;
}
static void cleanup_common(struct vidcap_decklink_state *s) {
while (!s->audioPackets.empty()) {
auto *audioPacket = s->audioPackets.front();
s->audioPackets.pop();
audioPacket->Release();
}
for (int i = 0; i < s->devices_cnt; ++i) {
RELEASE_IF_NOT_NULL(s->state[i].deckLinkConfiguration);
if(s->state[i].deckLinkConfiguration != NULL) {
s->state[i].deckLinkConfiguration->Release();
}
if(s->state[i].deckLinkInput != NULL)
{
s->state[i].deckLinkInput->Release();
s->state[i].deckLinkInput = NULL;
}
if(s->state[i].deckLink != NULL)
{
s->state[i].deckLink->Release();
s->state[i].deckLink = NULL;
}
}
free(s->audio.data);
vf_free(s->frame);
delete s;
decklink_uninitialize();
}
static void
vidcap_decklink_done(void *state)
{
debug_msg("vidcap_decklink_done\n"); /* TOREMOVE */
HRESULT result;
struct vidcap_decklink_state *s = (struct vidcap_decklink_state *) state;
assert (s != NULL);
for (int i = 0; i < s->devices_cnt; ++i)
{
result = s->state[i].deckLinkInput->StopStreams();
if (result != S_OK) {
string err_msg = bmd_hresult_to_string(result);
fprintf(stderr, MODULE_NAME "Could not stop stream: %s\n", err_msg.c_str());
}
if(s->grab_audio && i == 0) {
result = s->state[i].deckLinkInput->DisableAudioInput();
if (result != S_OK) {
string err_msg = bmd_hresult_to_string(result);
fprintf(stderr, MODULE_NAME "Could disable audio input: %s\n", err_msg.c_str());
}
}
result = s->state[i].deckLinkInput->DisableVideoInput();
if (result != S_OK) {
string err_msg = bmd_hresult_to_string(result);
fprintf(stderr, MODULE_NAME "Could disable video input: %s\n", err_msg.c_str());
}
}
cleanup_common(s);
}
/**
* This function basically counts frames from all devices, optionally
* with respect to timecode (if synchronized).
*
* Lock needs to be hold during the whole function call.
*
* @param s Blackmagic state
* @return number of captured tiles
*/
static int nr_frames(struct vidcap_decklink_state *s) {
BMDTimecodeBCD max_timecode = 0u;
int tiles_total = 0;
int i;
/* If we use timecode, take maximal timecode value... */
if (s->sync_timecode) {
for (i = 0; i < s->devices_cnt; ++i) {
if(s->state[i].delegate->newFrameReady) {
if (s->state[i].delegate->timecode > max_timecode) {
max_timecode = s->state[i].delegate->timecode;
}
}
}
}
/* count all tiles */
for (i = 0; i < s->devices_cnt; ++i) {
if(s->state[i].delegate->newFrameReady) {
/* if inputs are synchronized, use only up-to-date frames (with same TC)
* as the most recent */
if(s->sync_timecode) {
if(s->state[i].delegate->timecode && s->state[i].delegate->timecode != max_timecode) {
s->state[i].delegate->newFrameReady = FALSE;
} else {
tiles_total++;
}
}
/* otherwise, simply add up the count */
else {
tiles_total++;
}
}
}
return tiles_total;
}
static audio_frame *process_new_audio_packets(struct vidcap_decklink_state *s) {
if (s->audioPackets.empty()) {
return nullptr;
}
s->audio.data_len = 0;
while (!s->audioPackets.empty()) {
auto *audioPacket = s->audioPackets.front();
s->audioPackets.pop();
void *audioFrame = nullptr;
audioPacket->GetBytes(&audioFrame);
if (s->audio.ch_count == 1) { // there are actually 2 channels grabbed
if (s->audio.data_len + audioPacket->GetSampleFrameCount() * 1U * s->audio.bps <= static_cast<unsigned>(s->audio.max_size)) {
demux_channel(s->audio.data + s->audio.data_len, static_cast<char *>(audioFrame), s->audio.bps, min<int64_t>(audioPacket->GetSampleFrameCount() * 2 /* channels */ * s->audio.bps, INT_MAX), 2 /* channels (originally) */, 0 /* we want first channel */);
s->audio.data_len = min<int64_t>(s->audio.data_len + audioPacket->GetSampleFrameCount() * 1 * s->audio.bps, INT_MAX);
} else {
LOG(LOG_LEVEL_WARNING) << "[DeckLink] Audio frame too small!\n";
}
} else {
if (s->audio.data_len + audioPacket->GetSampleFrameCount() * s->audio.ch_count * s->audio.bps <= s->audio.max_size) {
memcpy(s->audio.data + s->audio.data_len, audioFrame, audioPacket->GetSampleFrameCount() * s->audio.ch_count * s->audio.bps);
s->audio.data_len = min<int64_t>(s->audio.data_len + audioPacket->GetSampleFrameCount() * s->audio.ch_count * s->audio.bps, INT_MAX);
} else {
LOG(LOG_LEVEL_WARNING) << "[DeckLink] Audio frame too small!\n";
}
}
audioPacket->Release();
}
return &s->audio;
}
static struct video_frame *
vidcap_decklink_grab(void *state, struct audio_frame **audio)
{
debug_msg("vidcap_decklink_grab\n"); /* TO REMOVE */
struct vidcap_decklink_state *s = (struct vidcap_decklink_state *) state;
int tiles_total = 0;
int i;
bool frame_ready = true;
int timeout = 0;
unique_lock<mutex> lk(s->lock);
// LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK - LOCK //
debug_msg("vidcap_decklink_grab - before while\n"); /* TOREMOVE */
tiles_total = nr_frames(s);
while(tiles_total != s->devices_cnt) {
//while (!s->state[0].delegate->newFrameReady) {
cv_status rc = cv_status::no_timeout;
debug_msg("vidcap_decklink_grab - pthread_cond_timedwait\n"); /* TOREMOVE */
steady_clock::time_point t0(steady_clock::now());
while(rc == cv_status::no_timeout
&& tiles_total != s->devices_cnt /* not all tiles */
&& !timeout) {
rc = s->boss_cv.wait_for(lk, microseconds(2 * s->next_frame_time));
// recompute tiles count
tiles_total = nr_frames(s);
// this is for the case of multiple tiles (eg. when one tile is persistently
// missing, eg. 01301301. Therefore, pthread_cond_timewait doesn't timeout but
// actual timeout time has reached.
steady_clock::time_point t(steady_clock::now());
if (duration_cast<microseconds>(t - t0).count() > 2 * s->next_frame_time)
timeout = 1;
}
debug_msg("vidcap_decklink_grab - AFTER pthread_cond_timedwait - %d tiles\n", tiles_total); /* TOREMOVE */
if (rc != cv_status::no_timeout || timeout) { //(rc == ETIMEDOUT) {
log_msg(LOG_LEVEL_VERBOSE, "Waiting for new frame timed out!\n");
// try to restart stream
/*
HRESULT result;
debug_msg("Try to restart DeckLink stream!\n");
result = s->deckLinkInput->StopStreams();
if (result != S_OK)
{
debug_msg("Could not stop stream: %08x\n", result);
}
result = s->deckLinkInput->StartStreams();
if (result != S_OK)
{
debug_msg("Could not start stream: %08x\n", result);
return NULL; // really end ???
}
*/
frame_ready = false;
break;
}
}
/* cleanup newframe flag */
for (i = 0; i < s->devices_cnt; ++i) {
if(s->state[i].delegate->newFrameReady == 0) {
frame_ready = false;
}
s->state[i].delegate->newFrameReady = 0;
}
*audio = process_new_audio_packets(s); // return audio even if there is no video to avoid
// hoarding and then dropping of audio packets
// UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UNLOCK - UN //
lk.unlock();
if(!frame_ready)
return NULL;
/* count returned tiles */
int count = 0;
if(s->stereo) {
if (s->state[0].delegate->pixelFrame != NULL &&
s->state[0].delegate->pixelFrameRight != NULL) {
s->frame->tiles[0].data = (char*)s->state[0].delegate->pixelFrame;
if (s->codec == RGBA) {
vc_copylineToRGBA_inplace((unsigned char*) s->frame->tiles[0].data,
(unsigned char*)s->frame->tiles[0].data,
s->frame->tiles[i].data_len, 16, 8, 0);
}
s->frame->tiles[1].data = (char*)s->state[0].delegate->pixelFrameRight;
if (s->codec == RGBA) {
vc_copylineToRGBA_inplace((unsigned char*) s->frame->tiles[1].data,
(unsigned char*)s->frame->tiles[1].data,
s->frame->tiles[i].data_len, 16, 8, 0);
}
++count;
} // else count == 0 -> return NULL
} else {
for (i = 0; i < s->devices_cnt; ++i) {
if (s->state[i].delegate->pixelFrame != NULL) {
s->frame->tiles[i].data = (char*)s->state[i].delegate->pixelFrame;
if (s->codec == RGBA) {
vc_copylineToRGBA_inplace((unsigned char*) s->frame->tiles[i].data,
(unsigned char*)s->frame->tiles[i].data,
s->frame->tiles[i].data_len, 16, 8, 0);
}
++count;
} else
break;
}
}
if (count == s->devices_cnt) {
s->frames++;
struct timeval t;
gettimeofday(&t, NULL);
double seconds = tv_diff(t, s->t0);
if (seconds >= 5) {
float fps = s->frames / seconds;
log_msg(LOG_LEVEL_INFO, "[Decklink capture] %d frames in %g seconds = %g FPS\n", s->frames, seconds, fps);
s->t0 = t;
s->frames = 0;
}
s->frame->timecode = s->state[0].delegate->timecode;
return s->frame;
}
return NULL;
}
/* function from DeckLink SDK sample DeviceList */
static list<tuple<int, string, string, string>> get_input_modes (IDeckLink* deckLink)
{
list<tuple<int, string, string, string>> ret;
IDeckLinkInput* deckLinkInput = NULL;
IDeckLinkDisplayModeIterator* displayModeIterator = NULL;
IDeckLinkDisplayMode* displayMode = NULL;
HRESULT result;
int displayModeNumber = 0;
// Query the DeckLink for its configuration interface
result = deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput);
if (result != S_OK)
{
string err_msg = bmd_hresult_to_string(result);
fprintf(stderr, "Could not obtain the IDeckLinkInput interface: %s\n", err_msg.c_str());
if (result == E_NOINTERFACE) {
printf("Device doesn't support video capture.\n");
}
goto bail;
}
// Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on input
result = deckLinkInput->GetDisplayModeIterator(&displayModeIterator);
if (result != S_OK)
{
string err_msg = bmd_hresult_to_string(result);
fprintf(stderr, "Could not obtain the video input display mode iterator: %s\n", err_msg.c_str());
goto bail;
}
// List all supported output display modes
while (displayModeIterator->Next(&displayMode) == S_OK)
{
BMD_STR displayModeString = NULL;
result = displayMode->GetName((BMD_STR *) &displayModeString);
if (result == S_OK)
{
int modeWidth;
int modeHeight;
BMDDisplayModeFlags flags;
BMDTimeValue frameRateDuration;
BMDTimeScale frameRateScale;
char *displayModeCString = get_cstr_from_bmd_api_str(displayModeString);
// Obtain the display mode's properties
flags = displayMode->GetFlags();
modeWidth = displayMode->GetWidth();
modeHeight = displayMode->GetHeight();
displayMode->GetFrameRate(&frameRateDuration, &frameRateScale);
uint32_t mode = ntohl(displayMode->GetDisplayMode());
string fcc{(char *) &mode, 4};
string name{displayModeCString};
char buf[1024];
snprintf(buf, sizeof buf, "%d x %d \t %2.2f FPS%s", modeWidth, modeHeight,
(float) ((double)frameRateScale / (double)frameRateDuration),
(flags & bmdDisplayModeSupports3D ? "\t (supports 3D)" : ""));
string details{buf};
ret.push_back(tuple<int, string, string, string> {displayModeNumber, fcc, name, details});
release_bmd_api_str(displayModeString);
free(displayModeCString);
}
// Release the IDeckLinkDisplayMode object to prevent a leak
displayMode->Release();
displayModeNumber++;
}
bail:
// Ensure that the interfaces we obtained are released to prevent a memory leak
if (displayModeIterator != NULL)
displayModeIterator->Release();
if (deckLinkInput != NULL)
deckLinkInput->Release();
return ret;
}
static void print_input_modes (IDeckLink* deckLink)
{
list<tuple<int, string, string, string>> ret = get_input_modes (deckLink);
printf("\tcapture modes:\n");
for (auto &i : ret) {
cout << "\t\t" << right << style::bold << setw(2) << get<0>(i) <<
" (" << get<1>(i) << ")" << style::reset << ") " <<
left << setw(20) << get<2>(i) << internal << " " <<
get<3>(i) << "\n";
}
}
static const struct video_capture_info vidcap_decklink_info = {
vidcap_decklink_probe,
vidcap_decklink_init,
vidcap_decklink_done,
vidcap_decklink_grab,
false
};
REGISTER_MODULE(decklink, &vidcap_decklink_info, LIBRARY_CLASS_VIDEO_CAPTURE, VIDEO_CAPTURE_ABI_VERSION);
/* vi: set expandtab sw=8: */