UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
blackmagic_common.cpp
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2014-2024 CESNET
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38#include <algorithm>
39#include <cassert>
40#include <cctype> // for isxdigit
41#include <chrono> // for seconds
42#include <climits> // for UINT_MAX
43#include <condition_variable>
44#include <cstdio> // for fprintf, stderr
45#include <cstdint> // for int64_t, uint32_t
46#include <cstring> // for strlen, NULL, strdup, memcpy, size_t
47#include <iomanip>
48#include <map>
49#include <mutex> // for mutex, lock_guard, unique_lock
50#include <sstream>
51#include <stdexcept>
52#include <unordered_map>
53#include <utility>
54
55#include "compat/htonl.h"
56#include "compat/misc.h" // for strncasecmp
57#include "DeckLinkAPIVersion.h"
58#include "blackmagic_common.hpp"
59#include "debug.h"
60#include "host.h"
61#include "utils/color_out.h"
62#include "utils/macros.h"
63#include "utils/windows.h"
64#include "utils/worker.h"
65
66#define MOD_NAME "[DeckLink] "
67
68using std::clamp;
69using std::fixed;
70using std::hex;
71using std::invalid_argument;
72using std::map;
73using std::min;
74using std::pair;
75using std::ostringstream;
76using std::setfill;
77using std::setw;
78using std::stod;
79using std::stoi;
80using std::string;
81using std::uppercase;
82using std::unordered_map;
83using std::vector;
84
85string bmd_hresult_to_string(HRESULT res)
86{
87 const char *errptr = nullptr;
88#ifdef _WIN32
89 errptr = hresult_to_str(res);
90#else
91 HRESULT_GET_ERROR_COMMON(res, errptr)
92#endif
93 ostringstream oss;
94 if (errptr) {
95 oss << errptr;
96 }
97 oss << " " << "(0x" << hex << setfill('0') << setw(8) << res << ")";
98 return oss.str();
99}
105{
106 if (!bmd_string) {
107 return strdup("(NULL!)");
108 }
109 char *cstr;
110#ifdef __APPLE__
111 size_t len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(bmd_string), kCFStringEncodingUTF8) + 1;
112 cstr = (char *) malloc(len);
113 CFStringGetCString(bmd_string, (char *) cstr, len, kCFStringEncodingUTF8);
114#elif defined _WIN32
115 size_t len = SysStringLen(bmd_string) * 4 + 1;
116 cstr = (char *) malloc(len);
117 wcstombs((char *) cstr, bmd_string, len);
118#else // Linux
119 cstr = strdup(bmd_string);
120#endif
121
122 return cstr;
123}
124
126{
127#ifdef __APPLE__
128 return CFStringCreateWithCString(kCFAllocatorMalloc, cstr, kCFStringEncodingUTF8);
129#elif defined _WIN32
130 mbstate_t mbstate{};
131 const char *tmp = cstr;
132 size_t required_size = mbsrtowcs(NULL, &tmp, 0, &mbstate) + 1;
133 BMD_STR out = (wchar_t *) malloc(required_size * sizeof(wchar_t));
134 mbsrtowcs(out, &tmp, required_size, &mbstate);
135 return out;
136#else
137 return strdup(cstr);
138#endif
139}
140
142{
143 if (!string) {
144 return;
145 }
146#ifdef __APPLE__
147 CFRelease(string);
148#elif defined _WIN32
149 SysFreeString(string);
150#else
151 free(const_cast<char *>(string));
152#endif
153}
154
156{
157 char *displayModeCString = get_cstr_from_bmd_api_str(string);
158 std::string out = displayModeCString;
159 free(displayModeCString);
160 return out;
161}
162
171IDeckLinkIterator *create_decklink_iterator(bool *com_initialized, bool verbose)
172{
173 IDeckLinkIterator *deckLinkIterator = nullptr;
174#ifdef _WIN32
175 com_initialize(com_initialized, "[BMD] ");
176 HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL,
177 IID_IDeckLinkIterator, (void **) &deckLinkIterator);
178 if (FAILED(result)) {
179 decklink_uninitialize(com_initialized);
180 deckLinkIterator = nullptr;
181 }
182#else
183 *com_initialized = false;
184 deckLinkIterator = CreateDeckLinkIteratorInstance();
185#endif
186
187 if (!deckLinkIterator && verbose) {
188 log_msg(LOG_LEVEL_ERROR, "A DeckLink iterator could not be created. The DeckLink drivers may not be installed or are outdated.\n");
189 log_msg(LOG_LEVEL_INFO, "This UltraGrid version was compiled with DeckLink drivers %s. You should have at least this version.\n\n",
190 BLACKMAGIC_DECKLINK_API_VERSION_STRING);
191
192 }
193
194 return deckLinkIterator;
195}
196
197void decklink_uninitialize(bool *com_initialized)
198{
199 com_uninitialize(com_initialized);
200}
201
203{
204 bool ret = false;
205 IDeckLinkAPIInformation *APIInformation = NULL;
206 HRESULT result;
207 bool com_initialized = false;
208
209 if (!com_initialize(&com_initialized, "[BMD] ")) {
210 goto cleanup;
211 }
212#ifdef _WIN32
213 result = CoCreateInstance(CLSID_CDeckLinkAPIInformation, NULL, CLSCTX_ALL,
214 IID_IDeckLinkAPIInformation, (void **) &APIInformation);
215 if(FAILED(result)) {
216#else
217 APIInformation = CreateDeckLinkAPIInformationInstance();
218 if(APIInformation == NULL) {
219#endif
220 log_msg(LOG_LEVEL_ERROR, "Cannot get API information! Perhaps drivers not installed.\n");
221 goto cleanup;
222 }
223 int64_t value;
224 result = APIInformation->GetInt(BMDDeckLinkAPIVersion, &value);
225 if(result != S_OK) {
226 log_msg(LOG_LEVEL_ERROR, "Cannot get API version!\n");
227 goto cleanup;
228 }
229
230 if (BLACKMAGIC_DECKLINK_API_VERSION > value) { // this is safe comparision, for internal structure please see SDK documentation
231 log_msg(LOG_LEVEL_ERROR, "The DeckLink drivers may not be installed or are outdated.\n");
232 log_msg(LOG_LEVEL_ERROR, "You should have at least the version UltraGrid has been linked with.\n");
233 log_msg(LOG_LEVEL_ERROR, "Vendor download page is http://www.blackmagic-design.com/support\n");
235 ret = false;
236 } else {
237 ret = true;
238 }
239
240cleanup:
241 if (APIInformation) {
242 APIInformation->Release();
243 }
244 decklink_uninitialize(&com_initialized);
245
246 return ret;
247}
248
250{
251 BMD_STR current_version = NULL;
252 IDeckLinkAPIInformation *APIInformation = NULL;
253 HRESULT result;
254
255#ifdef _WIN32
256 bool com_initialized = false;
257 if (!com_initialize(&com_initialized, "[BMD] ")) {
258 goto cleanup;
259 }
260
261 result = CoCreateInstance(CLSID_CDeckLinkAPIInformation, NULL, CLSCTX_ALL,
262 IID_IDeckLinkAPIInformation, (void **) &APIInformation);
263 if(FAILED(result)) {
264#else
265 APIInformation = CreateDeckLinkAPIInformationInstance();
266 if(APIInformation == NULL) {
267#endif
268 fprintf(stderr, "Cannot get API information! Perhaps drivers not installed.\n");
269 goto cleanup;
270 }
271
272 result = APIInformation->GetString(BMDDeckLinkAPIVersion, &current_version);
273 if (result != S_OK) {
274 fprintf(stderr, "Cannot get API version string!\n");
275 goto cleanup;
276 } else {
277 fprintf(stderr, "This UltraGrid version was compiled against DeckLink SDK %s. ", BLACKMAGIC_DECKLINK_API_VERSION_STRING);
278 const char *currentVersionCString = get_cstr_from_bmd_api_str(current_version);
279 fprintf(stderr, "System version is %s.\n", currentVersionCString);
280 release_bmd_api_str(current_version);
281 free(const_cast<char *>(currentVersionCString));
282 }
283
284cleanup:
285 if (APIInformation) {
286 APIInformation->Release();
287 }
288#ifdef _WIN32
289 com_uninitialize(&com_initialized);
290#endif
291}
292
293// Profile description map
294static const map<BMDProfileID, pair<const char *, const char *>> kDeviceProfiles =
295{
296 { bmdProfileOneSubDeviceFullDuplex, { "1 sub-device full-duplex", "8K Pro, Duo 2, Quad 2" } },
297 { bmdProfileOneSubDeviceHalfDuplex, { "1 sub-device half-duplex", "8K Pro" } },
298 { bmdProfileTwoSubDevicesFullDuplex, { "2 sub-devices full-duplex", "8K Pro" } },
299 { bmdProfileTwoSubDevicesHalfDuplex, { "2 sub-devices half-duplex", "Duo 2, Quad 2" } },
300 { bmdProfileFourSubDevicesHalfDuplex, { "4 sub-devices half-duplex", "8K Pro" } },
301};
302
303#define EXIT_IF_FAILED(cmd, name) \
304 do {\
305 HRESULT result = cmd;\
306 if (FAILED(result)) {;\
307 LOG(LOG_LEVEL_ERROR) << MOD_NAME << name << ": " << bmd_hresult_to_string(result) << "\n";\
308 ret = {};\
309 goto cleanup;\
310 }\
311 } while (0)
312
313#define RELEASE_IF_NOT_NULL(x) if (x != nullptr) { x->Release(); x = nullptr; }
314
315static BMDProfileID GetDeckLinkProfileID(IDeckLinkProfile* profile)
316{
317 IDeckLinkProfileAttributes* profileAttributes = nullptr;
318 if (HRESULT result = profile->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&profileAttributes); FAILED(result)) {
319 return {};
320 }
321
322 int64_t profileIDInt = 0;
323 // Get Profile ID attribute
324 const HRESULT res =
325 profileAttributes->GetInt(BMDDeckLinkProfileID, &profileIDInt);
326 if (SUCCEEDED(res)) {
327 profileAttributes->Release();
328 } else {
329 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "BMDDeckLinkProfileID: "
330 << bmd_hresult_to_string(res) << "\n";
331 }
332 return (BMDProfileID) profileIDInt;
333}
334
335class ProfileCallback : public IDeckLinkProfileCallback
336{
337 public:
338 ProfileCallback(IDeckLinkProfile *requestedProfile) : m_requestedProfile(requestedProfile) {
339 m_requestedProfile->AddRef();
340 }
341
342 HRESULT ProfileChanging (/* in */ [[maybe_unused]] IDeckLinkProfile* profileToBeActivated, /* in */ [[maybe_unused]] BMD_BOOL streamsWillBeForcedToStop) override { return S_OK; }
343 HRESULT ProfileActivated (/* in */ [[maybe_unused]] IDeckLinkProfile* activatedProfile) override {
344 {
345 std::lock_guard<std::mutex> lock(m_profileActivatedMutex);
347 }
348 m_profileActivatedCondition.notify_one();
349 return S_OK;
350 }
351 HRESULT STDMETHODCALLTYPE QueryInterface([[maybe_unused]] REFIID iid, [[maybe_unused]] LPVOID *ppv) override
352 {
353 *ppv = nullptr;
354 return E_NOINTERFACE;
355 }
356 ULONG STDMETHODCALLTYPE AddRef() override { return ++m_refCount; }
357 ULONG STDMETHODCALLTYPE Release() override {
358 ULONG refCount = --m_refCount;
359 if (refCount == 0)
360 delete this;
361
362 return refCount;
363 }
364
366 BMD_BOOL isActiveProfile = BMD_FALSE;
367 const char *profileName = kDeviceProfiles.find(GetDeckLinkProfileID(m_requestedProfile)) != kDeviceProfiles.end() ?
369 if ((m_requestedProfile->IsActive(&isActiveProfile) == S_OK) && isActiveProfile) {
370 LOG(LOG_LEVEL_INFO) << "[DeckLink] Profile " << profileName << " already active.\n";
371 return true;
372 }
373
374 LOG(LOG_LEVEL_INFO) << "[DeckLink] Waiting for profile activation... (this may take few seconds)\n";
375 std::unique_lock<std::mutex> lock(m_profileActivatedMutex);
376 bool ret = m_profileActivatedCondition.wait_for(lock, std::chrono::seconds{5}, [&]{ return m_requestedProfileActivated; });
377 if (ret) {
378 LOG(LOG_LEVEL_NOTICE) << "[DeckLink] Profile " << profileName << " activated succesfully.\n";
379 } else {
380 LOG(LOG_LEVEL_ERROR) << "[DeckLink] Profile " << profileName << " activation timeouted!\n";
381 }
382 return ret;
383 }
385 m_requestedProfile->Release();
386 }
387
388 private:
389 IDeckLinkProfile *m_requestedProfile;
390 int m_refCount = 1;
391 std::condition_variable m_profileActivatedCondition;
394};
395
399bool decklink_set_profile(IDeckLink *deckLink, bmd_option const &req_profile, bool stereo) {
400 if (req_profile.is_default() && !stereo) {
401 return true;
402 }
403
404 if (req_profile.is_help()) {
405 printf("Available profiles:\n");
407 return false;
408 }
409
410 bool ret = true;
411 IDeckLinkProfileManager *manager = nullptr;
412 IDeckLinkProfileIterator *it = nullptr;
413 IDeckLinkProfile *profile = nullptr;
414 bool found = false;
415 ProfileCallback *p = nullptr;
416
417 if (HRESULT res = deckLink->QueryInterface(IID_IDeckLinkProfileManager, (void**)&manager)) {
418 const bool error = !(req_profile.is_default() && res == E_NOINTERFACE);
419 LOG(error ? LOG_LEVEL_ERROR : LOG_LEVEL_VERBOSE) << MOD_NAME << "Cannot set duplex - query profile manager: " << bmd_hresult_to_string(res) << "\n";
420 return error;
421 }
422
423 // set '1dfd' for stereo if profile is not set explicitly
424 assert(!req_profile.is_default() || stereo);
425 const uint32_t profileID = req_profile.is_default() ? (int64_t) bmdProfileOneSubDeviceFullDuplex : req_profile.get_int();
426
427 EXIT_IF_FAILED(manager->GetProfiles(&it), "Cannot set duplex - get profiles");
428
429 while (it->Next(&profile) == S_OK) {
430 IDeckLinkProfileAttributes *attributes;
431 int64_t id;
432 if (profile->QueryInterface(IID_IDeckLinkProfileAttributes,
433 (void**)&attributes) != S_OK) {
434 LOG(LOG_LEVEL_WARNING) << "[DeckLink] Cannot get profile attributes!\n";
435 continue;
436 }
437 if (attributes->GetInt(BMDDeckLinkProfileID, &id) == S_OK) {
438 if (profileID == bmdDuplexHalf) {
439 if (id == bmdProfileTwoSubDevicesHalfDuplex || id == bmdProfileFourSubDevicesHalfDuplex) {
440 found = true;
441 }
442 } else if (profileID == id) {
443 found = true;
444 }
445 if (found) {
446 p = new ProfileCallback(profile);
447 BMD_CHECK(manager->SetCallback(p), "IDeckLinkProfileManager::SetCallback", goto cleanup);
448 if (profile->SetActive() != S_OK) {
449 LOG(LOG_LEVEL_ERROR) << "[DeckLink] Cannot set profile!\n";
450 ret = false;
451 }
452 if (!p->WaitForProfileActivation()) {
453 ret = false;
454 }
455 }
456 } else {
457 LOG(LOG_LEVEL_WARNING) << "[DeckLink] Cannot get profile ID!\n";
458 }
459 attributes->Release();
460 profile->Release();
461 if (found) {
462 break;
463 }
464 }
465
466 if (!found && ret) { // no err but not found
467 LOG(LOG_LEVEL_WARNING) << "[DeckLink] did not find suitable duplex profile!\n";
468 ret = false;
469 }
470
471cleanup:
474 RELEASE_IF_NOT_NULL(manager);
475 return ret;
476}
477
478static BMDProfileID decklink_get_active_profile_id(IDeckLink *decklink)
479{
480 BMDProfileID ret{};
481 IDeckLinkProfileManager *manager = nullptr;
482
483 if (HRESULT result = decklink->QueryInterface(IID_IDeckLinkProfileManager, (void**)&manager); FAILED(result)) {
484 if (result != E_NOINTERFACE) {
485 LOG(LOG_LEVEL_ERROR) << "Cannot get IDeckLinkProfileManager: " << bmd_hresult_to_string(result) << "\n";
486 }
487 return {};
488 }
489
490 IDeckLinkProfileIterator *it = nullptr;
491 IDeckLinkProfile *profile = nullptr;
492 EXIT_IF_FAILED(manager->GetProfiles(&it), "Cannot get profiles iterator");
493 while (it->Next(&profile) == S_OK) {
494 BMD_BOOL isActiveProfile = BMD_FALSE;
495 if ((profile->IsActive(&isActiveProfile) == S_OK) && isActiveProfile) {
496 ret = GetDeckLinkProfileID(profile);
497 profile->Release();
498 break;
499 }
500 profile->Release();
501 }
502
503cleanup:
505 RELEASE_IF_NOT_NULL(manager);
506 return ret;
507}
508
509bool bmd_check_stereo_profile(IDeckLink *deckLink) {
510 if (BMDProfileID profile_active = decklink_get_active_profile_id(deckLink)) {
511 if (profile_active != bmdProfileOneSubDeviceHalfDuplex &&
512 profile_active != bmdProfileOneSubDeviceFullDuplex) {
513 uint32_t profile_fcc_host = ntohl(profile_active);
514 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Active profile '%.4s' may not be compatible with stereo mode.\n", (char *) &profile_fcc_host);
515 log_msg(LOG_LEVEL_INFO, MOD_NAME "Use 'profile=' parameter to set 1-subdevice mode in either '1dhd' (half) or '1dfd' (full) duplex.\n");
516 }
517 return false;
518 }
519 return true;
520}
521
522string bmd_get_device_name(IDeckLink *decklink) {
523 BMD_STR deviceNameString = NULL;
524 char * deviceNameCString = NULL;
525 string ret;
526
527 if (decklink->GetDisplayName((BMD_STR *) &deviceNameString) == S_OK) {
528 deviceNameCString = get_cstr_from_bmd_api_str(deviceNameString);
529 ret = deviceNameCString;
530 release_bmd_api_str(deviceNameString);
531 free(deviceNameCString);
532 }
533
534 return ret;
535}
536
537uint32_t bmd_read_fourcc(const char *str) {
538 union {
539 uint32_t fourcc;
540 char c4[4];
541 } u;
542 memset(u.c4, ' ', 4);
543 memcpy(u.c4, str, min(strlen(str), sizeof u.c4));
544 return htonl(u.fourcc);
545}
546
547std::ostream &operator<<(std::ostream &output, REFIID iid)
548{
549#ifdef _WIN32
550 OLECHAR* guidString;
551 StringFromCLSID(iid, &guidString);
552 char buffer[128];
553 int ret = wcstombs(buffer, guidString, sizeof buffer);
554 if (ret == sizeof buffer) {
555 buffer[sizeof buffer - 1] = '\0';
556 }
557 output << buffer;
558 ::CoTaskMemFree(guidString);
559#else
560 auto flags = output.flags();
561 output << hex << uppercase << setfill('0') <<
562 setw(2) << static_cast<int>(iid.byte0) << setw(2) << static_cast<int>(iid.byte1) <<
563 setw(2) << static_cast<int>(iid.byte2) << setw(2) << static_cast<int>(iid.byte3) << "-" <<
564 setw(2) << static_cast<int>(iid.byte4) << setw(2) << static_cast<int>(iid.byte5) << "-" <<
565 setw(2) << static_cast<int>(iid.byte6) << setw(2) << static_cast<int>(iid.byte7) << "-" <<
566 setw(2) << static_cast<int>(iid.byte8) << setw(2) << static_cast<int>(iid.byte9) << "-" <<
567 setw(2) << static_cast<int>(iid.byte10) << setw(2) << static_cast<int>(iid.byte11) <<
568 setw(2) << static_cast<int>(iid.byte12) << setw(2) << static_cast<int>(iid.byte13) <<
569 setw(2) << static_cast<int>(iid.byte14) << setw(2) << static_cast<int>(iid.byte15);
570 output.flags(flags);
571#endif
572 return output;
573}
574
575static string fcc_to_string(uint32_t fourcc) {
576#define BMDFCC(x) {x,#x}
577 static const unordered_map<uint32_t, const char *> conf_name_map = {
578 BMDFCC(bmdVideo3DPackingSidebySideHalf), BMDFCC(bmdVideo3DPackingLinebyLine), BMDFCC(bmdVideo3DPackingTopAndBottom), BMDFCC(bmdVideo3DPackingFramePacking), BMDFCC(bmdVideo3DPackingRightOnly), BMDFCC(bmdVideo3DPackingLeftOnly),
579 BMDFCC(bmdDeckLinkCapturePassthroughModeDisabled),
580 BMDFCC(bmdDeckLinkCapturePassthroughModeCleanSwitch),
581 BMDFCC(bmdDeckLinkConfig444SDIVideoOutput),
582 BMDFCC(bmdDeckLinkConfigAnalogAudioConsumerLevels),
583 BMDFCC(bmdDeckLinkConfigCapture1080pAsPsF),
584 BMDFCC(bmdDeckLinkConfigCapturePassThroughMode),
585 BMDFCC(bmdDeckLinkConfigFieldFlickerRemoval),
586 BMDFCC(bmdDeckLinkConfigLowLatencyVideoOutput),
587 BMDFCC(bmdDeckLinkConfigHDMI3DPackingFormat),
588 BMDFCC(bmdDeckLinkConfigOutput1080pAsPsF),
589 BMDFCC(bmdDeckLinkConfigQuadLinkSDIVideoOutputSquareDivisionSplit),
590 BMDFCC(bmdDeckLinkConfigSDIOutputLinkConfiguration),
591 BMDFCC(bmdDeckLinkConfigSMPTELevelAOutput),
592 BMDFCC(bmdDeckLinkConfigVideoInputConnection),
593 BMDFCC(bmdDeckLinkConfigVideoInputConversionMode),
594 BMDFCC(bmdDeckLinkConfigVideoOutputConversionMode),
595 BMDFCC(bmdDeckLinkConfigVideoOutputIdleOperation),
596 BMDFCC(bmdIdleVideoOutputBlack),
597 BMDFCC(bmdIdleVideoOutputLastFrame),
598 BMDFCC(bmdLinkConfigurationSingleLink), BMDFCC(bmdLinkConfigurationDualLink), BMDFCC(bmdLinkConfigurationQuadLink),
599 };
600#undef BMDFCC
601 if (auto it = conf_name_map.find(fourcc); it != conf_name_map.end()) {
602 return it->second;
603 }
604 union {
605 char c[5];
606 uint32_t i;
607 } fcc{};
608 fcc.i = htonl(fourcc);
609 return string("'") + fcc.c + "'";
610}
611
612bmd_option::bmd_option(bool val, bool user_spec) : m_type(type_tag::t_flag), m_user_specified(user_spec) {
613 m_val.b = val;
614}
615
616bmd_option::bmd_option(int64_t val, bool user_spec) : m_type(type_tag::t_int), m_user_specified(user_spec) {
617 m_val.i = val;
618}
619
620std::ostream &operator<<(std::ostream &output, const bmd_option &b) {
621 switch (b.m_type) {
623 output << "(default)";
624 break;
626 output << "(keep)";
627 break;
629 output << (b.get_flag() ? "true" : "false");
630 break;
632 if (IS_FCC(b.get_int())) {
633 output << fcc_to_string(b.get_int());
634 } else if (b.get_int() >= 0) {
635 auto flags = output.flags();
636 output << b.get_int() << " (0x" << hex
637 << b.get_int() << ")";
638 output.flags(flags);
639 } else {
640 output << b.get_int();
641 }
642 break;
644 auto flags = output.flags();
645 output << fixed << b.m_val.f;
646 output.flags(flags);
647 break;
648 }
650 output << b.m_val.s;
651 break;
652 }
653 return output;
654}
655
656void bmd_option::set_flag(bool val_) {
657 m_val.b = val_;
659}
660void bmd_option::set_int(int64_t val_) {
661 m_val.i = val_;
663 m_user_specified = true;
664}
665void bmd_option::set_float(double val_) {
666 m_val.f = val_;
668 m_user_specified = true;
669}
670void bmd_option::set_string(const char *val_) {
671 strncpy(m_val.s, val_, sizeof m_val.s - 1);
673 m_user_specified = true;
674}
678bool bmd_option::keep() const {
679 return m_type == type_tag::t_keep;
680}
682 if (m_type != type_tag::t_flag) {
683 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Option is not set to a flag but get_flag() called! Current type tag: %d\n", (int) m_type);
684 return {};
685 }
686 return m_val.b;
687}
688int64_t bmd_option::get_int() const {
689 if (m_type != type_tag::t_int) {
690 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Option is not set to an int but get_int() called! Current type tag: %d\n", (int) m_type);
691 return {};
692 }
693 return m_val.i;
694}
695const char *bmd_option::get_string() const {
696 if (m_type != type_tag::t_string) {
697 MSG(WARNING,
698 "Option is not set to a string but get_string() called! Current "
699 "type tag: %d\n",
700 (int) m_type);
701 return {};
702 }
703 return m_val.s;
704}
706 return m_type == type_tag::t_default;
707}
709 return m_type == type_tag::t_string &&
710 strcmp(get_string(), "help") == 0;
711}
713 return m_user_specified;
714}
719void bmd_option::parse(const char *val)
720{
721 // check flag
722#ifdef __clang__
723#pragma clang diagnostic push
724#pragma clang diagnostic ignored "-Wnull-pointer-arithmetic"
725#endif // defined __clang__
726 if (val == nullptr || val == static_cast<char *>(nullptr) + 1 // allow constructions like parse_bmd_flag(strstr(opt, '=') + 1)
727 || strlen(val) == 0 || strcasecmp(val, "true") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0) {
728 set_flag(true);
729 return;
730 }
731#ifdef __clang__
732#pragma clang diagnostic pop
733#endif // defined __clang
734 if (strcasecmp(val, "false") == 0 || strcasecmp(val, "off") == 0 || strcasecmp(val, "no") == 0) {
735 set_flag(false);
736 return;
737 }
738
739 if (strcasecmp(val, "keep") == 0) {
740 set_keep();
741 return;
742 }
743
744 if (strcmp(val, "help") == 0) {
746 return;
747 }
748
749 // explicitly typed (either "str" or 'fcc')
750 if ((val[0] == '"' && val[strlen(val) - 1] == '"') || (val[0] == '\'' && val[strlen(val) - 1] == '\'')) {
751 string raw_val(val + 1);
752 raw_val.erase(raw_val.length() - 1, 1);
753 if (val[0] == '"') {
754 set_string(raw_val.c_str());
755 } else {
756 set_int(bmd_read_fourcc(raw_val.c_str()));
757 }
758 return;
759 }
760
761 // check number
762 bool is_number = true;
763 bool decimal_point = false;
764 for (size_t i = 0; i < strlen(val); ++i) {
765 if (val[i] == '.') {
766 if (decimal_point) { // there was already decimal point
767 is_number = false;
768 break;
769 }
770 decimal_point = true;
771 continue;
772 }
773 is_number = is_number && isxdigit(val[i]);
774 if (i == 0 && (val[i] == '-' || val[i] == '+')) {
775 is_number = true;
776 }
777 }
778 if (is_number) {
779 if (decimal_point) {
780 set_float(stod(val));
781 } else {
782 set_int(stoi(val, nullptr, 0));
783 }
784 return;
785 }
786 if (strlen(val) <= 4) {
788 return;
789 }
790
792}
793
794bool bmd_option::device_write(IDeckLinkConfiguration *deckLinkConfiguration, BMDDeckLinkConfigurationID opt, string const &log_prefix) const {
795 HRESULT res = E_FAIL;
796 switch (m_type) {
797 case type_tag::t_flag:
798 res = deckLinkConfiguration->SetFlag(opt, get_flag());
799 break;
800 case type_tag::t_int:
801 res = deckLinkConfiguration->SetInt(opt, get_int());
802 break;
804 res = deckLinkConfiguration->SetFloat(opt, m_val.f);
805 break;
806 case type_tag::t_string: {
808 res = deckLinkConfiguration->SetString(opt, s);
810 break;
811 }
812 case type_tag::t_keep:
814 return true;
815 }
816 ostringstream value_oss;
817 if (opt == bmdDeckLinkConfigVideoInputConnection && get_connection_string_map().find((BMDVideoConnection) get_int()) != get_connection_string_map().end()) {
818 value_oss << get_connection_string_map().at((BMDVideoConnection) get_int());
819 } else {
820 value_oss << *this;
821 }
822 if (res != S_OK) {
823 const int lvl = m_user_specified ? LOG_LEVEL_ERROR
824 : res == E_NOTIMPL ? LOG_LEVEL_INFO
826 LOG(lvl) << log_prefix << "Unable to set key "
827 << fcc_to_string(opt) << " to " << value_oss.str()
828 << ": " << bmd_hresult_to_string(res) << "\n";
829 return !m_user_specified;
830 }
831 LOG(LOG_LEVEL_INFO) << log_prefix << fcc_to_string(opt) << " set to: " << value_oss.str() << "\n";
832 return true;
833}
834
835static void apply_r10k_lut(void *i, void *o, size_t len, void *udata)
836{
837 auto lut = (const unsigned int * __restrict) udata;
838 auto *in = (const unsigned char *) i;
839 auto *out = (unsigned char *) o;
840 const unsigned char *in_end = in + len;
841 while (in < in_end) {
842 unsigned r = in[0] << 2U | in[1] >> 6U;
843 unsigned g = (in[1] & 0x3FU) << 4U | in[2] >> 4U;
844 unsigned b = (in[2] & 0xFU) << 6U | in[3] >> 2U;
845 r = lut[r];
846 g = lut[g];
847 b = lut[b];
848 out[0] = r >> 2U;
849 out[1] = (r & 0x3U) << 6U | (g >> 4U);
850 out[2] = (g & 0xFU) << 4U | (b >> 6U);
851 out[3] = (b & 0x3FU) << 2U;
852 in += 4;
853 out += 4;
854 }
855}
856
857static void fill_limited_to_full_lut(unsigned int *lut) {
858 for (int i = 0; i < 1024; ++i) {
859 int val = clamp(i, 64, 960);
860 val = 4 + (val - 64) * 1015 / 896;
861 lut[i] = val;
862 }
863}
869void r10k_limited_to_full(const char *in, char *out, size_t len)
870{
871 static unsigned int lut[1024];
872 if (lut[1023] == 0) {
874 }
876 respawn_parallel(const_cast<char *>(in), out, len / 4, 4, apply_r10k_lut, lut);
878}
879
880static void fill_full_to_limited_lut(unsigned int *lut) {
881 for (int i = 0; i < 1024; ++i) {
882 int val = clamp(i, 4, 1019);
883 val = 64 + (val - 4) * 896 / 1015;
884 lut[i] = val;
885 }
886}
892void r10k_full_to_limited(const char *in, char *out, size_t len)
893{
894 static unsigned int lut[1024];
895 if (lut[1023] == 0) {
897 }
899 respawn_parallel(const_cast<char *>(in), out, len / 4, 4, apply_r10k_lut, lut);
901}
902
903string bmd_get_audio_connection_name(BMDAudioOutputAnalogAESSwitch audioConnection) {
904 switch(audioConnection) {
905 case bmdAudioOutputSwitchAESEBU:
906 return "AES/EBU";
907 case bmdAudioOutputSwitchAnalog:
908 return "analog";
909 default:
910 return "default";
911 }
912}
913
914string bmd_get_flags_str(BMDDisplayModeFlags flags) {
915 bool first = true;
916 ostringstream oss;
917 vector<pair<uint32_t, const char *>> map {
918 { bmdDisplayModeColorspaceRec601, "Rec601" },
919 { bmdDisplayModeColorspaceRec709, "Rec709" },
920 { bmdDisplayModeColorspaceRec2020, "Rec2020" },
921 { bmdDisplayModeSupports3D, "3D" },
922 };
923
924 for (auto &f : map ) {
925 if (flags & f.first) {
926 oss << (!first ? ", " : "") << f.second;
927 first = false;
928 }
929 }
930 if (flags == 0) {
931 oss << "(none)";
932 }
933 if (flags >= bmdDisplayModeColorspaceRec2020 << 1) {
934 oss << ", (unknown flags)";
935 }
936 return oss.str();
937}
938
939void print_bmd_device_profiles(const char *line_prefix)
940{
941 for (const auto &p : kDeviceProfiles) {
942 const uint32_t fcc = htonl(p.first);
943 color_printf("%s" TBOLD("%.4s") " - %s (%s)\n", line_prefix, (const char *) &fcc, p.second.first, p.second.second);
944 }
945 color_printf("%s" TBOLD("keep") " - keep device setting\n", line_prefix);
946}
947
948const map<BMDVideoConnection, string> &get_connection_string_map() {
949 static const map<BMDVideoConnection, string> m = {
950 { bmdVideoConnectionSDI, "SDI" },
951 { bmdVideoConnectionHDMI, "HDMI"},
952 { bmdVideoConnectionOpticalSDI, "OpticalSDI"},
953 { bmdVideoConnectionComponent, "Component"},
954 { bmdVideoConnectionComposite, "Composite"},
955 { bmdVideoConnectionSVideo, "SVideo"}
956 };
957 return m;
958}
959
960template <typename T> struct bmd_no_conv {
961};
962template <> struct bmd_no_conv<IDeckLinkInput> {
963 static constexpr BMDVideoInputConversionMode value =
964 bmdNoVideoInputConversion;
965};
966template <> struct bmd_no_conv<IDeckLinkOutput> {
967 static constexpr BMDVideoOutputConversionMode value =
968 bmdNoVideoOutputConversion;
969};
977template <typename T>
978bool
979decklink_supports_codec(T *deckLink, BMDPixelFormat pf)
980{
981 IDeckLinkDisplayModeIterator *displayModeIterator = nullptr;
982 IDeckLinkDisplayMode *deckLinkDisplayMode = nullptr;
983
984 if (FAILED(
985 deckLink->GetDisplayModeIterator(&displayModeIterator))) {
986 MSG(ERROR, "Fatal: cannot create display mode iterator.\n");
987 return false;
988 }
989
990 while (displayModeIterator->Next(&deckLinkDisplayMode) == S_OK) {
991 BMD_BOOL supported = false;
992 const HRESULT res = deckLink->DoesSupportVideoMode(
993 bmdVideoConnectionUnspecified,
994 deckLinkDisplayMode->GetDisplayMode(), pf,
995 bmd_no_conv<T>::value, bmdSupportedVideoModeDefault,
996 nullptr, &supported);
997 deckLinkDisplayMode->Release();
998 if (res != S_OK) {
999 MSG(WARNING, "DoesSupportVideoMode: %s\n",
1000 bmd_hresult_to_string(res).c_str());
1001 continue;
1002 }
1003 if (supported) {
1004 displayModeIterator->Release();
1005 return true;
1006 }
1007 }
1008 displayModeIterator->Release();
1009
1010 return false;
1011}
1012template bool
1013decklink_supports_codec<IDeckLinkOutput>(IDeckLinkOutput *deckLink,
1014 BMDPixelFormat pf);
1015template bool decklink_supports_codec<IDeckLinkInput>(IDeckLinkInput *deckLink,
1016 BMDPixelFormat pf);
1017
1019bool
1020bmd_parse_audio_levels(const char *opt) noexcept(false)
1021{
1022 MSG(WARNING, "audio_level option is deprecated, use "
1023 "\"aacl[=true|false]\" instead\n");
1024 if (strcasecmp(opt, "false") == 0 || strcasecmp(opt, "off") == 0 ||
1025 strcasecmp(opt, "line") == 0) {
1026 return false;
1027 }
1028 if (strcasecmp(opt, "true") == 0 || strcasecmp(opt, "on") == 0 ||
1029 strcasecmp(opt, "mic") == 0) {
1030 return true;
1031 }
1032 throw invalid_argument(string("invalid BMD audio level ") + opt);
1033}
1034
1035void
1036print_bmd_attribute(IDeckLinkProfileAttributes *deckLinkAttributes,
1037 const char *query_prop_fcc)
1038{
1039 if (strcmp(query_prop_fcc, "help") == 0) {
1040 col{} << "Query usage:\n"
1041 << SBOLD("\tq[uery]=<fcc>")
1042 << " - gets DeckLink attribute value\n";
1043 return;
1044 }
1045 union {
1046 char fcc[sizeof(BMDDeckLinkAttributeID)] = { ' ', ' ', ' ',
1047 ' ' };
1048 BMDDeckLinkAttributeID key;
1049 };
1050 memcpy(fcc, query_prop_fcc, min(strlen(query_prop_fcc), sizeof fcc));
1051 key = (BMDDeckLinkAttributeID) htonl(key);
1052 BMD_BOOL bool_val{};
1053 int64_t int_val{};
1054 double float_val{};
1055 BMD_STR string_val{};
1056 ostringstream oss;
1057 HRESULT result = deckLinkAttributes->GetFlag(key, &bool_val);
1058 if (result == S_OK) {
1059 oss << (bool_val ? "true" : "false");
1060 }
1061 if (result == E_INVALIDARG) {
1062 result = deckLinkAttributes->GetInt(key, &int_val);
1063 if (result == S_OK) {
1064 oss << int_val << " (0x" << hex << int_val << ")";
1065 }
1066 }
1067 if (result == E_INVALIDARG) {
1068 result = deckLinkAttributes->GetFloat(key, &float_val);
1069 if (result == S_OK) {
1070 oss << float_val;
1071 }
1072 }
1073 if (result == E_INVALIDARG) {
1074 result = deckLinkAttributes->GetString(key, &string_val);
1075 if (result == S_OK) {
1076 string str = get_str_from_bmd_api_str(string_val);
1077 release_bmd_api_str(string_val);
1078 oss << str;
1079 }
1080 }
1081 if (result != S_OK) {
1083 << MOD_NAME << "Cannot get " << query_prop_fcc << ": "
1084 << bmd_hresult_to_string(result) << "\n";
1085 return;
1086 }
1087 col() << "\t" << hex << "Value of " << SBOLD(query_prop_fcc)
1088 << " attribute for this device is " << SBOLD(oss.str()) << "\n";
1089}
1090
1105std::vector<bmd_dev>
1106bmd_get_sorted_devices(bool *com_initialized, bool verbose, bool natural_sort)
1107{
1108 IDeckLinkIterator *deckLinkIterator =
1109 create_decklink_iterator(com_initialized, verbose);
1110 if (deckLinkIterator == nullptr) {
1111 return {};
1112 }
1113
1114 IDeckLink *deckLink = nullptr;
1115 std::vector<bmd_dev> out;
1116 int idx = 0;
1117 while (deckLinkIterator->Next(&deckLink) == S_OK) {
1118 IDeckLinkProfileAttributes *deckLinkAttributes = nullptr;
1119 HRESULT result = E_FAIL;
1120 result =
1121 deckLink->QueryInterface(IID_IDeckLinkProfileAttributes,
1122 (void **) &deckLinkAttributes);
1123 assert(result == S_OK);
1124 int64_t id = 0;
1125 result =
1126 deckLinkAttributes->GetInt(BMDDeckLinkTopologicalID, &id);
1127 if (result != S_OK) {
1128 id = UINT_MAX - idx;
1129 }
1130 assert(id >= 0 && id <= UINT_MAX);
1131 deckLinkAttributes->Release();
1132
1133 auto release = [](IDeckLink *d) { d->Release(); };
1134 auto &it = out.emplace_back(
1135 std::unique_ptr<IDeckLink, void (*)(IDeckLink *)>{
1136 deckLink, release },
1137 0, 0, 0);
1138 std::get<unsigned>(it) = id;
1139 std::get<int>(it) = idx++;
1140 }
1141 deckLinkIterator->Release();
1142 if (!natural_sort) {
1143 std::sort(out.begin(), out.end(), [](bmd_dev &a, bmd_dev &b) {
1144 return std::get<unsigned>(a) < std::get<unsigned>(b);
1145 });
1146 }
1147 // assign new indices
1148 char new_idx = 'a';
1149 for (auto &d : out) {
1150 std::get<char>(d) = new_idx++;
1151 }
1152 return out;
1153}
1154
1156 " Do not do conversion from/to limited range on in/out for R10k on BMD devs.\n");
1158 " Use the old BMD device sorting.\n");
1159
int void * val
int void size_t * len
const char unsigned int flags
#define MOD_NAME
Definition audio.cpp:110
static const map< BMDProfileID, pair< const char *, const char * > > kDeviceProfiles
bool bmd_parse_audio_levels(const char *opt) noexcept(false)
TOREMOVE (implicit "aacl" option is preferred)
static BMDProfileID GetDeckLinkProfileID(IDeckLinkProfile *profile)
BMD_STR get_bmd_api_str_from_cstr(const char *cstr)
template bool decklink_supports_codec< IDeckLinkOutput >(IDeckLinkOutput *deckLink, BMDPixelFormat pf)
static void fill_limited_to_full_lut(unsigned int *lut)
static void apply_r10k_lut(void *i, void *o, size_t len, void *udata)
#define BMDFCC(x)
bool blackmagic_api_version_check()
string bmd_get_flags_str(BMDDisplayModeFlags flags)
const map< BMDVideoConnection, string > & get_connection_string_map()
uint32_t bmd_read_fourcc(const char *str)
void print_bmd_device_profiles(const char *line_prefix)
static BMDProfileID decklink_get_active_profile_id(IDeckLink *decklink)
static string fcc_to_string(uint32_t fourcc)
std::ostream & operator<<(std::ostream &output, REFIID iid)
IDeckLinkIterator * create_decklink_iterator(bool *com_initialized, bool verbose)
void release_bmd_api_str(BMD_STR string)
void print_bmd_attribute(IDeckLinkProfileAttributes *deckLinkAttributes, const char *query_prop_fcc)
string bmd_hresult_to_string(HRESULT res)
string bmd_get_device_name(IDeckLink *decklink)
string bmd_get_audio_connection_name(BMDAudioOutputAnalogAESSwitch audioConnection)
#define MOD_NAME
void decklink_uninitialize(bool *com_initialized)
#define EXIT_IF_FAILED(cmd, name)
bool decklink_set_profile(IDeckLink *deckLink, bmd_option const &req_profile, bool stereo)
std::string get_str_from_bmd_api_str(BMD_STR string)
void r10k_full_to_limited(const char *in, char *out, size_t len)
bool bmd_check_stereo_profile(IDeckLink *deckLink)
template bool decklink_supports_codec< IDeckLinkInput >(IDeckLinkInput *deckLink, BMDPixelFormat pf)
#define RELEASE_IF_NOT_NULL(x)
std::vector< bmd_dev > bmd_get_sorted_devices(bool *com_initialized, bool verbose, bool natural_sort)
static void fill_full_to_limited_lut(unsigned int *lut)
bool decklink_supports_codec(T *deckLink, BMDPixelFormat pf)
void print_decklink_version()
char * get_cstr_from_bmd_api_str(BMD_STR bmd_string)
void r10k_limited_to_full(const char *in, char *out, size_t len)
BMD_STR get_bmd_api_str_from_cstr(const char *cstr)
#define R10K_FULL_OPT
#define BMD_BOOL
#define BMD_STR
const std::map< BMDVideoConnection, std::string > & get_connection_string_map()
#define BMD_CHECK(cmd, name, action)
std::string bmd_hresult_to_string(HRESULT res)
#define BMD_NAT_SORT
void release_bmd_api_str(BMD_STR string)
std::tuple< std::unique_ptr< IDeckLink, void(*)(IDeckLink *)>, unsigned, int, char > bmd_dev
#define BMD_FALSE
uint32_t bmd_read_fourcc(const char *)
HRESULT ProfileActivated(IDeckLinkProfile *activatedProfile) override
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) override
IDeckLinkProfile * m_requestedProfile
std::condition_variable m_profileActivatedCondition
HRESULT ProfileChanging(IDeckLinkProfile *profileToBeActivated, BMD_BOOL streamsWillBeForcedToStop) override
bool WaitForProfileActivation(void)
ULONG STDMETHODCALLTYPE Release() override
ULONG STDMETHODCALLTYPE AddRef() override
std::mutex m_profileActivatedMutex
ProfileCallback(IDeckLinkProfile *requestedProfile)
int color_printf(const char *format,...)
#define SBOLD(x)
Definition color_out.h:80
#define TBOLD(x)
Definition color_out.h:70
static __device__ vec2 clamp(const vec2 &v, float min_val, float max_val)
Definition cuda_dxt.cu:110
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define DEBUG_TIMER_START(name)
Definition debug.h:324
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define DEBUG_TIMER_STOP(name)
Definition debug.h:325
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define MSG(l, fmt,...)
Definition debug.h:96
#define LOG(level)
Definition debug.h:313
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
#define ADD_TO_PARAM(param, doc)
Definition host.h:203
#define IS_FCC(val)
Definition macros.h:116
#define min(a, b)
Definition rtp.c:99
codec_t first
Definition sdl2.c:423
static void cleanup()
Definition sdp.c:195
int64_t get_int() const
bmd_option()=default
const char * get_string() const
bool is_user_set() const
void set_string(const char *val_)
enum bmd_option::type_tag m_type
bool m_user_specified
ignore errors if set to false
void set_int(int64_t val_)
void parse(const char *)
bool keep() const
bool is_help() const
bool get_flag() const
bool is_default() const
bool device_write(IDeckLinkConfiguration *deckLinkConfiguration, BMDDeckLinkConfigurationID opt, std::string const &log_prefix="[DeckLink] ") const
void set_flag(bool val_)
union bmd_option::@11 m_val
void set_float(double val_)
const char * key
Definition cineform.cpp:125
@ FAILED
bool com_initialize(bool *com_initialized, const char *err_prefix)
Definition windows.c:65
void com_uninitialize(bool *com_initialized)
Definition windows.c:94
#define HRESULT_GET_ERROR_COMMON(res, errptr)
Definition windows.h:60
void respawn_parallel(void *in, void *out, size_t nmemb, size_t size, respawn_parallel_callback_t c, void *udata)
Definition worker.cpp:332